1. 程式人生 > >資料基礎---《利用Python進行資料分析·第2版》第11章 時間序列

資料基礎---《利用Python進行資料分析·第2版》第11章 時間序列

之前自己對於numpy和pandas是要用的時候東學一點西一點,直到看到《利用Python進行資料分析·第2版》,覺得只看這一篇就夠了。非常感謝原博主的翻譯和分享。

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

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

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

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

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

11.1 日期和時間資料型別及工具

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

from datetime import datetime
now =
datetime.now() now
datetime.datetime(2018, 10, 16, 17, 39, 7, 590407)
now.year,now.month,now.day
(2018, 10, 16)

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

datetime(2011, 1, 7) 
datetime.datetime(2011, 1, 7, 0, 0)
delta = datetime(2011,1,7)-datetime(2008,6,24,8,15)
delta
datetime.timedelta(926, 56700)

表示926天又56700秒(大約15.75個小時)

delta.days
926
delta.seconds
56700

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

from datetime import timedelta
start = datetime(2011, 1, 7)
start + timedelta(12)#預設時間間隔的單位是天,也可以自己進行設定
datetime.datetime(2011, 1, 19, 0, 0)
start - 2 * timedelta(12)
datetime.datetime(2010, 12, 14, 0, 0)

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

表11-1 datetime模組中的資料型別
型別 說明
date 以公曆形式儲存日曆日期(年、月、日)
time 將時間儲存為時、分、秒、毫秒
datetime 儲存日期和時間
timedelta 表示兩個 datetime值之間的差(日、秒、毫秒)
tzinfo 儲存時區資訊的基本型別

字串和datetime的相互轉換

stamp = datetime(2011, 1, 3)
str(stamp)
'2011-01-03 00:00:00'
stamp.strftime('%Y-%m-%d')#我一般固定使用這種方法轉換,就不會跟更種其他用法產生混亂
'2011-01-03'

datetime格式定義可參考其他資料

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

value = '2011-01-03'
datetime.strptime(value,'%Y-%m-%d')
datetime.datetime(2011, 1, 3, 0, 0)
datestrs = ['7/6/2011', '8/6/2011']
[datetime.strptime(x,'%m/%d/%Y') for x in datestrs]
[datetime.datetime(2011, 7, 6, 0, 0), datetime.datetime(2011, 8, 6, 0, 0)]

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

from dateutil.parser import parse
parse('2011-01-03')
datetime.datetime(2011, 1, 3, 0, 0)

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

parse('Jan 31, 1997 10:45 PM')
datetime.datetime(1997, 1, 31, 22, 45)

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

parse('6/12/2011',dayfirst=True)
datetime.datetime(2011, 12, 6, 0, 0)
parse('6/12/2011')
datetime.datetime(2011, 6, 12, 0, 0)

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

import pandas as pd
datestrs = ['2011-07-06 12:00:00', '2011-08-06 00:00:00']
pd.to_datetime(datestrs)
DatetimeIndex(['2011-07-06 12:00:00', '2011-08-06 00:00:00'], dtype='datetime64[ns]', freq=None)

自動處理完後變成了DatetimeIndex物件

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

idx=pd.to_datetime(datestrs + [None])
idx
DatetimeIndex(['2011-07-06 12:00:00', '2011-08-06 00:00:00', 'NaT'], dtype='datetime64[ns]', freq=None)
pd.isnull(idx)
array([False, False,  True])

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

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

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

表11-3 特定於當前環境的日期格式
程式碼 說明
%a 星期幾的簡寫
%A 星期幾的全稱
%b 月份的簡寫
%B 月份的全稱
%c 完整的日期和時間,例如“Tue 01 May 2012 04:20:57PM
%p 不同環境中的AM或PM
%x 適合於當前環境的日期格式,例如,在美國,“May1,2012”會產生05/01/2012
%X 適合於當前環境的時間格式,例如“04:24:12 PM”

11.2 時間序列基礎

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

from datetime import datetime
import numpy as np
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)]
ts=pd.Series(np.random.randn(6),index=dates)
ts
2011-01-02    1.367002
2011-01-05   -1.075364
2011-01-07    1.177715
2011-01-08    0.235663
2011-01-10   -0.404561
2011-01-12   -0.279518
dtype: float64

可以看到直接將datetime物件賦給了index,但其實這些datetime物件實際上是被放在一個DatetimeIndex中的,從結果看DatetimeIndex有對datetime物件作進一步的規整:

ts.index
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一樣,不同索引的時間序列之間的算術運算會自動按日期對齊:

