天天看點

Python 資料分析:時間序列1. 日期和時間資料的類型及工具2. 時間序列基礎3. 日期範圍、頻率和移位4. 時區處理5. 時間區間和區間算數6. 重新采樣與頻率轉換7. 移動視窗函數

Python 資料分析:時間序列

  • 1. 日期和時間資料的類型及工具
      • datetime 子產品中的類型表格
    • 1.1 字元串與 datetime 互相轉換
      • datetime 格式說明(相容 ISO C89)(基礎知識部分也有該表格)
  • 2. 時間序列基礎
    • 2.1 索引、選擇、子集
    • 2.2 含有重複索引的時間序列
  • 3. 日期範圍、頻率和移位
    • 3.1 生成日期範圍
      • 部分基礎時間序列頻率表格
    • 3.2 頻率和日期偏置
    • 3.3 移位(前向和後向)日期
  • 4. 時區處理
    • 4.1 時區的本地化和轉換
    • 4.2 時區感覺時間戳對象的操作
    • 4.3 不同時區間的操作
  • 5. 時間區間和區間算數
    • 5.1 區間頻率轉換
    • 5.2 季度區間頻率
    • 5.3 時間戳與區間的轉換
    • 5.4 從數組生成 PeriodIndex
  • 6. 重新采樣與頻率轉換
    • 6.1 向下采樣
    • 6.2 向上采樣與插值
    • 6.3 使用區間進行重新采樣
  • 7. 移動視窗函數
    • 7.1 指數權重函數
    • 7.2 二進制移動視窗函數
    • 7.3 使用者自定義的移動視窗函數

1. 日期和時間資料的類型及工具

  • Python 的标準庫包含日期、時間、月曆等相關子產品。其中 datetime, time, calender 是處理時間資料的主要内容。datetime.datetime 類是最常用的
  • datetime.now() 擷取目前的時間
import pandas as pd
import numpy as np
from datetime import datetime
now = datetime.now()
now
           
datetime.datetime(2019, 8, 11, 14, 17, 29, 402021)
           
  • datetime 既存儲了日期,也存儲了細化到微秒的時間
now.year, now.month, now.day
           
(2019, 8, 11)
           
  • timedelta 表示兩個 datetime 對象的時間差
delta = datetime(2019, 1, 1) - datetime(2018, 8, 8, 10, 10)
print(delta, delta.days, delta.seconds, sep='\n')
           
145 days, 13:50:00
145
49800
           
  • 為 datetime 對象加上(或減去)一個 timedelta 或其整數倍來産生一個新的 datetime 對象
from datetime import timedelta
start = datetime(2019, 8, 1)
start + timedelta(12) * 2
           
datetime.datetime(2019, 8, 25, 0, 0)
           

datetime 子產品中的類型表格

類型 描述
date 使用公曆月曆存儲月曆日期(年月日)
time 将時間存儲為小時、分鐘、秒和微秒
datetime 存儲日期和時間
timedelta 表示兩個 datetime 值之間的差(如日、秒和微秒)
tzinfo 用于存儲時區資訊的基本類型

1.1 字元串與 datetime 互相轉換

  • 使用 str 方法或傳遞一個指定的格式給 strftime 方法來對 datetime 對象和 pandas 的 Timestamp 對象進行格式化
stamp = datetime(2019, 8, 1)
print(str(stamp))
print(stamp.strftime('%Y-%m-%d'))
           
2019-08-01 00:00:00
2019-08-01
           
  • 使用 datetime.strptime 将字元串轉換為日期
value = '2019-08-01'
print(datetime.strptime(value, '%Y-%m-%d'))
datestrs = ['8/1/2019', '8/2/2019']
print([datetime.strptime(x, '%m/%d/%Y') for x in datestrs])
           
2019-08-01 00:00:00
[datetime.datetime(2019, 8, 1, 0, 0), datetime.datetime(2019, 8, 2, 0, 0)]
           
  • 使用 dateutil 的 parser.parse 方法解析大部分人類可了解的日期表示
from dateutil.parser import parse
print(parse('2019-08-01'))
print(parse('Aug 1, 2019 10:00 PM'))
print(parse('1/8/2019', dayfirst=True))    # dayfirst=True 表明日期在月份之前
           
2019-08-01 00:00:00
2019-08-01 22:00:00
2019-08-01 00:00:00
           
  • 使用 pandas.to_datetime 将日期表示轉換為 DataFrame 的索引
datestrs = ['2019-08-01 12:00:00', '2019-08-02 00:00:00']
print(pd.to_datetime(datestrs))
# to_datetime 還可以處理缺失值
idx = pd.to_datetime(datestrs + [None])
print(idx)
print(pd.isnull(idx))
           
DatetimeIndex(['2019-08-01 12:00:00', '2019-08-02 00:00:00'], dtype='datetime64[ns]', freq=None)
DatetimeIndex(['2019-08-01 12:00:00', '2019-08-02 00:00:00', 'NaT'], dtype='datetime64[ns]', freq=None)
[False False  True]
           

datetime 格式說明(相容 ISO C89)(基礎知識部分也有該表格)

https://blog.csdn.net/u012470887/article/details/92431590

類型 描述
%Y 四位的年份
%y 兩位的年份
%m 兩位的月份[01, 12]
%d 兩位的天數值 [01, 31]
%H 小時值(24 小時制)[00, 23]
%I 小時值(12 小時制)[01, 12]
%M 兩位的分鐘值 [00, 59]
%S 秒值 [00, 61] (60, 61用于區分閏秒)
%w 星期值 [0(星期天), 6]
%U 一年中的第幾個星期值 [00, 53],星期天為每周第一天,第一個星期天前一周是第0周
%W 一年中的第幾個星期值 [00, 53],星期一為每周第一天,第一個星期一前一周是第0周
%z UTC 時區偏置,格式為 +HHMM 或 -HHMM,如果是簡單時區則為空
%F %Y-%m-%d 的簡寫
%D %m/%d/%y 的簡寫

2. 時間序列基礎

  • pandas 中的基礎時間序列種類是由時間戳索引的 Series, 在 pandas 外部則通常表示為 Python 字元串或 datetime 對象
from datetime import datetime
dates = [datetime(2019, 8, 1), datetime(2019, 8, 5),
        datetime(2019, 8, 7), datetime(2019, 8, 8),
        datetime(2019, 8, 10), datetime(2019, 8, 12)]
ts = pd.Series(np.random.randn(6), index=dates)
ts
           
2019-08-01    0.468772
2019-08-05    2.333443
2019-08-07   -0.502920
2019-08-08    1.145697
2019-08-10   -0.504389
2019-08-12   -1.343926
dtype: float64
           
  • 這種情況下,這些 datetime 對象可以被放入 DatetimeIndex 中
ts.index
           
DatetimeIndex(['2019-08-01', '2019-08-05', '2019-08-07', '2019-08-08',
               '2019-08-10', '2019-08-12'],
              dtype='datetime64[ns]', freq=None)
           
  • 和其他 Series 一樣,不同索引的時間序列之間的算術運算在日期上自動對齊
