Pandas高效按季分组每年数据

4 投票
3 回答
7342 浏览
提问于 2025-04-19 14:43

我有一组多年的时间序列数据,想找出95%的数据落在什么范围内。我想按季节来查看这些数据(比如冬季、春季、夏季和秋季)。

我尝试过以下方法:

import pandas as pd
import numpy as np
FRAC_2_TAIL = 0.025
yr_idx = pd.date_range(start='2005-01-30', 
                       end='2008-02-02', freq='D')
data = np.random.rand(len(yr_idx))
df = pd.DataFrame(index=yr_idx, data=data, columns=['a'])
month_num_to_season =   { 1:'DJF',  2:'DJF', 
                          3:'MAM',  4:'MAM',  5:'MAM', 
                          6:'JJA',  7:'JJA',  8:'JJA',
                          9:'SON', 10:'SON', 11:'SON',
                         12:'DJF'}
grouped =  df.groupby(lambda x: month_num_to_season.get(x.month))                      
low_bounds = grouped.quantile(FRAC_2_TAIL)
high_bounds = grouped.quantile(1 - FRAC_2_TAIL) 

这个方法在某种程度上是有效的,给出了:

DJF   0.021284
JJA   0.024769
MAM   0.030149
SON   0.041784

但在处理我的分钟级别、长达十年的数据集时,速度非常慢。

我可以使用一个叫做 TimeGrouper 的工具,来得到我想要的“几乎”结果:

gp_time = df.groupby(pd.TimeGrouper('QS-DEC'))
low_bounds = gp_time.agg(lambda x: x.quantile(FRAC_2_TAIL)) 

不过这样每年的输出是分开的(没有明显的方法可以把这些年的分位数限制合并在一起)。

2004-12-01  0.036755
2005-03-01  0.034271
         ...
2007-09-01  0.098833
2007-12-01  0.068948

我还尝试过创建一个 freq='QS-DEC' 的时间序列,像“冬季”、“春季”等,以减少字典查找的次数,然后再上采样到 df.index.freq 并在此基础上进行分组。这样做也很慢,而且占用内存。

看起来我可能漏掉了一些明显的东西。

编辑

根据 @JohnE 的评论

在分组时的 dict 查找占用了很多时间。使用5年的分钟数据:

%%timeit
grouped =  df.groupby(lambda x: month_num_to_season.get(x.month)) 
> 13.3 s per loop

分位数计算的速度很快:

%%timeit
low_bounds = grouped.quantile(FRAC_2_TAIL)
> 2.94 ms per loop

添加一个季节列并在此基础上分组的整体时间也差不多。再次被 dict 查找所主导:

SEAS = 'season'
%%timeit
df[SEAS] = [month_num_to_season.get(t_stamp.month) for t_stamp in df.index]
> 13.1 s per loop

%%timeit
gp_on_col = df.groupby(SEAS)
> 10000 loops, best of 3: 62.7 µs per loop

%%timeit
gp_on_col.quantile(FRAC_2_TAIL)
> 753 ms per loop

我重新实现了创建季度季节数据框的方法,以减少 dict 查找的次数,然后进行上采样。这个方法现在看起来有了显著的改善:我不知道之前是怎么让它变得那么慢的:

SEASON_HALO = pd.datetools.relativedelta(months=4)
start_with_halo = df.index.min() - SEASON_HALO
end_with_halo = df.index.max() + SEASON_HALO
> 84.1 µs per loop

seasonal_idx = pd.DatetimeIndex(start=start_with_halo, end=end_with_halo, freq='QS-DEC')
seasonal_ts = pd.DataFrame(index=seasonal_idx)
> 440 µs per loop

seasonal_ts[SEAS] = [month_num_to_season.get(t_stamp.month) for t_stamp in seasonal_ts.index]
> 1.25 s per loop

seasonal_minutely_ts = seasonal_ts.resample(df.index.freq, fill_method='ffill')
> 5.12 ms per loop

df_via_resample = df.join(seasonal_minutely_ts)
> 47 ms per loop

gp_up_sample = df_via_resample.groupby(SEAS)
> 63.4 µs per loop

gp_up_sample.quantile(FRAC_2_TAIL)
> 834 ms per loop

这样大概是2秒对比其他方法的13秒。

3 个回答

-2

这可能会很有帮助

    data = pd.read_excel(DATAPATH)
    data["Date"] = pd.to_datetime(data["Date"])

    def MonthToSeason(x):   
        global season
        if x == 6 or x == 7 or x == 8 or x == 9:
             season = "Monsoon"
        elif x == 10 or x == 11:
             season = "Post-monsoon"
        elif x == 12 or x == 1 or x == 2:
             season = "Winter"
        elif x == 3 or x == 4 or x == 5:
             season = "Summer"
        else:
             season = np.nan 
        return season

    data['Season'] = data['Date'].dt.month.apply(lambda x : MonthToSeason(x))
    GroupedData = data.groupby(data["Season"]).agg(['count','min','mean','max','std'])
1

到目前为止,最快的方法是结合创建一个低频率的时间序列来进行季节查找,以及@Garrett的方法,使用numpy.array进行索引查找,而不是使用dict

season_lookup = np.array([
    None,
    'DJF', 'DJF',
    'MAM', 'MAM', 'MAM',
    'JJA', 'JJA', 'JJA',
    'SON', 'SON', 'SON',
    'DJF'])
SEASON_HALO = pd.datetools.relativedelta(months=4)
start_with_halo = df.index.min() - SEASON_HALO
end_with_halo = df.index.max() + SEASON_HALO
seasonal_idx = pd.DatetimeIndex(start=start_with_halo, end=end_with_halo, freq='QS-DEC')
seasonal_ts = pd.DataFrame(index=seasonal_idx)
seasonal_ts[SEAS] = season_lookup[seasonal_ts.index.month]
seasonal_minutely_ts = seasonal_ts.resample(df.index.freq, fill_method='ffill')
df_via_resample = df.join(seasonal_minutely_ts)
gp_up_sample = df_via_resample.groupby(SEAS)
gp_up_sample.quantile(FRAC_2_TAIL)

在我的机器上,使用10年的分钟数据,结果大致是:

  • 比低频率的dict查找后再上采样快约2%
  • 比正常频率的np.array查找快约7%
  • 比我最初的方法提高了超过400%

你的情况可能会有所不同

6

如果这对你有帮助,我建议你把你认为运行比较慢的列表推导和字典查找替换成下面的方式:

month_to_season_dct = {
    1: 'DJF', 2: 'DJF',
    3: 'MAM', 4: 'MAM', 5: 'MAM',
    6: 'JJA', 7: 'JJA', 8: 'JJA',
    9: 'SON', 10: 'SON', 11: 'SON',
    12: 'DJF'
}
grp_ary = [month_to_season_dct.get(t_stamp.month) for t_stamp in df.index]

这个新方法使用了一个numpy数组作为查找表。

month_to_season_lu = np.array([
    None,
    'DJF', 'DJF',
    'MAM', 'MAM', 'MAM',
    'JJA', 'JJA', 'JJA',
    'SON', 'SON', 'SON',
    'DJF'
])
grp_ary = month_to_season_lu[df.index.month]

下面是对这两种方法在大约三年的每分钟数据上的时间比较:

In [16]: timeit [month_to_season_dct.get(t_stamp.month) for t_stamp in df.index]
1 loops, best of 3: 12.3 s per loop

In [17]: timeit month_to_season_lu[df.index.month]
1 loops, best of 3: 549 ms per loop

撰写回答