社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

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

机器学习初学者 • 1 年前 • 298 次点击  
本次介绍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
 
298 次点击