【翻譯】《利用Python進行數據分析·第2版》第11章(中二) 時間序列

【翻譯】《利用Python進行數據分析·第2版》第11章(中二) 時間序列

來自專欄 Python程序員11 人贊了文章

作者:SeanCheney Python愛好者社區專欄作者

簡書專欄:jianshu.com/u/130f76596

前文傳送門:

【翻譯】《利用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章(上) 時間序列

【翻譯】《利用Python進行數據分析·第2版》第11章(中) 時間序列

11.4 時區處理

時間序列處理工作中最讓人不爽的就是對時區的處理。許多人都選擇以協調世界時(UTC,它是格林尼治標準時間(Greenwich Mean Time)的接替者,目前已經是國際標準了)來處理時間序列。時區是以UTC偏移量的形式表示的。例如,夏令時期間,紐約比UTC慢4小時,而在全年其他時間則比UTC慢5小時。

在Python中,時區信息來自第三方庫pytz,它使Python可以使用Olson資料庫(彙編了世界時區信息)。這對歷史數據非常重要,這是因為由於各地政府的各種突發奇想,夏令時轉變日期(甚至UTC偏移量)已經發生過多次改變了。就拿美國來說,DST轉變時間自1900年以來就改變過多次!

有關pytz庫的更多信息,請查閱其文檔。就本書而言,由於pandas包裝了pytz的功能,因此你可以不用記憶其API,只要記得時區的名稱即可。時區名可以在shell中看到,也可以通過文檔查看:

In [110]: import pytzIn [111]: pytz.common_timezones[-5:]Out[111]: [US/Eastern, US/Hawaii, US/Mountain, US/Pacific, UTC]

要從pytz中獲取時區對象,使用pytz.timezone即可:

In [112]: tz = pytz.timezone(America/New_York)In [113]: tzOut[113]: <DstTzInfo America/New_York LMT-1 day, 19:04:00 STD>

pandas中的方法既可以接受時區名也可以接受這些對象。

時區本地化和轉換

默認情況下,pandas中的時間序列是單純的(naive)時區。看看下面這個時間序列:

In [114]: rng = pd.date_range(3/9/2012 9:30, periods=6, freq=D)In [115]: ts = pd.Series(np.random.randn(len(rng)), index=rng)In [116]: tsOut[116]: 2012-03-09 09:30:00 -0.2024692012-03-10 09:30:00 0.0507182012-03-11 09:30:00 0.6398692012-03-12 09:30:00 0.5975942012-03-13 09:30:00 -0.7972462012-03-14 09:30:00 0.472879Freq: D, dtype: float64

其索引的tz欄位為None:

In [117]: print(ts.index.tz)None

可以用時區集生成日期範圍:

In [118]: pd.date_range(3/9/2012 9:30, periods=10, freq=D, tz=UTC)Out[118]: DatetimeIndex([2012-03-09 09:30:00+00:00, 2012-03-10 09:30:00+00:00, 2012-03-11 09:30:00+00:00, 2012-03-12 09:30:00+00:00, 2012-03-13 09:30:00+00:00, 2012-03-14 09:30:00+00:00, 2012-03-15 09:30:00+00:00, 2012-03-16 09:30:00+00:00, 2012-03-17 09:30:00+00:00, 2012-03-18 09:30:00+00:00], dtype=datetime64[ns, UTC], freq=D)

從單純到本地化的轉換是通過tz_localize方法處理的:

In [119]: tsOut[119]: 2012-03-09 09:30:00 -0.2024692012-03-10 09:30:00 0.0507182012-03-11 09:30:00 0.6398692012-03-12 09:30:00 0.5975942012-03-13 09:30:00 -0.7972462012-03-14 09:30:00 0.472879Freq: D, dtype: float64In [120]: ts_utc = ts.tz_localize(UTC)In [121]: ts_utcOut[121]: 2012-03-09 09:30:00+00:00 -0.2024692012-03-10 09:30:00+00:00 0.0507182012-03-11 09:30:00+00:00 0.6398692012-03-12 09:30:00+00:00 0.5975942012-03-13 09:30:00+00:00 -0.7972462012-03-14 09:30:00+00:00 0.472879Freq: D, dtype: float64In [122]: ts_utc.indexOut[122]: DatetimeIndex([2012-03-09 09:30:00+00:00, 2012-03-10 09:30:00+00:00, 2012-03-11 09:30:00+00:00, 2012-03-12 09:30:00+00:00, 2012-03-13 09:30:00+00:00, 2012-03-14 09:30:00+00:00], dtype=datetime64[ns, UTC], freq=D)