2019-08-01    0.937543
2019-08-05         NaN
2019-08-07   -1.005840
2019-08-08         NaN
2019-08-10   -1.008777
2019-08-12         NaN
dtype: float64
           
  • DatetimeIndex 中的标量值是 pandas 的 Timestamp 對象
stamp = ts.index[0]
stamp
           
Timestamp('2019-08-01 00:00:00')
           

2.1 索引、選擇、子集

  • 基于标簽進行索引和選擇時,時間序列的行為和其他的 pandas.Series 類似
stamp = ts.index[2]
s[stamp]
           
-0.5029199053879977
           
  • 為了友善,可以傳遞一個能被解釋為日期的字元串
print(ts['8/1/2019'])
print(ts['20190801'])
           
0.46877169482728676
0.46877169482728676
           
  • 對于一個長的時間序列,可以傳遞一個年份或一個年份和月份來選擇資料的切片
longer_ts = pd.Series(np.random.randn(1000), 
                     index=pd.date_range('1/1/2017', periods=1000))
print(longer_ts[:10], longer_ts['2018'][:10], longer_ts['2018-05'][:10], sep='\n' + '*' * 10 + '\n')
           
2017-01-01   -1.000006
2017-01-02   -0.012311
2017-01-03    1.290370
2017-01-04   -1.411002
2017-01-05   -1.043768
2017-01-06   -2.026386
2017-01-07    0.923300
2017-01-08   -1.159909
2017-01-09   -0.340406
2017-01-10   -2.195334
Freq: D, dtype: float64
**********
2018-01-01   -0.614101
2018-01-02    0.034989
2018-01-03   -0.816531
2018-01-04   -0.025899
2018-01-05    1.499072
2018-01-06   -0.025201
2018-01-07   -1.936647
2018-01-08    0.748457
2018-01-09   -0.159147
2018-01-10   -1.762938
Freq: D, dtype: float64
**********
2018-05-01    1.912698
2018-05-02   -0.614768
2018-05-03    3.010737
2018-05-04   -0.828237
2018-05-05    0.866249
2018-05-06   -0.035166
2018-05-07   -1.222878
2018-05-08    0.541532
2018-05-09    1.337297
2018-05-10   -1.633647
Freq: D, dtype: float64
           
  • 也可以使用 datetime 對象進行切片,由于大部分時間序列資料是按時間順序排序的,是以可以使用不包含在時間序列中的時間戳進行切片,以執行範圍查詢
2019-08-05    2.333443
2019-08-07   -0.502920
2019-08-08    1.145697
2019-08-10   -0.504389
dtype: float64
           
  • 使用 truncate() 方法也可以實作在兩個日期間對 Series 進行切片
2019-08-05    2.333443
2019-08-07   -0.502920
2019-08-08    1.145697
dtype: float64
           
  • 上面的操作也同樣适用于 DataFrame,并在其行上進行索引
dates = pd.date_range('1/1/2018', periods=100, freq='W-WED')
long_df = pd.DataFrame(np.random.randn(100, 4),
                      index=dates,
                      columns=['Colorado', 'Texas', 'Nwe York', 'Ohio'])
long_df[:10]
           
Colorado Texas Nwe York Ohio
2018-01-03 -0.865582 1.623844 -0.200563 -1.166387
2018-01-10 0.441938 1.022298 -0.834719 -0.125343
2018-01-17 -0.767586 -0.434506 0.008906 -0.359749
2018-01-24 0.151662 -0.904767 -1.224332 1.169810
2018-01-31 -0.172146 -0.495993 0.841341 -0.080990
2018-02-07 0.485385 -1.327956 0.144750 0.294078
2018-02-14 -0.071519 1.098334 -0.923484 -1.466727
2018-02-21 -0.482320 -0.122691 -0.719345 -1.044682
2018-02-28 0.160908 1.044807 -0.020349 -0.426059
2018-03-07 -0.009414 0.081428 -0.523577 1.182005
Colorado Texas Nwe York Ohio
2018-05-02 -1.519427 -0.158811 1.191555 1.005464
2018-05-09 -0.704590 -0.332483 0.096710 -0.031565
2018-05-16 0.062633 0.375264 -0.321858 -0.776452
2018-05-23 -1.657576 -0.445090 -0.718661 -1.409694
2018-05-30 0.153913 0.402754 1.435081 -0.883503

2.2 含有重複索引的時間序列

  • 對于有些資料,可能在某個特定的時間戳上有多個資料,導緻索引重複,可以調用索引對象的 is_unique 屬性檢視是否重複,對于不重複的索引,其索引的結果為标量值,對于重複的索引,其索引結果為 Series 的切片
dates = pd.DatetimeIndex(['1/1/2019', '1/2/2019', '1/2/2019', '1/2/2019', '1/3/2019'])
dup_ts = pd.Series(np.arange(5), index=dates)
print(dup_ts, dup_ts.index.is_unique, dup_ts['1/3/2019'], dup_ts['1/2/2019'], sep='\n' + '*' * 10 + '\n')
           
2019-01-01    0
2019-01-02    1
2019-01-02    2
2019-01-02    3
2019-01-03    4
dtype: int32
**********
False
**********
4
**********
2019-01-02    1
2019-01-02    2
2019-01-02    3
dtype: int32
           
  • 聚合含有非唯一時間戳的資料的一種方法是使用 groupby 并傳遞 level=0
grouped = dup_ts.groupby(level=0)
print(grouped, grouped.mean(), grouped.count(), sep='\n' + '*' * 10 + '\n')
           
<pandas.core.groupby.generic.SeriesGroupBy object at 0x000001CD6D385668>
**********
2019-01-01    0
2019-01-02    2
2019-01-03    4
dtype: int32
**********
2019-01-01    1
2019-01-02    3
2019-01-03    1
dtype: int64
           

3. 日期範圍、頻率和移位

  • pandas 的通用時間序列很多是頻率不定的,有時候需要将其轉化為固定頻率的資料,是以需要進行重新采樣、推斷頻率以及生成固定頻率的資料範圍,本節先學習如何使用基礎頻率及其倍數

3.1 生成日期範圍

  • 之前的示例中出現了 pd.date_range() 方法,其作用是根據特定頻率生成指定長度的 DatetimeIndex 對象,預設生成每日的時間戳
index = pd.date_range('2019-04-01', '2019-05-01')
print(index)
           
DatetimeIndex(['2019-04-01', '2019-04-02', '2019-04-03', '2019-04-04',
               '2019-04-05', '2019-04-06', '2019-04-07', '2019-04-08',
               '2019-04-09', '2019-04-10', '2019-04-11', '2019-04-12',
               '2019-04-13', '2019-04-14', '2019-04-15', '2019-04-16',
               '2019-04-17', '2019-04-18', '2019-04-19', '2019-04-20',
               '2019-04-21', '2019-04-22', '2019-04-23', '2019-04-24',
               '2019-04-25', '2019-04-26', '2019-04-27', '2019-04-28',
               '2019-04-29', '2019-04-30', '2019-05-01'],
              dtype='datetime64[ns]', freq='D')
           
  • 若隻傳遞一個起始或結尾日期,必須傳遞一個用于生成範圍的數字
