Pandas基礎(十一)時間序列


pandas提供了一套標准的時間序列處理工具和算法,使得我們可以非常高效的處理時間序列,比如切片、聚合、重采樣等等。

本節我們討論以下三種時間序列:

  • 時間戳(time stamp):一個特定的時間點,比如2018年4月2日。
  • 時間間隔和時期(time period):兩個特定的時間起點和終點,比如2018年4月2日-2019年4月2日。
  • 時間差或持續時間(time deltas):特定的時間長度,比如20秒。

Python的時間序列處理是一個很重要的話題,尤其在金融領域有着非常重要的應用。本節只做簡單的介紹,如果以后有需要的話再深入學習。本文參考的Working with Time Series對python原生的日期時間處理模塊datetime,numpy日期時間處理模塊datetime64,以及第三方日期時間處理模塊dateutil等都做了介紹,我們這里跳過這些,直接進入pandas的時間序列。

  1. import numpy as np
  2. import pandas as pd

1. pandas時間序列:時間索引

當你使用時間戳作為數據索引的時候,pandas的時間序列工具就變得是非有用。

  1. index = pd.DatetimeIndex(['2014-07-04', '2014-08-04',
  2. '2015-07-04', '2015-08-04'])
  3. data = pd.Series([0, 1, 2, 3], index=index)
  4. data
  5. #> 2014-07-04 0
  6. #> 2014-08-04 1
  7. #> 2015-07-04 2
  8. #> 2015-08-04 3
  9. #> dtype: int64
  10. data['2014-07-04':'2015-07-04']
  11. #> 2014-07-04 0
  12. #> 2014-08-04 1
  13. #> 2015-07-04 2
  14. #> dtype: int64
  15. data['2015']
  16. #> 2015-07-04 2
  17. #> 2015-08-04 3
  18. #> dtype: int64

2. pandas時間序列數據結構

  • 對於時間戳,pandas提供了Timestamp類型。它基於numpy.datetime64數據類型,與與之相關的索引類型是DatetimeIndex

  • 對於時間時期,pandas提供了Period類型,它是基於numpy.datetime64編碼的固定頻率間隔。與之相關的索引類型是PeriodIndex

  • 對於時間差,pandas提供了Timedelta類型,同樣基於numpy.datetime64。與之相關的索引結構是TimedeltaIndex

最基本的時間/日期是時間戳TimestampDatetimeIndex。但是我們可以直接使用pd.to_datetime()函數來直接解析不同的格式。傳入單個日期給pd.to_datetime()返回一個Timestamp,傳入一系列日期默認返回DatetimeIndex

  1. dates = pd.to_datetime([datetime(2015, 7, 3), '4th of July, 2015',
  2. '2015-Jul-6', '07-07-2015', '20150708'])
  3. dates
  4. #> DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-06', '2015-07-07',
  5. #> '2015-07-08'],
  6. #> dtype='datetime64[ns]', freq=None)

當一個日期減去另一個日期的時候,TimedeltaIndex就會被創建:

  1. dates - dates[0]
  2. #> TimedeltaIndex(['0 days', '1 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq=None)

2.1 定期序列

為了方便地創建定期的日期序列,pandas提供了一些函數:pd.date_range()創建時間戳,pd.period_range()創建時期,pd.timedelta_range()創建時間差。默認情況下頻率間隔是一天。

  1. pd.date_range('2015-07-03', '2015-07-10')
  2. #> DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-05', '2015-07-06',
  3. #> '2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10'],
  4. #> dtype='datetime64[ns]', freq='D')

另外可以不指定終點,而是提供一個時期數:

  1. pd.date_range('2015-07-03', periods=8)
  2. #> DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-05', '2015-07-06',
  3. #> '2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10'],
  4. #> dtype='datetime64[ns]', freq='D')

