《利用Python進行數據分析·第2版》第11章(上) 時間序列

《利用Python進行數據分析·第2版》第11章(上) 時間序列

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

作者: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章(下) 數據聚合與分組運算

時間序列(time series)數據是一種重要的結構化數據形式,應用於多個領域,包括金融學、經濟學、生態學、神經科學、物理學等。在多個時間點觀察或測量到的任何事物都可以形成一段時間序列。很多時間序列是固定頻率的,也就是說,數據點是根據某種規律定期出現的(比如每15秒、每5分鐘、每月出現一次)。時間序列也可以是不定期的,沒有固定的時間單位或單位之間的偏移量。時間序列數據的意義取決於具體的應用場景,主要有以下幾種:

  • 時間戳(timestamp),特定的時刻。
  • 固定時期(period),如2007年1月或2010年全年。
  • 時間間隔(interval),由起始和結束時間戳表示。時期(period)可以被看做間隔(interval)的特例。
  • 實驗或過程時間,每個時間點都是相對於特定起始時間的一個度量。例如,從放入烤箱時起,每秒鐘餅乾的直徑。

本章主要講解前3種時間序列。許多技術都可用於處理實驗型時間序列,其索引可能是一個整數或浮點數(表示從實驗開始算起已經過去的時間)。最簡單也最常見的時間序列都是用時間戳進行索引的。

提示:pandas也支持基於timedeltas的指數,它可以有效代表實驗或經過的時間。這本書不涉及timedelta指數,但你可以學習pandas的文檔(pandas.pydata.org/)。

pandas提供了許多內置的時間序列處理工具和數據演算法。因此,你可以高效處理非常大的時間序列,輕鬆地進行切片/切塊、聚合、對定期/不定期的時間序列進行重採樣等。有些工具特別適合金融和經濟應用,你當然也可以用它們來分析伺服器日誌數據。

11.1 日期和時間數據類型及工具

Python標準庫包含用於日期(date)和時間(time)數據的數據類型,而且還有日曆方面的功能。我們主要會用到datetime、time以及calendar模塊。datetime.datetime(也可以簡寫為datetime)是用得最多的數據類型:

In [10]: from datetime import datetimeIn [11]: now = datetime.now()In [12]: nowOut[12]: datetime.datetime(2017, 9, 25, 14, 5, 52, 72973)In [13]: now.year, now.month, now.dayOut[13]: (2017, 9, 25)

datetime以毫秒形式存儲日期和時間。timedelta表示兩個datetime對象之間的時間差:

In [14]: delta = datetime(2011, 1, 7) - datetime(2008, 6, 24, 8, 15)In [15]: deltaOut[15]: datetime.timedelta(926, 56700)In [16]: delta.daysOut[16]: 926In [17]: delta.secondsOut[17]: 56700

可以給datetime對象加上(或減去)一個或多個timedelta,這樣會產生一個新對象:

In [18]: from datetime import timedeltaIn [19]: start = datetime(2011, 1, 7)In [20]: start + timedelta(12)Out[20]: datetime.datetime(2011, 1, 19, 0, 0)In [21]: start - 2 * timedelta(12)Out[21]: datetime.datetime(2010, 12, 14, 0, 0)

datetime模塊中的數據類型參見表10-1。雖然本章主要講的是pandas數據類型和高級時間序列處理,但你肯定會在Python的其他地方遇到有關datetime的數據類型。

表11-1 datetime模塊中的數據類型

tzinfo 存儲時區信息的基本類型

字元串和datetime的相互轉換

利用str或strftime方法(傳入一個格式化字元串),datetime對象和pandas的Timestamp對象(稍後就會介紹)可以被格式化為字元串:

In [22]: stamp = datetime(2011, 1, 3)In [23]: str(stamp)Out[23]: 2011-01-03 00:00:00In [24]: stamp.strftime(%Y-%m-%d)Out[24]: 2011-01-03

表11-2列出了全部的格式化編碼。

表11-2 datetime格式定義(兼容ISO C89)

datetime.strptime可以用這些格式化編碼將字元串轉換為日期:

In [25]: value = 2011-01-03In [26]: datetime.strptime(value, %Y-%m-%d)Out[26]: datetime.datetime(2011, 1, 3, 0, 0)In [27]: datestrs = [7/6/2011, 8/6/2011]In [28]: [datetime.strptime(x, %m/%d/%Y) for x in datestrs]Out[28]: [datetime.datetime(2011, 7, 6, 0, 0), datetime.datetime(2011, 8, 6, 0, 0)]

datetime.strptime是通過已知格式進行日期解析的最佳方式。但是每次都要編寫格式定義是很麻煩的事情,尤其是對於一些常見的日期格式。這種情況下,你可以用dateutil這個第三方包中的parser.parse方法(pandas中已經自動安裝好了):

In [29]: from dateutil.parser import parseIn [30]: parse(2011-01-03)Out[30]: datetime.datetime(2011, 1, 3, 0, 0)

dateutil可以解析幾乎所有人類能夠理解的日期表示形式:

