如何通过 2x2 平均内核对 pandas 数据帧进行下采样

Posted

技术标签:

【中文标题】如何通过 2x2 平均内核对 pandas 数据帧进行下采样【英文标题】:How to downsample a pandas dataframe by 2x2 averaging kernel 【发布时间】:2013-09-20 10:46:49 【问题描述】:

我正在尝试对 pandas 数据帧进行下采样以降低粒度。例如,我想减少这个数据框:

1  2  3  4
2  4  3  3
2  2  1  3
3  1  3  2

对此(下采样以使用均值获得 2x2 数据帧):

2.25  3.25
2     2.25

有没有内置的方法或有效的方法,还是我必须自己写?

谢谢

【问题讨论】:

您想如何进行下采样?取相邻两行的平均值? 我想在子矩阵中分割原始矩阵,然后在每个子矩阵中进行块均值。例如对于结果矩阵中的元素 (1,1),求原始矩阵中子矩阵 (1:2, 1:2) 的块均值.. 我认为二维过滤器内核被称为2x2 Box blur 【参考方案1】:

您可以使用两次应用rolling_mean 函数,首先在列上,然后在行上,然后对结果进行切片:

rbs = 2 # row block size
cbs = 2 # column block size
pd.rolling_mean(pd.rolling_mean(df.T, cbs, center=True)[cbs-1::cbs].T,
                rbs)[rbs-1::rbs]

这给出了您想要的相同结果,除了索引会不同(但您可以使用 .reset_index(drop=True) 解决此问题):

      1     3
1  2.25  3.25
3  2.00  2.25

时间信息:

In [11]: df = pd.DataFrame(np.random.randn(100, 100))
In [12]: %%timeit
         pd.rolling_mean(pd.rolling_mean(df.T, 2, center=True)[1::2].T, 2)[1::2]
100 loops, best of 3: 4.75 ms per loop
In [13]: %%timeit
         df.groupby(lambda x: x/2).mean().groupby(lambda y: y/2, axis=1).mean()
100 loops, best of 3: 932 µs per loop

所以它比 groupby 慢 5 倍而不是 800x :)

【讨论】:

我不确定这是我需要的。结果矩阵必须是 2x2,其中每个样本是原始矩阵的四个元素之间的块均值。您的解决方案很有趣,但我不明白为什么生成的矩阵是 2x4 .. @Francesco 更新了答案。 如果你愿意,你可以改进你的答案概括它,为行和列添加不同的大小(不仅是平方矩阵的 bs)。无论如何很好的答案:) 这似乎不能很好地扩展,例如试试df = pd.DataFrame(np.random.randn(100000, 100000))。更小的 1000x1000 会使我的解释器崩溃。 :( @AndyHayden 我在 10000x10000 矩阵上尝试过,它在 12 秒内完成,但是是的,groupby 比 2.86 秒快 4 倍左右,但它只在列和行标签是整数的情况下才有效。没有其他情况会起作用。【参考方案2】:

一种选择是使用 groupby 两次。一次用于索引:

In [11]: df.groupby(lambda x: x//2).mean()
Out[11]:
     0    1  2    3
0  1.5  3.0  3  3.5
1  2.5  1.5  2  2.5

列一次:

In [12]: df.groupby(lambda x: x//2).mean().groupby(lambda y: y//2, axis=1).mean()
Out[12]:
      0     1
0  2.25  3.25
1  2.00  2.25

注意:只计算一次平均值的解决方案可能更可取...一种选择是堆叠、分组、平均和取消堆叠,但是atm 这有点繁琐。

这似乎比Vicktor's solution快得多:

In [21]: df = pd.DataFrame(np.random.randn(100, 100))

In [22]: %timeit df.groupby(lambda x: x//2).mean().groupby(lambda y: y//2, axis=1).mean()
1000 loops, best of 3: 1.64 ms per loop

In [23]: %timeit viktor()
1 loops, best of 3: 822 ms per loop

事实上,Viktor 的解决方案让我的(动力不足的)笔记本电脑因更大的 DataFrame 而崩溃:

In [31]: df = pd.DataFrame(np.random.randn(1000, 1000))

In [32]: %timeit df.groupby(lambda x: x//2).mean().groupby(lambda y: y//2, axis=1).mean()
10 loops, best of 3: 42.9 ms per loop

In [33]: %timeit viktor()
# crashes

正如 Viktor 指出的那样,这不适用于非整数索引,如果需要,您可以将它们存储为临时变量并在之后将它们反馈回来:

df_index, df_cols, df.index, df.columns = df.index, df.columns, np.arange(len(df.index)), np.arange(len(df.columns))
res = df.groupby(...
res.index, res.columns = df_index[::2], df_cols[::2]

【讨论】:

虽然我没有包括 timeit's 这对于 OP 的玩具示例来说也更快。 这里的timeit 不太对劲。对于rolling_mean,我得到best of 3: 4.67 ms per loop,对于groupby,我得到best of 3: 940 µs per loop。所以它慢了大约 5 倍,而不是 800 倍。 @ViktorKerkez 我刚刚将您的整个块复制为一个函数。这些是我得到的数字,对于“大”数据帧,它对我来说是崩溃的。 奇怪...我在帖子中添加了我的时间信息:-/ 在 Python3 中,确保使用地板除法 // : df.groupby(lambda x: x//2).mean().groupby(lambda y: y//2, axis=1).mean()

以上是关于如何通过 2x2 平均内核对 pandas 数据帧进行下采样的主要内容,如果未能解决你的问题,请参考以下文章

查找特定行数 pandas 数据帧的平均值

在循环中向 pandas 数据帧添加滚动平均值需要很长时间

如何在聚合的 pandas 数据帧上运行多个函数

如何根据Pandas中的其他列值计算每个年龄的平均值

pandas 按另一列的平均值对一列的值进行排序

Pandas - 如何将 Parquet 数据帧保存到本地磁盘?