Pandas: dropna后inplace重命名性能异常下降

46 投票
1 回答
24803 浏览
提问于 2025-04-17 23:07

我在pandas的问题页面上报告了这个问题。与此同时,我在这里分享,希望能帮到其他人,避免他们遇到类似的问题。

在对一个需要优化的过程进行分析时,我发现如果不直接在原地重命名列,性能(执行时间)会提升大约120倍。

分析结果显示,这与垃圾回收有关(见下文)。

此外,通过避免使用dropna方法,可以恢复预期的性能。

下面这个简单的例子展示了大约12倍的性能差异:

import pandas as pd
import numpy as np

inplace=True

%%timeit
np.random.seed(0)
r,c = (7,3)
t = np.random.rand(r)
df1 = pd.DataFrame(np.random.rand(r,c), columns=range(c), index=t)
indx = np.random.choice(range(r),r/3, replace=False)
t[indx] = np.random.rand(len(indx))
df2 = pd.DataFrame(np.random.rand(r,c), columns=range(c), index=t)
df = (df1-df2).dropna()
## inplace rename:
df.rename(columns={col:'d{}'.format(col) for col in df.columns}, inplace=True)

100次循环,最佳结果为3次:每次循环15.6毫秒

以下是%%prun的第一行输出:

调用次数 总时间 每次调用时间 累计时间 每次调用时间 文件名:行号(函数)

1  0.018 0.018 0.018 0.018 {gc.collect}

inplace=False

%%timeit
np.random.seed(0)
r,c = (7,3)
t = np.random.rand(r)
df1 = pd.DataFrame(np.random.rand(r,c), columns=range(c), index=t)
indx = np.random.choice(range(r),r/3, replace=False)
t[indx] = np.random.rand(len(indx))
df2 = pd.DataFrame(np.random.rand(r,c), columns=range(c), index=t)
df = (df1-df2).dropna()
## avoid inplace:
df = df.rename(columns={col:'d{}'.format(col) for col in df.columns})

1000次循环,最佳结果为3次:每次循环1.24毫秒

避免使用dropna

通过避免使用dropna方法,可以恢复预期的性能:

%%timeit
np.random.seed(0)
r,c = (7,3)
t = np.random.rand(r)
df1 = pd.DataFrame(np.random.rand(r,c), columns=range(c), index=t)
indx = np.random.choice(range(r),r/3, replace=False)
t[indx] = np.random.rand(len(indx))
df2 = pd.DataFrame(np.random.rand(r,c), columns=range(c), index=t)
#no dropna:
df = (df1-df2)#.dropna()
## inplace rename:
df.rename(columns={col:'d{}'.format(col) for col in df.columns}, inplace=True)

1000次循环,最佳结果为3次:每次循环865微秒

%%timeit
np.random.seed(0)
r,c = (7,3)
t = np.random.rand(r)
df1 = pd.DataFrame(np.random.rand(r,c), columns=range(c), index=t)
indx = np.random.choice(range(r),r/3, replace=False)
t[indx] = np.random.rand(len(indx))
df2 = pd.DataFrame(np.random.rand(r,c), columns=range(c), index=t)
## no dropna
df = (df1-df2)#.dropna()
## avoid inplace:
df = df.rename(columns={col:'d{}'.format(col) for col in df.columns})

1000次循环,最佳结果为3次:每次循环902微秒

1 个回答

73

这段内容是对github上解释的一个复制。

要注意的是,并不能保证一个inplace操作真的会更快。很多时候,它们其实是对一个副本进行操作,只不过最外层的引用被重新指向了。

造成这种性能差异的原因如下。

当你调用(df1-df2).dropna()时,会创建一个数据框的切片。当你再进行新的操作时,这会触发一个SettingWithCopy的检查,因为它可能是一个副本(但通常不是)。

这个检查需要进行垃圾回收,以清除一些缓存引用,以确定它是否是副本。不幸的是,Python的语法让这个过程是不可避免的。

你可以通过先创建一个副本来避免这种情况。

df = (df1-df2).dropna().copy()

然后再进行inplace操作,性能会和之前一样。

我个人的看法是:我从不使用就地操作。因为这种语法更难读,而且没有任何优势。

撰写回答