如何简单地在Python中计算时间序列的滚动/移动方差?

23

我有一个简单的时间序列,并且正在努力估计在移动窗口内的方差。更具体地说,我无法弄清楚实现滑动窗口函数的一些问题。例如,在使用NumPy和窗口大小=20时:

def rolling_window(a, window):
    shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
    strides = a.strides + (a.strides[-1],)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides) 

rolling_window(data, 20)
np.var(rolling_window(data, 20), -1)
datavar=np.var(rolling_window(data, 20), -1)

也许在我的思路中哪里出现了错误。是否有人知道一个简单的方法来做到这一点?任何帮助/建议都会非常受欢迎。

7个回答

29

Pandas的rolling_meanrolling_std函数已被弃用,并由更通用的"rolling"框架替换。 @elyase的示例可以进行修改:

使用新框架,可以像下面这样修改@elyase的示例:

import pandas as pd
import numpy as np
%matplotlib inline

# some sample data
ts = pd.Series(np.random.randn(1000), index=pd.date_range('1/1/2000', periods=1000)).cumsum()

#plot the time series
ts.plot(style='k--')

# calculate a 60 day rolling mean and plot
ts.rolling(window=60).mean().plot(style='k')

# add the 20 day rolling standard deviation:
ts.rolling(window=20).std().plot(style='b')

函数rolling支持多种窗口类型,详见此处。可在rolling对象上调用多个函数,包括var和其他有趣的统计量(如skewkurtquantile等)。由于这张图与均值在同一张图上,从单位的角度来看更有意义,因此我选择使用std


在ts.rolling(window=20).std().plot(style='b')中,“滚动方差”指的是“滚动标准差”吗?请确认。 - Lars Ericson
糟糕 - 我在文本中明确地说了 std,但评论仍然不正确。我会修复的... 完成 - sfjac

19
你应该看一下Pandas。例如:
import pandas as pd
import numpy as np

# some sample data
ts = pd.Series(np.random.randn(1000), index=pd.date_range('1/1/2000', periods=1000)).cumsum()

#plot the time series
ts.plot(style='k--')

# calculate a 60 day rolling mean and plot
pd.rolling_mean(ts, 60).plot(style='k')

# add the 20 day rolling variance:
pd.rolling_std(ts, 20).plot(style='b')

enter image description here


3
我认为Barry正在寻找滚动方差,而不是滚动标准偏差。他可以平方标准偏差以获得方差,或者使用pd.rolling_var(ts, 20).plot(style='b')。 - vlmercado
1
现在,随着 pandas 的更新,语法也会发生变化。请参阅文档了解更多信息。 - StSav012

14

虽然这是一个旧线程,但我会添加另一种方法,根据修改而来,并且不依赖于pandas或Python循环。基本上,使用numpy的stride技巧,您可以首先创建一个数组视图,其striding使得沿着最后一个轴计算函数的统计量等同于执行滚动统计。我已经修改了原始代码,以便输出形状与输入形状相同,通过在最后一个轴的开头填充添加。

import numpy as np

def rolling_window(a, window):
    pad = np.ones(len(a.shape), dtype=np.int32)
    pad[-1] = window-1
    pad = list(zip(pad, np.zeros(len(a.shape), dtype=np.int32)))
    a = np.pad(a, pad,mode='reflect')
    shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
    strides = a.strides + (a.strides[-1],)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

a = np.arange(30).reshape((5,6))

# rolling mean along last axis
np.mean(rolling_window(a, 3), axis=-1)

# rolling var along last axis
np.var(rolling_window(a, 3), axis=-1)

# rolling median along last axis
np.median(rolling_window(a, 3), axis=-1)


1
感谢提供仅使用np的解决方案。虽然我需要稍后理解填充和步幅,但现在它确实满足了我的需求。干杯! - stevosn
假设您的初始 a.shape(5,6),那么为什么 rolling_window(a, 3) 的输出形状为 (6, 6, 3)?对于任何 a.shape(n ,m) 的情况,输出始终为 (n+1, m, window)。第一维中的额外点来自哪里?它是否应该存在?我使用的是 Python 3.8.8 和 NumPy 1.20.1。 - Adriaan

