У меня есть таблица, которая выглядит следующим образом:

| date                | activity |
|---------------------|----------|
| 2017-03-30 01:00:00 | 1        |
| 2017-03-30 01:00:30 | 1        |
| 2017-03-30 01:01:00 | 1        |
| 2017-03-30 01:01:30 | 2        |
| 2017-03-30 01:02:00 | 2        |
| 2017-03-30 01:02:30 | 2        |
| 2017-03-30 01:03:00 | 1        |

Моя конечная цель - получить некоторую статистику для действия 1 из этого временного ряда. Для этого мне нужно начать с составления списка, который бы суммировал информацию об активности.

По сути, я хотел бы получить кортеж для каждого чанка (блок последовательных строк с меткой 1), где кортеж будет содержать дату начала чанка, а также общее количество строк в нем. Для предыдущего примера соответствующий список будет:

[(2017-03-30 01:00:00,3),(2017-03-30 01:03:00,1)]

Есть идеи о том, как этого достичь? Я также был бы открыт для других предложений, которые в конечном итоге дали бы ту же информацию.

1
mlx 24 Июн 2019 в 18:28

3 ответа

Лучший ответ

Поиск групп с одинаковым последовательным значением выполняется путем cumsum + сравнения shift. Используйте where, чтобы игнорировать группы, которые вас не интересуют.

#df = df.sort_values('date')

s = df.activity.ne(df.activity.shift(1)).cumsum()
res = df.groupby(s.where(df.activity.eq(1)).rename(None)).date.agg(['first', 'size'])

Выход:

                   first  size
1.0  2017-03-30 01:00:00     3
3.0  2017-03-30 01:03:00     1

Если вы действительно хотите список кортежей, то:

[tuple(x) for x in res.to_numpy()]
#[('2017-03-30 01:00:00', 3), ('2017-03-30 01:03:00', 1)]
2
ALollz 24 Июн 2019 в 15:57

В немного более удобоваримой, но, вероятно, менее элегантной форме для панд:

  • Сначала вы создаете некоторый идентификатор, чтобы различать различные «прогоны» активности, равные 1
  • Затем вы удаляете все записи, где активность не равна 1
  • Теперь самая сложная логика сделана, и мы можем просто использовать простой групповой

Если вы хотите получить список искомых кортежей, вы можете использовать .iterrows() в конце:

df['id'] = (df['activity'].shift(1) != df['activity']).cumsum()

inds = df['activity'] == 1
df = df.loc[inds, :]

result = df.groupby('id')['date'].agg(['min', 'size])
result
# id size   min 
#  1    3   2017-03-30 01:00:00
#  3    1   2017-03-30 01:03:00    

Для списка кортежей вы можете сделать:

[(row[1][1], row[1][0]) for row in result.iterrows()]
1
KenHBS 24 Июн 2019 в 16:14

Сначала вы можете назначить номер каждой группе, а затем использовать groupby. Первая часть не настолько питонна, но она работает:

import pandas as pd
 df = {'date': {0: '2017-03-30 01:00:00',
  1: '2017-03-30 01:00:30',
  2: '2017-03-30 01:01:00',
  3: '2017-03-30 01:01:30',
  4: '2017-03-30 01:02:00',
  5: '2017-03-30 01:02:30',
  6: '2017-03-30 01:03:00'},
 'activity': {0: 1, 1: 1, 2: 1, 3: 2, 4: 2, 5: 2, 6: 1}}

df = pd.DataFrame(df)

# add group
group = 0
groups = []
initial_value = df.iloc[0]["activity"]
for _, row in df.iterrows():
    if row["activity"]!= initial_value:
        initial_value = row["activity"]
        group +=1
    groups.append(group)

df["group"] = groups

# count and min date
out = df.groupby(["group", "activity"])\
        .agg({"date":{"min", "count"}})

out.columns = ["_".join(o) for o in out.columns]
out = out.reset_index()
0
rpanai 24 Июн 2019 в 15:50