一旦時間序列被本地化到某個特定時區,就可以用tz_convert將其轉換到別的時區了:

In [123]: ts_utc.tz_convert(America/New_York)Out[123]: 2012-03-09 04:30:00-05:00 -0.2024692012-03-10 04:30:00-05:00 0.0507182012-03-11 05:30:00-04:00 0.6398692012-03-12 05:30:00-04:00 0.5975942012-03-13 05:30:00-04:00 -0.7972462012-03-14 05:30:00-04:00 0.472879Freq: D, dtype: float64

對於上面這種時間序列(它跨越了美國東部時區的夏令時轉變期),我們可以將其本地化到EST,然後轉換為UTC或柏林時間:

In [124]: ts_eastern = ts.tz_localize(America/New_York)In [125]: ts_eastern.tz_convert(UTC)Out[125]: 2012-03-09 14:30:00+00:00 -0.2024692012-03-10 14:30:00+00:00 0.0507182012-03-11 13:30:00+00:00 0.6398692012-03-12 13:30:00+00:00 0.5975942012-03-13 13:30:00+00:00 -0.7972462012-03-14 13:30:00+00:00 0.472879Freq: D, dtype: float64In [126]: ts_eastern.tz_convert(Europe/Berlin)Out[126]: 2012-03-09 15:30:00+01:00 -0.2024692012-03-10 15:30:00+01:00 0.0507182012-03-11 14:30:00+01:00 0.6398692012-03-12 14:30:00+01:00 0.5975942012-03-13 14:30:00+01:00 -0.7972462012-03-14 14:30:00+01:00 0.472879Freq: D, dtype: float64

tz_localize和tz_convert也是DatetimeIndex的實例方法:

In [127]: ts.index.tz_localize(Asia/Shanghai)Out[127]: DatetimeIndex([2012-03-09 09:30:00+08:00, 2012-03-10 09:30:00+08:00, 2012-03-11 09:30:00+08:00, 2012-03-12 09:30:00+08:00, 2012-03-13 09:30:00+08:00, 2012-03-14 09:30:00+08:00], dtype=datetime64[ns, Asia/Shanghai], freq=D)

注意:對單純時間戳的本地化操作還會檢查夏令時轉變期附近容易混淆或不存在的時間。

操作時區意識型Timestamp對象

跟時間序列和日期範圍差不多,獨立的Timestamp對象也能被從單純型(naive)本地化為時區意識型(time zone-aware),並從一個時區轉換到另一個時區:

In [128]: stamp = pd.Timestamp(2011-03-12 04:00)In [129]: stamp_utc = stamp.tz_localize(utc)In [130]: stamp_utc.tz_convert(America/New_York)Out[130]: Timestamp(2011-03-11 23:00:00-0500, tz=America/New_York)

在創建Timestamp時,還可以傳入一個時區信息:

In [131]: stamp_moscow = pd.Timestamp(2011-03-12 04:00, tz=Europe/Moscow)In [132]: stamp_moscowOut[132]: Timestamp(2011-03-12 04:00:00+0300, tz=Europe/Moscow)

時區意識型Timestamp對象在內部保存了一個UTC時間戳值(自UNIX紀元(1970年1月1日)算起的納秒數)。這個UTC值在時區轉換過程中是不會發生變化的:

In [133]: stamp_utc.valueOut[133]: 1299902400000000000In [134]: stamp_utc.tz_convert(America/New_York).valueOut[134]: 1299902400000000000

當使用pandas的DateOffset對象執行時間算術運算時,運算過程會自動關注是否存在夏令時轉變期。這裡,我們創建了在DST轉變之前的時間戳。首先,來看夏令時轉變前的30分鐘:

In [135]: from pandas.tseries.offsets import HourIn [136]: stamp = pd.Timestamp(2012-03-12 01:30, tz=US/Eastern)In [137]: stampOut[137]: Timestamp(2012-03-12 01:30:00-0400, tz=US/Eastern)In [138]: stamp + Hour()Out[138]: Timestamp(2012-03-12 02:30:00-0400, tz=US/Eastern)

然後,夏令時轉變前90分鐘:

In [139]: stamp = pd.Timestamp(2012-11-04 00:30, tz=US/Eastern)In [140]: stampOut[140]: Timestamp(2012-11-04 00:30:00-0400, tz=US/Eastern)In [141]: stamp + 2 * Hour()Out[141]: Timestamp(2012-11-04 01:30:00-0500, tz=US/Eastern)

不同時區之間的運算