7
使用Pandas处理纯数值数据有点过度了,我认为;Bottleneck非常好用,但自2021年1月以来就没有更新过,并且不再适用于Python 3.9及更高版本。因此,我将发布一个基于Josh Albert's version的版本,记住文档中关于lib.stride_tricks.as_strided的注意事项,它可能不安全使用。
您可以使用NumPy的lib.stride_tricks.sliding_window_view(),它基本上是围绕lib.stride_tricks.as_strided的一个安全(或许)的包装器,创建一个具有窗口大小的额外轴的数组(在任意数量的维度中),允许您使用NumPy内置的统计函数在该轴上操作:
import numpy as np

window = 3  # size of the window
A = np.arange(10)

Aw = np.lib.stride_tricks.sliding_window_view(A, window)
Avar = np.var(Aw, axis=-1)

Avar
>>> array([0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
       0.66666667, 0.66666667, 0.66666667])

当然,这对于meanmaxminstd等函数也适用。

注意:据我所见,似乎没有办法包含数组的“边缘”,即无法达到完整窗口长度的A的开头和结尾。因此,生成的数组将缩短到可以达到完整窗口长度的那一部分,请参阅返回值的文档。


4

我也在寻找同样的解决方案,发现 bottleneck 包可以相当可靠和快速地完成任务。这里是稍微调整过的示例,来自于 https://kwgoodman.github.io/bottleneck-doc/reference.html#bottleneck.move_var

>>> import bottleneck as bn
>>> a = np.array([1.0, 2.0, 3.0, np.nan, 5.0])
>>> bn.move_var(a, window=2)
array([ nan,  0.25,  0.25,  nan,  nan])
>>> bn.move_var(a, window=2, min_count=1)
array([ 0. ,  0.25,  0.25,  0. ,  0. ])

请注意,结果方差对应于窗口的 最后 索引。
该软件包可从Ubuntu存储库、pip等获取。它可以在numpy-array等任意轴上操作。此外,据称在许多情况下比纯numpy实现更快。

1
Bottleneck 在 Python <3.8 上运行得非常棒,但是很遗憾在 Python >3.9 上存在着许多 bug,而开发者近一年来都没有回应 Github 上报告的这些问题。 - Adriaan

0
这是使用纯Python计算移动平均值(或在时间窗口内执行任何其他操作)的简单方法。
您可以通过更改window变量中的值来更改时间窗口。例如,如果您想要一个30分钟的时间窗口,您将把数字更改为3000000000。
在此示例中,条目保存在名为data的字典中。但是,您可以从适合您的任何集合中获取此数据。
您可以将结果保存到任何您喜欢的集合或数据库中。
data = {}

def one_min_avg():
    window = int(datetime.now().strftime("%H%M%S%f")) - 100000000
    history = {}
    for i in message_log.items():
        if i[0] >= window:
            history.update({i})
    for i in list(history):
        if i < window:
            history.pop(i)
    avg = sum(history.values()) / len(list(history))
    return avg

注意:您可能需要添加一些错误处理来避免除以零或者如果函数无法访问您的数据。

这里的时序与其他答案相比如何?这个问题经常被提出的一个原因是,对于这个问题来说,一个简单、天真的循环通常不太快。 - Adriaan

-1

做(几乎)任何滚动/移动计算的简单方法是使用卷积!

在这种情况下,您可以将数据中的标准偏差公式与固定窗口大小(ws)进行卷积。

moving_std = np.sqrt(np.convolve((data - np.mean(data))**2, np.ones(ws)/ws, mode='valid'))

只是不要忘记,当你绘制它时,由于窗口大小的原因,移动标准差的第一个点将向左移动ws个空格,与你的数据相比。因此,你需要通过在x轴上添加窗口大小来进行调整。


1
我不认为这是正确的。你难道不想使用窗口内数据的平均值,而不是整个系列的平均值吗?如果数据的一部分与整体的平均值有显著差异,那么标准偏差会比预期大得多,不是吗? - Mark H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接