Pandas: Parallélisation pour des tâches parallèles embarrassantes

Créé le 19 déc. 2013  ·  64Commentaires  ·  Source: pandas-dev/pandas

Je voudrais promouvoir l'idée d'appliquer l'exécution multiprocessing.Pool() de tâches parallèles embarrassantes, par exemple l'application d'une fonction à un grand nombre de colonnes.
De toute évidence, il y a des frais généraux pour le configurer, il y aura donc une limite inférieure de nombre de colonnes à partir de laquelle seule peut être plus rapide que l'approche cython déjà rapide. J'ajouterai mes tests de performances plus tard à ce numéro.
J'envoyais un e- @jreback à ce sujet et il a ajouté les remarques/complications suivantes :

  • le transfert de données numpy n'est pas si efficace - mais c'est quand même assez bon
  • vous pouvez transférer (pickle) des fonctions de type lambda, vous devrez donc peut-être utiliser une bibliothèque appelée dill (qui résout ce problème) - vous pourriez peut-être légèrement modifier msgpack pour le faire (et est déjà assez efficace pour transférer d'autres types d'objets)
  • pourrait également enquêter sur joblib - je pense que statsmodels l'utilise [ed: Cela semble être correct, j'ai lu dans leur groupe à propos de joblib]
  • Je créerais un nouveau noyau/parallèle de répertoire de niveau supérieur pour ce type de choses
  • la stratégie de ce lien pourrait être un bon moyen à suivre : http://stackoverflow.com/questions/17785275/share-large-read-only-numpy-array-between-multiprocessing-processes

liens:
http://stackoverflow.com/questions/13065172/multiprocess-python-numpy-code-for-processing-data-faster
http://docs.cython.org/src/userguide/parallelism.html
http://distarray.readthedocs.org/en/v0.5.0/

Docs Groupby IO HDF5 Performance

Commentaire le plus utile

J'ai chargé une trame de données avec environ 170 millions de lignes en mémoire (python a utilisé ~ 35 Go de RAM) et j'ai chronométré la même opération avec 3 méthodes et je l'ai exécutée pendant la nuit. La machine dispose de 32 cœurs physiques ou 64 cœurs hypervisés et de suffisamment de RAM libre. Bien que la conversion de date soit une opération très bon marché, elle montre la surcharge de ces méthodes.

Bien que la voie à un seul filetage soit la plus rapide, il est assez ennuyeux de voir un seul cœur fonctionner en continu à 100% alors que 63 sont au ralenti. Idéalement, je veux pour les opérations parallèles une sorte de traitement par lots pour réduire les frais généraux, par exemple toujours 100000 lignes ou quelque chose comme batchsize=100000.

<strong i="7">@interactive</strong>
def to_date(strdate) :
    return datetime.fromtimestamp(int(strdate)/1000)

%time res['milisecondsdtnormal']=res['miliseconds'].map(to_date)
#CPU times: user 14min 52s, sys: 2h 1min 30s, total: 2h 16min 22s
#Wall time: 2h 17min 5s

pool = Pool(processes=64)
%time res['milisecondsdtpool']=pool.map(to_date, res['miliseconds'])
#CPU times: user 21min 37s, sys: 2min 30s, total: 24min 8s
#Wall time: 5h 40min 50s

from IPython.parallel import Client
rc = Client() #local 64 engines
rc[:].execute("from datetime import datetime")
%time res['milisecondsipython'] = rc[:].map_sync(to_date, res['miliseconds'])
#CPU times: user 5h 27min 4s, sys: 1h 23min 50s, total: 6h 50min 54s
#Wall time: 10h 56min 18s

Tous les 64 commentaires

Peut-être que ce serait quelque chose de mieux fait dans un dossier sandbox/parallel
jusqu'à ce que nous décidions de ce qu'il est possible de faire ? Ce serait bien aussi d'avoir
quelqu'un pour tester les performances sur Windows aussi, j'ai entendu dire qu'il y a très
caractéristiques de performances différentes pour Windows vs Linux/OSX.

pour Windows vs. Linux/osx threading/multiprocessing c'est-à-dire.