ts+ts[::2]#ts[::2] 是每隔兩個取一個。
2011-01-02    2.734004
2011-01-05         NaN
2011-01-07    2.355430
2011-01-08         NaN
2011-01-10   -0.809121
2011-01-12         NaN
dtype: float64

pandas用NumPy的datetime64資料型別以納秒形式儲存時間戳:

ts.index.dtype
dtype('<M8[ns]')

DatetimeIndex中的各個標量值是pandas的Timestamp物件:

stamp=ts.index[0]
stamp
Timestamp('2011-01-02 00:00:00')

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

索引、選取、子集構造

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

stamp=ts.index[2]
ts[stamp]
1.1777148947786458

還有一種更為方便的用法:傳入一個可以被解釋為日期的字串:其實道理是一樣的,如上面那樣,我們可以用時間戳來索引資料,我們可以看成這裡日期的字串先轉成了時間戳索引

ts['1/10/2011']
-0.4045606741689914
ts['20110110']
-0.4045606741689914

對於較長的時間序列,只需傳入“年”或“年月”即可輕鬆選取資料的切片:

longer_ts=pd.Series(np.random.randn(1000),index=pd.date_range('1/1/2000',periods=1000))
longer_ts
2000-01-01   -0.695152
2000-01-02    0.490721
2000-01-03    0.057702
2000-01-04   -0.144661
2000-01-05    0.854743
2000-01-06   -0.620803
2000-01-07   -0.544654
2000-01-08   -0.975185
2000-01-09    1.932819
2000-01-10    0.939155
2000-01-11   -0.467155
2000-01-12    0.406459
2000-01-13    0.442194
2000-01-14    0.190556
2000-01-15    0.426418
2000-01-16    0.967863
2000-01-17    0.675566
2000-01-18    0.106862
2000-01-19   -1.975229
2000-01-20    1.097981
2000-01-21    1.801745
2000-01-22    2.113984
2000-01-23    0.958273
2000-01-24   -1.089947
2000-01-25   -0.693382
2000-01-26   -1.230901
2000-01-27   -1.178079
2000-01-28   -0.261554
2000-01-29    0.385875
2000-01-30   -1.129497
                ...   
2002-08-28    0.123033
2002-08-29   -1.364800
2002-08-30    0.108372
2002-08-31    0.105880
2002-09-01   -0.184650
2002-09-02   -1.394003
2002-09-03    0.099957
2002-09-04    0.595101
2002-09-05    1.687308
2002-09-06    1.718821
2002-09-07    1.313495
2002-09-08    0.747596
2002-09-09    2.477120
2002-09-10   -1.145726
2002-09-11   -1.157853
2002-09-12   -2.349212
2002-09-13    1.927618
2002-09-14   -1.527638
2002-09-15    0.494005
2002-09-16   -0.708996
2002-09-17   -1.187589
2002-09-18   -1.226050
2002-09-19    1.596807
2002-09-20   -2.539985
2002-09-21    0.471234
2002-09-22   -2.177011
2002-09-23   -0.448872
2002-09-24   -0.527784
2002-09-25    1.006599
2002-09-26   -1.892320
Freq: D, Length: 1000, dtype: float64
longer_ts['2001']
2001-01-01   -0.309328
2001-01-02   -0.612142
2001-01-03   -0.118255
2001-01-04    1.110361
2001-01-05    0.375092
2001-01-06    1.097576
2001-01-07    0.633201
2001-01-08    0.560926
2001-01-09    0.089869
2001-01-10    1.774513
2001-01-11    0.451253
2001-01-12    0.750574
2001-01-13   -0.227574
2001-01-14   -0.121207
2001-01-15    1.168656
2001-01-16    0.076074
2001-01-17   -1.484041
2001-01-18    0.715347
2001-01-19   -0.499619
2001-01-20    1.209174
2001-01-21   -1.257921
2001-01-22    0.498747
2001-01-23    0.487024
2001-01-24    0.451236
2001-01-25   -0.464199
2001-01-26    0.969991
2001-01-27    1.385279
2001-01-28    1.190629
2001-01-29   -0.666683
2001-01-30   -1.328294
                ...   
