J'ai lu ce qui suit SO thead et maintenant j'essaie de le comprendre. Voici mon exemple:
import dask.dataframe as dd
import pandas as pd
from dask.multiprocessing import get
import random
df = pd.DataFrame({'col_1':random.sample(range(10000), 10000), 'col_2': random.sample(range(10000), 10000) })
def test_f(col_1, col_2):
return col_1*col_2
ddf = dd.from_pandas(df, npartitions=8)
ddf['result'] = ddf.map_partitions(test_f, columns=['col_1', 'col_2']).compute(get=get)
Il génère l'erreur suivante ci-dessous. Qu'est-ce que je fais mal? Je ne sais pas non plus comment passer des paramètres supplémentaires pour fonctionner dans map_partitions
?
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\conda\conda\envs\tensorflow\lib\site-packages\dask\dataframe\utils.py in raise_on_meta_error(funcname)
136 try:
--> 137 yield
138 except Exception as e:
~\AppData\Local\conda\conda\envs\tensorflow\lib\site-packages\dask\dataframe\core.py in _emulate(func, *args, **kwargs)
3130 with raise_on_meta_error(funcname(func)):
-> 3131 return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))
3132
TypeError: test_f() got an unexpected keyword argument 'columns'
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
<ipython-input-9-913789c7326c> in <module>()
----> 1 ddf['result'] = ddf.map_partitions(test_f, columns=['col_1', 'col_2']).compute(get=get)
~\AppData\Local\conda\conda\envs\tensorflow\lib\site-packages\dask\dataframe\core.py in map_partitions(self, func, *args, **kwargs)
469 >>> ddf.map_partitions(func).clear_divisions() # doctest: +SKIP
470 """
--> 471 return map_partitions(func, self, *args, **kwargs)
472
473 @insert_meta_param_description(pad=12)
~\AppData\Local\conda\conda\envs\tensorflow\lib\site-packages\dask\dataframe\core.py in map_partitions(func, *args, **kwargs)
3163
3164 if meta is no_default:
-> 3165 meta = _emulate(func, *args, **kwargs)
3166
3167 if all(isinstance(arg, Scalar) for arg in args):
~\AppData\Local\conda\conda\envs\tensorflow\lib\site-packages\dask\dataframe\core.py in _emulate(func, *args, **kwargs)
3129 """
3130 with raise_on_meta_error(funcname(func)):
-> 3131 return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))
3132
3133
~\AppData\Local\conda\conda\envs\tensorflow\lib\contextlib.py in __exit__(self, type, value, traceback)
75 value = type()
76 try:
---> 77 self.gen.throw(type, value, traceback)
78 except StopIteration as exc:
79 # Suppress StopIteration *unless* it's the same exception that
~\AppData\Local\conda\conda\envs\tensorflow\lib\site-packages\dask\dataframe\utils.py in raise_on_meta_error(funcname)
148 ).format(" in `{0}`".format(funcname) if funcname else "",
149 repr(e), tb)
--> 150 raise ValueError(msg)
151
152
ValueError: Metadata inference failed in `test_f`.
Original error is below:
------------------------
TypeError("test_f() got an unexpected keyword argument 'columns'",)
Traceback:
---------
File "C:\Users\some_user\AppData\Local\conda\conda\envs\tensorflow\lib\site-packages\dask\dataframe\utils.py", line 137, in raise_on_meta_error
yield
File "C:\Users\some_user\AppData\Local\conda\conda\envs\tensorflow\lib\site-packages\dask\dataframe\core.py", line 3131, in _emulate
return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))
Il y a un exemple dans map_partitions
Docs pour réaliser exactement ce que vous essayez de faire:
ddf.map_partitions(lambda df: df.assign(z=df.x * df.y))
Lorsque vous appelez map_partitions
(Tout comme lorsque vous appelez .apply()
sur pandas.DataFrame
), La fonction que vous essayez de map
(ou apply
) recevra dataframe comme premier argument.
Dans le cas de dask.dataframe.map_partitions
, Ce premier argument sera une partition et dans le cas de pandas.DataFrame.apply
- une trame de données entière.
Ce qui signifie que votre fonction doit accepter le dataframe (partition) comme premier argument et dans votre cas pourrait ressembler à ceci:
def test_f(df, col_1, col_2):
return df.assign(result=df[col_1] * df[col_2])
Notez que l'attribution d'une nouvelle colonne dans ce cas se produit (c'est-à-dire qu'elle est planifiée) AVANT d'appeler .compute()
.
Dans votre exemple, vous affectez la colonne APRÈS que vous appelez .compute()
, ce qui va à l'encontre du but de l'utilisation de dask. C'est à dire. après avoir appelé .compute()
les résultats de cette opération sont chargés en mémoire s'il y a suffisamment d'espace pour ces résultats (sinon vous venez get MemoryError
).
Donc, pour que votre exemple fonctionne, vous pouvez:
1) Utilisez la fonction (avec les noms de colonnes comme arguments):
def test_f(df, col_1, col_2):
return df.assign(result=df[col_1] * df[col_2])
ddf_out = ddf.map_partitions(test_f, 'col_1', 'col_2')
# Here is good place to do something with BIG ddf_out dataframe before calling .compute()
result = ddf_out.compute(get=get) # Will load the whole dataframe into memory
2) Utilisez lambda
(avec les noms de colonne codés en dur dans la fonction):
ddf_out = ddf.map_partitions(lambda df: df.assign(result=df.col_1 * df.col_2))
# Here is good place to do something with BIG ddf_out dataframe before calling .compute()
result = ddf_out.compute(get=get) # Will load the whole dataframe into memory
Mise à jour:
Pour appliquer la fonction ligne par ligne, voici une citation de l'article que vous avez lié:
map
/apply
Vous pouvez mapper une fonction par ligne sur une série avec
map
df.mycolumn.map(func)
Vous pouvez mapper une fonction par ligne sur une trame de données avec
apply
df.apply(func, axis=1)
C'est à dire. pour l'exemple de fonction dans votre question, cela pourrait ressembler à ceci:
def test_f(dds, col_1, col_2):
return dds[col_1] * dds[col_2]
Puisque vous l'appliquerez ligne par ligne, le premier argument de la fonction sera une série (c'est-à-dire que chaque ligne d'une trame de données est une série).
Pour appliquer cette fonction, vous pouvez l'appeler comme ceci:
dds_out = ddf.apply(
test_f,
args=('col_1', 'col_2'),
axis=1,
meta=('result', int)
).compute(get=get)
Cela renverra une série nommée 'result'
.
Je suppose que vous pourriez également appeler .apply
Sur chaque partition avec une fonction, mais cela ne semble pas être plus efficace que d'appeler .apply
Directement sur la trame de données. Mais peut-être que vos tests prouveront le contraire.
Votre test_f
prend deux arguments: col_1
et col_2
. Vous passez un seul argument, ddf
.
Essayez quelque chose comme
In [5]: dd.map_partitions(test_f, ddf['col_1'], ddf['col_2'])
Out[5]:
Dask Series Structure:
npartitions=8
0 int64
1250 ...
...
8750 ...
9999 ...
dtype: int64
Dask Name: test_f, 32 tasks