Pandas 时间序列 - 实例方法与重采样

呆鸟云:“数据分析就像是夜里行军,业务知识是灯塔,分析思维是地图,没灯塔你不知道方向,没地图你不知道该怎么走。技术是你的交通工具,你用11路腿儿着,还是骑自行车,还是开跑车,交通工具越好,你实现目标的速度越快 。”

移位与延迟

有时,需要整体向前或向后移动时间序列里的值,这就是移位与延迟。实现这一操作的方法是 shift(),该方法适用于所有 Pandas 对象。

In [272]: ts = pd.Series(range(len(rng)), index=rng)

In [273]: ts = ts[:5]

In [274]: ts.shift(1)
Out[274]: 
2012-01-01    NaN
2012-01-02    0.0
2012-01-03    1.0
Freq: D, dtype: float64

shift 方法支持 freq 参数,可以把 DateOffsettimedelta 对象、偏移量别名 作为参数值:

In [275]: ts.shift(5, freq=pd.offsets.BDay())
Out[275]: 
2012-01-06    0
2012-01-09    1
2012-01-10    2
Freq: B, dtype: int64

In [276]: ts.shift(5, freq='BM')
Out[276]: 
2012-05-31    0
2012-05-31    1
2012-05-31    2
Freq: D, dtype: int64

除更改数据与索引的对齐方式外,DataFrameSeries 对象还提供了 tshift() 便捷方法,可以指定偏移量修改索引日期。

In [277]: ts.tshift(5, freq='D')
Out[277]: 
2012-01-06    0
2012-01-07    1
2012-01-08    2
Freq: D, dtype: int64

注意,使用 tshift() 时,因为数据没有重对齐,NaN 不会排在前面。

频率转换

改变频率的函数主要是 asfreq()。对于 DatetimeIndex,这就是一个调用 reindex(),并生成 date_range 的便捷打包器。

In [278]: dr = pd.date_range('1/1/2010', periods=3, freq=3 * pd.offsets.BDay())

In [279]: ts = pd.Series(np.random.randn(3), index=dr)

In [280]: ts
Out[280]: 
2010-01-01    1.494522
2010-01-06   -0.778425
2010-01-11   -0.253355
Freq: 3B, dtype: float64

In [281]: ts.asfreq(pd.offsets.BDay())
Out[281]: 
2010-01-01    1.494522
2010-01-04         NaN
2010-01-05         NaN
2010-01-06   -0.778425
2010-01-07         NaN
2010-01-08         NaN
2010-01-11   -0.253355
Freq: B, dtype: float64

asfreq 用起来很方便,可以为频率转化后出现的任意间隔指定插值方法。

In [282]: ts.asfreq(pd.offsets.BDay(), method='pad')
Out[282]: 
2010-01-01    1.494522
2010-01-04    1.494522
2010-01-05    1.494522
2010-01-06   -0.778425
2010-01-07   -0.778425
2010-01-08   -0.778425
2010-01-11   -0.253355
Freq: B, dtype: float64

向前与向后填充

asfreqreindex 相关的是 fillna(),有关文档请参阅缺失值。

转换 Python 日期与时间

to_datetime 方法可以把DatetimeIndex 转换为 Python 原生 datetime.datetime 对象数组。

重采样

0.18.0 版修改了 .resample 接口,现在的 .resample 更灵活,更像 groupby。参阅更新文档 ,对比新旧版本操作的区别。

Pandas 有一个虽然简单,但却强大、高效的功能,可在频率转换时执行重采样,如,将秒数据转换为 5 分钟数据,这种操作在金融等领域里的应用非常广泛。

resample() 是基于时间的分组操作,每个组都遵循归纳方法。参阅 Cookbook 示例了解高级应用。

从 0.18.0 版开始,resample() 可以直接用于 DataFrameGroupBy 对象,参阅 groupby 文档。

.resample() 类似于基于时间偏移量的 rolling() 操作,请参阅这里的讨论。

基础知识

In [283]: rng = pd.date_range('1/1/2012', periods=100, freq='S')

In [284]: ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)

In [285]: ts.resample('5Min').sum()
Out[285]: 
2012-01-01    25103
Freq: 5T, dtype: int64

resample 函数非常灵活,可以指定多种频率转换与重采样参数。

