【问题标题】:Rolling average with window size an interval of column values具有窗口大小的滚动平均值和列值的间隔
【发布时间】:2020-11-06 20:51:49
【问题描述】:

我正在尝试计算一些不完整数据的滚动平均值。我想在第 1 列中的值(英里)的大小为 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() 或类似的东西?

【问题讨论】:

  • 您的代码似乎没有正确对齐
  • @piterbarg 谢谢,我已经修好了它现在应该可以运行了

标签: python pandas dataframe


【解决方案1】:

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 可能已经在进行这样的优化(这是一个非常明显的优化)。时间不会随着更大的窗口而增加(这就是我说我应该先检查的原因)。

【讨论】:

  • 这很酷,我不知道 searchsorted。对大型数据集的性能有什么想法,或者当窗口大小有很多行时?
  • 这感觉像是 pandas API 中的一个缺口。我想知道这会超出规范多远
  • @anon01 np.searchsorted 相当快(算法与bisect 相同),但不可否认,当第二个数组被排序时它可能会更快(应该有一个标志告诉@987654338 @ 在这种情况下)。参见例如this SO answer 供讨论。也就是说,在 r5d.2xlarge EC2 实例上,我看到 searchsorted(a, b) 大约为 540 毫秒,其中 ab 已排序并有 1000 万个 float64。至于我上面回答中的整体操作:处理大窗口没有额外费用(我最初和你一样想,但事实并非如此)
【解决方案2】:

df.rollingseries.rolling 如果索引类型为 DateTimeIndexTimedeltaIndex,则允许基于值的窗口。您可以使用它来接近所需的结果:

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 日期时间后端。

缺点 这绝对是一个 hack,将您的英里列转换为时间增量秒,并且平均值未居中(center 未针对 datetimelike 和基于偏移的窗口实现)。

总体而言:如果您重视表现并且可以接受不居中的均值,那么这将是一两条评论的好方法。

【讨论】:

    猜你喜欢
    • 2019-09-09
    • 1970-01-01
    • 1970-01-01
    • 2020-07-04
    • 2021-11-11
    • 2020-07-12
    • 2019-01-11
    • 2021-12-11
    • 2019-02-11
    相关资源
    最近更新 更多