Pandas Groupby重采样表现不佳

时间:2019-08-19 09:35:35

标签: python pandas pandas-groupby

我的问题

我无法将重采样功能与groupby结合使用。目前,我正在对5000行的数据样本进行8+秒的操作,这完全不符合我的要求。

样本数据(500行)

以数据为字典的垃圾箱: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()中使用on=相比,resample的性能似乎要好一些,所以我现在执行以下操作。虽然它仍然很慢。 我倒退,因为resample似乎非强制性地将日期降序。

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

如前所述,具有5000行和大约16列,这需要15秒的时间来运行,因为我需要在两个单独的数据帧上执行此操作。 使用样本数据在pastebin(500行)中,该操作花费了我0.7s的时间,这对我来说太长了,因为我的最终数据将有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.28s,这是一个很大的改进。仍然不是很好。


如何加快速度?还有另一种方法可以做同样的事情吗?

4 个答案:

答案 0 :(得分:2)

我想举例说明我尝试找出哪种解决方案产生最大性能的实验,它表明@jsmart是最好的。

我的数据集如下所示(抱歉,我无法粘贴漂亮的表格的屏幕截图):

Dataframe to process

我的目标是让每个人(orgacom,客户)按工作日对指标进行重新采样。

解决方案1:按分组/按顺序申请

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

CPU时间:用户4min 6s,sys:2.91 s,总计:4min 9s 上场时间:4分9秒

解决方案2:groupby /应用重新索引(自@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时间:用户4min 13s,sys:2.16 s,总计:4min 15s 挂墙时间: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时间:用户1min 46s,sys:4.93 s,总计:1min 51s 挂墙时间:1分51秒

解决方案4:groupby / asfreq重新采样(从@jokab第一种解决方案开始)

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

CPU时间:用户4min 22s,sys:8.01 s,总计:4min 30s 挂墙时间:4分30秒

答案 1 :(得分:1)

我将25k行测试数据集的执行时间从850毫秒缩短到了320毫秒。我将重新索引逻辑包装在一个函数中,以使计时更容易:

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)

答案 2 :(得分:0)

我还注意到在groupby上重采样可能很慢。就我而言,我使用数据重塑来加快速度,

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

答案 3 :(得分:0)

还有另一种方法。使用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