重采样(Resampling
)指的是把时间序列的频度变为另一个频度的过程。把高频度的数据变为低频度叫做降采样(downsampling
),把低频度变为高频度叫做增采样(upsampling
)。并不是所有的重采样都会落入上面这几个类型,例如,把W-WED(weekly on Wednesday)变为
W-FRI`,既不属于降采样,也不属于增采样。
pandas
对象自带resample
方法,用于所有的频度变化。resample
有一个和groupby
类似的API
;我们可以用resample
来对数据进行分组,然后调用聚合函数(aggregation function)
:
import numpy as np
import pandas as pd
rng = pd.date_range('2000-01-01', periods=100, freq='D')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts
2000-01-01 0.141136
2000-01-02 0.955511
2000-01-03 -0.334537
2000-01-04 0.927611
2000-01-05 0.522567
2000-01-06 0.843023
2000-01-07 0.108661
2000-01-08 0.805668
2000-01-09 -0.470524
2000-01-10 1.162150
2000-01-11 -0.754087
2000-01-12 -1.846421
2000-01-13 -0.322607
2000-01-14 0.769992
2000-01-15 -0.596838
2000-01-16 0.865629
2000-01-17 -0.394363
2000-01-18 1.050334
2000-01-19 0.203739
2000-01-20 0.112178
2000-01-21 -1.858528
2000-01-22 0.921361
2000-01-23 -1.034003
2000-01-24 -0.319369
2000-01-25 0.626385
2000-01-26 2.319831
2000-01-27 0.640064
2000-01-28 0.762187
2000-01-29 -0.053246
2000-01-30 0.500993
...
2000-03-11 -1.036658
2000-03-12 0.569500
2000-03-13 -0.279623
2000-03-14 -1.593708
2000-03-15 -1.552634
2000-03-16 0.983931
2000-03-17 0.269289
2000-03-18 0.870814
2000-03-19 1.642178
2000-03-20 -0.109097
2000-03-21 -1.891613
2000-03-22 -1.867747
2000-03-23 -0.173888
2000-03-24 0.879418
2000-03-25 0.814583
2000-03-26 -1.683395
2000-03-27 -0.141228
2000-03-28 0.392206
2000-03-29 -1.288983
2000-03-30 1.052897
2000-03-31 -0.297663
2000-04-01 1.050265
2000-04-02 -0.072390
2000-04-03 1.482098
2000-04-04 -0.276297
2000-04-05 0.686525
2000-04-06 1.368484
2000-04-07 0.294756
2000-04-08 1.237246
2000-04-09 1.372567
Freq: D, dtype: float64
ts.resample('M').mean()
2000-01-31 -0.207554
2000-02-29 0.299003
2000-03-31 -0.095402
2000-04-30 -0.146846
Freq: M, dtype: float64
ts.resample('M', kind='period').mean()
2000-01 0.210165
2000-02 -0.051811
2000-03 -0.131131
2000-04 0.793695
Freq: M, dtype: float64
resample
是一个灵活且高效的方法,可以用于处理大量的时间序列。
把数据聚合为规律、低频度是一个很普通的时间序列任务。用于处理的数据不必是有固定频度的;我们想要设定的频度会定义箱界(bin edges
),根据bin edges
会把时间序列分割为多个片段,然后进行聚合。例如,转换为月度,比如'M'
或'BM'
,我们需要把数据以月为间隔进行切割。每一个间隔都是半开放的(half-open
);一个数据点只能属于一个间隔,所有间隔的合集,构成整个时间范围(time frame
)。当使用resample
去降采样数据的时候,有很多事情需要考虑:
bin
贴标签,可以使用间隔的开始或结束为了演示一下,下面用一个一分钟的数据来举例:
rng = pd.date_range('2000-01-01', periods=12, freq='T')
ts = pd.Series(np.arange(12), index=rng)
ts
2000-01-01 00:00:00 0
2000-01-01 00:01:00 1
2000-01-01 00:02:00 2
2000-01-01 00:03:00 3
2000-01-01 00:04:00 4
2000-01-01 00:05:00 5
2000-01-01 00:06:00 6
2000-01-01 00:07:00 7
2000-01-01 00:08:00 8
2000-01-01 00:09:00 9
2000-01-01 00:10:00 10
2000-01-01 00:11:00 11
Freq: T, dtype: int64
假设我们想要按5分钟一个数据块来进行聚合,然后对每一个组计算总和:
ts.resample('5min', closed='right').sum()
1999-12-31 23:55:00 0
2000-01-01 00:00:00 15
2000-01-01 00:05:00 40
2000-01-01 00:10:00 11
Freq: 5T, dtype: int64
我们传入的频度定义了每个bin
的边界按5分钟递增。默认,bin
的左边界是闭合的,所以00:00
值是属于00:00
到00:05
间隔的。设定closed='right'
,会让间隔的右边闭合:
ts.resample('5min', closed='right').sum()
1999-12-31 23:55:00 0
2000-01-01 00:00:00 15
2000-01-01 00:05:00 40
2000-01-01 00:10:00 11
Freq: 5T, dtype: int64
默认,每一个bin
的左边的时间戳,会被用来作为结果里时间序列的标签。通过设置label='right'
,我们可以使用bin
右边的时间戳来作为标签:
ts.resample('5min', closed='right', label='right').sum()
2000-01-01 00:00:00 0
2000-01-01 00:05:00 15
2000-01-01 00:10:00 40
2000-01-01 00:15:00 11
Freq: 5T, dtype: int64
最后,我们可能想要对结果的索引进行位移,比如在右边界减少一秒。想要实现的话,传递一个字符串或日期偏移给loffset
:
ts.resample('5min', closed='right',
label='right', loffset='-1s').sum()
1999-12-31 23:59:59 0
2000-01-01 00:04:59 15
2000-01-01 00:09:59 40
2000-01-01 00:14:59 11
Freq: 5T, dtype: int64
我们也可以使用shift
方法来实现上面loffset
的效果。
Open-High-Low-Close
: 开盘-盘高-盘低-收盘图;股票图;股价图
在经济界,一个比较流行的用法,是对时间序列进行聚合,计算每一个桶(bucket
)里的四个值:first(open),last(close),maximum(high),minimal(low)
,即开盘-收盘-盘高-盘低,四个值。使用ohlc
聚合函数可以得到这四个聚合结果:
ts.resample('5min').ohlc()
open | high | low | close | |
---|---|---|---|---|
2000-01-01 00:00:00 | 0 | 4 | 0 | 4 |
2000-01-01 00:05:00 | 5 | 9 | 5 | 9 |
2000-01-01 00:10:00 | 10 | 11 | 10 | 11 |
把一个低频度转换为高频度,是不需要进行聚合的。下面是一个有周数据的DataFrame
:
frame = pd.DataFrame(np.random.randn(2, 4),
index=pd.date_range('1/1/2000', periods=2,
freq='W-WED'),
columns=['Colorado', 'Texas', 'New York', 'Ohio'])
frame
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-12 | -1.125212 | -0.824337 | 0.803721 | -0.672660 |
当我们对这个数据进行聚合的的时候,每个组只有一个值,以及gap
(间隔)之间的缺失值。在不使用任何聚合函数的情况下,我们使用asfreq
方法将其转换为高频度:
df_daily = frame.resample('D').asfreq()
df_daily
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-06 | NaN | NaN | NaN | NaN |
2000-01-07 | NaN | NaN | NaN | NaN |
2000-01-08 | NaN | NaN | NaN | NaN |
2000-01-09 | NaN | NaN | NaN | NaN |
2000-01-10 | NaN | NaN | NaN | NaN |
2000-01-11 | NaN | NaN | NaN | NaN |
2000-01-12 | -1.125212 | -0.824337 | 0.803721 | -0.672660 |
假设我们想要用每周的值来填写非周三的部分。这种方法叫做填充(filling
)或插值(interpolation
),可以使用fillna
或reindex
方法来实现重采样:
frame.resample('D').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-06 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-07 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-08 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-09 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-10 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-11 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-12 | -1.125212 | -0.824337 | 0.803721 | -0.672660 |
我们可以选择只对一部分的周期进行填写:
frame.resample('D').ffill(limit=2)
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-06 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-07 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-08 | NaN | NaN | NaN | NaN |
2000-01-09 | NaN | NaN | NaN | NaN |
2000-01-10 | NaN | NaN | NaN | NaN |
2000-01-11 | NaN | NaN | NaN | NaN |
2000-01-12 | -1.125212 | -0.824337 | 0.803721 | -0.672660 |
注意,新的日期索引不能与旧的有重叠:
frame.resample('W-THU').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-06 | 0.138355 | 1.881517 | 0.655367 | 1.496932 |
2000-01-13 | -1.125212 | -0.824337 | 0.803721 | -0.672660 |
对周期的索引进行重采样的过程,与之前时间戳的方法相似:
frame = pd.DataFrame(np.random.randn(24, 4),
index=pd.period_range('1-2000', '12-2001',
freq='M'),
columns=['Colorado', 'Texas', 'New York', 'Ohio'])
frame[:5]
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01 | 1.451095 | 0.236027 | -1.114785 | 1.245450 |
2000-02 | 1.720449 | -0.724853 | -1.870676 | 1.089338 |
2000-03 | 0.411774 | -0.785979 | 1.749024 | 0.164739 |
2000-04 | -1.549051 | -0.050722 | 0.002775 | -1.606657 |
2000-05 | 1.011998 | 0.149377 | -1.608262 | 0.992927 |
annual_frame = frame.resample('A-DEC').mean()
annual_frame
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2001 | -0.401946 | 0.368050 | -0.209196 | -0.155851 |
增采样需要考虑的要多一些,比如在重采样前,选择哪一个时间跨度作为结束,就像asfreq
方法那样。convertion
参数默认是'start'
,但也能用'end'
:
# Q-DEC: Quarterly, year ending in December
annual_frame.resample('Q-DEC').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000Q1 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2000Q2 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2000Q3 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2000Q4 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2001Q1 | -0.401946 | 0.368050 | -0.209196 | -0.155851 |
2001Q2 | -0.401946 | 0.368050 | -0.209196 | -0.155851 |
2001Q3 | -0.401946 | 0.368050 | -0.209196 | -0.155851 |
2001Q4 | -0.401946 | 0.368050 | -0.209196 | -0.155851 |
annual_frame.resample('Q-DEC', convention='end').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000Q4 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2001Q1 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2001Q2 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2001Q3 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2001Q4 | -0.401946 | 0.368050 | -0.209196 | -0.155851 |
增采样和降采样的规则更严格一些:
subperiod
)superperiod
)如果不满足上面的规则,会报错。主要会影响到季度,年度,周度频度;例如,用Q-MAR
定义的时间跨度只与A-MAR, A-JUN, A-SEP, A-DEC
进行对齐(line up with
):
annual_frame.resample('Q-MAR').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000Q4 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2001Q1 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2001Q2 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2001Q3 | 0.208662 | -0.109971 | -0.233464 | 0.138465 |
2001Q4 | -0.401946 | 0.368050 | -0.209196 | -0.155851 |
2002Q1 | -0.401946 | 0.368050 | -0.209196 | -0.155851 |
2002Q2 | -0.401946 | 0.368050 | -0.209196 | -0.155851 |
2002Q3 | -0.401946 | 0.368050 | -0.209196 | -0.155851 |