第11章 时间序列

时间序列数据的意义取决于具体的应⽤场景,主要有以下⼏种:

  • 时间戳(timestamp),特定的时刻。
  • 固定时期(period),如2007年1⽉或2010年全年。
  • 时间间隔(interval),由起始和结束时间戳表示。时期
    (period)可以被看做间隔(interval)的特例。
  • 实验或过程时间,每个时间点都是相对于特定起始时间的⼀个度量。例如,从放⼊烤箱时起,每秒钟饼⼲的直径。

11.1 日期和时间数据类型及⼯具

Python时间日期相关的常用标准库:

  • datetime
  • time
  • calendar

11.1 ⽇期和时间数据类型及⼯具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from datetime import datetime,timedelta


now = datetime.now()
print(now)
# 2019-05-12 11:52:03.148521

print(now.year, now.month, now.day)
# 2019 5 12

print(now.hour, now.min, now.second)
# 11 0001-01-01 00:00:00 31



delta = datetime(2011, 1, 7) - datetime(2008, 6, 24, 8, 15)
print(delta)
# 926 days, 15:45:00

print(delta.days)
# 926

print(delta.seconds)
# 56700


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模块中的数据类型

42023537

字符串和datetime的相互转换

利⽤str或strftime⽅法(传⼊⼀个格式化字符串),datetime对象和pandas的Timestamp对象可以被格式化为字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from datetime import datetime,timedelta


stamp = datetime(2011, 1, 3)
print(str(stamp))
# 2011-01-03 00:00:00


# datetime对象转为字符串对象
print(stamp.strftime('%Y-%m-%d'))
# 2011-01-03


# 字符串对象转为datetime对象
value = '2011-01-03'
print(datetime.strptime(value, '%Y-%m-%d'))
# 2011-01-03 00:00:00


datestrs = ['7/6/2011', '8/6/2011']
print([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格式化编码。

32852537

datetime.strptime通过将字符串对象转为datetime对象,但是每次都要编写格式定义是很麻烦的事情,这时候就可以使用from dateutil.parser import parse.
dateutil可以解析几乎所有人类能够理解的日期表示形式

1
2
3
4
5
6
7
8
9
10
11
12
13
from dateutil.parser import parse


print(parse('2011-01-03'))
# 2011-01-03 00:00:00

print(parse('Jan 31, 1997 10:45 PM'))
# 1997-01-31 22:45:00


# dayfirst:日出现在⽉的前⾯
print(parse('6/12/2011', dayfirst=True))
# 2011-12-06 00:00:00

pandas使用to_datetime方法解析多种不同的日期表示形式.
NaT(Not a Time)是pandas中时间戳数据的null值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd


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


# 添加一个缺失值
idx = pd.to_datetime(datestrs + [None])
# 缺失值自动转为NaT(Not a Time)
print(idx)
# DatetimeIndex(['2011-07-06 12:00:00', '2011-08-06 00:00:00', 'NaT'], dtype='datetime64[ns]', freq=None)

print(idx[2])
# NaT

# NaT就是null
print(pd.isnull(idx))
# [False False True]

特定于当前环境的⽇期格式

169702537


11.2 时间序列基础

知识预备:
==两个series的运算,当匹配之后series数量不匹配时,没有被匹配到的行会变成NaN==.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import numpy as np
import pandas as pd


x = pd.Series(np.arange(10))
print(x)
# 0 0
# 1 1
# 2 2
# 3 3
# 4 4
# 5 5
# 6 6
# 7 7
# 8 8
# 9 9
# dtype: int32


y = pd.Series(np.arange(10))
print(y[::2])
# 0 0
# 2 2
# 4 4
# 6 6
# 8 8
# dtype: int32

# 当匹配之后series数量不匹配时,没有被匹配到的行会变成NaN
print(x + y[::2])
# 0 0.0
# 1 NaN
# 2 4.0
# 3 NaN
# 4 8.0
# 5 NaN
# 6 12.0
# 7 NaN
# 8 16.0
# 9 NaN
# dtype: float64

pandas最基本的时间序列类型就是以时间戳Datetime为索引的Series.

  • 以Datetime为index的类型是DatetimeIndex
  • pandas⽤NumPy的datetime64数据类型以纳秒形式存储==时间戳==
  • DatetimeIndex中的各个标量值是pandas的Timestamp对象
    简记:DatetimeIndex的元素是时间戳
  • TimeStamp可以随时⾃动转换为datetime对象。此外,它还可以存储频率信息(如果有的话),且知道如何执⾏时区转换以及其他操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from datetime import datetime

import numpy as np
import pandas as pd


np.random.seed(888)
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)

print(ts)
# 2011-01-02 -0.176201
# 2011-01-05 0.188876
# 2011-01-07 0.826747
# 2011-01-08 -0.032447
# 2011-01-10 -0.652499
# 2011-01-12 -0.105339
# dtype: float64

# datetime对象的index是DatetimeIndex
print(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)


print(ts[::2])
# 2011-01-02 -0.176201
# 2011-01-07 0.826747
# 2011-01-10 -0.652499
# dtype: float64


# 跟其他Series⼀样,不同索引的时间序列之间的算术运算会⾃动按⽇期对⻬:
print(ts + ts[::2])
# 2011-01-02 -0.352402
# 2011-01-05 NaN
# 2011-01-07 1.653494
# 2011-01-08 NaN
# 2011-01-10 -1.304999
# 2011-01-12 NaN
# dtype: float64


# pandas⽤NumPy的datetime64数据类型以纳秒形式存储时间戳:
print(ts.index.dtype)
# datetime64[ns]


# DatetimeIndex中的各个标量值是pandas的Timestamp对象
print(type(ts.index[0]))
# <class 'pandas._libs.tslibs.timestamps.Timestamp'>


print(ts.index[0])
# 2011-01-02 00:00:00

索引、选取、子集构造

DatetimeIndex索引有一种专属索引方法:

  • 传⼊⼀个可以被解释为日期的字符串
  • 甚至只传入不部分字符串(如传入“年”或“年⽉”)就可以索引得到
  • 传入Datatime对象
  • 使用不存在于该时间序列中的时间戳对其进⾏切⽚(即范围查询)
  • 使用truncate方法

注意,以上五种方法产⽣的都是源时间序列的视图,这意味着,没有数据被复制,对切⽚进⾏修改会反映到原始数据上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
from datetime import datetime

import numpy as np
import pandas as pd


np.random.seed(888)
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)

print(ts)
# 2011-01-02 -0.176201
# 2011-01-05 0.188876
# 2011-01-07 0.826747
# 2011-01-08 -0.032447
# 2011-01-10 -0.652499
# 2011-01-12 -0.105339
# dtype: float64


# 普通索引
stamp = ts.index[2]
print(stamp)
# 2011-01-07 00:00:00

print(ts[stamp])
# 0.8267471802513613


# DatetimeIndex专属索引方式
# 传⼊⼀个可以被解释为⽇期的字符串进行索引
print(ts['1/10/2011'])
# -0.6524994181480014

print(ts['20110110'])
# -0.6524994181480014




longer_ts = pd.Series(np.random.randn(1000),
index=pd.date_range('1/1/2000', periods=1000))
print(longer_ts)
# 2000-01-01 0.217776
# 2000-01-02 0.587282
# ...
# 2002-09-25 0.731959
# 2002-09-26 0.903034
# Freq: D, Length: 1000, dtype: float64


# 只传入部分字符串就可以进行索引
# 只传入年
print(longer_ts['2001'])
# 2001-01-01 -0.252939
# 2001-01-02 0.612337
# ...
# 2001-12-30 0.843535
# 2001-12-31 0.364403
# Freq: D, Length: 365, dtype: float64


# 传入年月
print(longer_ts['2001-05'])
# 2001-05-01 1.011072
# 2001-05-02 -1.197178
# ...
# 2001-05-30 -0.493460
# 2001-05-31 1.545260
# Freq: D, dtype: float64


# 传入datetime对象进行索引
print(ts[datetime(2011, 1, 7):])
# 2011-01-07 0.826747
# 2011-01-08 -0.032447
# 2011-01-10 -0.652499
# 2011-01-12 -0.105339
# dtype: float64



