pandas 中有3类窗口,分别是滑动窗口 rolling
、扩张窗口 expanding
以及指数加权窗口 ewm
。
要使用滑窗函数,就必须先要对一个序列使用 .rolling
得到滑窗对象,其最重要的参数为窗口大小 window
。
In [95]: s = pd.Series([1,2,3,4,5])
In [96]: roller = s.rolling(window = 3)
In [97]: roller
Out[97]: Rolling [window=3,center=False,axis=0]
在得到了滑窗对象后,能够使用相应的聚合函数进行计算,需要注意的是窗口包含当前行所在的元素,例如在第四个位置进行均值运算时,应当计算(2+3+4)/3
,而不是(1+2+3)/3
:
In [98]: roller.mean()
Out[98]:
0 NaN
1 NaN
2 2.0
3 3.0
4 4.0
dtype: float64
In [99]: roller.sum()
Out[99]:
0 NaN
1 NaN
2 6.0
3 9.0
4 12.0
dtype: float64
对于滑动相关系数或滑动协方差的计算,可以如下写出:
In [100]: s2 = pd.Series([1,2,6,16,30])
In [101]: roller.cov(s2)
Out[101]:
0 NaN
1 NaN
2 2.5
3 7.0
4 12.0
dtype: float64
In [102]: roller.corr(s2)
Out[102]:
0 NaN
1 NaN
2 0.944911
3 0.970725
4 0.995402
dtype: float64
此外,还支持使用 apply 传入自定义函数,其传入值是对应窗口的 Series ,例如上述的均值函数可以等效表示:
In [103]: roller.apply(lambda x:x.mean())
Out[103]:
0 NaN
1 NaN
2 2.0
3 3.0
4 4.0
dtype: float64
shift, diff, pct_change
是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,这里的 n 可以为负,表示反方向的类似操作。
函数 | 说明 |
---|---|
shift | 取向前第 n 个元素的值 |
diff | 与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分) |
pct_change | 与向前第 n 个元素相比计算增长率 |
In [104]: s = pd.Series([1,3,6,10,15])
In [105]: s.shift(2)
Out[105]:
0 NaN
1 NaN
2 1.0
3 3.0
4 6.0
dtype: float64
In [106]: s.diff(3)
Out[106]:
0 NaN
1 NaN
2 NaN
3 9.0
4 12.0
dtype: float64
In [107]: s.pct_change()
Out[107]:
0 NaN
1 2.000000
2 1.000000
3 0.666667
4 0.500000
dtype: float64
In [108]: s.shift(-1)
Out[108]:
0 3.0
1 6.0
2 10.0
3 15.0
4 NaN
dtype: float64
In [109]: s.diff(-2)
Out[109]:
0 -5.0
1 -7.0
2 -9.0
3 NaN
4 NaN
dtype: float64
将其视作类滑窗函数的原因是,它们的功能可以用窗口大小为 n+1 的 rolling 方法等价代替
In [110]: s.rolling(3).apply(lambda x:list(x)[0]) # s.shift(2)
Out[110]:
0 NaN
1 NaN
2 1.0
3 3.0
4 6.0
dtype: float64
In [111]: s.rolling(4).apply(lambda x:list(x)[-1]-list(x)[0]) # s.diff(3)
Out[111]:
0 NaN
1 NaN
2 NaN
3 9.0
4 12.0
dtype: float64
In [112]: def my_pct(x):
.....: L = list(x)
.....: return L[-1]/L[0]-1
.....:
In [113]: s.rolling(2).apply(my_pct) # s.pct_change()
Out[113]:
0 NaN
1 2.000000
2 1.000000
3 0.666667
4 0.500000
dtype: float64
扩张窗口又称累计窗口,可以理解为一个动态长度的窗口,其窗口的大小就是从序列开始处到具体操作的对应位置,其使用的聚合函数会作用于这些逐步扩张的窗口上。具体地说,设序列为a1, a2, a3, a4,则其每个位置对应的窗口即[a1]、[a1, a2]、[a1, a2, a3]、[a1, a2, a3, a4]。
In [114]: s = pd.Series([1, 3, 6, 10])
In [115]: s.expanding().mean()
Out[115]:
0 1.000000
1 2.000000
2 3.333333
3 5.000000
dtype: float64
cummax, cumsum
函数是典型的类扩张窗口函数,请使用 expanding
对象依次实现它们。
s = pd.Series([1, 3, 6, 10])
#cumsum
s.expanding().sum()
0 1.000000
1 2.000000
2 3.333333
3 5.000000
dtype: float64
#cummax
s.expanding().max()
0 1.0
1 3.0
2 6.0
3 10.0
dtype: float64
作为扩张窗口的 ewm
窗口
在扩张窗口中,用户可以使用各类函数进行历史的累计指标统计,但这些内置的统计函数往往把窗口中的所有元素赋予了同样的权重。事实上,可以给出不同的权重来赋给窗口中的元素,指数加权窗口就是这样一种特殊的扩张窗口。
其中,最重要的参数是 alpha
,它决定了默认情况下的窗口权重为
w i = ( 1 − α ) i , i ∈ { 0 , 1 , 2 , . . . , t } w_{i}=(1-\alpha)^{i},i\in \{0,1,2,...,t\} wi=(1−α)i,i∈{ 0,1,2,...,t},其中 i = t i=t i=t 表示当前元素, i = 0 i=0 i=0 表示序列的第一个元素。
从权重公式可以看出,离开当前值越远则权重越小,若记原序列为 x ,更新后的当前元素为 yt ,此时通过加权公式归一化后可知:
y t = ∑ i = 0 t w i x t − i ∑ i = 0 t w i = x t + ( 1 − α ) x t − 1 + ( 1 − α ) 2 x t − 2 + . . . ( 1 − α ) t x 0 1 + ( 1 − α ) + ( 1 − α ) 2 + . . . ( 1 − α ) t y_{t}=\frac{\sum_{i=0}^{t} w_i x_{t-i}}{\sum_{i=0}^{t} w_i}=\frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ... \ (1 - \alpha)^{t} x_{0}}{1 + (1 - \alpha) + (1 - \alpha)^2 + ... \ (1 - \alpha)^{t}} yt=∑i=0twi∑i=0twixt−i=1+(1−α)+(1−α)2+... (1−α)txt+(1−α)xt−1+(1−α)2xt−2+... (1−α)tx0
对于 Series
而言,可以用 ewm
对象如下计算指数平滑后的序列:
In [118]: np.random.seed(0)
In [119]: s = pd.Series(np.random.randint(-1,2,30).cumsum())
In [120]: s.head()
Out[120]:
0 -1
1 -1
2 -2
3 -2
4 -2
dtype: int32
In [121]: s.ewm(alpha=0.2).mean().head()
Out[121]:
0 -1.000000
1 -1.000000
2 -1.409836
3 -1.609756
4 -1.725845
dtype: float64