Py学习  »  Python

【Python】pandas 时序统计的高级用法!

机器学习初学者 • 2 月前 • 120 次点击  
本次介绍pandas时间统计分析的一个高级用法--重采样。

重采样指的是时间重采样,就是将时间序列从一个频率转换到另一个频率上,对应数据也跟着频率进行变化。比如时间序列数据是以天为周期的,通过重采样我们可以将其转换为按分钟、小时、周、月、季度等等的其他周期上。根据转换的频率精度可分为向上采样和向下采样。

  • 向上采样:转换到更细颗粒度的频率,比如将天转为小时、分钟、秒等
  • 向下采样:转换到更粗颗粒度的频率,比如将天转为周、月、季度、年等

resample用法

pandas中时间重采样的方法是resample(),可以对series和dataframe对象操作。由于重采样默认对索引执行变换,因此索引必须是时间类型,或者通过on指定要重采样的时间类型的column列。

用法:
pandas.DataFrame.resample()
pandas.Series.resample()
------
返回:Resampler对象

参数:

  • rule:定义重采样的规则,DateOffset,Timedelta或str类型,当为str类型时,其参数及含义如下表所示
  • axis:指定轴方向,str类型,默认为0
    • 0:代表索引
    • 1:代表列
  • closed:指定时间频率分组的左右闭合状态,默认M,A,Q,BM,BA,BQ,W右闭合,其余均是左闭合
    • left:指定左闭合
    • right:指定右闭合
  • label:指定左或右边界作为分组标签,默认M,A,Q,BM,BA,BQ,W以右边界为分组标签,其余均是以左边界为分组标签
    • left:以左边界为分组标签
    • right:以右边界为分组标签
  • kind:将结果索引转化为指定的时间类型
    • timestamp :将结果索引转换为DateTimeIndex
    • period:将结果索引转换为PeriodIndex
  • on:对于dataframe,指定被重采样的列,且列必须是时间类型
  • level:对于多级索引,指定要被重采样的索引层级,int或str类型。
    • int:索引层级
    • str:索引层级名称
  • origin:调整时间分组的起点。Timestamp或str类型,当为str时:
    • epoch:1970-01-01
    • start:时间序列的第一个值
    • start_day:时间序列第一天的午夜
    • end:时间序列的最后一个值
    • end_day:最后一天的午夜
  • offset:对origin添加的偏移量,Timedelta或str类型
  • group_keys:指定是否在结果索引包含分组keys,当采样对象使用了.apply()方法,默认False不包含

举例:

1)指定列名

resample默认只对索引对象操作,换句话说,默认情况下索引必须是时间类型的数据,否则执行会报错。

对于dataframe而言,如不想对索引重采样,可以通过on参数选择一个column列代替索引进行重采样操作。

# 将时间类型索引重置,变为column列
df.reset_index(drop=False,inplace=True)
# 通过参数on指定时间类型的列名,也可以实现重采样
df.resample('W', on='index')['C_0'].sum().head()

由于W是默认为右闭且取右边界作为分组标签的,重采样后结果如下。从1/3至1/9(绿色)是完整一周,因此之前非完整部分(黄色)自动归为一周,后面依次按周统计。

2)开闭区间指定

通过closed参数可以控制左右闭合的状态。

默认情况下,M,A,Q ,BM,BA,BQ,W是右闭合,其余频率均是左闭合。

下面将天频率转为W周频率(默认是右闭)。我们手动设置左、右闭合进行对比,可以看出二者区别,对于求和结果的影响。

df=generate_sample_data_datetime()
pd.concat([df.resample('W', closed='left')['C_0'].sum().to_frame(name='left_clsd'),
           df.resample('W', closed='right')['C_0'].sum().to_frame(name='right_clsd')],
          axis=1).head(5)
3)输出结果控制

通过label参数可以控制输出结果的标签。

默认情况下,M,A,Q,BM,BA,BQ,W以分组内右侧边界为输出的标签,其余均是以分组内左边界为标签。

下面将天频率转为W周频率(label默认右边界)。我们手动设置label为左、右进行对比,可以看出第二个采样分组下输出标签的区别。

df=generate_sample_data_datetime()
df.resample('W', label='left')['C_0'].sum().to_frame(name='left_bnd').head(5)
df.resample('W', label='right')['C_0'].sum().to_frame(name='right_bnd').head(5)
4)聚合统计

类似于groupby和窗口的聚合方法, 重采样也适用相关方法,参考pandas分组8个常用技巧!

以下是resample采样后可以支持的描述性统计和计算的内置函数。

内置方法下面例子中会举例说明。

上采样

分为上采样和下采样。通过以下数据举例说明。

# 生成时间索引的数据
def generate_sample_data_datetime():
    np.random.seed(123)
    number_or_rows = 365*2
    num_cols = 5
    start_date = '2022-01-01'
    cols = ["C_0""C_1""C_2""C_3""C_4"]
    df = pd.DataFrame(np.random.randint(1100, size = (number_or_rows, num_cols)), columns=cols)
    df.index = pd.date_range(start=start_date, periods=number_or_rows)
    return df
