Pandas: caída de rendimiento peculiar para cambio de nombre in situ después de dropna

He informado de esto como un problema en temas de pandas . Mientras tanto, publico esto aquí con la esperanza de ahorrar tiempo a otros, en caso de que encuentren problemas similares.

Al perfilar un proceso que necesitaba ser optimizado, encontré que cambiar el nombre de las columnas NO en el lugar mejora el rendimiento (tiempo de ejecución) en x120. El perfil indica que esto está relacionado con la recolección de basura (ver más abajo).

Además, el rendimiento esperado se recupera al evitar el método dropna.

El siguiente ejemplo corto demuestra un factor 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 bucles, lo mejor de 3: 15.6 ms por bucle

Primera línea de salida de %%prun :

ncalls tottime percall cumtime percall filename: lineno (función)

 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 bucles, lo mejor de 3: 1.24 ms por bucle

evitar dropna

El rendimiento esperado se recupera al evitar el método 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 bucles, lo mejor de 3: 865 µs por bucle

 %%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 bucles, lo mejor de 3: 902 µs por bucle

Esta es una copia de la explicación en github.

No hay garantía de que una operación inplace sea ​​realmente más rápida. A menudo, en realidad son la misma operación que funciona en una copia, pero la referencia de nivel superior se reasigna.

La razón de la diferencia en el rendimiento en este caso es la siguiente.

La llamada (df1-df2).dropna() crea una porción del dataframe. Cuando aplica una nueva operación, esto activa una comprobación de SettingWithCopy porque podría ser una copia (pero a menudo no lo es).

Esta verificación debe realizar una recolección de basura para borrar algunas referencias de caché para ver si es una copia. Desafortunadamente, la syntax de Python hace esto inevitable.

No puedes hacer que esto suceda, simplemente haciendo una copia primero.

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

seguido de una operación inplace será tan inplace como antes.

Mi opinión personal: nunca uso operaciones in situ. La syntax es más difícil de leer y no ofrece ninguna ventaja.