In [31]: parse(Jan 31, 1997 10:45 PM)Out[31]: datetime.datetime(1997, 1, 31, 22, 45)

在國際通用的格式中,日出現在月的前面很普遍,傳入dayfirst=True即可解決這個問題:

In [32]: parse(6/12/2011, dayfirst=True)Out[32]: datetime.datetime(2011, 12, 6, 0, 0)

pandas通常是用於處理成組日期的,不管這些日期是DataFrame的軸索引還是列。to_datetime方法可以解析多種不同的日期表示形式。對標準日期格式(如ISO8601)的解析非常快:

In [33]: datestrs = [2011-07-06 12:00:00, 2011-08-06 00:00:00]In [34]: pd.to_datetime(datestrs)Out[34]: DatetimeIndex([2011-07-06 12:00:00, 2011-08-06 00:00:00], dtype=datetime64[ns], freq=None)

它還可以處理缺失值(None、空字元串等):

In [35]: idx = pd.to_datetime(datestrs + [None])In [36]: idxOut[36]: DatetimeIndex([2011-07-06 12:00:00, 2011-08-06 00:00:00, NaT], dtype=datetime64[ns], freq=None)In [37]: idx[2]Out[37]: NaTIn [38]: pd.isnull(idx)Out[38]: array([False, False, True], dtype=bool)

NaT(Not a Time)是pandas中時間戳數據的null值。

注意:dateutil.parser是一個實用但不完美的工具。比如說,它會把一些原本不是日期的字元串認作是日期(比如"42"會被解析為2042年的今天)。

datetime對象還有一些特定於當前環境(位於不同國家或使用不同語言的系統)的格式化選項。例如,德語或法語系統所用的月份簡寫就與英語系統所用的不同。表11-3進行了總結。

表11-3 特定於當前環境的日期格式

11.2 時間序列基礎

pandas最基本的時間序列類型就是以時間戳(通常以Python字元串或datatime對象表示)為索引的Series:

In [39]: from datetime import datetimeIn [40]: dates = [datetime(2011, 1, 2), datetime(2011, 1, 5), ....: datetime(2011, 1, 7), datetime(2011, 1, 8), ....: datetime(2011, 1, 10), datetime(2011, 1, 12)]In [41]: ts = pd.Series(np.random.randn(6), index=dates)In [42]: tsOut[42]: 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: float64

這些datetime對象實際上是被放在一個DatetimeIndex中的:

In [43]: ts.indexOut[43]: DatetimeIndex([2011-01-02, 2011-01-05, 2011-01-07, 2011-01-08, 2011-01-10, 2011-01-12], dtype=datetime64[ns], freq=None)

跟其他Series一樣,不同索引的時間序列之間的算術運算會自動按日期對齊:

In [44]: ts + ts[::2]Out[44]: 2011-01-02 -0.4094152011-01-05 NaN2011-01-07 -1.0388772011-01-08 NaN2011-01-10 3.9315612011-01-12 NaNdtype: float64

ts[::2] 是每隔兩個取一個。

pandas用NumPy的datetime64數據類型以納秒形式存儲時間戳:

In [45]: ts.index.dtypeOut[45]: dtype(<M8[ns])

DatetimeIndex中的各個標量值是pandas的Timestamp對象:

In [46]: stamp = ts.index[0]In [47]: stampOut[47]: Timestamp(2011-01-02 00:00:00)

只要有需要,TimeStamp可以隨時自動轉換為datetime對象。此外,它還可以存儲頻率信息(如果有的話),且知道如何執行時區轉換以及其他操作。稍後將對此進行詳細講解。

索引、選取、子集構造

當你根據標籤索引選取數據時,時間序列和其它的pandas.Series很像:

In [48]: stamp = ts.index[2]In [49]: ts[stamp]Out[49]: -0.51943871505673811

還有一種更為方便的用法:傳入一個可以被解釋為日期的字元串:

In [50]: ts[1/10/2011]Out[50]: 1.9657805725027142In [51]: ts[20110110]Out[51]: 1.9657805725027142

對於較長的時間序列,只需傳入「年」或「年月」即可輕鬆選取數據的切片:

In [52]: longer_ts = pd.Series(np.random.randn(1000), ....: index=pd.date_range(1/1/2000, periods=1000))In [53]: longer_tsOut[53]: 2000-01-01 0.0929082000-01-02 0.2817462000-01-03 0.7690232000-01-04 1.2464352000-01-05 1.0071892000-01-06 -1.2962212000-01-07 0.2749922000-01-08 0.2289132000-01-09 1.3529172000-01-10 0.886429 ... 2002-09-17 -0.1392982002-09-18 -1.1599262002-09-19 0.6189652002-09-20 1.3738902002-09-21 -0.9835052002-09-22 0.9309442002-09-23 -0.8116762002-09-24 -1.8301562002-09-25 -0.1387302002-09-26 0.334088Freq: D, Length: 1000, dtype: float64In [54]: longer_ts[2001]Out[54]: 2001-01-01 1.5995342001-01-02 0.4740712001-01-03 0.1513262001-01-04 -0.5421732001-01-05 -0.4754962001-01-06 0.1064032001-01-07 -1.3082282001-01-08 2.1731852001-01-09 0.5645612001-01-10 -0.190481 ... 2001-12-22 0.0003692001-12-23 0.9008852001-12-24 -0.4548692001-12-25 -0.8645472001-12-26 1.1291202001-12-27 0.0578742001-12-28 -0.4337392001-12-29 0.0926982001-12-30 -1.3978202001-12-31 1.457823Freq: D, Length: 365, dtype: float64

