Pandas: dropna后inplace重命名性能异常下降
我在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
操作,性能会和之前一样。
我个人的看法是:我从不使用就地操作。因为这种语法更难读,而且没有任何优势。