頻率間隔可以通過freq參數設置,默認是D:

  1. pd.date_range('2015-07-03', periods=8, freq='H')
  2. #> DatetimeIndex(['2015-07-03 00:00:00', '2015-07-03 01:00:00',
  3. #> '2015-07-03 02:00:00', '2015-07-03 03:00:00',
  4. #> '2015-07-03 04:00:00', '2015-07-03 05:00:00',
  5. #> '2015-07-03 06:00:00', '2015-07-03 07:00:00'],
  6. #> dtype='datetime64[ns]', freq='H')

類似的

  1. pd.period_range('2015-07', periods=8, freq='M')
  2. #> PeriodIndex(['2015-07', '2015-08', '2015-09', '2015-10', '2015-11', '2015-12',
  3. #> '2016-01', '2016-02'],
  4. #> dtype='period[M]', freq='M')
  5. pd.timedelta_range(0, periods=10, freq='H')
  6. #> TimedeltaIndex(['00:00:00', '01:00:00', '02:00:00', '03:00:00', '04:00:00',
  7. #> '05:00:00', '06:00:00', '07:00:00', '08:00:00', '09:00:00'],
  8. #> dtype='timedelta64[ns]', freq='H')

3. 頻率和偏移

以上這些時間序列處理工具最基礎的概念是頻率和時間偏移。下表總結了主要的代碼表示:

代碼 描述 代碼 描述
D 日歷天 L 厘秒
W 星期 U 毫秒
M 月末 N 納秒
Q 季度末 B 工作日
A 年末 BM 工作月末
H 小時 BQ 工作季末
T 分鍾 BA 工作年末
S BH 工作時

上面的月,季度以及周期性頻率都被標記為這一時期的終點,如果想變成起始點可以加S后綴。

代碼 描述 代碼 描述
MS 月初 BMS 工作月初
QS 季度初 BQS 工作季初
AS 年初 BAS 工作年初

另外,還可以通過加后綴的方法改變季度或年的月份等。

  • Q-JANBQ-FEBBQS-APR等等;
  • A-JANBA-FEBBAS-APR等等。

    類似的,星期也可以分解成周一,周二…

  • W-SUNW-MONW-WED等等

    以上這些代碼還可以與數字結合來表示頻率, 例如我們要表示2小時30分的頻率:

  1. pd.timedelta_range(0, periods=9, freq="2H30T")
  2. #> TimedeltaIndex(['00:00:00', '02:30:00', '05:00:00', '07:30:00', '10:00:00',
  3. #> '12:30:00', '15:00:00', '17:30:00', '20:00:00'],
  4. #> dtype='timedelta64[ns]', freq='150T')

以上這些代碼表示的含義還可以通過pd.tseries.offsets模塊表示:

  1. from pandas.tseries.offsets import BDay
  2. pd.date_range('2015-07-01', periods=5, freq=BDay())
  3. #> DatetimeIndex(['2015-07-01', '2015-07-02', '2015-07-03', '2015-07-06',
  4. #> '2015-07-07'],
  5. #> dtype='datetime64[ns]', freq='B')

更加詳細的討論可以看DateOffset

4. 重采樣,轉移,加窗口

我們以一些真實的金融數據為例講解,pandas-datareader(可通過conda install pandas-datareader來安裝, 如果)包包含谷歌,雅虎等公司的金融數據。

  1. from pandas_datareader import data
  2. goog = data.DataReader('GOOG', start='2004', end='2016',
  3. data_source='google')
  4. goog.head()
  5. #> Open High Low Close Volume
  6. #> Date
  7. #> 2004-08-19 49.96 51.98 47.93 50.12 NaN
  8. #> 2004-08-20 50.69 54.49 50.20 54.10 NaN
  9. #> 2004-08-23 55.32 56.68 54.47 54.65 NaN
  10. #> 2004-08-24 55.56 55.74 51.73 52.38 NaN
  11. #> 2004-08-25 52.43 53.95 51.89 52.95 NaN

簡單起見,我們只是用收盤價格

  1. goog = goog['Close']

