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

5

假设我有下面这个数据框

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
距离和速度始终为正数,但不是整数。我更新了问题。谢谢。 - philshem
1
这个问题真的很难理解,我进行了编辑,希望使用@fuglede的答案来解释更清楚。希望我的编辑能使这更加清晰,当然您可以根据需要进一步编辑。 - JohnE
1
就问题而言,向量化可能会很困难(甚至不可能?)。如果@fuglede的答案足够快,我会直接使用它。如果不行,您可以考虑将他的函数转换为numpy或numba函数。将其转换为numba函数可能是最好的方法,因为它既快速又易于实现(而且可能是最快的方式)。 - JohnE
1
向量化特别困难的原因之一是窗口大小可能会变得任意大。如果距离是整数且为正,我们可以假设窗口的大小始终最多为5,在这种情况下,预计算可以提供可接受的解决方案。无论如何,根据@JohnE的评论,在下面的答案中我添加了一些示例,说明numba如何有效地解决该问题。 - fuglede
2个回答

5
并不是特别符合您所需的解决方案,但听起来您想要做类似于以下操作:
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秒±325毫秒
基于纯numpy数组            80.8毫秒±9.95毫秒
基于Jitted numpy数组      766微秒±52微秒
即使是看似无害的`mean`函数也足以让numba失效;如果我们放弃使用它,而改用:
@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微秒±8.41微秒。

如果您了解df [ 'distance']的结构,可以进一步优化while循环。 (例如,如果值始终远低于5,则从尾部裁剪累积和将更快,而不是重新计算所有内容。)


有趣 - 谢谢。 (对于30秒的混淆感到抱歉。我将它们替换为5秒,以使数据更加真实可信) - philshem
1
不错!Numba 有时真的很神奇。 - JohnE
你确定mean()对numba造成了问题吗?通常情况下,numpy代码与numba一起使用没有问题,特别是像mean这样的标准函数。我认为这不会影响速度,顺便说一句,如果numba无法处理numpy mean(),那将非常令人惊讶。 - JohnE
好问题。我不会自己深入研究数字的差异,但是inspect_llvm可能会提供一些提示? - fuglede
全局变量可能会对基准测试造成一些混乱,但是即使去掉它们,我仍然看到了很大的差异。 - fuglede

3
怎么样?
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)作为平均速度窗口的预聚合? - philshem
3
这里的 df['rv']df['distance'] 完全独立,但这并不是他们想要的。 - fuglede

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