pandas之時間序列

對於時間序列的處理在數據處理方面還是比較重要的一塊。這篇來記錄一下時間序列的一些知識點。

python標準庫包含日期(date)和時間(time)數據的數據類型。

經常使用的也就是datetime、time以及calendar模塊。datetime以毫秒形式存儲日期和時間。

datetime模塊中的數據類型
date以公曆形式存儲日曆日期(年、月、日)
time將時間存儲爲時、分、秒、毫秒
datetime存儲日期和時間
timedelta表示兩個datetime值之間的差(日、秒、毫秒)
In [5]: from datetime import datetime
In [6]: now = datetime.now()
In [7]: now
Out[7]: datetime.datetime(2018, 6, 25, 14, 38, 8, 361612)
In [9]: now.year
Out[9]: 2018
In [10]: now.month
Out[10]: 6
In [11]: now.day
Out[11]: 25

datetime.timedelta表示兩個datetime對象之間的時間差。給datetime對象加上或減去一個或多個timedelta,會產生一個新的對象。

In [13]: delta = now - datetime(1995,6,26,20,10)
In [14]: delta
Out[14]: datetime.timedelta(8399, 66488, 361612)
In [15]: delta.days
Out[15]: 8399
In [16]: delta.seconds
Out[16]: 66488
字符串和datetime之間的轉換

利用str 或strftime(傳入一個格式化的字符串) 將日期轉換爲字符串,

利用 datetime.strptime 將字符串轉換爲日期,這種方式是已知格式對日期解析的最佳方式。

In [17]: str(now)
Out[17]: '2018-06-25 14:38:08.361612'

In [18]: now.strftime('%Y-%m-%d')
Out[18]: '2018-06-25'

In [19]: str_date = '2018-06-25'
In [20]: datetime.strptime(str_date,'%Y-%m-%d')
Out[20]: datetime.datetime(2018, 6, 25, 0, 0)

datetime 格式定義:

%Y

4位數的年

%y

2位數的年

%m

2位數的月[01,12]

%d

2位數的日[01,31]

%H

時(24小時制)[00,23]

%l

時(12小時制)[01,12]

%M

2位數的分[00,59]

%S

秒[00,61]有閏秒的存在

%w

用整數表示的星期幾[0(星期天),6]

%F

%Y-%m-%d簡寫形式例如,2017-06-27

%D

%m/%d/%y簡寫形式

一些第三方庫也有對時間的解析:

In [21]: from dateutil.parser import parse
In [22]: parse(str_date)
Out[22]: datetime.datetime(2018, 6, 25, 0, 0)

有些日期日出現在月前面傳入  dayfirst=True 即可解決:

In [23]: parse('25/6/2018',dayfirst=True)
Out[23]: datetime.datetime(2018, 6, 25, 0, 0)

pandas 中對日期解析的方法 to_datetime 可以解析多種不同類型的日期表示形式:

 In [25]: date =  ['2018-6-26', '2018-6-25']
In [26]: pd.to_datetime(date)
Out[26]: DatetimeIndex(['2018-06-26', '2018-06-25'], dtype='dat
etime64[ns]', freq=None)

pandas中的時間序列基礎

In [51]: dates = ['2018-06-20','2018-06-21','2018-06-22','2018
    ...: -06-23','2018-06-24','2018-06-25','2018-06-26','2018-
    ...: 06-27']
In [52]: ts = pd.Series(np.random.randn(8),index=pd.to_datetim
    ...: e(dates))
In [53]: ts
Out[53]: 
2018-06-20    0.116617
2018-06-21   -0.160468
2018-06-22   -0.457980
2018-06-23   -0.994871
2018-06-24    1.028115
2018-06-25   -0.223018
2018-06-26    1.264067
2018-06-27    0.054394
dtype: float64
In [54]: ts.index
Out[54]: 
DatetimeIndex(['2018-06-20', '2018-06-21', '2018-06-22', '2018-06-23','2018-06-24', '2018-06-25',
'2018-06-26', '2018-06-27'],dtype='datetime64[ns]', freq=None)

pandas不同索引的時間序列之間的算術運算會自動按日期對齊

In [56]: ts + ts[::2]
Out[56]: 
2018-06-20    0.233233
2018-06-21         NaN
2018-06-22   -0.915960
2018-06-23         NaN
2018-06-24    2.056230
2018-06-25         NaN
2018-06-26    2.528134
2018-06-27         NaN
dtype: float64
時間序列的索引、選取方法:

1)一般索引,傳入對應的index

In [57]: ts[ts.index[3]]
Out[57]: -0.9948705675890571

2)一個可以被解析爲日期的字符串

