时间序列 Pandas
时间序列(time series)数据是一种重要的结构化数据形式,应用于多个领域,包括金融学、经济学、生态学、神经科学、物理学等。在多个时间点观察或测量到的任何事物都可以形成一段时间序列。很多时间序列是固定频率的,也就是说,数据点是根据某种规律定期出现的(比如每15秒、每5分钟、每月出现一次)。时间序列也可以是不定期的,没有固定的时间单位或单位之间的偏移量。时间序列数据的意义取决于具体的应用场景,主要有以下几种:

  • 时间戳(timestamp),特定的时刻。
  • 固定时期(period),如2008年1月或2020年全年。
  • 时间间隔(interval),由起始和结束时间戳表示。时期(period)可以被看做间隔(interval)的特例。

本文内容包括,索引、选取、子集构造,日期的范围、频率以及移动基础等。
首先回顾下datetime模块

  1. >>> from datetime import datetime
  2. >>> dates = [datetime(2011, 1, 2), datetime(2011, 1, 5),
  3. ... datetime(2011, 1, 7), datetime(2011, 1, 8),
  4. ... datetime(2011, 1, 10), datetime(2011, 1, 12)]
  5. >>> ts = pd.Series(np.random.randn(6),index = dates)
  6. >>> ts
  7. 2011-01-02 -0.162712
  8. 2011-01-05 1.876604
  9. 2011-01-07 -0.016393
  10. 2011-01-08 0.239276
  11. 2011-01-10 -0.704732
  12. 2011-01-12 -1.502936
  13. dtype: float64
  14. # 这些datetime对象实际上是被放在一个DatetimeIndex中
  15. >>> ts.index
  16. DatetimeIndex(['2011-01-02', '2011-01-05', '2011-01-07',
  17. '2011-01-08','2011-01-10', '2011-01-12']
  18. ,dtype='datetime64[ns]', freq=None)
  19. # DatetimeIndex中的各个标量值是pandas的Timestamp对象
  20. >>> stamp = ts.index[0]
  21. >>> stamp
  22. Timestamp('2011-01-02 00:00:00')

索引、选取、子集构造

根据标签索引

  1. >>> stamp = ts.index[2]
  2. >>> stamp
  3. Timestamp('2011-01-07 00:00:00')

传入一个可以被解释为日期的字符串

  1. >>> ts['1/10/2011']
  2. -0.7047322514407551
  3. >>> ts['20110110']
  4. -0.7047322514407551

对于较长的时间序列,只需传入“年”或“年月”即可轻松选取数据的切片

  1. >>> long_ts = pd.Series(np.random.randn(1000)
  2. ,index = pd.date_range('01/01/2000'
  3. ,periods = 1000))
  4. >>> long_ts
  5. 2000-01-01 1.054099
  6. 2000-01-02 -1.554446
  7. 2000-01-03 0.576552
  8. 2000-01-04 0.785388
  9. 2000-01-05 -1.674210
  10. ...
  11. 2002-09-22 -0.836620
  12. 2002-09-23 0.561945
  13. 2002-09-24 -0.196348
  14. 2002-09-25 -0.937816
  15. 2002-09-26 -0.431839
  16. Freq: D, Length: 1000, dtype: float64
  17. >>> long_ts['2002']
  18. 2002-01-01 -1.084543
  19. 2002-01-02 -2.452728
  20. 2002-01-03 0.415411
  21. 2002-01-04 1.284758
  22. 2002-01-05 -0.449588
  23. ...
  24. 2002-09-22 -0.836620
  25. 2002-09-23 0.561945
  26. 2002-09-24 -0.196348
  27. 2002-09-25 -0.937816
  28. 2002-09-26 -0.431839
  29. Freq: D, Length: 269, dtype: float64
  30. >>> long_ts['2002-08']
  31. 2002-08-01 1.061041
  32. 2002-08-02 0.181386
  33. 2002-08-03 0.697954
  34. 2002-08-04 0.729076
  35. 2002-08-05 -0.573171
  36. ...
  37. 2002-08-27 -1.240861
  38. 2002-08-28 0.397729
  39. 2002-08-29 -2.765702
  40. 2002-08-30 -2.228761
  41. 2002-08-31 -0.386724
  42. Freq: D, dtype: float64

