Как сгруппировать строки в течение периода времени с помощью Python

У меня есть DataFrame некоторых транзакций. Я хочу сгруппировать эти транзакции по значениям их столбцов item и time: цель состоит в том, чтобы сгруппировать элементы, которые находятся в пределах 1 часа друг от друга. Таким образом, мы начинаем новую группу во время следующего наблюдения, которое не было в пределах часа предыдущего наблюдения (см. столбец start time в DataFrame B).

Вот данные: я хочу преобразовать A в B.

A=
item    time             result
A   2016-04-18 13:08:25  Y
A   2016-04-18 13:57:05  N
A   2016-04-18 14:00:12  N
A   2016-04-18 23:45:50  Y
A   2016-04-20 16:53:48  Y
A   2016-04-20 17:11:47  N
B   2016-04-18 15:24:48  N
C   2016-04-23 13:20:44  N
C   2016-04-23 14:02:23  Y


B=
item    start time            end time      Ys  Ns  total count
A   2016-04-18 13:08:25 2016-04-18 14:08:25 1   2   3
A   2016-04-18 23:45:50 2016-04-18 00:45:50 1   0   1
A   2016-04-20 16:53:48 2016-04-20 17:53:48 1   1   2
B   2016-04-18 15:24:48 2016-04-18 16:24:48 0   1   1
C   2016-04-23 13:20:44 2016-04-23 14:20:44 1   1   2

Вот что я сделал:

grouped = A.groupby('item')
A['end'] = (grouped['time'].transform(lambda grp: grp.min()+pd.Timedelta(hours=1)))
A2 = A.loc[(A['time'] <= A['end'])]

Это дает мне одну группу в день: транзакция в течение 1 часа после первой транзакции. Итак, я пропускаю другие транзакции в тот же день, но с интервалом более 1 часа от первой. Моя борьба заключается в том, как получить эти группы. Затем я могу использовать pd.crosstab для получения нужных сведений из столбца result.

У меня есть еще одна идея: отсортировать A по item и time, а затем пройтись по строкам. Если время находится в пределах 1 часа от предыдущей строки, оно добавляется в эту группу, в противном случае создается новая группа.


person Ana    schedule 11.05.2016    source источник
comment
Есть много вопросов, оставшихся без ответа. Например, сгруппированы в течение одного часа после того, когда? Один час первого наблюдения? Что насчет следующего часа? Он начинается, когда закончился последний час? Или мы начинаем новый час во время следующего наблюдения, которое не было в пределах часа предыдущего наблюдения?   -  person piRSquared    schedule 11.05.2016
comment
что такое grouped в вашем коде? Как ты получил это?   -  person MaxU    schedule 11.05.2016
comment
@piRSquared Я добавил больше деталей к вопросу, чтобы уточнить.   -  person Ana    schedule 11.05.2016
comment
@MaxU Я сгруппировал по элементам, я добавил это к вопросу.   -  person Ana    schedule 11.05.2016


Ответы (3)


1) Настройте столбец window_end для последующего использования с .groupby() и определите .get_windows() для проверки для каждой группы item, соответствует ли row текущему 1-часовому окну, или ничего не делайте и сохраните инициализированное значение. Применить ко всем item группам:

df['window_end'] = df.time + pd.Timedelta('1H')

def get_windows(data):
    window_end = data.iloc[0].window_end
    for index, row in data.iloc[1:].iterrows():
        if window_end > row.time:
            df.loc[index, 'window_end'] = window_end
        else:
            window_end = row.window_end

df.groupby('item').apply(lambda x: get_windows(x))

2) Используйте windows и item с .groupby() и верните .value_counts() как transposed DataFrame, очистите index и добавьте total:

df = df.groupby(['window_end', 'item']).result.apply(lambda x: x.value_counts().to_frame().T)
df = df.fillna(0).astype(int).reset_index(level=2, drop=True)
df['total'] = df.sum(axis=1)

получить:

                            N  Y  total
