时期及其算术运算
时期(period)表示的是时间区间,比如数日、数月、数季、数年等。Period类所表示的就是这种数据类型,其构造函数需要用到一个字符串或整数,以及表10-4中的频率:
- In [454]: p = pd.Period(2007, freq='A-DEC')
- In [455]: p
- Out[455]: Period('2007', 'A-DEC')
这个Period对象表示的是从2007年1月1日到2007年12月31日之间的整段时间。只需对Period对象加上或减去一个整数即可达到根据其频率进行位移的效果:
- In [456]: p + 5 In [457]: p - 2
- Out[456]: Period('2012', 'A-DEC') Out[457]: Period('2005', 'A-DEC')
如果两个Period对象拥有相同的频率,则它们的差就是它们之间的单位数量:
- In [458]: pd.Period('2014', freq='A-DEC') - p
- Out[458]: 7
period_range函数可用于创建规则的时期范围:
- In [459]: rng = pd.period_range('1/1/2000', '6/30/2000', freq='M')
- In [460]: rng
- Out[460]:
- <class 'pandas.tseries.period.PeriodIndex'>
- freq: M
- [2000-01, ..., 2000-06]
- length: 6
PeriodIndex类保存了一组Period,它可以在任何pandas数据结构中被用作轴索引:
- In [461]: Series(np.random.randn(6), index=rng)
- Out[461]:
- 2000-01 -0.309119
- 2000-02 0.028558
- 2000-03 1.129605
- 2000-04 -0.374173
- 2000-05 -0.011401
- 2000-06 0.272924
- Freq: M
PeriodIndex类的构造函数还允许直接使用一组字符串:
- In [462]: values = ['2001Q3', '2002Q2', '2003Q1']
- In [463]: index = pd.PeriodIndex(values, freq='Q-DEC')
- In [464]: index
- Out[464]:
- <class 'pandas.tseries.period.PeriodIndex'>
- freq: Q-DEC
- [2001Q3, ..., 2003Q1]
- length: 3
时期的频率转换
Period和PeriodIndex对象都可以通过其asfreq方法被转换成别的频率。假设我们有一个年度时期,希望将其转换为当年年初或年末的一个月度时期。该任务非常简单:
- In [465]: p = pd.Period('2007', freq='A-DEC')
- In [466]: p.asfreq('M', how='start') In [467]: p.asfreq('M', how='end')
- Out[466]: Period('2007-01', 'M') Out[467]: Period('2007-12', 'M')
你可以将Period('2007','A-DEC')看做一个被划分为多个月度时期的时间段中的游标。图10-1对此进行了说明。对于一个不以12月结束的财政年度,月度子时期的归属情况就不一样了:
- In [468]: p = pd.Period('2007', freq='A-JUN')
- In [469]: p.asfreq('M', 'start') In [470]: p.asfreq('M', 'end')
- Out[469]: Period('2007-06', 'M') Out[470]: Period('2007-06', 'M')
在将高频率转换为低频率时,超时期(superperiod)是由子时期(subperiod)所属的位置决定的。例如,在A-JUN频率中,月份“2007年8月”实际上是属于周期“2008年”的:
- In [471]: p = pd.Period('2007-08', 'M')
- In [472]: p.asfreq('A-JUN')
- Out[472]: Period('2008', 'A-JUN')
PeriodIndex或TimeSeries的频率转换方式也是如此:
- In [473]: rng = pd.period_range('2006', '2009', freq='A-DEC')
- In [474]: ts = Series(np.random.randn(len(rng)), index=rng)
- In [475]: ts
- Out[475]:
- 2006 -0.601544
- 2007 0.574265
- 2008 -0.194115
- 2009 0.202225
- Freq: A-DEC
- In [476]: ts.asfreq('M', how='start') In [477]: ts.asfreq('B', how='end')
- Out[476]: Out[477]:
- 2006-01 -0.601544 2006-12-29 -0.601544
- 2007-01 0.574265 2007-12-31 0.574265
- 2008-01 -0.194115 2008-12-31 -0.194115
- 2009-01 0.202225 2009-12-31 0.202225
- Freq: M Freq: B
图10-1:Period频率转换示例
按季度计算的时期频率
季度型数据在会计、金融等领域中很常见。许多季度型数据都会涉及“财年末”的概念,通常是一年12个月中某月的最后一个日历日或工作日。就这一点来说,时期"2012Q4"根据财年末的不同会有不同的含义。pandas支持12种可能的季度型频率,即Q-JAN到Q-DEC:
- In [478]: p = pd.Period('2012Q4', freq='Q-JAN')
- In [479]: p
- Out[479]: Period('2012Q4', 'Q-JAN')
在以1月结束的财年中,2012Q4是从11月到1月(将其转换为日型频率就明白了)。图10-2对此进行了说明:
- In [480]: p.asfreq('D', 'start') In [481]: p.asfreq('D', 'end')
- Out[480]: Period('2011-11-01', 'D') Out[481]: Period('2012-01-31', 'D')
因此,Period之间的算术运算会非常简单。例如,要获取该季度倒数第二个工作日下午4点的时间戳,你可以这样:
- In [482]: p4pm = (p.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
- In [483]: p4pm
- Out[483]: Period('2012-01-30 16:00', 'T')
- In [484]: p4pm.to_timestamp()
- Out[484]: <Timestamp: 2012-01-30 16:00:00>
图10-2:不同季度型频率之间的转换
period_range还可用于生成季度型范围。季度型范围的算术运算也跟上面是一样的:
- In [485]: rng = pd.period_range('2011Q3', '2012Q4', freq='Q-JAN')
- In [486]: ts = Series(np.arange(len(rng)), index=rng)
- In [487]: ts
- Out[487]:
- 2011Q3 0
- 2011Q4 1
- 2012Q1 2
- 2012Q2 3
- 2012Q3 4
- 2012Q4 5
- Freq: Q-JAN
- In [488]: new_rng = (rng.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
- In [489]: ts.index = new_rng.to_timestamp()
- In [490]: ts
- Out[490]:
- 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
将Timestamp转换为Period(及其反向过程)
通过使用to_period方法,可以将由时间戳索引的Series和DataFrame对象转换为以时期索引:
- In [491]: rng = pd.date_range('1/1/2000', periods=3, freq='M')
- In [492]: ts = Series(randn(3), index=rng)
- In [493]: pts = ts.to_period()
- In [494]: ts
- Out[494]:
- 2000-01-31 -0.505124
- 2000-02-29 2.954439
- 2000-03-31 -2.630247
- Freq: M
- In [495]: pts
- Out[495]:
- 2000-01 -0.505124
- 2000-02 2.954439
- 2000-03 -2.630247
- Freq: M
由于时期指的是非重叠时间区间,因此对于给定的频率,一个时间戳只能属于一个时期。新PeriodIndex的频率默认是从时间戳推断而来的,你也可以指定任何别的频率。结果中允许存在重复时期:
- In [496]: rng = pd.date_range('1/29/2000', periods=6, freq='D')
- In [497]: ts2 = Series(randn(6), index=rng)
- In [498]: ts2.to_period('M')
- Out[498]:
- 2000-01 -0.352453
- 2000-01 -0.477808
- 2000-01 0.161594
- 2000-02 1.686833
- 2000-02 0.821965
- 2000-02 -0.667406
- Freq: M
要转换为时间戳,使用to_timestamp即可:
- In [499]: pts = ts.to_period()
- In [500]: pts
- Out[500]:
- 2000-01 -0.505124
- 2000-02 2.954439
- 2000-03 -2.630247
- Freq: M
- In [501]: pts.to_timestamp(how='end')
- Out[501]:
- 2000-01-31 -0.505124
- 2000-02-29 2.954439
- 2000-03-31 -2.630247
- Freq: M
通过数组创建PeriodIndex
固定频率的数据集通常会将时间信息分开存放在多个列中。例如,在下面这个宏观经济数据集中,年度和季度就分别存放在不同的列中:
- In [502]: data = pd.read_csv('ch08/macrodata.csv')
- In [503]: data.year In [504]: data.quarter
- Out[503]: Out[504]:
- 0 1959 0 1
- 1 1959 1 2
- 2 1959 2 3
- 3 1959 3 4
- ... ...
- 199 2008 199 4
- 200 2009 200 1
- 201 2009 201 2
- 202 2009 202 3
- Name: year, Length: 203 Name: quarter, Length: 203
将这两个数组以及一个频率传入PeriodIndex,就可以将它们合并成DataFrame的一个索引:
- In [505]: index = pd.PeriodIndex(year=data.year, quarter=data.quarter, freq='Q-DEC')
- In [506]: index
- Out[506]:
- <class 'pandas.tseries.period.PeriodIndex'>
- freq: Q-DEC
- [1959Q1, ..., 2009Q3]
- length: 203
- In [507]: data.index = index
- In [508]: data.infl
- Out[508]:
- 1959Q1 0.00
- 1959Q2 2.34
- 1959Q3 2.74
- 1959Q4 0.27
- ...
- 2008Q4 -8.79
- 2009Q1 0.94
- 2009Q2 3.37
- 2009Q3 3.56
- Freq: Q-DEC, Name: infl, Length: 203