DatetimeIndex(['2019-04-01', '2019-04-02', '2019-04-03', '2019-04-04',
               '2019-04-05', '2019-04-06', '2019-04-07', '2019-04-08',
               '2019-04-09', '2019-04-10', '2019-04-11', '2019-04-12',
               '2019-04-13', '2019-04-14', '2019-04-15', '2019-04-16',
               '2019-04-17', '2019-04-18', '2019-04-19', '2019-04-20'],
              dtype='datetime64[ns]', freq='D')
           
  • 若傳入的時間戳包含時間資料,則預設保留該資料至生成的時間索引中,若想要将其标準化為零點,則隻需傳入 normalize=True
print(pd.date_range('2019-05-02 12:56:31', periods=5))
print('*' * 30)
print(pd.date_range('2019-05-02 12:56:31', periods=5, normalize=True))
           
DatetimeIndex(['2019-05-02 12:56:31', '2019-05-03 12:56:31',
               '2019-05-04 12:56:31', '2019-05-05 12:56:31',
               '2019-05-06 12:56:31'],
              dtype='datetime64[ns]', freq='D')
******************************
DatetimeIndex(['2019-05-02', '2019-05-03', '2019-05-04', '2019-05-05',
               '2019-05-06'],
              dtype='datetime64[ns]', freq='D')
           

部分基礎時間序列頻率表格

别名 偏置類型 描述
D Day 月曆日的每天
B BusinessDay 工作日的每天
H Hour 每小時
T 或 min Minute 每分鐘
S Second 每秒
L 或 ms Milli 每毫秒
U Micro 每微秒
M MonthEnd 月曆日的每個月底日期
BM BusinessMonthEnd 工作日的每個月底日期
MS MonthBegin 月曆日的每個月初日期
BMS BusinessMonthBegin 工作日的每個月初日期
W-MON, W-TUE… Week 每周幾(MON, TUE, WED, THU, FRI, SAT, SUN)
WOM-1MON, WOM-2MON… WeekOfMonth 每月的第幾個周幾(如 WOM-3FRI 代表每月的第三個星期五)
Q-JAN, Q-FEB… QuarterEnd 每季度,且指定月最後一個月曆日為季度結束日(如 Q-JAN 的每個季度分别為上一年的 11 月到本年的 1 月,本年的 2 月到 4 月,以此類推;JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC)
BQ-JAN, BQ-FEB… BusinessQuarterEnd 每季度,且指定月最後一個工作日為季度結束日
QS-JAN, QS-FEB… QuarterBegin 每季度,且指定月第一個月曆日為季度開始日
BQS-JAN, BQS-FEB… BusinessQuarterBegin 每季度,且指定月第一個工作日為季度開始日
A-JAN, A-FEB… YearEnd 每年,且指定月的最後一個月曆日為年度結束日
BA-JAN, BA-FEB BusinessYearEnd 每年,且指定月的最後一個工作日為年度結束日
AS-JAN, AS-FEB… YearBegin 每年,且指定月的第一個月曆日為年度開始日
BAS-JAN, BAS-FEB… BusinessYearBegin 每年,且指定月的第一個工作日為年度開始日

3.2 頻率和日期偏置

  • pandas 中的頻率是由基礎頻率和倍數組成的,對于每個基礎頻率,都有一個對象可以被用于定義日期偏置,如每小時的頻率可以使用 Hour 類來表示
from pandas.tseries.offsets import Hour, Minute
hour = Hour()
print(hour)
four_hours = Hour(4)
print(four_hours)
           
<Hour>
<4 * Hours>
           
  • 大多數時候我們使用字元串别名來表示,在基礎頻率前放一個倍數就可以生成倍數,且多個偏置可以通過加法進行聯合,或者直接使用頻率字元串
  • 有些頻率描述點的時間并不是均勻分隔的,例如’M’和‘BM’取決于當月天數,這些日期被稱為錨定偏置量
print(pd.date_range('2019-01-01', '2019-01-03 23:59', freq='4H'))
print('*' * 20)
print(Hour(2) + Minute(30))
print('*' * 20)
print(pd.date_range('2019-01-01', periods=10, freq='1h30min'))
           
DatetimeIndex(['2019-01-01 00:00:00', '2019-01-01 04:00:00',
               '2019-01-01 08:00:00', '2019-01-01 12:00:00',
               '2019-01-01 16:00:00', '2019-01-01 20:00:00',
               '2019-01-02 00:00:00', '2019-01-02 04:00:00',
               '2019-01-02 08:00:00', '2019-01-02 12:00:00',
               '2019-01-02 16:00:00', '2019-01-02 20:00:00',
               '2019-01-03 00:00:00', '2019-01-03 04:00:00',
               '2019-01-03 08:00:00', '2019-01-03 12:00:00',
               '2019-01-03 16:00:00', '2019-01-03 20:00:00'],
              dtype='datetime64[ns]', freq='4H')
********************
<150 * Minutes>
********************
DatetimeIndex(['2019-01-01 00:00:00', '2019-01-01 01:30:00',
               '2019-01-01 03:00:00', '2019-01-01 04:30:00',
               '2019-01-01 06:00:00', '2019-01-01 07:30:00',
               '2019-01-01 09:00:00', '2019-01-01 10:30:00',
               '2019-01-01 12:00:00', '2019-01-01 13:30:00'],
              dtype='datetime64[ns]', freq='90T')
           
  • 對于一個常用的頻率類,月中某星期,可以使用以 ‘WOM’ 開頭的頻率字元串
[Timestamp('2019-01-18 00:00:00', freq='WOM-3FRI'),
 Timestamp('2019-02-15 00:00:00', freq='WOM-3FRI'),
 Timestamp('2019-03-15 00:00:00', freq='WOM-3FRI'),
 Timestamp('2019-04-19 00:00:00', freq='WOM-3FRI'),
 Timestamp('2019-05-17 00:00:00', freq='WOM-3FRI'),
 Timestamp('2019-06-21 00:00:00', freq='WOM-3FRI'),
 Timestamp('2019-07-19 00:00:00', freq='WOM-3FRI'),
 Timestamp('2019-08-16 00:00:00', freq='WOM-3FRI')]
           

3.3 移位(前向和後向)日期

  • 移位是指将日期按時間向前或向後移動,Series 和 DataFrame 都有一個 shift 方法用于簡單的前向或後向移位,而不改變索引。shift 常用于計算時間序列或 DataFrame 多列時間序列的百分比變化
ts = pd.Series(np.random.randn(4),
              index=pd.date_range('1/1/2019', periods=4, freq='M'))
print(ts)
print('*' * 20)
print(ts.shift(2))
print('*' * 20)
print(ts.shift(-2))
print('*' * 20)
print(ts/ts.shift(1) - 1)
           
