簡體   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