Pandas必備技能之“時間序列數(shù)據(jù)處理”
時間序列數(shù)據(jù)Time Series Data是在不同時間上收集到的數(shù)據(jù),這類數(shù)據(jù)是按時間順序收集到的,用于所描述現(xiàn)象隨時間變化的情況。
時間序列分析廣泛應(yīng)用于計量經(jīng)濟學(xué)模型中,通過尋找歷史數(shù)據(jù)中某一現(xiàn)象的發(fā)展規(guī)律,對未來進行預(yù)測。
時間序列數(shù)據(jù)作為時間序列分析的基礎(chǔ),學(xué)會如何對它進行巧妙地處理是非常必要的,Python中的Pandas庫為我們提供了強大的時間序列數(shù)據(jù)處理的方法,本文會介紹其中常用的幾個。
【工具】
- Python 3
- Tushare
01、時間格式轉(zhuǎn)換
有時候,我們獲得的原始數(shù)據(jù)并不是按照時間類型索引進行排列的,需要先進行時間格式的轉(zhuǎn)換,為后續(xù)的操作和分析做準備。
這里介紹兩種方法。第一種方法是用pandas.read_csv導(dǎo)入文件的時候,通過設(shè)置參數(shù)parse_dates和index_col,直接對日期列進行轉(zhuǎn)換,并將其設(shè)置為索引。關(guān)于參數(shù)的詳細解釋,請查看文檔【1】。
如下示例中,在沒有設(shè)置參數(shù)之前,可以觀察到數(shù)據(jù)集中的索引是數(shù)字0-208,'date'列的數(shù)據(jù)類型也不是日期。
- In [8]: data = pd.read_csv('unemployment.csv')
- In [9]: data.info()
- <class 'pandas.core.frame.DataFrame'>
- RangeIndex: 209 entries, 0 to 208
- Data columns (total 2 columns):
- date 209 non-null object
- UNRATE 209 non-null float64
- dtypes: float64(1), object(1)
- memory usage: 3.3+ KB
設(shè)置參數(shù)parse_dates = ['date'] ,將數(shù)據(jù)類型轉(zhuǎn)換成日期,再設(shè)置 index_col = 'date',將這一列用作索引,結(jié)果如下。
- In [11]: data = pd.read_csv('unemployment.csv', parse_dates=['date'], index_col='date')
- In [12]: data.info()
- <class 'pandas.core.frame.DataFrame'>
- DatetimeIndex: 209 entries, 2000-01-01 to 2017-05-01
- Data columns (total 1 columns):
- UNRATE 209 non-null float64
- dtypes: float64(1)
- memory usage: 13.3 KB
這時,索引變成了日期'20000101'-'2017-05-01',數(shù)據(jù)類型是datetime。
第二種方法是在已經(jīng)導(dǎo)入數(shù)據(jù)的情況下,用pd.to_datetime()【2】將列轉(zhuǎn)換成日期類型,再用 df.set_index()【3】將其設(shè)置為索引,完成轉(zhuǎn)換。
以tushare.pro上面的日線行情數(shù)據(jù)為例,我們把'trade_date'列轉(zhuǎn)換成日期類型,并設(shè)置成索引。
- import tushare as ts
- import pandas as pd
- pd.set_option('expand_frame_repr', False) # 列太多時不換行
- pro = ts.pro_api()
- df = pro.daily(ts_code='000001.SZ', start_date='20180701', end_date='20180718')
- df.info()
- <class 'pandas.core.frame.DataFrame'>
- RangeIndex: 13 entries, 0 to 12
- Data columns (total 11 columns):
- ts_code 13 non-null object
- trade_date 13 non-null object
- open 13 non-null float64
- high 13 non-null float64
- low 13 non-null float64
- close 13 non-null float64
- pre_close 13 non-null float64
- change 13 non-null float64
- pct_chg 13 non-null float64
- vol 13 non-null float64
- amount 13 non-null float64
- dtypes: float64(9), object(2)
- memory usage: 1.2+ KB
- None
- df['trade_date'] = pd.to_datetime(df['trade_date'])
- df.set_index('trade_date', inplace=True)
- df.sort_values('trade_date', ascending=True, inplace=True) # 升序排列
- df.info()
- <class 'pandas.core.frame.DataFrame'>
- DatetimeIndex: 13 entries, 2018-07-02 to 2018-07-18
- Data columns (total 10 columns):
- ts_code 13 non-null object
- open 13 non-null float64
- high 13 non-null float64
- low 13 non-null float64
- close 13 non-null float64
- pre_close 13 non-null float64
- change 13 non-null float64
- pct_chg 13 non-null float64
- vol 13 non-null float64
- amount 13 non-null float64
- dtypes: float64(9), object(1)
- memory usage: 1.1+ KB
打印出前5行,效果如下。
- df.head()
- Out[15]:
- ts_code open high low close pre_close change pct_chg vol amount
- trade_date
- 2018-07-02 000001.SZ 9.05 9.05 8.55 8.61 9.09 -0.48 -5.28 1315520.13 1158545.868
- 2018-07-03 000001.SZ 8.69 8.70 8.45 8.67 8.61 0.06 0.70 1274838.57 1096657.033
- 2018-07-04 000001.SZ 8.63 8.75 8.61 8.61 8.67 -0.06 -0.69 711153.37 617278.559
- 2018-07-05 000001.SZ 8.62 8.73 8.55 8.60 8.61 -0.01 -0.12 835768.77 722169.579
- 2018-07-06 000001.SZ 8.61 8.78 8.45 8.66 8.60 0.06 0.70 988282.69 852071.526
02、時間周期轉(zhuǎn)換
在完成時間格式轉(zhuǎn)換之后,我們就可以進行后續(xù)的日期操作了。下面介紹一下如何對時間序列數(shù)據(jù)進行重采樣resampling。
重采樣指的是將時間序列從⼀個頻率轉(zhuǎn)換到另⼀個頻率的處理過程。將⾼頻率數(shù)據(jù)聚合到低頻率稱為降采樣downsampling,如將股票的日線數(shù)據(jù)轉(zhuǎn)換成周線數(shù)據(jù),⽽將低頻率數(shù)據(jù)轉(zhuǎn)換到⾼頻率則稱為升采樣upsampling,如將股票的周線數(shù)據(jù)轉(zhuǎn)換成日線數(shù)據(jù)。
降采樣:以日線數(shù)據(jù)轉(zhuǎn)換周線數(shù)據(jù)為例。繼續(xù)使用上面的tushare.pro日線行情數(shù)據(jù),選出特定的幾列。
- df = df[['ts_code', 'open', 'high', 'low', 'close', 'vol']] # 單位:成交量 (手)
- ts_code open high low close vol
- trade_date
- 2018-07-02 000001.SZ 9.05 9.05 8.55 8.61 1315520.13
- 2018-07-03 000001.SZ 8.69 8.70 8.45 8.67 1274838.57
- 2018-07-04 000001.SZ 8.63 8.75 8.61 8.61 711153.37
- 2018-07-05 000001.SZ 8.62 8.73 8.55 8.60 835768.77
- 2018-07-06 000001.SZ 8.61 8.78 8.45 8.66 988282.69
- 2018-07-09 000001.SZ 8.69 9.03 8.68 9.03 1409954.60
- 2018-07-10 000001.SZ 9.02 9.02 8.89 8.98 896862.02
- 2018-07-11 000001.SZ 8.76 8.83 8.68 8.78 851296.70
- 2018-07-12 000001.SZ 8.60 8.97 8.58 8.88 1140492.31
- 2018-07-13 000001.SZ 8.92 8.94 8.82 8.88 603378.21
- 2018-07-16 000001.SZ 8.85 8.90 8.69 8.73 689845.58
- 2018-07-17 000001.SZ 8.74 8.75 8.66 8.72 375356.33
- 2018-07-18 000001.SZ 8.75 8.85 8.69 8.70 525152.77
為了方便大家觀察,把這段時間的日歷附在下面,'2018-07-02'正好是星期一。
轉(zhuǎn)換的思路是這樣的,以日歷中的周進行聚合,如'20180702'-'20180708',取該周期內(nèi),日線開盤價的第一個值作為周開盤價,日線最高價的最大值作為周最高價,日線最低價的最小值作為周最低價,日線收盤價的最后一個值作為周最收盤價,日線最高價的最大值作為周最高價,日線成交量的求和作為周成交量(手),如下圖黃色方框所示。
我們可以通過.resample()【4】方法實現(xiàn)上述操作,對DataFrame和Series都適用。其中,參數(shù)rule設(shè)置需要轉(zhuǎn)換成的頻率,'1W'是一周。
具體轉(zhuǎn)換的代碼如下,日期默認為本周的星期日,如果周期內(nèi)數(shù)據(jù)不全,如'20180722'這周只有3行數(shù)據(jù),也會按照上述方法進行轉(zhuǎn)換。
- freq = '1W'
- df_weekly = df[['open']].resample(rule=freq).first()
- df_weekly['high'] = df['high'].resample(rule=freq).max()
- df_weekly['low'] = df['low'].resample(rule=freq).min()
- df_weekly['close'] = df['close'].resample(rule=freq).last()
- df_weekly['vol'] = df['vol'].resample(rule=freq).sum()
- df_weekly
- Out[33]:
- open high low close vol
- trade_date
- 2018-07-08 9.05 9.05 8.45 8.66 5125563.53
- 2018-07-15 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-22 8.85 8.90 8.66 8.70 1590354.68
升采樣:以周線數(shù)據(jù)轉(zhuǎn)換日線數(shù)據(jù)為例。繼續(xù)使用上面剛剛轉(zhuǎn)換好的周線數(shù)據(jù),我們再試著把它轉(zhuǎn)換成日線數(shù)據(jù)。先通過.resample('D').asfreq()【5】方法,將周線數(shù)據(jù)的頻率轉(zhuǎn)換成日線,效果如下。
- df_daily = df_weekly.resample('D').asfreq()
- print(df_daily)
- Out[52]:
- open high low close vol
- trade_date
- 2018-07-08 9.05 9.05 8.45 8.66 5125563.53
- 2018-07-09 NaN NaN NaN NaN NaN
- 2018-07-10 NaN NaN NaN NaN NaN
- 2018-07-11 NaN NaN NaN NaN NaN
- 2018-07-12 NaN NaN NaN NaN NaN
- 2018-07-13 NaN NaN NaN NaN NaN
- 2018-07-14 NaN NaN NaN NaN NaN
- 2018-07-15 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-16 NaN NaN NaN NaN NaN
- 2018-07-17 NaN NaN NaN NaN NaN
- 2018-07-18 NaN NaN NaN NaN NaN
- 2018-07-19 NaN NaN NaN NaN NaN
- 2018-07-20 NaN NaN NaN NaN NaN
- 2018-07-21 NaN NaN NaN NaN NaN
- 2018-07-22 8.85 8.90 8.66 8.70 1590354.68
結(jié)果中出現(xiàn)了很多空值,需要我們按照一定的方法進行填充,可以通過添加.ffill()或者.bfill()實現(xiàn)。
其中,.ffill()代表用前值進行填充,也就是用前面的非空值對后面的NaN值進行填充,如'20180709'-20180714' 的NaN值都等于'20180708'這一行的非空值,效果如下。
- df_daily = df_weekly.resample('D').ffill()
- df_daily
- Out[54]:
- open high low close vol
- trade_date
- 2018-07-08 9.05 9.05 8.45 8.66 5125563.53
- 2018-07-09 9.05 9.05 8.45 8.66 5125563.53
- 2018-07-10 9.05 9.05 8.45 8.66 5125563.53
- 2018-07-11 9.05 9.05 8.45 8.66 5125563.53
- 2018-07-12 9.05 9.05 8.45 8.66 5125563.53
- 2018-07-13 9.05 9.05 8.45 8.66 5125563.53
- 2018-07-14 9.05 9.05 8.45 8.66 5125563.53
- 2018-07-15 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-16 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-17 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-18 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-19 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-20 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-21 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-22 8.85 8.90 8.66 8.70 1590354.68
同理,.bfill()代表用后值對空值進行填充,效果如下。
- df_daily = df_weekly.resample('D').bfill()
- df_daily
- Out[55]:
- open high low close vol
- trade_date
- 2018-07-08 9.05 9.05 8.45 8.66 5125563.53
- 2018-07-09 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-10 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-11 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-12 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-13 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-14 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-15 8.69 9.03 8.58 8.88 4901983.84
- 2018-07-16 8.85 8.90 8.66 8.70 1590354.68
- 2018-07-17 8.85 8.90 8.66 8.70 1590354.68
- 2018-07-18 8.85 8.90 8.66 8.70 1590354.68
- 2018-07-19 8.85 8.90 8.66 8.70 1590354.68
- 2018-07-20 8.85 8.90 8.66 8.70 1590354.68
- 2018-07-21 8.85 8.90 8.66 8.70 1590354.68
- 2018-07-22 8.85 8.90 8.66 8.70 1590354.68
03、時間窗口函數(shù)
當我們想要比較數(shù)據(jù)在相同時間窗口的不同特征和變化時,可以借助窗口函數(shù)rolling【6】進行計算。
看一個實例:計算股票收盤價的移動平均值。
- df = df[['ts_code', 'close']]
- df
- Out[58]:
- ts_code close
- trade_date
- 2018-07-02 000001.SZ 8.61
- 2018-07-03 000001.SZ 8.67
- 2018-07-04 000001.SZ 8.61
- 2018-07-05 000001.SZ 8.60
- 2018-07-06 000001.SZ 8.66
- 2018-07-09 000001.SZ 9.03
- 2018-07-10 000001.SZ 8.98
- 2018-07-11 000001.SZ 8.78
- 2018-07-12 000001.SZ 8.88
- 2018-07-13 000001.SZ 8.88
- 2018-07-16 000001.SZ 8.73
- 2018-07-17 000001.SZ 8.72
- 2018-07-18 000001.SZ 8.70
調(diào)用rolling函數(shù),通過設(shè)置參數(shù)window的值規(guī)定窗口大小,這里設(shè)置為3,并且調(diào)用.mean()方法計算窗口期為3天的均值,結(jié)果如下。
其中,'20180704'當天的平均值等于'20180702'-'20180704'三天的收盤價取平均的結(jié)果,'20180705'當天的平均值等于'20180703'-'20180705'三天的收盤價取平均的結(jié)果,以此類推。
- df['MA3'] = df['close'].rolling(3).mean()
- df
- Out[76]:
- ts_code close MA3
- trade_date
- 2018-07-02 000001.SZ 8.61 NaN
- 2018-07-03 000001.SZ 8.67 NaN
- 2018-07-04 000001.SZ 8.61 8.630000
- 2018-07-05 000001.SZ 8.60 8.626667
- 2018-07-06 000001.SZ 8.66 8.623333
- 2018-07-09 000001.SZ 9.03 8.763333
- 2018-07-10 000001.SZ 8.98 8.890000
- 2018-07-11 000001.SZ 8.78 8.930000
- 2018-07-12 000001.SZ 8.88 8.880000
- 2018-07-13 000001.SZ 8.88 8.846667
- 2018-07-16 000001.SZ 8.73 8.830000
- 2018-07-17 000001.SZ 8.72 8.776667
- 2018-07-18 000001.SZ 8.70 8.716667
還有一個常用的窗口函數(shù)是expanding,每增加一行數(shù)據(jù),窗口會相應(yīng)的增大。比如,我們想計算某只股票每天的累計漲跌幅,就可以調(diào)用此函數(shù)。
- df = df[['ts_code', 'pct_chg']] # 列pct_chg單位是(%)
- Out[71]:
- ts_code pct_chg
- trade_date
- 2018-07-02 000001.SZ -5.28
- 2018-07-03 000001.SZ 0.70
- 2018-07-04 000001.SZ -0.69
- 2018-07-05 000001.SZ -0.12
- 2018-07-06 000001.SZ 0.70
- 2018-07-09 000001.SZ 4.27
- 2018-07-10 000001.SZ -0.55
- 2018-07-11 000001.SZ -2.23
- 2018-07-12 000001.SZ 2.78
- 2018-07-13 000001.SZ 0.00
- 2018-07-16 000001.SZ -1.69
- 2018-07-17 000001.SZ -0.11
- 2018-07-18 000001.SZ -0.23
對列'pct_chg'調(diào)用窗口函數(shù)expanding,再調(diào)用.sum()方法求累計值。
- df['cum_pct_chg'] = df['pct_chg'].expanding().sum()
- df
- Out[78]:
- ts_code pct_chg cum_pct_chg
- trade_date
- 2018-07-02 000001.SZ -5.28 -5.28
- 2018-07-03 000001.SZ 0.70 -4.58
- 2018-07-04 000001.SZ -0.69 -5.27
- 2018-07-05 000001.SZ -0.12 -5.39
- 2018-07-06 000001.SZ 0.70 -4.69
- 2018-07-09 000001.SZ 4.27 -0.42
- 2018-07-10 000001.SZ -0.55 -0.97
- 2018-07-11 000001.SZ -2.23 -3.20
- 2018-07-12 000001.SZ 2.78 -0.42
- 2018-07-13 000001.SZ 0.00 -0.42
- 2018-07-16 000001.SZ -1.69 -2.11
- 2018-07-17 000001.SZ -0.11 -2.22
- 2018-07-18 000001.SZ -0.23 -2.45
04、總結(jié)
本文介紹了Pandas庫中處理時間序列數(shù)據(jù)的幾種常用方法。
在時間格式轉(zhuǎn)換部分,介紹了兩種將時間轉(zhuǎn)化成日期類型的方法,分別是通過設(shè)置參數(shù)parse_dates和調(diào)用方法pd.to_datetime()。
接著,介紹了時間周期的轉(zhuǎn)換,通過調(diào)用.resample()方法實現(xiàn),包括降采樣和升采樣。
最后,介紹兩個常用的窗口函數(shù)rolling和expanding。
希望大家能靈活掌握本文中提到的方法,并應(yīng)用到實際工作和學(xué)習中去!
譯者簡介:
Little monster,北京第二外國語學(xué)院國際商務(wù)專業(yè)研一在讀,目前在學(xué)習Python編程和量化投資相關(guān)知識。