In [58]: ts['2018-06-26 ']
Out[58]: 1.264066795280468

3)對於,較長的時間序列,只需傳入‘年’或‘年月’可返回對應的數據切片

In [59]: ts['2018-06':]
Out[59]: 
2018-06-20    0.116617
2018-06-21   -0.160468
2018-06-22   -0.457980
2018-06-23   -0.994871
2018-06-24    1.028115
2018-06-25   -0.223018
2018-06-26    1.264067
2018-06-27    0.054394
dtype: float64
4)通過時間範圍進行切片索引
In [60]: ts['2018-06-21':'2018-06-25']
Out[60]: 
2018-06-21   -0.160468
2018-06-22   -0.457980
2018-06-23   -0.994871
2018-06-24    1.028115
2018-06-25   -0.223018
dtype: float64
日期的範圍、頻率及移動

       有的時候我們需要對包含時間的數據按照相對固定的時間頻率進行分析,因此pandas中就提供了一套標準時間序列頻率以及用於重採樣、頻率推斷、生成固定頻率日期範圍的工具。

生成日期範圍:pandas.date_arnge 生成指定長度的 DatetimeIndex

pandas.date_range(start=None, end=None, periods=None, freq='D',
                   tz=None, normalize=False, name=None, closed=None, **kwargs)
           start : str or datetime-like, optional Left bound for generating dates.
             end : str or datetime-like, optional Right bound for generating dates.
         periods : integer, optional Number of periods to generate.
            freq : str or DateOffset, default ‘D’ (calendar daily) Frequency strings can
                   have multiples, e.g. ‘5H’. See here for a list of frequency aliases.
             tz : str or tzinfo, optional Time zone name for returning localized DatetimeIndex, for example
                  ‘Asia/Hong_Kong’. By default, the resulting DatetimeIndex is timezone-naive.
      normalize : bool, default False Normalize start/end dates to midnight before generating date range.
           name : str, default None Name of the resulting DatetimeIndex.
         closed : {None, ‘left’, ‘right’}, optional Make the interval closed with respect to the given
                  frequency to the ‘left’, ‘right’, or both sides (None, the default).
        **kwargs  For compatibility. Has no effect on the result.

In [188]: date_index = pd.date_range(start='2018-6-27',periods=6)
In [189]: date_index
Out[189]:
DatetimeIndex(['2018-06-27', '2018-06-28', '2018-06-29', '2018-06-30',
               '2018-07-01', '2018-07-02'],dtype='datetime64[ns]', freq='D')
In [190]: date_index = pd.date_range(start='2018-6-27 14:31',periods=6)  # 帶時間的保留時間
In [191]: date_index
Out[191]: 
DatetimeIndex(['2018-06-27 14:31:00', '2018-06-28 14:31:00',
               '2018-06-29 14:31:00', '2018-06-30 14:31:00',
               '2018-07-01 14:31:00', '2018-07-02 14:31:00'],
              dtype='datetime64[ns]', freq='D')

In [192]: date_index = pd.date_range(start='2018-6-27 14:31',end='2019-01-01',freq='BM',normalize=True)
In [193]: date_index                               # 生成日期的頻率參數  後面會學到各種時間頻率
Out[193]: 
DatetimeIndex(['2018-06-29', '2018-07-31', '2018-08-31', '2018-09-28',
               '2018-10-31', '2018-11-30', '2018-12-31'],
               dtype='datetime64[ns]', freq='BM')