如果兩個時間序列的時區不同,在將它們合併到一起時,最終結果就會是UTC。由於時間戳其實是以UTC存儲的,所以這是一個很簡單的運算,並不需要發生任何轉換:

In [142]: rng = pd.date_range(3/7/2012 9:30, periods=10, freq=B)In [143]: ts = pd.Series(np.random.randn(len(rng)), index=rng)In [144]: tsOut[144]: 2012-03-07 09:30:00 0.5223562012-03-08 09:30:00 -0.5463482012-03-09 09:30:00 -0.7335372012-03-12 09:30:00 1.3027362012-03-13 09:30:00 0.0221992012-03-14 09:30:00 0.3642872012-03-15 09:30:00 -0.9228392012-03-16 09:30:00 0.3126562012-03-19 09:30:00 -1.1284972012-03-20 09:30:00 -0.333488Freq: B, dtype: float64In [145]: ts1 = ts[:7].tz_localize(Europe/London)In [146]: ts2 = ts1[2:].tz_convert(Europe/Moscow)In [147]: result = ts1 + ts2In [148]: result.indexOut[148]: DatetimeIndex([2012-03-07 09:30:00+00:00, 2012-03-08 09:30:00+00:00, 2012-03-09 09:30:00+00:00, 2012-03-12 09:30:00+00:00, 2012-03-13 09:30:00+00:00, 2012-03-14 09:30:00+00:00, 2012-03-15 09:30:00+00:00], dtype=datetime64[ns, UTC], freq=B)

11.5 時期及其算術運算

時期(period)表示的是時間區間,比如數日、數月、數季、數年等。Period類所表示的就是這種數據類型,其構造函數需要用到一個字元串或整數,以及表11-4中的頻率:

In [149]: p = pd.Period(2007, freq=A-DEC)In [150]: pOut[150]: Period(2007, A-DEC)

這裡,這個Period對象表示的是從2007年1月1日到2007年12月31日之間的整段時間。只需對Period對象加上或減去一個整數即可達到根據其頻率進行位移的效果:

In [151]: p + 5Out[151]: Period(2012, A-DEC)In [152]: p - 2Out[152]: Period(2005, A-DEC)

如果兩個Period對象擁有相同的頻率,則它們的差就是它們之間的單位數量:

In [153]: pd.Period(2014, freq=A-DEC) - pOut[153]: 7

period_range函數可用於創建規則的時期範圍:

In [154]: rng = pd.period_range(2000-01-01, 2000-06-30, freq=M)In [155]: rngOut[155]: PeriodIndex([2000-01, 2000-02, 2000-03, 2000-04, 2000-05, 2000-06], dtype=period[M], freq=M)

PeriodIndex類保存了一組Period,它可以在任何pandas數據結構中被用作軸索引:

In [156]: pd.Series(np.random.randn(6), index=rng)Out[156]: 2000-01 -0.5145512000-02 -0.5597822000-03 -0.7834082000-04 -1.7976852000-05 -0.1726702000-06 0.680215Freq: M, dtype: float64

如果你有一個字元串數組,你也可以使用PeriodIndex類:

In [157]: values = [2001Q3, 2002Q2, 2003Q1]In [158]: index = pd.PeriodIndex(values, freq=Q-DEC)In [159]: indexOut[159]: PeriodIndex([2001Q3, 2002Q2, 2003Q1], dtype=period[Q-DEC], freq=Q-DEC)

時期的頻率轉換

Period和PeriodIndex對象都可以通過其asfreq方法被轉換成別的頻率。假設我們有一個年度時期,希望將其轉換為當年年初或年末的一個月度時期。該任務非常簡單:

In [160]: p = pd.Period(2007, freq=A-DEC)In [161]: pOut[161]: Period(2007, A-DEC)In [162]: p.asfreq(M, how=start)Out[162]: Period(2007-01, M)In [163]: p.asfreq(M, how=end)Out[163]: Period(2007-12, M)

你可以將Period(2007,A-DEC)看做一個被劃分為多個月度時期的時間段中的游標。圖11-1對此進行了說明。對於一個不以12月結束的財政年度,月度子時期的歸屬情況就不一樣了:

In [164]: p = pd.Period(2007, freq=A-JUN)In [165]: pOut[165]: Period(2007, A-JUN)In [166]: p.asfreq(M, start)Out[166]: Period(2006-07, M)In [167]: p.asfreq(M, end)Out[167]: Period(2007-06, M)

圖11-1 Period頻率轉換示例

