窗口大小为列值间隔的滚动平均值



我正试图计算一些不完整数据的滚动平均值。我想在大小为1.0的窗口中对第2列中的值求平均值(英里(。我尝试过.rolling((,但(据我所知(这只会基于索引创建窗口,而不会基于列值。

import pandas as pd
import numpy as np
df = pd.DataFrame([
[4.5, 10],
[4.6, 11],
[4.8, 9],
[5.5, 6],
[5.6, 6],
[8.1, 10],
[8.2, 13]
])
averages = []
for index in range(len(df)):
nearby = df.loc[np.abs(df[0] - df.loc[index][0]) <= 0.5]
averages.append(nearby[1].mean())
df['rollingAve'] = averages

提供所需输出:

0   1  rollingAve
0  4.5  10        10.0
1  4.6  11        10.0
2  4.8   9        10.0
3  5.5   6         6.0
4  5.6   6         6.0
5  8.1  10        11.5
6  8.2  13        11.5

但对于大数据帧来说,速度会大大减慢。有没有一种方法可以在不同的窗口大小或类似的情况下实现.rolling((?

Panda的BaseIndexer非常方便,尽管它需要一点抓耳挠腮才能做好。

在下文中,我使用np.searchsorted快速查找每个窗口的索引(开始、结束(:

from pandas.api.indexers import BaseIndexer
class RangeWindow(BaseIndexer):
def __init__(self, val, width):
self.val = val.values
self.width = width
def get_window_bounds(self, num_values, min_periods, center, closed):
if min_periods is None: min_periods = 0
if closed is None: closed = 'left'
w = (-self.width/2, self.width/2) if center else (0, self.width)
side0 = 'left' if closed in ['left', 'both'] else 'right'
side1 = 'right' if closed in ['right', 'both'] else 'left'
ix0 = np.searchsorted(self.val, self.val + w[0], side=side0)
ix1 = np.searchsorted(self.val, self.val + w[1], side=side1)
ix1 = np.maximum(ix1, ix0 + min_periods)
return ix0, ix1

一些高级选项:min_periodscenterclosed是根据DataFrame.rolling指定的内容实现的。

应用程序:

df = pd.DataFrame([
[4.5, 10],
[4.6, 11],
[4.8, 9],
[5.5, 6],
[5.6, 6],
[8.1, 10],
[8.2, 13]
], columns='a b'.split())
df.b.rolling(RangeWindow(df.a, width=1.0), center=True, closed='both').mean()
# gives:
0    10.0
1    10.0
2    10.0
3     6.0
4     6.0
5    11.5
6    11.5
Name: b, dtype: float64

定时:

df = pd.DataFrame(
np.random.uniform(0, 1000, size=(1_000_000, 2)),
columns='a b'.split(),
)
df = df.sort_values('a').reset_index(drop=True)

%%time
avg = df.b.rolling(RangeWindow(df.a, width=1.0)).mean()
CPU times: user 133 ms, sys: 3.58 ms, total: 136 ms
Wall time: 135 ms

性能更新:

根据@anon01的评论,我想知道当滚动涉及大窗口时,是否可以更快地进行。原来我应该先测量Pandas的滚动平均值和总和性能。。。(过早优化,有人吗?(看看最后的原因。

无论如何,我们的想法是只做一次cumsum,然后取windows端点取消引用的元素的差异:

# both below working on numpy arrays:
def fast_rolling_sum(a, b, width):
z = np.concatenate(([0], np.cumsum(b)))
ix0 = np.searchsorted(a, a - width/2, side='left')
ix1 = np.searchsorted(a, a + width/2, side='right')
return z[ix1] - z[ix0]
def fast_rolling_mean(a, b, width):
z = np.concatenate(([0], np.cumsum(b)))
ix0 = np.searchsorted(a, a - width/2, side='left')
ix1 = np.searchsorted(a, a + width/2, side='right')
return (z[ix1] - z[ix0]) / (ix1 - ix0)

有了这个(以及上面的100万行df(,我看到:

%timeit fast_rolling_mean(df.a.values, df.b.values, width=100.0)
# 93.9 ms ± 335 µs per loop

对比:

%timeit df.rolling(RangeWindow(df.a, width=100.0), min_periods=1).mean()
# 248 ms ± 1.54 ms per loop

然而Pandas很可能已经在做这样的优化了(这是一个非常明显的优化(。时间不会随着窗口的增大而增加(这就是为什么我说我应该先检查一下(。

如果索引类型为DateTimeIndexTimedeltaIndex,则df.rollingseries.rolling允许使用基于值的窗口。您可以使用它来接近所需的结果:
df = df.set_index(pd.TimedeltaIndex(df[0]*1e9))
df["rolling_mean"] = df[1].rolling("1s").mean()
df = df.reset_index(drop=True)

输出:

0   1  rolling_mean
0  4.5  10     10.000000
1  4.6  11     10.500000
2  4.8   9     10.000000
3  5.5   6      8.666667
4  5.6   6      7.000000
5  8.1  10     10.000000
6  8.2  13     11.500000

优点这是一个三行解决方案,应该具有良好的性能,利用pandas日期时间后端。

缺点这绝对是一个破解方法,将里程列转换为时间增量秒,并且平均值不居中(center不适用于基于日期时间和偏移量的窗口(。

总的来说:如果你重视表现,并且能够接受非中心的平均值,这将是一个很好的方式来发表一两条评论。

最新更新