datetime对象也可以进行切

  1. >>> long_ts[datetime(2001,5,10)::100]
  2. 2001-05-10 -1.412274
  3. 2001-08-18 -0.051782
  4. 2001-11-26 -0.948275
  5. 2002-03-06 0.565756
  6. 2002-06-14 0.040260
  7. 2002-09-22 -0.836620
  8. Freq: 100D, dtype: float64
  9. >>> '''由于大部分时间序列数据都是按照时间先后排序的,
  10. ... 因此你也可以用不存在于该时间序列中的时间戳对其进行切片(即范围查询)'''
  11. >>> ts['1/6/2011':'1/11/2011']
  12. 2011-01-07 -0.016393
  13. 2011-01-08 0.239276
  14. 2011-01-10 -0.704732
  15. dtype: float64

truncate()等价的实例方法也可以截取两个日期之间TimeSeries:

  1. >>> ts.truncate(after = '1/9/2011')
  2. 2011-01-02 -0.162712
  3. 2011-01-05 1.876604
  4. 2011-01-07 -0.016393
  5. 2011-01-08 0.239276
  6. dtype: float64

对DataFrame的行进行索引

  1. >>> dates = pd.date_range('2/1/2020', periods = 10, freq = 'W-WED')
  2. >>> dates_1 = pd.date_range('2/1/2020', periods = 10, freq = 'W-SUN')
  3. >>> dates
  4. DatetimeIndex(['2020-02-05', '2020-02-12', '2020-02-19', '2020-02-26',
  5. '2020-03-04', '2020-03-11', '2020-03-18', '2020-03-25',
  6. '2020-04-01', '2020-04-08'],
  7. dtype='datetime64[ns]', freq='W-WED')
  8. >>> dates_1
  9. DatetimeIndex(['2020-02-02', '2020-02-09', '2020-02-16', '2020-02-23',
  10. '2020-03-01', '2020-03-08', '2020-03-15', '2020-03-22',
  11. '2020-03-29', '2020-04-05'],
  12. dtype='datetime64[ns]', freq='W-SUN')
  13. >>> long_df = pd.DataFrame(np.random.randn(10, 4)
  14. , index=dates
  15. , columns=['Colorado', 'Texas','New York', 'Ohio'])
  16. >>> long_df

Colorado TexasNew York New YorkOhio Ohio
2020-02-02 -0.029801 0.514341 -0.141655 1.715595
2020-02-09 -0.824540 -0.296288 -0.827877 0.245774
2020-02-16 -0.084484 0.126171 0.423891 -1.709223
2020-02-23 -1.688280 1.032951 -0.062944 -0.521174
2020-03-01 0.322167 0.226218 0.515736 0.098784
2020-03-08 2.117617 1.840786 -0.283062 -1.414009
2020-03-15 -0.853829 0.101525 0.395773 -0.938408
2020-03-22 0.022767 -1.761823 0.361345 -1.015794
2020-03-29 -1.075985 -0.403549 2.053879 -0.075490
2020-04-05 -0.468759 1.591693 0.012494 1.013237

同样只需传入“年”或“年月”即可轻松选取数据的切片

  1. long_df.loc['3-2020']

Colorado Texas New York Ohio
2020-03-01 -0.983091 -0.359375 0.014695 1.123173
2020-03-08 0.506222 -0.188421 -1.298069 -1.769189
2020-03-15 -1.773529 -1.664837 0.045604 0.365199
2020-03-22 -2.059035 -1.386787 0.402163 -1.967558
2020-03-29 -1.254208 -0.272317 -1.595532 1.349384

带有重复索引的时间序列

  1. >>> dates = pd.DatetimeIndex(['1/1/2000', '1/2/2000', '1/2/2000','1/2/2000', '1/3/2000'])
  2. >>> dup_ts = pd.Series(np.arange(5), index = dates)
  3. >>> dup_ts
  4. 2000-01-01 0
  5. 2000-01-02 1
  6. 2000-01-02 2
  7. 2000-01-02 3
  8. 2000-01-03 4
  9. dtype: int64
  10. >>> dup_ts.index.is_unique
  11. False
  12. >>> dup_ts['1/2/2000']
  13. 2000-01-02 1
  14. 2000-01-02 2
  15. 2000-01-02 3
  16. dtype: int64
  17. >>> """假设你想要对具有非唯一时间戳的数据进行聚合。
  18. ... 一个办法是使用groupby,并传入level=0 """
  19. >>> group = dup_ts.groupby(level=0)
  20. >>> group.mean()
  21. 2000-01-01 0
  22. 2000-01-02 2
  23. 2000-01-03 4
  24. dtype: int64