Je pense que cela pourrait commencer par un mot-clé/une option facultatifs pour activer la calcul parallèle lorsque cela est utile. cela résout les problèmes Windows/Linux car la valeur par défaut pour faire le parallèle peut être différente (ou avoir des seuils différents, comme nous le faisons pour numexpr)

Ils pourraient tous être regroupés sous un attribut par comme les méthodes str . Qu'est-ce que tout est à l'esprit ici à part postuler ? La somme, la prod, etc. pourraient-elles en bénéficier ?

@michaelaye pouvez-vous mettre en place un exemple simple qui serait bien pour l'analyse comparative ?

Voici une implémentation utilisant joblib :

https://github.com/jreback/pandas/tree/parallel

et quelques timings.....j'ai dû utiliser une fonction assez artificielle en fait....
vous devez peser le temps de cornichon pour les sous-cadres par rapport au temps de fonction
le temps de pickle indiqué ci-dessous est sur le disque, ce qui n'est pas le cas lors de l'envoi aux sous-processus
(mais c'est quand même le facteur limitant je pense). Pour info, si le cadre est déjà sur le disque (par exemple HDF), cela pourrait avoir un avantage assez substantiel, je pense.

In [1]: df1  = DataFrame(np.random.randn(20000, 1000))

In [2]: def f1(x):
   ...:         result = [ np.sqrt(x) for i in range(10) ]
   ...:         return result[-1]
   ...: 
In [8]: %timeit df1.to_pickle('test.p')
1 loops, best of 3: 1.77 s per loop
# reg apply
In [3]: %timeit df1.apply(f1)
1 loops, best of 3: 6.28 s per loop

# using 12 cores (6 real x 2 hyperthread)
In [4]: %timeit df1.apply(f1,engine='joblib')
1 loops, best of 3: 2.06 s per loop

# 1 core pass thru
In [5]: %timeit df1.apply(f1,engine=pd.create_parallel_engine(name='joblib',force=True,max_cpu=1))
1 loops, best of 3: 6.28 s per loop

In [6]: %timeit df1.apply(f1,engine=pd.create_parallel_engine(name='joblib',force=True,max_cpu=4))
1 loops, best of 3: 2.68 s per loop

In [7]: %timeit df1.apply(f1,engine=pd.create_parallel_engine(name='joblib',force=True,max_cpu=2))
1 loops, best of 3: 3.87 s per loop

le temps de pickle l'emporte sur les gains de perf, la fonction est trop rapide donc aucun avantage ici

In [6]: %timeit df1.apply(f2)
1 loops, best of 3: 981 ms per loop

In [7]: %timeit df1.apply(f2,engine='joblib')
1 loops, best of 3: 1.8 s per loop

In [8]: def f2(x):
    return np.sqrt(x)
   ...: 

Vous avez donc besoin d'une fonction suffisamment lente sur une seule colonne pour que cela en vaille la peine
(ce serait assez facile de faire un exemple de synchronisation d'une seule colonne et de décider si aller en parallèle ou non)
pour le moment, il suffit de l'utiliser à la demande (utilisateur/option) spécifié

In [9]: %timeit f1(df1.icol(0))
100 loops, best of 3: 5.89 ms per loop

In [10]: %timeit f2(df1.icol(0))
1000 loops, best of 3: 639 ᄉs per loop

Bonne rédaction Jeff. Je pense que le temps de cornichon est un facteur important, mais aussi le temps d'engendrer un nouveau processus.

J'imagine que cela fonctionne avec un ensemble de processus de calcul qui sont pré-lancés au démarrage et attendent de fonctionner. Pour mon futur travail parallèle, j'utiliserai probablement le parallèle iPython sur des données hdf5 distribuées. Le problème que je rencontre souvent est la consommation de mémoire croissante pour les processus python qui durent trop longtemps.

Sur le disque, l'accès parallèle aux morceaux de rangée HDF5 pour accélérer le calcul sonne bien.

@dragoljub

ouais... Je ne pense pas que l'ajout d'un back-end parallèle IPython serait si difficile (ou un autre type de backends distribués). Il suffit d'hériter et de se connecter.

HDF5 et groupby s'appliquent comme des cas particulièrement intéressants à améliorer avec cela.

Veuillez jouer et me donner des commentaires sur l'API (et même essayer un backend !)

http://docs.cython.org/src/userguide/parallelism.html

un moteur Cynthon est également simple (bien qu'il nécessite un léger changement de configuration pour compiler avec le support OpenMP)
mais semble simple

J'ai chargé une trame de données avec environ 170 millions de lignes en mémoire (python a utilisé ~ 35 Go de RAM) et j'ai chronométré la même opération avec 3 méthodes et je l'ai exécutée pendant la nuit. La machine dispose de 32 cœurs physiques ou 64 cœurs hypervisés et de suffisamment de RAM libre. Bien que la conversion de date soit une opération très bon marché, elle montre la surcharge de ces méthodes.

Bien que la voie à un seul filetage soit la plus rapide, il est assez ennuyeux de voir un seul cœur fonctionner en continu à 100% alors que 63 sont au ralenti. Idéalement, je veux pour les opérations parallèles une sorte de traitement par lots pour réduire les frais généraux, par exemple toujours 100000 lignes ou quelque chose comme batchsize=100000.

<strong i="7">@interactive</strong>
def to_date(strdate) :
    return datetime.fromtimestamp(int(strdate)/1000)

%time res['milisecondsdtnormal']=res['miliseconds'].map(to_date)
#CPU times: user 14min 52s, sys: 2h 1min 30s, total: 2h 16min 22s
#Wall time: 2h 17min 5s

pool = Pool(processes=64)
%time res['milisecondsdtpool']=pool.map(to_date, res['miliseconds'])
#CPU times: user 21min 37s, sys: 2min 30s, total: 24min 8s
#Wall time: 5h 40min 50s

from IPython.parallel import Client
rc = Client() #local 64 engines
rc[:].execute("from datetime import datetime")
%time res['milisecondsipython'] = rc[:].map_sync(to_date, res['miliseconds'])
#CPU times: user 5h 27min 4s, sys: 1h 23min 50s, total: 6h 50min 54s
#Wall time: 10h 56min 18s

ce que vous chronométrez ici n'est pas du tout clair ; la façon dont pool et ipython se séparent est extrêmement pauvre; ils transforment ce type de tâche en beaucoup d'évaluations scalaires où le coût de transport est BEAUCOUP plus élevé que le temps d'évaluation.

le pr fait exactement ce type de dosage

vous devez faire en sorte que chaque processeur exécute une tranche et y travaille en une seule tâche (pour chaque processeur), ne pas perturber le pool comme vous le montrez.

@michaelaye avez-vous jeté un œil à cette branche ? https://github.com/jreback/pandas/tree/parallel

Oh, c'est excitant. J'attendais une fonction d'application de dispersion/regroupement parallèle utilisant IPython.parallel. Veuillez nous tenir au courant de tout progrès ici.

En effet! Ce serait une excellente fonctionnalité. J'utilise concurrent.futures et cela rend les choses assez faciles, mais le coût de mise en place de nouveaux processus prend encore beaucoup de temps. Si nous avons des noyaux parallèles IPython qui n'attendent que de travailler avec toutes les importations appropriées, leur transmettre des pointeurs de données et agréger les résultats serait fantastique.

@dragoljub tu as mis le joblib est bien, mais vous n'avez souvent pas besoin de générer des processus de cette façon ; généralement, vous avez un moteur qui traîne.

Avez-vous un code que je pourrais détourner ?

Je ne pense pas qu'il serait très difficile d'ajouter ceci en utilisant IPython.parallel ; Je ne l'ai tout simplement jamais utilisé (comme pour ce que je fais, j'engendre souvent des processus de longue durée)

C'est peut-être exagéré, mais j'ai un cahier sur l'utilisation d'IPython.parallel ici. Il y a quelques exemples rapides.

https://github.com/jseabold/zorro

Leurs documents sont également assez bons

http://ipython.org/ipython-doc/dev/parallel/

merci skipper... ok prochaine chose... avez-vous des exemples non triviaux à des fins de vbench ? par exemple, des choses qui font un travail réel (et prennent un temps non négligeable) qui peuvent être utilisées pour l'analyse comparative ? (doit être relativement autonome .... bien que cela puisse évidemment utiliser des modèles de statistiques :)

Il se trouve que je rencontre un tel problème en ce moment. :) Je saute la candidature en faveur de la carte joblib.Parallel. Laissez-moi voir si je peux le rendre autonome.

Hum, c'est peut-être trop trivial. Mon cas d'utilisation actuel prend beaucoup plus de temps 20 obs ~ 1s et les données sont assez volumineuses. Trouver la première occurrence d'un mot dans un texte. Vous pouvez augmenter n , allonger les "titres", inclure l'unicode, etc. et cela prend rapidement du temps.

n = 100

random_strings = pd.util.testing.makeStringIndex().tolist()
X = pd.DataFrame({'title' : random_strings * n,
                                'year' : np.random.randint(1400, 1800, size=10*n)})

def min_date(x): # can't be a lambda for joblib/pickling
    # watch out for global
    return X.ix[X.title.str.contains('\\b{}\\b'.format(x))].year.min()

X.title.apply(min_date) 

Il y a peut-être de meilleurs exemples dans ipython/examples/parallel . Il y en a aussi un couple dans mon carnet. Par exemple, l'optimisation parallèle, mais je ne suis pas sûr que ce soit un cas d'utilisation réel de l'application scatter-gather à laquelle je pense. Quelque chose comme

def crazy_optimization_func(...):
    ....

df = pd.DataFrame(random_start_values)
df.apply(crazy_optimization_func, ...)

Où le DataFrame contient des lignes de valeurs de départ aléatoires et vous parcourez l'axe zéro pour effectuer l'optimisation globale du pauvre.

Quelques inspirations API. Voir aaply, aply, etc.

http://cran.r-project.org/web/packages/plyr/index.html

J'ai oublié que Hadley a gentiment réattribué la licence de tout son code à la compatibilité BSD, vous pouvez donc vous inspirer de plus que l'API si, pour une raison quelconque, vous êtes curieux.

en fait, l'api est vraiment très simple :

df.apply(my_cool_function, engine='ipython')

from IPython.parallel import Client
df.apply(my_cool_function,engine=pd.create_parallel_engine(client=Client(profile='mpi')))

par exemple vous venez de passer un moteur (prob vous permettra simplement de passer un Client directement en tant que moteur)

Super.

Vous pouvez également autoriser uniquement « ipython » et utiliser un appel Client() par défaut. Si vous démarrez votre session/notebook IPython avec le bon profil, il doit le respecter et rechercher dans ce répertoire le code de configuration dont il a besoin. Il y avait un bogue ici dans certains IPython 1.x mais il devrait être corrigé maintenant.

https://github.com/ipython/ipython/issues/4238

passer engine='ipython' créera un client par défaut
également réglable via une option, parallel.default_engine

et ne passera qu'avec un nombre seuil de lignes (une fonction pourrait également le faire)

Ça à l'air génial. Je ne peux pas attendre ça. Ça va être une grande fonctionnalité.

Quel est le statut de ceci ? Cela semble génial. Avez-vous juste besoin de quelques fonctions pour les benchmarks ? Je peux proposer quelque chose si c'est utile/tout ce qui est nécessaire.

combien devrait prendre une fonction cible (par ligne, disons ; c'est ce sur quoi j'applique toujours) ? 0,1 s ? 1 seconde ? 10 secondes ? Limites de la RAM ?

Eh bien, cela fonctionne pour joblib, en quelque sorte avec IPython.parallel. a besoin de plus de travail/temps. Je suis également convaincu que vous avez besoin d'une tâche assez intensive pour que cela soit vraiment utile. par exemple, le temps de création/comm n'est pas trivial.

Je n'aurai pas le temps pour ça pendant un moment, mais mon code est là.

Je suis vraiment excité à ce sujet ! J'ai écrit ma propre fonction pour diviser - map - concat mais j'ai rencontré des problèmes avec le multitraitement (http://stackoverflow.com/questions/26665809/multiprocessing-on-pandas-dataframe-confusing-behavior-based-on-input-size ).

Des idées quand cela pourrait sortir?

+1 pour le moteur d'application parallèle !

Cela fait un an que @jreback a mis à disposition une version parallèle des pandas et on ne sait pas ce qu'est un hold-up ne permettant pas d'en faire une fonctionnalité des pandas ordinaires (même sous une forme "test").

Bien qu'il ait été démontré que cela fonctionne, il semble que l'on comprenne que des tests supplémentaires pourraient être nécessaires. Est-ce que rendre cette fonctionnalité disponible/accessible (même à des fins de test) dans les pandas réguliers ne faciliterait-il pas davantage de tests et de rapports de bogues ?

@wikiped
Je n'ai pas pu faire fonctionner cela du point de vue des tests pour les UDF génériques (fonctions définies par l'utilisateur). et manquait de temps.

C'est en fait beaucoup plus facile/mieux à utiliser : http://blaze.pydata.org/docs/latest/index.html
pour ce type de parallélisation car il est assez bien supporté de nos jours.

Ce serait une fonctionnalité complémentaire qui nécessiterait en fait pas mal de travail/tests/analyse comparative pour faire partie du noyau de pandas.

Vous êtes invités à le faire si vous le souhaitez.

@jreback
Merci d'avoir partagé votre point de vue à ce sujet. J'aimerais pouvoir aider en ce qui concerne le code, mais malheureusement, c'est au-dessus de mon niveau de compétence.

Et merci pour le lien blaze - il faudra approfondir le sujet - à première vue, cela semble intéressant, mais me fait me sentir perdu avec tous les nouveaux concepts et ne pas (encore) atteindre les documents/exemples.

Lier cela avec #3202.

Y a-t-il eu des progrès à ce sujet? J'aimerais aider si nous avons besoin de développeurs pour travailler là-dessus ! Une table de données parallèle serait _vraiment utile_ étant donné que j'ai une machine à 36 cœurs utilisant seulement 1 thread :( Pyspark a une implémentation parallèle mais elle est vraiment lente sur une seule machine.

Il y a beaucoup de nouveaux outils intéressants dans cet espace : dask, Ibis, SFrame. Je recommanderais de jeter un coup d'œil à certains d'entre eux. Étendre les pandas pour prendre en charge nativement le calcul de base serait formidable, mais ce serait beaucoup de travail et ce n'est dans l'agenda immédiat de personne. Honnêtement, il vaut probablement mieux le laisser à d'autres projets.

Le vendredi 24 juillet 2015 à 8h59, abell25 [email protected] a écrit :

Y a-t-il eu des progrès à ce sujet? J'aimerais aider si nous avons besoin de développeurs pour travailler là-dessus ! Une table de données parallèle serait _vraiment utile_ étant donné que j'ai une machine à 36 cœurs utilisant seulement 1 thread :( Pyspark a une implémentation parallèle mais elle est vraiment lente sur une seule machine.

Répondez directement à cet e-mail ou consultez-le sur GitHub :
https://github.com/pydata/pandas/issues/5751#issuecomment-124566446

l'utilisation de pandas de manière parallèle nécessite une bonne quantité de travail sur la mise en œuvre. Heureusement, Dask l' a déjà fait, et cela fonctionne bien avec les pandas (en fait, nous avons publié le gil et créé le CategoricalIndex pour faciliter cela. Je recommanderais donc ceci pour des calculs parallèles embarrassants (et en fait des calculs parallèles généralisés) .

Je vais changer ce problème en l'un des docs, car je pense que nous devrions ajouter une section à enhancingperf.rst pour indiquer la voie à dask.

cc @mrocklin

De plus, il peut être souhaitable de fournir une interface du côté des pandas à dask (et peut-être à certaines des bibliothèques de calcul hors-core/parallèles), par exemple quelque chose du genre de

df.apply(f, engine='dask') . Qui peut expédier le calcul. Cependant, ce ne serait pas un mécanisme généralisé car souvent ce que vous voulez vraiment faire est de faire une série de calculs. Donc pas sûr de ce que nous devrions faire ici.

Je serais très heureux de travailler avec des personnes intéressées par la mise en parallèle de calculs de type pandas avec dask. Je soupçonne que cela impliquera un peu de travail à la fois pour s'assurer que dask.dataframe englobe toutes les API Pandas nécessaires et pour s'assurer que Pandas publie le GIL aux emplacements appropriés.

Faire en sorte que cela fonctionne très bien nécessite probablement l'action de quelques parties, d'un utilisateur avec une application de conduite, d'un développeur core pandas et d'un développeur core dask. Jeff et moi avons eu de bonnes interactions à ce sujet dans le passé ; ce serait bien d'ajouter quelques personnes avec des cas d'utilisation concrets.

Est-ce que df.apply(f, engine='dask') (ou tout autre moteur) _doit_ renvoyer le même résultat que df.apply(f) ? Ou Dask pourrait-il renvoyer un graphique Dask ? Je serais intéressé de voir ce que le retour d'un graphique vous permettrait de faire et à quel point ce serait difficile à mettre en œuvre? J'adorerais en parler aujourd'hui

@TomAugspurger

SI nous avons fait cela, alors je pense que cela ferait simplement le calcul (en parallèle) et renverrait le résultat. Un peu hé, utilisez simplement le type de chose multi-cœurs. Mon point de vue est que cela donne aux utilisateurs moyens de pandas un gros marteau, donc au lieu de vectoriser (ou d'utiliser cython/numba), ils vont IMMÉDIATEMENT vers le parallèle quand ce n'est pas nécessaire.

Voilà donc mon hésitation ici. (d'autres ont fait valoir que vous vous contentez de documenter/d'éduquer et de fournir les outils). Mon argument est que nous ne devrions peut-être PAS du tout faire cela dans les pandas, et si vous êtes suffisamment sophistiqué pour utiliser l'exécution parallèle, alors utiliser dask mainline (ou ibis et al.) est la bonne façon de procéder.

Mes opinions correspondent à celles de

Très raisonnable.

D'un autre côté, pour les utilisateurs novices de pandas, utiliser numba ou même dask (et parfois même vectoriser) n'est pas si facile tout en améliorant les performances sans avoir besoin de faire quoi que ce soit (c'est-à-dire une option de configuration "utiliser le parallèle partout" définie sur True ) est un très bon avantage pour tout le monde, même les utilisateurs expérimentés.

Je pense que nous pourrions ajouter un moteur = argument

et permet de dire : numba, dask (si ils sont installés)

qui fait la plupart du temps là-bas

D'accord avec les commentaires ci-dessus :

Je pense que si vos données vivent déjà dans un DataFrame pandas, une exécution parallèle avide fournit probablement la plupart des avantages auxquels vous devriez vous attendre sans rien d'inattendu.

C'est mort.

De plus, j'aime beaucoup l'idée d'une option moteur = argument. Ce serait un énorme avantage pour la plupart des utilisateurs finaux, en particulier ceux qui utilisent les pandas comme dépendance principale dans leurs propres applications - parallélisme immédiat entre .map et .apply avec l'inclusion d'une dépendance (ala dask) et / ou JIT (ala numba) et une option de configuration simple.

Excité à ce sujet, et tout simplement incroyable, tout le travail ici. Bravo à vous tous.

Est-il possible d'intégrer des pandas avec le décorateur @parallel dans ipyparallel, comme l'exemple qu'ils ont avec numpy ?

http://ipyparallel.readthedocs.org/en/latest/multiengine.html#remote -function-decorators

Je pense que théoriquement parlant, même les pandas ne prennent pas en charge le calcul parallèle par défaut, l'utilisateur peut toujours se référer à mpi4py pour le calcul parallèle. C'est juste un peu plus de temps de codage si l'on connaît déjà MPI.

@ Aspire1Inspire2 vérifie le cadre de données dask. https://jakevdp.github.io/blog/2015/08/14/out-of-core-dataframes-in-python/

doc simplement ceci pour diriger vers dask

J'ai essayé groupby et appliqué avec pandas 0.17.1, et surpris de voir que la fonction est appliquée en parallèle. Je suis confus, cette fonctionnalité est-elle déjà ajoutée et activée par défaut ? Je n'utilise pas dask.

@heroxbd eh bien, le GIL est libéré lors des opérations groupby. Cependant il n'y a pas de parallélisme nativement/inhérent au groupby. Pourquoi pensez-vous que c'est en parallèle?

La preuve est que toutes les charges de mes processeurs atteignent 100% lorsque j'appelle groupby s'applique.
sph = tt.groupby('ev').apply(sphericity)

donc si vous faites des opérations arithmétiques dans sphericity (par exemple df + df ) ou comme. Ceux-ci se réfèrent à numexpr , qui utilise des multi-cœurs par défaut. Pouvez-vous fournir un croquis de cette fonction?

Ce sont des choses de performance que les pandas font sans que l'utilisateur en soit spécifiquement conscient.

@jreback Ah-ha. C'est ici:
pmtl, ql = event['pmt'].values, event['q'].values
pl = pdir[pmtl] * ql[:,np.newaxis]
s = np.sum(pl[:,:,np.newaxis] * pl[:,np.newaxis,:], axis=0)
qs = np.sum(ql _2)eig = np.sort(np.linalg.eigvals(s/qs))return pd.Series({'S': (eig[0] + eig[1])_1.5, 'A': eig[0] 1.5}) # sphéricité, aplanarité

Désolé pour le bruit. L'exécution parallèle provient d'OpenMP utilisé par OpenBLAS, qui à son tour est utilisé par NumPy.

fermer ceci en tant que dask est le plus approprié pour cela. Cela pourrait certainement ajouter une certaine utilisation au sein des pandas pour utiliser réellement dask, mais ce sont des problèmes distincts (par exemple, sur un groupe suffisamment grand (nombre de groupes), le report à dask est une bonne chose)

Y avait-il une raison à la publication du GIL dans les opérations de groupe par pandas pour autoriser uniquement les opérations de groupe et d'application séparées à se produire simultanément plutôt que de calculer des agrégations indépendantes au niveau du groupe en parallèle ?

Une fois que vous disposez d'opérations groupby compatibles avec GIL, d'autres développeurs peuvent utiliser Pandas en parallèle. Il est en fait assez délicat d'écrire intelligemment les algorithmes pour gérer tous les groupbys. Je pense que si quelqu'un veut faire ça pour les pandas, ce serait génial. C'est quand même une entreprise assez sérieuse.

Pour ce faire avec dask.dataframe

$ conda install dask
or 
$ pip install dask[dataframe]
import pandas as pd
df = ... your pandas code ...

import dask.dataframe as dd
df = dd.from_pandas(df, npartitions=20)
dd_result = df.groupby(df.A).B.mean()  # or whatever
pd_result = dd_result.compute()  # uses as many threads as you have logical cores

Bien sûr, cela ne fonctionne pas sur tous les groupbys (comme mentionné précédemment, c'est incroyablement difficile à faire en général), mais cela fonctionne dans de nombreux cas courants.

@mrocklin merci pour le conseil. Combien de temps df = dd.from_pandas(df, npartitions=20) prendrait-il sur une trame de données avec, disons, 10 à 100 millions de lignes ? Y a-t-il une copie impliquée? Dask prend-il en charge les colonnes catégorielles ?

Il n'y a qu'une seule copie impliquée (uniquement pour les autres cadres de données de pandas). En fait, nous ne faisons que dfs = df.iloc[i: i + blocksize] for i in range(0, len(df), blocksize)] . Nous faisons également un tri sur l'index à l'avance que, si vos algorithmes ne l'exigent pas, vous pouvez désactiver avec sort=False (bien que certains groupbys, en particulier groupby.applys apprécieront certainement un index trié .)

Généralement, l'appel from_pandas est bon marché par rapport aux appels groupés. La copie des données en mémoire s'exécute généralement assez rapidement.

@dragoljub

En particulier YMMV en fonction de choses telles que le nombre de groupes que vous traitez et la façon dont vous partitionnez

Petit nombre de groupes

In [7]: N = 10000000

In [8]: ngroups = 100

In [9]: df = pd.DataFrame({'A' : np.random.randn(N), 'B' : np.random.randint(0,ngroups,size=N)})

In [10]: %timeit df.groupby('B').A.mean()
10 loops, best of 3: 161 ms per loop
In [15]: ddf = dd.from_pandas(df, npartitions=8)

In [16]: %timeit ddf.groupby(ddf.B).mean().compute()
1 loop, best of 3: 223 ms per loop

Plus grand nombre de groupes

In [17]: ngroups = 10000

In [18]: df = pd.DataFrame({'A' : np.random.randn(N), 'B' : np.random.randint(0,ngroups,size=N)})

In [19]: %timeit df.groupby('B').A.mean()
1 loop, best of 3: 591 ms per loop

In [21]: %timeit ddf.groupby(ddf.B).mean().compute()
1 loop, best of 3: 323 ms per loop

Peut faire encore mieux si vous utilisez réellement notre index

In [32]: ddf = dd.from_pandas(df.set_index('B'), npartitions=8)

In [33]: %timeit ddf.groupby(ddf.index).mean().compute()
1 loop, best of 3: 215 ms per loop

Notez que ce sont des timings assez naïfs. Il s'agit d'un calcul _unique_ qui est divisé en tâches parallèles embarrassantes. En général, vous utiliserez dask pour plusieurs étapes d'un calcul. Si vos données _ne rentrent pas_ dans la mémoire, ce dask peut souvent aider beaucoup plus.

Dans un calcul parallèle embarrassant, je crée de nombreuses trames de données qui doivent être transférées sur le disque (il s'agit de la sortie souhaitée du programme). J'ai essayé de faire le calcul et le dumping (vers hdf5) en parallèle à l'aide de joblib. Et je rencontre des problèmes avec HDFWrite. Notez qu'à ce stade, je ne m'inquiète pas tellement des performances de l'écriture parallèle.

Un exemple qui démontre le problème est dans https://github.com/rbiswas4/ParallelHDFWriteProblem

Le programme demo_problem.py inclut une fonction appelée worker(i) qui crée une trame de données très simple basée sur l'entrée i et l'ajoute à un fichier hdf. Je le fais deux fois en série et une fois en parallèle en utilisant joblib.

Ce que je trouve, c'est que le boitier série fonctionne toujours. Mais le cas parallèle n'est pas reproductible : Parfois ça marche sans problème et parfois ça plante. Les deux fichiers journaux https://github.com/rbiswas4/ParallelHDFWriteProblem/blob/master/demo.log.worked
et
https://github.com/rbiswas4/ParallelHDFWriteProblem/blob/master/demo.log_problem
sont deux cas où cela a fonctionné et n'a pas fonctionné respectivement.

Existe-t-il un meilleur moyen d'écrire dans des fichiers hdf de manière parallèle à partir de pandas que je devrais utiliser ? Est-ce une question pour d'autres forums comme joblib et pyTables ?

@rbiswas4 Si vous souhaitez vider un tas de données sur le disque en parallèle, la chose la plus simple à faire est de créer un fichier HDF5 distinct pour chaque processus. Votre approche va certainement entraîner des données corrompues - consultez la FAQ pytables pour plus de détails (http://www.pytables.org/FAQ.html#can-pytables-be-used-in-concurrent-access-scenarios) . Vous pourriez également être intéressé par dash.

Écrire dans des fichiers séparés est quelque chose que je dois éviter car je pourrais finir par créer trop de fichiers (limites d'inode). Je suppose que cela signifie l'un des éléments suivants :

  • Je ne devrais pas regarder hdf5 comme format de sortie possible, mais utiliser une base de données.
  • Utilisez un plus petit nombre de partitions et donc de fichiers créés. C'est encore assez inélégant
  • envisager une méthodologie par laquelle je peux écrire les dataframes via un processus séparé (je ne sais pas comment diviser les choses)

Il semble que le premier soit le meilleur pari. Et, oui j'ai l'intention de voir si je dois utiliser dask !

Merci @shoyer

@rbiswas4 , si vous pouvez écrire hdf5 brut (via h5py) au lieu de pytables, veuillez consulter SWMR,

https://www.hdfgroup.org/HDF5/docNewFeatures/NewFeaturesSwmrDocs.html

disponible en hdf5-1.10.

Cette page vous a été utile?
0 / 5 - 0 notes