对于时间序列的处理在数据处理方面还是比较重要的一块。这篇来记录一下时间序列的一些知识点。
python标准库包含日期(date)和时间(time)数据的数据类型。
经常使用的也就是datetime、time以及calendar模块。datetime以毫秒形式存储日期和时间。
date | 以公历形式存储日历日期(年、月、日) |
time | 将时间存储为时、分、秒、毫秒 |
datetime | 存储日期和时间 |
timedelta | 表示两个datetime值之间的差(日、秒、毫秒) |
In [5]: from datetime import datetime In [6]: now = datetime.now() In [7]: now Out[7]: datetime.datetime(2018, 6, 25, 14, 38, 8, 361612) In [9]: now.year Out[9]: 2018 In [10]: now.month Out[10]: 6 In [11]: now.day Out[11]: 25
datetime.timedelta表示两个datetime对象之间的时间差。给datetime对象加上或减去一个或多个timedelta,会产生一个新的对象。
In [13]: delta = now - datetime(1995,6,26,20,10) In [14]: delta Out[14]: datetime.timedelta(8399, 66488, 361612) In [15]: delta.days Out[15]: 8399 In [16]: delta.seconds Out[16]: 66488
利用str 或strftime(传入一个格式化的字符串) 将日期转换为字符串,
利用 datetime.strptime 将字符串转换为日期,这种方式是已知格式对日期解析的最佳方式。
In [17]: str(now) Out[17]: '2018-06-25 14:38:08.361612' In [18]: now.strftime('%Y-%m-%d') Out[18]: '2018-06-25' In [19]: str_date = '2018-06-25' In [20]: datetime.strptime(str_date,'%Y-%m-%d') Out[20]: datetime.datetime(2018, 6, 25, 0, 0)
datetime 格式定义:
%Y |
4位数的年 |
%y |
2位数的年 |
%m |
2位数的月[01,12] |
%d |
2位数的日[01,31] |
%H |
时(24小时制)[00,23] |
%l |
时(12小时制)[01,12] |
%M |
2位数的分[00,59] |
%S |
秒[00,61]有闰秒的存在 |
%w |
用整数表示的星期几[0(星期天),6] |
%F |
%Y-%m-%d简写形式例如,2017-06-27 |
%D |
%m/%d/%y简写形式 |
一些第三方库也有对时间的解析:
In [21]: from dateutil.parser import parse In [22]: parse(str_date) Out[22]: datetime.datetime(2018, 6, 25, 0, 0)
有些日期日出现在月前面传入 dayfirst=True 即可解决:
In [23]: parse('25/6/2018',dayfirst=True) Out[23]: datetime.datetime(2018, 6, 25, 0, 0)
pandas 中对日期解析的方法 to_datetime 可以解析多种不同类型的日期表示形式:
In [25]: date = ['2018-6-26', '2018-6-25'] In [26]: pd.to_datetime(date) Out[26]: DatetimeIndex(['2018-06-26', '2018-06-25'], dtype='dat etime64[ns]', freq=None)
pandas中的时间序列基础
In [51]: dates = ['2018-06-20','2018-06-21','2018-06-22','2018 ...: -06-23','2018-06-24','2018-06-25','2018-06-26','2018- ...: 06-27'] In [52]: ts = pd.Series(np.random.randn(8),index=pd.to_datetim ...: e(dates)) In [53]: ts Out[53]: 2018-06-20 0.116617 2018-06-21 -0.160468 2018-06-22 -0.457980 2018-06-23 -0.994871 2018-06-24 1.028115 2018-06-25 -0.223018 2018-06-26 1.264067 2018-06-27 0.054394 dtype: float64 In [54]: ts.index Out[54]: DatetimeIndex(['2018-06-20', '2018-06-21', '2018-06-22', '2018-06-23','2018-06-24', '2018-06-25', '2018-06-26', '2018-06-27'],dtype='datetime64[ns]', freq=None)
pandas不同索引的时间序列之间的算术运算会自动按日期对齐
In [56]: ts + ts[::2] Out[56]: 2018-06-20 0.233233 2018-06-21 NaN 2018-06-22 -0.915960 2018-06-23 NaN 2018-06-24 2.056230 2018-06-25 NaN 2018-06-26 2.528134 2018-06-27 NaN dtype: float64
1)一般索引,传入对应的index
In [57]: ts[ts.index[3]] Out[57]: -0.9948705675890571
2)一个可以被解析为日期的字符串
In [58]: ts['2018-06-26 '] Out[58]: 1.264066795280468
3)对于,较长的时间序列,只需传入‘年’或‘年月’可返回对应的数据切片
In [59]: ts['2018-06':] Out[59]: 2018-06-20 0.116617 2018-06-21 -0.160468 2018-06-22 -0.457980 2018-06-23 -0.994871 2018-06-24 1.028115 2018-06-25 -0.223018 2018-06-26 1.264067 2018-06-27 0.054394 dtype: float644)通过时间范围进行切片索引
In [60]: ts['2018-06-21':'2018-06-25'] Out[60]: 2018-06-21 -0.160468 2018-06-22 -0.457980 2018-06-23 -0.994871 2018-06-24 1.028115 2018-06-25 -0.223018 dtype: float64
有的时候我们需要对包含时间的数据按照相对固定的时间频率进行分析,因此pandas中就提供了一套标准时间序列频率以及用于重采样、频率推断、生成固定频率日期范围的工具。
生成日期范围:pandas.date_arnge 生成指定长度的 DatetimeIndex
pandas.date_range(start=None, end=None, periods=None, freq='D', tz=None, normalize=False, name=None, closed=None, **kwargs) start : str or datetime-like, optional Left bound for generating dates. end : str or datetime-like, optional Right bound for generating dates. periods : integer, optional Number of periods to generate. freq : str or DateOffset, default ‘D’ (calendar daily) Frequency strings can have multiples, e.g. ‘5H’. See here for a list of frequency aliases. tz : str or tzinfo, optional Time zone name for returning localized DatetimeIndex, for example ‘Asia/Hong_Kong’. By default, the resulting DatetimeIndex is timezone-naive. normalize : bool, default False Normalize start/end dates to midnight before generating date range. name : str, default None Name of the resulting DatetimeIndex. closed : {None, ‘left’, ‘right’}, optional Make the interval closed with respect to the given frequency to the ‘left’, ‘right’, or both sides (None, the default). **kwargs For compatibility. Has no effect on the result.
In [188]: date_index = pd.date_range(start='2018-6-27',periods=6) In [189]: date_index Out[189]: DatetimeIndex(['2018-06-27', '2018-06-28', '2018-06-29', '2018-06-30', '2018-07-01', '2018-07-02'],dtype='datetime64[ns]', freq='D') In [190]: date_index = pd.date_range(start='2018-6-27 14:31',periods=6) # 带时间的保留时间 In [191]: date_index Out[191]: DatetimeIndex(['2018-06-27 14:31:00', '2018-06-28 14:31:00', '2018-06-29 14:31:00', '2018-06-30 14:31:00', '2018-07-01 14:31:00', '2018-07-02 14:31:00'], dtype='datetime64[ns]', freq='D') In [192]: date_index = pd.date_range(start='2018-6-27 14:31',end='2019-01-01',freq='BM',normalize=True) In [193]: date_index # 生成日期的频率参数 后面会学到各种时间频率 Out[193]: DatetimeIndex(['2018-06-29', '2018-07-31', '2018-08-31', '2018-09-28', '2018-10-31', '2018-11-30', '2018-12-31'], dtype='datetime64[ns]', freq='BM') In [194]: date_index = pd.date_range(start='2018-6-27 14:31',periods=3,normalize=True) In [195]: date_index # 保留时间到午夜 好像不咋管用,就是不显示时间了呗 Out[195]: DatetimeIndex(['2018-06-27', '2018-06-28', '2018-06-29'], dtype='datetime64[ns]', freq='D')
日期偏移对象在 pandas.tseries.offsets 之中
In [200]: import pandas.tseries.offsets as offs In [201]: offs.Hour() Out[201]:In [202]: offs.Hour(4) # 4h 的偏移量 Out[202]: <4 * Hours> In [203]: pd.date_range(start='2018-6-27 14:00:00',periods=3,freq='4h') # 这种方式可以 Out[203]: DatetimeIndex(['2018-06-27 14:00:00', '2018-06-27 18:00:00', '2018-06-27 22:00:00'], dtype='datetime64[ns]', freq='4H') In [204]: pd.date_range(start='2018-6-27 14:00:00',periods=3,freq='1h30min') # 这种也可以 Out[204]: DatetimeIndex(['2018-06-27 14:00:00', '2018-06-27 15:30:00', '2018-06-27 17:00:00'], dtype='datetime64[ns]', freq='90T')
下面是pandas中的频率代码和日期偏移量类型:
别名 | 偏移量类型 | 说明 |
D | Day | 每日历日 |
B | BusinessDay | 每工作日 |
H | Hour | 每小时 |
T/min | Minute | 每分 |
S | Second | 每秒 |
L/ms | Million | 每毫秒 |
U | Micro | 每微妙 |
M | MonthEnd | 每月最后一个日历日 |
BM | BusinessMonthEnd | 每月最后一个工作日 |
MS | MonthBegin | 每月第一个日历日 |
BMS | BusinessMonthBegin | 每月第一个工作日 |
W-MON、W-TUE… | Week | 从指定的星期几开始算起,每周 |
WOM-1MON、WOM-2MON… | WeekOfMonth | 产生每月第一、二、三、四周的星期几,例如WOM-1MON表示每月的第一个星期一 |
Q-JAN、Q-FEB… | QuarterEnd | 对于以指定月份(JAN、FEB、…、DEC)结束的年度,每季度的最后一月的最后一个日历日 |
BQ-JAN、BQ-FEB… | BusinessQuarterEnd | 对于以指定月份(JAN、FEB、…、DEC)结束的年度,每季度的最后一月的最后一个工作日 |
QS-JAN、QS-FEB… | QuarterBegin | 对于以指定月份(JAN、FEB、…、DEC)结束的年度,每季度的最后一月的第一个日历日 |
BQS-JAN、BQS-FEB… | BusinessQuarterBegin | 对于以指定月份(JAN、FEB、…、DEC)结束的年度,每季度的最后一月的第一个工作日 |
A-JAN、A-FEB… | YearEnd | 每年指定月份最后一个日历日 |
BA-JAN、BA-FEB… | BusinessYearEnd | 每年指定月份最后一个工作日 |
AS-JAN、AS-FEB… | YearBegin | 每月指定月份第一个日历日 |
BAS-JAN、BAS-FEB… | BusinessYearBegin | 每月指定月份第一个工作日 |
In [206]: rng = pd.date_range('2018-1-1','2018-6-27',freq='WOM-3FRI') # 每月第三个星期五 In [207]: rng Out[207]: DatetimeIndex(['2018-01-19', '2018-02-16', '2018-03-16', '2018-04-20', '2018-05-18', '2018-06-15'], dtype='datetime64[ns]', freq='WOM-3FRI')
In [209]: ts = pd.Series(np.random.randn(4),index=pd.date_rang e('1/1/2018', periods=4, freq='M')) In [210]: ts Out[210]: 2018-01-31 -0.266415 2018-02-28 1.328264 2018-03-31 -0.938386 2018-04-30 1.874953 Freq: M, dtype: float64 In [211]: ts.shift(2) # 前移 Out[211]: 2018-01-31 NaN 2018-02-28 NaN 2018-03-31 -0.266415 2018-04-30 1.328264 Freq: M, dtype: float64 In [212]: ts.shift(-2) # 后移 Out[212]: 2018-01-31 -0.938386 2018-02-28 1.874953 2018-03-31 NaN 2018-04-30 NaN Freq: M, dtype: float64 In [213]: ts.shift(2,freq='M') # 带频率的移动 Out[213]: 2018-03-31 -0.266415 2018-04-30 1.328264 2018-05-31 -0.938386 2018-06-30 1.874953 Freq: M, dtype: float64shift 通常用于计算一个或多个时间序列的百分比变化: ts/ts.shift( ) - 1
In [214]: now = datetime.now() In [215]: now Out[215]: datetime.datetime(2018, 6, 26, 15, 30, 30, 259357)
In [216]: now + offs.MonthEnd() Out[216]: Timestamp('2018-06-30 15:30:30.259357') In [217]: off = offs.MonthEnd() In [218]: off.rollforward(now) Out[218]: Timestamp('2018-06-30 15:30:30.259357') In [219]: off.rollback(now) Out[219]: Timestamp('2018-05-31 15:30:30.259357')
这里还有个巧妙的用法,结合groupby 和滚动方法 :
In [223]: ts = pd.Series(np.random.randn(20),index=pd.date_range('1/15/2018', periods=20, freq='4d')) In [224]: ts Out[224]: 2018-01-15 -0.071915 2018-01-19 2.403895 ............ 2018-04-01 1.046591 Freq: 4D, dtype: float64 In [225]: ts.groupby(off.rollforward).mean() Out[225]: 2018-01-31 -0.351028 2018-02-28 -0.495122 2018-03-31 -0.457727 2018-04-30 1.046591
更简单的方式: In [229]: ts.resample('M').mean() Out[229]: 2018-01-31 -0.351028 2018-02-28 -0.495122 2018-03-31 -0.457727 2018-04-30 1.046591 Freq: M, dtype: float64
重采样及频率转换
重采样(resampling)指的是将时间序列从一个频率转换到另一频率的处理过程。
resample 参数:
参数 | 说明 |
freq | 表示重采样频率,例如‘M’、‘5min’,Second(15) |
how=’mean’ (已废弃) | 用于产生聚合值的函数名或数组函数,例如‘mean’、‘ohlc’、np.max等,默认是‘mean’,其他常用的值由:‘first’、‘last’、‘median’、‘max’、‘min’ |
axis=0 | 默认是纵轴,横轴设置axis=1 |
fill_method = None | 升采样时如何插值,比如‘ffill’、‘bfill’等 |
closed = ‘left’ | 在降采样时,各时间段的哪一段是闭合的,‘right’或‘left’,默认‘left’ |
label= ‘left’ | 在降采样时,如何设置聚合值的标签,例如,9:30-9:35会被标记成9:30还是9:35,默认9:35 |
loffset = None | 面元标签的时间校正值,比如‘-1s’或Second(-1)用于将聚合标签调早1秒 |
limit=None | 在向前或向后填充时,允许填充的最大时期数 |
kind = None | 聚合到时期(‘period’)或时间戳(‘timestamp’),默认聚合到时间序列的索引类型 |
convention = None | 当重采样时期时,将低频率转换到高频率所采用的约定(start或end)。默认‘end’ |
In [229]: ts.resample('M').mean()
Out[229]:
2018-01-31 -0.351028
2018-02-28 -0.495122
2018-03-31 -0.457727
2018-04-30 1.046591
Freq: M, dtype: float64
降采样:将高频率聚合到低频率成为降采样
有一个频率为 1 min 的时间序列:
In [231]: rng = pd.date_range('2018-6-26',periods=10,freq='T') In [232]: ts = pd.Series(np.random.randn(10),index=rng) In [233]: ts Out[233]: 2018-06-26 00:00:00 0.616423 2018-06-26 00:01:00 -0.815095 2018-06-26 00:02:00 -0.593021 2018-06-26 00:03:00 1.388699 2018-06-26 00:04:00 -0.900769 2018-06-26 00:05:00 -1.941041 2018-06-26 00:06:00 0.264235 2018-06-26 00:07:00 -1.185064 2018-06-26 00:08:00 0.242785 2018-06-26 00:09:00 0.098842 Freq: T, dtype: float64
将时间序列聚合到 5mins :
In [253]: rng = pd.date_range('2018-6-26',periods=12,freq='T') In [254]: ts = pd.Series(np.arange(12),index=rng) In [255]: ts.resample('5min').sum() Out[255]: 2018-06-26 00:00:00 10 2018-06-26 00:05:00 35 2018-06-26 00:10:00 21 Freq: 5T, dtype: int32 In [256]: ts.resample('5min',closed='right').sum() # 包含了右边界,以左边界为标记 Out[256]: 2018-06-25 23:55:00 0 2018-06-26 00:00:00 15 2018-06-26 00:05:00 40 2018-06-26 00:10:00 11 Freq: 5T, dtype: int32 In [258]: ts.resample('5min',closed='left').sum() # 包含了左边界,以左边界为标记 Out[258]: 2018-06-26 00:00:00 10 2018-06-26 00:05:00 35 2018-06-26 00:10:00 21 Freq: 5T, dtype: int32 In [259]: ts.resample('5min',closed='right',label='right').sum() # 包含了右边界,以右边界为标记 Out[259]: 2018-06-26 00:00:00 0 2018-06-26 00:05:00 15 2018-06-26 00:10:00 40 2018-06-26 00:15:00 11 Freq: 5T, dtype: int32 In [260]: ts.resample('5min',closed='right',label='left').sum() Out[260]: 2018-06-25 23:55:00 0 2018-06-26 00:00:00 15 2018-06-26 00:05:00 40 2018-06-26 00:10:00 11 Freq: 5T, dtype: int32
通过groupby进行重采样
只需要传入一个能够访问时间序列的索引上的阻断的函数即可。
In [270]: rng = pd.date_range('2018-6-26',periods=100,freq='D') In [271]: ts = pd.Series(np.arange(100),index=rng) In [272]: ts.groupby(lambda x:x.month).mean() Out[272]: 6 2.0 7 20.0 8 51.0 9 81.5 10 98.0 dtype: float64
升采样和插值
将数据从低频转换到高频,就是升采样。升采样会引入缺失值,此时就需要插值处理。
插值方式跟 fillna 和reindex 一样。
In [299]: frame = pd.DataFrame(np.random.randn(2, 4),index=pd. ...: date_range('1/1/2000', periods=2, freq='W-WED'),colu ...: mns=['Colorado', 'Texas', 'New York', 'Ohio']) In [300]: frame.resample('W-THU').ffill() Out[300]: Colorado Texas New York Ohio 2000-01-06 1.202063 -0.703830 0.050375 -0.158644 2000-01-13 -0.276499 2.183782 0.565678 0.584314 In [301]: frame.resample('D').ffill() Out[301]: Colorado Texas New York Ohio 2000-01-05 1.202063 -0.703830 0.050375 -0.158644 2000-01-06 1.202063 -0.703830 0.050375 -0.158644 2000-01-07 1.202063 -0.703830 0.050375 -0.158644 2000-01-08 1.202063 -0.703830 0.050375 -0.158644 2000-01-09 1.202063 -0.703830 0.050375 -0.158644 2000-01-10 1.202063 -0.703830 0.050375 -0.158644 2000-01-11 1.202063 -0.703830 0.050375 -0.158644 2000-01-12 -0.276499 2.183782 0.565678 0.584314