日期的范围、频率以及移动

pandas中的原生时间序列一般被认为是不规则的,也就是说,它们没有固定的频率。对于大部分应用程序而言,这是无所谓的。但是,它常常需要以某种相对固定 的频率进行分析,比如每日、每月、每15分钟等(这样自然会在时间序列中引入缺失值)。幸运的是,pandas有一整套标准时间序列频率以及用于重采样、频率推断、生成固定频率日期范围的工具。例如,可以将之前那个时间序列转换为一 个具有固定频率(每日)的时间序列,只需调用resample即可


pandas.date_range() 生成日期范围

  • pandas.date_range可用于根据指定的频率生成指定长度的DatetimeIndex
  • 默认情况下,date_range会产生按天计算的时间点。
  • 如果只传入起始或结束日期,那就还得传入一个表示一段时间的数字,起始和结束日期定义了日期索引的严格边界 ```python

    pd.date_range(start=’2012-04-01’, periods=20) DatetimeIndex([‘2012-04-01’, ‘2012-04-02’, ‘2012-04-03’, ‘2012-04-04’,

    1. '2012-04-05', '2012-04-06', '2012-04-07', '2012-04-08',
    2. '2012-04-09', '2012-04-10', '2012-04-11', '2012-04-12',
    3. '2012-04-13', '2012-04-14', '2012-04-15', '2012-04-16',
    4. '2012-04-17', '2012-04-18', '2012-04-19', '2012-04-20'],
    5. dtype='datetime64[ns]', freq='D')

pd.date_range(end =’2012-04-01’, periods=20) DatetimeIndex([‘2012-03-13’, ‘2012-03-14’, ‘2012-03-15’, ‘2012-03-16’, ‘2012-03-17’, ‘2012-03-18’, ‘2012-03-19’, ‘2012-03-20’, ‘2012-03-21’, ‘2012-03-22’, ‘2012-03-23’, ‘2012-03-24’, ‘2012-03-25’, ‘2012-03-26’, ‘2012-03-27’, ‘2012-03-28’, ‘2012-03-29’, ‘2012-03-30’, ‘2012-03-31’, ‘2012-04-01’], dtype=’datetime64[ns]’, freq=’D’) ``` 如果想要生成一个由每月最后一个工作日组成的日期索引,可以传入”BM”频率(表示business end of month,下表是频率列表),这样就只会包含时间间隔内(或刚好在边界上的)符合频率要求的日期:

别名 便宜量类型 说明
D Day 每日历日
B BusinessDay 每工作日
H Hour 每小时
T 或 min Minute 每分
S Second 每秒
L或ms Milli 每毫秒(即千万分之一秒)
U Micro 每微秒(即百万分之一秒)
M MonthEnd 每月最后一个日历日
BM BusinessMonthEnd 每月最后一个工作日
MS MonthBegin 每月第一个日历日
BMS BusinessMonthBegin 每月第一个工作日
W-MON、W-TUE … Week 从指定的星期几(MON、TUE、WED、THU、FRI、SAT、SUN)开始算起,每周
WON-1MON、WOM-2MON… WeekOfMonth 产生每月第一、第二、第三或第四周的星期即。例如,WOM-3FRI表示每月第3个星期五
Q-JAN、Q-FEB… QuarterEnd 对于以指定月份(JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、EDC)结束的年度,每季度最后一个月的最后一个日历日
BQ-JAN、BQ-FEB… BusinessQuarterEnd 对于以指定月份结束的年度,每季度最后一月的最后一个工作日
QS-JAN、QS-FEB… QuarterBegin 对于以指定月份结束的年度、每季度最后一月的第一个日历日
BQS-JAN、BQS-FEB… BusinessQuarterBegin 对于以指定月份结束的年度、每季度最后一月的第一个工作日
A-JAN、A-FEB… YearEnd 每年指定月份(JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、EDC)的最后一个日历日
BA-JAN、BA-FEB… BusinessYearEnd 每年指定月份的最后一个工作日
AS-JAN、As-FEB YearBegin 每年指定月份的第一个日历日
BAS-JAN、BAS-FEB… BusinessYearnBegin 每年指定月份的第一个工作日

