时间序列 Pandas
时间序列(time series)数据是一种重要的结构化数据形式,应用于多个领域,包括金融学、经济学、生态学、神经科学、物理学等。在多个时间点观察或测量到的任何事物都可以形成一段时间序列。很多时间序列是固定频率的,也就是说,数据点是根据某种规律定期出现的(比如每15秒、每5分钟、每月出现一次)。时间序列也可以是不定期的,没有固定的时间单位或单位之间的偏移量。时间序列数据的意义取决于具体的应用场景,主要有以下几种:
- 时间戳(timestamp),特定的时刻。
- 固定时期(period),如2008年1月或2020年全年。
- 时间间隔(interval),由起始和结束时间戳表示。时期(period)可以被看做间隔(interval)的特例。
本文内容包括,索引、选取、子集构造,日期的范围、频率以及移动基础等。
首先回顾下datetime模块
>>> from datetime import datetime>>> dates = [datetime(2011, 1, 2), datetime(2011, 1, 5),... datetime(2011, 1, 7), datetime(2011, 1, 8),... datetime(2011, 1, 10), datetime(2011, 1, 12)]>>> ts = pd.Series(np.random.randn(6),index = dates)>>> ts2011-01-02 -0.1627122011-01-05 1.8766042011-01-07 -0.0163932011-01-08 0.2392762011-01-10 -0.7047322011-01-12 -1.502936dtype: float64# 这些datetime对象实际上是被放在一个DatetimeIndex中>>> ts.indexDatetimeIndex(['2011-01-02', '2011-01-05', '2011-01-07','2011-01-08','2011-01-10', '2011-01-12'],dtype='datetime64[ns]', freq=None)# DatetimeIndex中的各个标量值是pandas的Timestamp对象>>> stamp = ts.index[0]>>> stampTimestamp('2011-01-02 00:00:00')
索引、选取、子集构造
根据标签索引
>>> stamp = ts.index[2]>>> stampTimestamp('2011-01-07 00:00:00')
传入一个可以被解释为日期的字符串
>>> ts['1/10/2011']-0.7047322514407551>>> ts['20110110']-0.7047322514407551
对于较长的时间序列,只需传入“年”或“年月”即可轻松选取数据的切片
>>> long_ts = pd.Series(np.random.randn(1000),index = pd.date_range('01/01/2000',periods = 1000))>>> long_ts2000-01-01 1.0540992000-01-02 -1.5544462000-01-03 0.5765522000-01-04 0.7853882000-01-05 -1.674210...2002-09-22 -0.8366202002-09-23 0.5619452002-09-24 -0.1963482002-09-25 -0.9378162002-09-26 -0.431839Freq: D, Length: 1000, dtype: float64>>> long_ts['2002']2002-01-01 -1.0845432002-01-02 -2.4527282002-01-03 0.4154112002-01-04 1.2847582002-01-05 -0.449588...2002-09-22 -0.8366202002-09-23 0.5619452002-09-24 -0.1963482002-09-25 -0.9378162002-09-26 -0.431839Freq: D, Length: 269, dtype: float64>>> long_ts['2002-08']2002-08-01 1.0610412002-08-02 0.1813862002-08-03 0.6979542002-08-04 0.7290762002-08-05 -0.573171...2002-08-27 -1.2408612002-08-28 0.3977292002-08-29 -2.7657022002-08-30 -2.2287612002-08-31 -0.386724Freq: D, dtype: float64
datetime对象也可以进行切
>>> long_ts[datetime(2001,5,10)::100]2001-05-10 -1.4122742001-08-18 -0.0517822001-11-26 -0.9482752002-03-06 0.5657562002-06-14 0.0402602002-09-22 -0.836620Freq: 100D, dtype: float64>>> '''由于大部分时间序列数据都是按照时间先后排序的,... 因此你也可以用不存在于该时间序列中的时间戳对其进行切片(即范围查询)'''>>> ts['1/6/2011':'1/11/2011']2011-01-07 -0.0163932011-01-08 0.2392762011-01-10 -0.704732dtype: float64
truncate()等价的实例方法也可以截取两个日期之间TimeSeries:
>>> ts.truncate(after = '1/9/2011')2011-01-02 -0.1627122011-01-05 1.8766042011-01-07 -0.0163932011-01-08 0.239276dtype: float64
对DataFrame的行进行索引
>>> dates = pd.date_range('2/1/2020', periods = 10, freq = 'W-WED')>>> dates_1 = pd.date_range('2/1/2020', periods = 10, freq = 'W-SUN')>>> datesDatetimeIndex(['2020-02-05', '2020-02-12', '2020-02-19', '2020-02-26','2020-03-04', '2020-03-11', '2020-03-18', '2020-03-25','2020-04-01', '2020-04-08'],dtype='datetime64[ns]', freq='W-WED')>>> dates_1DatetimeIndex(['2020-02-02', '2020-02-09', '2020-02-16', '2020-02-23','2020-03-01', '2020-03-08', '2020-03-15', '2020-03-22','2020-03-29', '2020-04-05'],dtype='datetime64[ns]', freq='W-SUN')>>> long_df = pd.DataFrame(np.random.randn(10, 4), index=dates, columns=['Colorado', 'Texas','New York', 'Ohio'])>>> 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 |
同样只需传入“年”或“年月”即可轻松选取数据的切片
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 |
带有重复索引的时间序列
>>> dates = pd.DatetimeIndex(['1/1/2000', '1/2/2000', '1/2/2000','1/2/2000', '1/3/2000'])>>> dup_ts = pd.Series(np.arange(5), index = dates)>>> dup_ts2000-01-01 02000-01-02 12000-01-02 22000-01-02 32000-01-03 4dtype: int64>>> dup_ts.index.is_uniqueFalse>>> dup_ts['1/2/2000']2000-01-02 12000-01-02 22000-01-02 3dtype: int64>>> """假设你想要对具有非唯一时间戳的数据进行聚合。... 一个办法是使用groupby,并传入level=0 """>>> group = dup_ts.groupby(level=0)>>> group.mean()2000-01-01 02000-01-02 22000-01-03 4dtype: 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’,
'2012-04-05', '2012-04-06', '2012-04-07', '2012-04-08','2012-04-09', '2012-04-10', '2012-04-11', '2012-04-12','2012-04-13', '2012-04-14', '2012-04-15', '2012-04-16','2012-04-17', '2012-04-18', '2012-04-19', '2012-04-20'],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选项即可实现该功能:
>>> pd.date_range('2012-05-02 12:56:31', periods=5, normalize=True)>>> DatetimeIndex(['2012-05-02', '2012-05-03', '2012-05-04', '2012-05-05','2012-05-06'],dtype='datetime64[ns]', freq='D')
频率和日期偏移量
pandas中的频率是由一个基础频率(base frequency)和一个乘数组成的。基础频 率通常以一个字符串别名表示,比如”M”表示每月,”H”表示每小时。对于每个基础 频率,都有一个被称为日期偏移量(date offset)的对象与之对应
>>> from pandas.tseries import offsets>>> offsets.Hour()<Hour># 传入一个整数即可定义偏移量的倍数:>>> offsets.Hour(2)<2 * Hours># 大部分偏移量对象都可通过加法进行连接>>> Hour(2) + Minute(30)<150 * Minutes>#在创建日期范围时,给freq传入参数即可实现偏移频率>>> pd.date_range('2000-01-01', periods=10, freq='1h30min')DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 01:30:00','2000-01-01 03:00:00', '2000-01-01 04:30:00','2000-01-01 06:00:00', '2000-01-01 07:30:00','2000-01-01 09:00:00', '2000-01-01 10:30:00','2000-01-01 12:00:00', '2000-01-01 13:30:00'],dtype='datetime64[ns]', freq='90T')
WOM 日期 (Week Of Month)
WOM(Week Of Month)是一种非常实用的频率类,它以WOM开头。它使你能获得诸如“每月第3个星期五”之类的日期:
>>> rng = pd.date_range('2012-01-01', '2012-09-01', freq='WOM-3FRI') # 每月第3个星期五>>> list(rng)[Timestamp('2012-01-20 00:00:00', freq='WOM-3FRI'),Timestamp('2012-02-17 00:00:00', freq='WOM-3FRI'),Timestamp('2012-03-16 00:00:00', freq='WOM-3FRI'),Timestamp('2012-04-20 00:00:00', freq='WOM-3FRI'),Timestamp('2012-05-18 00:00:00', freq='WOM-3FRI'),Timestamp('2012-06-15 00:00:00', freq='WOM-3FRI'),Timestamp('2012-07-20 00:00:00', freq='WOM-3FRI'),Timestamp('2012-08-17 00:00:00', freq='WOM-3FRI')]
shfit() — 移动(超前和滞后)数据
移动(shifting)指的是沿着时间轴将数据前移或后移。Series和DataFrame都有一个shift方法用于执行单纯的前移或后移操作,保持索引不变:
>>> ts2011-01-02 -0.1627122011-01-05 1.8766042011-01-07 -0.0163932011-01-08 0.2392762011-01-10 -0.7047322011-01-12 -1.502936dtype: float64# 往前移动2格,索引不变>>> ts.shift(2)2011-01-02 NaN2011-01-05 NaN2011-01-07 -0.1627122011-01-08 1.8766042011-01-10 -0.0163932011-01-12 0.239276dtype: float64>>> ts.shift(-2)2011-01-02 -0.0163932011-01-05 0.2392762011-01-07 -0.7047322011-01-08 -1.5029362011-01-10 NaN2011-01-12 NaNdtype: float64
shift通常用于计算一个时间序列或多个时间序列(如DataFrame的列)中的百分比变化。
>>> ts/ts.shift(1)-12011-01-02 NaN2011-01-05 -12.5332762011-01-07 -1.0087352011-01-08 -15.5965142011-01-10 -3.9452702011-01-12 1.132634dtype: float64>>> ts2011-01-02 -0.1627122011-01-05 1.8766042011-01-07 -0.0163932011-01-08 0.2392762011-01-10 -0.7047322011-01-12 -1.502936dtype: float64>>> ts.shift(2)2011-01-02 NaN2011-01-05 NaN2011-01-07 -0.1627122011-01-08 1.8766042011-01-10 -0.0163932011-01-12 0.239276dtype: float64>>> ts.shift(2,freq = 'D')2011-01-04 -0.1627122011-01-07 1.8766042011-01-09 -0.0163932011-01-10 0.2392762011-01-12 -0.7047322011-01-14 -1.502936dtype: float64>>> ts.shift(2, freq = 'BM')2011-02-28 -0.1627122011-02-28 1.8766042011-02-28 -0.0163932011-02-28 0.2392762011-02-28 -0.7047322011-02-28 -1.502936dtype: float64
通过偏移量对日期进行位移
>>> from pandas.tseries.offsets import *>>> now = datetime(2020,2,20)>>> now + 3 * Day()Timestamp('2020-02-23 00:00:00')
如果加的是锚点偏移量(比如MonthEnd),第一次增量会将原日期向前滚动到符合频率规则的下一个日期:
>>> now + MonthEnd()Timestamp('2020-02-29 00:00:00')
通过锚点偏移量的rollforward和rollback方法,可明确地将日期向前或向后“滚动”:
>>> nowdatetime.datetime(2020, 2, 20, 0, 0)>>> me_offset = MonthEnd()>>> MonthEnd().rollforward(now)Timestamp('2020-02-29 00:00:00')>>> me_offset.rollback(now)Timestamp('2020-01-31 00:00:00')
结合groupby运用‘滚动’
>>> ts = pd.Series(np.random.randn(20), index = pd.date_range('1/15/2020',periods = 20, freq = '5d'))>>> ts2020-01-15 1.0368272020-01-20 0.0139902020-01-25 0.8333302020-01-30 0.3772232020-02-04 -0.2755112020-02-09 -0.3312962020-02-14 -0.4334512020-02-19 -0.5186792020-02-24 0.8287222020-02-29 0.1584642020-03-05 0.7371442020-03-10 0.3184042020-03-15 -0.2401182020-03-20 -0.9107732020-03-25 0.4163532020-03-30 -1.2654802020-04-04 0.8827852020-04-09 2.6285882020-04-14 0.7770442020-04-19 -1.821877Freq: 5D, dtype: float64>>> ts.groupby(MonthEnd().rollforward).mean()2020-01-31 0.5653432020-02-29 -0.0952922020-03-31 -0.1574122020-04-30 0.616635dtype: float64
更简单、更快速地实现该功能的办法是使用resample。
>>> ts.resample('M').mean()2020-01-31 0.5653432020-02-29 -0.0952922020-03-31 -0.1574122020-04-30 0.616635Freq: M, dtype: float64
