1
votes

Alternatives plus rapides à Pandas pivot_table

J'utilise la fonction Pandas pivot_table sur un grand ensemble de données (10 millions de lignes, 6 colonnes). Le temps d'exécution étant primordial, j'essaie d'accélérer le processus. Actuellement, il faut environ 8 secondes pour traiter l'ensemble de données, ce qui est beaucoup trop lent et j'espère trouver des alternatives pour améliorer la vitesse / les performances.

Ma table pivot Pandas actuelle:

df_new = df_original.groupby(["months", "industry"]).agg({"orders": np.sum, "client_name": pd.Series.nunique}).unstack(level="months").fillna(0)

df_original inclut toutes les données (10m lignes, importées depuis un csv). L'industrie est l'industrie du client, les mois sont les mois de commande (janvier à décembre), les commandes sont le nombre de commandes. Toutes les données ont été converties en données catégorielles , à l'exception du nombre de commandes (type de données int ). À l'origine, l'industrie, les mois et le nom du client étaient des chaînes.

J'ai essayé d'utiliser pandas.DataFrame.unstack - ce qui était encore plus lent. J'ai également expérimenté avec Dask . Le dask pivot_table a apporté une certaine amélioration (temps d'exécution de 6 sec - donc 2 sec de moins). Cependant, c'est encore assez lent. Existe-t-il des alternatives plus rapides (pour les grands ensembles de données)? Peut-être recréation du tableau croisé dynamique avec groupy , crosstab , ... Malheureusement, je n'ai pas du tout fait fonctionner les alternatives et je suis encore assez nouveau dans Python et Pandas ... Nous attendons vos suggestions avec plaisir. Merci d'avance!

Mise à jour:

J'ai trouvé le groupby way avec:

df_pivot = df_original.pivot_table(index="industry", columns = "months",
                    values = ["orders", "client_name"],
                    aggfunc ={"orders": np.sum, "client_name": pd.Series.nunique})

C'est beaucoup plus rapide maintenant avec environ 2-3 secondes. Y a-t-il encore des options pour améliorer encore la vitesse?


9 commentaires

Quel est votre environnement d'exécution? Plusieurs cœurs, cluster ou une seule machine? Grande RAM ou accès rapide au disque? Optimiser les performances signifie connaître les ressources dont vous disposez.


Vous avez raison, désolé !! Machine unique, 16 Go de RAM, 8 cœurs (processeur i7-8650U à 1,90 GHz). J'exécute le code dans mon IDE (Visual Studio Code). Idéalement, mon module devrait également fonctionner "rapidement" sur une machine avec moins de RAM (par exemple 8 Go) et moins de puissance CPU ...


OK question suivante, les données sont-elles classées? c.-à-d. pouvez-vous vous fier aux divisions que vous souhaitez créer dans vos pivots pour se refléter dans les données sous-jacentes? Il serait également intéressant de trouver une limite inférieure de l'attente en chronométrant une fonction modérément plus simple, juste pour voir à quoi pourrait ressembler un meilleur résultat. par exemple. combien de temps faut-il pour lire chaque ligne du fichier en mémoire sans aucun traitement supplémentaire?