有时,虽然起始和结束日期带有时间信息,但希望产生一组被规范化 (normalize)到午夜的时间戳。normalize选项即可实现该功能:

  1. >>> pd.date_range('2012-05-02 12:56:31', periods=5, normalize=True)
  2. >>> DatetimeIndex(['2012-05-02', '2012-05-03', '2012-05-04', '2012-05-05',
  3. '2012-05-06'],
  4. dtype='datetime64[ns]', freq='D')

频率和日期偏移量

pandas中的频率是由一个基础频率(base frequency)和一个乘数组成的。基础频 率通常以一个字符串别名表示,比如”M”表示每月,”H”表示每小时。对于每个基础 频率,都有一个被称为日期偏移量(date offset)的对象与之对应

  1. >>> from pandas.tseries import offsets
  2. >>> offsets.Hour()
  3. <Hour>
  4. # 传入一个整数即可定义偏移量的倍数:
  5. >>> offsets.Hour(2)
  6. <2 * Hours>
  7. # 大部分偏移量对象都可通过加法进行连接
  8. >>> Hour(2) + Minute(30)
  9. <150 * Minutes>
  10. #在创建日期范围时,给freq传入参数即可实现偏移频率
  11. >>> pd.date_range('2000-01-01', periods=10, freq='1h30min')
  12. DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 01:30:00',
  13. '2000-01-01 03:00:00', '2000-01-01 04:30:00',
  14. '2000-01-01 06:00:00', '2000-01-01 07:30:00',
  15. '2000-01-01 09:00:00', '2000-01-01 10:30:00',
  16. '2000-01-01 12:00:00', '2000-01-01 13:30:00'],
  17. dtype='datetime64[ns]', freq='90T')

WOM 日期 (Week Of Month)

WOM(Week Of Month)是一种非常实用的频率类,它以WOM开头。它使你能获得诸如“每月第3个星期五”之类的日期:

  1. >>> rng = pd.date_range('2012-01-01', '2012-09-01', freq='WOM-3FRI') # 每月第3个星期五
  2. >>> list(rng)
  3. [Timestamp('2012-01-20 00:00:00', freq='WOM-3FRI'),
  4. Timestamp('2012-02-17 00:00:00', freq='WOM-3FRI'),
  5. Timestamp('2012-03-16 00:00:00', freq='WOM-3FRI'),
  6. Timestamp('2012-04-20 00:00:00', freq='WOM-3FRI'),
  7. Timestamp('2012-05-18 00:00:00', freq='WOM-3FRI'),
  8. Timestamp('2012-06-15 00:00:00', freq='WOM-3FRI'),
  9. Timestamp('2012-07-20 00:00:00', freq='WOM-3FRI'),
  10. Timestamp('2012-08-17 00:00:00', freq='WOM-3FRI')]

shfit() — 移动(超前和滞后)数据

移动(shifting)指的是沿着时间轴将数据前移或后移。Series和DataFrame都有一个shift方法用于执行单纯的前移或后移操作,保持索引不变:

  1. >>> ts
  2. 2011-01-02 -0.162712
  3. 2011-01-05 1.876604
  4. 2011-01-07 -0.016393
  5. 2011-01-08 0.239276
  6. 2011-01-10 -0.704732
  7. 2011-01-12 -1.502936
  8. dtype: float64
  9. # 往前移动2格,索引不变
  10. >>> ts.shift(2)
  11. 2011-01-02 NaN
  12. 2011-01-05 NaN
  13. 2011-01-07 -0.162712
  14. 2011-01-08 1.876604
  15. 2011-01-10 -0.016393
  16. 2011-01-12 0.239276
  17. dtype: float64
  18. >>> ts.shift(-2)
  19. 2011-01-02 -0.016393
  20. 2011-01-05 0.239276
  21. 2011-01-07 -0.704732
  22. 2011-01-08 -1.502936
  23. 2011-01-10 NaN
  24. 2011-01-12 NaN
  25. dtype: float64

