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, будет как и раньше.

Мое личное мнение: я никогда не использую операции на месте. Синтаксис труднее читать, и он не дает никаких преимуществ.