2019-01-31    1.768847
2019-02-28   -0.255197
2019-03-31    0.685883
2019-04-30    0.613614
Freq: M, dtype: float64
********************
2019-01-31         NaN
2019-02-28         NaN
2019-03-31    1.768847
2019-04-30   -0.255197
Freq: M, dtype: float64
********************
2019-01-31    0.685883
2019-02-28    0.613614
2019-03-31         NaN
2019-04-30         NaN
Freq: M, dtype: float64
********************
2019-01-31         NaN
2019-02-28   -1.144273
2019-03-31   -3.687666
2019-04-30   -0.105367
Freq: M, dtype: float64
           
  • 上面的這種簡單移位并不改變索引,但是一些資料會被丢棄,如果頻率是已知的,則可以将頻率傳遞給 shift 來推移時間戳而不是資料
print(ts.shift(2, freq='M'))
print('*' * 20)
print(ts.shift(3, freq='D'))
           
2019-03-31    1.768847
2019-04-30   -0.255197
2019-05-31    0.685883
2019-06-30    0.613614
Freq: M, dtype: float64
********************
2019-02-03    1.768847
2019-03-03   -0.255197
2019-04-03    0.685883
2019-05-03    0.613614
dtype: float64
           
  • pandas 日期偏置也可以使用 datetime 或 Timestamp 對象來完成
from pandas.tseries.offsets import Day, MonthEnd
now = datetime(2019, 8, 11)
print(now + 3 * Day())
           
2019-08-14 00:00:00
           
  • 如果添加了一個錨定偏置量,比如 MonthEnd,根據頻率規則,第一個增量會将日期“前滾”到下一個日期,錨定偏置可以使用其 rollforward() 方法和 rollback() 方法分别顯式地将日期向前或向後“滾動”
print(now + MonthEnd())
print(now + MonthEnd(2))
offset = MonthEnd()
print(offset.rollforward(now))
print(offset.rollback(now))
           
2019-08-31 00:00:00
2019-09-30 00:00:00
2019-08-31 00:00:00
2019-07-31 00:00:00
           
  • 将移位方法和 groupby 一起使用,可以實作重采樣,其效果和 Series 的 resample() 方法一樣
ts = pd.Series(np.random.randn(10),
              index=pd.date_range('8/1/2019', periods=10, freq='4d'))
print(ts)
print('*' * 20)
print(ts.groupby(offset.rollforward).mean())
print('*' * 20)
print(ts.resample('M').mean())
           
2019-08-01   -1.043915
2019-08-05    1.200408
2019-08-09    0.858810
2019-08-13   -1.427360
2019-08-17   -0.439623
2019-08-21    1.591962
2019-08-25    0.320857
2019-08-29   -0.842882
2019-09-02    0.857552
2019-09-06    0.498283
Freq: 4D, dtype: float64
********************
2019-08-31    0.027282
2019-09-30    0.677918
dtype: float64
********************
2019-08-31    0.027282
2019-09-30    0.677918
Freq: M, dtype: float64
           

4. 時區處理

  • 對于時區,目前的國際标準為世界協調時間或 UTC,時區通常表示為 UTC 的偏置
  • Python 中,時區資訊來源于 pytz 庫,時區名稱可以在其文檔中找到,也可以使用 pytz.timezone 得到時區對象,pandas 接收時區名稱或時區對象
import pytz
print(pytz.common_timezones[-5:])
pytz.timezone('America/New_York')
           
['US/Eastern', 'US/Hawaii', 'US/Mountain', 'US/Pacific', 'UTC']
<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>
           

4.1 時區的本地化和轉換

  • 預設情況下,pandas 中的時間序列是時區簡單型的,即索引的 tz 屬性是 None
rng = pd.date_range('3/9/2019 9:30', periods=6, freq='D')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
print(ts)
print('*' * 20)
print(ts.index.tz)
           
2019-03-09 09:30:00    1.426332
2019-03-10 09:30:00   -1.166613
2019-03-11 09:30:00   -1.142714
2019-03-12 09:30:00   -1.110601
2019-03-13 09:30:00    1.143094
2019-03-14 09:30:00    1.664323
Freq: D, dtype: float64
********************
None
           
  • 可以在定義索引的時候添加時區屬性進行本地化,也可以使用 tz_localize() 方法從簡單時區轉化到本地化時區
print(pd.date_range('3/9/2019 9:30', periods=6, freq='D', tz='UTC'))
print('*' * 20)
ts_utc = ts.tz_localize('UTC')
print(ts_utc)
print('*' * 20)
print(ts_utc.index)
           
DatetimeIndex(['2019-03-09 09:30:00+00:00', '2019-03-10 09:30:00+00:00',
               '2019-03-11 09:30:00+00:00', '2019-03-12 09:30:00+00:00',
               '2019-03-13 09:30:00+00:00', '2019-03-14 09:30:00+00:00'],
              dtype='datetime64[ns, UTC]', freq='D')
********************
2019-03-09 09:30:00+00:00    1.426332
2019-03-10 09:30:00+00:00   -1.166613
2019-03-11 09:30:00+00:00   -1.142714
2019-03-12 09:30:00+00:00   -1.110601
2019-03-13 09:30:00+00:00    1.143094
2019-03-14 09:30:00+00:00    1.664323
Freq: D, dtype: float64
********************
DatetimeIndex(['2019-03-09 09:30:00+00:00', '2019-03-10 09:30:00+00:00',
               '2019-03-11 09:30:00+00:00', '2019-03-12 09:30:00+00:00',
               '2019-03-13 09:30:00+00:00', '2019-03-14 09:30:00+00:00'],
              dtype='datetime64[ns, UTC]', freq='D')
           
  • 一旦時間序列被本地化為某個特定的時區,則可以通過 tz_convert 将其轉換為另一個時區
  • 本地化是添加 tz 屬性,時間不變,而時區轉化不僅添加了 tz 屬性,還改變了時間
  • 美國分了夏令時和冬令時,夏令時比 UTC 晚 4 小時,冬令時晚 5 小時
print(ts_utc.tz_convert('America/New_York'))
print('*' * 20)
ts_eastern = ts.tz_localize('America/New_York')
print(ts_eastern)
print('*' * 20)
print(ts_eastern.tz_convert('UTC'))
print('*' * 20)
print(ts_eastern.tz_convert('Europe/Berlin'))
           
2019-03-09 04:30:00-05:00    1.426332
2019-03-10 05:30:00-04:00   -1.166613
2019-03-11 05:30:00-04:00   -1.142714
2019-03-12 05:30:00-04:00   -1.110601
2019-03-13 05:30:00-04:00    1.143094
2019-03-14 05:30:00-04:00    1.664323
Freq: D, dtype: float64
********************
2019-03-09 09:30:00-05:00    1.426332
2019-03-10 09:30:00-04:00   -1.166613
2019-03-11 09:30:00-04:00   -1.142714
2019-03-12 09:30:00-04:00   -1.110601
2019-03-13 09:30:00-04:00    1.143094
2019-03-14 09:30:00-04:00    1.664323
Freq: D, dtype: float64
********************
2019-03-09 14:30:00+00:00    1.426332
2019-03-10 13:30:00+00:00   -1.166613
2019-03-11 13:30:00+00:00   -1.142714
2019-03-12 13:30:00+00:00   -1.110601
2019-03-13 13:30:00+00:00    1.143094
2019-03-14 13:30:00+00:00    1.664323
Freq: D, dtype: float64
********************
2019-03-09 15:30:00+01:00    1.426332
2019-03-10 14:30:00+01:00   -1.166613
2019-03-11 14:30:00+01:00   -1.142714
2019-03-12 14:30:00+01:00   -1.110601
2019-03-13 14:30:00+01:00    1.143094
2019-03-14 14:30:00+01:00    1.664323
Freq: D, dtype: float64
           
  • tz_convert 和 tz_localize 也是 DatetimeIndex 的執行個體方法
