簡體   English   中英

Pandas groupby 重新采樣性能不佳

[英]Pandas groupby resample poor performance

我的問題

我在與 groupby 結合使用 resample 函數時遇到了問題。 我正在做的操作目前在 5000 行的數據樣本上需要 8 秒以上的時間,這對於我的要求來說是完全不合理的。

樣本數據(500 行)

Pastebin 以數據為字典: https : //pastebin.com/RPNdhXsy


邏輯

我有一個季度間隔的日期數據,我想按列分組,然后每月重新采樣組內的日期。

Input:
     isin  report_date   val
    SE001   2018-12-31     1
    SE001   2018-09-30     2
    SE001   2018-06-31     3
    US001   2018-10-31     4
    US001   2018-07-31     5

Output:
    isin   report_date      val        
    SE001   2018-12-31        1
            2018-11-30      NaN
            2018-10-31      NaN
            2018-09-30        2
            2018-08-31      NaN
            2018-07-31      NaN
            2018-06-30        3
    US001   2018-10-30        4    
            2018-09-31      NaN
            2018-08-31      NaN
            2018-07-31        5

我曾經有過這樣的操作:

df.groupby('isin').resample('M', on="report_date").first()[::-1]

由於似乎asfreq()性能比在resample使用on=略好,因此我目前執行以下操作。 不過還是很慢。 我反轉,因為resample似乎非可選地對日期進行降序排序。

df.set_index('report_date').groupby('isin').resample('M').asfreq()[::-1]

如前所述,5000 行和大約 16 列這需要 15 秒才能運行,因為我需要在兩個單獨的數據幀上進行。 使用 pastebin 中的示例數據(500 行),操作需要 0.7 秒,這對我來說太長了,因為我的最終數據將有 80 萬行。

編輯:不同操作的時間

當前方式

setindex --- 0.001055002212524414 seconds ---
groupby --- 0.00033092498779296875 seconds ---
resample --- 0.004662036895751953 seconds ---
asfreq --- 0.8990700244903564 seconds ---
[::-1] --- 0.0013098716735839844 seconds ---
= 0.9056s

老辦法

groupby --- 0.0005779266357421875 seconds ---
resample --- 0.0044629573822021484 seconds ---
first --- 1.6829369068145752 seconds ---
[::-1] --- 0.001600027084350586 seconds ---
= 1.6894s

由此看來,從pandas.core.resample.DatetimeIndexResamplerGroupby轉換為 df 似乎需要很長時間。 怎么辦?

EDIT2:使用重新索引

df.set_index('report_date').groupby('isin').apply(lambda x: x.reindex(pd.date_range(x.index.min(), x.index.max(), freq='M'), fill_value=0))[::-1]

這需要 0.28 秒,這是一個巨大的改進。 不過還是不太好。


我怎樣才能加快速度? 有沒有另一種方法可以做同樣的事情?

我將 25k 行測試數據集的執行時間從 850 毫秒縮短到 320 毫秒。 我將 reindex 邏輯包裝在一個函數中,以簡化計時:

def orig_pipeline(df):
    return (df
            .set_index('report_date')
            .groupby('isin')
            .apply(lambda x: x.reindex(pd.date_range(x.index.min(), 
                                                     x.index.max(), 
                                                     freq='M'), 
                                       fill_value=0))
            [::-1])

然后,我創建了新函數來使日期算術和重新索引更快:

def create_params(df):
    return (df.groupby('isin')['report_date']
            .agg(['min', 'max']).sort_index().reset_index())

def create_multiindex(df, params):
    all_dates = pd.date_range(start='1999-12-31', end='2020-12-31', freq='M')
    midx = (
        (row.isin, d)
        for row in params.itertuples()
        for d in all_dates[(row.min <= all_dates) & (all_dates <= row.max)])
    return pd.MultiIndex.from_tuples(midx, names=['isin', 'report_date'])

def apply_mulitindex(df, midx):
    return df.set_index(['isin', 'report_date']).reindex(midx)

