【问题标题】:mitigating a performance warning from pandas (DataFrame is highly fragmented)减轻 pandas 的性能警告(DataFrame 高度分散)
【发布时间】:2022-01-27 01:11:51
【问题描述】:

假设我们有一个函数bar(df),它返回一个长度为len(df)的numpy数组,给定一个数据帧df。

现在考虑成语

def foo(df):
    for i in range(N):
        df['FOO_' + str(i)] = bar(df)
    return df

最近的熊猫更新开始导致以下警告

PerformanceWarning:DataFrame 高度分散。这通常是 多次调用frame.insert的结果,差 表现。考虑使用一次连接所有列 pd.concat(axis=1) 代替。要获得碎片整理的帧,请使用 newframe = frame.copy()

据我所知,缓解这种情况的一种方法是将上面的代码更改为以下成语

def foo2(df):
    frames = [df]
    for i in range(N):
        frames += [pd.Series(bar(df), index=df.index)]
    return pd.concat(frames, axis=1)

上面的代码修复了警告,但导致执行时间更短。

In [110]: %timeit foo()
1.73 s ± 11 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [111]: %timeit foo2()
2.51 s ± 25.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

抑制引入额外开销的性能警告的修复似乎很愚蠢。因此我的问题是

如何在修复警告的同时获得更好的性能。换句话说,有没有办法改进函数 foo2 以提供比 foo 更好的性能?

【问题讨论】:

  • 在您的示例中,N 是否等于 df 的长度?另外,我认为foo 不会像您的示例中所写的那样运行:df[i] 查找列i,而不是行i。另一个问题是foo 覆盖了传入的DataFrame df。
  • @PeterLeimbigler N 是任意的,通常是很大的。我已经编辑了代码以反映您对 df[i] 的评论。至于覆盖,我对此很好,但很好奇:这样做的礼貌方式是什么?
  • @Jernej 你能分享一些数据和bar()body 吗?
  • @DanilaGanchar 有多种情况使用这种成语。我希望看到一个不依赖于特定数据实现或 bar 函数的解决方案。

标签: python pandas dataframe


【解决方案1】:

优化代码的一个机会是将+= 重构为列表理解:

import pandas as pd

N = 5000
df = pd.DataFrame(index=[_ for _ in range(100)])


def bar(df):
    return np.random.rand(len(df))


def foo2_orig(df):
    frames = [df]
    for i in range(N):
        frames += [pd.Series(bar(df), index=df.index)]
    return pd.concat(frames, axis=1)


def foo2_opt(df):
    frames = pd.concat([pd.Series(bar(df), index=df.index) for i in range(N)], axis=1)
    return pd.concat([df, frames], axis=1)

在我的机器上,我看到性能提高了 2 倍,但我不确定 100 行和 5000 列是否适合您的情况。提速的原因是list comprehensions are more efficient.

更新:

如果列名列表已知 (list_col_names),则可以使用 name kwarg 为各个系列分配自定义列名:

def foo2_opt(df):
    frames = pd.concat([pd.Series(bar(df), index=df.index, name=col_name) for i, col_name in zip(range(N), list_col_names)], axis=1)
    return pd.concat([df, frames], axis=1)

【讨论】:

  • 我可以确认这似乎运作良好。两个后续问题 - 为什么第二个版本更有效?而且,是否可以为新创建的列设置自定义名称?我们可以假设给出了一个长度为 N 的字符串列表。
  • 我用一个链接更新了答案,以更好地解释效率提升和列的自定义命名。
【解决方案2】:

从 numpy 数组按列构建数据框的一种非常有效且优雅的方法是构建列的字典,然后将它们立即转换为所需的数据框,因为这意味着整合数据和管理的成本索引只需支付一次。

如果我用foo2_dict 变体扩展@SultanOrazbayev 的example:

def foo2_dict(df):
    new_columns = pd.DataFrame({f"FOO_{i}": bar(df) for i in range(N)})
    return pd.concat([df, new_columns], axis=1)

我看到从 foo2_opt (390ms) 到 foo2_dict (58ms) 的额外因子 6 改进,但这当然高度依赖于底层 bar 函数的实际实现。

还要注意,通过使用字典理解来提高速度是次要的,即一堆 numpy -> pandas 转换,即。 foo2_incremental_dict 对我来说需要 59 毫秒。

def foo2_incremental_dict(df):
    new_columns = {}
    for i in range(N):
        new_columns[f"FOO_{i}"] = bar(df)
    new_columns = pd.DataFrame(new_columns, index=df.index)

    return pd.concat([df, new_columns], axis=1)

【讨论】:

  • 这个很有趣也很有用,谢谢!
猜你喜欢
  • 2018-09-17
  • 2021-10-30
  • 2012-08-16
  • 2019-05-26
  • 2016-12-09
  • 1970-01-01
  • 2019-04-01
  • 2022-11-16
  • 2022-06-11
相关资源
最近更新 更多