【翻譯】《利用Python進行數據分析·第2版》第11章(中) 時間序列
來自專欄 Python程序員20 人贊了文章
作者:SeanCheney Python愛好者社區專欄作者
簡書專欄:https://www.jianshu.com/u/130f76596b02
前文傳送門:
【翻譯】《利用Python進行數據分析·第2版》第1章 準備工作
【翻譯】《利用Python進行數據分析·第2版》第2章(上)Python語法基礎,IPython和Jupyter
【翻譯】《利用Python進行數據分析·第2版》第2章(中)Python語法基礎,IPython和Jupyter
【翻譯】《利用Python進行數據分析·第2版》第2章(下)Python語法基礎,IPython和Jupyter
【翻譯】《利用Python進行數據分析·第2版》第3章(上)Python的數據結構、函數和文件
【翻譯】《利用Python進行數據分析·第2版》第3章(中)Python的數據結構、函數和文件
【翻譯】《利用Python進行數據分析·第2版》第3章(下)Python的數據結構、函數和文件
【翻譯】《利用Python進行數據分析·第2版》第4章(上)NumPy基礎:數組和矢量計算
【翻譯】《利用Python進行數據分析·第2版》第4章(中)NumPy基礎:數組和矢量計算
【翻譯】《利用Python進行數據分析·第2版》第4章(下)NumPy基礎:數組和矢量計算
【翻譯】《利用Python進行數據分析·第2版》第5章(上)pandas入門
【翻譯】《利用Python進行數據分析·第2版》第5章(中)pandas入門
【翻譯】《利用Python進行數據分析·第2版》第5章(下)pandas入門
【翻譯】《利用Python進行數據分析·第2版》第6章(上) 數據載入、存儲與文件格式
【翻譯】《利用Python進行數據分析·第2版》第6章(中) 數據載入、存儲與文件格式
【翻譯】《利用Python進行數據分析·第2版》第6章(下) 數據載入、存儲與文件格式
【翻譯】《利用Python進行數據分析·第2版》第7章(上)數據清洗和準備
【翻譯】《利用Python進行數據分析·第2版》第7章(中) 數據清洗和準備
【翻譯】《利用Python進行數據分析·第2版》第7章(下) 數據清洗和準備
【翻譯】《利用Python進行數據分析·第2版》第8章(上) 數據規整:聚合、合併和重塑
【翻譯】《利用Python進行數據分析·第2版》第8章(中) 數據規整:聚合、合併和重塑
【翻譯】《利用Python進行數據分析·第2版》第8章(下) 數據規整:聚合、合併和重塑
【翻譯】《利用Python進行數據分析·第2版》第9章(上) 繪圖和可視化
【翻譯】《利用Python進行數據分析·第2版》第9章(中) 繪圖和可視化
【翻譯】《利用Python進行數據分析·第2版》第9章(下) 繪圖和可視化
【翻譯】《利用Python進行數據分析·第2版》第10章(上) 數據聚合與分組運算
【翻譯】《利用Python進行數據分析·第2版》第10章(中) 數據聚合與分組運算
【翻譯】《利用Python進行數據分析·第2版》第10章(下) 數據聚合與分組運算
【翻譯】《利用Python進行數據分析·第2版》第11章(上) 時間序列
11.3 日期的範圍、頻率以及移動
pandas中的原生時間序列一般被認為是不規則的,也就是說,它們沒有固定的頻率。對於大部分應用程序而言,這是無所謂的。但是,它常常需要以某種相對固定的頻率進行分析,比如每日、每月、每15分鐘等(這樣自然會在時間序列中引入缺失值)。幸運的是,pandas有一整套標準時間序列頻率以及用於重採樣、頻率推斷、生成固定頻率日期範圍的工具。例如,我們可以將之前那個時間序列轉換為一個具有固定頻率(每日)的時間序列,只需調用resample即可:
In [72]: tsOut[72]: 2011-01-02 -0.2047082011-01-05 0.4789432011-01-07 -0.5194392011-01-08 -0.5557302011-01-10 1.9657812011-01-12 1.393406dtype: float64In [73]: resampler = ts.resample(D)
字元串「D」是每天的意思。
頻率的轉換(或重採樣)是一個比較大的主題,稍後將專門用一節來進行討論(11.6小節)。這裡,我將告訴你如何使用基本的頻率和它的倍數。
生成日期範圍
雖然我之前用的時候沒有明說,但你可能已經猜到pandas.date_range可用於根據指定的頻率生成指定長度的DatetimeIndex:
In [74]: index = pd.date_range(2012-04-01, 2012-06-01)In [75]: indexOut[75]: DatetimeIndex([2012-04-01, 2012-04-02, 2012-04-03, 2012-04-04, 2012-04-05, 2012-04-06, 2012-04-07, 2012-04-08, 2012-04-09, 2012-04-10, 2012-04-11, 2012-04-12, 2012-04-13, 2012-04-14, 2012-04-15, 2012-04-16, 2012-04-17, 2012-04-18, 2012-04-19, 2012-04-20, 2012-04-21, 2012-04-22, 2012-04-23, 2012-04-24, 2012-04-25, 2012-04-26, 2012-04-27, 2012-04-28, 2012-04-29, 2012-04-30, 2012-05-01, 2012-05-02, 2012-05-03, 2012-05-04, 2012-05-05, 2012-05-06, 2012-05-07, 2012-05-08, 2012-05-09, 2012-05-10, 2012-05-11, 2012-05-12, 2012-05-13, 2012-05-14, 2012-05-15, 2012-05-16, 2012-05-17, 2012-05-18, 2012-05-19, 2012-05-20, 2012-05-21, 2012-05-22, 2012-05-23, 2012-05-24, 2012-05-25, 2012-05-26, 2012-05-27, 2012-05-28, 2012-05-29, 2012-05-30, 2012-05-31, 2012-06-01], dtype=datetime64[ns], freq=D)
默認情況下,date_range會產生按天計算的時間點。如果只傳入起始或結束日期,那就還得傳入一個表示一段時間的數字:
In [76]: pd.date_range(start=2012-04-01, periods=20)Out[76]: DatetimeIndex([2012-04-01, 2012-04-02, 2012-04-03, 2012-04-04, 2012-04-05, 2012-04-06, 2012-04-07, 2012-04-08, 2012-04-09, 2012-04-10, 2012-04-11, 2012-04-12, 2012-04-13, 2012-04-14, 2012-04-15, 2012-04-16, 2012-04-17, 2012-04-18, 2012-04-19, 2012-04-20], dtype=datetime64[ns], freq=D)In [77]: pd.date_range(end=2012-06-01, periods=20)Out[77]: DatetimeIndex([2012-05-13, 2012-05-14, 2012-05-15, 2012-05-16, 2012-05-17, 2012-05-18, 2012-05-19, 2012-05-20, 2012-05-21, 2012-05-22, 2012-05-23, 2012-05-24, 2012-05-25, 2012-05-26, 2012-05-27,2012-05-28, 2012-05-29, 2012-05-30, 2012-05-31, 2012-06-01], dtype=datetime64[ns], freq=D)
起始和結束日期定義了日期索引的嚴格邊界。例如,如果你想要生成一個由每月最後一個工作日組成的日期索引,可以傳入"BM"頻率(表示business end of month,表11-4是頻率列表),這樣就只會包含時間間隔內(或剛好在邊界上的)符合頻率要求的日期:
In [78]: pd.date_range(2000-01-01, 2000-12-01, freq=BM)Out[78]: DatetimeIndex([2000-01-31, 2000-02-29, 2000-03-31, 2000-04-28, 2000-05-31, 2000-06-30, 2000-07-31, 2000-08-31, 2000-09-29, 2000-10-31, 2000-11-30], dtype=datetime64[ns], freq=BM)
表11-4 基本的時間序列頻率(不完整)
date_range默認會保留起始和結束時間戳的時間信息(如果有的話):
In [79]: pd.date_range(2012-05-02 12:56:31, periods=5)Out[79]: DatetimeIndex([2012-05-02 12:56:31, 2012-05-03 12:56:31, 2012-05-04 12:56:31, 2012-05-05 12:56:31, 2012-05-06 12:56:31], dtype=datetime64[ns], freq=D)
有時,雖然起始和結束日期帶有時間信息,但你希望產生一組被規範化(normalize)到午夜的時間戳。normalize選項即可實現該功能:
In [80]: pd.date_range(2012-05-02 12:56:31, periods=5, normalize=True)Out[80]: DatetimeIndex([2012-05-02, 2012-05-03, 2012-05-04, 2012-05-05, 2012-05-06], dtype=datetime64[ns], freq=D)
頻率和日期偏移量
pandas中的頻率是由一個基礎頻率(base frequency)和一個乘數組成的。基礎頻率通常以一個字元串別名表示,比如"M"表示每月,"H"表示每小時。對於每個基礎頻率,都有一個被稱為日期偏移量(date offset)的對象與之對應。例如,按小時計算的頻率可以用Hour類表示:
In [81]: from pandas.tseries.offsets import Hour, MinuteIn [82]: hour = Hour()In [83]: hourOut[83]: <Hour>
傳入一個整數即可定義偏移量的倍數:
In [84]: four_hours = Hour(4)In [85]: four_hoursOut[85]: <4 * Hours>
一般來說,無需明確創建這樣的對象,只需使用諸如"H"或"4H"這樣的字元串別名即可。在基礎頻率前面放上一個整數即可創建倍數:
In [86]: pd.date_range(2000-01-01, 2000-01-03 23:59, freq=4h)Out[86]: DatetimeIndex([2000-01-01 00:00:00, 2000-01-01 04:00:00, 2000-01-01 08:00:00, 2000-01-01 12:00:00, 2000-01-01 16:00:00, 2000-01-01 20:00:00, 2000-01-02 00:00:00, 2000-01-02 04:00:00, 2000-01-02 08:00:00, 2000-01-02 12:00:00, 2000-01-02 16:00:00, 2000-01-02 20:00:00, 2000-01-03 00:00:00, 2000-01-03 04:00:00, 2000-01-03 08:00:00, 2000-01-03 12:00:00, 2000-01-03 16:00:00, 2000-01-03 20:00:00], dtype=datetime64[ns], freq=4H)
大部分偏移量對象都可通過加法進行連接:
In [87]: Hour(2) + Minute(30)Out[87]: <150 * Minutes>
同理,你也可以傳入頻率字元串(如"2h30min"),這種字元串可以被高效地解析為等效的表達式:
In [88]: pd.date_range(2000-01-01, periods=10, freq=1h30min)Out[88]: DatetimeIndex([2000-01-01 00:00:00, 2000-01-01 01:30:00, 2000-01-01 03:00:00, 2000-01-01 04:30:00, 2000-01-01 06:00:00, 2000-01-01 07:30:00, 2000-01-01 09:00:00, 2000-01-01 10:30:00, 2000-01-01 12:00:00, 2000-01-01 13:30:00], dtype=datetime64[ns], freq=90T)
有些頻率所描述的時間點並不是均勻分隔的。例如,"M"(日曆月末)和"BM"(每月最後一個工作日)就取決於每月的天數,對於後者,還要考慮月末是不是周末。由於沒有更好的術語,我將這些稱為錨點偏移量(anchored offset)。
表11-4列出了pandas中的頻率代碼和日期偏移量類。
筆記:用戶可以根據實際需求自定義一些頻率類以便提供pandas所沒有的日期邏輯,但具體的細節超出了本書的範圍。
表11-4 時間序列的基礎頻率
WOM日期
WOM(Week Of Month)是一種非常實用的頻率類,它以WOM開頭。它使你能獲得諸如「每月第3個星期五」之類的日期:
In [89]: rng = pd.date_range(2012-01-01, 2012-09-01, freq=WOM-3FRI)In [90]: list(rng)Out[90]: [Timestamp(2012-01-20 00:00:00, freq=WOM-3FRI), Timestamp(2012-02-17 00:00:00, freq=WOM-3FRI), Timestamp(2012-03-16 00:00:00, freq=WOM-3FRI), Timestamp(2012-04-20 00:00:00, freq=WOM-3FRI), Timestamp(2012-05-18 00:00:00, freq=WOM-3FRI), Timestamp(2012-06-15 00:00:00, freq=WOM-3FRI), Timestamp(2012-07-20 00:00:00, freq=WOM-3FRI), Timestamp(2012-08-17 00:00:00, freq=WOM-3FRI)]
移動(超前和滯後)數據
移動(shifting)指的是沿著時間軸將數據前移或後移。Series和DataFrame都有一個shift方法用於執行單純的前移或後移操作,保持索引不變:
In [91]: ts = pd.Series(np.random.randn(4), ....: index=pd.date_range(1/1/2000, periods=4, freq=M))In [92]: tsOut[92]: 2000-01-31 -0.0667482000-02-29 0.8386392000-03-31 -0.1173882000-04-30 -0.517795Freq: M, dtype: float64In [93]: ts.shift(2)Out[93]: 2000-01-31 NaN2000-02-29 NaN2000-03-31 -0.0667482000-04-30 0.838639Freq: M, dtype: float64In [94]: ts.shift(-2)Out[94]: 2000-01-31 -0.1173882000-02-29 -0.5177952000-03-31 NaN2000-04-30 NaNFreq: M, dtype: float64
當我們這樣進行移動時,就會在時間序列的前面或後面產生缺失數據。
shift通常用於計算一個時間序列或多個時間序列(如DataFrame的列)中的百分比變化。可以這樣表達:
ts / ts.shift(1) - 1
由於單純的移位操作不會修改索引,所以部分數據會被丟棄。因此,如果頻率已知,則可以將其傳給shift以便實現對時間戳進行位移而不是對數據進行簡單位移:
In [95]: ts.shift(2, freq=M)Out[95]: 2000-03-31 -0.0667482000-04-30 0.8386392000-05-31 -0.1173882000-06-30 -0.517795Freq: M, dtype: float64
這裡還可以使用其他頻率,於是你就能非常靈活地對數據進行超前和滯後處理了:
In [96]: ts.shift(3, freq=D)Out[96]: 2000-02-03 -0.0667482000-03-03 0.8386392000-04-03 -0.1173882000-05-03 -0.517795dtype: float64In [97]: ts.shift(1, freq=90T)Out[97]: 2000-01-31 01:30:00 -0.0667482000-02-29 01:30:00 0.8386392000-03-31 01:30:00 -0.1173882000-04-30 01:30:00 -0.517795Freq: M, dtype: float64
通過偏移量對日期進行位移
pandas的日期偏移量還可以用在datetime或Timestamp對象上:
In [98]: from pandas.tseries.offsets import Day, MonthEndIn [99]: now = datetime(2011, 11, 17)In [100]: now + 3 * Day()Out[100]: Timestamp(2011-11-20 00:00:00)
如果加的是錨點偏移量(比如MonthEnd),第一次增量會將原日期向前滾動到符合頻率規則的下一個日期:
In [101]: now + MonthEnd()Out[101]: Timestamp(2011-11-30 00:00:00)In [102]: now + MonthEnd(2)Out[102]: Timestamp(2011-12-31 00:00:00)
通過錨點偏移量的rollforward和rollback方法,可明確地將日期向前或向後「滾動」:
In [103]: offset = MonthEnd()In [104]: offset.rollforward(now)Out[104]: Timestamp(2011-11-30 00:00:00)In [105]: offset.rollback(now)Out[105]: Timestamp(2011-10-31 00:00:00)
日期偏移量還有一個巧妙的用法,即結合groupby使用這兩個「滾動」方法:
In [106]: ts = pd.Series(np.random.randn(20), .....: index=pd.date_range(1/15/2000, periods=20, freq=4d))In [107]: tsOut[107]: 2000-01-15 -0.1166962000-01-19 2.3896452000-01-23 -0.9324542000-01-27 -0.2293312000-01-31 -1.1403302000-02-04 0.4399202000-02-08 -0.8237582000-02-12 -0.5209302000-02-16 0.3502822000-02-20 0.2043952000-02-24 0.1334452000-02-28 0.3279052000-03-03 0.0721532000-03-07 0.1316782000-03-11 -1.2974592000-03-15 0.9977472000-03-19 0.8709552000-03-23 -0.9912532000-03-27 0.1516992000-03-31 1.266151Freq: 4D, dtype: float64In [108]: ts.groupby(offset.rollforward).mean()Out[108]: 2000-01-31 -0.0058332000-02-29 0.0158942000-03-31 0.150209dtype: float64
當然,更簡單、更快速地實現該功能的辦法是使用resample(11.6小節將對此進行詳細介紹):
In [109]: ts.resample(M).mean()Out[109]: 2000-01-31 -0.0058332000-02-29 0.0158942000-03-31 0.150209Freq: M, dtype: float64
推薦閱讀:
※黃小龍第三講:簡單的數據處理筆記
※2016年歐洲移民大數據分析!
※今日數據行業日報(2017.4.21)
※邏輯回歸學習
※神策數據為何屢被譽為「中國大數據分析的實力象徵」?