print(ts)
# 2011-01-02 -0.176201
# 2011-01-05 0.188876
# 2011-01-07 0.826747
# 2011-01-08 -0.032447
# 2011-01-10 -0.652499
# 2011-01-12 -0.105339
# dtype: float64


# 不传入存在于该时间序列中的时间戳对其进⾏切⽚索引
print(ts['1/6/2011':'1/11/2011'])
# 2011-01-07 0.826747
# 2011-01-08 -0.032447
# 2011-01-10 -0.652499
# dtype: float64


# 使用truncate方法
print(ts.truncate(after='1/9/2011'))
# 2011-01-02 -0.176201
# 2011-01-05 0.188876
# 2011-01-07 0.826747
# 2011-01-08 -0.032447
# dtype: float64

带有重复索引的时间序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import numpy as np
import pandas as pd

dates = pd.DatetimeIndex(['1/1/2000', '1/2/2000', '1/2/2000',
'1/2/2000', '1/3/2000'])

dup_ts = pd.Series(np.arange(5), index=dates)
print(dup_ts)
# 2000-01-01 0
# 2000-01-02 1
# 2000-01-02 2
# 2000-01-02 3
# 2000-01-03 4
# dtype: int32


# 索引存在重复
print(dup_ts.index.is_unique)
# False


# 不存在重复索引
print(dup_ts['1/3/2000'])
# 4

# 存在重复索引
print(dup_ts['1/2/2000'])
# 2000-01-02 1
# 2000-01-02 2
# 2000-01-02 3
# dtype: int32



# 假设想要对具有非唯⼀时间戳的数据进⾏聚合。⼀个办法是使⽤groupby,并传⼊level=0:
grouped = dup_ts.groupby(level=0)
print(grouped.mean())
# 2000-01-01 0
# 2000-01-02 2
# 2000-01-03 4
# dtype: int32

print(grouped.count())
# 2000-01-01 1
# 2000-01-02 3
# 2000-01-03 1
# dtype: int64

11.3 日期的范围、频率以及移动

部分基本的时间序列频率(freq参数)

别名 偏移量类型 说明
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 从指定的星球几开始算起,每周
WOM-1MON
WOM-2MON…
WeekOfMonth 每月第一,第二…个星期几
(例如:WOM-3FRI表示每月第三个星期五)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import numpy as np
import pandas as pd


