【问题标题】:Pandas count the number of times an event has occurred in last n days by groupPandas 按组计算事件在过去 n 天内发生的次数
【发布时间】:2016-12-08 04:29:28
【问题描述】:

我有按 id 发生的事件表。如何计算在当前行之前每个事件类型在过去 n 天内发生的次数?

例如有一个事件列表,例如:

df = pd.DataFrame([{'id': 1, 'event_day': '2016-01-01', 'event_type': 'type1'},
{'id': 1, 'event_day': '2016-01-02', 'event_type': 'type1'},
{'id': 2, 'event_day': '2016-02-01', 'event_type': 'type2'},
{'id': 2, 'event_day': '2016-02-15', 'event_type': 'type3'},
{'id': 3, 'event_day': '2016-01-06', 'event_type': 'type3'},
{'id': 3, 'event_day': '2016-03-11', 'event_type': 'type3'},])
df['event_day'] = pd.to_datetime(df['event_day'])
df = df.sort_values(['id', 'event_day'])

或:

   event_day event_type  id
0 2016-01-01      type1   1
1 2016-01-02      type1   1
2 2016-02-01      type2   2
3 2016-02-15      type3   2
4 2016-01-06      type3   3
5 2016-03-11      type3   3

by id 我想计算过去 n 天内每个 event_type 在当前行之前发生的次数。例如,在第 3 行 id=2 中,那么在事件历史记录中的该点之前(但不包括)有多少次事件类型 1、2 和 3 在过去 n 天内针对 id 2 发生?

所需的输出如下所示:

    event_day   event_type  event_type1_in_last_30days  event_type2_in_last_30days  event_type3_in_last_30days  id
0   2016-01-01  type1       0                           0                           0                           1
1   2016-01-02  type1       1                           0                           0                           1
2   2016-02-01  type2       0                           0                           0                           2
3   2016-02-15  type3       0                           1                           0                           2
4   2016-01-06  type3       0                           0                           0                           3
5   2016-03-11  type3       0                           0                           0                           3

【问题讨论】:

  • 您的预期输出的最后一行不应该是过去 30 天的事件计数全为零吗?倒数第二行是 id 3 的唯一另一行,event_day 值相隔超过 30 天。
  • @root 你是对的。已更新。
  • 这两种方法都很好用。我在一个包含大约 10k 行的更大数据集上进行了测试,ayhan 的方法大约是 5 倍(3 秒对 15 秒),但两者都是可行的。我在示例中未能展示的一件事是 event_day 不是唯一的,因此合并并不能很好地工作 juanpa.arrivillaga,但我只是连接了两个 dfs,它工作正常。而且,两者相互匹配。

标签: python pandas


【解决方案1】:
res = ((((df['event_day'].values >= df['event_day'].values[:, None] - pd.to_timedelta('30 days')) 
        & (df['event_day'].values < df['event_day'].values[:, None]))
        & (df['id'].values == df['id'].values[:, None]))
        .dot(pd.get_dummies(df['event_type'])))