為了更好的說明問題,我們可以利用matplotlib對數據進行可視化,關於matplotlib可以通過Python for Data Analysis這本書進行學習,也可以通過官網的gallary進行學習,我個人當時是在官網看了大量的實例,有什么需求直接去官網找例子,都很方便。本系列學習筆記不會包含數據可視化部分。

  1. import matplotlib.pyplot as plt
  2. goog.plot()

4.1 重采樣及頻率轉換

重采樣(resampling)指的是將時間序列從一個頻率轉換到另一個頻率的處理過程。將高頻數據聚合到低頻稱為降采樣(downsampling),將低頻數據轉換到高頻則稱為升采樣(upsampling)。除此以外還存在一種采樣方式既不是升采樣,也不是降采樣,比如W-WED轉換成W-FRI

可以通過resample()函數來實現,也可以通過更簡單的方式asfreq()函數來實現。兩者基本的不同點在於resample()是一種數據聚合方式asfreq()是一種數據選取方式。

  1. goog.plot(alpha=0.5, style='-')
  2. goog.resample('BA').mean().plot(style=':')
  3. goog.asfreq('BA').plot(style='--');
  4. plt.legend(['input', 'resample', 'asfreq'],
  5. loc='upper left')

resample()的處理過程是取整年的數據的平均值,而asfreq()是選取年末的時間點的數據。

對於升采樣來說,resample()asfreq()是等效的。對於空置采樣點都會用NaN來進行填充。就像pd.fillna()方法,asfreq()接受一個參數method可指定缺失值的填充方法。ffill表示與前面的值保持一致,bfill表示與后面的值保持一致等等。

  1. fig, ax = plt.subplots(2, sharex=True)
  2. data = goog.iloc[:10]
  3. data.asfreq('D').plot(ax=ax[0], marker='o')
  4. data.asfreq('D', method='bfill').plot(ax=ax[1], style='-o')
  5. data.asfreq('D', method='ffill').plot(ax=ax[1], style='--o')
  6. ax[1].legend(["back-fill", "forward-fill"])

4.2 時間移動

另一個與時間序列相關的操作是數據在時間軸的移動。pandas提供了兩個相關的函數:shift()tshift(),兩者的不同是shift()移動的是數據,tshift()移動的是時間軸(索引)。

  1. fig, ax = plt.subplots(3, sharey=True)
  2. # apply a frequency to the data
  3. goog = goog.asfreq('D', method='pad')
  4. goog.plot(ax=ax[0])
  5. goog.shift(900).plot(ax=ax[1])
  6. goog.tshift(900).plot(ax=ax[2])
  7. # legends and annotations
  8. local_max = pd.to_datetime('2007-11-05')
  9. offset = pd.Timedelta(900, 'D')
  10. ax[0].legend(['input'], loc=2)
  11. ax[0].get_xticklabels()[2].set(weight='heavy', color='red')
  12. ax[0].axvline(local_max, alpha=0.3, color='red')
  13. ax[1].legend(['shift(900)'], loc=2)
  14. ax[1].get_xticklabels()[2].set(weight='heavy', color='red')
  15. ax[1].axvline(local_max + offset, alpha=0.3, color='red')
  16. ax[2].legend(['tshift(900)'], loc=2)
  17. ax[2].get_xticklabels()[1].set(weight='heavy', color='red')
  18. ax[2].axvline(local_max + offset, alpha=0.3, color='red')

時移一個重要作用即使計算一段時間內的差別,比如計算谷歌股票一年的投資回報率:

  1. ROI = 100 * (goog.tshift(-365) / goog - 1)
  2. ROI.plot()
  3. plt.ylabel('% Return on Investment')

4.3 滾動窗口

滾動統計是時間序列的又一重要的操作。可以通過SeriesDataFramerolling()方法實現,返回類似於groupby的操作,同樣也有許多數據聚合的操作。

  1. rolling = goog.rolling(365, center=True)
  2. data = pd.DataFrame({'input': goog,
  3. 'one-year rolling_mean': rolling.mean(),
  4. 'one-year rolling_std': rolling.std()})
  5. ax = data.plot(style=['-', '--', ':'])
  6. ax.lines[0].set_alpha(0.3)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM