對於時間序列的處理在數據處理方面還是比較重要的一塊。這篇來記錄一下時間序列的一些知識點。
python標準庫包含日期(date)和時間(time)數據的數據類型。
經常使用的也就是datetime、time以及calendar模塊。datetime以毫秒形式存儲日期和時間。
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
字符串和datetime之間的轉換
利用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]: <Hour> 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')
移動數據: shift,rollforward,rollback
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
如果加的是錨點偏移量(比如 MonthEnd 當月月底)第一次增量會將原日期向前滾動到符合頻率規則的下一個日期:
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