In [194]: date_index = pd.date_range(start='2018-6-27 14:31',periods=3,normalize=True) 
In [195]: date_index                         # 保留時間到午夜 好像不咋管用,就是不顯示時間了唄
Out[195]: DatetimeIndex(['2018-06-27', '2018-06-28', '2018-06-29'], dtype='datetime64[ns]', freq='D')
頻率和日期偏移

日期偏移對象在 pandas.tseries.offsets 之中

In [200]: import pandas.tseries.offsets as offs
In [201]: offs.Hour()
Out[201]: <Hour>
In [202]: offs.Hour(4)  # 4h 的偏移量
Out[202]: <4 * Hours>
In [203]: pd.date_range(start='2018-6-27 14:00:00',periods=3,freq='4h')  # 這種方式可以
Out[203]: 
DatetimeIndex(['2018-06-27 14:00:00', '2018-06-27 18:00:00',
               '2018-06-27 22:00:00'],
              dtype='datetime64[ns]', freq='4H')
In [204]: pd.date_range(start='2018-6-27 14:00:00',periods=3,freq='1h30min')  # 這種也可以
Out[204]: 
DatetimeIndex(['2018-06-27 14:00:00', '2018-06-27 15:30:00',
               '2018-06-27 17:00:00'],
              dtype='datetime64[ns]', freq='90T')

下面是pandas中的頻率代碼和日期偏移量類型:

別名偏移量類型說明
DDay每日曆日
BBusinessDay每工作日
HHour每小時
T/minMinute每分
SSecond每秒
L/msMillion每毫秒
UMicro每微妙
MMonthEnd每月最後一個日曆日
BMBusinessMonthEnd每月最後一個工作日
MSMonthBegin每月第一個日曆日
BMSBusinessMonthBegin每月第一個工作日
W-MON、W-TUE…Week從指定的星期幾開始算起,每週
WOM-1MON、WOM-2MON…WeekOfMonth產生每月第一、二、三、四周的星期幾,例如WOM-1MON表示每月的第一個星期一
Q-JAN、Q-FEB…QuarterEnd對於以指定月份(JAN、FEB、…、DEC)結束的年度,每季度的最後一月的最後一個日曆日
BQ-JAN、BQ-FEB…BusinessQuarterEnd對於以指定月份(JAN、FEB、…、DEC)結束的年度,每季度的最後一月的最後一個工作日
QS-JAN、QS-FEB…QuarterBegin對於以指定月份(JAN、FEB、…、DEC)結束的年度,每季度的最後一月的第一個日曆日
BQS-JAN、BQS-FEB…BusinessQuarterBegin對於以指定月份(JAN、FEB、…、DEC)結束的年度,每季度的最後一月的第一個工作日
A-JAN、A-FEB…YearEnd每年指定月份最後一個日曆日
BA-JAN、BA-FEB…BusinessYearEnd每年指定月份最後一個工作日
AS-JAN、AS-FEB…YearBegin每月指定月份第一個日曆日
BAS-JAN、BAS-FEB…BusinessYearBegin每月指定月份第一個工作日
In [206]: rng = pd.date_range('2018-1-1','2018-6-27',freq='WOM-3FRI')   # 每月第三個星期五
In [207]: rng
Out[207]: 
DatetimeIndex(['2018-01-19', '2018-02-16', '2018-03-16', '2018-04-20',
               '2018-05-18', '2018-06-15'],
               dtype='datetime64[ns]', freq='WOM-3FRI')
移動數據: shift,rollforward,rollback
In [209]: ts = pd.Series(np.random.randn(4),index=pd.date_rang e('1/1/2018', periods=4, freq='M'))
In [210]: ts
Out[210]: 
2018-01-31   -0.266415
2018-02-28    1.328264
2018-03-31   -0.938386
2018-04-30    1.874953
Freq: M, dtype: float64

In [211]: ts.shift(2)  # 前移
Out[211]: 
2018-01-31         NaN
2018-02-28         NaN
2018-03-31   -0.266415
2018-04-30    1.328264
Freq: M, dtype: float64

In [212]: ts.shift(-2)  # 後移
Out[212]: 
2018-01-31   -0.938386
2018-02-28    1.874953
2018-03-31         NaN
2018-04-30         NaN
Freq: M, dtype: float64

In [213]: ts.shift(2,freq='M')  # 帶頻率的移動
Out[213]: 
2018-03-31   -0.266415
2018-04-30    1.328264
2018-05-31   -0.938386
2018-06-30    1.874953
Freq: M, dtype: float64
shift 通常用於計算一個或多個時間序列的百分比變化: ts/ts.shift( ) - 1
如果加的是錨點偏移量(比如 MonthEnd 當月月底)第一次增量會將原日期向前滾動到符合頻率規則的下一個日期:
In [214]: now = datetime.now()
In [215]: now
Out[215]: datetime.datetime(2018, 6, 26, 15, 30, 30, 259357)
In [216]: now + offs.MonthEnd()
Out[216]: Timestamp('2018-06-30 15:30:30.259357')
In [217]: off = offs.MonthEnd()
In [218]: off.rollforward(now)
Out[218]: Timestamp('2018-06-30 15:30:30.259357')
In [219]: off.rollback(now)
Out[219]: Timestamp('2018-05-31 15:30:30.259357')

這裏還有個巧妙的用法,結合groupby 和滾動方法 :

In [223]: ts = pd.Series(np.random.randn(20),index=pd.date_range('1/15/2018', periods=20, freq='4d'))
In [224]: ts
Out[224]: 
2018-01-15   -0.071915
2018-01-19    2.403895
............
2018-04-01    1.046591
Freq: 4D, dtype: float64
In [225]: ts.groupby(off.rollforward).mean()
Out[225]: 
2018-01-31   -0.351028
2018-02-28   -0.495122
2018-03-31   -0.457727
2018-04-30    1.046591
更簡單的方式:
In [229]: ts.resample('M').mean()
Out[229]: 
2018-01-31   -0.351028
2018-02-28   -0.495122
2018-03-31   -0.457727
2018-04-30    1.046591
Freq: M, dtype: float64

重採樣及頻率轉換

重採樣(resampling)指的是將時間序列從一個頻率轉換到另一頻率的處理過程。

resample 參數:

參數 說明
freq 表示重採樣頻率,例如‘M’、‘5min’,Second(15)
how=’mean’            (已廢棄)用於產生聚合值的函數名或數組函數,例如‘mean’、‘ohlc’、np.max等,默認是‘mean’,其他常用的值由:‘first’、‘last’、‘median’、‘max’、‘min’
axis=0 默認是縱軸,橫軸設置axis=1
fill_method = None 升採樣時如何插值,比如‘ffill’、‘bfill’等
closed = ‘left’ 在降採樣時,各時間段的哪一段是閉合的,‘right’或‘left’,默認‘left’
label= ‘left’ 在降採樣時,如何設置聚合值的標籤,例如,9:30-9:35會被標記成9:30還是9:35,默認9:35
loffset = None 面元標籤的時間校正值,比如‘-1s’或Second(-1)用於將聚合標籤調早1秒
limit=None 在向前或向後填充時,允許填充的最大時期數
kind = None 聚合到時期(‘period’)或時間戳(‘timestamp’),默認聚合到時間序列的索引類型
convention = None 當重採樣時期時,將低頻率轉換到高頻率所採用的約定(start或end)。默認‘end’
In [229]: ts.resample('M').mean()
Out[229]: 
2018-01-31   -0.351028
2018-02-28   -0.495122
2018-03-31   -0.457727
2018-04-30    1.046591
Freq: M, dtype: float64

降採樣:將高頻率聚合到低頻率成爲降採樣

有一個頻率爲 1 min 的時間序列:

