利用Python进行数据分析笔记-时间序列(时区、周期、频率)

时区处理

时区可以理解为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

1、时区定位和转换

默认的,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')

2、时区的操作-意识到时间戳对象

和时间序列或日期范围(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')

3、不同时区间的运算

如果两个不同时区的时间序列被合并,那么结果为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')

1、周期频度转换

通过使用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

2、季度周期频度

季度数据经常出现在会计,经济等领域。大部分季度数据都与财政年度结束日(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

3、时间戳与周期相互转换

用时间戳作为索引的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

4、从数组中创建一个周期索引

有固定频度的数据集,有时会在很多列上存储时间跨度信息。例如,在下面的宏观经济数据及上,年度和季度在不同的列

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是一个灵活且高效的方法,可以用于处理大量的时间序列。下面是一些相关的选项:

1、Downsampling(降采样)

把数据聚合为规律、低频度是一个很普通的时间序列任务。用于处理的数据不必是有固定频度的;我们想要设定的频度会定义箱界(bin edges),根据bin edges会把时间序列分割为多个片段,然后进行聚合。例如,转换为月度,比如’M’或’BM’,我们需要把数据以月为间隔进行切割。每一个间隔都是半开放的(half-open);一个数据点只能属于一个间隔,所有间隔的合集,构成整个时间范围(time frame)。当使用resample去降采样数据的时候,有很多事情需要考虑:

  • 在每个间隔里,哪一边要闭合
  • 怎样对每一个聚合的bin贴标签,可以使用间隔的开始或结束
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

2、股价图重取样

在经济界,一个比较流行的用法,是对时间序列进行聚合,计算每一个桶(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

3、增采样和插值

把一个低频度转换为高频度,是不需要进行聚合的。

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

4、对周期进行重采样

对周期的索引进行重采样的过程,与之前时间戳的方法相似

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

增采样和降采样的规则更严格一些:

  • 降采样中,目标频度必须是原频度的子周期(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.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

5、分组时间重采样

对于时间序列数据,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

你可能感兴趣的:(利用Python进行数据分析,数据分析)