Python Pandas:计算可变行数的滚动平均值(移动平均值)

Posted

技术标签:

【中文标题】Python Pandas:计算可变行数的滚动平均值(移动平均值)【英文标题】:Python Pandas: calculate rolling mean (moving average) over variable number of rows 【发布时间】:2018-05-08 22:06:52 【问题描述】:

假设我有以下数据框

import pandas as pd
df = pd.DataFrame( 'distance':[2.0, 3.0, 1.0, 4.0],
                    'velocity':[10.0, 20.0, 5.0, 40.0] )

给出数据框

   distance  velocity
0         2.0        10.0
1         3.0        20.0
2         1.0        5.0
3         4.0        40.0

如何计算速度列的平均值与距离列的滚动总和?在上面的示例中,创建最后 N 行的滚动总和以获得最小累积距离 5,然后计算这些行的平均速度。

我的目标输出将是这样的:

   distance  velocity    rv
0         2.0        10.0    NaN
1         3.0        20.0    15.0
2         1.0         5.0    11.7
3         4.0        40.0    22.5

在哪里

15.0 = (10+20)/2        (2 because 3 + 2     >= 5)
11.7 = (10 + 20 + 5)/3  (3 because 1 + 3 + 2 >= 5) 
22.5 = (5 + 40)/2       (2 because 4 + 1     >= 5)

更新:在 Pandas 中,我的代码应该从我的当前记录中找到反向累积距离总和的索引(例如 5 或更大),然后使用该索引计算移动平均线的起点。

【问题讨论】:

距离总是整数和正数吗? 距离和速度总是正数但不是整数。我更新了问题。谢谢 这个问题真的很难理解,我编辑希望使用@fuglede 的答案来解释更清楚。希望我的编辑使这一点更加清晰,当然您可以根据需要进一步编辑。 就问题而言,很难(也许不可能?)矢量化。如果@fuglede 的答案足够快,我会使用它。如果不是,您可以将他的函数转换为 numpy 或 numba 函数。将其转换为 numba 函数可能是最好的方法,因为它可以快速、可读且易于实现(而且它可能只是最快的方式) 矢量化变得困难,特别是由于窗口可能变得任意大。如果距离是整数且为正的,我们可以假设窗口的大小始终最多为 5,此时预先计算可以提供一个可以通过的解决方案。无论如何,在@JohnE 的评论之后,我在下面的答案中添加了一些 numba 如何有效解决问题的示例。 【参考方案1】:

不是一个特别熊猫的解决方案,但听起来你想做类似的事情

df['rv'] = np.nan
for i in range(len(df)):
    j = i
    s = 0
    while j >= 0 and s < 5:
        s += df['distance'].loc[j]
        j -= 1
    if s >= 5:
        df['rv'].loc[i] = df['velocity'][j+1:i+1].mean()

更新:自从有了这个答案,OP 声明他们想要一个“有效的 Pandas 解决方案(例如没有循环)”。如果我们认为这意味着他们想要比上述更高性能的东西,那么,也许具有讽刺意味的是,考虑到评论,首先想到的优化是避免使用数据框,除非需要:

l = len(df)
a = np.empty(l)
d = df['distance'].values
v = df['velocity'].values
for i in range(l):
    j = i
    s = 0
    while j >= 0 and s < 5:
        s += d[j]
        j -= 1
    if s >= 5:
        a[i] = v[j+1:i+1].mean()
df['rv'] = a

此外,正如@JohnE 所建议的那样,numba 很快就能派上用场,以进一步优化。虽然它不会对上面的第一个解决方案做太多,但第二个解决方案可以用开箱即用的@numba.jit 进行装饰,并立即获得好处。对所有三种解决方案进行基准测试

pd.DataFrame('velocity': 50*np.random.random(10000), 'distance': 5*np.random.rand(10000))

我得到以下结果:

方法基准 ----------------------------------------------------------- 基于原始数据帧 4.65 s ± 325 ms 基于纯 numpy 数组的 80.8 ms ± 9.95 ms 基于 766 µs ± 52 µs 的 Jitted numpy 数组

即使是一脸无辜的mean,也足以让人麻木;如果我们摆脱它并改为使用

@numba.jit
def numba_example():
    l = len(df)
    a = np.empty(l)
    d = df['distance'].values
    v = df['velocity'].values
    for i in range(l):
        j = i
        s = 0
        while j >= 0 and s < 5:
            s += d[j]
            j -= 1
        if s >= 5:
            for k in range(j+1, i+1):
                a[i] += v[k]
            a[i] /= (i-j)
    df['rv'] = a

然后基准降低到 158 µs ± 8.41 µs。

现在,如果您对df['distance'] 的结构有更多了解,那么while 循环可能可以进一步优化。 (例如,如果这些值恰好总是远低于 5,那么从尾部减去累积和会更快,而不是重新计算所有内容。)

【讨论】:

很有趣 - 谢谢。 (抱歉与 30s 混淆。我将它们替换为 5s 以使数据更合理) 不错! Numba 有时非常棒。 你确定 mean() 导致了 numba 的问题吗?通常 numpy 代码可以很好地与 numba 配合使用,尤其是像 mean 这样的标准东西。我认为这对速度并不重要,顺便说一句,如果 numba 无法处理 numpy mean(),我会感到非常惊讶。 好问题。我自己不打算深入研究数字上的差异,但inspect_llvm 可能会提供一些提示? 全局变量可能会稍微弄乱基准,但删除那些我仍然看到结果有很大差异。【参考方案2】:

怎么样

df.rolling(window=3, min_periods=2).mean()

   distance   velocity
0       NaN        NaN
1  2.500000  15.000000
2  2.000000  11.666667
3  2.666667  21.666667

将它们组合起来

df['rv'] = df.velocity.rolling(window=3, min_periods=2).mean()

窗口的形状看起来有点不对劲。

【讨论】:

我知道这是如何工作的,但是我将如何调整 rolling_sum(distance) 作为平均速度窗口的预聚合? df['rv'] 在这里完全独立于df['distance'];这不是他们想要的。

以上是关于Python Pandas:计算可变行数的滚动平均值(移动平均值)的主要内容,如果未能解决你的问题,请参考以下文章

基于不同列的具有可变窗口的 Pandas 滚动平均值

熊猫滚动适用于可变窗口长度

python pandas使用chunksize异步拆分固定行数的文件

使用Pandas的rolling函数计算滚动平均值(rolling average with Pandas rolling)seaborn使用lineplot函数可视化时间序列数据并添加滚动平均值

如何在 Python 的滚动平均值计算中忽略 NaN

如何在 pandas DataFrame 中忽略滚动平均值计算的 NaN 值?