繁体   English   中英

使用 groupby 在日期范围内有效地计算值的出现次数

[英]Efficiently count occurrences of a value with groupby and within a date range

我有可以做到这一点的代码,但我正在使用iterrows()遍历 dataframe 的每一行。 考虑到它正在检查超过 6M 行,需要很长时间来处理。 并希望使用矢量化来加快速度。

我已经研究过使用pd.Grouperfreq ,但是一直坚持如何使用 2 个数据帧来进行此检查。

鉴于以下2个数据框:

我想查看df1中的所有行(按'sid''modtype'分组):

df1:

   sid servid       date modtype service
0  123    881 2022-07-05      A1       z
1  456    879 2022-07-02      A2       z

然后在df2中找到它们,并在df1中该组的日期后 3 天内计算这些组的出现次数,以计算该组在前 3 天内出现的次数,以及在 3 天内出现的次数后。

df2:

    sid servid       date modtype
0   123   1234 2022-07-03      A1
1   123    881 2022-07-05      A1
2   123  65781 2022-07-06      A1
3   123   8552 2022-07-30      A1
4   123   3453 2022-07-04      A2
5   123   5681 2022-07-07      A2
6   456     78 2022-07-01      A1
7   456  26744 2022-05-05      A2
8   456  56166 2022-06-29      A2
9   456  56717 2022-06-30      A2
10  456    879 2022-07-02      A2
11  456     56 2022-07-25      A2

因此,基本上,在我在下面提供的样本集中,我的 output 最终会得到:

   sid servid       date modtype service  cnt_3day_before   cnt_3day_after
0  123    881 2022-07-05      A1       z    1                 1
1  456    879 2022-07-02      A2       z    2                 0

样本集:

import pandas as pd

data1 = {
    'sid':['123','456'],
    'servid':['881','879'],
    'date':['2022-07-05','2022-07-02'],
    'modtype':['A1','A2'],
    'service':['z','z']}

df1 = pd.DataFrame(data1)
df1['date'] = pd.to_datetime(df1['date'])
df1 = df1.sort_values(by=['sid','modtype','date'], ascending=[True, True, True]).reset_index(drop=True)



data2 = {
        'sid':['123','123','123','123','123','123',
               '456','456','456','456','456','456'],
        'servid':['1234','3453','881','65781','5681','8552',
                  '26744','56717','879','56166','56','78'],
        'date':['2022-07-03','2022-07-04','2022-07-05','2022-07-06','2022-07-07','2022-07-30',
                '2022-05-05','2022-06-30','2022-07-02','2022-06-29','2022-07-25','2022-07-01'],
        'modtype':['A1','A2','A1','A1','A2','A1',
                   'A2','A2','A2','A2','A2','A1']}

df2 = pd.DataFrame(data2)
df2['date'] = pd.to_datetime(df2['date'])
df2 = df2.sort_values(by=['sid','modtype','date'], ascending=[True, True, True]).reset_index(drop=True)

注释代码

# Merge the dataframes on sid and modtype
keys = ['sid', 'modtype']
s = df2.merge(df1[[*keys, 'date']], on=keys, suffixes=['', '_'])

# Create boolean condtitions as per requirements
s['cnt_3day_after']  = s['date'].between(s['date_'], s['date_'] + pd.DateOffset(days=3), inclusive='right')
s['cnt_3day_before'] = s['date'].between(s['date_'] - pd.DateOffset(days=3), s['date_'], inclusive='left' )

# group the boolean conditions by sid and modtype
# and aggregate with sum to count the number of True values
s = s.groupby(keys)[['cnt_3day_after', 'cnt_3day_before']].sum()

# Join the aggregated counts back with df1
df_out = df1.join(s, on=keys)

结果

print(df_out)

   sid servid       date modtype service  cnt_3day_after  cnt_3day_before
0  123    881 2022-07-05      A1       z               1                1
1  456    879 2022-07-02      A2       z               0                2

我认为肯定存在更快的解决方案,但你可以试试这个。 它遍历df1中的“查询”,并为每个query计算df2中 3 天前后发生的事件数。 为了计算此类事件的数量,我们首先将sidmodtype设置为索引列,然后我们 select 通过索引匹配事件并计算所选事件和查询之间的时间差,然后我们只计算在 +/- 3 天内发生的事件。 如果您对日期列进行了排序,则可以使用二进制搜索优化这个位置,从而为您提供 O(logN) 而不是 O(N) 复杂度。

df2 = df2.set_index(['sid', 'modtype'])
seconds_in_3days = 3*24*60*60
    
def before_and_after_3days(query):
    dates = df2.loc[tuple(query[['sid', 'modtype']]), 'date']
    secs = (dates - query['date']).dt.total_seconds().astype(int)
    before = ((-seconds_in_3days <= secs) & (secs < 0)).sum()
    after = ((0 < secs) & (secs < seconds_in_3days)).sum()
    return before, after
    
before_after = df1.apply(before_and_after_3days, axis=1)
df1[['cnt_3day_before', 'cnt_3day_after']] = before_after.tolist()

这是一个部分解决方案。 没有时间做完整的事情。 以后可能有时间。 但我想我会传递这个想法,以防它可以帮助你朝着正确的方向前进。

def a(x):
    s = x['sid_y'].isna()
    
    if s.all():
        return pd.Series([0,0], index=['before','after'])
    
    idx = (~s).idxmax()
    
    nb_before = ((x.loc[idx,'date'] > x['date']) & (x.loc[idx,'date'] - x['date'] <= pd.Timedelta('3D'))).sum()
    nb_after = ((x.loc[idx,'date'] < x['date']) & (x['date'] - x.loc[idx,'date'] < pd.Timedelta('3D'))).sum()
    
    return pd.Series([nb_before,nb_after], index=['before','after'])
    
df2.merge(df1, how='left', on='date').groupby(['sid_x','modtype_x']).apply(a)

结果

                 before  after
sid_x modtype_x               
123   A1              1      1
      A2              0      0
456   A1              0      0
      A2              2      0

你必须弄清楚细节。 就像重命名一样,合并回你想要的任何结果 dataframe 。 您还需要调整TimeDelta比较。 我所拥有的不一致,但您可能可以从这里获取。 IE

x['date'] - x.loc[idx,'date'] < pd.Timedelta('3D')

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM