Построение режима и соответствующих функций подсчета с использованием пользовательских функций агрегирования для GroupBy в Dask

Таким образом, dask был обновлен для поддержки пользовательских функций агрегирования для групповой работы. (Спасибо команде разработчиков и @chmp за работу над этим!). В настоящее время я пытаюсь построить функцию режима и соответствующую функцию подсчета. По сути, я предполагаю, что режим возвращает список для каждой группы наиболее распространенных значений для определенного столбца (т. Е. [4, 1, 2]). Кроме того, существует соответствующая функция подсчета, которая возвращает количество экземпляров этих значений, т.е. 3.

Сейчас я пытаюсь реализовать это в коде. В соответствии с файлом groupby.py параметры для пользовательских агрегатов следующие:

Parameters
    ----------
    name : str
        the name of the aggregation. It should be unique, since intermediate
        result will be identified by this name.
    chunk : callable
        a function that will be called with the grouped column of each
        partition. It can either return a single series or a tuple of series.
        The index has to be equal to the groups.
    agg : callable
        a function that will be called to aggregate the results of each chunk.
        Again the argument(s) will be grouped series. If ``chunk`` returned a
        tuple, ``agg`` will be called with all of them as individual positional
        arguments.
    finalize : callable
        an optional finalizer that will be called with the results from the
        aggregation.

Вот предоставленный код для среднего значения:

    custom_mean = dd.Aggregation(
        'custom_mean',
        lambda s: (s.count(), s.sum()),
        lambda count, sum: (count.sum(), sum.sum()),
        lambda count, sum: sum / count,
    )
    df.groupby('g').agg(custom_mean)

Я пытаюсь придумать лучший способ сделать это. В настоящее время у меня есть следующие функции:

def custom_count(x):
    count = Counter(x)
    freq_list = count.values()
    max_cnt = max(freq_list)
    total = freq_list.count(max_cnt)
    return count.most_common(total)

custom_mode = dd.Aggregation(
    'custom_mode',
    lambda s: custom_count(s),
    lambda s1: s1.extend(),
    lambda s2: ......
)

Однако я застреваю в понимании, как именно должна работать agg-часть. Любая помощь по этой проблеме будет принята с благодарностью.

Спасибо!

1 ответ

По общему признанию, документы в настоящее время немного легки в деталях. Спасибо, что обратили мое внимание на эту проблему. Пожалуйста, позвольте мне сейчас, если этот ответ поможет, и я внесу обновленную версию документации в dask.

На ваш вопрос: для одного возвращаемого значения различные этапы агрегирования эквивалентны:

res = chunk(df.groupby('g')['col'])
res = agg(res.groupby(level=[0]))
res = finalize(res)

В этих терминах функция mode может быть реализована следующим образом:

def chunk(s):
    # for the comments, assume only a single grouping column, the 
    # implementation can handle multiple group columns.
    #
    # s is a grouped series. value_counts creates a multi-series like 
    # (group, value): count
    return s.value_counts()


def agg(s):
    # s is a grouped multi-index series. In .apply the full sub-df will passed
    # multi-index and all. Group on the value level and sum the counts. The
    # result of the lambda function is a series. Therefore, the result of the 
    # apply is a multi-index series like (group, value): count
    return s.apply(lambda s: s.groupby(level=-1).sum())

    # faster version using pandas internals
    s = s._selected_obj
    return s.groupby(level=list(range(s.index.nlevels))).sum()


def finalize(s):
    # s is a multi-index series of the form (group, value): count. First
    # manually group on the group part of the index. The lambda will receive a
    # sub-series with multi index. Next, drop the group part from the index.
    # Finally, determine the index with the maximum value, i.e., the mode.
    level = list(range(s.index.nlevels - 1))
    return (
        s.groupby(level=level)
        .apply(lambda s: s.reset_index(level=level, drop=True).argmax())
    )

mode = dd.Aggregation('mode', chunk, agg, finalize)

Обратите внимание, что эта реализация не соответствует фрейму данных .mode функция в случае галстуков. Эта версия будет возвращать одно из значений в случае связи вместо всех значений.

Режим агрегации теперь можно использовать как в

import pandas as pd
import dask.dataframe as dd

df = pd.DataFrame({
    'col': [0, 1, 1, 2, 3] * 10,
    'g0': [0, 0, 0, 1, 1] * 10,
    'g1': [0, 0, 0, 1, 1] * 10,
})
ddf = dd.from_pandas(df, npartitions=10)

res = ddf.groupby(['g0', 'g1']).agg({'col': mode}).compute()
print(res)
Другие вопросы по тегам