這裡,字元串「2001」被解釋成年,並根據它選取時間區間。指定月也同樣奏效:

In [55]: longer_ts[2001-05]Out[55]: 2001-05-01 -0.6225472001-05-02 0.9362892001-05-03 0.7500182001-05-04 -0.0567152001-05-05 2.3006752001-05-06 0.5694972001-05-07 1.4894102001-05-08 1.2642502001-05-09 -0.7618372001-05-10 -0.331617 ... 2001-05-22 0.5036992001-05-23 -1.3878742001-05-24 0.2048512001-05-25 0.6037052001-05-26 0.5456802001-05-27 0.2354772001-05-28 0.1118352001-05-29 -1.2515042001-05-30 -2.9493432001-05-31 0.634634Freq: D, Length: 31, dtype: float64

datetime對象也可以進行切片:

In [56]: ts[datetime(2011, 1, 7):]Out[56]: 2011-01-07 -0.5194392011-01-08 -0.5557302011-01-10 1.9657812011-01-12 1.393406dtype: float64

由於大部分時間序列數據都是按照時間先後排序的,因此你也可以用不存在於該時間序列中的時間戳對其進行切片(即範圍查詢):

In [57]: tsOut[57]: 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 [58]: ts[1/6/2011:1/11/2011]Out[58]: 2011-01-07 -0.5194392011-01-08 -0.5557302011-01-10 1.965781dtype: float64

跟之前一樣,你可以傳入字元串日期、datetime或Timestamp。注意,這樣切片所產生的是源時間序列的視圖,跟NumPy數組的切片運算是一樣的。

這意味著,沒有數據被複制,對切片進行修改會反映到原始數據上。

此外,還有一個等價的實例方法也可以截取兩個日期之間TimeSeries:

In [59]: ts.truncate(after=1/9/2011)Out[59]: 2011-01-02 -0.2047082011-01-05 0.4789432011-01-07 -0.5194392011-01-08 -0.555730dtype: float64

這些操作對DataFrame也有效。例如,對DataFrame的行進行索引:

In [60]: dates = pd.date_range(1/1/2000, periods=100, freq=W-WED)In [61]: long_df = pd.DataFrame(np.random.randn(100, 4), ....: index=dates, ....: columns=[Colorado, Texas, ....: New York, Ohio])In [62]: long_df.loc[5-2001]Out[62]: Colorado Texas New York Ohio2001-05-02 -0.006045 0.490094 -0.277186 -0.7072132001-05-09 -0.560107 2.735527 0.927335 1.5139062001-05-16 0.538600 1.273768 0.667876 -0.9692062001-05-23 1.676091 -0.817649 0.050188 1.9513122001-05-30 3.260383 0.963301 1.201206 -1.852001

帶有重複索引的時間序列

在某些應用場景中,可能會存在多個觀測數據落在同一個時間點上的情況。下面就是一個例子:

In [63]: dates = pd.DatetimeIndex([1/1/2000, 1/2/2000, 1/2/2000, ....: 1/2/2000, 1/3/2000])In [64]: dup_ts = pd.Series(np.arange(5), index=dates)In [65]: dup_tsOut[65]: 2000-01-01 02000-01-02 12000-01-02 22000-01-02 32000-01-03 4dtype: int64

通過檢查索引的is_unique屬性,我們就可以知道它是不是唯一的:

In [66]: dup_ts.index.is_uniqueOut[66]: False

對這個時間序列進行索引,要麼產生標量值,要麼產生切片,具體要看所選的時間點是否重複:

In [67]: dup_ts[1/3/2000] # not duplicatedOut[67]: 4In [68]: dup_ts[1/2/2000] # duplicatedOut[68]: 2000-01-02 12000-01-02 22000-01-02 3dtype: int64

假設你想要對具有非唯一時間戳的數據進行聚合。一個辦法是使用groupby,並傳入level=0:

In [69]: grouped = dup_ts.groupby(level=0)In [70]: grouped.mean()Out[70]: 2000-01-01 02000-01-02 22000-01-03 4dtype: int64In [71]: grouped.count()Out[71]: 2000-01-01 12000-01-02 32000-01-03 1dtype: int64

推薦閱讀:

爬取股票歷史數據並繪製K線圖
python對字典類型按值進行排序
提升Python程序性能的7個習慣
vim-python 更快的換行
有輕功:用3行代碼讓Python數據處理腳本獲得4倍提速

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