df=generate_sample_data_datetime()

以上生成数据时间索引是以天为频率的。

根据rule参数含义码表,H代表小时的意思,12H也就是12小时。这是resample非常强大的地方,可以把采样定位的非常精确。

下面将天的时间频率转换为12小时的频率,并对新的频率分组后求和。

df.resample('12H')['C_0'].sum().head(10)

比天颗粒度更小的还可以有分钟、秒、毫秒、微秒、纳秒,可根据实际情况自行设定频率大小。

以上可以看到,上采样的过程中由于频率更高导致采样后数据部分缺失。这时候可以使用上采样的填充方法,方法如下:

1)ffill

只有一个参数limit控制向前填充的数量。

下面将天为频率的数据上采样到8H频率,向前填充1行和2行的结果。

df.resample('8H')['C_0'].ffill(limit=1)
2)bfill

与向前填充用法一样,下面向后填充1行和2行的结果。

df.resample('8H')['C_0'].bfill(limit=1)
3)nearest

该方法为就近填充,无确定方向,可能向前或者向后。参数也是limit对填充数量进行控制。以下对缺失部分按最近数据填充1行,结果如下。

df.resample('8H'


    
)['C_0'].nearest(limit=1)
4)fillna

该方法是前三种方法的集合,参数method可设置{'pad'/'ffill','bfill','nearest'}三种,分别代表向前,向后、取最近,同时也可以设置limit进行数量控制,因此该方法可以取代前面三种。

df.resample('8H')['C_0'].fillna(method='pad', limit=1)
5)asfreq

该方法可以指定固定值对所有缺失部分一次性填充,比如对缺失部分统一填充-999。

df.resample('8H')['C_0'].asfreq(-999)
6)interpolate

该方法可以使用更高级的算法进行填充。具体方法可通过参数method设置,不详细介绍,这里以linear线性插值方法举例。

df.resample('8H').interpolate(method='linear').applymap(lambda x:round(x,2))

应用函数

1)agg

如果想同时对多列的聚合,或者对单列赋予多个聚合函数,可以使用agg()聚合方法。

下面进行下采样,将天频率降为周,并对多个变量进行多种聚合操作。

df.resample('W').agg(
    {
        'C_0': ['sum''mean'],
        'C_1'lambda x: np.std(x, ddof=1)
    }
).head()

以上结果列名显示了两个层级,如果想去掉层级并自定义结果中的变量名,可通过以下代码实现。

df=generate_sample_data_datetime()
df.resample('W').agg(
        C_0_sum=('C_0','sum'),
        C_0_avg=('C_0','mean'),
        C_1_delta=('C_1'lambda x:x.max()-x.min())
).head()
2)apply

使用apply函数也可以达到agg的聚合效果,以下对多个变量进行不同的聚合函数,其中也可以自定义函数。

def agg_func(x):
    names = {
        'C_0_mean': round(x['C_0'].mean(),2),
        'C_1_sum':  x['C_1'].sum(),
        'C_2_max':  x['C_2'].max(),
        'C_3_mean_plus1': round(x['C_3'].mean()+1,2),
    }
    return pd.Series(names, index=[ key for key in names.keys()])

df.resample('W').apply(agg_func).head()
3)transform

transform在分组系列中介绍过,会对原数据进行分组内转换但不改变原索引结构,在重采样中用法一样。transform()函数的使用方法可参考pandas transform 数据转换的 4 个常用技巧!

以下对C_0变量进行采样分组内的累加和排序操作。

df['C_0_cumsum'] = df.resample('W')['C_0'].transform('cumsum')
df['C_0_rank'] = df.resample('W')['C_0'].transform('rank')
df.head(10)
4)pipe

pipe()被称为管道函数,可以对重采样后的resampler对象应用带参数的自定义函数。pipe()函数的使用方法可参考pandas一个优雅的高级应用函数!

它最大的优势在于可以链式使用,每次函数执行后的输出结果可以作为下一个函数的参数,形式如:pipe(func1).pipe(func2),参数可以是series、dataFrames、groupBy对象、或者resampler对象。

通过pipe的链式可以像管道一样按顺序依次执行操作,并且只需要一行代码即可,极大地提高了可读性。

以下对下采样后的C_0和C_1变量进行累加求和操作,然后再对两个求和作差。

df['cumsum_delta'] = df.resample('W')['C_0','C_1'] \
    .pipe(lambda x:x.cumsum()) \
    .pipe(lambda  x:x['C_1']-x['C_0'])
df.head(10)

这里当pipe应用了cumsum()函数后,与transform一样可以返回不改变原索引的结果。

-end-

往期精彩回顾




  • 交流群

欢迎加入机器学习爱好者微信群一起和同行交流,目前有机器学习交流群、博士群、博士申报交流、CV、NLP等微信群,请扫描下面的微信号加群,备注:”昵称-学校/公司-研究方向“,例如:”张小明-浙大-CV“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~(也可以加入机器学习交流qq群772479961


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/166914
 
120 次点击