任何支持派送(dispatch)的函数都可用于 resample 返回对象,包括 summeanstdsemmaxminmidmedianfirstlastohlc

In [286]: ts.resample('5Min').mean()
Out[286]: 
2012-01-01    251.03
Freq: 5T, dtype: float64

In [287]: ts.resample('5Min').ohlc()
Out[287]: 
            open  high  low  close
2012-01-01   308   460    9    205

In [288]: ts.resample('5Min').max()
Out[288]: 
2012-01-01    460
Freq: 5T, dtype: int64

对于下采样,closed 可以设置为leftright,用于指定关闭哪一端间隔:

In [289]: ts.resample('5Min', closed='right').mean()
Out[289]: 
2011-12-31 23:55:00    308.000000
2012-01-01 00:00:00    250.454545
Freq: 5T, dtype: float64

In [290]: ts.resample('5Min', closed='left').mean()
Out[290]: 
2012-01-01    251.03
Freq: 5T, dtype: float64

labelloffset 等参数用于生成标签。label 指定生成的结果是否要为间隔标注起始时间。loffset 调整输出标签的时间。

In [291]: ts.resample('5Min').mean()  # 默认为 label='left'
Out[291]: 
2012-01-01    251.03
Freq: 5T, dtype: float64

In [292]: ts.resample('5Min', label='left').mean()
Out[292]: 
2012-01-01    251.03
Freq: 5T, dtype: float64

In [293]: ts.resample('5Min', label='left', loffset='1s').mean()
Out[293]: 
2012-01-01 00:00:01    251.03
dtype: float64

除了 MAQBMBABQW 的默认值是 right 外,其它频率偏移量的 labelclosed 默认值都是 left

这种操作可能会导致时间回溯,即后面的时间会被拉回到前面的时间,如下例的 BusinessDay 频率所示。

In [294]: s = pd.date_range('2000-01-01', '2000-01-05').to_series()

In [295]: s.iloc[2] = pd.NaT

In [296]: s.dt.weekday_name
Out[296]: 
2000-01-01     Saturday
2000-01-02       Sunday
2000-01-03          NaN
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: D, dtype: object

# 默认为:label='left', closed='left'
In [297]: s.resample('B').last().dt.weekday_name
Out[297]: 
1999-12-31       Sunday
2000-01-03          NaN
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: B, dtype: object

看到了吗?星期日被拉回到了上一个星期五。要想把星期日移至星期一,改用以下代码:

In [298]: s.resample('B', label='right', closed='right').last().dt.weekday_name
Out[298]: 
2000-01-03       Sunday
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: B, dtype: object

axis 参数的值为 01,并可指定 DataFrame 重采样的轴。

kind 参数可以是 timestampperiod,转换为时间戳或时间段形式的索引。resample 默认保留输入的日期时间形式。

重采样 period 数据时(详情见下文),convention 可以设置为 startend。指定低频时间段如何转换为高频时间段。

上采样

上采样可以指定上采样的方式及插入时间间隔的 limit 参数:

# 从秒到每 250 毫秒
In [299]: ts[:2].resample('250L').asfreq()
Out[299]: 
2012-01-01 00:00:00.000    308.0
2012-01-01 00:00:00.250      NaN
2012-01-01 00:00:00.500      NaN
2012-01-01 00:00:00.750      NaN
2012-01-01 00:00:01.000    204.0
Freq: 250L, dtype: float64

In [300]: ts[:2].resample('250L').ffill()
Out[300]: 
2012-01-01 00:00:00.000    308
2012-01-01 00:00:00.250    308
2012-01-01 00:00:00.500    308
2012-01-01 00:00:00.750    308
2012-01-01 00:00:01.000    204
Freq: 250L, dtype: int64

In [301]: ts[:2].resample('250L').ffill(limit=2)
Out[301]: 
2012-01-01 00:00:00.000    308.0
2012-01-01 00:00:00.250    308.0
2012-01-01 00:00:00.500    308.0
2012-01-01 00:00:00.750      NaN
2012-01-01 00:00:01.000    204.0
Freq: 250L, dtype: float64

稀疏重采样

相对于时间点总量,稀疏时间序列重采样的点要少很多。单纯上采样稀疏系列可能会生成很多中间值。未指定填充值,即 fill_methodNone 时,中间值将填充为 NaN

鉴于 resample 是基于时间的分组,下列这种方法可以有效重采样,只是分组不是都为 NaN