shift通常用于计算一个时间序列或多个时间序列(如DataFrame的列)中的百分比变化。

  1. >>> ts/ts.shift(1)-1
  2. 2011-01-02 NaN
  3. 2011-01-05 -12.533276
  4. 2011-01-07 -1.008735
  5. 2011-01-08 -15.596514
  6. 2011-01-10 -3.945270
  7. 2011-01-12 1.132634
  8. dtype: float64
  9. >>> ts
  10. 2011-01-02 -0.162712
  11. 2011-01-05 1.876604
  12. 2011-01-07 -0.016393
  13. 2011-01-08 0.239276
  14. 2011-01-10 -0.704732
  15. 2011-01-12 -1.502936
  16. dtype: float64
  17. >>> ts.shift(2)
  18. 2011-01-02 NaN
  19. 2011-01-05 NaN
  20. 2011-01-07 -0.162712
  21. 2011-01-08 1.876604
  22. 2011-01-10 -0.016393
  23. 2011-01-12 0.239276
  24. dtype: float64
  25. >>> ts.shift(2,freq = 'D')
  26. 2011-01-04 -0.162712
  27. 2011-01-07 1.876604
  28. 2011-01-09 -0.016393
  29. 2011-01-10 0.239276
  30. 2011-01-12 -0.704732
  31. 2011-01-14 -1.502936
  32. dtype: float64
  33. >>> ts.shift(2, freq = 'BM')
  34. 2011-02-28 -0.162712
  35. 2011-02-28 1.876604
  36. 2011-02-28 -0.016393
  37. 2011-02-28 0.239276
  38. 2011-02-28 -0.704732
  39. 2011-02-28 -1.502936
  40. dtype: float64

通过偏移量对日期进行位移

  1. >>> from pandas.tseries.offsets import *
  2. >>> now = datetime(2020,2,20)
  3. >>> now + 3 * Day()
  4. Timestamp('2020-02-23 00:00:00')

如果加的是锚点偏移量(比如MonthEnd),第一次增量会将原日期向前滚动到符合频率规则的下一个日期:

  1. >>> now + MonthEnd()
  2. Timestamp('2020-02-29 00:00:00')

通过锚点偏移量的rollforwardrollback方法,可明确地将日期向前或向后“滚动”:

  1. >>> now
  2. datetime.datetime(2020, 2, 20, 0, 0)
  3. >>> me_offset = MonthEnd()
  4. >>> MonthEnd().rollforward(now)
  5. Timestamp('2020-02-29 00:00:00')
  6. >>> me_offset.rollback(now)
  7. Timestamp('2020-01-31 00:00:00')

结合groupby运用‘滚动’

  1. >>> ts = pd.Series(np.random.randn(20), index = pd.date_range('1/15/2020',periods = 20, freq = '5d'))>>> ts
  2. 2020-01-15 1.036827
  3. 2020-01-20 0.013990
  4. 2020-01-25 0.833330
  5. 2020-01-30 0.377223
  6. 2020-02-04 -0.275511
  7. 2020-02-09 -0.331296
  8. 2020-02-14 -0.433451
  9. 2020-02-19 -0.518679
  10. 2020-02-24 0.828722
  11. 2020-02-29 0.158464
  12. 2020-03-05 0.737144
  13. 2020-03-10 0.318404
  14. 2020-03-15 -0.240118
  15. 2020-03-20 -0.910773
  16. 2020-03-25 0.416353
  17. 2020-03-30 -1.265480
  18. 2020-04-04 0.882785
  19. 2020-04-09 2.628588
  20. 2020-04-14 0.777044
  21. 2020-04-19 -1.821877
  22. Freq: 5D, dtype: float64
  23. >>> ts.groupby(MonthEnd().rollforward).mean()
  24. 2020-01-31 0.565343
  25. 2020-02-29 -0.095292
  26. 2020-03-31 -0.157412
  27. 2020-04-30 0.616635
  28. dtype: float64

更简单、更快速地实现该功能的办法是使用resample。

  1. >>> ts.resample('M').mean()
  2. 2020-01-31 0.565343
  3. 2020-02-29 -0.095292
  4. 2020-03-31 -0.157412
  5. 2020-04-30 0.616635
  6. Freq: M, dtype: float64