DatetimeIndex(['2019-03-09 09:30:00+08:00', '2019-03-10 09:30:00+08:00',
               '2019-03-11 09:30:00+08:00', '2019-03-12 09:30:00+08:00',
               '2019-03-13 09:30:00+08:00', '2019-03-14 09:30:00+08:00'],
              dtype='datetime64[ns, Asia/Shanghai]', freq='D')
           

4.2 時區感覺時間戳對象的操作

  • 與時間序列和日期範圍類似,單獨的 Timestamp 對象也可以從簡單時間戳本地化為時區感應時間戳,并從一個時區轉換為另一個時區
stamp = pd.Timestamp('2011-03-12 04:00')
stamp_utc = stamp.tz_localize('utc')
print(stamp_utc)
print(stamp_utc.tz_convert('America/New_York'))
stamp_moscow = pd.Timestamp('2011-03-12 04:00', tz='Europe/Moscow')
print(stamp_moscow)
           
2011-03-12 04:00:00+00:00
2011-03-11 23:00:00-05:00
2011-03-12 04:00:00+03:00
           
  • 時區感覺的 Timestamp 對象内部存儲了一個 Unix 紀元(1970/1/1)至今的納秒數量 UTC 時間戳數值,該數值在時區轉換中是不變的
print(stamp_utc.value)
print(stamp_utc.tz_convert('America/New_York').value)
           
1299902400000000000
1299902400000000000
           

4.3 不同時區間的操作

  • 如果兩個不同時區的時間序列需要聯合,那麼結果将是 UTC 時間的
rng = pd.date_range('3/7/2012 9:30', periods=10, freq='B')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
print(ts)
print('*' * 20)
ts_1 = ts[:7].tz_localize('Europe/London')
print(ts_1)
print('*' * 20)
ts_2 = ts_1[2:].tz_convert('Europe/Moscow')
print(ts_2)
print('*' * 20)
result = ts_1 + ts_2
print(result)
           
2012-03-07 09:30:00    1.024024
2012-03-08 09:30:00   -0.693508
2012-03-09 09:30:00   -1.810992
2012-03-12 09:30:00    0.526046
2012-03-13 09:30:00    0.033328
2012-03-14 09:30:00    0.142689
2012-03-15 09:30:00    0.115741
2012-03-16 09:30:00    0.216307
2012-03-19 09:30:00   -2.500368
2012-03-20 09:30:00   -1.309708
Freq: B, dtype: float64
********************
2012-03-07 09:30:00+00:00    1.024024
2012-03-08 09:30:00+00:00   -0.693508
2012-03-09 09:30:00+00:00   -1.810992
2012-03-12 09:30:00+00:00    0.526046
2012-03-13 09:30:00+00:00    0.033328
2012-03-14 09:30:00+00:00    0.142689
2012-03-15 09:30:00+00:00    0.115741
Freq: B, dtype: float64
********************
2012-03-09 13:30:00+04:00   -1.810992
2012-03-12 13:30:00+04:00    0.526046
2012-03-13 13:30:00+04:00    0.033328
2012-03-14 13:30:00+04:00    0.142689
2012-03-15 13:30:00+04:00    0.115741
Freq: B, dtype: float64
********************
2012-03-07 09:30:00+00:00         NaN
2012-03-08 09:30:00+00:00         NaN
2012-03-09 09:30:00+00:00   -3.621983
2012-03-12 09:30:00+00:00    1.052092
2012-03-13 09:30:00+00:00    0.066656
2012-03-14 09:30:00+00:00    0.285378
2012-03-15 09:30:00+00:00    0.231482
Freq: B, dtype: float64
           

5. 時間區間和區間算數

  • 時間區間表示的是時間範圍,比如一些天,一些月。Period 類表示的正是這種資料類型,其 freq 參數表示了時間的長度
p = pd.Period(2018, freq='A-DEC')    # ‘A-DEC’代表 12 月的最後一個工作日所對應的年度日期
p
           
Period('2018', 'A-DEC')
           
  • 在時間段上增加或減去整數,進而根據其頻率進行移位
(Period('2023', 'A-DEC'), Period('2016', 'A-DEC'))
           
  • 如果兩個區間擁有相同的頻率,則它們可以相減
pd.Period('2020', freq='A-DEC') - p
           
<2 * YearEnds: month=12>
           
  • 使用 period_range 函數可以構造規則區間序列,傳回的是 PeriodIndex 對象
  • 該對象存儲的是區間的序列,可以作為資料結構的軸索引
  • 對于字元串數組,也可以使用 PeriodIndex 類
rng = pd.period_range('2018-01-01', '2018-06-30', freq='M')
print(rng)
print('*' * 20)
print(pd.Series(np.random.randn(6), index=rng))
print('*' * 20)
values = ['2017Q3', '2018Q2', '2019Q1']
index = pd.PeriodIndex(values, freq='Q-DEC')
print(index)
           
PeriodIndex(['2018-01', '2018-02', '2018-03', '2018-04', '2018-05', '2018-06'], dtype='period[M]', freq='M')
********************
2018-01   -1.428061
2018-02    0.743515
2018-03   -0.829522
2018-04    0.955304
2018-05   -0.669098
2018-06   -0.247631
Freq: M, dtype: float64
********************
PeriodIndex(['2017Q3', '2018Q2', '2019Q1'], dtype='period[Q-DEC]', freq='Q-DEC')
           

5.1 區間頻率轉換

  • 使用 asfreq 可以将區間和 PeriodIndex 對象轉換為其他的頻率
  • 從低頻率到高頻率轉換時,需指定是轉換為其開始或結束的區間
p = pd.Period('2018', freq='A-JUN')
p
           
Period('2018', 'A-JUN')
           
Period('2017-07', 'M')
           
Period('2018-06', 'M')
           
  • 從高頻率到低頻率轉換時,pandas 根據子區間的“所屬”來決定父區間
p = pd.Period('Aug-2018', 'M')
p
           
Period('2018-08', 'M')
           
Period('2019', 'A-JUN')
           
  • 完整的 PeriodIndex 對象或時間序列可以按照相同的語義進行轉換
rng = pd.period_range('2016', '2019', freq='A-DEC')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
print(ts)
print('*' * 20)
print(ts.asfreq('M', how='start'))
print('*' * 20)
print(ts.asfreq('B', how='end'))
           
2016    0.307965
2017    2.008601
2018    0.442805
2019    0.730941
Freq: A-DEC, dtype: float64
********************
2016-01    0.307965
2017-01    2.008601
2018-01    0.442805
2019-01    0.730941
Freq: M, dtype: float64
********************
2016-12-30    0.307965
2017-12-29    2.008601
2018-12-31    0.442805
2019-12-31    0.730941
Freq: B, dtype: float64
           

