#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