web-dev-qa-db-fra.com

Pourquoi la concaténation des DataFrames devient-elle exponentiellement plus lente?

J'ai une fonction qui traite un DataFrame, en grande partie pour traiter les données dans des compartiments créer une matrice binaire de fonctionnalités dans une colonne particulière en utilisant pd.get_dummies(df[col]).

Pour éviter de traiter toutes mes données à l'aide de cette fonction à la fois (ce qui manque de mémoire et provoque le blocage d'iPython), j'ai divisé le grand DataFrame en morceaux en utilisant:

chunks = (len(df) / 10000) + 1
df_list = np.array_split(df, chunks)

pd.get_dummies(df) créera automatiquement de nouvelles colonnes basées sur le contenu de df[col] et celles-ci sont susceptibles de différer pour chaque df dans df_list.

Après le traitement, je concatène les DataFrames ensemble en utilisant:

for i, df_chunk in enumerate(df_list):
    print "chunk", i
    [x, y] = preprocess_data(df_chunk)
    super_x = pd.concat([super_x, x], axis=0)
    super_y = pd.concat([super_y, y], axis=0)
    print datetime.datetime.utcnow()

Le temps de traitement du premier bloc est parfaitement acceptable, cependant, il augmente par bloc! Cela n'a rien à voir avec la preprocess_data(df_chunk) car il n'y a aucune raison pour qu'elle augmente. Cette augmentation de temps se produit-elle à la suite de l'appel à pd.concat()?

Veuillez consulter le journal ci-dessous:

chunks 6
chunk 0
2016-04-08 00:22:17.728849
chunk 1
2016-04-08 00:22:42.387693 
chunk 2
2016-04-08 00:23:43.124381
chunk 3
2016-04-08 00:25:30.249369
chunk 4
2016-04-08 00:28:11.922305
chunk 5
2016-04-08 00:32:00.357365

Existe-t-il une solution de contournement pour accélérer cela? J'ai 2900 morceaux à traiter, donc toute aide est appréciée!

Ouvert à toutes autres suggestions en Python!

23
jfive

N'appelez jamais DataFrame.append Ou pd.concat Dans une boucle for. Cela conduit à une copie quadratique.

pd.concat Renvoie un nouveau DataFrame. De l'espace doit être alloué pour le nouveau DataFrame, et les données des anciens DataFrames doivent être copiées dans le nouveau DataFrame. Considérez la quantité de copie requise par cette ligne dans le for-loop (En supposant que chaque x a la taille 1):

super_x = pd.concat([super_x, x], axis=0)

| iteration | size of old super_x | size of x | copying required |
|         0 |                   0 |         1 |                1 |
|         1 |                   1 |         1 |                2 |
|         2 |                   2 |         1 |                3 |
|       ... |                     |           |                  |
|       N-1 |                 N-1 |         1 |                N |

1 + 2 + 3 + ... + N = N(N+1)/2. Il y a donc O(N**2) copies nécessaires pour terminer la boucle.

Considérez maintenant

super_x = []
for i, df_chunk in enumerate(df_list):
    [x, y] = preprocess_data(df_chunk)
    super_x.append(x)
super_x = pd.concat(super_x, axis=0)

L'ajout à une liste est une opération O(1) et ne nécessite pas de copie. Il y a maintenant un seul appel à pd.concat Après la boucle. Cet appel à pd.concat Nécessite N copies, car super_x Contient N DataFrames de taille 1. Ainsi, lorsqu'il est construit de cette façon, super_x Nécessite O(N) copies.

35
unutbu

Chaque fois que vous concaténez, vous retournez une copie des données.

Vous souhaitez conserver une liste de vos morceaux, puis tout concaténer comme étape finale.

df_x = []
df_y = []
for i, df_chunk in enumerate(df_list):
    print "chunk", i
    [x, y] = preprocess_data(df_chunk)
    df_x.append(x)
    df_y.append(y)

super_x = pd.concat(df_x, axis=0)
del df_x  # Free-up memory.
super_y = pd.concat(df_y, axis=0)
del df_y  # Free-up memory.
7
Alexander