【问题标题】:Faster way to forward-fill and back-fill a groupby更快地向前填充和回填 groupby
【发布时间】:2020-06-05 03:45:58
【问题描述】:

我想 ffillbfill 在 groupby 之后的特定列。

我的解决方案有效:

import numpy as np
import pandas as pd

df = pd.DataFrame({
    "A": [1, 1, 1, 1, 2, 2, 2, 2],
    "B": [np.nan, 'f1', 'b1', np.nan, np.nan, 'f2', 'b2', np.nan]
})
df['B'] = df.groupby('A')['B'].apply(lambda _: _.ffill().bfill())

所以这个:

    A   B
0   1   NaN
1   1   f1
2   1   b1
3   1   NaN
4   2   NaN
5   2   f2
6   2   b2
7   2   NaN

变成这样:

    A   B
0   1   f1
1   1   f1
2   1   b1
3   1   b1
4   2   f2
5   2   f2
6   2   b2
7   2   b2

请注意,我要填充和 bfill 的序列将始终采用这种格式 (Nan, x, y, Nan)

虽然这可行,但在大型数据帧上速度非常慢。

我正在寻找一些优化以使其更快(最好不要使用 Dask 或多处理),也许我可以进行 Numpy 优化?

我在查看其他答案时运气不佳,例如 this 一个。

【问题讨论】:

  • “以这种格式”你真的是指组总是 4 行长,缺少第一个和最后一个元素,还是更能说明问题,即会有 一些 NaN 的条纹(任何长度),2 个非空值,然后是其他一些 NaN 的条纹?建议的解决方案仅适用于非常具体的第一种情况。
  • 感谢您的提问;我的意思是这些组总是 4 行长,外面的两个值是 NaN,中间的两个值会被填充。
  • 顺便说一句,您不需要那个 lambda,只需执行 df.groupby('A')['B'].ffill().bfill()。我怀疑它明显更快,只是更简单。 groupby 本身已经防止每个组中的第一个值被前一个组的最后一个值填充。
  • 哦,天哪,我刚刚做了一些快速计时,将其扩展到大约 32,000 行。看起来apply/lambda 只是在谋杀这里的速度。仅仅像我之前的评论那样重写就会大大加快速度,下面的所有 3 个答案也是如此。
  • @JohnE 刚刚证实了你的发现,这太疯狂了!

标签: python pandas optimization


【解决方案1】:

如果你想要速度,避免使用 groupby 并使用 numpy 代替 pandas 是很好的规则。这通常是不可能的,但在这里你有非常规则数据的特殊情况,你所需要的只是[start:end:stride] 形式的下标三元组:

df.iloc[0::4,1] = df.iloc[1::4,1].values
df.iloc[3::4,1] = df.iloc[2::4,1].values

解释:大多数人都知道您可以使用[start:stop] 形式的下标,但您也可以添加可选的stride 参数。所以第一行说用元素 1,5,9,... 替换元素 0,4,8,... “值”是删除 pandas 索引所必需的,这实际上是有害的。

仅通过避免 groupby 应该会更快一些。为了更快一点,您可以将 B 列输出到 numpy,在 numpy 中工作(基本上是相同的代码),然后重新导入到 pandas:

arr = df.B.values
arr[0::4] = arr[1::4]  
arr[3::4] = arr[2::4]
df.B = arr

如果您想留在 pandas 中,您可以做的另一件事是取消堆叠,复制整个列,然后重新堆叠。无论如何,这基本上就是上面的代码正在做的事情。老实说,对于这样一个矩形问题,任何数组样式的方法都会相当快。

【讨论】:

  • 这是最快的解决方案。 @JohnE 的另一个建议 - 删除 apply/lambda 也带来了巨大的加速。只是为了可读性,我可能会使用它。
【解决方案2】:

如果您的数据结构非常好,包含连续组,那么您可以通过使用ffillbfill 中的limit 参数来避免groupby,例如:

print (df['B'].ffill(limit=1).bfill(limit=1))
0    f1
1    f1
2    b1
3    b1
4    f2
5    f2
6    b2
7    b2
Name: B, dtype: object

【讨论】:

  • 啊,如果你住在pandas,这绝对是最简单的方法。不确定它是否会比 numpy 更快(可能是这样),但我猜在最坏的情况下非常接近。
  • @JohnE 我尝试使用从 pd.concat([df]*1000) 构建的数据框(所以 8K 行),您的解决方案很容易获胜:13.3 µs ± 279 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) 是您的解决方案,比这个快 100 倍:1.53 ms ± 76.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
  • 好的,谢谢。我在 32,000 行上做了一些快速计时,它比这更接近(但我做得很快,所以谁知道)。我的 iloc 版本要快一点,然后 numpy 版本还要快一点(大约是你的 10 倍)。无论如何,OP 都有很多简单和/或快速可供选择。
  • @JohnE 我对 numpy 解决方案的速度如此之快并不感到惊讶,但也许我在时间上也犯了错误,有时可能会很棘手:)
【解决方案3】:

如果你的格式前缀为(Nan, x, y, Nan),什么时候可以做

df.B=df.groupby([df.A,df.index//2]).B.transform('first')
Out[169]: 
    B
0  f1
1  f1
2  b1
3  b1
4  f2
5  f2
6  b2
7  b2

【讨论】:

    猜你喜欢
    • 2019-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-26
    相关资源
    最近更新 更多