Применить функцию к сгруппированному фрейму данных в Dask: как вы определяете сгруппированный Dataframe как аргумент в функции?

У меня есть dask dataframe сгруппированный по индексу (first_name).

import pandas as pd
import numpy as np

from multiprocessing import cpu_count

from dask import dataframe as dd
from dask.multiprocessing import get 
from dask.distributed import Client


NCORES = cpu_count()
client = Client()

entities = pd.DataFrame({'first_name':['Jake','John','Danae','Beatriz', 'Jacke', 'Jon'],'last_name': ['Del Toro', 'Foster', 'Smith', 'Patterson', 'Toro', 'Froster'], 'ID':['X','U','X','Y', '12','13']})

df = dd.from_pandas(entities, npartitions=NCORES)
df = client.persist(df.set_index('first_name'))

(Очевидно, entities в реальной жизни - несколько тысяч строк)

Я хочу применить определенную пользователем функцию к каждому сгруппированному кадру данных. Я хочу сравнить каждую строку со всеми остальными строками в группе (что-то похожее на Pandas, сравнивает каждую строку со всеми строками в кадре данных и сохраняет результаты в списке для каждой строки).

Следующей является функция, которую я пытаюсь применить:

def contraster(x, DF):
    matches = DF.apply(lambda row: fuzz.partial_ratio(row['last_name'], x) >= 50, axis = 1) 
    return [i for i, x in enumerate(matches) if x]

Для кадра данных тестовых entities вы можете применить функцию как обычно:

entities.apply(lambda row: contraster(row['last_name'], entities), axis =1)

И ожидаемый результат:

Out[35]: 
0    [0, 4]
1    [1, 5]
2       [2]
3       [3]
4    [0, 4]
5    [1, 5]
dtype: object

Когда entities огромны, в решении используется использование dask. Обратите внимание, что DF в функции contraster должен быть групповым файловым кадром.

Я пытаюсь использовать следующее:

df.groupby('first_name').apply(func=contraster, args=????)

Но как мне указать сгруппированный фрейм данных (т.е. DF в contraster?)

Ответ 1

С небольшим количеством догадок, я думаю, что следующее, что вы после.

def mapper(d):

    def contraster(x, DF=d):
        matches = DF.apply(lambda row: fuzz.partial_ratio(row['last_name'], x) >= 50, axis = 1)
        return [d.ID.iloc[i] for i, x in enumerate(matches) if x]
    d['out'] = d.apply(lambda row: 
        contraster(row['last_name']), axis =1)
    return d

df.groupby('first_name').apply(mapper).compute()

Применительно к вашим данным, вы получаете:

   ID first_name  last_name   out
2   X      Danae      Smith   [X]
4  12      Jacke       Toro  [12]
0   X       Jake   Del Toro   [X]
1   U       John     Foster   [U]
5  13        Jon    Froster  [13]
3   Y    Beatriz  Patterson   [Y]

т.е. потому, что вы группируете по первому имени, каждая группа содержит только один элемент, который соответствует только самому себе.

Если, однако, у вас есть некоторые значения first_name, которые были в нескольких строках, вы получите совпадения:

entities = pd.DataFrame(
    {'first_name':['Jake','Jake', 'Jake', 'John'],
     'last_name': ['Del Toro', 'Toro', 'Smith'
                   'Froster'],
     'ID':['Z','U','X','Y']})

Выход:

  ID first_name last_name     out
0  Z       Jake  Del Toro  [Z, U]
1  U       Jake      Toro  [Z, U]
2  X       Jake     Smith     [X]
3  Y       John   Froster     [Y]

Если вам не нужны точные совпадения для first_name, то, возможно, вам нужно отсортировать/установить индекс по map_partitions имени и использовать map_partitions аналогичным образом. В этом случае вам нужно будет реформировать свой вопрос.

Ответ 2

Функция, которую вы предоставляете groupby-apply, должна принимать в качестве входного кадра или серии Pandas данные и в идеале возвращать один (или скалярное значение) в качестве вывода. Дополнительные параметры прекрасны, но они должны быть вторичными, а не первым аргументом. Это то же самое, что и в кадре данных Pandas и Dask.

def func(df, x=None):
    # do whatever you want here
    # the input to this function will have all the same first name
    return pd.DataFrame({'x': [x] * len(df),
                         'count': len(df),
                         'first_name': df.first_name})

Затем вы можете вызвать df.groupby как обычно

import pandas as pd
import dask.dataframe as dd

df = pd.DataFrame({'first_name':['Alice', 'Alice', 'Bob'],
                   'last_name': ['Adams', 'Jones', 'Smith']})

ddf = dd.from_pandas(df, npartitions=2)

ddf.groupby('first_name').apply(func, x=3).compute()

Это даст тот же результат в pandas или dask.dataframe

   count first_name  x
0      2      Alice  3
1      2      Alice  3
2      1        Bob  3