2001-12-02   -1.077133
2001-12-03   -1.287080
2001-12-04    2.895630
2001-12-05   -1.306801
2001-12-06    1.088254
2001-12-07    0.176656
2001-12-08    1.391077
2001-12-09    0.776549
2001-12-10   -0.715340
2001-12-11   -0.220713
2001-12-12   -0.854277
2001-12-13   -1.304135
2001-12-14   -0.691430
2001-12-15   -0.562327
2001-12-16    1.081123
2001-12-17    1.566561
2001-12-18    0.469260
2001-12-19   -0.151059
2001-12-20   -0.203601
2001-12-21   -0.454817
2001-12-22   -0.406576
2001-12-23    0.305009
2001-12-24   -0.177214
2001-12-25    0.776997
2001-12-26   -0.611962
2001-12-27   -1.042043
2001-12-28   -0.017181
2001-12-29   -1.381223
2001-12-30   -0.089794
2001-12-31    0.254798
Freq: D, Length: 365, dtype: float64

這裡,字串“2001”被解釋成年,並根據它選取時間區間。指定月也同樣奏效:這樣就很方便了,不需要自己去擷取一部字串

longer_ts['2001-05']
2001-05-01   -2.291270
2001-05-02   -0.956773
2001-05-03   -0.364571
2001-05-04    0.369068
2001-05-05    0.696646
2001-05-06   -1.237324
2001-05-07    1.548785
2001-05-08    1.659536
2001-05-09   -0.344854
2001-05-10    1.108528
2001-05-11    0.183657
2001-05-12   -0.454190
2001-05-13   -0.305806
2001-05-14   -0.392677
2001-05-15   -0.080647
2001-05-16    0.072310
2001-05-17   -0.155399
2001-05-18    1.541267
2001-05-19   -1.478289
2001-05-20   -0.213289
2001-05-21    0.286438
2001-05-22   -0.446343
2001-05-23   -0.942977
2001-05-24    0.282540
2001-05-25    0.615960
2001-05-26   -1.003852
2001-05-27    1.004090
2001-05-28    1.585460
2001-05-29   -0.311037
2001-05-30    0.107670
2001-05-31    0.420645
Freq: D, dtype: float64

datetime物件也可以用來切片:

ts[datetime(2011, 1, 7):]
2011-01-07    1.177715
2011-01-08    0.235663
2011-01-10   -0.404561
2011-01-12   -0.279518
dtype: float64

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

ts
2011-01-02    1.367002
2011-01-05   -1.075364
2011-01-07    1.177715
2011-01-08    0.235663
2011-01-10   -0.404561
2011-01-12   -0.279518
dtype: float64
ts['1/6/2011':'1/11/2011']
2011-01-07    1.177715
2011-01-08    0.235663
2011-01-10   -0.404561
dtype: float64

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

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

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

ts.truncate(after='1/9/2011')
2011-01-02    1.367002
2011-01-05   -1.075364
2011-01-07    1.177715
2011-01-08    0.235663
dtype: float64

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

dates=pd.date_range('1/1/2000',periods=100,freq='W-WED')
dates
DatetimeIndex(['2000-01-05', '2000-01-12', '2000-01-19', '2000-01-26',
               '2000-02-02', '2000-02-09', '2000-02-16', '2000-02-23',
               '2000-03-01', '2000-03-08', '2000-03-15', '2000-03-22',
               '2000-03-29', '2000-04-05', '2000-04-12', '2000-04-19',
               '2000-04-26', '2000-05-03', '2000-05-10', '2000-05-17',
               '2000-05-24', '2000-05-31', '2000-06-07', '2000-06-14',
               '2000-06-21', '2000-06-28', '2000-07-05', '2000-07-12',
               '2000-07-19', '2000-07-26', '2000-08-02', '2000-08-09',
               '2000-08-16', '2000-08-23', '2000-08-30', '2000-09-06',
               '2000-09-13', '2000-09-20', '2000-09-27', '2000-10-04',
               '2000-10-11', '2000-10-18', '2000-10-25', '2000-11-01',
               '2000-11-08', '2000-11-15', '2000-11-22', '2000-11-29',
               '2000-12-06', '2000-12-13', '2000-12-20', '2000-12-27',
               '2001-01-03', '2001-01-10', '2001-01-17', '2001-01-24',
               '2001-01-31', '2001-02-07', '2001-02-14', '2001-02-21',
               '2001-02-28', '2001-03-07', '2001-03-14', '2001-03-21',
               '2001-03-28', '2001-04-04', '2001-04-11', '2001-04-18',
               '2001-04-25', '2001-05-02', '2001-05-09', '2001-05-16',
               '2001-05-23', '2001-05-30', '2001-06-06', '2001-06-13',
               '2001-06-20', '2001-06-27', '2001-07-04', '2001-07-11',
               '2001-07-18', '2001-07-25', '2001-08-01', '2001-08-08',
               '2001-08-15', '2001-08-22', '2001-08-29', '2001-09-05',
               '2001-09-12', '2001-09-19', '2001-09-26', '2001-10-03',
               '2001-10-10', '2001-10-17', '2001-10-24', '2001-10-31',
               '2001-11-07', '2001-11-14', '2001-11-21', '2001-11-28'],
              dtype='datetime64[ns]', freq='W-WED')
long_df = pd.DataFrame(np.random.randn(100, 4), index=dates,columns=['Colorado', 'Texas','New York', 'Ohio'])
long_df.loc['5-2001']
Colorado Texas New York Ohio
2001-05-02 0.502144 -0.675565 0.010877 -0.141896
2001-05-09 0.686185 -1.244643 0.737462 0.603310
2001-05-16 2.093320 -1.844095 -1.557235 1.336359
2001-05-23 0.052609 -0.263812 -0.963784 0.452913
2001-05-30 -0.856077 1.726397 0.556801 -0.699935

帶有重複索引的時間序列

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

dates=pd.DatetimeIndex(['1/1/2000', '1/2/2000', '1/2/2000','1/2/2000', '1/3/2000'])
dup_ts = pd.Series(range(5),index=dates)
dup_ts
2000-01-01    0
2000-01-02    1
2000-01-02    2
2000-01-02    3
2000-01-03    4
dtype: int64

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

dup_ts.index.is_unique
False

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

dup_ts['1/3/2000']#直接用字串也能索引
4
dup_ts['1/2/2000']
2000-01-02    1
2000-01-02    2
2000-01-02    3
dtype: int64

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

grouped = dup_ts.groupby(level=0)
grouped.mean()
2000-01-01    0
2000-01-02    2
2000-01-03    4
dtype: int64

11.3 日期的範圍、頻率以及移動

pandas中的原生時間序列一般被認為是不規則的,也就是說,它們沒有固定的頻率。對於大部分應用程式而言,這是無所謂的。但是,它常常需要以某種相對固定的頻率進行分析,比如每日、每月、每15分鐘等(這樣自然會在時間序列中引入缺失值)。幸運的是,pandas有一整套標準時間序列頻率以及用於重取樣、頻率推斷、生成固定頻率日期範圍的工具。例如,我們可以將之前那個時間序列轉換為一個具有固定頻率(每日)的時間序列,只需呼叫resample即可:

ts
2011-01-02    1.367002
2011-01-05   -1.075364
2011-01-07    1.177715
2011-01-08    0.235663
2011-01-10   -0.404561
2011-01-12   -0.279518
dtype: float64
resampler = ts.resample('D')
resampler
DatetimeIndexResampler [freq=<Day>, axis=0, closed=left, label=left, convention=start, base=0]

字串“D”是每天的意思。

頻率的轉換(或重取樣)是一個比較大的主題,稍後將專門用一節來進行討論(11.6小節)。這裡,我將告訴你如何使用基本的頻率和它的倍數。

生成日期範圍

雖然我之前用的時候沒有明說,但你可能已經猜到pandas.date_range可用於根據指定的頻率生成指定長度的DatetimeIndex:

index=pd.date_range('2012-04-01', '2012-06-01')
index
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會產生按天計算的時間點。如果只傳入起始或結束日期,那就還得傳入一個表示一段時間的數字:

pd.date_range(start='2012-04-01',periods=20)
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')

起始和結束日期定義了日期索引的嚴格邊界。例如,如果你想要生成一個由每月最後一個工作日組成的日期索引,可以傳入"BM"頻率(表示business end of month,表11-4是頻率列表),這樣就只會包含時間間隔內(或剛好在邊界上的)符合頻率要求的日期:

