Pandas: inplace重命名后dropna出现性能下降问题

45
我已经在pandas issues上报告了这个问题。同时,我在这里发帖,希望能为其他人节省时间,以防他们遇到类似的问题。
在对需要优化的进程进行分析时,我发现重命名列不是inplace可以将执行时间提高x120。分析表明,这与垃圾回收有关(见下文)。
此外,避免使用dropna方法可以恢复预期的性能。
以下简短的示例展示了一个因子x12:
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的第一行输出:

ncalls tottime percall cumtime percall filename:lineno(function)

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个回答

72

这是来自github上的说明副本。

不能保证 inplace 操作会比其他操作更快。通常它们实际上是在副本上执行相同的操作,但顶层引用被重新分配。

导致性能差异的原因如下。

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

此检查必须执行垃圾回收以清除一些缓存引用来确定它是否是副本。不幸的是,Python 语法使这种情况不可避免。

您可以通过先创建副本来避免这种情况发生。

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

在进行inplace操作之后,代码性能与之前相同。

个人意见:我从不使用就地操作。它的语法更难阅读,而且没有任何优点。


1
我从不使用原地操作。这种语法更难读,并且没有任何优势。有趣的观点。我将来应该考虑这一点。.copy()的建议确实解决了问题。感谢您详细而及时的回复! - eldad-a
8
我说这句话的原因是pandas操作的核心是链式操作,每次操作都返回一个副本,例如df.dropna().rename(....).sum()非常直观/易读。当您使用inplace操作时,无法进行链式操作。 - Jeff
13
我不会说语法没有任何优点——它允许您避免在等号两侧放置长规范。这是一种类似于 some_long_complicated_expression[some:long_slice, more_information_here] += 1 相对于 some_long_complicated_expression[some:long_slice, more_information_here] = some_long_complicated_expression[some:long_slice, more_information_here] + 1 的优势的变体。 - DSM
1
@DSM 的观点很有道理。我通常会使用一个临时变量,比如“mask”,其中含义是清晰的。(尽管在你的例子中,在 rhs 上实际上不需要它,因为框架将会被对齐,例如你可以简单地使用:“some_long_complicated_expression + 1”(尽管可能会有性能影响)。 - Jeff
1
不想争论总体观点,只是想问一个可能有点幼稚的问题。当你说“语法更难读且没有任何优势”时,如果它真的在某个地方做了很大的事情,那么内存效率不会是一个积极因素吗?假设是本地操作? - JimLohse
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接