5.2 季度區間頻率

  • 季度資料是會計、金融和其他領域的标準,pandas 支援所有的可能的 12 個季度頻率從 Q-JAN 到 Q-DEC
  • 在财年結束于1月的情況下,2018Q4 運作時間為 2017 年 11 月至 2018 年 1 月
p = pd.Period('2018Q4', freq='Q-JAN')
p, p.asfreq('D', 'start'), p.asfreq('D', 'end')
           
(Period('2018Q4', 'Q-JAN'),
 Period('2017-11-01', 'D'),
 Period('2018-01-31', 'D'))
           
  • 可以做簡單的區間算術,如下擷取在季度倒數第二個工作日下午 4 點的時間戳
p4pm = (p.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
p4pm, p4pm.to_timestamp()
           
(Period('2018-01-30 16:00', 'T'), Timestamp('2018-01-30 16:00:00'))
           
  • 可以使用 period_range 生成季度序列,其算術也是一樣的
rng = pd.period_range('2017Q3', '2018Q4', freq='Q-JAN')
ts = pd.Series(np.arange(len(rng)), index=rng)
print(ts)
print('*' * 20)
new_rng = (rng.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
ts.index = new_rng.to_timestamp()
print(ts)
           
2017Q3    0
2017Q4    1
2018Q1    2
2018Q2    3
2018Q3    4
2018Q4    5
Freq: Q-JAN, dtype: int32
********************
2016-10-28 16:00:00    0
2017-01-30 16:00:00    1
2017-04-27 16:00:00    2
2017-07-28 16:00:00    3
2017-10-30 16:00:00    4
2018-01-30 16:00:00    5
dtype: int32
           

5.3 時間戳與區間的轉換

  • 通過時間戳索引的 Series 和 DataFrame 可以被 to_period 方法轉換為區間
rng = pd.date_range('2018-01-01', periods=3, freq='M')
ts = pd.Series(np.random.randn(3), index=rng)
print(ts)
print('*' * 20)
pts = ts.to_period()
print(pts)
           
2018-01-31    0.022206
2018-02-28   -0.297590
2018-03-31    0.529964
Freq: M, dtype: float64
********************
2018-01    0.022206
2018-02   -0.297590
2018-03    0.529964
Freq: M, dtype: float64
           
  • 雖然時間區間是非重疊時間範圍,一個時間戳隻能屬于給定頻率的單個區間。預設情況下根據時間戳推斷出新 PeriodIndex 的頻率,但可以指定任何想要的頻率,在結果中包含重複的區間也是沒有問題的
rng = pd.date_range('1/29/2018', periods=6, freq='D')
ts2 = pd.Series(np.random.randn(6), index=rng)
print(ts2)
print('*' * 20)
print(ts2.to_period('M'))
           
2018-01-29    0.109360
2018-01-30    0.595276
2018-01-31    0.627808
2018-02-01   -1.367416
2018-02-02    0.986550
2018-02-03    2.713046
Freq: D, dtype: float64
********************
2018-01    0.109360
2018-01    0.595276
2018-01    0.627808
2018-02   -1.367416
2018-02    0.986550
2018-02    2.713046
Freq: M, dtype: float64
           
  • 使用 to_timestamp 可以将區間再轉換為時間戳
pts = ts2.to_period()
print(pts)
print('*' * 20)
print(pts.to_timestamp(how='S'))
           
2018-01-29    0.109360
2018-01-30    0.595276
2018-01-31    0.627808
2018-02-01   -1.367416
2018-02-02    0.986550
2018-02-03    2.713046
Freq: D, dtype: float64
********************
2018-01-29    0.109360
2018-01-30    0.595276
2018-01-31    0.627808
2018-02-01   -1.367416
2018-02-02    0.986550
2018-02-03    2.713046
Freq: D, dtype: float64
           

5.4 從數組生成 PeriodIndex

  • 固定頻率資料集有時存儲在跨越多列的時間範圍資訊中,通過将這些數組和頻率傳遞給 PeriodIndex,可以聯合這些數組形成 DataFrame 索引
data = pd.read_csv(r'C:/Users/Raymone/Data Analysis/examples/macrodata.csv')
data.head(5)
           
year quarter realgdp realcons realinv realgovt realdpi cpi m1 tbilrate unemp pop infl realint
1959.0 1.0 2710.349 1707.4 286.898 470.045 1886.9 28.98 139.7 2.82 5.8 177.146 0.00 0.00
1 1959.0 2.0 2778.801 1733.7 310.859 481.301 1919.7 29.15 141.7 3.08 5.1 177.830 2.34 0.74
2 1959.0 3.0 2775.488 1751.8 289.226 491.260 1916.4 29.35 140.5 3.82 5.3 178.657 2.74 1.09
3 1959.0 4.0 2785.204 1753.7 299.356 484.052 1931.3 29.37 140.0 4.33 5.6 179.386 0.27 4.06
4 1960.0 1.0 2847.699 1770.5 331.722 462.199 1955.5 29.54 139.6 3.50 5.2 180.007 2.31 1.19
index = pd.PeriodIndex(year=data.year, quarter=data.quarter, freq='Q-DEC')
data.index = index
data.head(5)
           
year quarter realgdp realcons realinv realgovt realdpi cpi m1 tbilrate unemp pop infl realint
1959Q1 1959.0 1.0 2710.349 1707.4 286.898 470.045 1886.9 28.98 139.7 2.82 5.8 177.146 0.00 0.00
1959Q2 1959.0 2.0 2778.801 1733.7 310.859 481.301 1919.7 29.15 141.7 3.08 5.1 177.830 2.34 0.74
1959Q3 1959.0 3.0 2775.488 1751.8 289.226 491.260 1916.4 29.35 140.5 3.82 5.3 178.657 2.74 1.09
1959Q4 1959.0 4.0 2785.204 1753.7 299.356 484.052 1931.3 29.37 140.0 4.33 5.6 179.386 0.27 4.06
1960Q1 1960.0 1.0 2847.699 1770.5 331.722 462.199 1955.5 29.54 139.6 3.50 5.2 180.007 2.31 1.19

6. 重新采樣與頻率轉換

  • 重新采樣是指将時間序列從一個頻率轉換為另一個頻率的過程
  • 将更高頻率的資料聚合到低頻率被稱為向下采樣,從低頻率轉換到高頻率稱為向上采樣
  • 但不是所有的頻率轉換都是上面兩類,如從 W-WED 每周三轉換到 W-FRI 每周五就不屬于上面兩類
  • pandas 對象都有 resample 方法用于頻率轉換,它類似 groupby 函數,調用該方法進行分組,再調用聚合函數輸出結果
  • resample 的示例及參數如下
參數 描述
freq 表明所需采樣頻率的字元串或 DataOffset 對象(例如,‘M’, ‘5min’ 或 Second(1))
axis 需要采樣的軸向,預設為 0
fill_method 向上采樣時的插值方式,‘ffill’ 或 ‘bfill’,預設不插值
closed 向下采樣中,每段間隔的哪一段是封閉的(包含的),‘right’ 或 ‘left’
label 向下采樣中,如何用 ‘right’ 或 ‘left’ 的箱标簽标記聚合結果
loffset 對箱标簽進行時間調校,如 ‘-1s’ 可以将聚合标簽向前移動一秒
limit 在前向或後向填充時,填充區間的最大值
kind 對區間(‘period’)或時間戳(‘timestamp’)的聚合,預設為時間序列索引的類型
convention 在對區間重新采樣時,用于将低頻周期轉換為高頻的約定(‘start’ or ‘end’),預設‘end’
rng = pd.date_range('2018-01-01', periods=100, freq='D')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts.resample('M').mean()
           
2018-01-31    0.090781
2018-02-28    0.194547
2018-03-31    0.202273
2018-04-30   -0.032387
Freq: M, dtype: float64
           

6.1 向下采樣

  • 向下采樣為高頻率聚合至低頻率,要聚合的資料不必是固定頻率的
  • 期望的頻率定義了用于對時間序列切片以聚合的箱體邊界,或者說區間,區間的集合必須是整個時間幀,是以每個區間都是半閉合的,且一個資料點隻能屬于一個時間間隔
  • 是以在進行向下采樣時,需考慮每段間隔的哪一邊是閉合的(closed 參數,預設 left),以及如何在間隔的起始或結束位置标記每個已聚合的箱體(label 參數,預設 left)
rng = pd.date_range('2018-01-01', periods=12, freq='T')
ts = pd.Series(np.arange(12), index=rng)
print('原始資料:\n', ts, sep='')
print('*' * 20)
print('右側封閉,左側标記:\n', ts.resample('5min', closed='right').sum(), sep='')
print('*' * 20)
print('左側封閉,左側标記:\n', ts.resample('5min').sum(), sep='')
print('*' * 20)
print('右側封閉,右側标記:\n', ts.resample('5min', closed='right', label='right').sum(), sep='')
           
原始資料:
2018-01-01 00:00:00     0
2018-01-01 00:01:00     1
2018-01-01 00:02:00     2
2018-01-01 00:03:00     3
2018-01-01 00:04:00     4
2018-01-01 00:05:00     5
2018-01-01 00:06:00     6
2018-01-01 00:07:00     7
2018-01-01 00:08:00     8
2018-01-01 00:09:00     9
2018-01-01 00:10:00    10
2018-01-01 00:11:00    11
Freq: T, dtype: int32
********************
右側封閉,左側标記:
2017-12-31 23:55:00     0
2018-01-01 00:00:00    15
2018-01-01 00:05:00    40
2018-01-01 00:10:00    11
Freq: 5T, dtype: int32
********************
左側封閉,左側标記:
2018-01-01 00:00:00    10
2018-01-01 00:05:00    35
2018-01-01 00:10:00    21
Freq: 5T, dtype: int32
********************
右側封閉,右側标記:
2018-01-01 00:00:00     0
2018-01-01 00:05:00    15
2018-01-01 00:10:00    40
2018-01-01 00:15:00    11
Freq: 5T, dtype: int32
           
  • loffset 參數指定将結果索引移動一定數量,傳遞值為字元串或日期偏置
2017-12-31 23:59:59     0
2018-01-01 00:04:59    15
2018-01-01 00:09:59    40
2018-01-01 00:14:59    11
Freq: 5T, dtype: int32
           
  • 開端、峰值、谷值、結束(OHLC)為金融中的重要名額,比如股票 K 線的開盤價、最高價、最低價、收盤價,使用ohlc() 聚合函數很容易實作:
open high low close
2018-01-01 00:00:00 4 4
2018-01-01 00:05:00 5 9 5 9
2018-01-01 00:10:00 10 11 10 11

6.2 向上采樣與插值

  • 當從低頻率到高頻率轉換時,不需要任何聚合,而需要進行插值,因為資料量變多了,若不進行插值,則會出現 NA
s = pd.Series(np.random.randn(2),
                    index=pd.date_range('1/1/2019', periods=2, freq='W-WED'))
print(s)
print('*' * 20)
print(s.resample('D').asfreq())
           
2019-01-02   -0.846711
2019-01-09    0.758713
Freq: W-WED, dtype: float64
********************
2019-01-02   -0.846711
2019-01-03         NaN
2019-01-04         NaN
2019-01-05         NaN
2019-01-06         NaN
2019-01-07         NaN
2019-01-08         NaN
2019-01-09    0.758713
Freq: D, dtype: float64
           
  • 插值是通過 filna 完成的,可以選擇向前或向後填充,或者隻填充一定數量的區間
print(s.resample('D').ffill())
print('*' * 20)
print(s.resample('D').ffill(limit=2))
           
2019-01-02   -0.846711
2019-01-03   -0.846711
2019-01-04   -0.846711
2019-01-05   -0.846711
2019-01-06   -0.846711
2019-01-07   -0.846711
2019-01-08   -0.846711
2019-01-09    0.758713
Freq: D, dtype: float64
********************
2019-01-02   -0.846711
2019-01-03   -0.846711
2019-01-04   -0.846711
2019-01-05         NaN
2019-01-06         NaN
2019-01-07         NaN
2019-01-08         NaN
2019-01-09    0.758713
Freq: D, dtype: float64
           
  • 需要注意的是,重采樣後的日期索引可以不和舊的索引重疊
2019-01-03   -0.846711
2019-01-10    0.758713
Freq: W-THU, dtype: float64
           

6.3 使用區間進行重新采樣

  • 對以區間為索引的資料進行采樣與時間戳的情況類似,如下為向下采樣
s = pd.Series(np.random.randn(24),
                    index=pd.period_range('1-2017', '12-2018', freq='M'))
print(s[:5])
print('*' * 20)
annual_s = s.resample('A-DEC').mean()
print(annual_s)
           
2017-01   -0.357309
2017-02    1.328097
2017-03    1.169833
2017-04   -1.096610
2017-05   -1.901794
Freq: M, dtype: float64
********************
2017   -0.293896
2018   -0.008636
Freq: A-DEC, dtype: float64
           
  • 向上采樣更為細緻,因為必須在重新采樣前決定新頻率中在時間段的哪一端放置數值,就像 asfreq 方法一樣,resample 中使用 convention 參數進行選擇
print(annual_s.resample('Q-DEC').ffill())
print('*' * 20)
print(annual_s.resample('Q-DEC', convention='end').ffill())
           
2017Q1   -0.293896
2017Q2   -0.293896
2017Q3   -0.293896
2017Q4   -0.293896
2018Q1   -0.008636
2018Q2   -0.008636
2018Q3   -0.008636
2018Q4   -0.008636
Freq: Q-DEC, dtype: float64
********************
2017Q4   -0.293896
2018Q1   -0.293896
2018Q2   -0.293896
2018Q3   -0.293896
2018Q4   -0.008636
Freq: Q-DEC, dtype: float64
           
  • 由于涉及時間範圍,是以:
    • 在向下采樣中,目标頻率必須是原頻率的子區間
    • 在向上采樣中,目标頻率必須是原頻率的父區間
  • 例如 “Q-MAR” 是 “A-MAR”, “A-JUN”, “A-SEP”, “A-DEC” 的子區間
2017Q4   -0.293896
2018Q1   -0.293896
2018Q2   -0.293896
2018Q3   -0.293896
2018Q4   -0.008636
2019Q1   -0.008636
2019Q2   -0.008636
2019Q3   -0.008636
Freq: Q-MAR, dtype: float64
           

7. 移動視窗函數

  • 通過移動視窗或指數衰減而運作的函數是用于時間序列操作的數組變換的一個重要類别。這些函數有助于平滑噪聲或粗糙的資料。這些函數被稱為移動視窗函數
  • 如下以通過 rolling() 函數計算蘋果公司股價的 250 日均線為例,rolling 與 resample 和 groupby 行為類似,在 Series 或 DataFrame 上通過一個window(以一個區間的數字來表示)進行調用,它建立的對象是根據250日滑動視窗分組而不是直接分組,是以得到 250 日均線
# 載入一些時間序列資料并按照工作日頻率進行重新采樣
close_px_all = pd.read_csv(r'C:/Users/Raymone/Data Analysis/examples/stock_px_2.csv',
                          parse_dates=True, index_col=0)
close_px = close_px_all[['AAPL', 'MSFT', 'XOM']]
close_px = close_px.resample('B').ffill()
%matplotlib notebook
close_px.AAPL.plot()
close_px.AAPL.rolling(250).mean().plot()
           
Python 資料分析:時間序列1. 日期和時間資料的類型及工具2. 時間序列基礎3. 日期範圍、頻率和移位4. 時區處理5. 時間區間和區間算數6. 重新采樣與頻率轉換7. 移動視窗函數
  • 在傳入的視窗大小參數是數字的時候,rolling 函數的 min_periods 參數預設為視窗大小,是以預設情況滾動函數需要視窗中所有的值必須是非 NA 值,例如在起始位置資料少于視窗區間,是以會得到 NA,直到第 250 條資料才能計算均值。
  • 可以通過 min_periods 參數傳入想要的值,例如 min_priods=10 代表視窗中有 10 個非 NA 值就開始計算均值,然後視窗開始擴大,達到 250 條後視窗停止擴大而開始移動
appl_std250 = close_px.AAPL.rolling(250, min_periods=10).std()
print(appl_std250[5:12])
appl_std250.plot()
           
2003-01-09         NaN
2003-01-10         NaN
2003-01-13         NaN
2003-01-14         NaN
2003-01-15    0.077496
2003-01-16    0.074760
2003-01-17    0.112368
Freq: B, Name: AAPL, dtype: float64
           
Python 資料分析:時間序列1. 日期和時間資料的類型及工具2. 時間序列基礎3. 日期範圍、頻率和移位4. 時區處理5. 時間區間和區間算數6. 重新采樣與頻率轉換7. 移動視窗函數
  • rolling 函數也接受表示固定大小的時間偏置字元串,注意這種情況下 min_periods 預設為 1
AAPL MSFT XOM
2003-01-02 7.400000 21.110000 29.220000
2003-01-03 7.425000 21.125000 29.230000
2003-01-06 7.433333 21.256667 29.473333
2003-01-07 7.432500 21.425000 29.342500
2003-01-08 7.402000 21.402000 29.240000
  • expanding 算子可以計算視窗擴充均值,即從時間序列的起始位置開始時間視窗,并增加視窗的大小,直到它涵蓋整個序列
  • expanding 的預設 min_priods 為 1,即從一開始就計算
expanding_mean = close_px.AAPL.expanding().mean()
expanding_mean[:5]
           
2003-01-02    7.400000
2003-01-03    7.425000
2003-01-06    7.433333
2003-01-07    7.432500
2003-01-08    7.402000
Freq: B, Name: AAPL, dtype: float64
           
  • 在 DataFrame 上調用一個移動視窗函數會将變化應用到每一列上
Python 資料分析:時間序列1. 日期和時間資料的類型及工具2. 時間序列基礎3. 日期範圍、頻率和移位4. 時區處理5. 時間區間和區間算數6. 重新采樣與頻率轉換7. 移動視窗函數

7.1 指數權重函數

  • 指定一個常數衰減因子以向更多近期觀測值提供更多權重,其中一種方法為使用一個跨度(span)指定衰減因子,α=2/(span+1), for span≥1
  • pandas 擁有 ewm 算子,和 rolling, expanding 算子一起使用
  • 如下為蘋果公司股價 60 日均線與 span=60 的 EW 移動平均線進行比較的例子
  • 關于 ewm 的更多參數,參考官方文檔:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.ewm.html#pandas.Series.ewm
import matplotlib.pyplot as plt
appl_px = close_px.AAPL['2006':'2007']
ma60 = appl_px.rolling(30, min_periods=20).mean()
ewma60 = appl_px.ewm(span=30).mean()
ma60.plot(style='k--', label='Simple MA')
ewma60.plot(style='k-', label='EW MA')
plt.legend()
           
Python 資料分析:時間序列1. 日期和時間資料的類型及工具2. 時間序列基礎3. 日期範圍、頻率和移位4. 時區處理5. 時間區間和區間算數6. 重新采樣與頻率轉換7. 移動視窗函數

7.2 二進制移動視窗函數

  • 一些統計算子,如相關度和協方差,需要操作兩個時間序列,實際的例子是分析某股票與大盤指數的關聯性
  • 首先分别計算該股票和大盤指數的百分比變換,然後調用 rolling,這種情況下,corr 函數可以根據 spx_rets,即大盤百分比變化計算滾動相關性
spx_px = close_px_all['SPX']
spx_rets = spx_px.pct_change()
returns = close_px.pct_change()
corr = returns.AAPL.rolling(125, min_periods=100).corr(spx_rets)
corr.plot()
           
Python 資料分析:時間序列1. 日期和時間資料的類型及工具2. 時間序列基礎3. 日期範圍、頻率和移位4. 時區處理5. 時間區間和區間算數6. 重新采樣與頻率轉換7. 移動視窗函數
  • 如果要一次性計算多隻股票與大盤指數的相關性,可以對 DataFrame 直接使用 rolling_corr 計算 Series 與 DataFrame 中每一列的相關性
corr = returns.rolling(125,min_periods=100).corr(spx_rets)
corr.plot()
           
Python 資料分析:時間序列1. 日期和時間資料的類型及工具2. 時間序列基礎3. 日期範圍、頻率和移位4. 時區處理5. 時間區間和區間算數6. 重新采樣與頻率轉換7. 移動視窗函數

7.3 使用者自定義的移動視窗函數

  • 在 rolling 及其相關方法上使用 apply 方法,可以在移動視窗中應用自己設計的數組函數
  • 如下例子計算一年視窗下蘋果公司股價 2% 收益的百分位等級
from scipy.stats import percentileofscore
score_at_2percent = lambda x: percentileofscore(x, 0.02)
result = returns.AAPL.rolling(250).apply(score_at_2percent, raw=True)
result.plot()
           
Python 資料分析:時間序列1. 日期和時間資料的類型及工具2. 時間序列基礎3. 日期範圍、頻率和移位4. 時區處理5. 時間區間和區間算數6. 重新采樣與頻率轉換7. 移動視窗函數

繼續閱讀