J'ai récemment trouvé le module dask qui vise à être un module de traitement parallèle python facile à utiliser. Le gros argument de vente pour moi est que cela fonctionne avec les pandas.
Après avoir lu un peu sur sa page de manuel, je ne trouve pas le moyen de faire cette tâche trivialement parallélisable:
ts.apply(func) # for pandas series
df.apply(func, axis = 1) # for pandas DF row apply
À l'heure actuelle, pour y parvenir en dask, autant que je sache,
ddf.assign(A=lambda df: df.apply(func, axis=1)).compute() # dask DataFrame
qui est la syntaxe laide et est en fait plus lent que carrément
df.apply(func, axis = 1) # for pandas DF row apply
Toute suggestion?
Edit: Merci @MRocklin pour la fonction de carte. Il semble être plus lent que les pandas ordinaires. Est-ce lié à un problème de libération de GIL par des pandas ou est-ce que je le fais mal?
import dask.dataframe as dd
s = pd.Series([10000]*120)
ds = dd.from_pandas(s, npartitions = 3)
def slow_func(k):
A = np.random.normal(size = k) # k = 10000
s = 0
for a in A:
if a > 0:
s += 1
else:
s -= 1
return s
s.apply(slow_func) # 0.43 sec
ds.map(slow_func).compute() # 2.04 sec
map_partitions
Vous pouvez appliquer votre fonction à toutes les partitions de votre dataframe avec la fonction map_partitions
.
df.map_partitions(func, columns=...)
Notez que l'on ne donnera à func qu'une partie du jeu de données à la fois, et non le jeu de données entier comme avec pandas apply
(ce que vous ne voudriez probablement pas faire si vous voulez faire du parallélisme.)
map
/apply
Vous pouvez mapper une fonction par rangée sur une série avec map
df.mycolumn.map(func)
Vous pouvez mapper une fonction ligne par ligne sur une image de données avec apply
df.apply(func, axis=1)
A partir de la version 0.6.0, dask.dataframes
est en parallèle avec les threads. Les fonctions Python personnalisées ne bénéficieront pas beaucoup du parallélisme basé sur les threads. Vous pouvez essayer des processus à la place
df = dd.read_csv(...)
from dask.multiprocessing import get
df.map_partitions(func, columns=...).compute(get=get)
apply
Cependant, vous devriez vraiment éviter apply
avec les fonctions Python personnalisées, à la fois dans Pandas et dans Dask. C'est souvent une source de piètre performance. Il se peut que si vous trouviez le moyen de faire votre opération de manière vectorielle, votre code Pandas serait 100 fois plus rapide et vous n’auriez plus besoin de dask.dataframe.
numba
Pour votre problème particulier, vous pourriez envisager numba
. Cela améliore considérablement vos performances.
In [1]: import numpy as np
In [2]: import pandas as pd
In [3]: s = pd.Series([10000]*120)
In [4]: %paste
def slow_func(k):
A = np.random.normal(size = k) # k = 10000
s = 0
for a in A:
if a > 0:
s += 1
else:
s -= 1
return s
## -- End pasted text --
In [5]: %time _ = s.apply(slow_func)
CPU times: user 345 ms, sys: 3.28 ms, total: 348 ms
Wall time: 347 ms
In [6]: import numba
In [7]: fast_func = numba.jit(slow_func)
In [8]: %time _ = s.apply(fast_func) # First time incurs compilation overhead
CPU times: user 179 ms, sys: 0 ns, total: 179 ms
Wall time: 175 ms
In [9]: %time _ = s.apply(fast_func) # Subsequent times are all gain
CPU times: user 68.8 ms, sys: 27 µs, total: 68.8 ms
Wall time: 68.7 ms
Avertissement: je travaille pour la société qui fabrique à la fois numba
et dask
et qui emploie de nombreux développeurs pandas
.
À partir de v dask.dataframe
., Appliquez la responsabilité des délégués à map_partitions
:
@insert_meta_param_description(pad=12)
def apply(self, func, convert_dtype=True, meta=no_default, args=(), **kwds):
""" Parallel version of pandas.Series.apply
...
"""
if meta is no_default:
msg = ("`meta` is not specified, inferred from partial data. "
"Please provide `meta` if the result is unexpected.\n"
" Before: .apply(func)\n"
" After: .apply(func, meta={'x': 'f8', 'y': 'f8'}) for dataframe result\n"
" or: .apply(func, meta=('x', 'f8')) for series result")
warnings.warn(msg)
meta = _emulate(M.apply, self._meta_nonempty, func,
convert_dtype=convert_dtype,
args=args, **kwds)
return map_partitions(M.apply, self, func,
convert_dtype, args, meta=meta, **kwds)