如何使用 Python 在一个时间段内对行进行分组

Posted

技术标签:

【中文标题】如何使用 Python 在一个时间段内对行进行分组【英文标题】:How to group rows within a time period using Python 【发布时间】:2016-09-07 06:25:57 【问题描述】:

我有一些交易的DataFrame。我想根据它们的 itemtime 列值对这些事务进行分组:目标是对彼此相隔 1 小时内的项目进行分组。因此,我们在下一次观察时开始一个新组,该组不在之前观察的一小时内(参见DataFrameB 中的start time 列)。

这是数据:我想将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.crosstabresult 列中获取我想要的详细信息。

我的另一个想法是将Aitemtime 排序,然后逐行进行。如果时间在前一行的 1 小时内,则添加到该组,否则,它会创建一个新组。

【问题讨论】:

还有很多问题没有得到解答。比如,在一小时内分组?第一次观察一小时?下一个小时呢?它会在最后一小时结束时开始吗?或者我们是否在下一次观察时开始新的一小时,而不是在前一次观察的一小时内? 您的代码中的grouped 是什么?你是怎么得到它的? @piRSquared 我在问题中添加了更多细节以澄清。 @MaxU 我按项目分组,我将其添加到问题中。 【参考方案1】:

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) 将windowsitem.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

【讨论】:

谢谢,是的,很遗憾我不能用 Hour 作为我的石斑鱼。 谢谢,一对夫妇。在您的第二步中,windows 应替换为 window_end, right? Also you may want to use another for your result`DataFrame,因此不会误认为列 result 没错,在这里编辑的时候一直在摆弄代码,从来都不是一个好主意。现在应该可以工作了。【参考方案2】:

受到 Stefan 解决方案的启发 (+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

【讨论】:

Thatnks @MaxU,我收到 AttributeError: 'TimedeltaProperties' object has no attribute 'total_seconds' 错误。我有import datetime as dt【参考方案3】:

设置

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="\s2,", 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

【讨论】:

以上是关于如何使用 Python 在一个时间段内对行进行分组的主要内容,如果未能解决你的问题,请参考以下文章

在Microsoft SWS中,如何对行进行分组?

如何在不使用 GROUP BY 子句的情况下对行进行分组

如何动态生成表并对行进行分组

使用熊猫对行进行分组并找到平均值[重复]

按值对行进行分组,直到它更改(分组包括第一个更改的值)

允许对行进行分组的函数