Python Pandas:计算可变行数的滚动平均值(移动平均值)
Python Pandas: calculate rolling mean (moving average) over variable number of rows
假设我有以下数据框
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-speak中,我的代码应该从我的当前记录中找到反向累积距离总和的索引(这样它是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 表示他们想要 "valid Pandas solution (e.g. without loops)"。如果我们认为这意味着他们想要比上述更高性能的东西,那么,也许具有讽刺意味的是,考虑到评论,第一个想到的优化是避免数据框,除非需要:
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)})
我得到以下结果:
Method Benchmark
-----------------------------------------------
Original data frame based 4.65 s ± 325 ms
Pure numpy array based 80.8 ms ± 9.95 ms
Jitted numpy array based 766 µs ± 52 µs
再无辜的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 µs ± 8.41 µs。
现在,如果您碰巧对df['distance']
的结构了解得更多,while
循环可能会进一步优化。 (例如,如果值恰好总是远低于 5,则从其尾部切掉累积和会比重新计算所有内容更快。)
怎么样
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()
看起来 window 形状有点不对劲。
假设我有以下数据框
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-speak中,我的代码应该从我的当前记录中找到反向累积距离总和的索引(这样它是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 表示他们想要 "valid Pandas solution (e.g. without loops)"。如果我们认为这意味着他们想要比上述更高性能的东西,那么,也许具有讽刺意味的是,考虑到评论,第一个想到的优化是避免数据框,除非需要:
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)})
我得到以下结果:
Method Benchmark ----------------------------------------------- Original data frame based 4.65 s ± 325 ms Pure numpy array based 80.8 ms ± 9.95 ms Jitted numpy array based 766 µs ± 52 µs
再无辜的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 µs ± 8.41 µs。
现在,如果您碰巧对df['distance']
的结构了解得更多,while
循环可能会进一步优化。 (例如,如果值恰好总是远低于 5,则从其尾部切掉累积和会比重新计算所有内容更快。)
怎么样
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()
看起来 window 形状有点不对劲。