def new_pipeline(df):
    params = create_params(df)
    midx = create_multiindex(df, params)
    return apply_mulitindex(df, midx)

新舊管道給出相同的結果(除了可能的排序順序):

v1 = orig_pipeline(df).drop(columns='isin').sort_index()
v2 = new_pipeline(df).sort_index().fillna(0)
assert(v1 == v2).all().all()

計時結果:

%%timeit
v1 = orig_pipeline(df_big)
854 ms ± 2.72 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
v2 = new_pipeline(df_big)
322 ms ± 5.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

我想舉例說明我試圖找出哪種解決方案產生最高性能的實驗,它表明 @jsmart 是最好的。

我的數據集如下(對不起,我無法粘貼漂亮的表格):

要處理的數據框

我的目標是為每個(orgacom、客戶)結合按工作日重新采樣的指標。

解決方案1:groupby / apply asfreq

%%time
sol1 = (
to_process.groupby(['orgacom', 'client'], observed=True, )
          .apply(lambda x: x.asfreq('B', fill_value=np.nan))
)

CPU 時間:用戶 4 分 6 秒,系統:2.91 秒,總計:4 分 9 秒掛牆時間:4 分 9 秒

解決方案 2:groupby / apply reindex(從 @jokab EDIT2 開始)

%%time
sol2 = (
to_process.groupby(['orgacom', 'client'], observed=True, )
    .apply(lambda x: x.reindex(pd.date_range(x.index.min(), x.index.max(), freq='B'), fill_value=np.nan))
)

CPU 時間:用戶 4 分 13 秒,系統:2.16 秒,總計:4 分 15 秒掛牆時間:4 分 15 秒

解決方案 3:重新編碼重新采樣(從@jsmart 回答開始)

def create_params(df):
    return (df.reset_index().groupby(['orgacom', 'client'], observed=True, )['date']
            .agg(['min', 'max']).sort_index().reset_index())

def create_multiindex(df, params):
    all_dates = pd.date_range(start='2016-12-31', end='2020-12-31', freq='B')
    midx = (
        (row.orgacom, row.client, d)
        for row in params.itertuples()
        for d in all_dates[(row.min <= all_dates) & (all_dates <= row.max)])
    return pd.MultiIndex.from_tuples(midx, names=['orgacom', 'client', 'date'])

def apply_mulitindex(df, midx):
    return df.set_index(['orgacom', 'client', 'date']).reindex(midx)

def new_pipeline(df):
    params = create_params(df)
    midx = create_multiindex(df, params)
    return apply_mulitindex(df, midx)

%%time
sol3 = new_pipeline(to_process.reset_index())

CPU 時間:用戶 1 分 46 秒,系統:4.93 秒,總計:1 分 51 秒掛牆時間:1 分 51 秒

解決方案 4:groupby / resample asfreq(從@jokab 第一個解決方案開始)

%%time
sol4 = to_process.groupby(['orgacom', 'client']).resample('B').asfreq()

CPU 時間:用戶 4 分 22 秒,系統:8.01 秒,總計:4 分 30 秒掛牆時間:4 分 30 秒

我還注意到在 groupby 上重新采樣可能很慢。 就我而言,我使用數據整形來加速,

df.set_index(['isin', 'report_date'])['val'].unstack(0).resample('M')

還有另一種方法可以做到這一點。 使用 itertools.groupby() 和列表理解

import time
from itertools import groupby
print(time.time())
data = (
    ('SE001', '2018-12-31', 1),
    ('SE001', '2018-09-30', 2),
    ('SE001', '2018-06-31', 3),
    ('US001', '2018-10-31', 4),
    ('US001', '2018-07-31', 5),
)

aggr = [(key, sum([g[2] for g in grp])) for key, grp in groupby(sorted(data), key=lambda x: x[0])]
print(aggr)
print(time.time())


# 100,000 records
# 2.5 seconds

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM