微信公众号: BrainZou
时间序列
时间序列是指将同一统计指标的数值按其发生的时间先后顺序排列而成的数列. 时间序列分析的主要目的是根据已有的历史数据对未来进行预测.
日期和时间数据类型及工具
Python 标准库包含用于日期 (data) 和时间 (time) 数据的数据类型, 而且还有日历方面的功能. 我们主要会用到 datetime,time 以及 calendar 模块. datetime.datetime(也可以简写为 datetime)是用得最多的数据类型:
- from datetime import datetime
- now = datetime.now()
- now
out:datetime.datetime(2018, 4, 30, 15, 8, 59, 633049)
- # 为年月日时分秒毫秒, 分别可以用
- #(year,month,day,hour,minute,second,microsecond)
- # 获取, 例如:
- now.second
- out:59
datetime 以毫秒的形式存储日期和时间, datetime.timedelta 表示两个 datetime 对象之间的时间差:
- delta = datetime(2011,1,7)-datetime(2008,6,24,8,15)
- out:datetime.timedelta(926, 56700)
- delta.days
- out:926
- delta.seconds
- out:56700
也可以在 datetime 对象上加或减一个或多个 timedelta, 产生一个新对象:
- from datetime import timedelta
- start = datetime(2011,1,7)
- start + 2*timedelta(12)
- out:datetime.datetime(2011, 1, 31, 0, 0)
datetime 中的数据类型
字符串和 datetime 相互转换
利用 str 或 strftime 方法, datetime 对象和 pandas 的 Timestamp 对象就可以被格式化为字符串.
- stamp = datetime(2011,1,3)
- str(stamp)
- out:'2011-01-03 00:00:00'
- stamp.strftime('%Y-%m-%d')
- out:'2011-01-03'
datetime.strptime 可以用这些格式化编码将字符串转换为日期:
- [datetime.strptime(x,'%m/%d/%Y') for x in datestrs]
- out:[datetime.datetime(2011, 7, 6, 0, 0), datetime.datetime(2011, 8, 6, 0, 0)]
由于用这种格式定义是很麻烦的, 所以 dateutil 这个第三方包中的 parser.parse 方法就显得格外简洁:
- parse('Jan 31,1997 10:45 PM')
- out:datetime.datetime(2018, 1, 31, 22, 45)
默认认为月是出现在日之前的, 而如果是国际通用格式, 日在月之前, 传入 dayfirst=True 即可.
但是 parser.parse 很实用, 但是并不严谨, 比如对于一个 42 数字, 它会理解为 2042 年的今天.
pandas 的 to_datetime 方法可以解析多种不同的日期表示形式. 对标准的日期格式解析非常快.
- datestrs
- out:['7/6/2011', '8/6/2011']
- pd.to_datetime(datestrs)
- out:DatetimeIndex(['2011-07-06', '2011-08-06'], dtype='datetime64[ns]', freq=None)
- # 处理缺失值
- idx = pd.to_datetime(datestrs + [None])
- idx
- #NaT 是 pandas 中时间戳数据的 NA 值.
- out:DatetimeIndex(['2011-07-06', '2011-08-06', 'NaT'], dtype='datetime64[ns]', freq=None)
- pd.isnull(idx)
- out:array([False, False, True], dtype=bool)
datetime 格式定义
时间序列基础
以时间戳为索引的 Series 就叫时间序列.
- ts = Series(np.random.randn(3),index = idx)
- ts
- out:
- 2011-07-06 -0.764380
- 2011-08-06 -1.918176
- NaT -0.036477
- dtype: float64
- ts.index
- out:DatetimeIndex(['2011-07-06', '2011-08-06', 'NaT'], dtype='datetime64[ns]', freq=None)
DatetimeIndex 中各个标量值是 pandas 的 Timestamp 对象:
- ts.index[0]
- out:Timestamp('2011-07-06 00:00:00')
索引, 选取, 子集构造
索引:
- ts[ts.index[0]]
- out:-0.76438007139328756
- # 还可以直接传入一个日期的字符串
- ts['7/6/2011']
- out:2011-07-06 -0.76438
- dtype: float64
对于较长的时间序列, 只需传入 "年" 或 "年月" 即可轻松选取数据切片. 与之前类似, 可以把字符串日期, datetime 或 Timestamp 传入用来切片.
- ts[datetime(2011,1,7):]
- ts['1/6/2011':'1/11/2011']
还有一个等价的方法也可以截取两个日期之间的 TimeSeries:
ts.truncate(after='1/9/2011')
以上操作对 DataFrame 同样有效.
带有重复索引的时间序列
当某个时间点是有多个数据时, 就会出现索引重复的情况. 用 is_unique 属性可以判断索引是否唯一.
- ts.index.is_unique
- out:True
如果想要对具有非唯一时间戳的数据进行聚合. 一个方法是使用 groupby, 并传入 level=0(索引唯一一层):
- grouped=ts.groupby(level=0)
- grouped.mean()
- grouped.count()
日期的范围, 频率以及移动
时间序列一般是无规则的, 但是有时候它需要以某种特定的频率进行分析(而且很常见), 比如每日, 每月. 例如想要将时间序列转换成每日的时间序列, 只需要调用 resample:
ts.resample('D')
生成日期范围
pandas.date_range 可用于生成指定长度的 DatetimeIndex:
- index = pd.date_range('4/1/2012','6/1/2012')
- index
- out:
DatetimeIndex(['2012-04-01', '2012-04-02'........'2012-05-31', '2012-06-01'], dtype='datetime64[ns]', freq='D')
默认情况下是按天计算的, 如果只传入起始或结束日期(start='4/1/2012'), 则还需要传入一个代表时间长度的值(periods=20): 则代表了 2012/4/1-2012/4/20.
如果你想要生成一个由每月的最后一个工作日组成的日期索引, 可以传入 "BM" 频率 (表示 bussiness end of month), 这样就只包含时间间隔内(或刚好在边界上的) 符合频率要求的日期:
pd.date_range('1/1/2000','12/1/2000',freq='BM')
date_range 默认保留起始和结束时间戳的时间信息(如果有的话, 即保留时分秒等)
如果不想要这部分, 可以传入 normallize=True 实现.
频率和日期偏移量
pandas 中的频率是由一个基础频率和一个乘数组成的, 基础频率通常以一个字符串别名表示, 比如 "M" 表示每月,"H" 表示每小时. 对于每个基础频率, 都有一个被称为日期偏移量的对象与之对应. 例如, 按小时计算的频率可以用 Hour 类表示:
- from pandas.tseries.offsets import Hour,Minute
- hour = Hour()
- hour
- out:<Hour>
- four_hours = Hour(4)
- four_hours
- out:<4 * Hours>
- # 当然一般不需要创建这种对象, 只需要传入 "H" 或者 "4H" 即可. 例如 freq='4h'
而偏移量也可以通过加法连接:
Hour(2)+Minute(30)
同理, 你甚至可以传入 freq='1h30min'.
时间序列的基础频率
WOM 日期
WOM(Week Of Month)是一种非常实用的频率类, 它以 WOM 开头. 它使你能获得诸如 "每个月第三个星期五" 之类的日期:
rng = pd.date_range('1/1/2012','9/1/2012',freq='WOM-3FRI')
移动 (超前和滞后) 数据
移动 (shifting) 是沿着时间轴将数据前移或后移. Series 和 DateFrame 都有 shift 方法用于执行单纯的前移或后移操作, 保持索引不变:
- ts = Series(np.random.randn(4),index=pd.date_range('1/1/2000',periods=4,freq='M'))
- ts
- out:
- 2000-01-31 0.770290
- 2000-02-29 1.295360
- 2000-03-31 0.208220
- 2000-04-30 -0.534682
Freq: M, dtype: float64
- ts.shift(2)
- out:
- 2000-01-31 NaN
- 2000-02-29 NaN
- 2000-03-31 0.77029
- 2000-04-30 1.29536
Freq: M, dtype: float64
- ts.shift(-2)
- out:
- 2000-01-31 0.208220
- 2000-02-29 -0.534682
- 2000-03-31 NaN
- 2000-04-30 NaN
Freq: M, dtype: float64
shift 通常用于计算一个时间序列或多个时间序列 (如 DataFrame 的列) 中百分比变化. 可以这样表达 ts/ts.shift(1)-1
前面是将数据进行移动, 我们也可以对时间戳进行位移:
- ts.shift(2,freq='3d')
- out:
- 2000-02-06 0.770290
- 2000-03-06 1.295360
- 2000-04-06 0.208220
- 2000-05-06 -0.534682
- dtype: float64
通过偏移量对日期进行偏移
pandas 的日期偏移量还可以用在 datetime 或 Timestamp 对象上:
- # 偏移到月底(加个 2 说明第二个月底)
- from pandas.tseries.offsets import Day,MonthEnd
- now = datetime(2011,11,17)
- now + MonthEnd(2)
- out:Timestamp('2011-12-31 00:00:00')
通过 rollforward 和 rollback 方法, 可显式地将日期往前或往后 "滚动"
- offset = MonthEnd()
- offset.rollforward(now)
- out:Timestamp('2011-11-30 00:00:00')
- offset.rollback(now)
- out:Timestamp('2011-10-31 00:00:00')
通过 groupby 可以更巧妙的使用:
- ts = Series(np.random.randn(20),index=pd.date_range('1/15/2000',periods=20,freq='4d'))
- ts
- out:
- 2000-01-15 1.173212
- 2000-01-19 0.505253
- 2000-01-23 -0.755936
- 2000-01-27 -0.496667
- 2000-01-31 -0.500123
- 2000-02-04 -0.305935
- 2000-02-08 -0.637126
- 2000-02-12 -0.437494
- 2000-02-16 0.429528
- 2000-02-20 1.174809
- 2000-02-24 -0.028770
- 2000-02-28 0.174715
- 2000-03-03 1.118145
- 2000-03-07 -0.813746
- 2000-03-11 0.729049
- 2000-03-15 -0.005806
- 2000-03-19 -3.121317
- 2000-03-23 -2.352350
- 2000-03-27 -1.460366
- 2000-03-31 -0.775489
Freq: 4D, dtype: float64
- ts.groupby(offset.rollforward).mean()
- out:
- 2000-01-31 -0.014852
- 2000-02-29 0.052818
- 2000-03-31 -0.835235
- dtype: float64
巧妙的通过 groupby 将当月的值都统计到月底了.
当然更简单, 快速的方法是使用 resample:
ts.resample('M').mean()
最后, 假期愉快!
来源: https://juejin.im/post/5ae70e55518825673a205638