在將高頻率轉換為低頻率時,超時期(superperiod)是由子時期(subperiod)所屬的位置決定的。例如,在A-JUN頻率中,月份「2007年8月」實際上是屬於周期「2008年」的:

In [168]: p = pd.Period(Aug-2007, M)In [169]: p.asfreq(A-JUN)Out[169]: Period(2008, A-JUN)

完整的PeriodIndex或TimeSeries的頻率轉換方式也是如此:

In [170]: rng = pd.period_range(2006, 2009, freq=A-DEC)In [171]: ts = pd.Series(np.random.randn(len(rng)), index=rng)In [172]: tsOut[172]: 2006 1.6075782007 0.2003812008 -0.8340682009 -0.302988Freq: A-DEC, dtype: float64In [173]: ts.asfreq(M, how=start)Out[173]: 2006-01 1.6075782007-01 0.2003812008-01 -0.8340682009-01 -0.302988Freq: M, dtype: float64

這裡,根據年度時期的第一個月,每年的時期被取代為每月的時期。如果我們想要每年的最後一個工作日,我們可以使用「B」頻率,並指明想要該時期的末尾:

In [174]: ts.asfreq(B, how=end)Out[174]: 2006-12-29 1.6075782007-12-31 0.2003812008-12-31 -0.8340682009-12-31 -0.302988Freq: B, dtype: float64

按季度計算的時期頻率

季度型數據在會計、金融等領域中很常見。許多季度型數據都會涉及「財年末」的概念,通常是一年12個月中某月的最後一個日曆日或工作日。就這一點來說,時期"2012Q4"根據財年末的不同會有不同的含義。pandas支持12種可能的季度型頻率,即Q-JAN到Q-DEC:

In [175]: p = pd.Period(2012Q4, freq=Q-JAN)In [176]: pOut[176]: Period(2012Q4, Q-JAN)

在以1月結束的財年中,2012Q4是從11月到1月(將其轉換為日型頻率就明白了)。圖11-2對此進行了說明:

In [177]: p.asfreq(D, start)Out[177]: Period(2011-11-01, D)In [178]: p.asfreq(D, end)Out[178]: Period(2012-01-31, D)

圖11.2 不同季度型頻率之間的轉換

因此,Period之間的算術運算會非常簡單。例如,要獲取該季度倒數第二個工作日下午4點的時間戳,你可以這樣:

In [179]: p4pm = (p.asfreq(B, e) - 1).asfreq(T, s) + 16 * 60In [180]: p4pmOut[180]: Period(2012-01-30 16:00, T)In [181]: p4pm.to_timestamp()Out[181]: Timestamp(2012-01-30 16:00:00)

period_range可用於生成季度型範圍。季度型範圍的算術運算也跟上面是一樣的:

In [182]: rng = pd.period_range(2011Q3, 2012Q4, freq=Q-JAN)In [183]: ts = pd.Series(np.arange(len(rng)), index=rng)In [184]: tsOut[184]: 2011Q3 02011Q4 12012Q1 22012Q2 32012Q3 42012Q4 5Freq: Q-JAN, dtype: int64In [185]: new_rng = (rng.asfreq(B, e) - 1).asfreq(T, s) + 16 * 60In [186]: ts.index = new_rng.to_timestamp()In [187]: tsOut[187]:2010-10-28 16:00:00 02011-01-28 16:00:00 12011-04-28 16:00:00 22011-07-28 16:00:00 32011-10-28 16:00:00 42012-01-30 16:00:00 5dtype: int64

將Timestamp轉換為Period(及其反向過程)

通過使用to_period方法,可以將由時間戳索引的Series和DataFrame對象轉換為以時期索引:

In [188]: rng = pd.date_range(2000-01-01, periods=3, freq=M)In [189]: ts = pd.Series(np.random.randn(3), index=rng)In [190]: tsOut[190]: 2000-01-31 1.6632612000-02-29 -0.9962062000-03-31 1.521760Freq: M, dtype: float64In [191]: pts = ts.to_period()In [192]: ptsOut[192]: 2000-01 1.6632612000-02 -0.9962062000-03 1.521760Freq: M, dtype: float64

由於時期指的是非重疊時間區間,因此對於給定的頻率,一個時間戳只能屬於一個時期。新PeriodIndex的頻率默認是從時間戳推斷而來的,你也可以指定任何別的頻率。結果中允許存在重複時期:

