Jer*_*nej 13 python dataframe pandas
假设我们有一个函数,它返回给定 dataframebar(df)的长度的 numpy 数组。len(df)df
现在考虑这个习语
\ndef foo(df):\n for i in range(N):\n df['FOO_' + str(i)] = bar(df)\n return df\nRun Code Online (Sandbox Code Playgroud)\n最近的 pandas 更新开始导致以下警告
\n\n\n性能警告:DataFrame 高度碎片化。这通常是\n多次调用的结果
\nframe.insert,\n性能较差。考虑使用 pd.concat(axis=1) 一次连接所有列。要获取碎片整理的帧,请使用\nnewframe = frame.copy()
据我了解,缓解这种情况的一种方法是将上面的代码更改为以下习惯用法
\ndef foo2(df):\n frames = [df]\n for i in range(N):\n frames += [pd.Series(bar(df), index=df.index)]\n return pd.concat(frames, axis=1)\nRun Code Online (Sandbox Code Playgroud)\n上面的代码修复了警告,但导致执行时间更差。
\nIn [110]: %timeit foo()\n1.73 s \xc2\xb1 11 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\nIn [111]: %timeit foo2()\n2.51 s \xc2\xb1 25.1 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\nRun Code Online (Sandbox Code Playgroud)\n抑制引入额外开销的性能警告的修复似乎很愚蠢。因此我的问题是
\n\n\n如何修复警告,同时获得更好的性能。换句话说,有没有办法改进函数 foo2 以提供比 foo 更好的性能?
\n
从 numpy 数组按列构建数据帧的一种非常有效且优雅的方法是构建列的字典,然后立即将它们转换为所需的数据帧,因为这意味着合并数据和管理索引的成本已降低只支付一次。
如果我用一个变体扩展@SultanOrazbayev 的示例foo2_dict:
def foo2_dict(df):
new_columns = pd.DataFrame({f"FOO_{i}": bar(df) for i in range(N)}, index=df.index)
return pd.concat([df, new_columns], axis=1)
Run Code Online (Sandbox Code Playgroud)
foo2_opt我看到从(390ms) 到(58ms)的额外因子 6 改进foo2_dict,但这当然高度依赖于底层bar函数的实际实现。
另请注意,通过使用字典理解来提高速度对于 numpy -> pandas 转换来说是次要的,即。afoo2_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)
Run Code Online (Sandbox Code Playgroud)
优化代码的一个机会是将其重新构建+=为列表理解:
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)
Run Code Online (Sandbox Code Playgroud)
在我的机器上,我看到性能提高了 2 倍,尽管我不确定 100 行和 5000 列是否适合您的情况。加速的原因是列表理解更加高效。
更新:
如果列名称列表已知 ( ),则可以使用kwarglist_col_names为各个系列分配自定义列名称:name
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)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
8747 次 |
| 最近记录: |