Сдвиньте неперекрывающиеся столбцы, чтобы они перекрывали / выравнивали - PullRequest
0 голосов
/ 11 января 2019

У меня есть неравномерно распределенные временные ряды, которые выполняют повторную выборку с чуть более высокой частотой (в данном случае 1min), чтобы я мог выполнить некоторые вычисления. Теперь есть один столбец с именем minor в примере, который несколько раз задерживается на несколько строк, иногда он корректно выравнивается. Мне нужно найти способ выровнять конец ненулевых блоков в 'minor' с концами ненулевых блоков в major, как показано в примере:

major = [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]
minor = [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]
# correctly aligned minor row:
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]
df = pd.DataFrame(
data={'major': major, 'minor': minor, 'minor_aligned': minor_aligned})
df.index.name = 'index'

Ожидаемый результат:
Значения в minor должны быть выровнены как в minor_aligned.

Краткое объяснение:
Последнее ненулевое значение каждого последовательного блока ненулевых значений в minor должно быть выровнено с последним ненулевым значением каждого блока в major, как показано с помощью minor_aligned. Применяются следующие дополнительные ограничения:

  • minor будет ровно 1 в 95% времени (или 0), остальное будет несколько значений между ними.
  • minor может быть только > 0, где major > 0
  • ненулевой блок в minor может быть не больше, чем соответствующий блок в major, но никогда не дольше. Это будет в основном намного короче, чем блок в major
  • Если соответствующего блока нет, minor должно быть 0. (Я не нашел такого случая, поэтому это необязательно)

Что я пробовал до сих пор:
[Из этого поста] я скопировал метод подсчета block, кроме того, я попытался реализовать некоторые маскировки и экспериментировал со всеми видами cumcount, cumsum и т. Д., Но я не смог найти решение.

df['mask_mult'] = pd.DataFrame(  # mask where shifted rows exist
    np.where((df.minor != 0.) & (df.major == 0.), 1 * df.minor, 0),
    index=df.index, columns=['outliers'])
# block counting method:
df['block'] = (df.minor.shift(1) != df.minor).astype(int).cumsum()
df.loc[:, 'block'][df['minor'] == 0] = 0  # set zero-blocks to zero

Используя groupby, категории и агрегаты (понятия не имею, как правильно их использовать), я попытался использовать маски / блоки, но у меня ничего не получилось:

# make block counting categories:
df_cat = df.set_index(pd.cut(df.block, np.arange(-1, df.block.max() + 1)))
# groupby blocks and use mask as amount of shift indices:
df_grpd = df.groupby('block').sum()

Я подумал, что можно либо перебрать все категории в df_cat, чтобы получить индексы сдвига, либо перебрать сгруппированные блоки в df_grpd, чтобы сделать то же самое (и использовать суммированную mask как число строк для сдвига) , но в обоих случаях я не получаю правильные результаты из-за значения 0.9.

Есть идеи о том, как я могу сделать это с такими значениями, как 0.9 и, если возможно, полностью избегая циклов?
Заранее спасибо!

1 Ответ

0 голосов
/ 11 января 2019

Я нашел решение после понимания того, как работает 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.

...