In [302]: rng = pd.date_range('2014-1-1', periods=100, freq='D') + pd.Timedelta('1s')

In [303]: ts = pd.Series(range(100), index=rng)

Series 全范围重采样。

In [304]: ts.resample('3T').sum()
Out[304]: 
2014-01-01 00:00:00     0
2014-01-01 00:03:00     0
2014-01-01 00:06:00     0
2014-01-01 00:09:00     0
2014-01-01 00:12:00     0
                       ..
2014-04-09 23:48:00     0
2014-04-09 23:51:00     0
2014-04-09 23:54:00     0
2014-04-09 23:57:00     0
2014-04-10 00:00:00    99
Freq: 3T, Length: 47521, dtype: int64

对以下包含点的分组重采样:

In [305]: from functools import partial

In [306]: from pandas.tseries.frequencies import to_offset

In [307]: def round(t, freq):
   .....:     freq = to_offset(freq)
   .....:     return pd.Timestamp((t.value // freq.delta.value) * freq.delta.value)
   .....: 

In [308]: ts.groupby(partial(round, freq='3T')).sum()
Out[308]: 
2014-01-01     0
2014-01-02     1
2014-01-03     2
2014-01-04     3
2014-01-05     4
              ..
2014-04-06    95
2014-04-07    96
2014-04-08    97
2014-04-09    98
2014-04-10    99
Length: 100, dtype: int64

聚合

类似于聚合 API,Groupby API 及窗口函数 API,Resampler 可以有选择地重采样。

DataFrame 重采样,默认用相同函数操作所有列。

In [309]: df = pd.DataFrame(np.random.randn(1000, 3),
   .....:                   index=pd.date_range('1/1/2012', freq='S', periods=1000),
   .....:                   columns=['A', 'B', 'C'])
   .....: 

In [310]: r = df.resample('3T')

In [311]: r.mean()
Out[311]: 
                            A         B         C
2012-01-01 00:00:00 -0.033823 -0.121514 -0.081447
2012-01-01 00:03:00  0.056909  0.146731 -0.024320
2012-01-01 00:06:00 -0.058837  0.047046 -0.052021
2012-01-01 00:09:00  0.063123 -0.026158 -0.066533
2012-01-01 00:12:00  0.186340 -0.003144  0.074752
2012-01-01 00:15:00 -0.085954 -0.016287 -0.050046

标准 getitem 操作可以指定的一列或多列。

In [312]: r['A'].mean()
Out[312]: 
2012-01-01 00:00:00   -0.033823
2012-01-01 00:03:00    0.056909
2012-01-01 00:06:00   -0.058837
2012-01-01 00:09:00    0.063123
2012-01-01 00:12:00    0.186340
2012-01-01 00:15:00   -0.085954
Freq: 3T, Name: A, dtype: float64

In [313]: r[['A', 'B']].mean()
Out[313]: 
                            A         B
2012-01-01 00:00:00 -0.033823 -0.121514
2012-01-01 00:03:00  0.056909  0.146731
2012-01-01 00:06:00 -0.058837  0.047046
2012-01-01 00:09:00  0.063123 -0.026158
2012-01-01 00:12:00  0.186340 -0.003144
2012-01-01 00:15:00 -0.085954 -0.016287

聚合还支持函数列表与字典,输出的是 DataFrame

In [314]: r['A'].agg([np.sum, np.mean, np.std])
Out[314]: 
                           sum      mean       std
2012-01-01 00:00:00  -6.088060 -0.033823  1.043263
2012-01-01 00:03:00  10.243678  0.056909  1.058534
2012-01-01 00:06:00 -10.590584 -0.058837  0.949264
2012-01-01 00:09:00  11.362228  0.063123  1.028096
2012-01-01 00:12:00  33.541257  0.186340  0.884586
2012-01-01 00:15:00  -8.595393 -0.085954  1.035476

重采样后的 DataFrame,可以为每列指定函数列表,生成结构化索引的聚合结果:

In [315]: r.agg([np.sum, np.mean])
Out[315]: 
                             A                    B                    C          
                           sum      mean        sum      mean        sum      mean
2012-01-01 00:00:00  -6.088060 -0.033823 -21.872530 -0.121514 -14.660515 -0.081447
2012-01-01 00:03:00  10.243678  0.056909  26.411633  0.146731  -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837   8.468289  0.047046  -9.363825 -0.052021
2012-01-01 00:09:00  11.362228  0.063123  -4.708526 -0.026158 -11.975895 -0.066533
2012-01-01 00:12:00  33.541257  0.186340  -0.565895 -0.003144  13.455299  0.074752
2012-01-01 00:15:00  -8.595393 -0.085954  -1.628689 -0.016287  -5.004580 -0.050046

把字典传递给 aggregate,可以为 DataFrame 里不同的列应用不同聚合函数。

In [316]: r.agg({'A': np.sum,
   .....:        'B': lambda x: np.std(x, ddof=1)})
   .....: 
Out[316]: 
                             A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312

还可以用字符串代替函数名。为了让字符串有效,必须在重采样对象上操作:

In [317]: r.agg({'A': 'sum', 'B': 'std'})
Out[317]: 
                             A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312

甚至还可以为每列单独多个聚合函数。

In [318]: r.agg({'A': ['sum', 'std'], 'B': ['mean', 'std']})
Out[318]: 
                             A                   B          
                           sum       std      mean       std
2012-01-01 00:00:00  -6.088060  1.043263 -0.121514  1.001294
2012-01-01 00:03:00  10.243678  1.058534  0.146731  1.074597
2012-01-01 00:06:00 -10.590584  0.949264  0.047046  0.987309
2012-01-01 00:09:00  11.362228  1.028096 -0.026158  0.944953
2012-01-01 00:12:00  33.541257  0.884586 -0.003144  1.095025
2012-01-01 00:15:00  -8.595393  1.035476 -0.016287  1.035312

如果 DataFrame 用的不是 datetime 型索引,则可以基于 datetime 数据列重采样,用关键字 on 控制。

In [319]: df = pd.DataFrame({'date': pd.date_range('2015-01-01', freq='W', periods=5),
   .....:                    'a': np.arange(5)},
   .....:                   index=pd.MultiIndex.from_arrays([
   .....:                       [1, 2, 3, 4, 5],
   .....:                       pd.date_range('2015-01-01', freq='W', periods=5)],
   .....:                       names=['v', 'd']))
   .....: 

In [320]: df
Out[320]: 
                   date  a
v d                       
1 2015-01-04 2015-01-04  0
2 2015-01-11 2015-01-11  1
3 2015-01-18 2015-01-18  2
4 2015-01-25 2015-01-25  3
5 2015-02-01 2015-02-01  4

In [321]: df.resample('M', on='date').sum()
Out[321]: 
            a
date         
2015-01-31  6
2015-02-28  4

同样,还可以对 datetime MultiIndex 重采样,通过关键字 level 传递名字与位置。

In [322]: df.resample('M', level='d').sum()
Out[322]: 
            a
d            
2015-01-31  6
2015-02-28  4

分组迭代

Resampler对象迭代分组数据的操作非常自然,类似于 itertools.groupby()

In [323]: small = pd.Series(
   .....:     range(6),
   .....:     index=pd.to_datetime(['2017-01-01T00:00:00',
   .....:                           '2017-01-01T00:30:00',
   .....:                           '2017-01-01T00:31:00',
   .....:                           '2017-01-01T01:00:00',
   .....:                           '2017-01-01T03:00:00',
   .....:                           '2017-01-01T03:05:00'])
   .....: )
   .....: 

In [324]: resampled = small.resample('H')

In [325]: for name, group in resampled:
   .....:     print("Group: ", name)
   .....:     print("-" * 27)
   .....:     print(group, end="\n\n")
   .....: 
Group:  2017-01-01 00:00:00
---------------------------
2017-01-01 00:00:00    0
2017-01-01 00:30:00    1
2017-01-01 00:31:00    2
dtype: int64

Group:  2017-01-01 01:00:00
---------------------------
2017-01-01 01:00:00    3
dtype: int64

Group:  2017-01-01 02:00:00
---------------------------
Series([], dtype: int64)

Group:  2017-01-01 03:00:00
---------------------------
2017-01-01 03:00:00    4
2017-01-01 03:05:00    5
dtype: int64

了解更多详情,请参阅分组迭代或 itertools.groupby()

Pandas 百问百答

你可能感兴趣的:(Pandas 时间序列 - 实例方法与重采样)