pandas 時間序列
時間序列資料的意義取決於具體的應用場景,主要有以下幾種:
- 時間戳(timestamp):特定的時刻。
- 固定時期(period):如2017年1月或2018年全年
- 時間間隔(interval):由起始和結束時間戳表示。時期(period)可以被看做間隔(interval)的特例。
- 實驗或過程時間:每個時間點都是相對於特定起始時間的一個度量。例如,從放入烤箱時起,每秒鐘餅乾的直徑。
pandas提供了一組標準的時間序列,輕鬆地進行切片/切塊、聚合、對定期/不定期的時間序列進行重取樣等。
可以你已經猜到了,這些工具中大部分都對金融和經濟資料尤其有用,但你當然也可以用它們來分析伺服器日誌資料。
1.日期和時間資料型別及工具:
from datetime import datetime now=datetime.now() print now delta=datetime(2017,8,4)-datetime(2017,6,24) print now.year,now.month,now.day print delta print delta.days print delta.seconds #輸出結果如下: # 2018-07-26 15:37:20.905441 # 2018 7 26 # 41 days, 0:00:00 # 41 # 0 #(1)可以給datetime物件加上(或減去)一個或多個timedelta,這樣會產生一個新物件: from datetime import timedelta start=datetime(2011,1,7) print start+timedelta(12) #輸出:2011-01-19 00:00:00 print start-2*timedelta(12) #輸出:2010-12-14 00:00:00
datetime模組中的資料型別
型別 說明
date 以公曆形式儲存日曆日期(年、月、日)
time 將時間儲存為時、分、秒、毫秒
datetime 儲存日期和時間
timedelta 表示兩個datetime值之間的差(日、秒、毫秒)
注:datetime是python的模組函式,但pandas一定會用到。
字串和datetime的相互轉換:str/strftime,time.strptime,parser,pd.to_datetime
import datetime
import time
import pandas as pd
#(1)時間轉化為字串:str;
# 格式化不籽符串:strftime('%Y-%m-%d')
# stamp=datetime(2017,1,3)
# print str(stamp) #輸出:2017-01-03 00:00:00
# print stamp.strftime('%Y-%m-%d') #輸出:2017-01-03
#(2) 將字串轉化為時間
value='2011-01-03'
print time.strptime(value,'%Y-%m-%d')
datestrs=['7/6/2011','8/6/2011']
print [time.strptime(x,'%m/%d/%Y') for x in datestrs]
#輸出結果如下:
# [time.struct_time(tm_year=2011, tm_mon=7, tm_mday=6, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=2, tm_yday=187, tm_isdst=-1), time.struct_time(tm_year=2011, tm_mon=8, tm_mday=6, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=218, tm_isdst=-1)]
#(3)time.strptime是通過已知格式進行日期解析的最佳方式。但是每次都要編寫格式定義是很麻煩的事情,
#尤其是對於一些常見的日期格式。這種情況下,可以用dateutil這個第三方包中的parser.parse方法
from dateutil.parser import parse
print parse('2017-01-03')
#輸出結果如下:
# 2017-01-03 00:00:00
#dateutil可以解析幾乎所有人類能夠理解的日期表示形式:
print parse('Jan 31,2017 10:45 PM') #輸出:2017-01-31 22:45:00
#(4)在國際通用的格式中,日通常出現在月的前面,傳入dayfirst=True即可解決這個問題:
print parse('6/12/2011',dayfirst=True) #輸出:2011-12-06 00:00:00
#注意:dateutil.parser是一個實用但不完美的工具。比如,它會把一些原本不是日期的字串認作是日期
#(比如"42"會被解析為2042年的今天)
#(5)pandas通常是用於處理成組日期的,不管這些日期是DataFrame的軸索引還是列。
#to_datetime方法可以解析多種不同的日期表示形式。對標準日期格式的解析非常快。
print datestrs #輸出:['7/6/2011', '8/6/2011']
print pd.to_datetime(datestrs)
#輸出結果:
# DatetimeIndex(['2011-07-06', '2011-08-06'], dtype='datetime64[ns]', freq=None)
#(5)它還可以處理缺失值(None,空字串等):NAT(Not a Time)是pandas中時間戳資料的NA值。
idx=pd.to_datetime(datestrs+[None])
print idx
#輸出結果如下:
# DatetimeIndex(['2011-07-06', '2011-08-06', 'NaT'], dtype='datetime64[ns]', freq=None)
datetime格式字義:
程式碼 說明
%Y 4位數的年
%y 2位數的年
%m 2位數的月[01,12]
%d 2們數的日[01,31]
%H 時(24小時制)[00,23]
%I 時(12小時制)[01,12]
%M 2位數的分[00,59]
%S 秒[00,60]
%w 用整數表示的星期幾[0(星期天),6]
%U 每年的第幾周[00,53].星期天被認為是每週的第一天,第年第一個星期天之前的那幾天被認為是“第0周”
%W 每年的第幾周[00,53]。星期一被認為是每週的第一天,每年第一個星期一之前的那幾天被認為是“第0周”
%z 以+HHMM或-HHMM表示的UTC時區偏移量,如果時區為naive,則返回空字串
%F %Y-%m-%d簡寫形式,例如2017-4-18
%D %m/%d/%y簡寫形式,例如04/18/12
datetime物件還有一些特定於當前環境(位於不同國家或使用不同語言的系統)
特定於當前環境的日期格式:
程式碼 說明
%a 星期幾的簡寫
%A 星期幾的全稱
%b 月份的簡寫
%B 月份的全稱
%c 完整的日期和時間,例如:“Tue 01 May 2017 04:20:57 PM”
%p 不同環境中的AM或PM
%x 適合於當前環境的日期格式。例如,在美國,“May 1,2017”會產生"05/01/2017"
%X 適合於當前環境的時間格式,例如“04:24:12PM”
2.時間序列基礎:
pandas最基本的時間序列型別就是以時間戳(通常以python字串或datetime物件表示)為索引的series:
from datetime import datetime
import pandas as pd
from pandas import DataFrame,Series
import numpy as np
dates=[datetime(2017,1,2),datetime(2017,1,5),datetime(2017,1,7),
datetime(2017,1,8),datetime(2017,1,10),datetime(2017,1,12)]
ts=Series(np.random.randn(6),index=dates)
print ts
#輸出結果如下:
# 2017-01-02 -1.618294
# 2017-01-05 1.838125
# 2017-01-07 0.491439
# 2017-01-08 0.329895
# 2017-01-10 1.541238
# 2017-01-12 0.917043
#這些datetime物件實際上是被放在一個DatetimeIndex中的,現在,變數ts就成為一個TimeSeries了。
print type(ts) #輸出結果:<class 'pandas.core.series.Series'>
print ts.index
#輸出結果如下:
# DatetimeIndex(['2017-01-02', '2017-01-05', '2017-01-07', '2017-01-08',
# '2017-01-10', '2017-01-12'],
# dtype='datetime64[ns]', freq=None)
#(1)時間序列的Series跟其它Series一樣,不同索引的時間序列之間的算術運算會自動按日期對齊
print ts+ts[::2]
#輸出結果如下:
# 2017-01-02 -2.109843
# 2017-01-05 NaN
# 2017-01-07 -4.117566
# 2017-01-08 NaN
# 2017-01-10 0.976704
# 2017-01-12 NaN
# dtype: float64
#pandas用Numpy的datetime64資料型別以納秒形式儲存時間戳:
print ts.index.dtype #輸出結果:datetime64[ns]
#DatetimeIndex中的各個標量值是pandas的Timestamp物件:
stamp=ts.index[0]
print stamp #輸出結果如下:2017-01-02 00:00:00
索引、選取、子集構造:
由於TimeSeries是Series的一個子類,所以在索引以及資料選取方面它們的行為是一樣的。
ts.index[2]; ts['1/10/2017']; index=pd.date_range('1/1/2017',periods=10) ; ts[datetime('1/1/2017')] ; ts.truncate(after='1/1/2017'); ts['1/1/2017','2/1/2017']
from datetime import datetime
import pandas as pd
from pandas import DataFrame,Series
import numpy as np
#(1) index的下標取值
dates=[datetime(2017,1,2),datetime(2017,1,5),datetime(2017,1,7),
datetime(2017,1,8),datetime(2017,1,10),datetime(2017,1,12)]
ts=Series(np.random.randn(6),index=dates)
print ts
stamp=ts.index[2] #下標從0開始,即第三行資料
print ts[stamp]
#輸出結果:0.376927445233
#(2)還有一個更為方便的用法:傳入一個可以被解釋為日期的字串。
print ts['1/10/2017'] #取2017年1月10號的資料
#(3)對於較長的時間序列,只需傳入"年"或"年月"即可輕鬆選取資料的切片:
longer_ts=Series(np.random.randn(1000),index=pd.date_range('1/1/2017',periods=1000))
# print longer_ts
#輸出結果如下:index從2017年1月1日開始,1000天。故到2019年9月27日
# 2017-01-01 -0.825255
# 2017-01-02 0.024477
# 2017-01-03 -1.299953
# ......................
# 2019-09-26 -0.132728
# 2019-09-27 -1.187214
print longer_ts['2017-05'] #取2017年所有5月份的資料
#(4)通過日期進行切片的方式只對規則Series有效:
print ts[datetime(2017,1,7):] #取datetime(2017,1,7)後面的資料
#輸出結果如下:
# 2017-01-07 -0.227280
# 2017-01-08 -0.307934
# 2017-01-10 0.923490
# 2017-01-12 -0.165595
# dtype: float64
#(5)由於大部分時間序列資料都是按照時間先後排序的,因此你也可以用不存在於該時間序列中的時間戳對基進行切片(即範圍查詢):
print ts
#輸出結果如下:
# 2017-01-02 0.148366
# 2017-01-05 0.159362
# 2017-01-07 0.760312
# 2017-01-08 -0.185600
# 2017-01-10 0.310633
# 2017-01-12 1.879177
# dtype: float64
print ts['1/6/2017':'1/11/2017'] #取從2017年1月6號到2017年1月11號之間的資料
#輸出結果如下:
# 2017-01-07 0.760312
# 2017-01-08 -0.185600
# 2017-01-10 0.310633
# dtype: float64
#(6)跟之前一樣,這裡可以傳入字串日期、datetime或Timestamp.
#注意,這樣切片所產生的是源時間序列的檢視,跟Numpy陣列的切片運算是一樣的。此外,還有一個等價的例項方法
#也可以擷取兩個日期之間TimeSeries.
print ts.truncate(after='1/9/2017') #將'1/9/2017'之後的資料擷取掉,取'1/9/2017'之前的資料
#輸出結果如下:
# 2017-01-02 -0.529299
# 2017-01-05 1.041631
# 2017-01-07 -0.791962
# 2017-01-08 -0.774595
#(7)上面這些操作對DataFrame也有效,例如,對DataFrmae的行進行索引。
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'])
print long_df.ix['5-2017']
帶有重複索引的時間序列:
在某些應用場景中,可能會存在多個觀測資料落在同一個時間點上的情況。
from datetime import datetime
import pandas as pd
from pandas import DataFrame,Series
import numpy as np
#下面就是一個例子
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)
print dup_ts
#輸出結果如下:
# 2017-01-01 0
# 2017-01-02 1
# 2017-01-02 2
# 2017-01-02 3
# 2017-01-03 4
# dtype: int64
#(1)通過檢查索引的is_unique屬性,我們就可以知道它是不是唯一的:
print dup_ts.is_unique #輸出:True,它這個是檢視整條資料的
#(2)對這個時間序列進行索引,要麼產生標量值,要麼產生切片,具體要看所選的時間點是否重複:
print dup_ts['1/3/2017'] #不重複
print dup_ts['1/2/2017'] #重複
#輸出結果如下:
# 2017-01-02 1
# 2017-01-02 2
# 2017-01-02 3
#(3)假設你想要對具有非唯一時間戳的資料進行聚合。一個辦法是使用groupby,並傳入level=0(索引的唯一一層)
grouped=dup_ts.groupby(level=0)
print grouped.mean()
print grouped.count()
#輸出結果如下:
# 2017-01-01 0
# 2017-01-02 2
# 2017-01-03 4
# dtype: int64
# 2017-01-01 1
# 2017-01-02 3
# 2017-01-03 1
# dtype: int64
3.日期的範圍、頻率以及移動:
pandas中的時間序列一般被認為是不規則的,也就是說,它們沒有固定的頻率。
對於大部分應用程式而言,這是無所謂的。但是,它常常需要以某種相對固定的頻率進行分析,比如每日、
每月、每15分鐘等(這樣自然會在時間序列中引入缺失值)。幸運的是,pandas有一整套標準時間序列頻率
以及用於重取樣、頻率推斷、生成固定頻率日期範圍的工具。
生成日期範圍:
from datetime import datetime
import pandas as pd
from pandas import DataFrame,Series
import numpy as np
#(1) pandas.date_range可用於生成指定長度的DatetimeIndex:
index=pd.date_range('4/1/2017','4/5/2017')
print index
#輸出結果如下:freq='D'表示具有固定頻率的時間序列。
# DatetimeIndex(['2017-04-01', '2017-04-02', '2017-04-03', '2017-04-04',
# '2017-04-05'],
# dtype='datetime64[ns]', freq='D')
#(2)預設情況下,date_range會產生按天計算的時間點。如果只傳入起始或結束日期,那就還得傳入一個表示一段時間的數字。
print pd.date_range(start='4/1/2017',periods=10)
#輸出結果如下:
# DatetimeIndex(['2017-04-01', '2017-04-02', '2017-04-03', '2017-04-04',
# '2017-04-05', '2017-04-06', '2017-04-07', '2017-04-08',
# '2017-04-09', '2017-04-10'],
# dtype='datetime64[ns]', freq='D')
print pd.date_range(end='4/10/2017',periods=8)
#輸出結果如下:
# DatetimeIndex(['2017-04-03', '2017-04-04', '2017-04-05', '2017-04-06',
# '2017-04-07', '2017-04-08', '2017-04-09', '2017-04-10'],
# dtype='datetime64[ns]', freq='D')
#(3)起始和結束日期定義了日期索引的嚴格邊界。例如,如果你想要生成一個由每月最後一個工作日組成的日期索引,可以傳入
# "BM"頻率,這樣就只會包含時間間隔內(或剛好在邊界上的)符合頻率要求的日期:
print 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預設會保留起始和結束時間戳的時間資訊(如果有的話):
print pd.date_range('5/2/2017 12:56:31',periods=5)
#輸出結果如下:
# DatetimeIndex(['2017-05-02 12:56:31', '2017-05-03 12:56:31',
# '2017-05-04 12:56:31', '2017-05-05 12:56:31',
# '2017-05-06 12:56:31'],
# dtype='datetime64[ns]', freq='D')
#(4)有時,雖然起始和結束日期帶有時間資訊,但你希望產生一組被規範化到午夜的時間戳。
#normalize選項即可實現該功能:
print pd.date_range('5/2/2017 12:56:31',periods=5,normalize=True)
#輸出結果如下:
# DatetimeIndex(['2017-05-02', '2017-05-03', '2017-05-04', '2017-05-05',
# '2017-05-06'],
# dtype='datetime64[ns]', freq='D')
頻率和日期偏移量:
pandas中的頻率是由一個基礎頻率(base frequency)和一個乘陣列成。基礎頻率通常以一個字串別名表示,比如‘M’表示每月,
‘H’表示每小時。對於每個基礎頻率都有一個被稱為日期偏移量的物件與之對應。例如,按小時計算的頻率可以用Hour類表示。
from datetime import datetime
import pandas as pd
from pandas import DataFrame,Series
import numpy as np
from pandas.tseries.offsets import Hour,Minute
hour=Hour()
print hour #輸出結果:<Hour>
#(1)傳入一個整數即可定義偏移量的倍數:
four_hours=Hour(4)
print four_hours #輸出:<4 * Hours>
#(2)一般來說,無需顯式建立這樣的物件,只需使用諸如"H"或"4H"這樣的字串別名即可。在基礎頻率前面放上一個整數即可建立倍數:
print pd.date_range('1/1/2018','1/3/2018',freq='4h')
#輸出結果如下:
# DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 04:00:00',
# '2018-01-01 08:00:00', '2018-01-01 12:00:00',
# '2018-01-01 16:00:00', '2018-01-01 20:00:00',
# '2018-01-02 00:00:00', '2018-01-02 04:00:00',
# '2018-01-02 08:00:00', '2018-01-02 12:00:00',
# '2018-01-02 16:00:00', '2018-01-02 20:00:00',
# '2018-01-03 00:00:00'],
# dtype='datetime64[ns]', freq='4H') #4H是每隔4Hours建立一個數
#(3)大部分偏移量物件都可通過加法進行連線:
print Hour(2)+Minute(30) #輸出結果:<150 * Minutes>
#同理,你也可以傳入頻率字串(如'2h30min),這種字串可以被高效地解析為等效的表示式:
print pd.date_range('1/1/2018',periods=10,freq='1h30min')
#輸出結果如下:
# DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 01:30:00',
# '2018-01-01 03:00:00', '2018-01-01 04:30:00',
# '2018-01-01 06:00:00', '2018-01-01 07:30:00',
# '2018-01-01 09:00:00', '2018-01-01 10:30:00',
# '2018-01-01 12:00:00', '2018-01-01 13:30:00'],
# dtype='datetime64[ns]', freq='90T')
#(4)有些頻率所描述的時間點並不是均勻分隔的。例如,"M"(日曆月末)和"BM"(每月最後一個工作日)就取決於每月的天數,
#對於後者,還要考慮月末是不是週末。由於沒有列好的術語,我將這些稱為錨點偏移量。
時間序列的基礎頻率
別名 偏移量型別 說明
D Day 每日曆日
B BussinessDay 每工作日
H Hour 每小時
T或min Minute 每分
S Second 每秒
L或ms Milli 每毫秒(即每千分之一秒)
U Micro 每微秒(即每百萬分之一秒)
M MonthEnd 每月最後一個日曆日
BM BusinessMonthEnd 每月最後一個工作日
MS MonthBegin 每月第一個日曆日
BMS BusinessMothBegin 每月第一個工作日
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... BusinessQuarterEnd 對於以指定月份結束的年度,每季度最後一月的最後一個工作日
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日期:
from datetime import datetime
import pandas as pd
from pandas import DataFrame,Series
import numpy as np
#WOM日期
#WOM(Week Of Month)是一種非常實用的頻率類。它以WOM開頭。它使你能獲得諸如"每月第3個星期五"之類的日期:
rng=pd.date_range('1/1/2017','9/1/2017',freq='WOM-3FRI')
print rng
#輸出結果如下:若要將Datetime改成List輸出,則list(rng)
# DatetimeIndex(['2017-01-20', '2017-02-17', '2017-03-17', '2017-04-21',
# '2017-05-19', '2017-06-16', '2017-07-21', '2017-08-18'],
# dtype='datetime64[ns]', freq='WOM-3FRI')
#美國的股票期權交易人會意識到這些日子就是標準的月度到期日
移動(超前和洩後)資料: