web-dev-qa-db-fra.com

exemple simple de map_partitions dask

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))
6
user1700890

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.

13
Primer

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
5
TomAugspurger