时期及其算术运算

时期(period)表示的是时间区间,比如数日、数月、数季、数年等。Period类所表示的就是这种数据类型,其构造函数需要用到一个字符串或整数,以及表10-4中的频率:

  1. In [454]: p = pd.Period(2007, freq='A-DEC')
  2.  
  3. In [455]: p
  4. Out[455]: Period('2007', 'A-DEC')

这个Period对象表示的是从2007年1月1日到2007年12月31日之间的整段时间。只需对Period对象加上或减去一个整数即可达到根据其频率进行位移的效果:

  1. In [456]: p + 5 In [457]: p - 2
  2. Out[456]: Period('2012', 'A-DEC') Out[457]: Period('2005', 'A-DEC')

如果两个Period对象拥有相同的频率,则它们的差就是它们之间的单位数量:

  1. In [458]: pd.Period('2014', freq='A-DEC') - p
  2. Out[458]: 7

period_range函数可用于创建规则的时期范围:

  1. In [459]: rng = pd.period_range('1/1/2000', '6/30/2000', freq='M')
  2.  
  3. In [460]: rng
  4. Out[460]:
  5. <class 'pandas.tseries.period.PeriodIndex'>
  6. freq: M
  7. [2000-01, ..., 2000-06]
  8. length: 6

PeriodIndex类保存了一组Period,它可以在任何pandas数据结构中被用作轴索引:

  1. In [461]: Series(np.random.randn(6), index=rng)
  2. Out[461]:
  3. 2000-01 -0.309119
  4. 2000-02 0.028558
  5. 2000-03 1.129605
  6. 2000-04 -0.374173
  7. 2000-05 -0.011401
  8. 2000-06 0.272924
  9. Freq: M

PeriodIndex类的构造函数还允许直接使用一组字符串:

  1. In [462]: values = ['2001Q3', '2002Q2', '2003Q1']
  2.  
  3. In [463]: index = pd.PeriodIndex(values, freq='Q-DEC')
  4.  
  5. In [464]: index
  6. Out[464]:
  7. <class 'pandas.tseries.period.PeriodIndex'>
  8. freq: Q-DEC
  9. [2001Q3, ..., 2003Q1]
  10. length: 3

时期的频率转换

Period和PeriodIndex对象都可以通过其asfreq方法被转换成别的频率。假设我们有一个年度时期,希望将其转换为当年年初或年末的一个月度时期。该任务非常简单:

  1. In [465]: p = pd.Period('2007', freq='A-DEC')
  2.  
  3. In [466]: p.asfreq('M', how='start') In [467]: p.asfreq('M', how='end')
  4. Out[466]: Period('2007-01', 'M') Out[467]: Period('2007-12', 'M')

你可以将Period('2007','A-DEC')看做一个被划分为多个月度时期的时间段中的游标。图10-1对此进行了说明。对于一个不以12月结束的财政年度,月度子时期的归属情况就不一样了:

  1. In [468]: p = pd.Period('2007', freq='A-JUN')
  2.  
  3. In [469]: p.asfreq('M', 'start') In [470]: p.asfreq('M', 'end')
  4. Out[469]: Period('2007-06', 'M') Out[470]: Period('2007-06', 'M')

在将高频率转换为低频率时,超时期(superperiod)是由子时期(subperiod)所属的位置决定的。例如,在A-JUN频率中,月份“2007年8月”实际上是属于周期“2008年”的:

  1. In [471]: p = pd.Period('2007-08', 'M')
  2.  
  3. In [472]: p.asfreq('A-JUN')
  4. Out[472]: Period('2008', 'A-JUN')

PeriodIndex或TimeSeries的频率转换方式也是如此:

  1. In [473]: rng = pd.period_range('2006', '2009', freq='A-DEC')
  2.  
  3. In [474]: ts = Series(np.random.randn(len(rng)), index=rng)
  4.  
  5. In [475]: ts
  6. Out[475]:
  7. 2006 -0.601544
  8. 2007 0.574265
  9. 2008 -0.194115
  10. 2009 0.202225
  11. Freq: A-DEC
  12.  
  13. In [476]: ts.asfreq('M', how='start') In [477]: ts.asfreq('B', how='end')
  14. Out[476]: Out[477]:
  15. 2006-01 -0.601544 2006-12-29 -0.601544
  16. 2007-01 0.574265 2007-12-31 0.574265
  17. 2008-01 -0.194115 2008-12-31 -0.194115
  18. 2009-01 0.202225 2009-12-31 0.202225
  19. Freq: M Freq: B

时期及其算术运算 - 图1

图10-1:Period频率转换示例

按季度计算的时期频率

季度型数据在会计、金融等领域中很常见。许多季度型数据都会涉及“财年末”的概念,通常是一年12个月中某月的最后一个日历日或工作日。就这一点来说,时期"2012Q4"根据财年末的不同会有不同的含义。pandas支持12种可能的季度型频率,即Q-JAN到Q-DEC:

  1. In [478]: p = pd.Period('2012Q4', freq='Q-JAN')
  2.  
  3. In [479]: p
  4. Out[479]: Period('2012Q4', 'Q-JAN')

在以1月结束的财年中,2012Q4是从11月到1月(将其转换为日型频率就明白了)。图10-2对此进行了说明:

  1. In [480]: p.asfreq('D', 'start') In [481]: p.asfreq('D', 'end')
  2. Out[480]: Period('2011-11-01', 'D') Out[481]: Period('2012-01-31', 'D')