In [193]: rng = pd.date_range(1/29/2000, periods=6, freq=D)In [194]: ts2 = pd.Series(np.random.randn(6), index=rng)In [195]: ts2Out[195]: 2000-01-29 0.2441752000-01-30 0.4233312000-01-31 -0.6540402000-02-01 2.0891542000-02-02 -0.0602202000-02-03 -0.167933Freq: D, dtype: float64In [196]: ts2.to_period(M)Out[196]: 2000-01 0.2441752000-01 0.4233312000-01 -0.6540402000-02 2.0891542000-02 -0.0602202000-02 -0.167933Freq: M, dtype: float64

要轉換回時間戳,使用to_timestamp即可:

In [197]: pts = ts2.to_period()In [198]: ptsOut[198]: 2000-01-29 0.2441752000-01-30 0.4233312000-01-31 -0.6540402000-02-01 2.0891542000-02-02 -0.0602202000-02-03 -0.167933Freq: D, dtype: float64In [199]: pts.to_timestamp(how=end)Out[199]: 2000-01-29 0.2441752000-01-30 0.4233312000-01-31 -0.6540402000-02-01 2.0891542000-02-02 -0.0602202000-02-03 -0.167933Freq: D, dtype: float64

通過數組創建PeriodIndex

固定頻率的數據集通常會將時間信息分開存放在多個列中。例如,在下面這個宏觀經濟數據集中,年度和季度就分別存放在不同的列中:

In [200]: data = pd.read_csv(examples/macrodata.csv)In [201]: data.head(5)Out[201]: year quarter realgdp realcons realinv realgovt realdpi cpi 1959.0 1.0 2710.349 1707.4 286.898 470.045 1886.9 28.98 1 1959.0 2.0 2778.801 1733.7 310.859 481.301 1919.7 29.15 2 1959.0 3.0 2775.488 1751.8 289.226 491.260 1916.4 29.35 3 1959.0 4.0 2785.204 1753.7 299.356 484.052 1931.3 29.37 4 1960.0 1.0 2847.699 1770.5 331.722 462.199 1955.5 29.54 m1 tbilrate unemp pop infl realint 0 139.7 2.82 5.8 177.146 0.00 0.00 1 141.7 3.08 5.1 177.830 2.34 0.74 2 140.5 3.82 5.3 178.657 2.74 1.09 3 140.0 4.33 5.6 179.386 0.27 4.06 4 139.6 3.50 5.2 180.007 2.31 1.19 In [202]: data.yearOut[202]: 0 1959.01 1959.02 1959.03 1959.04 1960.05 1960.06 1960.07 1960.08 1961.09 1961.0 ... 193 2007.0194 2007.0195 2007.0196 2008.0197 2008.0198 2008.0199 2008.0200 2009.0201 2009.0202 2009.0Name: year, Length: 203, dtype: float64In [203]: data.quarterOut[203]: 0 1.01 2.02 3.03 4.04 1.05 2.06 3.07 4.08 1.09 2.0 ... 193 2.0194 3.0195 4.0196 1.0197 2.0198 3.0199 4.0200 1.0201 2.0202 3.0Name: quarter, Length: 203, dtype: float64

通過通過將這些數組以及一個頻率傳入PeriodIndex,就可以將它們合併成DataFrame的一個索引:

In [204]: index = pd.PeriodIndex(year=data.year, quarter=data.quarter, .....: freq=Q-DEC)In [205]: indexOut[205]: PeriodIndex([1959Q1, 1959Q2, 1959Q3, 1959Q4, 1960Q1, 1960Q2, 1960Q3, 1960Q4, 1961Q1, 1961Q2, ... 2007Q2, 2007Q3, 2007Q4, 2008Q1, 2008Q2, 2008Q3, 2008Q4, 2009Q1, 2009Q2, 2009Q3], dtype=period[Q-DEC], length=203, freq=Q-DEC)In [206]: data.index = indexIn [207]: data.inflOut[207]: 1959Q1 0.001959Q2 2.341959Q3 2.741959Q4 0.271960Q1 2.311960Q2 0.141960Q3 2.701960Q4 1.211961Q1 -0.401961Q2 1.47 ... 2007Q2 2.752007Q3 3.452007Q4 6.382008Q1 2.822008Q2 8.532008Q3 -3.162008Q4 -8.792009Q1 0.942009Q2 3.372009Q3 3.56Freq: Q-DEC, Name: infl, Length: 203, dtype: float64

推薦閱讀:

機器學習和數據科學領域必讀的10本免費書籍
數據挖掘哪家強?Excel回歸分析篇
如何對抗「大數據殺熟」?
川普贏了,但美國的數據同行們卻輸了
演算法工程師的能力素質模型

TAG:數據分析 | 數據挖掘 | Python |