window_end          item               
2016-04-18 14:08:25 A    A  2  1      3
2016-04-18 16:24:48 B    B  1  0      1
2016-04-19 00:45:50 A    A  0  1      1
2016-04-20 17:53:48 A    A  1  1      2
2016-04-23 14:20:44 C    C  1  1      2
person Stefan    schedule 11.05.2016
comment
Спасибо, да, к сожалению, я не могу использовать Hour в качестве окуня. - person Ana; 11.05.2016
comment
Спасибо, Пара комментариев. На втором этапе windows следует заменить на window_end, а , right? Also you may want to use another for your result` DataFrame, чтобы не ошибиться со столбцом result. - person Ana; 12.05.2016
comment
Правильно, возился с кодом во время редактирования здесь, никогда не было хорошей идеей. Должно работать сейчас. - person Stefan; 12.05.2016

вдохновленный (+1) решением Стефана, я пришел к этому:

B = (A.groupby(['item', A.groupby('item')['time']
                         .diff().fillna(0).dt.total_seconds()//60//60
               ],
               as_index=False)['time'].min()
)


B[['N','Y']] = (A.groupby(['item', A.groupby('item')['time']
                                    .diff().fillna(0).dt.total_seconds()//60//60
                          ])['result']
                 .apply(lambda x: x.value_counts().to_frame().T).fillna(0)
                 .reset_index()[['N','Y']]
)

Выход:

In [178]: B
Out[178]:
  item                time    N    Y
0    A 2016-04-18 13:08:25  3.0  1.0
1    A 2016-04-18 23:45:50  0.0  1.0
2    A 2016-04-20 16:53:48  0.0  1.0
3    B 2016-04-18 15:24:48  1.0  0.0
4    C 2016-04-23 13:20:44  1.0  1.0

PS идея состоит в том, чтобы использовать A.groupby('item')['time'].diff().fillna(0).dt.total_seconds()//60//60 как часть группировки:

In [179]: A.groupby('item')['time'].diff().fillna(0).dt.total_seconds()//60//60
Out[179]:
0     0.0
1     0.0
2     0.0
3     9.0
4    41.0
5     0.0
6     0.0
7     0.0
8     0.0
Name: time, dtype: float64
person MaxU    schedule 11.05.2016
comment
Спасибо @MaxU, я получаю ошибку AttributeError: 'TimedeltaProperties' object has no attribute 'total_seconds'. У меня есть 2_. - person Ana; 11.05.2016

Настраивать

import pandas as pd
from StringIO import StringIO

text = """item    time             result
A   2016-04-18 13:08:25  Y
A   2016-04-18 13:57:05  N
A   2016-04-18 14:00:12  N
A   2016-04-18 23:45:50  Y
A   2016-04-20 16:53:48  Y
A   2016-04-20 17:11:47  N
B   2016-04-18 15:24:48  N
C   2016-04-23 13:20:44  N
C   2016-04-23 14:02:23  Y
"""

df = pd.read_csv(StringIO(text), delimiter="\s{2,}", parse_dates=[1], engine='python')

Решение

Мне нужно было создать несколько функций процесса:

def set_time_group(df):
    cur_time = pd.NaT
    for index, row in df.iterrows():
        if pd.isnull(cur_time):
            cur_time = row.time
        delta = row.time - cur_time
        if delta.seconds / 3600. < 1:
            df.loc[index, 'time_ref'] = cur_time
        else:
            df.loc[index, 'time_ref'] = row.time
            cur_time = row.time
    return df

def summarize_results(df):
    df_ = df.groupby('result').count().iloc[:, 0]
    df_.loc['total count'] = df_.sum()
    return df_

dfg1 = df.groupby('item').apply(set_time_group)
dfg2 = dfg1.groupby(['item', 'time_ref']).apply(summarize_results)
df_f = dfg2.unstack().fillna(0)

Демонстрация

print df_f

result                      N    Y  total count
item time_ref                                  
A    2016-04-18 13:08:25  2.0  1.0          3.0
     2016-04-18 23:45:50  0.0  1.0          1.0
     2016-04-20 16:53:48  1.0  1.0          2.0
B    2016-04-18 15:24:48  1.0  0.0          1.0
C    2016-04-23 13:20:44  1.0  1.0          2.0
person piRSquared    schedule 11.05.2016