Les données (c'est-à-dire le nombre de commandes ) ne sont pas commandées. Devrait-ce être? Comment cela contribue-t-il à la performance? Je convertis d'abord le fichier csv, puis je le convertis au format HDF5 pour un chargement plus rapide la prochaine fois que je travaillerai avec l'ensemble de données. Je ne m'inquiète pas tellement de lire chaque ligne du fichier en mémoire. Peut-être ai-je mal compris votre question ...


J'ai trouvé la méthode groupby avec: df_new = df_original.groupby (["mois", "industrie"]). Agg ({"orders": np.sum, "client_name": pd.Series.nunique}). unstack (level = "mois"). fillna (0) C'est beaucoup plus rapide maintenant avec environ 2 secondes. Y a-t-il encore des options pour améliorer encore la vitesse?


Changez simplement import pandas.modin en pd et voyez les différences.


@ astro123: J'utilise Visual Studio Code sur une machine Windows avec Python 3.7.2 64 bits. Lorsque j'exécute pip install modin , j'obtiens le message d'erreur "Impossible de trouver une version qui satisfait l'exigence ray (à partir des versions:) Aucune distribution correspondante trouvée pour ray". Pourquoi donc? J'aimerais vraiment essayer modin / Ray et je n'ai pas trouvé de réponse sur: ray.readthedocs.io/en/latest/...


@pythoneer Je vous suggère fortement d'installer ANACONDA et de créer de nouveaux environnements. J'ai également rencontré des erreurs d'installation de ce module à plusieurs reprises et la meilleure solution que j'ai trouvée est d'utiliser conda et de créer un nouvel environnement chaque fois que j'ai des problèmes avec l'installation d'un module dans un environnement donné. J'espère que cela pourra aider.


Suivi de la méthode groupby - comment puis-je ajouter une ligne et une colonne de total / sous-total comparables aux pandas.pivot_table marges ? Je peux ajouter une ligne totale avec df_new ["all"] = df_new.sum (axis = 0) et une colonne avec df_new ["orders", "all"] = df_new ["orders "] .sum (axis = 1) . Cependant, cela ne fonctionne pas pour les valeurs nunique . Comment puis-je accomplir cela? Je veux uniquement le nombre unique de clients pour les colonnes qui incluent le nombre de clients, c'est-à-dire que je veux afficher la somme des colonnes avec le nombre de commandes et le nombre unique de clients dans les colonnes avec nom_client. Avez-vous une idée? Merci


3 Réponses :


1
votes

Convertissez les colonnes mois et secteur en colonnes catégorielles: https://pandas.pydata.org/pandas-docs/stable/ user_guide / categorical.html De cette façon, vous évitez de nombreuses comparaisons de chaînes.


1 commentaires

Merci, je l'ai déjà fait et le temps d'exécution est d'environ 8 secondes. Toutes les données ont été converties en colonnes catégoriques auparavant, par exemple df_original ["industrie"] = df_original ["industrie"]. astype ("catégorie")



-1
votes

Lorsque vous lisez le fichier csv dans un df, vous pouvez passer une fonction de conversion (via le paramètre read_csv converters ), pour transformer client_name en hachage et downcast commandes à un type int approprié, en particulier non signé.

Cette fonction répertorie les types et leurs plages:

    int,  0:       <class 'numpy.int8'>  From:                 -128 To: 127
    int,  1:      <class 'numpy.int16'>  From:               -32768 To: 32767
    int,  2:      <class 'numpy.int32'>  From:          -2147483648 To: 2147483647
    int,  3:      <class 'numpy.int64'>  From: -9223372036854775808 To: 9223372036854775807
   uint,  0:      <class 'numpy.uint8'>  From:                    0 To: 255
   uint,  1:     <class 'numpy.uint16'>  From:                    0 To: 65535
   uint,  2:     <class 'numpy.uint32'>  From:                    0 To: 4294967295
   uint,  3:     <class 'numpy.uint64'>  From:                    0 To: 18446744073709551615
  float,  0:    <class 'numpy.float16'>
  float,  1:    <class 'numpy.float32'>
  float,  2:    <class 'numpy.float64'>
complex,  0:  <class 'numpy.complex64'>
complex,  1: <class 'numpy.complex128'>
 others,  0:             <class 'bool'>
 others,  1:           <class 'object'>
 others,  2:            <class 'bytes'>
 others,  3:              <class 'str'>
 others,  4:       <class 'numpy.void'>

Sortie:

import numpy as np

def list_np_types():
    for k, v in np.sctypes.items():
        for i, d in enumerate(v):
            if np.dtype(d).kind in 'iu':
                # only int and uint have a definite range
                fmt = '{:>7}, {:>2}: {:>26}  From: {:>20}\tTo: {}'
                print(fmt.format(k, i, str(d),
                                 str(np.iinfo(d).min),
                                 str(np.iinfo(d).max)))

            else:
                print('{:>7}, {:>2}: {:>26}'.format(k, i, str(d)))


list_np_types()


2 commentaires

Merci pour la suggestion. Le downcast à uint améliorerait-il la vitesse? Qu'entendez-vous par transformer client_name en hachage? Quelle est l'idée / l'amélioration derrière cela?


Mon mauvais sur client_name : c'est déjà catégorique. Pour réduire les commandes , vous devez savoir quelle est la limite maximale ou supérieure; je suppose que vous ne devriez pas avoir besoin de unit64 (pandas par défaut). Consultez ce message. L'idée est que, si vous voulez que votre code soit aussi rapide sur une machine avec la moitié de la RAM, cela devrait vous aider.



0
votes

Vous pouvez utiliser des matrices clairsemées. Ils sont rapides à mettre en œuvre, mais un peu restreints. Par exemple: vous ne pouvez pas faire d'indexation sur un COO_matrix

J'ai récemment eu besoin de former un système de recommandation (lightFM) et il a accepté des matrices clairsemées en entrée, ce qui a rendu mon travail beaucoup plus facile. Regardez-le en action:

>>> print(mat)
  (0, 0)    4
  (3, 3)    5
  (1, 1)    7
  (0, 2)    9
>>> print(mat.toarray())
[[4 0 9 0]
 [0 7 0 0]
 [0 0 0 0]
 [0 0 0 5]]
row  = np.array([0, 3, 1, 0])
col = np.array([0, 3, 1, 2])
data = np.array([4, 5, 7, 9])
mat = sparse.coo_matrix((data, (row, col)), shape=(4, 4))

Comme vous pouvez le voir, il crée automatiquement un tableau croisé dynamique pour vous en utilisant les colonnes et les lignes des données que vous avoir et remplit le reste avec des zéros. Vous pouvez également convertir la matrice éparse en tableau et en dataframe ( df = pd.DataFrame.sparse.from_spmatrix (mat, index = ..., columns = ...) )


0 commentaires