web-dev-qa-db-fra.com

Pandas: baisse de performances particulière pour le renommage en place après Dropna

J'ai signalé cela comme un problème sur problèmes de pandas . En attendant, je poste ici en espérant gagner du temps aux autres, au cas où ils rencontreraient des problèmes similaires.

Lors du profilage d'un processus qui devait être optimisé, j'ai trouvé que renommer les colonnes NON en place améliore les performances (temps d'exécution) de x120. Le profilage indique que cela est lié à la récupération de place (voir ci-dessous).

De plus, les performances attendues sont récupérées en évitant la méthode dropna.

L'exemple court suivant illustre un facteur 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 boucles, meilleur de 3: 15,6 ms par boucle

première ligne de sortie de %%prun:

ncalls tottime percall cumtime percall nom de fichier: lineno (fonction)

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 boucles, meilleur de 3: 1,24 ms par boucle

éviter dropna

Les performances attendues sont récupérées en évitant la méthode 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 boucles, le meilleur de 3: 865 µs par boucle

%%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 boucles, le meilleur de 3: 902 µs par boucle

33
eldad-a

Ceci est une copie de l'explication sur github.

Il y a aucune garantie qu'une opération inplace est en fait plus rapide. Il s'agit souvent de la même opération qui fonctionne sur une copie, mais la référence de niveau supérieur est réaffectée.

La raison de la différence de performances dans ce cas est la suivante.

Le (df1-df2).dropna() appel crée une tranche de la trame de données. Lorsque vous appliquez une nouvelle opération, cela déclenche un contrôle SettingWithCopy car il pourrait être une copie (mais ce n'est souvent pas le cas).

Cette vérification doit effectuer une récupération de place pour effacer certaines références de cache pour voir s'il s'agit d'une copie. Malheureusement, la syntaxe python rend cela inévitable.

Vous ne pouvez pas faire en sorte que cela se produise, en faisant simplement une copie en premier.

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

suivi d'une opération inplace sera aussi performant qu'auparavant.

Mon opinion personnelle: je jamais utilise des opérations sur place. La syntaxe est plus difficile à lire et n'offre aucun avantage.

41
Jeff