res
Out: 
array([[ 0.,  0.,  0.],
       [ 1.,  0.,  0.],
       [ 0.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])

第一部分是生成矩阵如下:

(df['event_day'].values >= df['event_day'].values[:, None] - pd.to_timedelta('30 days'))
Out: 
array([[ True,  True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True,  True],
       [False,  True,  True,  True,  True,  True],
       [False, False,  True,  True, False,  True],
       [ True,  True,  True,  True,  True,  True],
       [False, False, False,  True, False,  True]], dtype=bool)

这是一个 6x6 矩阵,每一行都会与其他行进行比较。它利用 NumPy 的广播进行成对比较(.values[:, None] 添加了另一个轴)。为了使它完整,我们需要检查这一行是否也比另一行发生得早:

(((df['event_day'].values >= df['event_day'].values[:, None] - pd.to_timedelta('30 days')) 
   & (df['event_day'].values < df['event_day'].values[:, None])))
Out: 
array([[False, False, False, False, False, False],
       [ True, False, False, False, False, False],
       [False,  True, False, False,  True, False],
       [False, False,  True, False, False, False],
       [ True,  True, False, False, False, False],
       [False, False, False,  True, False, False]], dtype=bool)

另一个条件是关于 id 的。使用类似的方法,您可以构建一个显示 id 何时匹配的成对比较矩阵:

(df['id'].values == df['id'].values[:, None])
Out: 
array([[ True,  True, False, False, False, False],
       [ True,  True, False, False, False, False],
       [False, False,  True,  True, False, False],
       [False, False,  True,  True, False, False],
       [False, False, False, False,  True,  True],
       [False, False, False, False,  True,  True]], dtype=bool)

变成:

(((df['event_day'].values >= df['event_day'].values[:, None] - pd.to_timedelta('30 days')) 
    & (df['event_day'].values < df['event_day'].values[:, None]))
    & (df['id'].values == df['id'].values[:, None]))
Out: 
array([[False, False, False, False, False, False],
       [ True, False, False, False, False, False],
       [False, False, False, False, False, False],
       [False, False,  True, False, False, False],
       [False, False, False, False, False, False],
       [False, False, False, False, False, False]], dtype=bool)

最后,您希望查看每种类型的数据,以便使用 get_dummies:

pd.get_dummies(df['event_type'])
Out: 
   type1  type2  type3
0    1.0    0.0    0.0
1    1.0    0.0    0.0
2    0.0    1.0    0.0
3    0.0    0.0    1.0
4    0.0    0.0    1.0
5    0.0    0.0    1.0

如果将结果矩阵与这个矩阵相乘,它应该会为您提供满足每种类型条件的行数。您可以将结果数组传递给 DataFrame 构造函数并连接:

pd.concat([df, pd.DataFrame(res, columns = ['e1', 'e2', 'e3'])], axis=1)
Out: 
   event_day event_type  id   e1   e2   e3
0 2016-01-01      type1   1  0.0  0.0  0.0
1 2016-01-02      type1   1  1.0  0.0  0.0
2 2016-02-01      type2   2  0.0  0.0  0.0
3 2016-02-15      type3   2  0.0  1.0  0.0
4 2016-01-06      type3   3  0.0  0.0  0.0
5 2016-03-11      type3   3  0.0  0.0  0.0

【讨论】:

    【解决方案2】:

    好的,我真的很喜欢 ayhan 的方法。但是我有另一个可能更慢(只是我假设apply 通常很慢),尽管我认为逻辑更简单。如果有人想尝试比较两者,尤其是它们的扩展方式,我会非常感兴趣:

    In [1]: import pandas as pd, numpy as np
    
    In [2]: df = pd.DataFrame([{'id': 1, 'event_day': '2016-01-01', 'event_type': 'type1'},
    {'id': 1, 'event_day': '2016-01-02', 'event_type': 'type1'},
    {'id': 2, 'event_day': '2016-02-01', 'event_type': 'type2'},
    {'id': 2, 'event_day': '2016-02-15', 'event_type': 'type3'},
    {'id': 3, 'event_day': '2016-01-06', 'event_type': 'type3'},
    {'id': 3, 'event_day': '2016-03-11', 'event_type': 'type3'},])
    
    In [3]: df['event_day'] = pd.to_datetime(df['event_day'])
    
    In [4]: df = df.sort_values(['id', 'event_day'])
    
    In [5]: dummies = pd.get_dummies(df)
    
    In [6]: dummies.set_index('event_day', inplace=True)
    
    In [7]: dummies
    Out[7]: 
                id  event_type_type1  event_type_type2  event_type_type3
    event_day                                                           
    2016-01-01   1               1.0               0.0               0.0
    2016-01-02   1               1.0               0.0               0.0
    2016-02-01   2               0.0               1.0               0.0
    2016-02-15   2               0.0               0.0               1.0
    2016-01-06   3               0.0               0.0               1.0
    2016-03-11   3               0.0               0.0               1.0
    
    In [8]: import datetime
    
    In [9]: delta30 = datetime.timedelta(days=30)
    
    In [10]: delta1 = datetime.timedelta(days=1)
    
    In [11]: dummies.apply(lambda x: dummies[dummies.id == x.id].loc[x.name - delta30:x.name - delta1].sum() ,axis=1)
    Out[11]: 
                 id  event_type_type1  event_type_type2  event_type_type3
    event_day                                                            
    2016-01-01  0.0               0.0               0.0               0.0
    2016-01-02  1.0               1.0               0.0               0.0
    2016-02-01  0.0               0.0               0.0               0.0
    2016-02-15  2.0               0.0               1.0               0.0
    2016-01-06  0.0               0.0               0.0               0.0
    2016-03-11  0.0               0.0               0.0               0.0
    

    最后,你可以mergedummies和你的原始数据框删除dummies中的'id'列:

    In [12]: dummies.drop('id', inplace = True,axis=1)
    
    In [13]: dummies
    Out[13]: 
       event_day  event_type_type1  event_type_type2  event_type_type3
    0 2016-01-01               0.0               0.0               0.0
    1 2016-01-02               1.0               0.0               0.0
    2 2016-02-01               0.0               0.0               0.0
    3 2016-02-15               0.0               1.0               0.0
    4 2016-01-06               0.0               0.0               0.0
    5 2016-03-11               0.0               0.0               0.0
    
    In [14]: pd.merge(df, dummies, on="event_day")
    Out[14]: 
       event_day event_type  id  event_type_type1  event_type_type2  \
    0 2016-01-01      type1   1               0.0               0.0   
    1 2016-01-02      type1   1               1.0               0.0   
    2 2016-02-01      type2   2               0.0               0.0   
    3 2016-02-15      type3   2               0.0               1.0   
    4 2016-01-06      type3   3               0.0               0.0   
    5 2016-03-11      type3   3               0.0               0.0   
    
       event_type_type3  
    0               0.0  
    1               0.0  
    2               0.0  
    3               0.0  
    4               0.0  
    5               0.0 
    

    【讨论】:

    • 是的,它很快就失控了 :) 在另一个样本上测试它们会很好。我仍然不确定我的工作是否正常。
    • @ayhan 是的,我正试图通过重采样和累积和来实现这一目标。几乎最终采用了“功能性蛮力”方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-21
    • 2017-05-27
    • 2023-01-23
    • 2016-03-06
    • 1970-01-01
    • 2012-10-20
    相关资源
    最近更新 更多