时间序列(time series)数据是⼀种重要的结构化数据形式,应⽤于多个领域,包括⾦融学、经济学、⽣态学、神经科学、物理学等。
时间序列数据的意义取决于具体的应⽤场景,主要有以下⼏种:
- 时间戳(timestamp),特定的时刻。
- 固定时期(period),如2007年1⽉或2010年全年。
- 时间间隔(interval),由起始和结束时间戳表示。时期(period)可以被看做间隔(interval)的特例。
- 实验或过程时间,每个时间点都是相对于特定起始时间的⼀个度量。例如,从放⼊烤箱时起,每秒钟饼⼲的直径。
下面主要讲解前3种时间序列。许多技术都可⽤于处理实验型时间序列,其索引可能是⼀个整数或浮点数(表示从实验开始算起已经过去的时间)。最简单也最常⻅的时间序列都是⽤时间戳进⾏索引的。
日期和时间数据类型及⼯具
import numpy as np
import pandas as pd
np.random.seed(12345)
import matplotlib.pyplot as plt
plt.rc('figure', figsize=(10, 6))
PREVIOUS_MAX_ROWS = pd.options.display.max_rows
pd.options.display.max_rows = 20
np.set_printoptions(precision=4, suppress=True)
Python标准库包含⽤于⽇期(date)和时间(time)数据的数据类型,⽽且还有⽇历⽅⾯的功能。我们主要会⽤到datetime、time以及calendar模块。datetime.datetime(也可以简写为datetime)是⽤得最多的数据类型:
from datetime import datetime
now = datetime.now()
now
now.year, now.month, now.day
datetime以毫秒形式存储⽇期和时间。timedelta表示两个datetime对象之间的时间差:
delta = datetime(2011, 1, 7) - datetime(2008, 6, 24, 8, 15)
delta
delta.days
delta.seconds
可以给datetime对象加上(或减去)⼀个或多个timedelta,这样会产⽣⼀个新对象:
from datetime import timedelta
start = datetime(2011, 1, 7)
start + timedelta(12)
start - 2 * timedelta(12)
datetime模块中的数据类型参⻅表1。虽然主要讲的是pandas数据类型和⾼级时间序列处理,但你肯定会在Python的其他地⽅遇到有关datetime的数据类型。
表1 datetime模块中的数据类型
类型 | 说明 |
---|---|
date | 以公历形式存储日历日期(年月日) |
time | 将时间存储为时,分,秒,毫秒 |
datetime | 存储日期和时间 |
timedelta | 表示两个datetime值之间的差(日,秒,毫秒) |
tzinfo | 存储时区信息的基本类型 |
字符串和datetime的相互转换
利⽤str或strftime⽅法(传⼊⼀个格式化字符串,datetime对象和pandas的Timestamp对象(稍后就会介绍) 可以被格式化为字符串
stamp = datetime(2011, 1, 3)
str(stamp)
stamp.strftime('%Y-%m-%d')
表2列出了全部的格式化编码。
表2 datetime格式定义(兼容ISO C89)
代码 | 说明 |
---|---|
%Y | 4位数的年 |
%y | 2位数的年 |
%m | 2位数的月 |
%d | 2位数的日 |
%H | 时(24小时制) |
%I | 时(12小时制) |
%M | 2位数的分 |
%S | 秒[0,61](秒60和秒61用于闰秒) |
%w | 用于整数表示的星期几[0(星期天),6] |
%U | 每年的第几周【0,53】。星期天被认为是每周的第一天,每年第一个星期天之前的那几天被认为是第0周 |
%W | 每年的第几周【0,53】。星期一被认为是每周的第一天,每年的第一个星期一之前的那几天会被认为是第0周 |
%z | 以+HHMM或-HHMM表示的UTC时区偏移量,如果时区为naive,则返回空的字符串 |
%F | %Y-%m-%d简写形式 |
%D | %m%d%y简写形式 |
datetime.strptime可以⽤这些格式化编码将字符串转换为⽇期:
value = '2011-01-03'
datetime.strptime(value, '%Y-%m-%d')
datestrs = ['7/6/2011', '8/6/2011']
[datetime.strptime(x, '%m/%d/%Y') for x in datestrs]
datetime.strptime是通过已知格式进⾏⽇期解析的最佳⽅式。但是每次都要编写格式定义是很麻烦的事情,尤其是对于⼀些常⻅的⽇期格式。这种情况下,你可以⽤dateutil这个第三⽅包中的parser.parse⽅法(pandas中已经⾃动安装好了):
from dateutil.parser import parse
parse('2011-01-03')
parse可以解析⼏乎所有⼈类能够理解的⽇期表示形式:
parse('Jan 31, 1997 10:45 PM')
在国际通⽤的格式中,⽇出现在⽉的前⾯很普遍,传⼊dayfirst=True即可解决这个问题:
parse('6/12/2011', dayfirst=True)
pandas通常是⽤于处理成组⽇期的,不管这些⽇期是DataFrame的轴索引还是列。to_datetime⽅法可以解析多种不同的⽇期表示形式。对标准⽇期格式(如ISO8601)的解析⾮常快:
datestrs = ['2011-07-06 12:00:00', '2011-08-06 00:00:00']
pd.to_datetime(datestrs)
它还可以处理缺失值(None、空字符串等):
#处理缺失值
idx = pd.to_datetime(datestrs + [None])
idx
idx[2]
pd.isnull(idx)
NaT(Not a Time)是pandas中时间戳数据的null值。
注意:dateutil.parser是⼀个实⽤但不完美的⼯具。⽐如说,它会把⼀些原本不是⽇期的字符串认作是⽇期(⽐如"42"会被解析为2042年的今天)。
datetime对象还有⼀些特定于当前环境(位于不同国家或使⽤不同语⾔的系统)的格式化选项。例如,德语或法语系统所⽤的⽉份简写就与英语系统所⽤的不同。表3进⾏了总结。
表3 特定于当前环境的⽇期格式
代码 | 说明 |
---|---|
%a | 星期几的简写 |
%A | 星期几的全称 |
%b | 月份的简写 |
%B | 月份的全称 |
%c | 完整的日期和时间,例如“Tue01 May 2012 04:20:57 PM” |
%p | 不同环境中的AM或PM |
%x | 适合于当前环境的日期格式,例如在美国,“May 1 2012“会产生“05/01/2012” |
%X | 适合于当前环境的时间格式 |
时间序列基础
pandas最基本的时间序列类型就是以时间戳(通常以Python字符串或datatime对象表示)为索引的Series:
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)
ts
这些datetime对象实际上是被放在⼀个DatetimeIndex中的:
ts.index
跟其他Series⼀样,不同索引的时间序列之间的算术运算会⾃动按⽇期对⻬:
ts + ts[::2]
pandas⽤NumPy的datetime64数据类型以纳秒形式存储时间戳:
ts.index.dtype
DatetimeIndex中的各个标量值是pandas的Timestamp对象:
stamp = ts.index[0]
stamp
只要有需要,TimeStamp可以随时⾃动转换为datetime对象。此外,它还可以存储频率信息(如果有的话),且知道如何执⾏时区转换以及其他操作。稍后将对此进⾏详细讲解。
索引、选取、子集构造
当你根据标签索引选取数据时,时间序列和其它的pandas.Series很像:
stamp = ts.index[2]
ts[stamp]
还有⼀种更为⽅便的⽤法:传⼊⼀个可以被解释为⽇期的字符串:
ts['1/10/2011']
ts['20110110']
对于较⻓的时间序列,只需传⼊“年”或“年⽉”即可轻松选取数据的切⽚:
longer_ts = pd.Series(np.random.randn(1000),
index=pd.date_range('1/1/2000', periods=1000))
longer_ts
longer_ts['2001']
这⾥,字符串“2001”被解释成年,并根据它选取时间区间。指定⽉也同样奏效:
longer_ts['2001-05']
datetime对象也可以进⾏切⽚:
ts[datetime(2011, 1, 7):]
由于⼤部分时间序列数据都是按照时间先后排序的,因此你也可以⽤不存在于该时间序列中的时间戳对其进⾏切⽚(即范围查询):
ts
ts['1/6/2011':'1/11/2011']
跟之前⼀样,你可以传⼊字符串⽇期、datetime或Timestamp。注意,这样切⽚所产⽣的是源时间序列的视图,跟NumPy数组的切⽚运算是⼀样的。
这意味着,没有数据被复制,对切⽚进⾏修改会反映到原始数据上。
此外,还有⼀个等价的实例⽅法也可以截取两个⽇期之间TimeSeries:
ts.truncate(after='1/9/2011')
这些操作对DataFrame也有效。例如,