# date_range:根据指定的频率⽣成指定⻓度的DatetimeIndex:(注意包含上下限)
# freq='D'是每天的意思。
index = pd.date_range('2012-04-01', '2012-05-01')
print(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'],
# dtype='datetime64[ns]', freq='D')


# 使用periods,按天计算的时间点
print(pd.date_range(start='2012-04-01', periods=3))
# DatetimeIndex(['2012-04-01', '2012-04-02', '2012-04-03'], dtype='datetime64[ns]', freq='D')

print(pd.date_range(end='2012-06-01', periods=3))
# DatetimeIndex(['2012-05-30', '2012-05-31', '2012-06-01'], dtype='datetime64[ns]', freq='D')


# 通过传入频率freq作为时间间隔
print(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')


# date_range默认会保留时间信息(12:56:31)
print(pd.date_range('2012-05-02 12:56:31', periods=5))
# DatetimeIndex(['2012-05-02 12:56:31', '2012-05-03 12:56:31',
# '2012-05-04 12:56:31', '2012-05-05 12:56:31',
# '2012-05-06 12:56:31'],
# dtype='datetime64[ns]', freq='D')

# normalize:去除时间信息,规范化(normalize)到午夜的时间戳。
print(pd.date_range('2012-05-02 12:56:31', periods=5, normalize=True))
# DatetimeIndex(['2012-05-02', '2012-05-03', '2012-05-04', '2012-05-05',
# '2012-05-06'],
# dtype='datetime64[ns]', freq='D')

频率和⽇期偏移量

pandas中的频率是由⼀个基础频率(base frequency)和⼀个乘数组成的。
简单来说.

频率 = 基础频率 * 日期偏移量

而且偏移量可以进行加减乘除运算连接,
偏移量可以随意组合,这称为频率字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import numpy as np
import pandas as pd
from pandas.tseries.offsets import Hour, Minute


hour = Hour()
print(hour)
# <Hour>

# offsets.Hour对象
print(type(hour))
# <class 'pandas.tseries.offsets.Hour'>

four_hours = Hour(4)
print(four_hours)
# <4 * Hours>

# 偏移量对象可以通过运算连接
print(Hour(2) + Minute(30))
# <150 * Minutes>


# ⽆需明确创建对象,只需使⽤诸如"H"或"4H"这样的字符串别名即可。
print(pd.date_range('2000-01-01', '2000-01-01 23:59', freq='4h'))
# DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 04:00:00',
# '2000-01-01 08:00:00', '2000-01-01 12:00:00',
# '2000-01-01 16:00:00', '2000-01-01 20:00:00'],
# dtype='datetime64[ns]', freq='4H')


# 偏移量可以随意组合,这称为'频率字符串'
print(pd.date_range('2000-01-01', periods=3, freq='1h30min'))
# DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 01:30:00',
# '2000-01-01 03:00:00'],
# dtype='datetime64[ns]', freq='90T')

WOM日期

WOM(Week Of Month)是⼀种⾮常实⽤的频率类,它以WOM开头。它使你能获得诸如“每⽉第3个星期五”之类的⽇期:

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
import pandas as pd


# 每月第三个星期五
rng = pd.date_range('2012-01-01', '2012-06-01', freq='WOM-3FRI')
print(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')]

移动(超前和滞后)数据

  • 移动(shifting)指的是沿着时间轴将数据前移或后移
  • 使用Series或者DataFrame的shift方法实现移动:
    ⽤于执行单纯的前移或后移操作,保持索引不变
  • 使用shift方法的freq参数,实现对Index的位移
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import numpy as np
import pandas as pd


np.random.seed(888)
ts = pd.Series(np.random.randn(4),
index=pd.date_range('1/1/2000', periods=4, freq='Y'))

print(ts)
# 2000-12-31 -0.176201
# 2001-12-31 0.188876
# 2002-12-31 0.826747
# 2003-12-31 -0.032447
# Freq: A-DEC, dtype: float64


# 将数据移动两个索引
print(ts.shift(2))
# 2000-12-31 NaN
# 2001-12-31 NaN
# 2002-12-31 -0.176201
# 2003-12-31 0.188876
# Freq: A-DEC, dtype: float64

print(ts.shift(-2))
# 2000-12-31 0.826747
# 2001-12-31 -0.032447
# 2002-12-31 NaN
# 2003-12-31 NaN
# Freq: A-DEC, dtype: float64



print(ts)
# 2000-12-31 -0.176201
# 2001-12-31 0.188876
# 2002-12-31 0.826747
# 2003-12-31 -0.032447
# Freq: A-DEC, dtype: float64

print(ts.shift(1))
# 2000-12-31 NaN
# 2001-12-31 -0.176201
# 2002-12-31 0.188876
# 2003-12-31 0.826747
# Freq: A-DEC, dtype: float64


# 计算时间序列的百分⽐变化
# -2.071938 = (0.188876 / -0.176201) - 1
# 3.377187 = (0.826747 / 0.188876) - 1
print(ts / ts.shift(1) - 1)
# 2000-12-31 NaN
# 2001-12-31 -2.071938
# 2002-12-31 3.377187
# 2003-12-31 -1.039247
# Freq: A-DEC, dtype: float64





print(ts)
# 2000-12-31 -0.176201
# 2001-12-31 0.188876
# 2002-12-31 0.826747
# 2003-12-31 -0.032447
# Freq: A-DEC, dtype: float64


# 传入freq,实现对时间戳位移(index的位移)
print(ts.shift(2, freq='M'))
# 2001-02-28 -0.176201
# 2002-02-28 0.188876
# 2003-02-28 0.826747
# 2004-02-29 -0.032447
# Freq: A-FEB, dtype: float64


print(ts.shift(3, freq='D'))
# 2001-01-03 -0.176201
# 2002-01-03 0.188876
# 2003-01-03 0.826747
# 2004-01-03 -0.032447
# Freq: 365D, dtype: float64


print(ts.shift(1, freq='90T'))
# 2000-12-31 01:30:00 -0.176201
# 2001-12-31 01:30:00 0.188876
# 2002-12-31 01:30:00 0.826747
# 2003-12-31 01:30:00 -0.032447
# Freq: A-DEC, dtype: float64

通过偏移量对日期进行位移

  • pandas的日期偏移量还可以⽤在datetime或Timestamp对象上
  • 还可以添加锚点偏移量(⽐如MonthEnd):
    第⼀次增量会将原⽇期向前滚动到符合频率规则的下⼀个⽇期
  • 如果想要将日期向后滚动锚点偏移量:
    可以使用锚点偏移量的rollforward和rollback⽅法,可明确地将⽇期向前或向后“滚动”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from datetime import datetime

import numpy as np
import pandas as pd
from pandas.tseries.offsets import Day, MonthEnd


# pandas的日期偏移量用在datetime或Timestamp对象
now = datetime(2011, 11, 17)
print(now + 3 * Day())
# 2011-11-20 00:00:00


# 锚点偏移量
print(MonthEnd())
# <MonthEnd>

print(MonthEnd(2))
# <2 * MonthEnds>

# 向前滚动到符合规则的下一个日期(跳到这个月月底)
print(now + MonthEnd())
# 2011-11-30 00:00:00

# 跳到下两个月月底
print(now + MonthEnd(2))
# 2011-12-31 00:00:00


# 使用锚点偏移量的rollforward和rollback⽅法,
# 明确是向前还是向后滚动
offset = MonthEnd()
print(offset.rollforward(now))
# 2011-11-30 00:00:00

print(offset.rollback(now))
# 2011-10-31 00:00:00

日期偏移量结合groupby使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import numpy as np
import pandas as pd
from pandas.tseries.offsets import MonthEnd


np.random.seed(888)
ts = pd.Series(np.random.randn(10),
index=pd.date_range('1/15/2000', periods=10, freq='4d'))

print(ts)
# 2000-01-15 -0.176201
# 2000-01-19 0.188876
# 2000-01-23 0.826747
# 2000-01-27 -0.032447
# 2000-01-31 -0.652499
# 2000-02-04 -0.105339
# 2000-02-08 0.217776
# 2000-02-12 0.587282
# 2000-02-16 0.100238
# 2000-02-20 -1.099947
# Freq: 4D, dtype: float64


# 将offset.rollforward传给groupby函数
offset = MonthEnd()
print(ts.groupby(offset.rollforward).mean())
# 2000-01-31 0.030895
# 2000-02-29 -0.059998
# dtype: float64


# 当然,使用resample更加方便快速
print(ts.resample('M').mean())
# 2000-01-31 0.030895
# 2000-02-29 -0.059998
# Freq: M, dtype: float64

11.4 时区处理

时区是以UTC偏移量的形式表示的.

在Python中,时区信息来⾃第三⽅库pytz,它使Python可以使⽤Olson数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pytz


# 获取时区名称列表
print(pytz.common_timezones[-5:])
# ['US/Eastern', 'US/Hawaii', 'US/Mountain', 'US/Pacific', 'UTC']


# 获取时区对象
tz = pytz.timezone('America/New_York')
print(type(tz))
# <class 'pytz.tzfile.America/New_York'>

print(tz)
# America/New_York

时区本地化和转换

默认情况下,pandas中的时间序列是单纯的(naive)时区。

  • 默认情况下,pandas中的时间序列的tz属性为None
  • 使用tz_localize方法转为本地化时区
  • 使用tz_convert方法将本地化时区转换到别的时区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import numpy as np
import pandas as pd


np.random.seed(888)
rng = pd.date_range('3/9/2012 9:30', periods=6, freq='D')

print(len(rng))
# 6

ts = pd.Series(np.random.randn(len(rng)), index=rng)

print(ts)
# 2012-03-09 09:30:00 -0.176201
# 2012-03-10 09:30:00 0.188876
# 2012-03-11 09:30:00 0.826747
# 2012-03-12 09:30:00 -0.032447
# 2012-03-13 09:30:00 -0.652499
# 2012-03-14 09:30:00 -0.105339
# Freq: D, dtype: float64


print(ts.index)
# DatetimeIndex(['2012-03-09 09:30:00', '2012-03-10 09:30:00',
# '2012-03-11 09:30:00', '2012-03-12 09:30:00',
# '2012-03-13 09:30:00', '2012-03-14 09:30:00'],
# dtype='datetime64[ns]', freq='D')


# 索引的tz字段为None
# 默认情况下,pandas中的时间序列的tz属性为None
print(ts.index.tz)
# None


# tz_localize⽅法:将单纯转化为本地化
ts_utc = ts.tz_localize('UTC')
print(ts_utc)
# 2012-03-09 09:30:00+00:00 -0.176201
# 2012-03-10 09:30:00+00:00 0.188876
# 2012-03-11 09:30:00+00:00 0.826747
# 2012-03-12 09:30:00+00:00 -0.032447
# 2012-03-13 09:30:00+00:00 -0.652499
# 2012-03-14 09:30:00+00:00 -0.105339
# Freq: D, dtype: float64


# index具有tz属性了
print(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')


print(ts_utc.index.tz)
# UTC


# 使用tz_convert将本地化时区转换到别的时区
print(ts_utc.tz_convert('America/New_York'))
# 2012-03-09 04:30:00-05:00 -0.176201
# 2012-03-10 04:30:00-05:00 0.188876
# 2012-03-11 05:30:00-04:00 0.826747
# 2012-03-12 05:30:00-04:00 -0.032447
# 2012-03-13 05:30:00-04:00 -0.652499
# 2012-03-14 05:30:00-04:00 -0.105339
# Freq: D, dtype: float64


# 本地化到America/New_York,然后转为UTC
ts_eastern = ts.tz_localize('America/New_York')
print(ts_eastern.tz_convert('UTC'))
# 2012-03-09 14:30:00+00:00 -0.176201
# 2012-03-10 14:30:00+00:00 0.188876
# 2012-03-11 13:30:00+00:00 0.826747
# 2012-03-12 13:30:00+00:00 -0.032447
# 2012-03-13 13:30:00+00:00 -0.652499
# 2012-03-14 13:30:00+00:00 -0.105339
# Freq: D, dtype: float64

值得注意的是:
tz_localize和tz_convert是DataFrame和Serise的方法.也还是DatetimeIndex的实例⽅法:

1
2
3
4
5
print(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),并从⼀个时区转换到另⼀个时区:

  • Timestamp对象也能使用tz_localize方法和tz_convert方法
  • 默认情况下,pandas中的时间序列的tz属性为None,但是Timestamp对象支持传入tz属性,在初始化的时候就有tx属性了
  • 使用Timestamp.value获取Timestamp对象的时间戳属性
  • Timestamp对象能和DateOffset对象运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import numpy as np
import pandas as pd


stamp = pd.Timestamp('2011-03-12 04:00')

# Timestamp对象一样能使用tz_localize,tz_convert函数
stamp_utc = stamp.tz_localize('utc')
print(stamp_utc.tz_convert('America/New_York'))
# 2011-03-11 23:00:00-05:00


# Timestamp在初始化的时候就可以传⼊⼀个时区信息
stamp_moscow = pd.Timestamp('2011-03-12 04:00', tz='Europe/Moscow')
print(stamp_moscow)
# 2011-03-12 04:00:00+03:00

# 拥有tz属性(拥有时区信息)
print(stamp_moscow.tz)
# Europe/Moscow


# Timestamp对象在内部保存了⼀个UTC时间戳值
# (自UNIX纪元(1970年1⽉1⽇)算起的纳秒数)
print(stamp_utc.value)
# 1299902400000000000

# 时间戳是不会因为转换时区而改变的
print(stamp_utc.tz_convert('America/New_York').value)
# 1299902400000000000



from pandas.tseries.offsets import Hour

# Hour()属于DateOffset对象
print(type(Hour()))
# <class 'pandas.tseries.offsets.Hour'>

stamp = pd.Timestamp('2012-03-12 01:30', tz='US/Eastern')
print(stamp)
# 2012-03-12 01:30:00-04:00


# Timestamp对象能和DateOffset对象运算
print(stamp + Hour())
# 2012-03-12 02:30:00-04:00


stamp = pd.Timestamp('2012-11-04 00:30', tz='US/Eastern')
print(stamp)
# 2012-11-04 00:30:00-04:00

print(stamp + 2 * Hour())
# 2012-11-04 01:30:00-05:00

不同时区之间的运算

两个不同时区Datetime对象相加减的最终结果就会是UTC。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import numpy as np
import pandas as pd


rng = pd.date_range('3/7/2012 9:30', periods=10, freq='B')
print(rng)
# DatetimeIndex(['2012-03-07 09:30:00', '2012-03-08 09:30:00',
# '2012-03-09 09:30:00', '2012-03-12 09:30:00',
# '2012-03-13 09:30:00', '2012-03-14 09:30:00',
# '2012-03-15 09:30:00', '2012-03-16 09:30:00',
# '2012-03-19 09:30:00', '2012-03-20 09:30:00'],
# dtype='datetime64[ns]', freq='B')


np.random.seed(888)
ts = pd.Series(np.random.randn(len(rng)), index=rng)
print(ts)
# 2012-03-07 09:30:00 -0.176201
# 2012-03-08 09:30:00 0.188876
# 2012-03-09 09:30:00 0.826747
# 2012-03-12 09:30:00 -0.032447
# 2012-03-13 09:30:00 -0.652499
# 2012-03-14 09:30:00 -0.105339
# 2012-03-15 09:30:00 0.217776
# 2012-03-16 09:30:00 0.587282
# 2012-03-19 09:30:00 0.100238
# 2012-03-20 09:30:00 -1.099947
# Freq: B, dtype: float64

ts1 = ts[:7].tz_localize('Europe/London')

print(ts1)
# 2012-03-07 09:30:00+00:00 -0.176201
# 2012-03-08 09:30:00+00:00 0.188876
# 2012-03-09 09:30:00+00:00 0.826747
# 2012-03-12 09:30:00+00:00 -0.032447
# 2012-03-13 09:30:00+00:00 -0.652499
# 2012-03-14 09:30:00+00:00 -0.105339
# 2012-03-15 09:30:00+00:00 0.217776
# Freq: B, dtype: float64

ts2 = ts1[2:].tz_convert('Europe/Moscow')


print(ts2)
# 2012-03-09 13:30:00+04:00 0.826747
# 2012-03-12 13:30:00+04:00 -0.032447
# 2012-03-13 13:30:00+04:00 -0.652499
# 2012-03-14 13:30:00+04:00 -0.105339
# 2012-03-15 13:30:00+04:00 0.217776
# Freq: B, dtype: float64



# 不同时区的DateTime对象相加减
result = ts1 + ts2

print(result)
# 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 1.653494
# 2012-03-12 09:30:00+00:00 -0.064895
# 2012-03-13 09:30:00+00:00 -1.304999
# 2012-03-14 09:30:00+00:00 -0.210679
# 2012-03-15 09:30:00+00:00 0.435552
# Freq: B, dtype: float64


# 两个不同时区Datetime对象相加减的最终结果就会是UTC。
print(result.index)
# DatetimeIndex(['2012-03-07 09:30:00+00:00', '2012-03-08 09:30:00+00:00',
# '2012-03-09 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'],
# dtype='datetime64[ns, UTC]', freq='B')

11.5 时期及其算术运算

Period类:

  • 时期(period)表示的是时间区间,⽐如数日、数⽉、数季、数年等。(例如:p=pd.Period(2007,freq='A-DEC'),p对象表示的从2007年1⽉1⽇到2007年12⽉31⽇之间的整段时间。)
  • 因为是时间区间,所以可以传入freq频率参数
  • period_range函数可⽤于创建规则的时期范围,返回的是PeriodIndex对象
  • 既然有PeriodIndex对象,自然可以用来做Index
  • 就像DateTimeIndex一样,DateTimeIndex索引不一定传入要DateTimeIndex对象,传入字符串组成的列表也可以
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import numpy as np
import pandas as pd


p = pd.Period(2007, freq='A-DEC')
print(p)
# 2007

print(p + 5)
# 2012

print(p - 2)
# 2005

# 两个Period对象拥有相同的频率,则它们的差就是它们之间的单位数量
print(pd.Period('2014', freq='A-DEC') - p)
# 7


# period_range函数可⽤于创建规则的时期范围,返回PeriodIndex对象
rng = pd.period_range('2000-01-01', '2000-03-30', freq='M')
print(rng)
# PeriodIndex(['2000-01', '2000-02', '2000-03'], dtype='period[M]', freq='M')


# 使用PeriodIndex作为Index
np.random.seed(888)
print(pd.Series(np.random.randn(3), index=rng))
# 2000-01 -0.176201
# 2000-02 0.188876
# 2000-03 0.826747
# Freq: M, dtype: float64


# 就像DateTimeIndex一样,DateTimeIndex索引不一定传入要DateTimeIndex对象
# 传入字符串组成的列表也可以
values = ['2001Q3', '2002Q2', '2003Q1']
index = pd.PeriodIndex(values, freq='Q-DEC')
print(index)
# PeriodIndex(['2001Q3', '2002Q2', '2003Q1'], dtype='period[Q-DEC]', freq='Q-DEC')

时期的频率转换

Period和PeriodIndex对象都可以通过其asfreq⽅法被转换成别的频率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
import pandas as pd


p = pd.Period('2007', freq='A-DEC')
print(p)
# 2007

# 将时间转为年初第一个月
print(p.asfreq('M', how='start'))
# 2007-01

# 将时间站位年末最后一个月
print(p.asfreq('M', how='end'))
# 2007-12

以将Period('2007','A-DEC')看做⼀个被划分为多个⽉度时期的时间段中的游标
1557729803602

在A-JUN频率中:一年的范围是从7月到下一年的6月

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
import pandas as pd


# 在A-JUN频率中,⽉份“2007年8⽉”实际上是属于周期“2008年”的
# 一年的范围是从7月到下一年的6月
p = pd.Period('2007', freq='A-JUN')
print(p)
# 2007

print(p.asfreq('M', 'start'))
# 2006-07

print(p.asfreq('M', 'end'))
# 2007-06

高频率转换为低频率时,父时期(superperiod)是由子时期(subperiod)所属的位置决定的。

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
import pandas as pd


p = pd.Period('Aug-2007', 'M')
print(p)
# 2007-08

print(p.asfreq('A-JUN'))
# 2008

print(p.freq)
# <MonthEnd>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import numpy as np
import pandas as pd


np.random.seed(888)
rng = pd.period_range('2006', '2009', freq='A-DEC')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
print(ts)
# 2006 -0.176201
# 2007 0.188876
# 2008 0.826747
# 2009 -0.032447
# Freq: A-DEC, dtype: float64

print(ts.asfreq('M', how='start'))
# 2006-01 -0.176201
# 2007-01 0.188876
# 2008-01 0.826747
# 2009-01 -0.032447
# Freq: M, dtype: float64


print(ts.asfreq('B', how='end'))
# 2006-12-29 -0.176201
# 2007-12-31 0.188876
# 2008-12-31 0.826747
# 2009-12-31 -0.032447
# Freq: B, dtype: float64


print(ts.asfreq('B', how='start'))
# 2006-01-02 -0.176201
# 2007-01-01 0.188876
# 2008-01-01 0.826747
# 2009-01-01 -0.032447
# Freq: B, dtype: float64

按季度计算的时期频率

pandas⽀持12种季度型频率,即QJAN到Q-DEC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
import pandas as pd


p = pd.Period('2012Q4', freq='Q-JAN')
print(p)
# 2012Q4


# 在以1⽉结束的财年中,2012Q4是从11⽉到1⽉
print(p.asfreq('D', 'start'))
# 2011-11-01
print(p.asfreq('D', 'end'))
# 2012-01-31



p = pd.Period('2012Q4', freq='Q-JAN')

# 获取该季度倒数第⼆个⼯作⽇下午4点的时间戳,
p4pm = (p.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
print(p4pm)
# 2012-01-30 16:00

print(p4pm.to_timestamp())
# 2012-01-30 16:00:00

20190513150924

period_range传入像Q-JAN一样的freq可以生成季度型范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import numpy as np
import pandas as pd


# period_range传入像Q-JAN一样的freq可以生成季度型范围
rng = pd.period_range('2011Q3', '2012Q4', freq='Q-JAN')
ts = pd.Series(np.arange(len(rng)), index=rng)
print(ts)
# 2011Q3 0
# 2011Q4 1
# 2012Q1 2
# 2012Q2 3
# 2012Q3 4
# 2012Q4 5
# Freq: Q-JAN, dtype: int32


# 获取该季度倒数第⼆个⼯作⽇下午4点的时间戳,
new_rng = (rng.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
ts.index = new_rng.to_timestamp()
print(ts)
# 2010-10-28 16:00:00 0
# 2011-01-28 16:00:00 1
# 2011-04-28 16:00:00 2
# 2011-07-28 16:00:00 3
# 2011-10-28 16:00:00 4
# 2012-01-30 16:00:00 5
# dtype: int32

将Timestamp转换为Period(及其反向过程)

  • to_period()方法:
    时间戳索引转换为时期索引
    (也就是TimestampIndex转为PeriodIndex)
  • to_timestamp方法:
    将时期索引转为时间戳索引
    (也就是说PeriodIndex转为TimestampIndex)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import numpy as np
import pandas as pd


np.random.seed(888)
rng = pd.date_range('2000-01-01', periods=3, freq='M')
ts = pd.Series(np.random.randn(3), index=rng)
print(ts)
# 2000-01-31 -0.176201
# 2000-02-29 0.188876
# 2000-03-31 0.826747
# Freq: M, dtype: float64

# ts的行索引为DatetimeIndex
print(type(ts.index))
# <class 'pandas.core.indexes.datetimes.DatetimeIndex'>


pts = ts.to_period()
print(pts)
# 2000-01 -0.176201
# 2000-02 0.188876
# 2000-03 0.826747
# Freq: M, dtype: float64

# pts的行索引为PeriodIndex
print(type(pts.index))
# <class 'pandas.core.indexes.period.PeriodIndex'>



# 新PeriodIndex的频率默认是从时间戳推断⽽来的,你也可以指定任何别的频率
rng = pd.date_range('1/29/2000', periods=6, freq='D')
ts2 = pd.Series(np.random.randn(6), index=rng)
print(ts2)
# 2000-01-29 -0.032447
# 2000-01-30 -0.652499
# 2000-01-31 -0.105339
# 2000-02-01 0.217776
# 2000-02-02 0.587282
# 2000-02-03 0.100238
# Freq: D, dtype: float64

print(type(ts2.index))
# <class 'pandas.core.indexes.datetimes.DatetimeIndex'>


print(ts2.to_period('M'))
# 2000-01 -0.032447
# 2000-01 -0.652499
# 2000-01 -0.105339
# 2000-02 0.217776
# 2000-02 0.587282
# 2000-02 0.100238
# Freq: M, dtype: float64

print(ts2.to_period('M').index)
# PeriodIndex(['2000-01', '2000-01', '2000-01', '2000-02', '2000-02', '2000-02'], dtype='period[M]', freq='M')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import numpy as np
import pandas as pd


np.random.seed(888)
rng = pd.date_range('1/29/2000', periods=6, freq='D')
ts2 = pd.Series(np.random.randn(6), index=rng)
print(ts2)
# 2000-01-29 -0.176201
# 2000-01-30 0.188876
# 2000-01-31 0.826747
# 2000-02-01 -0.032447
# 2000-02-02 -0.652499
# 2000-02-03 -0.105339
# Freq: D, dtype: float64

print(type(ts2.index))
# <class 'pandas.core.indexes.datetimes.DatetimeIndex'>


# 使用to_period将DateTimeIndex改为PeriodIndex
ts3 = ts2.to_period('M')
print(ts3)
# 2000-01 -0.176201
# 2000-01 0.188876
# 2000-01 0.826747
# 2000-02 -0.032447
# 2000-02 -0.652499
# 2000-02 -0.105339
# Freq: M, dtype: float64

print(type(ts3.index))
# <class 'pandas.core.indexes.period.PeriodIndex'>

通过数组创建PeriodIndex

有可能年度(year)和季度(quarter)不再同一列,这时就可以使用PeriodIndex的year参数和quarter参数将二者合起来,创建一个新的PeriodIndex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import numpy as np
import pandas as pd


data = pd.read_csv('examples/macrodata.csv')

# 年度(year)和季度(quarter)在不同列
print(data.head(3))
# year quarter realgdp realcons realinv realgovt realdpi cpi \
# 0 1959.0 1.0 2710.349 1707.4 286.898 470.045 1886.9 28.98
# 1 1959.0 2.0 2778.801 1733.7 310.859 481.301 1919.7 29.15
# 2 1959.0 3.0 2775.488 1751.8 289.226 491.260 1916.4 29.35
# m1 tbilrate unemp pop infl realint
# 0 139.7 2.82 5.8 177.146 0.00 0.00
# 1 141.7 3.08 5.1 177.830 2.34 0.74
# 2 140.5 3.82 5.3 178.657 2.74 1.09

print(data.year)
# 0 1959.0
# 1 1959.0
# ...
# 201 2009.0
# 202 2009.0
# Name: year, Length: 203, dtype: float64

print(data.quarter)
# 0 1.0
# 1 2.0
# ...
# 201 2.0
# 202 3.0
# Name: quarter, Length: 203, dtype: float64


# 将不同的列传入PeriodIndex不同的参数,组合年度和季度
index = pd.PeriodIndex(year=data.year, quarter=data.quarter,
freq='Q-DEC')

print(index)
# PeriodIndex(['1959Q1', '1959Q2', '1959Q3', '1959Q4', '1960Q1', '1960Q2',
# '1960Q3', '1960Q4', '1961Q1', '1961Q2',
# ...
# '2007Q2', '2007Q3', '2007Q4', '2008Q1', '2008Q2', '2008Q3',
# '2008Q4', '2009Q1', '2009Q2', '2009Q3'],
# dtype='period[Q-DEC]', length=203, freq='Q-DEC')

data.index = index

print(data.infl)
# 1959Q1 0.00
# 1959Q2 2.34
# ...
# 2009Q2 3.37
# 2009Q3 3.56
# Freq: Q-DEC, Name: infl, Length: 203, dtype: float64

11.6 重采样及频率转换

重采样(resampling)指的是将时间序列从⼀个频率转换到另⼀个频率的处理过程。

  • 高频率数据聚合到低频率称为降采样(downsampling)。
  • 低频率数据转换到高频率称为升采样(upsampling)。
  • 并不是所有的重采样都能被划分到这两个⼤类中。

升采样与降采样:

  • 降采样:时间粒度变大。例如,原来是按天统计的数据,现在变成按周统计。降采样会涉及到数据的聚合,比如天数据变成周数据,那么就得对一周的7天数据聚合,聚合的方式可以是求和,求均值等等。

  • 升采样:时间粒度变小。例如,原来是按周统计的数据,现在变成按天统计。升采样会涉及到数据的填充,根据填充的方法不同填充的数据也就不同。

各种频率转换⼯作:resample⽅法

==resample有类似于groupby的API==,调⽤resample可以分组数据,然后会调⽤⼀个聚合函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import numpy as np
import pandas as pd


rng = pd.date_range('2000-01-01', periods=100, freq='D')
ts = pd.Series(np.arange(len(rng)), index=rng)
print(ts)
# 2000-01-01 0
# 2000-01-02 1
# ..
# 2000-04-08 98
# 2000-04-09 99
# Freq: D, Length: 100, dtype: int32


# 根据月来聚合PeriodIndex,计算平均值
print(ts.resample('M').mean())
# 2000-01-31 15
# 2000-02-29 45
# 2000-03-31 75
# 2000-04-30 95
# Freq: M, dtype: int32

print(ts.resample('M', kind='period').mean())
# 2000-01 15
# 2000-02 45
# 2000-03 75
# 2000-04 95
# Freq: M, dtype: int32

resample⽅法的参数

9656e742f7

降采样

使用降采样必须保证所有时间段的并集必须能组成整个时间帧

使用resample对数据进⾏降采样时,需要考虑两样东⻄:

  • 各区间哪边是闭合的。
  • 如何标记各个聚合⾯元,用区间的开头还是末尾。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import numpy as np
import pandas as pd


rng = pd.date_range('2000-01-01', periods=12, freq='T')
ts = pd.Series(np.arange(12), index=rng)
print(ts)
# 2000-01-01 00:00:00 0
# 2000-01-01 00:01:00 1
# 2000-01-01 00:02:00 2
# 2000-01-01 00:03:00 3
# 2000-01-01 00:04:00 4
# 2000-01-01 00:05:00 5
# 2000-01-01 00:06:00 6
# 2000-01-01 00:07:00 7
# 2000-01-01 00:08:00 8
# 2000-01-01 00:09:00 9
# 2000-01-01 00:10:00 10
# 2000-01-01 00:11:00 11
# Freq: T, dtype: int32


# 将高频的1分钟聚合成5分钟
# 默认情况下,⾯元的右边界是包含的,因此00:00到00:05的区间中是包含00:05的。
print(ts.resample('5min', closed='right').count())
# 1999-12-31 23:55:00 1
# 2000-01-01 00:00:00 5
# 2000-01-01 00:05:00 5
# 2000-01-01 00:10:00 1
# Freq: 5T, dtype: int64


# 传⼊label='right'即使用区间的右边值作为索引:
print(ts.resample('5min', closed='right', label='right').count())
# 2000-01-01 00:00:00 1
# 2000-01-01 00:05:00 5
# 2000-01-01 00:10:00 5
# 2000-01-01 00:15:00 1
# Freq: 5T, dtype: int64


# 传⼊closed='left'会让区间以左边界闭合
print(ts.resample('5min', closed='left').count())
# #2000-01-01 00:00:00 5
# 2000-01-01 00:05:00 5
# 2000-01-01 00:10:00 2
# Freq: 5T, dtype: int64


# loffset:对索引做位移
print(ts.resample('5min', closed='right',label='right', loffset='-1s').count())
# 1999-12-31 23:59:59 1
# 2000-01-01 00:04:59 5
# 2000-01-01 00:09:59 5
# 2000-01-01 00:14:59 1
# Freq: 5T, dtype: int64

closed参数说明:

  • closed用于区间的开闭.
    closed = ‘right’ 左开右闭
    closed = ‘left’ 左闭右开

  • 区间的范围:
    比如periods范围是2018年1月1号-12号.

    1. 执行a = ts.resample('5D', closed='left').sum()
      那么区间就是:

      1
      2
      3
      区间1:[1, 6)
      区间2: [6, 11)
      区间3:[11, 16)
    2. 执行a = ts.resample('5D', closed='right').sum()
      那么区间就是:

      1
      2
      3
      4
      区间1:(27, 1]
      区间2:(1, 6]
      区间3: (6, 11]
      区间4:(11, 16]
  • 总结:
    ==区间只需看看第一天==:

    1. 如果closed='left',那么就是以第一天开始,往后推
    2. 如果closed='right',那么就是以第一天结束,往前推

label参数说明:

  • label参数决定索引的值
  • label为left的时候,就以区间左边的那个日期作为索引;label为right的时候,就以区间的右边那个日期作为索引。

29702537

loffset参数:
行索引做位移


升采样

就像前面说的,升采样时间粒度变小。例如,原来是按周统计的数据,现在变成按天统计。升采样会涉及到数据的填充

四种填充方法(实际是三种):

  • asfreq:
    不填充。那么对应无值的地方,用NaN代替。
  • ffill/pad:
    用前值填充。用前面的值填充无值的地方。
  • bfill:
    用后值填充。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import numpy as np
import pandas as pd


rng = pd.date_range('20180101', periods=2)
ts = pd.Series(np.arange(2), index=rng)

print(ts)
# 2018-01-01 0
# 2018-01-02 1
# Freq: D, dtype: int32

ts_6h_asfreq = ts.resample('6H').asfreq()
print(ts_6h_asfreq)
# 2018-01-01 00:00:00 0.0
# 2018-01-01 06:00:00 NaN
# 2018-01-01 12:00:00 NaN
# 2018-01-01 18:00:00 NaN
# 2018-01-02 00:00:00 1.0
# Freq: 6H, dtype: float64

ts_6h_pad = ts.resample('6H').pad()
print(ts_6h_pad)
# 2018-01-01 00:00:00 0
# 2018-01-01 06:00:00 0
# 2018-01-01 12:00:00 0
# 2018-01-01 18:00:00 0
# 2018-01-02 00:00:00 1
# Freq: 6H, dtype: int32

ts_6h_ffill = ts.resample('6H').ffill()
print(ts_6h_ffill)
# 2018-01-01 00:00:00 0
# 2018-01-01 06:00:00 0
# 2018-01-01 12:00:00 0
# 2018-01-01 18:00:00 0
# 2018-01-02 00:00:00 1
# Freq: 6H, dtype: int32

ts_6h_bfill = ts.resample('6H').bfill()
print(ts_6h_bfill)
# 2018-01-01 00:00:00 0
# 2018-01-01 06:00:00 1
# 2018-01-01 12:00:00 1
# 2018-01-01 18:00:00 1
# 2018-01-02 00:00:00 1
# Freq: 6H, dtype: int32

OHLC重采样

OHLC重采样:
计算各⾯元的四个值:第⼀个值(open,开盘)、最后⼀个值(close,收盘)、最⼤值(high,最⾼)以及最⼩值(low,最低)。

使用how参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import numpy as np
import pandas as pd


rng = pd.date_range('2000-01-01', periods=12, freq='T')
ts = pd.Series(np.arange(12), index=rng)
print(ts)
# 2000-01-01 00:00:00 0
# 2000-01-01 00:01:00 1
# 2000-01-01 00:02:00 2
# 2000-01-01 00:03:00 3
# 2000-01-01 00:04:00 4
# 2000-01-01 00:05:00 5
# 2000-01-01 00:06:00 6
# 2000-01-01 00:07:00 7
# 2000-01-01 00:08:00 8
# 2000-01-01 00:09:00 9
# 2000-01-01 00:10:00 10
# 2000-01-01 00:11:00 11
# Freq: T, dtype: int32


# 使用how参数
print(ts.resample('5min').ohlc())
# open high low close
# 2000-01-01 00:00:00 0 4 0 4
# 2000-01-01 00:05:00 5 9 5 9
# 2000-01-01 00:10:00 10 11 10 11

升采样和插值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import numpy as np
import pandas as pd


frame = pd.DataFrame(np.arange(10000,10008).reshape(2,4),
index=pd.date_range('1/1/2000', periods=2,freq='W-WED'),
columns=['Colorado', 'Texas', 'New York', 'Ohio'])
print(frame)
# Colorado Texas New York Ohio
# 2000-01-05 10000 10001 10002 10003
# 2000-01-12 10004 10005 10006 10007


# asfreq不填充数据
df_daily = frame.resample('D').asfreq()
print(df_daily)
# Colorado Texas New York Ohio
# 2000-01-05 10000.0 10001.0 10002.0 10003.0
# 2000-01-06 NaN NaN NaN NaN
# 2000-01-07 NaN NaN NaN NaN
# 2000-01-08 NaN NaN NaN NaN
# 2000-01-09 NaN NaN NaN NaN
# 2000-01-10 NaN NaN NaN NaN
# 2000-01-11 NaN NaN NaN NaN
# 2000-01-12 10004.0 10005.0 10006.0 10007.0


# ffill:前向填充
print(frame.resample('D').ffill())
# Colorado Texas New York Ohio
# 2000-01-05 10000 10001 10002 10003
# 2000-01-06 10000 10001 10002 10003
# 2000-01-07 10000 10001 10002 10003
# 2000-01-08 10000 10001 10002 10003
# 2000-01-09 10000 10001 10002 10003
# 2000-01-10 10000 10001 10002 10003
# 2000-01-11 10000 10001 10002 10003
# 2000-01-12 10004 10005 10006 10007


# limit:限制填充数量
print(frame.resample('D').ffill(limit=2))
# Colorado Texas New York Ohio
# 2000-01-05 10000.0 10001.0 10002.0 10003.0
# 2000-01-06 10000.0 10001.0 10002.0 10003.0
# 2000-01-07 10000.0 10001.0 10002.0 10003.0
# 2000-01-08 NaN NaN NaN NaN
# 2000-01-09 NaN NaN NaN NaN
# 2000-01-10 NaN NaN NaN NaN
# 2000-01-11 NaN NaN NaN NaN
# 2000-01-12 10004.0 10005.0 10006.0 10007.0


# 每周的星期二,所以一行就是一周(周二到下周一)
print(frame.resample('W-THU').ffill())
# Colorado Texas New York Ohio
# 2000-01-06 10000 10001 10002 10003
# 2000-01-13 10004 10005 10006 10007

通过时期进行重采样

对那些使⽤时期索引的数据进行重采样与时间戳很像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import numpy as np
import pandas as pd


frame = pd.DataFrame(np.arange(10000,10096).reshape(24,4),
index=pd.period_range('1-2000', '12-2001',freq='M'),
columns=['Colorado', 'Texas', 'New York', 'Ohio'])

print(frame[:5])
# Colorado Texas New York Ohio
# 2000-01 10000 10001 10002 10003
# 2000-02 10004 10005 10006 10007
# 2000-03 10008 10009 10010 10011
# 2000-04 10012 10013 10014 10015
# 2000-05 10016 10017 10018 10019


annual_frame = frame.resample('A-DEC').mean()
print(annual_frame)
# Colorado Texas New York Ohio
# 2000 10022 10023 10024 10025
# 2001 10070 10071 10072 10073



print(annual_frame.resample('Q-DEC').ffill())
# Colorado Texas New York Ohio
# 2000Q1 10022 10023 10024 10025
# 2000Q2 10022 10023 10024 10025
# 2000Q3 10022 10023 10024 10025
# 2000Q4 10022 10023 10024 10025
# 2001Q1 10070 10071 10072 10073
# 2001Q2 10070 10071 10072 10073
# 2001Q3 10070 10071 10072 10073
# 2001Q4 10070 10071 10072 10073


# convention参数默认为'end',可设置为'start':
print(annual_frame.resample('Q-DEC', convention='end').ffill())
# Colorado Texas New York Ohio
# 2000Q4 10022 10023 10024 10025
# 2001Q1 10022 10023 10024 10025
# 2001Q2 10022 10023 10024 10025
# 2001Q3 10022 10023 10024 10025
# 2001Q4 10070 10071 10072 10073

由于时期指的是时间区间,所以升采样和降采样的规则就⽐较严格:

  • 在降采样中,⽬标频率必须是源频率的子时期(subperiod)。
  • 在升采样中,⽬标频率必须是源频率的超时期(superperiod)。

11.7 移动窗⼝函数

什么是滑动(移动)窗口?

为了提升数据的准确性,将某个点的取值扩大到包含这个点的一段区间,用区间来进行判断,这个区间就是窗口。

例如想使用2011年1月1日的一个数据,单取这个时间点的数据当然是可行的,但是太过绝对,有没有更好的办法呢?可以选取2010年12月16日到2011年1月15日,通过求均值来评估1月1日这个点的值,2010-12-16到2011-1-15就是一个窗口,窗口的长度window=30.

移动窗口就是窗口向一端滑行,每次滑动(行)并不是区间整块的滑行,而是一个单位一个单位的滑行。例如窗口2010-12-16到2011-1-15,下一个窗口并不是2011-1-15到2011-2-15,而是2010-12-17到2011-1-16(假设数据的截取是以天为单位),==整体向右移动一个单位,而不是一个窗口==。这样统计的每个值始终都是30单位的均值。 窗口中的值从覆盖整个窗口的位置开始产生,在此之前即为NaN,举例如下:窗口大小为10,前9个都不足够为一个一个窗口的长度,因此都无法取值。

rolling函数返回的是window对象rolling子类,可以通过调用该对象的mean(),sum(),std(),count()等函数计算返回窗口的值,还可以通过该对象的apply(func)函数,通过自定义函数计算窗口的特定的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import numpy as np
import pandas as pd


df = pd.DataFrame({'B': [0, 1, 2, np.nan, 4]})

# 1.0 = 0+1
# 3.0 = 1+2
# NaN = 2+NaN
# NaN = NaN+4
print(df.rolling(2).sum())
# B
# 0 NaN
# 1 1.0
# 2 3.0
# 3 NaN
# 4 NaN


s = [1,2,3,5,6,10,12,14,12,30]
print(pd.Series(s))
# 0 1
# 1 2
# 2 3
# 3 5
# 4 6
# 5 10
# 6 12
# 7 14
# 8 12
# 9 30
# dtype: int64


print(pd.Series(s).rolling(window=3).mean())
# 0 NaN
# 1 NaN
# 2 2.000000
# 3 3.333333
# 4 4.666667
# 5 7.000000
# 6 9.333333
# 7 12.000000
# 8 12.666667
# 9 18.666667
# dtype: float64

在移动窗口上计算的各种统计函数也是⼀类常⻅于时间序列的数组变换。这样可以圆滑噪⾳数据或断裂数据。这种函数称为移动窗口函数(moving windowfunction),其中还包括那些窗⼝不定⻓的函数(如指数加权移动平均)。跟其他统计函数⼀样,移动窗口函数也会自动排除缺失值

rolling运算符,它与resample和groupby很像。可以在TimeSeries或DataFrame以及⼀个window上调⽤它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

close_px_all = pd.read_csv('examples/stock_px_2.csv',parse_dates=True, index_col=0)

print(close_px_all.head())
# AAPL MSFT XOM SPX
# 2003-01-02 7.40 21.11 29.22 909.03
# 2003-01-03 7.45 21.14 29.24 908.59
# 2003-01-06 7.45 21.52 29.96 929.01
# 2003-01-07 7.43 21.93 28.95 922.93
# 2003-01-08 7.28 21.31 28.83 909.93

close_px = close_px_all[['AAPL', 'MSFT', 'XOM']]

close_px = close_px.resample('B').ffill()

print(close_px.AAPL.plot())
# AxesSubplot(0.125,0.11;0.775x0.77)


# 调用rolling
print(close_px.AAPL.rolling(250).mean().plot())
# AxesSubplot(0.125,0.11;0.775x0.77)

plt.show()

Figure_10

表达式rolling(250)与groupby很像,但不是对其进⾏分组、创建⼀个按照250天分组的滑动窗⼝对象。然后,我们就得到了苹果公司股价的250天的移动窗⼝。

DataFrame.rolling(window, min_periods=None, center=False, win_type=None, on=None, axis=0, closed=None)

  • min_periods参数:
    最少需要有值的观测点的数量,就是说滑动窗口最少需要多少个非NaN的数字
  • center参数:
    如果设为True,表示在取窗口覆盖的区间时,以当前label为中心,向两边取,若为False则表示以当前label为窗口的最右侧,向左侧取,默认为False.
    要注意的是,当为True时,如果窗口长度为奇数,则中心位置很好确定,就是最中间的位置,但是如果长度为偶数,则默认中心位置为中间偏右的那一个位置
  • win_type参数:
    表示不同的窗口类型,可以通过这个参数给窗口成员赋予不同的权重,默认为等权重
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

close_px_all = pd.read_csv('examples/stock_px_2.csv',parse_dates=True, index_col=0)

print(close_px_all.head())
# AAPL MSFT XOM SPX
# 2003-01-02 7.40 21.11 29.22 909.03
# 2003-01-03 7.45 21.14 29.24 908.59
# 2003-01-06 7.45 21.52 29.96 929.01
# 2003-01-07 7.43 21.93 28.95 922.93
# 2003-01-08 7.28 21.31 28.83 909.93

close_px = close_px_all[['AAPL', 'MSFT', 'XOM']]

close_px = close_px.resample('B').ffill()


# min_periods:最少需要有值的观测点的数量,就是说滑动窗口最少需要多少个非NaN的数字
appl_std250 = close_px.AAPL.rolling(250, min_periods=10).std()
print(appl_std250[5:12])
# 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

appl_std250.plot()
plt.show()

Figure_12

rolling还可以接受一个指定固定⼤⼩时间补偿字符串作为一个窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

close_px_all = pd.read_csv('examples/stock_px_2.csv',parse_dates=True, index_col=0)

print(close_px_all.head())
# AAPL MSFT XOM SPX
# 2003-01-02 7.40 21.11 29.22 909.03
# 2003-01-03 7.45 21.14 29.24 908.59
# 2003-01-06 7.45 21.52 29.96 929.01
# 2003-01-07 7.43 21.93 28.95 922.93
# 2003-01-08 7.28 21.31 28.83 909.93

close_px = close_px_all[['AAPL', 'MSFT', 'XOM']]

close_px = close_px.resample('B').ffill()

# rolling还可以接受一个指定固定⼤⼩时间补偿字符串作为一个窗口
# 计算计算20天的滚动均值
print(close_px.rolling('20D').mean())
# AAPL MSFT XOM
# 2003-01-02 7.400000 21.110000 29.220000
# 2003-01-03 7.425000 21.125000 29.230000
# ... ... ... ...
# 2011-10-13 388.826429 25.961429 73.905000
# 2011-10-14 391.038000 26.048667 74.185333
# [2292 rows x 3 columns]

扩展窗口:

“扩展”意味着,从时间序列的起始处开始窗口,增加窗口直到它超过所有的序列

DataFrame.expanding(min_periods=1, center=False, axis=0)
其中参数的意义和rolling一样,只是不是固定窗口长度,其长度是不断的扩大的

rolling和expanding都是类似的,假设目的是查看股票市场价格随着时间的变化,不同的是rolling average算的是最近一个窗口期(比如说20天)的一个平均值,过了一天这个窗口又会向下滑动一天算20天的平均值;expanding的话,是从第一个值就开始累加地计算平均值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import numpy as np
import pandas as pd

df = pd.DataFrame({'B': [1,1,1,1,1,1,1,1]})

print(df.rolling(2).sum())
# B
# 0 NaN
# 1 2.0
# 2 2.0
# 3 2.0
# 4 2.0
# 5 2.0
# 6 2.0
# 7 2.0

print(df.expanding().sum())
# B
# 0 1.0
# 1 2.0
# 2 3.0
# 3 4.0
# 4 5.0
# 5 6.0
# 6 7.0
# 7 8.0

print(df.expanding(3).sum())
# B
# 0 NaN
# 1 NaN
# 2 3.0
# 3 4.0
# 4 5.0
# 5 6.0
# 6 7.0
# 7 8.0

指数加权函数

  • rolling:简单移动
  • ewm:指数加权滑动

指数加权函数会赋予近期的观测值更大的权数,因此,它能适应更快的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

close_px_all = pd.read_csv('examples/stock_px_2.csv',parse_dates=True, index_col=0)

print(close_px_all.head())
# AAPL MSFT XOM SPX
# 2003-01-02 7.40 21.11 29.22 909.03
# 2003-01-03 7.45 21.14 29.24 908.59
# 2003-01-06 7.45 21.52 29.96 929.01
# 2003-01-07 7.43 21.93 28.95 922.93
# 2003-01-08 7.28 21.31 28.83 909.93

close_px = close_px_all[['AAPL', 'MSFT', 'XOM']]

close_px = close_px.resample('B').ffill()

aapl_px = close_px.AAPL['2006':'2007']



ma60 = aapl_px.rolling(30, min_periods=20).mean()

# 加权指数函数
ewma60 = aapl_px.ewm(span=30).mean()


ma60.plot(style='b--', label='Simple MA')
ewma60.plot(style='r-', label='EW MA')
plt.legend()

plt.show()

Figure_15

⼆元移动窗口函数

一些统计计算符,比如相关性和协方差,需要在两个时间序列上进行计算。
例如,经济分析通常喜欢比较一只股票与基础指数标普500之间的相关性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

close_px_all = pd.read_csv('examples/stock_px_2.csv',parse_dates=True, index_col=0)

print(close_px_all.head())
# AAPL MSFT XOM SPX
# 2003-01-02 7.40 21.11 29.22 909.03
# 2003-01-03 7.45 21.14 29.24 908.59
# 2003-01-06 7.45 21.52 29.96 929.01
# 2003-01-07 7.43 21.93 28.95 922.93
# 2003-01-08 7.28 21.31 28.83 909.93

close_px = close_px_all[['AAPL', 'MSFT', 'XOM']]
close_px = close_px.resample('B').ffill()

spx_px = close_px_all['SPX']

print(spx_px.head())
# 2003-01-02 909.03
# 2003-01-03 908.59
# 2003-01-06 929.01
# 2003-01-07 922.93
# 2003-01-08 909.93
# Name: SPX, dtype: float64

print(close_px.head())
# AAPL MSFT XOM
# 2003-01-02 7.40 21.11 29.22
# 2003-01-03 7.45 21.14 29.24
# 2003-01-06 7.45 21.52 29.96
# 2003-01-07 7.43 21.93 28.95
# 2003-01-08 7.28 21.31 28.83


# pct_change()函数。此函数将每个元素与其前一个元素进行比较,并计算变化百分比。
spx_rets = spx_px.pct_change()
returns = close_px.pct_change()


print(spx_rets.head())
# 2003-01-02 NaN
# 2003-01-03 -0.000484
# 2003-01-06 0.022474
# 2003-01-07 -0.006545
# 2003-01-08 -0.014086
# Name: SPX, dtype: float64


print(returns.head())
# AAPL MSFT XOM
# 2003-01-02 NaN NaN NaN
# 2003-01-03 0.006757 0.001421 0.000684
# 2003-01-06 0.000000 0.017975 0.024624
# 2003-01-07 -0.002685 0.019052 -0.033712
# 2003-01-08 -0.020188 -0.028272 -0.004145


# corr聚合函数计算AAPL与spx_rets滚动相关系数
corr = returns.AAPL.rolling(125, min_periods=100).corr(spx_rets)

corr.plot()

plt.show()

Figure_16

⽤户定义的移动窗口函数

通过rolling().apply()方法,可以在移动窗口上使用自己定义的函数。唯一需要满足的是,在数组的每一个片段上,函数必须产生单个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import percentileofscore

close_px_all = pd.read_csv('examples/stock_px_2.csv',parse_dates=True, index_col=0)
close_px = close_px_all[['AAPL', 'MSFT', 'XOM']].resample('B').ffill()


print(close_px.head())
# AAPL MSFT XOM
# 2003-01-02 7.40 21.11 29.22
# 2003-01-03 7.45 21.14 29.24
# 2003-01-06 7.45 21.52 29.96
# 2003-01-07 7.43 21.93 28.95
# 2003-01-08 7.28 21.31 28.83


# pct_change()函数。此函数将每个元素与其前一个元素进行比较,并计算变化百分比。
returns = close_px.pct_change()


print(returns.head())
# AAPL MSFT XOM
# 2003-01-02 NaN NaN NaN
# 2003-01-03 0.006757 0.001421 0.000684
# 2003-01-06 0.000000 0.017975 0.024624
# 2003-01-07 -0.002685 0.019052 -0.033712
# 2003-01-08 -0.020188 -0.028272 -0.004145


# 自定义的函数
score_at_2percent = lambda x: percentileofscore(x, 0.02)

# rolling().apply()
result = returns.AAPL.rolling(250).apply(score_at_2percent)
result.plot()

plt.show()

Figure_17