Как изменить входные параметры функции pandas groupby.agg?

#python #pandas #pandas-groupby

#python #pandas #pandas-groupby

Вопрос:

У меня возникли проблемы с использованием метода groupby_object.agg() с функциями, в которых я хочу изменить входные параметры. Есть ли доступный ресурс с именами функций, которые принимает agg(), и как передать им параметры?

Смотрите пример ниже:

 import pandas as pd
import numpy as np

df = pd.DataFrame({'numbers': [1, 2, 3, 2, 1, 3], 
               'colors': ['red', 'white', 'blue', 'red', 'white', np.nan], 
               'weight': [10, 10, 20, 5, 10, 20]})

df['colors'].nunique() # Returns 3 as NaN is not counted
df['colors'].nunique(dropna=False) # Returns 4 as NaN is counted
 

Когда я затем groupby «окрашиваю», как я могу передать dropna=False параметр с помощью функции?

 df.groupby('numbers').agg({'colors': 'nunique', 'weight': 'sum'})
 

Ответ №1:

Хотя pandas имеет хороший синтаксис для агрегирования с помощью dicts и NamedAggs, это может привести к огромным затратам на эффективность. Причина в том, что вместо использования встроенных методов groupby, которые оптимизированы и / или реализованы в cython, any .agg(lambda x: ...) или .apply(lambda x: ...) будет идти гораздо более медленным путем.

Это означает, что вы должны придерживаться встроенных модулей, на которые вы можете ссылаться напрямую или по псевдониму. Только в качестве последнего средства вы должны попытаться использовать lambda :

В данном конкретном случае используйте

 df.groupby('numbers')[['colors']].agg('nunique', dropna=False)
 

Избегайте

 df.groupby('numbers').agg({'colors': lambda x: x.nunique(dropna=False)})
 

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

 import perfplot
import pandas as pd
import numpy as np

def built_in(df):
    return df.groupby('numbers')[['colors']].agg('nunique', dropna=False)

def apply(df):
    return df.groupby('numbers').agg({'colors': lambda x: x.nunique(dropna=False)})

perfplot.show(
    setup=lambda n: pd.DataFrame({'numbers': np.random.randint(0, n//10 1, n),
                                  'colors': np.random.choice([np.NaN]   [*range(100)])}),
    kernels=[
        lambda df: built_in(df),
        lambda df: apply(df)],
    
    labels=['Built-In', 'Apply'],
    n_range=[2 ** k for k in range(1, 20)],
    equality_check=np.allclose,  
    xlabel='~N Groups'
)
 

введите описание изображения здесь


Но вы хотите выполнить несколько агрегаций и использовать разные столбцы

.groupby() Часть groupby на самом деле не делает так много; это просто гарантирует правильность сопоставления. Таким образом, хотя это и неинтуитивно, все же гораздо быстрее агрегировать со встроенным по отдельности и в конечном итоге объединить результаты, чем аггитировать с более простым dict с использованием лямбда.

Вот пример, также желающий sum столбец weight , и мы видим, что разделение все еще намного быстрее, несмотря на необходимость объединения вручную

 def built_in(df):
    return pd.concat([df.groupby('numbers')[['colors']].agg('nunique', dropna=False),
                      df.groupby('numbers')[['weight']].sum()], axis=1)

def apply(df):
    return df.groupby('numbers').agg({'colors': lambda x: x.nunique(dropna=False), 
                                      'weight': 'sum'})

perfplot.show(
    setup=lambda n: pd.DataFrame({'numbers': np.random.randint(0, n//10 1, n),
                                  'colors': np.random.choice([np.NaN]   [*range(100)]),
                                  'weight': np.random.normal(0,1,n)}),
    kernels=[
        lambda df: built_in(df),
        lambda df: apply(df)],
    
    labels=['Built-In', 'Apply'],
    n_range=[2 ** k for k in range(1, 20)],
    equality_check=np.allclose,  
    xlabel='~N Groups'
)
 

введите описание изображения здесь

Комментарии:

1. Это очень хорошее объяснение, и спасибо вам за него. Вопрос, основанный на вашем ответе, df.groupby('numbers')[['colors']].agg('nunique', dropna=False) это означает, что единственным агрегированным столбцом будет colors , и я могу изменить df.groupby('numbers')[['colors','weight']].agg('nunique', dropna=False) , чтобы получить уникальный список обоих столбцов. Однако, что, если кто-то захочет получить столбец max() on weight и столбец nunique цветов?

2. @sophods ах да, извините, я просто добавил это. Вы можете посмотреть на мой второй пример, где я разделил один groupby, а затем concat в конце. Если эффективность не является большой проблемой (т.Е., Возможно, у вас небольшой df, а не один с 20 миллионами групп), тогда я бы сказал, что удобочитаемость может быть более важной. Но во многих случаях с большими данными эти замедления могут составлять разницу между вычислением в 1 минуту или 1 час, поэтому часто лучше выбрать немного более сложный путь кодирования.

Ответ №2:

Вы можете использовать эту ссылку https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html

и перейдите в раздел «Агрегация», где вы можете найти различные методы, доступные для сгруппированных данных.

В вашем случае вы можете передать dropna=False , если используете lambda функцию:

 df.groupby('numbers').agg({'colors': lambda x: x.nunique(dropna=False), 'weight': 'sum'})

Out[324]: 
         colors  weight
numbers                
1             2      20
2             2      15
3             2      40