In [231]: rng = pd.date_range('2018-6-26',periods=10,freq='T')
In [232]: ts = pd.Series(np.random.randn(10),index=rng)
In [233]: ts
Out[233]: 
2018-06-26 00:00:00    0.616423
2018-06-26 00:01:00   -0.815095
2018-06-26 00:02:00   -0.593021
2018-06-26 00:03:00    1.388699
2018-06-26 00:04:00   -0.900769
2018-06-26 00:05:00   -1.941041
2018-06-26 00:06:00    0.264235
2018-06-26 00:07:00   -1.185064
2018-06-26 00:08:00    0.242785
2018-06-26 00:09:00    0.098842
Freq: T, dtype: float64

將時間序列聚合到 5mins :

In [253]: rng = pd.date_range('2018-6-26',periods=12,freq='T')
In [254]: ts = pd.Series(np.arange(12),index=rng)
In [255]: ts.resample('5min').sum()
Out[255]: 
2018-06-26 00:00:00    10
2018-06-26 00:05:00    35
2018-06-26 00:10:00    21
Freq: 5T, dtype: int32

In [256]: ts.resample('5min',closed='right').sum()  # 包含了右邊界,以左邊界爲標記
Out[256]: 
2018-06-25 23:55:00     0
2018-06-26 00:00:00    15
2018-06-26 00:05:00    40
2018-06-26 00:10:00    11
Freq: 5T, dtype: int32

In [258]: ts.resample('5min',closed='left').sum()   # 包含了左邊界,以左邊界爲標記
Out[258]: 
2018-06-26 00:00:00    10
2018-06-26 00:05:00    35
2018-06-26 00:10:00    21
Freq: 5T, dtype: int32

In [259]: ts.resample('5min',closed='right',label='right').sum()  # 包含了右邊界,以右邊界爲標記
Out[259]: 
2018-06-26 00:00:00     0
2018-06-26 00:05:00    15
2018-06-26 00:10:00    40
2018-06-26 00:15:00    11
Freq: 5T, dtype: int32

In [260]: ts.resample('5min',closed='right',label='left').sum()
Out[260]: 
2018-06-25 23:55:00     0
2018-06-26 00:00:00    15
2018-06-26 00:05:00    40
2018-06-26 00:10:00    11
Freq: 5T, dtype: int32

通過groupby進行重採樣

只需要傳入一個能夠訪問時間序列的索引上的阻斷的函數即可。

In [270]: rng = pd.date_range('2018-6-26',periods=100,freq='D')
In [271]: ts = pd.Series(np.arange(100),index=rng)
In [272]: ts.groupby(lambda x:x.month).mean()
Out[272]: 
6      2.0
7     20.0
8     51.0
9     81.5
10    98.0
dtype: float64

升採樣和插值

將數據從低頻轉換到高頻,就是升採樣。升採樣會引入缺失值,此時就需要插值處理。

插值方式跟 fillna 和reindex 一樣。

In [299]: frame = pd.DataFrame(np.random.randn(2, 4),index=pd.
     ...: date_range('1/1/2000', periods=2, freq='W-WED'),colu
     ...: mns=['Colorado', 'Texas', 'New York', 'Ohio'])

In [300]: frame.resample('W-THU').ffill()
Out[300]: 
            Colorado     Texas  New York      Ohio
2000-01-06  1.202063 -0.703830  0.050375 -0.158644
2000-01-13 -0.276499  2.183782  0.565678  0.584314

In [301]: frame.resample('D').ffill()
Out[301]: 
            Colorado     Texas  New York      Ohio
2000-01-05  1.202063 -0.703830  0.050375 -0.158644
2000-01-06  1.202063 -0.703830  0.050375 -0.158644
2000-01-07  1.202063 -0.703830  0.050375 -0.158644
2000-01-08  1.202063 -0.703830  0.050375 -0.158644
2000-01-09  1.202063 -0.703830  0.050375 -0.158644
2000-01-10  1.202063 -0.703830  0.050375 -0.158644
2000-01-11  1.202063 -0.703830  0.050375 -0.158644
2000-01-12 -0.276499  2.183782  0.565678  0.584314
發佈了49 篇原創文章 · 獲贊 14 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章