時間序列
日期和時間數據類型及工具
Python標準庫包含用於日期(date)和時間(time)數據的數據類型,還有日曆方面的功能。主要會用到datetime、time以及calendar模塊。datetime.datetime(也可以簡寫爲datetime)是用得最多的數據類型
from pandas import Series,DataFrame
import pandas as pd
import numpy as np
from datetime import datetime
now=datetime.now()
now
datetime.datetime(2017, 12, 27, 10, 25, 45, 574261)
now.year,now.month,now.day
(2017, 12, 26)
datetime以毫秒形式存儲日期和時間。
datetime.timedelta表示兩個datetime對象之間的時間差
delta=datetime(2017,12,1)-datetime(2012,9,8,8,15)
delta
datetime.timedelta(1909, 56700)
delta.days #日的時間差
1909
delta.seconds #秒的時間差
56700
可以給datetime對象加上(或減去)一個或多個timedelta,這樣會產生一個新對象
from datetime import timedelta
start=datetime(2017,6,13)
start+timedelta(12)
datetime.datetime(2017, 6, 25, 0, 0)
start-2*timedelta(12)
datetime.datetime(2017, 5, 20, 0, 0)
datetime模塊中的數據類型參見表10-1
字符串和datetime的相互轉換
利用str或strftime方法(傳入一個格式化字符串),datetime對象和pandas的Timestamp對象可以被格式化爲字符串
stamp=datetime(2017,12,26)
str(stamp)
'2017-12-26 00:00:00'
stamp.strftime('%Y-%m-%d')
'2017-12-26'
datetime.strptime可以用表中的這些格式化編碼將字符串轉換爲日期
value='2017-12-26'
datetime.strptime(value,'%Y-%m-%d')
datetime.datetime(2017, 12, 26, 0, 0)
datestrs=['11/12/2017','12/12/2017']
[datetime.strptime(x,'%m/%d/%Y') for x in datestrs]
[datetime.datetime(2017, 11, 12, 0, 0), datetime.datetime(2017, 12, 12, 0, 0)]
datetime.strptime是通過已知格式進行日期解析的最佳方式。d但是對於其他常見的日期格式,需要使用dateutil這個第三方包中的parser.parse方法
from dateutil.parser import parse
parse('2017-12-06')
datetime.datetime(2017, 12, 6, 0, 0)
dateutil可以解析幾乎所有人類能夠理解的日期表示形式
parse('Jan 31,2017 9:35 PM')
datetime.datetime(2017, 1, 31, 21, 35)
在國際通用的格式中,日通常出現在月的前面,傳入dayfirst=True即可解決
parse('10/12/2017',dayfirst=True)
datetime.datetime(2017, 12, 10, 0, 0)
pandas通常是用於處理成組日期的,不管這些日期是DataFrame的軸索引還是列。to_datetime方法可以解析多種不同的日期表示形式。對標準日期格式(如ISO8601)的解析非常快。可以處理缺失值(None、空字符串等)
datestrs
['11/12/2017', '12/12/2017']
pd.to_datetime(datestrs)
DatetimeIndex(['2017-11-12', '2017-12-12'], dtype='datetime64[ns]', freq=None)
idx=pd.to_datetime(datestrs+[None])
idx
DatetimeIndex(['2017-11-12', '2017-12-12', 'NaT'], dtype='datetime64[ns]', freq=None)
idx[2]
NaT
idx[1]
Timestamp('2017-12-12 00:00:00')
pd.isnull(idx)
array([False, False, True], dtype=bool)
NaT(Not a Time)是pandas中時間戳數據的NA值
警告: dateutil.parser是一個實用但不完美的工具。比如說,它會把一些原本不是日期的字符串認作是日期(比如”42”會被解析爲2042年的今天)
時間序列基礎
pandas最基本的時間序列類型就是以時間戳(通常以Python字符串或datatime對象表示)爲索引的Series
from datetime import datetime
dates=[datetime(2017,1,6),datetime(2017,1,10),datetime(2017,1,13),
datetime(2017,1,15),datetime(2017,1,18),datetime(2017,1,20)]
ts=Series(np.random.randn(6),index=dates)
ts
2017-01-06 0.005613
2017-01-10 1.222545
2017-01-13 0.000879
2017-01-15 0.172794
2017-01-18 -0.530976
2017-01-20 -0.808555
dtype: float64
可以看出datetime對象是被放在一個DatetimeIndex中,變量ts就成爲一個TimeSeries
type(ts)
pandas.core.series.Series
ts.index
DatetimeIndex(['2017-01-06', '2017-01-10', '2017-01-13', '2017-01-15',
'2017-01-18', '2017-01-20'],
dtype='datetime64[ns]', freq=None)
注意: 沒必要顯式使用TimeSeries的構造函數。當創建一個帶有DatetimeIndex的Series時,pandas就會知道該對象是一個時間序列。
ts[::2]
2017-01-06 0.170305
2017-01-13 -0.798955
2017-01-18 -0.367438
dtype: float64
跟其他Series一樣,不同索引的時間序列之間的算術運算會自動按日期對齊
ts+ts[::2]
2017-01-06 0.340610
2017-01-10 NaN
2017-01-13 -1.597911
2017-01-15 NaN
2017-01-18 -0.734876
2017-01-20 NaN
dtype: float64
pandas用NumPy的datetime64數據類型以納秒形式存儲時間戳
ts.index.dtype
dtype('<M8[ns]')
DatetimeIndex中的各個標量值是pandas的Timestamp對象
stamp=ts.index[0]
stamp
Timestamp('2017-01-06 00:00:00')
ts.index[3]
Timestamp('2017-01-15 00:00:00')
如果有需要,TimeStamp可以隨時自動轉換爲datetime對象
索引、選取、子集構造
由於TimeSeries是Series的一個子類,所以在索引以及數據選取方面它們的行爲是一樣的
stamp=ts.index[2]
stamp
Timestamp('2017-01-13 00:00:00')
ts[stamp]
-0.79895542662575525
可以傳入一個可以被解釋爲日期的字符串
ts['1/10/2017']
1.3762403458229933
ts['20170118']
-0.36743784149263731
對於較長的時間序列,只需傳入“年”或“年月”即可輕鬆選取數據的切片
longer_ts=Series(np.random.randn(1000),
index=pd.date_range('11/25/2017',periods=1000))
longer_ts
2017-11-25 -0.659067
2017-11-26 -1.539291
2017-11-27 -1.731498
2017-11-28 -0.560947
... ...
2020-08-17 -0.499714
2020-08-18 -1.978626
2020-08-19 1.757046
2020-08-20 -0.401384
Freq: D, Length: 1000, dtype: float64
longer_ts['2017']
2017-11-25 -0.659067
2017-11-26 -1.539291
2017-11-27 -1.731498
2017-11-28 -0.560947
2017-11-29 -2.086909
... ...
2017-12-28 0.123651
2017-12-29 -0.188641
2017-12-30 1.206445
2017-12-31 -1.734814
Freq: D, dtype: float64
longer_ts['2017-11']
2017-11-25 -0.659067
2017-11-26 -1.539291
2017-11-27 -1.731498
2017-11-28 -0.560947
2017-11-29 -2.086909
2017-11-30 -0.673677
Freq: D, dtype: float64
通過日期進行切片的方式只對規則Series有效。
ts
2017-01-06 0.170305
2017-01-10 1.376240
2017-01-13 -0.798955
2017-01-15 -0.496169
2017-01-18 -0.367438
2017-01-20 0.059816
dtype: float64
ts[datetime(2017,1,13):]
2017-01-13 -0.798955
2017-01-15 -0.496169
2017-01-18 -0.367438
2017-01-20 0.059816
dtype: float64
由於大部分時間序列數據都是按照時間先後排序的,因此你也可以用不存在於該時間序列中的時間戳對其進行切片(即範圍查詢
ts['1/12/2017':'1/17/2017']
2017-01-13 -0.798955
2017-01-15 -0.496169
dtype: float64
這裏可以傳入字符串日期、datetime或Timestamp。注意,這樣切片所產生的是源時間序列的視圖,跟NumPy數組的切片運算是一樣的。此外,還有一個等價的實例方法也可以截取兩個日期之間TimeSeries
ts.truncate(after='1/14/2017')
2017-01-06 0.170305
2017-01-10 1.376240
2017-01-13 -0.798955
dtype: float64
這些操作對DataFrame也有效
dates=pd.date_range('1/1/2017',periods=100,freq='W-WED')
long_df=DataFrame(np.random.randn(100,4),index=dates,
columns=['Colorado','Texas','New York','Ohio'])
long_df.ix['5-2017']
帶有重複索引的時間序列
在某些應用場景中,可能會存在多個觀測數據落在同一個時間點上的情況
dates=pd.DatetimeIndex(['1/1/2017','1/2/2017','1/2/2017','1/2/2017','1/3/2017'])
dup_ts=Series(np.arange(5),index=dates)
dup_ts
2017-01-01 0
2017-01-02 1
2017-01-02 2
2017-01-02 3
2017-01-03 4
dtype: int32
通過檢查索引的is_unique屬性,我們就可以知道它是不是唯一的
dup_ts.index.is_unique
False
對這個時間序列進行索引,要麼產生標量值,要麼產生切片,具體要看所選的時間點是否重複
dup_ts['1/3/2017'] #不重複
4
dup_ts['1/2/2017'] #重複
2017-01-02 1
2017-01-02 2
2017-01-02 3
dtype: int32
假設你想要對具有非唯一時間戳的數據進行聚合。一個辦法是使用groupby,並傳入level=0(索引的唯一一層!)
grouped=dup_ts.groupby(level=0)
grouped.mean()
2017-01-01 0
2017-01-02 2
2017-01-03 4
dtype: int32
grouped.count()
2017-01-01 1
2017-01-02 3
2017-01-03 1
dtype: int64
日期的範圍、頻率以及移動
pandas有一整套標準時間序列頻率以及用於重採樣、頻率推斷、生成固定頻率日期範圍的工具。例如,我們可以將之前那個時間序列轉換爲一個具有固定頻率(每日)的時間序列,只需調用resample
ts
2017-01-06 0.005613
2017-01-10 1.222545
2017-01-13 0.000879
2017-01-15 0.172794
2017-01-18 -0.530976
2017-01-20 -0.808555
dtype: float64
ts.resample('D')
DatetimeIndexResampler [freq=<Day>, axis=0, closed=left, label=left, convention=start, base=0]
生成日期範圍
pandas.date_range可用於生成指定長度的DatetimeIndex
index=pd.date_range('6/10/2017','7/1/2017')
index
DatetimeIndex(['2017-06-10', '2017-06-11', '2017-06-12', '2017-06-13',
'2017-06-14', '2017-06-15', '2017-06-16', '2017-06-17',
'2017-06-18', '2017-06-19', '2017-06-20', '2017-06-21',
'2017-06-22', '2017-06-23', '2017-06-24', '2017-06-25',
'2017-06-26', '2017-06-27', '2017-06-28', '2017-06-29',
'2017-06-30', '2017-07-01'],
dtype='datetime64[ns]', freq='D')
默認情況下,date_range會產生按天計算的時間點。如果只傳入起始或結束日期,那就還得傳入一個表示一段時間的數字
pd.date_range(start='12/8/2017',periods=20)
DatetimeIndex(['2017-12-08', '2017-12-09', '2017-12-10', '2017-12-11',
'2017-12-12', '2017-12-13', '2017-12-14', '2017-12-15',
'2017-12-16', '2017-12-17', '2017-12-18', '2017-12-19',
'2017-12-20', '2017-12-21', '2017-12-22', '2017-12-23',
'2017-12-24', '2017-12-25', '2017-12-26', '2017-12-27'],
dtype='datetime64[ns]', freq='D')
起始和結束日期定義了日期索引的嚴格邊界。例如,如果你想要生成一個由每月最後一個工作日組成的日期索引,可以傳入”BM”頻率(表示business end of month),這樣就只會包含時間間隔內(或剛好在邊界上的)符合頻率要求的日期
pd.date_range('1/1/2017','12/1/2017',freq='BM')
DatetimeIndex(['2017-01-31', '2017-02-28', '2017-03-31', '2017-04-28',
'2017-05-31', '2017-06-30', '2017-07-31', '2017-08-31',
'2017-09-29', '2017-10-31', '2017-11-30'],
dtype='datetime64[ns]', freq='BM')
date_range默認會保留起始和結束時間戳的時間信息(如果有的話)
pd.date_range('5/6/2017 12:30:20',periods=5)
DatetimeIndex(['2017-05-06 12:30:20', '2017-05-07 12:30:20',
'2017-05-08 12:30:20', '2017-05-09 12:30:20',
'2017-05-10 12:30:20'],
dtype='datetime64[ns]', freq='D')
有時,雖然起始和結束日期帶有時間信息,但你希望產生一組被規範化(normalize)到午夜的時間戳。normalize選項即可實現該功能
pd.date_range('12/15/2017 12:50:33',periods=5,normalize=True)
DatetimeIndex(['2017-12-15', '2017-12-16', '2017-12-17', '2017-12-18',
'2017-12-19'],
dtype='datetime64[ns]', freq='D')
頻率和日期偏移量
pandas中的頻率是由一個基礎頻率(base frequency)和一個乘數組成的。基礎頻率通常以一個字符串別名表示,比如”M”表示每月,”H”表示每小時。對於每個基礎頻率,都有一個被稱爲日期偏移量(date offset)的對象與之對應。例如,按小時計算的頻率可以用Hour類表示
from pandas.tseries.offsets import Hour,Minute
hour=Hour()
hour
<Hour>
傳入一個整數即可定義偏移量的倍數
four_fours=Hour(4)
four_fours
<4 * Hours>
無需顯式創建這樣的對象,只需使用諸如”H”或”4H”這樣的字符串別名即可。在基礎頻率前面放上一個整數即可創建倍數
pd.date_range('1/1/2017','1/3/2017 23:59',freq='4H')
DatetimeIndex(['2017-01-01 00:00:00', '2017-01-01 04:00:00',
'2017-01-01 08:00:00', '2017-01-01 12:00:00',
'2017-01-01 16:00:00', '2017-01-01 20:00:00',
'2017-01-02 00:00:00', '2017-01-02 04:00:00',
'2017-01-02 08:00:00', '2017-01-02 12:00:00',
'2017-01-02 16:00:00', '2017-01-02 20:00:00',
'2017-01-03 00:00:00', '2017-01-03 04:00:00',
'2017-01-03 08:00:00', '2017-01-03 12:00:00',
'2017-01-03 16:00:00', '2017-01-03 20:00:00'],
dtype='datetime64[ns]', freq='4H')
大部分偏移量對象都可通過加法進行連接;你也可以傳入頻率字符串(如”2h30min”),這種字符串可以被高效地解析爲等效的表達式
Hour(3)+Minute(30)
<210 * Minutes>
pd.date_range('1/1/2017',periods=10,freq='1h30min')
DatetimeIndex(['2017-01-01 00:00:00', '2017-01-01 01:30:00',
'2017-01-01 03:00:00', '2017-01-01 04:30:00',
'2017-01-01 06:00:00', '2017-01-01 07:30:00',
'2017-01-01 09:00:00', '2017-01-01 10:30:00',
'2017-01-01 12:00:00', '2017-01-01 13:30:00'],
dtype='datetime64[ns]', freq='90T')
有些頻率所描述的時間點並不是均勻分隔的。例如,”M”(日曆月末)和”BM”(每月最後一個工作日)就取決於每月的天數,對於後者,還要考慮月末是不是週末。由於沒有更好的術語,我將這些稱爲錨點偏移量(anchored offset)。
表10-4列出了pandas中的頻率代碼和日期偏移量類。
WOM日期
WOM(Week Of Month)是一種非常實用的頻率類,它以WOM開頭。它使你能獲得諸如“每月第3個星期五”之類的日期
rng=pd.date_range('1/1/2017','9/1/2017',freq='WOM-3FRI')
list(rng)
[Timestamp('2017-01-20 00:00:00', freq='WOM-3FRI'),
Timestamp('2017-02-17 00:00:00', freq='WOM-3FRI'),
Timestamp('2017-03-17 00:00:00', freq='WOM-3FRI'),
Timestamp('2017-04-21 00:00:00', freq='WOM-3FRI'),
Timestamp('2017-05-19 00:00:00', freq='WOM-3FRI'),
Timestamp('2017-06-16 00:00:00', freq='WOM-3FRI'),
Timestamp('2017-07-21 00:00:00', freq='WOM-3FRI'),
Timestamp('2017-08-18 00:00:00', freq='WOM-3FRI')]
美國的股票期權交易人會意識到這些日子就是標準的月度到期日
移動(超前和滯後)數據
移動(shifting)指的是沿着時間軸將數據前移或後移。Series和DataFrame都有一個shift方法用於執行單純的前移或後移操作,保持索引不變
ts=Series(np.random.randn(4),
index=pd.date_range('1/1/2017',periods=4,freq='M'))
ts
2017-01-31 -1.555270
2017-02-28 1.325946
2017-03-31 -1.458090
2017-04-30 1.064776
Freq: M, dtype: float64
ts.shift(2)
2017-01-31 NaN
2017-02-28 NaN
2017-03-31 -1.555270
2017-04-30 1.325946
Freq: M, dtype: float64
ts.shift(-2)
2017-01-31 -1.458090
2017-02-28 1.064776
2017-03-31 NaN
2017-04-30 NaN
Freq: M, dtype: float64
shift通常用於計算一個時間序列或多個時間序列(如DataFrame的列)中的百分比變化。可以這樣表達:
ts / ts.shift(1) - 1
由於單純的移位操作不會修改索引,所以部分數據會被丟棄。因此,如果頻率已知,則可以將其傳給shift以便實現對時間戳進行位移而不是對數據進行簡單位移
ts.shift(2,freq='M')
2017-03-31 -1.555270
2017-04-30 1.325946
2017-05-31 -1.458090
2017-06-30 1.064776
Freq: M, dtype: float64
還可以使用其他頻率,於是你就能非常靈活地對數據進行超前和滯後處理了
ts.shift(3,freq='D')
2017-02-03 -1.555270
2017-03-03 1.325946
2017-04-03 -1.458090
2017-05-03 1.064776
dtype: float64
ts.shift(1,freq='3D')
2017-02-03 -1.555270
2017-03-03 1.325946
2017-04-03 -1.458090
2017-05-03 1.064776
dtype: float64
ts.shift(-3,freq='D')
2017-01-28 -1.555270
2017-02-25 1.325946
2017-03-28 -1.458090
2017-04-27 1.064776
dtype: float64
通過偏移量對日期進行位移
pandas的日期偏移量還可以用在datetime或Timestamp對象上
from pandas.tseries.offsets import Day,MonthEnd
now=datetime(2017,12,27)
now+3*Day()
Timestamp('2017-12-30 00:00:00')
如果加的是錨點偏移量(比如MonthEnd),第一次增量會將原日期向前滾動到符合頻率規則的下一個日期
now+MonthEnd()
Timestamp('2017-12-31 00:00:00')
now+MonthEnd(3)
Timestamp('2018-02-28 00:00:00')
通過錨點偏移量的rollforward和rollback方法,可顯式地將日期向前或向後“滾動”
offset=MonthEnd()
offset.rollforward(now)
Timestamp('2017-12-31 00:00:00')
offset.rollback(now)
Timestamp('2017-11-30 00:00:00')
日期偏移量還有一個巧妙的用法,即結合groupby使用這兩個“滾動”方法
ts=Series(np.random.randn(10),
index=pd.date_range('1/15/2017',periods=10,freq='4d'))
ts
2017-01-15 1.060753
2017-01-19 0.410493
2017-01-23 2.188356
2017-01-27 -0.335335
2017-01-31 -1.976843
2017-02-04 1.644975
2017-02-08 0.243292
2017-02-12 -0.444232
2017-02-16 -0.665907
2017-02-20 -0.771520
Freq: 4D, dtype: float64
ts.groupby(offset.rollforward).mean()
2017-01-31 0.269485
2017-02-28 0.001322
dtype: float64
更簡單、更快速地實現該功能的辦法是使用resample
ts.resample('M',how='mean')
2017-01-31 0.269485
2017-02-28 0.001322
Freq: M, dtype: float64
本章的時間序列知識點比較多,需要分階段練習。