Pandas: своеобразное падение производительности для inplace rename после dropna
Я сообщил об этом как о проблеме pandas.
Тем временем я публикую это здесь, надеясь спасти других, если они столкнутся с подобными проблемами.
После профилирования процесса, который необходимо оптимизировать, я обнаружил, что переименование столбцов NOT 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 (функция)
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
Это копия объяснения в github.
Существует без гарантии, что операция inplace
выполняется быстрее. Часто они фактически являются той же самой операцией, которая работает на копии, но ссылка верхнего уровня переназначается.
Причина разницы в производительности в этом случае заключается в следующем.
Вызов (df1-df2).dropna()
создает срез блока данных. Когда вы применяете новую операцию, это вызывает проверку SettingWithCopy
, потому что это может быть копия (но часто это не так).
Эта проверка должна выполнить сборку мусора, чтобы уничтожить некоторые ссылки на кеш, чтобы увидеть, является ли это копией. К сожалению, синтаксис python делает это неизбежным.
Вы не можете этого сделать, просто сделав копию первой.
df = (df1-df2).dropna().copy()
за которым следует операция inplace
, будет как и раньше.
Мое личное мнение: я никогда не использую операции на месте. Синтаксис труднее читать, и он не дает никаких преимуществ.