十一、處理時間序列
本文涉及的日期與時間數據主要包含三類:
- 時間戳:表示某個具體的時間點
- 時間間隔與週期:表示開始時間點與結束時間點之間的時間長度,週期通常指一種特殊形式的時間間隔,每個間隔長度相同,彼此之間不會重疊
- 時間增量(time delta)或持續時間(duration):表示精確的時間長度
(一)Python的日期與時間工具
在Python標準庫與第三方庫中有許多可以表示日期、時間、時間增量和時間跨度(timespan)的工具,儘管Pandas提供的時間序列工具更適合處理數據科學問題,但瞭解Pandas與Python標準庫以及第三方庫中的其他時間序列工具之間的關聯性將大有裨益。
(1) 原生的Python日期與時間工具:datetime
與dateutil
Python基本的日期與時間功能都在標準庫的datetime
模塊中,如果和第三方庫dateutil
模塊搭配使用,可以快速實現許多處理日期與時間的功能。
優缺點:datetime
和dateutil
模塊在靈活性和易用性方面都表現出色,但如果處理的時間數據量比較大,速度會比較慢。
# 用datetime類型創建日期
from datetime import datetime
datetime(year=2020, month=5, day=8)
# datetime.datetime(2020, 5, 8, 0, 0)
# 使用dateutil模塊對各種字符串格式的日期進行解析
from dateutil import parser
date = parser.parse("8th of May, 2020")
date
# datetime.datetime(2020, 5, 8, 0, 0)
# 打印date是星期幾
date.strftime('%A')
# 'Friday'
(2) 時間類型數組:NumPy的datetime64
類型
datetime64
類型將日期編碼爲64位整數,使日期數組更節省內存,datetime64
需要在設置日期時確定具體的輸入類型
import numpy as np
date = np.array('2020-05-08', dtype=np.datetime64)
date
# array('2020-05-08', dtype='datetime64[D]')
# 可以進行快速的向量化運算
date + np.arange(12)
# array(['2020-05-08', '2020-05-09', '2020-05-10', '2020-05-11',
# '2020-05-12', '2020-05-13', '2020-05-14', '2020-05-15',
# '2020-05-16', '2020-05-17', '2020-05-18', '2020-05-19'],
# dtype='datetime64[D]')
datetime64
是在基本時間單位(fundamental time unit)的基礎上建立的,可編碼的時間範圍是基本單元的倍,在時間精度(time resolution)與最大時間跨度(maximum time span)之間達成一種平衡。
時區將自動設置爲執行代碼的操作系統的當地時區,可以通過各種格式的代碼設置基本時間單位
# 以天爲單位的日期
np.datetime64('2020-05-08')
# numpy.datetime64('2020-05-08')
# 以分鐘爲單位的日期
np.datetime64('2020-05-08 20:00')
# numpy.datetime64('2020-05-08T20:00')
# 將時間單位設置爲納秒
np.datetime64('2020-05-08 19:59:59.50', 'ns')
# numpy.datetime64('2020-05-08T19:59:59.500000000')
日期與時間單位格式代碼
代碼 | 含義 |
---|---|
Y | 年(year) |
M | 月(month) |
W | 周(week) |
D | 日(day) |
h | 時(hour) |
m | 分(minute) |
s | 秒(second) |
ms | 毫秒(millisecond) |
us | 微秒(microsecond) |
ns | 納秒(nanosecond) |
ps | 皮秒(picosecond) |
fs | 飛秒(femtosecond) |
as | 原秒(attosecond) |
對於日常工作中的時間數據類型,默認單位都用納秒datetime64[ns]
,因爲用它表示時間範圍精度可以滿足絕大部分需求。
優缺點:
- NumPy的
datetime64
比Python的datetime
對象的運算速度快很多,尤其是處理較大數組時 - 雖然
datetime64
彌補了Python原生的datetime
類型的不足,但是缺少許多datetime(dateutil)
原本具備的便捷方法與函數。
(3) Pandas的日期與時間工具:理想與現實的最佳解決方案
Pandas所有關於日期與時間的處理方法都是通過Timestamp
對象實現的,它利用numpy.datetime64
的有效存儲和向量化接口將datetime
和dateutil
的易用性有機結合起來。
Pandas通過一組Timestamp
對象就可以創建一個可以作爲Series或DataFrame索引的DatetimeIndex
。
import pandas as pd
date = pd.to_datetime("8th of May, 2020")
date
# Timestamp('2020-05-08 00:00:00')
# 獲取星期幾
date.strftime('%A')
# 'Friday'
# 向量化運算
date + pd.to_timedelta(np.arange(12), 'D')
# DatetimeIndex(['2020-05-08', '2020-05-09', '2020-05-10', '2020-05-11',
# '2020-05-12', '2020-05-13', '2020-05-14', '2020-05-15',
# '2020-05-16', '2020-05-17', '2020-05-18', '2020-05-19'],
# dtype='datetime64[ns]', freq=None)
(二)Pandas時間序列:用時間作索引
Pandas時間序列工具非常適合用來處理帶時間戳的索引數據。
# 通過一個時間索引數據創建一個Series對象
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
# 直接通過年份切片獲取該年的數據(僅在此類Series上可用的取值操作)
data['2015']
# 2015-07-04 2
# 2015-08-04 3
# dtype: int64
(三)Pandas時間序列數據結構
Pandas用來處理時間序列的基礎數據類型:(均基於numpy.datetime64
類型基礎創建)
- 針對時間戳數據,
Timestamp
類型,對應索引數據結構是DatetimeIndex
(替代Python原生datetime
類型) - 針對時間週期數據,
Period
類型,對應索引數據結構PeriodIndex
- 針對時間增量或持續時間,
Timedelta
類型,對應索引數據結構TimedeltaIndex
(替代Python原生datetime.timedelta
類型)
最基礎的日期/時間對象是Timestamp
和DatetimeIndex
,兩種對象可以直接使用。
最常用方法是pd.to_datetime()
函數,可以解析許多日期與時間格式。傳遞一個日期會返回一個Timestamp
類型,傳遞一個時間序列會返回一個DatetimeIndex
類型。
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)
任何DatetimeIndex類型都可以通過to_period()
方法和一個頻率代碼轉換成PeriodIndex
類型
# 用'D'將數據轉換成單日的時間序列
dates.to_period('D')
# PeriodIndex(['2015-07-03', '2015-07-04', '2015-07-06', '2015-07-07',
# '2015-07-08'],
# dtype='period[D]', freq='D')
當用一個日期減去另一個日期時,返回的結果是TimedeltaIndex
類型
dates - dates[0]
# TimedeltaIndex(['0 days', '1 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq=None)
有規律的時間序列:pd.date_range()
pd.date_range()
可以處理時間戳、pd.period_range()
可以處理週期、pd.timedelta_range()
可以處理時間間隔
- 用法與Python的
range()
和NumPy的np.arange()
類似,通過開始日期、結束日期和頻率代碼(可選)創建一個有規律的日期序列,默認頻率是天 - 日期範圍不一定非是開始時間與結束時間,也可以時開始時間與週期數
periods
- 可以通過
freq
參數改變時間間隔,默認值是D
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')
# 按小時變化的時間戳
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')
(四)時間頻率與偏移量
Pandas時間序列工具的基礎是時間頻率或偏移量(offset)代碼。
Pandas頻率代碼
代碼 | 描述 | 代碼 | 描述 |
---|---|---|---|
D | 天(calendar day,按日曆算,含雙休日) | B | 天(business day,僅含工作日) |
W | 周(weekly) | ||
M | 月末(month end) | BM | 月末(僅含工作日) |
Q | 季末(quarter end) | BQ | 季末(僅含工作日) |
A | 年末(year end) | BA | 年末(僅含工作日) |
H | 小時(hours) | BH | 小時(僅含工作日) |
T | 分鐘(minutes) | ||
S | 秒(seconds) | ||
L | 毫秒(milliseconds) | ||
U | 微秒(microseconds) | ||
N | 納秒(nanoseconds) |
月、季、年頻率都是具體週期的結束時間(月末、季末、年末),而以S爲後綴的代碼表示日期開始
帶開始索引的頻率代碼
代碼 | 頻率 | 代碼 | 頻率 |
---|---|---|---|
MS | 月初(month start) | BMS | 月初(bisiness month start,僅含工作日) |
QS | 季初(quarter start) | BQS | 季初(僅含工作日) |
AS | 年初(year start) | BAS | 年初(僅含工作日) |
另外,可以在頻率代碼後面加三維月份縮寫字母來改變季、年頻率的開始時間:
- Q-JAN, BQ-FEB, QS-MAR, BQS-APR等
- A-JAN, BA-FEB, AS-MAR, BAS-APR等
也可以在後面加三位星期縮寫字母來改變一週的開始時間:
- W-SUN, W-MON, W-TUE, W-WED等
還可以將頻率組合起來創建新的週期
# 用小時(H)和分鐘(T)的組合來實現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')
頻率代碼都對應Pandas時間序列的偏移量,詳見pd.tseries.offsets
模塊(Date 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')
Pandas 相關閱讀:
[Python3] Pandas —— (一) 對象、數據取值與運算
[Python3] Pandas —— (二) 處理缺失值
[Python3] Pandas —— (三) 層級索引
[Python3] Pandas —— (四) 合併數據集
[Python3] Pandas —— (五) 累計與分組
[Python3] Pandas —— (六) 數據透視表
[Python3] Pandas —— (七) 向量化字符串操作
總結自《Python數據科學手冊》