Сверните несколько строк временных меток в одну

#python #pandas

#python #панды

Вопрос:

У меня есть такая серия:

 s = pd.DataFrame({'ts': [1, 2, 3, 6, 7, 11, 12, 13]})
s

    ts
0   1
1   2
2   3
3   6
4   7
5   11
6   12
7   13
 

Я хотел бы свернуть строки, разница между которыми меньше, чем MAX_DIFF (2) . Это означает, что желаемый результат должен быть:

 [{'ts_from': 1, 'ts_to': 3},
 {'ts_from': 6, 'ts_to': 7},
 {'ts_from': 11, 'ts_to': 13}]
 

Я сделал некоторое кодирование:

 s['close'] = s.diff().shift(-1)
s['close'] = s[s['close'] > MAX_DIFF].astype('bool')
s['close'].iloc[-1] = True

parts = []
ts_from = None

for _, row in s.iterrows():
    if row['close'] is True:
        part = {'ts_from': ts_from, 'ts_to': row['ts']}
        parts.append(part)
        ts_from = None
        continue
    
    if not ts_from:
        ts_from = row['ts']
 

Это работает, но не кажется оптимальным из-за iterrows() . Я думал о рангах, но не мог понять, как их реализовать, чтобы группировать по рангу дальше.

Есть ли способ оптимизировать алгоритм?

Комментарии:

1. Можете ли вы уточнить один момент. Если в начале были дополнительные строки, так что это было 1,2,3,4,5,6,7,11,12,13, группируются ли 1-7 вместе или они разделяются на несколько групп, например 1-3, 4-6, 7-7(?), ?

2. 1-7 будут сгруппированы вместе

Ответ №1:

Вы можете создавать группы, проверяя, где разница превышает ваш порог, и получать итоговую сумму. Тогда действуйте так, как вам хотелось бы, возможно first , и last в этом случае.

 gp = s['ts'].diff().abs().ge(2).cumsum().rename(None)
res = s.groupby(gp).agg(ts_from=('ts', 'first'),
                        ts_to=('ts', 'last'))
#   ts_from  ts_to
#0        1      3
#1        6      7
#2       11     13
 

И если вам нужен список dicts, тогда:

 res.to_dict('records')
#[{'ts_from': 1, 'ts_to': 3},
# {'ts_from': 6, 'ts_to': 7},
# {'ts_from': 11, 'ts_to': 13}]
 

Для полноты картины вот как группировщик выравнивается с фреймом данных:

 s['gp'] = gp
print(s)

   ts  gp
0   1   0     # `1` becomes ts_from for group 0
1   2   0
2   3   0     # `3` becomes ts_to for group 0
3   6   1     # `6` becomes ts_from for group 1
4   7   1     # `7` becomes ts_to for group 1
5  11   2     # `11` becomes ts_from for group 2
6  12   2
7  13   2     # `13` becomes ts_to for group 2
 

Комментарии:

1. Можете ли вы объяснить вторую строку более подробно?

2. @ggaurav итак, первая часть является базовой groupby , я создаю серию, которая идентифицирует группы, и pandas затем применяет вычисления внутри этой группы. Все, что после agg , является более поздним синтаксическим добавлением namedAggregations . Это полезный синтаксис, который позволяет выполнять несколько агрегаций в одном столбце и переименовывать выходные данные (без создания мультииндекса для столбцов). Итак, в этом случае я создаю столбцы вывода с именами ts_from и ts_to, которые, соответственно, являются первым и последним значениями 'ts' столбца в каждой группе

3. Спасибо! Узнал немало вещей. Например, я не знал, что для использования group by столбец не обязательно должен быть частью таблицы, а также называться агрегациями.

4. Подход правильный. Но это не сработает, если в серии есть последовательные числа, например, от 1 до 8 в серии

5. @ggaurav оОо. Я полагаю, что тогда я неправильно понял вопрос. Я понимаю вашу точку зрения, в этом случае это гораздо более сложная проблема, и я не думаю, что есть какой-либо возможный способ сделать это без итерации, поскольку группировка более поздних строк зависит от того, что было раньше.