时区可以理解为UTC的偏移(offset),例如,在夏令时,纽约时间落后于UTC时间四个小时,而在一年的其他时间里,纽约时间落后于UTC时间五个小时。
在python中,时区信息来自第三方的pytz库,这个库利用的是奥尔森数据库,这个数据库汇集了世界时区信息。这个信息对于历史数据很重要,因为夏令时(daylight saving time,DST)的交接日(transition date)取决于当地政府的心血来潮。在美国,自1900年后,夏令时的交接日已经被改了很多次。
关于pytz库的更多信息,需要查看相关的文档。本书中pandas包含了一些pytz的功能,除了时区的名字,其他的API都不用去查。时区名字可以通过下面的方法获得:
import pytz
pytz.common_timezones[-5:]
['US/Eastern', 'US/Hawaii', 'US/Mountain', 'US/Pacific', 'UTC']
# 从pytz中得到一个时区对象,使用pytz.timezone
tz = pytz.timezone('America/New_York')
tz
默认的,pandas中的时间序列是time zone naive(朴素时区)。
import pandas as pd
import numpy as np
rng = pd.date_range('3/9/2012 9:30', periods=6, freq='D')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts
2012-03-09 09:30:00 0.070052
2012-03-10 09:30:00 0.721449
2012-03-11 09:30:00 -0.266241
2012-03-12 09:30:00 -1.022387
2012-03-13 09:30:00 -1.476888
2012-03-14 09:30:00 0.770954
Freq: D, dtype: float64
# 使用tz_localize方法,可以实现从朴素到本地化(naive to localized)的转变
# 定位时区
ts_utc = ts.tz_localize('UTC')
ts_utc
2012-03-09 09:30:00+00:00 0.070052
2012-03-10 09:30:00+00:00 0.721449
2012-03-11 09:30:00+00:00 -0.266241
2012-03-12 09:30:00+00:00 -1.022387
2012-03-13 09:30:00+00:00 -1.476888
2012-03-14 09:30:00+00:00 0.770954
Freq: D, dtype: float64
一旦时间序列被定位到某个时区,那么它就可以被转换为任何其他时区,使用tz_convert:
# 转换时区
ts_utc.tz_convert('America/New_York')
2012-03-09 04:30:00-05:00 0.070052
2012-03-10 04:30:00-05:00 0.721449
2012-03-11 05:30:00-04:00 -0.266241
2012-03-12 05:30:00-04:00 -1.022387
2012-03-13 05:30:00-04:00 -1.476888
2012-03-14 05:30:00-04:00 0.770954
Freq: D, dtype: float64
在处理时间序列的时候,我们可以先把时间定位到纽约时间,然后转换到柏林时间
# 定位纽约再转换成UTC时区
ts_eastern = ts.tz_localize('America/New_York')
ts_eastern.tz_convert('UTC')
2012-03-09 14:30:00+00:00 0.070052
2012-03-10 14:30:00+00:00 0.721449
2012-03-11 13:30:00+00:00 -0.266241
2012-03-12 13:30:00+00:00 -1.022387
2012-03-13 13:30:00+00:00 -1.476888
2012-03-14 13:30:00+00:00 0.770954
Freq: D, dtype: float64
# 转换到柏林时间
ts_eastern.tz_convert('Europe/Berlin')
2012-03-09 15:30:00+01:00 0.070052
2012-03-10 15:30:00+01:00 0.721449
2012-03-11 14:30:00+01:00 -0.266241
2012-03-12 14:30:00+01:00 -1.022387
2012-03-13 14:30:00+01:00 -1.476888
2012-03-14 14:30:00+01:00 0.770954
Freq: D, dtype: float64
tz_localize和tz_convert也是DatetimeIndex上的实例方法(instance methods)
ts.index.tz_localize('Asia/Shanghai')
DatetimeIndex(['2012-03-09 09:30:00+08:00', '2012-03-10 09:30:00+08:00',
'2012-03-11 09:30:00+08:00', '2012-03-12 09:30:00+08:00',
'2012-03-13 09:30:00+08:00', '2012-03-14 09:30:00+08:00'],
dtype='datetime64[ns, Asia/Shanghai]', freq='D')
和时间序列或日期范围(date ranges)相似,单独的Timestamp object(时间戳对象)也能从朴素(即无时区)本地化为有时区的日期,然后就可以转换为其他时区了
stamp = pd.Timestamp('2011-03-12 04:00')
stamp_utc = stamp.tz_localize('utc') # 定位本地的时区
stamp_utc.tz_convert('America/New_York') # 转换成纽约时区
Timestamp('2011-03-11 23:00:00-0500', tz='America/New_York')
# 创建Timestamp的时候,我们可以传递一个时区
stamp_moscow = pd.Timestamp('2011-03-12 04:00', tz='Europe/Moscow')
stamp_moscow
Timestamp('2011-03-12 04:00:00+0300', tz='Europe/Moscow')
有时区的Timestamp对象内部存储了一个UTC时间戳,这个值是从Unix纪元(即1907年1月1日)到现在的纳秒;这个UTC值在即使换了不同的时区,也是不变的
stamp_utc.value
1299902400000000000
stamp_utc.tz_convert('America/New_York').value
1299902400000000000
在使用pandas的DateOffset对象进行算数运算的时候,如果夏令时存在,pandas也会考虑进去。这里我们构建一个时间戳,正好出现在夏令时转换前。首先,在变为夏令时的前30分钟
from pandas.tseries.offsets import Hour
stamp = pd.Timestamp('2012-03-12 01:30', tz='US/Eastern')
stamp
Timestamp('2012-03-12 01:30:00-0400', tz='US/Eastern')
stamp + Hour()
Timestamp('2012-03-12 02:30:00-0400', tz='US/Eastern')
变为夏令时的90分钟前
stamp = pd.Timestamp('2012-11-04 00:30', tz='US/Eastern')
stamp
Timestamp('2012-11-04 00:30:00-0400', tz='US/Eastern')
stamp + 2 * Hour()
Timestamp('2012-11-04 01:30:00-0500', tz='US/Eastern')
如果两个不同时区的时间序列被合并,那么结果为UTC。因为时间戳是以UTC为背后机制的,这种变化是直接的,不需要手动转换
rng = pd.date_range('3/7/2012 9:30', periods=10, freq='B') # freq='B'表示按上班时间
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts
2012-03-07 09:30:00 1.128677
2012-03-08 09:30:00 0.865172
2012-03-09 09:30:00 1.003891
2012-03-12 09:30:00 0.594445
2012-03-13 09:30:00 -0.779890
2012-03-14 09:30:00 0.561338
2012-03-15 09:30:00 0.101160
2012-03-16 09:30:00 -0.314883
2012-03-19 09:30:00 -0.385164
2012-03-20 09:30:00 0.708143
Freq: B, dtype: float64
ts1 = ts[:7].tz_localize('Europe/London')
ts2 = ts1[2:].tz_convert('Europe/Moscow')
result = ts1 + ts2
result.index
DatetimeIndex(['2012-03-07 09:30:00+00:00', '2012-03-08 09:30:00+00:00',
'2012-03-09 09:30:00+00:00', '2012-03-12 09:30:00+00:00',
'2012-03-13 09:30:00+00:00', '2012-03-14 09:30:00+00:00',
'2012-03-15 09:30:00+00:00'],
dtype='datetime64[ns, UTC]', freq='B')
Periods(周期)表示时间跨度(timespans),比如天,月,季,年。Period类表示的就是这种数据类型,构建的时候需要用字符串或整数
p = pd.Period(2007, freq='A-DEC')
p
Period('2007', 'A-DEC')
Period对象代表了整个2007年一年的时间跨度,从1月1日到12月31日。在Period对象上进行加减,会有和对频度进行位移(shifting)一样的效果
p + 5
Period('2012', 'A-DEC')
p - 2
Period('2005', 'A-DEC')
如果两个周期有相同的频度,二者的区别就是它们之间有多少个单元(units)
pd.Period('2014', freq='A-DEC') - p
7
固定范围的周期(Regular ranges of periods)可以通过period_range函数创建
rng = pd.period_range('2000-01-01', '2000-06-03', freq='M') # freq='M'表示按月
rng
PeriodIndex(['2000-01', '2000-02', '2000-03', '2000-04', '2000-05', '2000-06'], dtype='period[M]', freq='M')
PeriodIndex类能存储周期组成的序列,而且可以作为任何pandas数据结构中的轴索引(axis index)
pd.Series(np.random.randn(6), index=rng)
2000-01 0.180966
2000-02 -0.801255
2000-03 -0.269305
2000-04 -1.614798
2000-05 -0.577700
2000-06 1.717878
Freq: M, dtype: float64
如果我们有字符串组成的数组,可以使用PeriodIndex类
values = ['2001Q3', '2002Q2', '2003Q1']
index = pd.PeriodIndex(values, freq='Q-DEC')
index
PeriodIndex(['2001Q3', '2002Q2', '2003Q1'], dtype='period[Q-DEC]', freq='Q-DEC')
通过使用asfreq方法,Periods和PeriodIndex对象能被转换为其他频度。例如,假设我们有一个年度期间(annual period),并且想要转换为月度期间(monthly period),做法非常直观:
p = pd.Period('2007', freq='A-DEC') # freq='A-DEC'指定周期结束月份为12月
p
Period('2007', 'A-DEC')
p.asfreq('M', how='start')
Period('2007-01', 'M')
p.asfreq('M', how='end')
Period('2007-12', 'M')
我们可以认为Period('2007', freq='A-DEC')
是某种指向时间跨度的光标,而这个时间跨度被细分为月度期间。可以看下面的图示:
如果一个财政年度(fiscal year)是在1月结束,而不是12月,那么对应的月度期间会不一样:
p = pd.Period('2007', freq='A-JUN') # freq='A-JUN'指定周期结束月份为6月
p
Period('2007', 'A-JUN')
p.asfreq('M', 'start')
Period('2006-07', 'M')
p.asfreq('M', 'end')
Period('2007-06', 'M')
当我们转换高频度为低频度时,pandas会根据 subperiod(次周期;子周期)的归属来决定superperiod(超周期;母周期)。例如,在A-JUN频度中,月份Aug-2007其实是个2008周期的一部分:
p = pd.Period('Aug-2007', 'M')
p.asfreq('A-JUN')
Period('2008', 'A-JUN')
整个PeriodIndex对象或时间序列可以被转换为一样的语义(semantics):
rng = pd.period_range('2006', '2009', freq='A-NOV') # freq='A-JUN'指定周期结束月份为11月
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts
2006 0.518204
2007 -1.310516
2008 0.879978
2009 0.452713
Freq: A-NOV, dtype: float64
ts.asfreq('M', how='start')
2005-12 0.518204
2006-12 -1.310516
2007-12 0.879978
2008-12 0.452713
Freq: M, dtype: float64
这里,年度周期可以用月度周期替换,对应的第一个月也会包含在每个年度周期里。如果我们想要每年的最后一个工作日的话,可以使用’B’频度,并指明我们想要周期的结尾
ts.asfreq('B', how='end')
2006-11-30 0.518204
2007-11-30 -1.310516
2008-11-28 0.879978
2009-11-30 0.452713
Freq: B, dtype: float64
季度数据经常出现在会计,经济等领域。大部分季度数据都与财政年度结束日(fiscal year end)相关,比如12月最后一个工作日。因此,根据财政年度结束的不同,周期2012Q4也有不同的意义。pandas支持所有12个周期频度,从Q-JAN到Q-DEC。
p = pd.Period('2012Q4', freq='Q-JAN') # freq='A-JUN'指定周期结束月份为1月
p
Period('2012Q4', 'Q-JAN')
如果是财政年度结束日在一月份,那么2012Q4代表从11月到1月,可以用日频度查看。可以看下面的图示帮助理解:
p.asfreq('D', 'start')
Period('2011-11-01', 'D')
p.asfreq('D', 'end')
Period('2012-01-31', 'D')
因此,做些简单的周期运算也是可能的,例如,获得每个季度的,第二个到最后一个工作日的,下午4点的时间戳:
p4pm = (p.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
p4pm
Period('2012-01-30 16:00', 'T')
# 转换成时间戳
p4pm.to_timestamp()
Timestamp('2012-01-30 16:00:00')
还可以用period_range产生季度范围数据。运算方法也一样:
rng = pd.period_range('2011Q3', '2012Q4', freq='Q-JAN')
ts = pd.Series(np.arange(len(rng)), index=rng)
ts
2011Q3 0
2011Q4 1
2012Q1 2
2012Q2 3
2012Q3 4
2012Q4 5
Freq: Q-JAN, dtype: int32
new_rng = (rng.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
ts.index = new_rng.to_timestamp()
ts
2010-10-28 16:00:00 0
2011-01-28 16:00:00 1
2011-04-28 16:00:00 2
2011-07-28 16:00:00 3
2011-10-28 16:00:00 4
2012-01-30 16:00:00 5
dtype: int32
用时间戳作为索引的Series和DataFrame对象,可以用to_period方法转变为周期
rng = pd.date_range('2000-01-01', periods=3, freq='M')
ts = pd.Series(np.random.randn(3), index=rng)
ts
2000-01-31 1.110931
2000-02-29 0.329854
2000-03-31 0.054687
Freq: M, dtype: float64
pts = ts.to_period()
pts
2000-01 1.110931
2000-02 0.329854
2000-03 0.054687
Freq: M, dtype: float64
因为周期是不重复的时间跨度(non-overlapping timespans),一个时间戳只能属于一个有指定频度的单独周期。尽管默认情况下新的PeriodIndex的频度会从时间戳中来推测,但我们也可以自己设定想要的频度。结果中有重复的周期也没有关系
rng = pd.date_range('1/29/2000', periods=6, freq='D')
ts2 = pd.Series(np.random.randn(6), index=rng)
ts2
2000-01-29 0.296895
2000-01-30 0.054110
2000-01-31 0.835634
2000-02-01 0.986595
2000-02-02 2.245309
2000-02-03 -0.590988
Freq: D, dtype: float64
ts2.to_period('M')
2000-01 0.296895
2000-01 0.054110
2000-01 0.835634
2000-02 0.986595
2000-02 2.245309
2000-02 -0.590988
Freq: M, dtype: float64
想转换回时间戳的话,使用to_timestamp:
pts = ts2.to_period()
pts
2000-01-29 0.296895
2000-01-30 0.054110
2000-01-31 0.835634
2000-02-01 0.986595
2000-02-02 2.245309
2000-02-03 -0.590988
Freq: D, dtype: float64
pts.to_timestamp(how='end')
2000-01-29 0.296895
2000-01-30 0.054110
2000-01-31 0.835634
2000-02-01 0.986595
2000-02-02 2.245309
2000-02-03 -0.590988
Freq: D, dtype: float64
有固定频度的数据集,有时会在很多列上存储时间跨度信息。例如,在下面的宏观经济数据及上,年度和季度在不同的列
data = pd.read_csv('../examples/macrodata.csv')
data.tail()
year | quarter | realgdp | realcons | realinv | realgovt | realdpi | cpi | m1 | tbilrate | unemp | pop | infl | realint | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
198 | 2008.0 | 3.0 | 13324.600 | 9267.7 | 1990.693 | 991.551 | 9838.3 | 216.889 | 1474.7 | 1.17 | 6.0 | 305.270 | -3.16 | 4.33 |
199 | 2008.0 | 4.0 | 13141.920 | 9195.3 | 1857.661 | 1007.273 | 9920.4 | 212.174 | 1576.5 | 0.12 | 6.9 | 305.952 | -8.79 | 8.91 |
200 | 2009.0 | 1.0 | 12925.410 | 9209.2 | 1558.494 | 996.287 | 9926.4 | 212.671 | 1592.8 | 0.22 | 8.1 | 306.547 | 0.94 | -0.71 |
201 | 2009.0 | 2.0 | 12901.504 | 9189.0 | 1456.678 | 1023.528 | 10077.5 | 214.469 | 1653.6 | 0.18 | 9.2 | 307.226 | 3.37 | -3.19 |
202 | 2009.0 | 3.0 | 12990.341 | 9256.0 | 1486.398 | 1044.088 | 10040.6 | 216.385 | 1673.9 | 0.12 | 9.6 | 308.013 | 3.56 | -3.44 |
data.year[:5]
0 1959.0
1 1959.0
2 1959.0
3 1959.0
4 1960.0
Name: year, dtype: float64
通过把这些数组传递给PeriodIndex,并指定频度,我们可以把这些合并得到一个新的DataFrame:
index = pd.PeriodIndex(year=data.year, quarter=data.quarter,
freq='Q-DEC')
index
PeriodIndex(['1959Q1', '1959Q2', '1959Q3', '1959Q4', '1960Q1', '1960Q2',
'1960Q3', '1960Q4', '1961Q1', '1961Q2',
...
'2007Q2', '2007Q3', '2007Q4', '2008Q1', '2008Q2', '2008Q3',
'2008Q4', '2009Q1', '2009Q2', '2009Q3'],
dtype='period[Q-DEC]', length=203, freq='Q-DEC')
# 更改索引
data.index = index
data[:5]
year | quarter | realgdp | realcons | realinv | realgovt | realdpi | cpi | m1 | tbilrate | unemp | pop | infl | realint | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1959Q1 | 1959.0 | 1.0 | 2710.349 | 1707.4 | 286.898 | 470.045 | 1886.9 | 28.98 | 139.7 | 2.82 | 5.8 | 177.146 | 0.00 | 0.00 |
1959Q2 | 1959.0 | 2.0 | 2778.801 | 1733.7 | 310.859 | 481.301 | 1919.7 | 29.15 | 141.7 | 3.08 | 5.1 | 177.830 | 2.34 | 0.74 |
1959Q3 | 1959.0 | 3.0 | 2775.488 | 1751.8 | 289.226 | 491.260 | 1916.4 | 29.35 | 140.5 | 3.82 | 5.3 | 178.657 | 2.74 | 1.09 |
1959Q4 | 1959.0 | 4.0 | 2785.204 | 1753.7 | 299.356 | 484.052 | 1931.3 | 29.37 | 140.0 | 4.33 | 5.6 | 179.386 | 0.27 | 4.06 |
1960Q1 | 1960.0 | 1.0 | 2847.699 | 1770.5 | 331.722 | 462.199 | 1955.5 | 29.54 | 139.6 | 3.50 | 5.2 | 180.007 | 2.31 | 1.19 |
重采样(Resampling)指的是把时间序列的频度变为另一个频度的过程。把高频度的数据变为低频度叫做降采样(downsampling),把低频度变为高频度叫做增采样(upsampling)。并不是所有的重采样都会落入上面这几个类型,例如,把W-WED(weekly on Wednesday)变为W-FRI,既不属于降采样,也不属于增采样。
pandas对象自带resampe方法,用于所有的频度变化。resample有一个和groupby类似的API;我们可以用resample来对数据进行分组,然后调用聚合函数(aggregation function):
rng = pd.date_range('2000-01-01', periods=100, freq='D')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts2 = ts[::10] # 间隔10取数
ts2
2000-01-01 0.791932
2000-01-11 0.146998
2000-01-21 0.352762
2000-01-31 0.173872
2000-02-10 1.214505
2000-02-20 -1.144078
2000-03-01 -1.612532
2000-03-11 0.888548
2000-03-21 -0.663674
2000-03-31 -1.492769
Freq: 10D, dtype: float64
# 按月分组
ts.resample('M').mean()
2000-01-31 0.090178
2000-02-29 -0.089179
2000-03-31 0.163698
2000-04-30 0.190000
Freq: M, dtype: float64
# 按月分组,以月为周期
ts.resample('M', kind='period').mean()
2000-01 0.090178
2000-02 -0.089179
2000-03 0.163698
2000-04 0.190000
Freq: M, dtype: float64
resample是一个灵活且高效的方法,可以用于处理大量的时间序列。下面是一些相关的选项:
把数据聚合为规律、低频度是一个很普通的时间序列任务。用于处理的数据不必是有固定频度的;我们想要设定的频度会定义箱界(bin edges),根据bin edges会把时间序列分割为多个片段,然后进行聚合。例如,转换为月度,比如’M’或’BM’,我们需要把数据以月为间隔进行切割。每一个间隔都是半开放的(half-open);一个数据点只能属于一个间隔,所有间隔的合集,构成整个时间范围(time frame)。当使用resample去降采样数据的时候,有很多事情需要考虑:
rng = pd.date_range('2000-01-01', periods=12, freq='T') # 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: int32
假设我们想要按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: int32
ts.resample('5min', closed='left', label='right').sum()
2000-01-01 00:05:00 10
2000-01-01 00:10:00 35
2000-01-01 00:15:00 21
Freq: 5T, dtype: int32
可以看下图方便理解:
最后,我们可能想要对结果的索引进行位移,比如在右边界减少一秒。想要实现的话,传递一个字符串或日期偏移给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: int32
我们也可以使用shift方法来实现上面loffset的效果。
ts.shift(2) # shift位移时,会引入缺失值
2000-01-01 00:00:00 NaN
2000-01-01 00:01:00 NaN
2000-01-01 00:02:00 0.0
2000-01-01 00:03:00 1.0
2000-01-01 00:04:00 2.0
2000-01-01 00:05:00 3.0
2000-01-01 00:06:00 4.0
2000-01-01 00:07:00 5.0
2000-01-01 00:08:00 6.0
2000-01-01 00:09:00 7.0
2000-01-01 00:10:00 8.0
2000-01-01 00:11:00 9.0
Freq: T, dtype: float64
在经济界,一个比较流行的用法,是对时间序列进行聚合,计算每一个桶(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 |
把一个低频度转换为高频度,是不需要进行聚合的。
frame = pd.DataFrame(np.random.randn(2, 4),
index=pd.date_range('1/1/2000', periods=2,
freq='W-WED'), # freq='W-WED'表示按周
columns=['Colorado', 'Texas', 'New York', 'Ohio'])
frame
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | -0.838067 | -0.327341 | -0.372956 | 1.714622 |
2000-01-12 | 0.948086 | 0.265658 | -0.573967 | -1.685807 |
当我们对这个数据进行聚合的的时候,每个组只有一个值,以及gap(间隔)之间的缺失值。在不使用任何聚合函数的情况下,我们使用asfreq方法将其转换为高频度:
df_daily = frame.resample('D').asfreq()
df_daily
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | -0.838067 | -0.327341 | -0.372956 | 1.714622 |
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 | 0.948086 | 0.265658 | -0.573967 | -1.685807 |
假设我们想要用每周的值来填写非周三的部分。这种方法叫做填充(filling)或插值(interpolation),可以使用fillna或reindex方法来实现重采样:
frame.resample('D').ffill(limit=2) # 填充前三行
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | -0.838067 | -0.327341 | -0.372956 | 1.714622 |
2000-01-06 | -0.838067 | -0.327341 | -0.372956 | 1.714622 |
2000-01-07 | -0.838067 | -0.327341 | -0.372956 | 1.714622 |
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 | 0.948086 | 0.265658 | -0.573967 | -1.685807 |
注意,新的日期索引不能与旧的有重叠
frame.resample('W-THU').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-06 | -0.838067 | -0.327341 | -0.372956 | 1.714622 |
2000-01-13 | 0.948086 | 0.265658 | -0.573967 | -1.685807 |
对周期的索引进行重采样的过程,与之前时间戳的方法相似
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 | -0.635160 | 0.011303 | 1.250453 | -1.584201 |
2000-02 | 0.718639 | -0.300432 | 0.962824 | 1.843192 |
2000-03 | 1.162719 | 0.102825 | 0.341636 | 0.964942 |
2000-04 | -1.751530 | -0.146332 | 0.867388 | 1.227828 |
2000-05 | 0.209254 | -0.877639 | 0.381841 | 0.189415 |
annual_frame = frame.resample('A-DEC').mean()
annual_frame
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000 | 0.01502 | -0.453686 | 0.206244 | 0.203825 |
2001 | -0.43239 | 0.110276 | 0.196113 | -0.504091 |
增采样需要考虑的要多一些,比如在重采样前,选择哪一个时间跨度作为结束,就像asfreq方法那样。convertion参数默认是’start’,但也能用’end’:
annual_frame.resample('Q-DEC', convention='end').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000Q4 | 0.01502 | -0.453686 | 0.206244 | 0.203825 |
2001Q1 | 0.01502 | -0.453686 | 0.206244 | 0.203825 |
2001Q2 | 0.01502 | -0.453686 | 0.206244 | 0.203825 |
2001Q3 | 0.01502 | -0.453686 | 0.206244 | 0.203825 |
2001Q4 | -0.43239 | 0.110276 | 0.196113 | -0.504091 |
增采样和降采样的规则更严格一些:
如果不满足上面的规则,会报错。主要会影响到季度,年度,周度频度;例如,用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.01502 | -0.453686 | 0.206244 | 0.203825 |
2001Q1 | 0.01502 | -0.453686 | 0.206244 | 0.203825 |
2001Q2 | 0.01502 | -0.453686 | 0.206244 | 0.203825 |
2001Q3 | 0.01502 | -0.453686 | 0.206244 | 0.203825 |
2001Q4 | -0.43239 | 0.110276 | 0.196113 | -0.504091 |
2002Q1 | -0.43239 | 0.110276 | 0.196113 | -0.504091 |
2002Q2 | -0.43239 | 0.110276 | 0.196113 | -0.504091 |
2002Q3 | -0.43239 | 0.110276 | 0.196113 | -0.504091 |
对于时间序列数据,resample方法是一个基于时间的组操作。
N = 15
times = pd.date_range('2017-05-20 00:00', freq='1min', periods=N)
df = pd.DataFrame({'time': times, 'value': np.arange(N)})
df
time | value | |
---|---|---|
0 | 2017-05-20 00:00:00 | 0 |
1 | 2017-05-20 00:01:00 | 1 |
2 | 2017-05-20 00:02:00 | 2 |
3 | 2017-05-20 00:03:00 | 3 |
4 | 2017-05-20 00:04:00 | 4 |
5 | 2017-05-20 00:05:00 | 5 |
6 | 2017-05-20 00:06:00 | 6 |
7 | 2017-05-20 00:07:00 | 7 |
8 | 2017-05-20 00:08:00 | 8 |
9 | 2017-05-20 00:09:00 | 9 |
10 | 2017-05-20 00:10:00 | 10 |
11 | 2017-05-20 00:11:00 | 11 |
12 | 2017-05-20 00:12:00 | 12 |
13 | 2017-05-20 00:13:00 | 13 |
14 | 2017-05-20 00:14:00 | 14 |
我们用time索引,然后重采样
# set_index设置索引
df.set_index('time').resample('5min').count()
value | |
---|---|
time | |
2017-05-20 00:00:00 | 5 |
2017-05-20 00:05:00 | 5 |
2017-05-20 00:10:00 | 5 |
假设一个DataFrame包含多个时间序列,用多一个key列来表示
df2 = pd.DataFrame({'time': times.repeat(3),
'key': np.tile(['a', 'b', 'c'], N),
'value': np.arange(N * 3.)})
df2[:7]
key | time | value | |
---|---|---|---|
0 | a | 2017-05-20 00:00:00 | 0.0 |
1 | b | 2017-05-20 00:00:00 | 1.0 |
2 | c | 2017-05-20 00:00:00 | 2.0 |
3 | a | 2017-05-20 00:01:00 | 3.0 |
4 | b | 2017-05-20 00:01:00 | 4.0 |
5 | c | 2017-05-20 00:01:00 | 5.0 |
6 | a | 2017-05-20 00:02:00 | 6.0 |
对key列的值做重采样,然后设置time为索引,对key和time_key做分组,然后聚合
time_key = pd.Grouper(freq='5min') # pd.Grouper()
resampled = (df2.set_index('time') # set_index设置索引
.groupby(['key', time_key])
.sum())
resampled
value | ||
---|---|---|
key | time | |
a | 2017-05-20 00:00:00 | 30.0 |
2017-05-20 00:05:00 | 105.0 | |
2017-05-20 00:10:00 | 180.0 | |
b | 2017-05-20 00:00:00 | 35.0 |
2017-05-20 00:05:00 | 110.0 | |
2017-05-20 00:10:00 | 185.0 | |
c | 2017-05-20 00:00:00 | 40.0 |
2017-05-20 00:05:00 | 115.0 | |
2017-05-20 00:10:00 | 190.0 |
# reset_index 重置索引
resampled.reset_index()
key | time | value | |
---|---|---|---|
0 | a | 2017-05-20 00:00:00 | 30.0 |
1 | a | 2017-05-20 00:05:00 | 105.0 |
2 | a | 2017-05-20 00:10:00 | 180.0 |
3 | b | 2017-05-20 00:00:00 | 35.0 |
4 | b | 2017-05-20 00:05:00 | 110.0 |
5 | b | 2017-05-20 00:10:00 | 185.0 |
6 | c | 2017-05-20 00:00:00 | 40.0 |
7 | c | 2017-05-20 00:05:00 | 115.0 |
8 | c | 2017-05-20 00:10:00 | 190.0 |