【问题标题】:How to speed up calculations involving previous row in pandas?如何加快涉及熊猫前一行的计算?
【发布时间】:2020-12-26 08:59:39
【问题描述】:

我正在尝试使用正在创建的列的移位值创建一个新的 Pandas DataFrame 列。

我能够做到这一点的唯一方法是遍历太慢并导致我的代码出现瓶颈的数据。

import pandas as pd 

df = pd.DataFrame([1,6,2,8], columns=['a'])
df.at[0, 'b'] = 5

for i in range(1, len(df)):
    df.loc[i, ('b')] = (df.a[i-1] + df.b[i-1]) /2

我尝试使用 shift 但它不起作用。它填充第 1 行的值,其余部分填充 NaN。我假设此方法无法即时读取新创建的值。

df.loc[1:, ('b')] = (df.a.shift() + df.b.shift()) /2

更新

通过在迭代中使用df.at 而不是df.loc,我能够显着减少时间

def with_df_loc(df):
    for i in range(1, len(df)):
        df.loc[i, ('b')] = (df.a[i-1] + df.b[i-1]) /2
    return df

def with_df_at(df):
    for i in range(1, len(df)):
        df.at[i, 'b'] = (df.a[i-1] + df.b[i-1]) /2
    return df



%timeit with_df_loc(df)
183 ms ± 75.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit with_df_at(df)
19.4 ms ± 2.74 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

此时间基于 150 行的较大数据集。考虑到df.rolling(20).mean() 大约需要 3 毫秒,我认为这可能是我能做的最好的了。

感谢大家的回答,如果需要进一步优化我会考虑Asish Mnumba的建议。

【问题讨论】:

  • pandas 确实没有简单的方法来处理递归的东西 - 查看 numba 以加快 for 循环。
  • 作为替代方案 - scipy.signal.filter 在这里似乎很有用 - 请参阅 stackoverflow.com/questions/26267809/…
  • 您还在为上述循环寻找更高效的解决方案吗?我有点困惑,因为问题和答案不匹配。
  • @cs95 我对问题进行了一些编辑,因为其中一个答案有效,但这只是因为 df['a'] 列值按数字顺序排列,如果该列包含随机值,它应该可以工作。我可以通过在迭代中使用df.at 而不是df.loc 来加快速度,但是如果您知道另一种方式,我很想听听。一直在寻找改进代码的方法。

标签: python pandas dataframe numba


【解决方案1】:

我们可以在这里使用numba 来加快计算速度,请参阅文档中的Enhancing performance 部分。

import numba 

@numba.njit
def func(a, b_0=5):
    n = len(a)
    b = np.full(n, b_0, dtype=np.float64)
    for i in range(1, n):
        b[i] = (b[i - 1] + a[i - 1]) / 2
    return b

df['b'] = func(df['a'].to_numpy())
df

   a     b
0  1  5.00
1  6  3.00
2  2  4.50
3  8  3.25

比较性能

Benchmarking code, for reference.

蓝线代表您当前方法的最快版本的性能(使用.at)。橙色线代表 numba 的性能。

【讨论】:

  • 谢谢@cs95 - 我能够使用您的代码并将时间减少 9 毫秒(使用不同的更大的 df),而使用 df.at 则为 19.4 毫秒,这太棒了。我对此感到非常高兴。快速提问,在您的 benchmark code 中,我没有看到 func 函数或 numba 导入 - 我错过了什么?
  • @JDavda 啊,复制粘贴错误。我已经修复了所有错误并测试了代码是否有效。如果您有任何后续问题,请告诉我,否则如果您能将答案标记为已接受,我将不胜感激(单击答案左侧的灰色复选标记将其切换为绿色)。
  • @cs95 很好的答案先生,继续努力:)
  • @cs95 我已将其标记为已接受 - 再次感谢,与 numba 合作已经有一段时间了,感谢您提供指导!
【解决方案2】:

你可以试试shift + cumsum,从5开始,加上fillna

import pandas as pd


df = pd.DataFrame([1,2,3,4], columns=['a'])
df['b'] = df['a'].shift().fillna(5).cumsum()

print(df)

输出

   a     b
0  1   5.0
1  2   6.0
2  3   8.0
3  4  11.0

【讨论】:

  • 谢谢,我也许可以使用fillna() 而不是我当前的df.at 方法,但计算需要基于之前的a+b 值。在这种情况下,您的代码有效,因为“a”值为 1、2、3、4,但我简化了此示例的实际代码 - 它是原始代码中“a”的随机值。我将编辑问题以清除它
【解决方案3】:

我很可能误解了您的问题,但如果您想创建一个移位列,请尝试以下操作:

df = pd.DataFrame([1,2,3,4], columns=['a'])
df["b"] = df.a.shift()

【讨论】:

    猜你喜欢
    • 2020-11-12
    • 1970-01-01
    • 2016-07-01
    • 2017-10-02
    • 1970-01-01
    • 1970-01-01
    • 2020-03-01
    • 2015-08-24
    • 2022-01-06
    相关资源
    最近更新 更多