pd.date_range('2000-01-01', '2000-12-01',freq='BM')
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 基本的時間序列頻率(不完整)
別名 偏移量型別 說明
D Day 每日曆日
B BusinessDay 每工作日
H Hour 每小時
T或min Minute 每分
S Second 每秒
L或ms Mili 每毫秒(即每千分之一秒)
U Micro 每微秒(即每百萬分之一秒)
M MonthEnd 每月最後一個日曆日
BM Businessmonth End 每月最後一個工作日
MS MonthBegin 每月第一個日曆日
BMS BusinessMonth Begin 每月第一個工作日
W-MON、W-TUE Week 從指定的星期幾(MON、TUE、WED、THU、FRI、SAT、SUN)開始算起,每週
WOM-1MON、WOM-2MoN… WeekOfMonth 產生每月第一、第二、第三或第四周的星期幾。例如,WOM-3FRI表示每月第3個星期五
Q-JAN、Q-FEB QuarterEnd 對於以指定月份(JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、DEC)結束的年度,每季度最後一月的最後一個日曆日
BQ-JAN、BQ-FEB BusinessQuarter End 對於以指定月份結束的年度,每季度最後一月的最後一個工作日
QS-JAN、 QS-FEB QuarterBegin 對於以指定月份結束的年度,每季度最後一月的第一個日曆日
BQS-JAN、 BQS-FEB BusinessQuarterBegin 對於以指定月份結束的年度,每季
A-JAN、A-FEB YearEnd 每年指定月份(JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、DEC)的最後一個日曆日
BA-JAN、BA-FEB… BusinessYearEnd 每年指定月份的最後一個工作日
AS-JAN、AS-FEB YearBegin 每年指定月份的第一個日曆日
BAS-JAN、BAS-FEB BusinessYearBegin 每年指定月份的第一個工作日

WOM日期

WOM(Week Of Month)是一種非常實用的頻率類,它以WOM開頭。它使你能獲得諸如“每月第3個星期五”之類的日期:

rng=pd.date_range('2012-01-01', '2012-09-01',freq='WOM-3FRI')
rng
DatetimeIndex(['2012-01-20', '2012-02-17', '2012-03-16', '2012-04-20',
               '2012-05-18', '2012-06-15', '2012-07-20', '2012-08-17'],
              dtype='datetime64[ns]', freq='WOM-3FRI')
list(rng)
[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方法用於執行單純的前移或後移操作,保持索引不變:

ts=pd.Series(np.random.randn(4),index=pd.date_range('1/1/2000',periods=4,freq='M'))
ts
2000-01-31    0.600662
2000-02-29    0.357738
2000-03-31    0.687984
2000-04-30   -0.515376
Freq: M, dtype: float64
ts.shift(2)
2000-01-31         NaN
2000-02-29         NaN
2000-03-31    0.600662
2000-04-30    0.357738
Freq: M, dtype: float64
ts.shift(-2)
2000-01-31    0.687984
2000-02-29   -0.515376
2000-03-31         NaN
2000-04-30         NaN
Freq: M, dtype: float64

當我們這樣進行移動時,就會在時間序列的前面或後面產生缺失資料。

shift通常用於計算一個時間序列或多個時間序列(如DataFrame的列)中的百分比變化。可以這樣表達:

ts/ts.shift(1)
2000-01-31         NaN
2000-02-29    0.595572
2000-03-31    1.923151
2000-04-30   -0.749111
Freq: M, dtype: float64
ts / ts.shift(1) - 1
2000-01-31         NaN
2000-02-29   -0.404428
2000-03-31    0.923151
2000-04-30   -1.749111
Freq: M, dtype: float64

由於單純的移位操作不會修改索引,所以部分資料會被丟棄。因此,如果頻率已知,則可以將其傳給shift以便實現對時間戳進行位移而不是對資料進行簡單位移:

ts.shift(2,freq='M')
2000-03-31    0.600662
2000-04-30    0.357738
2000-05-31    0.687984
2000-06-30   -0.515376
Freq: M, dtype: float64

這裡還可以使用其他頻率,於是你就能非常靈活地對資料進行超前和滯後處理了:

ts.shift(3,freq='D')
2000-02-03    0.600662
2000-03-03    0.357738
2000-04-03    0.687984
2000-05-03   -0.515376
dtype: float64
ts.shift(1,freq='90T')
2000-01-31 01:30:00    0.600662
2000-02-29 01:30:00    0.357738
2000-03-31 01:30:00    0.687984
2000-04-30 01:30:00   -0.515376
Freq: M, dtype: float64

通過偏移量對日期進行位移

pandas的日期偏移量還可以用在datetime或Timestamp物件上:這個跟timedelta實現的功能好像沒什麼大的區別

from pandas.tseries.offsets import Day,MonthEnd
now=datetime(2011,11,17)
now+3*Day()
Timestamp('2011-11-20 00:00:00')

如果加的是錨點偏移量(比如MonthEnd),第一次增量會將原日期向前滾動到符合頻率規則的下一個日期:

now+MonthEnd()
Timestamp('2011-11-30 00:00:00')
now+MonthEnd(2)
Timestamp('2011-12-31 00:00:00')

通過錨點偏移量的rollforward和rollback方法,可明確地將日期向前或向後“滾動”:

offset=MonthEnd()
offset.rollforward(now)
Timestamp('2011-11-30 00:00:00')
offset.rollback(now)
Timestamp('2011-10-31 00:00:00')

日期偏移量還有一個巧妙的用法,即結合groupby使用這兩個“滾動”方法:

ts = pd.Series(np.random.randn(20),index=pd.date_range('1/15/2000', periods=20, freq='4d'))
ts
2000-01-15   -0.752742
2000-01-19   -1.414074
2000-01-23   -1.162491
2000-01-27    0.534581
2000-01-31   -0.377659
2000-02-04   -1.685432
2000-02-08   -1.782411
2000-02-12   -1.219173
2000-02-16   -0.961443
2000-02-20   -0.864551
2000-02-24    0.315910
2000-02-28    0.531089
2000-03-03   -2.682542
2000-03-07    1.436291
2000-03-11    1.613283
2000-03-15    1.448720
2000-03-19    0.442549
2000-03-23    0.538157
2000-03-27   -1.225652
2000-03-31   -0.762207
Freq: 4D, dtype: float64
ts.groupby(offset.rollforward).mean()
2000-01-31   -0.634477
2000-02-29   -0.809430
2000-03-31    0.101075
dtype: float64

當然,更簡單、更快速地實現該功能的辦法是使用resample(11.6小節將對此進行詳細介紹):

ts.resample('M').mean()
2000-01-31   -0.634477
2000-02-29   -0.809430
2000-03-31    0.101075
Freq: M, dtype: float64

11.4 時區處理

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

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

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

import pytz
pytz.common_timezones[-5:]
['US/Eastern', 'US/Hawaii', 'US/Mountain', 'US/Pacific', 'UTC']

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

tz = pytz.timezone('America/New_York')
tz
<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>

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

時區本地化和轉換

rng=pd.date_range('3/9/2012 9:30',periods=6,freq='D')
ts=pd.Series(np.random.randn(len(rng)),index=rng)
ts
2012-03-09 09:30:00   -1.202927
2012-03-10 09:30:00   -0.880422
2012-03-11 09:30:00    1.126177
2012-03-12 09:30:00    1.116623
2012-03-13 09:30:00    0.954839
2012-03-14 09:30:00    2.791434
Freq: D, dtype: float64

其索引的tz欄位為None:

print(ts.index.tz)
None

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

pd.date_range('3/9/2012 9:30',periods=10,freq='D',tz='UTC')#tz體現在dtype上
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為none)到本地化的轉換是通過tz_localize方法處理的:

