在了解了如何解决问题后,我找到了一个解决方案
agg
/
aggregate
作品我无法完全避免循环,但至少它只是在聚合块上循环,而其余的是矢量化的。
只要满足沿轴1连接的一般形状和索引要求,此解决方案应适用于大多数类型的输入/类型。
def align_non_overlapping(ser_maj, ser_min, full_output=False):
minor_colname = ( # backup name of minor if available
ser_min.name if isinstance(ser_min, pd.Series)
else ser_min.column if isinstance(ser_min, pd.DataFrame) else 'minor')
# merge both series in df
df = pd.DataFrame({'major': ser_maj, 'minor': ser_min})
df_idx = df.index # backup and drop index for easy general shifting
df.reset_index(drop=True, inplace=True)
# make mask where values are not overlapping
df['mask_no'] = np.where((df.minor != 0.) & (df.major == 0.), 1, 0)
# make mask where minor values are not zero
df['mask_nz'] = (df.minor != 0).astype(int)
# get blocks of consecutive non-zero values in minor
df['block'] = (
df['mask_nz'].shift(1) != df['mask_nz']).astype(int).cumsum()
# set block to zero where minor is zero
df.loc[df['minor'] == 0., 'block'] = 0
# set block to zero where
# generate shifting information. summing of mask_no gives amount of non
# overlapping rows (n rows to shift), index min and max gives start and
# end indices of blocks of non zero blocks. reset index to be able to apply
# aggregate function on index
shifter = df.reset_index().groupby('block').agg(
{'mask_no': 'sum',
'index': {'index_start': 'min', 'index_stop': 'max'}})
# drop first level of MultiIndex for easier indexing
shifter.columns = shifter.columns.droplevel(0)
# loop over blocks and shift each block by aggregated values
for blck in shifter.index:
if shifter.loc[blck, 'sum'] == 0: # skip all zero blocks/shifts
continue
n_shift, istart, iend = shifter.loc[blck] # extract shifting bounds
df.loc[istart - n_shift:iend, 'minor'] = np.roll( # roll window
df.loc[istart - n_shift:iend, 'minor'], -n_shift)
df.set_index(df_idx, inplace=True) # set backup index
df.rename(columns={'minor': minor_colname}, inplace=True) # set old name
if not full_output: # return full output with information only if required
return df[minor_colname]
else:
return df
测试结果与预期结果对比:
ser_maj = pd.Series([0,0,0,0,0,0,0,0,4,4,4,4,4,5,6,7,0,0,0,0,4,3,5,6,4,0,0,0], name='major')
ser_min = pd.Series([0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,1,.9,0], name='minor')
minor_aligned = [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,1,.9,0,0,0]
print(align_non_overlapping(ser_maj, ser_min, full_output=False) == minor_aligned).all())
# Out: True
这些条件目前被忽略:
-
小调只能是
> 0
哪里
major > 0
-
小调中的非零块最多可以与大调中的对应块一样长,但不能更长。它通常比大调的街区要短得多
但两者都可以很容易地用
df[df.major == 0] = 0
.