因此,Period之间的算术运算会非常简单。例如,要获取该季度倒数第二个工作日下午4点的时间戳,你可以这样:

  1. In [482]: p4pm = (p.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
  2.  
  3. In [483]: p4pm
  4. Out[483]: Period('2012-01-30 16:00', 'T')
  5.  
  6. In [484]: p4pm.to_timestamp()
  7. Out[484]: <Timestamp: 2012-01-30 16:00:00>

时期及其算术运算 - 图2

图10-2:不同季度型频率之间的转换

period_range还可用于生成季度型范围。季度型范围的算术运算也跟上面是一样的:

  1. In [485]: rng = pd.period_range('2011Q3', '2012Q4', freq='Q-JAN')
  2.  
  3. In [486]: ts = Series(np.arange(len(rng)), index=rng)
  4.  
  5. In [487]: ts
  6. Out[487]:
  7. 2011Q3 0
  8. 2011Q4 1
  9. 2012Q1 2
  10. 2012Q2 3
  11. 2012Q3 4
  12. 2012Q4 5
  13. Freq: Q-JAN
  14.  
  15. In [488]: new_rng = (rng.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
  16.  
  17. In [489]: ts.index = new_rng.to_timestamp()
  18.  
  19. In [490]: ts
  20. Out[490]:
  21. 2010-10-28 16:00:00 0
  22. 2011-01-28 16:00:00 1
  23. 2011-04-28 16:00:00 2
  24. 2011-07-28 16:00:00 3
  25. 2011-10-28 16:00:00 4
  26. 2012-01-30 16:00:00 5

将Timestamp转换为Period(及其反向过程)

通过使用to_period方法,可以将由时间戳索引的Series和DataFrame对象转换为以时期索引:

  1. In [491]: rng = pd.date_range('1/1/2000', periods=3, freq='M')
  2.  
  3. In [492]: ts = Series(randn(3), index=rng)
  4.  
  5. In [493]: pts = ts.to_period()
  6.  
  7. In [494]: ts
  8. Out[494]:
  9. 2000-01-31 -0.505124
  10. 2000-02-29 2.954439
  11. 2000-03-31 -2.630247
  12. Freq: M
  13. In [495]: pts
  14. Out[495]:
  15. 2000-01 -0.505124
  16. 2000-02 2.954439
  17. 2000-03 -2.630247
  18. Freq: M

由于时期指的是非重叠时间区间,因此对于给定的频率,一个时间戳只能属于一个时期。新PeriodIndex的频率默认是从时间戳推断而来的,你也可以指定任何别的频率。结果中允许存在重复时期:

  1. In [496]: rng = pd.date_range('1/29/2000', periods=6, freq='D')
  2.  
  3. In [497]: ts2 = Series(randn(6), index=rng)
  4.  
  5. In [498]: ts2.to_period('M')
  6. Out[498]:
  7. 2000-01 -0.352453
  8. 2000-01 -0.477808
  9. 2000-01 0.161594
  10. 2000-02 1.686833
  11. 2000-02 0.821965
  12. 2000-02 -0.667406
  13. Freq: M

要转换为时间戳,使用to_timestamp即可:

  1. In [499]: pts = ts.to_period()
  2.  
  3. In [500]: pts
  4. Out[500]:
  5. 2000-01 -0.505124
  6. 2000-02 2.954439
  7. 2000-03 -2.630247
  8. Freq: M
  9.  
  10. In [501]: pts.to_timestamp(how='end')
  11. Out[501]:
  12. 2000-01-31 -0.505124
  13. 2000-02-29 2.954439
  14. 2000-03-31 -2.630247
  15. Freq: M

通过数组创建PeriodIndex

固定频率的数据集通常会将时间信息分开存放在多个列中。例如,在下面这个宏观经济数据集中,年度和季度就分别存放在不同的列中:

  1. In [502]: data = pd.read_csv('ch08/macrodata.csv')
  2.  
  3. In [503]: data.year In [504]: data.quarter
  4. Out[503]: Out[504]:
  5. 0 1959 0 1
  6. 1 1959 1 2
  7. 2 1959 2 3
  8. 3 1959 3 4
  9. ... ...
  10. 199 2008 199 4
  11. 200 2009 200 1
  12. 201 2009 201 2
  13. 202 2009 202 3
  14. Name: year, Length: 203 Name: quarter, Length: 203

将这两个数组以及一个频率传入PeriodIndex,就可以将它们合并成DataFrame的一个索引:

  1. In [505]: index = pd.PeriodIndex(year=data.year, quarter=data.quarter, freq='Q-DEC')
  2.  
  3. In [506]: index
  4. Out[506]:
  5. <class 'pandas.tseries.period.PeriodIndex'>
  6. freq: Q-DEC
  7. [1959Q1, ..., 2009Q3]
  8. length: 203
  9.  
  10. In [507]: data.index = index
  11.  
  12. In [508]: data.infl
  13. Out[508]:
  14. 1959Q1 0.00
  15. 1959Q2 2.34
  16. 1959Q3 2.74
  17. 1959Q4 0.27
  18. ...
  19. 2008Q4 -8.79
  20. 2009Q1 0.94
  21. 2009Q2 3.37
  22. 2009Q3 3.56
  23. Freq: Q-DEC, Name: infl, Length: 203