pandas提供了一套標準的時間序列處理工具和算法,使得咱們能夠很是高效的處理時間序列,好比切片、聚合、重採樣等等。html
本節咱們討論如下三種時間序列:python
Python的時間序列處理是一個很重要的話題,尤爲在金融領域有着很是重要的應用。本節只作簡單的介紹,若是之後有須要的話再深刻學習。本文參考的Working with Time Series對python原生的日期時間處理模塊datetime
,numpy日期時間處理模塊datetime64
,以及第三方日期時間處理模塊dateutil
等都作了介紹,咱們這裏跳過這些,直接進入pandas的時間序列。git
import numpy as np
import pandas as pd
當你使用時間戳做爲數據索引的時候,pandas的時間序列工具就變得是非有用。github
index = pd.DatetimeIndex(['2014-07-04', '2014-08-04',
'2015-07-04', '2015-08-04'])
data = pd.Series([0, 1, 2, 3], index=index)
data
#> 2014-07-04 0
#> 2014-08-04 1
#> 2015-07-04 2
#> 2015-08-04 3
#> dtype: int64
data['2014-07-04':'2015-07-04']
#> 2014-07-04 0
#> 2014-08-04 1
#> 2015-07-04 2
#> dtype: int64
data['2015']
#> 2015-07-04 2
#> 2015-08-04 3
#> dtype: int64
對於時間戳,pandas提供了Timestamp
類型。它基於numpy.datetime64
數據類型,與與之相關的索引類型是DatetimeIndex
。算法
對於時間時期,pandas提供了Period
類型,它是基於numpy.datetime64
編碼的固定頻率間隔。與之相關的索引類型是PeriodIndex
。markdown
對於時間差,pandas提供了Timedelta
類型,一樣基於numpy.datetime64
。與之相關的索引結構是TimedeltaIndex
。數據結構
最基本的時間/日期是時間戳Timestamp
和DatetimeIndex
。可是咱們能夠直接使用pd.to_datetime()
函數來直接解析不一樣的格式。傳入單個日期給pd.to_datetime()
返回一個Timestamp
,傳入一系列日期默認返回DatetimeIndex
。app
dates = pd.to_datetime([datetime(2015, 7, 3), '4th of July, 2015',
'2015-Jul-6', '07-07-2015', '20150708'])
dates
#> DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-06', '2015-07-07',
#> '2015-07-08'],
#> dtype='datetime64[ns]', freq=None)
當一個日期減去另外一個日期的時候,TimedeltaIndex
就會被建立:函數
dates - dates[0]
#> TimedeltaIndex(['0 days', '1 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq=None)
爲了方便地建立按期的日期序列,pandas提供了一些函數:pd.date_range()
建立時間戳,pd.period_range()
建立時期,pd.timedelta_range()
建立時間差。默認狀況下頻率間隔是一天。工具
pd.date_range('2015-07-03', '2015-07-10')
#> DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-05', '2015-07-06',
#> '2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10'],
#> dtype='datetime64[ns]', freq='D')
另外能夠不指定終點,而是提供一個時期數:
pd.date_range('2015-07-03', periods=8)
#> DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-05', '2015-07-06',
#> '2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10'],
#> dtype='datetime64[ns]', freq='D')
頻率間隔能夠經過freq
參數設置,默認是D
:
pd.date_range('2015-07-03', periods=8, freq='H')
#> DatetimeIndex(['2015-07-03 00:00:00', '2015-07-03 01:00:00',
#> '2015-07-03 02:00:00', '2015-07-03 03:00:00',
#> '2015-07-03 04:00:00', '2015-07-03 05:00:00',
#> '2015-07-03 06:00:00', '2015-07-03 07:00:00'],
#> dtype='datetime64[ns]', freq='H')
相似的
pd.period_range('2015-07', periods=8, freq='M')
#> PeriodIndex(['2015-07', '2015-08', '2015-09', '2015-10', '2015-11', '2015-12',
#> '2016-01', '2016-02'],
#> dtype='period[M]', freq='M')
pd.timedelta_range(0, periods=10, freq='H')
#> TimedeltaIndex(['00:00:00', '01:00:00', '02:00:00', '03:00:00', '04:00:00',
#> '05:00:00', '06:00:00', '07:00:00', '08:00:00', '09:00:00'],
#> dtype='timedelta64[ns]', freq='H')
以上這些時間序列處理工具最基礎的概念是頻率和時間偏移。下表總結了主要的代碼表示:
代碼 | 描述 | 代碼 | 描述 |
---|---|---|---|
D |
日曆天 | L |
釐秒 |
W |
星期 | U |
毫秒 |
M |
月末 | N |
納秒 |
Q |
季度末 | B |
工做日 |
A |
年底 | BM |
工做月末 |
H |
小時 | BQ |
工做季末 |
T |
分鐘 | BA |
工做年底 |
S |
秒 | BH |
工做時 |
上面的月,季度以及週期性頻率都被標記爲這一時期的終點,若是想變成起始點能夠加S
後綴。
代碼 | 描述 | 代碼 | 描述 |
---|---|---|---|
MS |
月初 | BMS |
工做月初 |
QS |
季度初 | BQS |
工做季初 |
AS |
年初 | BAS |
工做年初 |
另外,還能夠經過加後綴的方法改變季度或年的月份等。
Q-JAN
, BQ-FEB
, BQS-APR
等等;A-JAN
, BA-FEB
, BAS-APR
等等。
相似的,星期也能夠分解成周一,週二…
W-SUN
, W-MON
, W-WED
等等
以上這些代碼還能夠與數字結合來表示頻率, 例如咱們要表示2小時30分的頻率:
pd.timedelta_range(0, periods=9, freq="2H30T")
#> TimedeltaIndex(['00:00:00', '02:30:00', '05:00:00', '07:30:00', '10:00:00',
#> '12:30:00', '15:00:00', '17:30:00', '20:00:00'],
#> dtype='timedelta64[ns]', freq='150T')
以上這些代碼表示的含義還能夠經過pd.tseries.offsets
模塊表示:
from pandas.tseries.offsets import BDay
pd.date_range('2015-07-01', periods=5, freq=BDay())
#> DatetimeIndex(['2015-07-01', '2015-07-02', '2015-07-03', '2015-07-06',
#> '2015-07-07'],
#> dtype='datetime64[ns]', freq='B')
更加詳細的討論能夠看DateOffset。
咱們以一些真實的金融數據爲例講解,pandas-datareader
(可經過conda install pandas-datareader
來安裝, 若是)包包含谷歌,雅虎等公司的金融數據。
from pandas_datareader import data
goog = data.DataReader('GOOG', start='2004', end='2016',
data_source='google')
goog.head()
#> Open High Low Close Volume
#> Date
#> 2004-08-19 49.96 51.98 47.93 50.12 NaN
#> 2004-08-20 50.69 54.49 50.20 54.10 NaN
#> 2004-08-23 55.32 56.68 54.47 54.65 NaN
#> 2004-08-24 55.56 55.74 51.73 52.38 NaN
#> 2004-08-25 52.43 53.95 51.89 52.95 NaN
簡單起見,咱們只是用收盤價格
goog = goog['Close']
爲了更好的說明問題,咱們能夠利用matplotlib對數據進行可視化,關於matplotlib能夠經過Python for Data Analysis這本書進行學習,也能夠經過官網的gallary進行學習,我我的當時是在官網看了大量的實例,有什麼需求直接去官網找例子,都很方便。本系列學習筆記不會包含數據可視化部分。
import matplotlib.pyplot as plt
goog.plot()
重採樣(resampling)指的是將時間序列從一個頻率轉換到另外一個頻率的處理過程。將高頻數據聚合到低頻稱爲降採樣(downsampling),將低頻數據轉換到高頻則稱爲升採樣(upsampling)。除此之外還存在一種採樣方式既不是升採樣,也不是降採樣,好比W-WED
轉換成W-FRI
。
能夠經過resample()
函數來實現,也能夠經過更簡單的方式asfreq()
函數來實現。二者基本的不一樣點在於resample()
是一種數據聚合方式asfreq()
是一種數據選取方式。
goog.plot(alpha=0.5, style='-')
goog.resample('BA').mean().plot(style=':')
goog.asfreq('BA').plot(style='--');
plt.legend(['input', 'resample', 'asfreq'],
loc='upper left')
resample()
的處理過程是取全年的數據的平均值,而asfreq()
是選取年底的時間點的數據。
對於升採樣來講,resample()
與asfreq()
是等效的。對於空置採樣點都會用NaN
來進行填充。就像pd.fillna()
方法,asfreq()
接受一個參數method
可指定缺失值的填充方法。ffill
表示與前面的值保持一致,bfill
表示與後面的值保持一致等等。
fig, ax = plt.subplots(2, sharex=True)
data = goog.iloc[:10]
data.asfreq('D').plot(ax=ax[0], marker='o')
data.asfreq('D', method='bfill').plot(ax=ax[1], style='-o')
data.asfreq('D', method='ffill').plot(ax=ax[1], style='--o')
ax[1].legend(["back-fill", "forward-fill"])
另外一個與時間序列相關的操做是數據在時間軸的移動。pandas提供了兩個相關的函數:shift()
和tshift()
,二者的不一樣是shift()
移動的是數據,tshift()
移動的是時間軸(索引)。
fig, ax = plt.subplots(3, sharey=True)
# apply a frequency to the data
goog = goog.asfreq('D', method='pad')
goog.plot(ax=ax[0])
goog.shift(900).plot(ax=ax[1])
goog.tshift(900).plot(ax=ax[2])
# legends and annotations
local_max = pd.to_datetime('2007-11-05')
offset = pd.Timedelta(900, 'D')
ax[0].legend(['input'], loc=2)
ax[0].get_xticklabels()[2].set(weight='heavy', color='red')
ax[0].axvline(local_max, alpha=0.3, color='red')
ax[1].legend(['shift(900)'], loc=2)
ax[1].get_xticklabels()[2].set(weight='heavy', color='red')
ax[1].axvline(local_max + offset, alpha=0.3, color='red')
ax[2].legend(['tshift(900)'], loc=2)
ax[2].get_xticklabels()[1].set(weight='heavy', color='red')
ax[2].axvline(local_max + offset, alpha=0.3, color='red')
時移一個重要做用即便計算一段時間內的差異,好比計算谷歌股票一年的投資回報率:
ROI = 100 * (goog.tshift(-365) / goog - 1)
ROI.plot()
plt.ylabel('% Return on Investment')
滾動統計是時間序列的又一重要的操做。能夠經過Series
和DataFrame
的rolling()
方法實現,返回相似於groupby
的操做,一樣也有許多數據聚合的操做。
rolling = goog.rolling(365, center=True)
data = pd.DataFrame({'input': goog,
'one-year rolling_mean': rolling.mean(),
'one-year rolling_std': rolling.std()})
ax = data.plot(style=['-', '--', ':'])
ax.lines[0].set_alpha(0.3)