11.5 时期及其算术运算

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

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

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

  1. In [151]: p + 5
  2. Out[151]: Period('2012', 'A-DEC')
  3. In [152]: p - 2
  4. Out[152]: Period('2005', 'A-DEC')

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

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

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

  1. In [154]: rng = pd.period_range('2000-01-01', '2000-06-30', freq='M')
  2. In [155]: rng
  3. Out[155]: PeriodIndex(['2000-01', '2000-02', '2000-03', '2000-04', '2000-05', '20
  4. 00-06'], dtype='period[M]', freq='M')

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

  1. In [156]: pd.Series(np.random.randn(6), index=rng)
  2. Out[156]:
  3. 2000-01 -0.514551
  4. 2000-02 -0.559782
  5. 2000-03 -0.783408
  6. 2000-04 -1.797685
  7. 2000-05 -0.172670
  8. 2000-06 0.680215
  9. Freq: M, dtype: float64

如果你有一个字符串数组,你也可以使用PeriodIndex类:

  1. In [157]: values = ['2001Q3', '2002Q2', '2003Q1']
  2. In [158]: index = pd.PeriodIndex(values, freq='Q-DEC')
  3. In [159]: index
  4. Out[159]: PeriodIndex(['2001Q3', '2002Q2', '2003Q1'], dtype='period[Q-DEC]', freq
  5. ='Q-DEC')

时期的频率转换

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

  1. In [160]: p = pd.Period('2007', freq='A-DEC')
  2. In [161]: p
  3. Out[161]: Period('2007', 'A-DEC')
  4. In [162]: p.asfreq('M', how='start')
  5. Out[162]: Period('2007-01', 'M')
  6. In [163]: p.asfreq('M', how='end')
  7. Out[163]: Period('2007-12', 'M')

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

  1. In [164]: p = pd.Period('2007', freq='A-JUN')
  2. In [165]: p
  3. Out[165]: Period('2007', 'A-JUN')
  4. In [166]: p.asfreq('M', 'start')
  5. Out[166]: Period('2006-07', 'M')
  6. In [167]: p.asfreq('M', 'end')
  7. Out[167]: Period('2007-06', 'M')

图11-1 Period频率转换示例

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

  1. In [168]: p = pd.Period('Aug-2007', 'M')
  2. In [169]: p.asfreq('A-JUN')
  3. Out[169]: Period('2008', 'A-JUN')

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

  1. In [170]: rng = pd.period_range('2006', '2009', freq='A-DEC')
  2. In [171]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
  3. In [172]: ts
  4. Out[172]:
  5. 2006 1.607578
  6. 2007 0.200381
  7. 2008 -0.834068
  8. 2009 -0.302988
  9. Freq: A-DEC, dtype: float64
  10. In [173]: ts.asfreq('M', how='start')
  11. Out[173]:
  12. 2006-01 1.607578
  13. 2007-01 0.200381
  14. 2008-01 -0.834068
  15. 2009-01 -0.302988
  16. Freq: M, dtype: float64

这里,根据年度时期的第一个月,每年的时期被取代为每月的时期。如果我们想要每年的最后一个工作日,我们可以使用“B”频率,并指明想要该时期的末尾:

  1. In [174]: ts.asfreq('B', how='end')
  2. Out[174]:
  3. 2006-12-29 1.607578
  4. 2007-12-31 0.200381
  5. 2008-12-31 -0.834068
  6. 2009-12-31 -0.302988
  7. Freq: B, dtype: float64

按季度计算的时期频率

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

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

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

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

图11.2 不同季度型频率之间的转换

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

  1. In [179]: p4pm = (p.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
  2. In [180]: p4pm
  3. Out[180]: Period('2012-01-30 16:00', 'T')
  4. In [181]: p4pm.to_timestamp()
  5. Out[181]: Timestamp('2012-01-30 16:00:00')

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

  1. In [182]: rng = pd.period_range('2011Q3', '2012Q4', freq='Q-JAN')
  2. In [183]: ts = pd.Series(np.arange(len(rng)), index=rng)
  3. In [184]: ts
  4. Out[184]:
  5. 2011Q3 0
  6. 2011Q4 1
  7. 2012Q1 2
  8. 2012Q2 3
  9. 2012Q3 4
  10. 2012Q4 5
  11. Freq: Q-JAN, dtype: int64
  12. In [185]: new_rng = (rng.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
  13. In [186]: ts.index = new_rng.to_timestamp()
  14. In [187]: ts
  15. Out[187]:
  16. 2010-10-28 16:00:00 0
  17. 2011-01-28 16:00:00 1
  18. 2011-04-28 16:00:00 2
  19. 2011-07-28 16:00:00 3
  20. 2011-10-28 16:00:00 4
  21. 2012-01-30 16:00:00 5
  22. dtype: int64

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

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

  1. In [188]: rng = pd.date_range('2000-01-01', periods=3, freq='M')
  2. In [189]: ts = pd.Series(np.random.randn(3), index=rng)
  3. In [190]: ts
  4. Out[190]:
  5. 2000-01-31 1.663261
  6. 2000-02-29 -0.996206
  7. 2000-03-31 1.521760
  8. Freq: M, dtype: float64
  9. In [191]: pts = ts.to_period()
  10. In [192]: pts
  11. Out[192]:
  12. 2000-01 1.663261
  13. 2000-02 -0.996206
  14. 2000-03 1.521760
  15. Freq: M, dtype: float64

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

  1. In [193]: rng = pd.date_range('1/29/2000', periods=6, freq='D')
  2. In [194]: ts2 = pd.Series(np.random.randn(6), index=rng)
  3. In [195]: ts2
  4. Out[195]:
  5. 2000-01-29 0.244175
  6. 2000-01-30 0.423331
  7. 2000-01-31 -0.654040
  8. 2000-02-01 2.089154
  9. 2000-02-02 -0.060220
  10. 2000-02-03 -0.167933
  11. Freq: D, dtype: float64
  12. In [196]: ts2.to_period('M')
  13. Out[196]:
  14. 2000-01 0.244175
  15. 2000-01 0.423331
  16. 2000-01 -0.654040
  17. 2000-02 2.089154
  18. 2000-02 -0.060220
  19. 2000-02 -0.167933
  20. Freq: M, dtype: float64

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

  1. In [197]: pts = ts2.to_period()
  2. In [198]: pts
  3. Out[198]:
  4. 2000-01-29 0.244175
  5. 2000-01-30 0.423331
  6. 2000-01-31 -0.654040
  7. 2000-02-01 2.089154
  8. 2000-02-02 -0.060220
  9. 2000-02-03 -0.167933
  10. Freq: D, dtype: float64
  11. In [199]: pts.to_timestamp(how='end')
  12. Out[199]:
  13. 2000-01-29 0.244175
  14. 2000-01-30 0.423331
  15. 2000-01-31 -0.654040
  16. 2000-02-01 2.089154
  17. 2000-02-02 -0.060220
  18. 2000-02-03 -0.167933
  19. Freq: D, dtype: float64

通过数组创建PeriodIndex

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

  1. In [200]: data = pd.read_csv('examples/macrodata.csv')
  2. In [201]: data.head(5)
  3. Out[201]:
  4. year quarter realgdp realcons realinv realgovt realdpi cpi \
  5. 0 1959.0 1.0 2710.349 1707.4 286.898 470.045 1886.9 28.98
  6. 1 1959.0 2.0 2778.801 1733.7 310.859 481.301 1919.7 29.15
  7. 2 1959.0 3.0 2775.488 1751.8 289.226 491.260 1916.4 29.35
  8. 3 1959.0 4.0 2785.204 1753.7 299.356 484.052 1931.3 29.37
  9. 4 1960.0 1.0 2847.699 1770.5 331.722 462.199 1955.5 29.54
  10. m1 tbilrate unemp pop infl realint
  11. 0 139.7 2.82 5.8 177.146 0.00 0.00
  12. 1 141.7 3.08 5.1 177.830 2.34 0.74
  13. 2 140.5 3.82 5.3 178.657 2.74 1.09
  14. 3 140.0 4.33 5.6 179.386 0.27 4.06
  15. 4 139.6 3.50 5.2 180.007 2.31 1.19
  16. In [202]: data.year
  17. Out[202]:
  18. 0 1959.0
  19. 1 1959.0
  20. 2 1959.0
  21. 3 1959.0
  22. 4 1960.0
  23. 5 1960.0
  24. 6 1960.0
  25. 7 1960.0
  26. 8 1961.0
  27. 9 1961.0
  28. ...
  29. 193 2007.0
  30. 194 2007.0
  31. 195 2007.0
  32. 196 2008.0
  33. 197 2008.0
  34. 198 2008.0
  35. 199 2008.0
  36. 200 2009.0
  37. 201 2009.0
  38. 202 2009.0
  39. Name: year, Length: 203, dtype: float64
  40. In [203]: data.quarter
  41. Out[203]:
  42. 0 1.0
  43. 1 2.0
  44. 2 3.0
  45. 3 4.0
  46. 4 1.0
  47. 5 2.0
  48. 6 3.0
  49. 7 4.0
  50. 8 1.0
  51. 9 2.0
  52. ...
  53. 193 2.0
  54. 194 3.0
  55. 195 4.0
  56. 196 1.0
  57. 197 2.0
  58. 198 3.0
  59. 199 4.0
  60. 200 1.0
  61. 201 2.0
  62. 202 3.0
  63. Name: quarter, Length: 203, dtype: float64

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

  1. In [204]: index = pd.PeriodIndex(year=data.year, quarter=data.quarter,
  2. .....: freq='Q-DEC')
  3. In [205]: index
  4. Out[205]:
  5. PeriodIndex(['1959Q1', '1959Q2', '1959Q3', '1959Q4', '1960Q1', '1960Q2',
  6. '1960Q3', '1960Q4', '1961Q1', '1961Q2',
  7. ...
  8. '2007Q2', '2007Q3', '2007Q4', '2008Q1', '2008Q2', '2008Q3',
  9. '2008Q4', '2009Q1', '2009Q2', '2009Q3'],
  10. dtype='period[Q-DEC]', length=203, freq='Q-DEC')
  11. In [206]: data.index = index
  12. In [207]: data.infl
  13. Out[207]:
  14. 1959Q1 0.00
  15. 1959Q2 2.34
  16. 1959Q3 2.74
  17. 1959Q4 0.27
  18. 1960Q1 2.31
  19. 1960Q2 0.14
  20. 1960Q3 2.70
  21. 1960Q4 1.21
  22. 1961Q1 -0.40
  23. 1961Q2 1.47
  24. ...
  25. 2007Q2 2.75
  26. 2007Q3 3.45
  27. 2007Q4 6.38
  28. 2008Q1 2.82
  29. 2008Q2 8.53
  30. 2008Q3 -3.16
  31. 2008Q4 -8.79
  32. 2009Q1 0.94
  33. 2009Q2 3.37
  34. 2009Q3 3.56
  35. Freq: Q-DEC, Name: infl, Length: 203, dtype: float64