ts_utc = ts.tz_localize('UTC')
ts_utc
2012-03-09 09:30:00+00:00   -1.202927
2012-03-10 09:30:00+00:00   -0.880422
2012-03-11 09:30:00+00:00    1.126177
2012-03-12 09:30:00+00:00    1.116623
2012-03-13 09:30:00+00:00    0.954839
2012-03-14 09:30:00+00:00    2.791434
Freq: D, dtype: float64
ts_utc.index
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將其轉換到別的時區了:

ts_utc.tz_convert('America/New_York')
2012-03-09 04:30:00-05:00   -1.202927
2012-03-10 04:30:00-05:00   -0.880422
2012-03-11 05:30:00-04:00    1.126177
2012-03-12 05:30:00-04:00    1.116623
2012-03-13 05:30:00-04:00    0.954839
2012-03-14 05:30:00-04:00    2.791434
Freq: D, dtype: float64

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

ts_eastern = ts.tz_localize('America/New_York')
ts_eastern.tz_convert('UTC')
2012-03-09 14:30:00+00:00   -1.202927
2012-03-10 14:30:00+00:00   -0.880422
2012-03-11 13:30:00+00:00    1.126177
2012-03-12 13:30:00+00:00    1.116623
2012-03-13 13:30:00+00:00    0.954839
2012-03-14 13:30:00+00:00    2.791434
Freq: D, dtype: float64
ts_eastern.tz_convert('Europe/Berlin')
2012-03-09 15:30:00+01:00   -1.202927
2012-03-10 15:30:00+01:00   -0.880422
2012-03-11 14:30:00+01:00    1.126177
2012-03-12 14:30:00+01:00    1.116623
2012-03-13 14:30:00+01:00    0.954839
2012-03-14 14:30:00+01:00    2.791434
Freq: D, dtype: float64

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

ts.index.tz_localize('Asia/Shanghai')
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),並從一個時區轉換到另一個時區:

stamp