apply() com GroupBy
Passe uma função que opere sobre várias linhas a groupby().apply() para calcular resumos complexos por grupo que agg() não consegue expressar.
apply() com GroupBy é uma aula grátis de Pandas & NumPy Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Pandas & NumPy Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Pandas & NumPy Academy inclui 4 aulas no total.
Por que GroupBy precisa de apply()
O método integrado agg() lida com agregações simples, como sum, mean e count — uma saída escalar por grupo. No entanto, alguns cálculos no nível do grupo exigem analisar todo o sub-DataFrame do grupo, e não apenas uma única coluna. groupby().apply(func) passa o DataFrame completo do grupo para sua função e reúne os resultados, permitindo criar resumos complexos que agg() não consegue expressar.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'region': ['North', 'North', 'South', 'South', 'North'],
'product': ['A', 'B', 'A', 'A', 'C'],
'revenue': [100, 250, 180, 90, 300]
})
print(df)Retornando um escalar por grupo
Quando a função passada para groupby().apply() retorna um escalar, o resultado é uma Series indexada pelas chaves dos grupos — idêntica à produzida por agg(). Essa forma é útil quando o escalar exige uma lógica que envolve várias colunas, como calcular a proporção da receita do produto líder em relação à receita total do grupo, algo que não pode ser expresso em uma única especificação de coluna de agg().
def top_product_share(group):
top = group['revenue'].max()
total = group['revenue'].sum()
return top / total
share = df.groupby('region').apply(top_product_share)
print(share)Retornando uma Series por grupo
Quando a função retorna uma pd.Series, o resultado tem um MultiIndex: o nível externo é a chave do grupo e o nível interno é o índice da Series. Isso é útil para calcular várias estatísticas por grupo em uma única chamada de apply, produzindo uma tabela de resumo na qual cada grupo tem várias linhas de métricas.
def group_stats(group):
return pd.Series({
'total': group['revenue'].sum(),
'top_product': group.loc[group['revenue'].idxmax(), 'product'],
'n_products': group['product'].nunique()
})
result = df.groupby('region').apply(group_stats)
print(result)Retornando um DataFrame por grupo
Quando a função retorna um pd.DataFrame, groupby().apply() concatena verticalmente todos os sub-DataFrames. Esta é a forma mais poderosa: ela permite filtrar ou transformar as linhas dentro de cada grupo e retornar um subconjunto modificado. Por exemplo, manter apenas os 2 principais produtos por receita dentro de cada região.
def top2_per_region(group):
return group.nlargest(2, 'revenue')
top2 = df.groupby('region').apply(top2_per_region)
print(top2.reset_index(drop=True))Calculando pontuações Z dentro dos grupos
Um uso comum de groupby().apply() é calcular a padronização dentro dos grupos. Em vez de normalizar a receita em relação a todos os pedidos, misturando as regiões, calcule a pontuação Z da receita dentro de cada região. Uma pontuação Z de 2 no Norte significa "2 desvios-padrão acima da média do Norte" — uma referência mais significativa do que 2 desvios-padrão acima da média global.
def within_group_zscore(group):
mean = group['revenue'].mean()
std = group['revenue'].std()
group = group.copy()
group['revenue_z'] = (group['revenue'] - mean) / std
return group
df_z = df.groupby('region').apply(within_group_zscore).reset_index(drop=True)
print(df_z)Agregação personalizada: média winsorizada
A média winsorizada limita os valores extremos antes de calcular a média, tornando-se mais robusta do que a média simples em distribuições assimétricas. Essa agregação personalizada não pode ser expressa com funções padrão de agg(), mas é simples com groupby().apply(): limite os valores no 5º e no 95º percentis dentro de cada grupo e, em seguida, calcule a média dos valores limitados.
def winsorised_mean(group, lower_pct=0.05, upper_pct=0.95):
col = group['revenue']
lo = col.quantile(lower_pct)
hi = col.quantile(upper_pct)
clipped = col.clip(lo, hi)
return clipped.mean()
wins_mean = df.groupby('region').apply(winsorised_mean)
print('Winsorised mean by region:')
print(wins_mean)Janela móvel personalizada por grupo
As janelas móveis aplicadas ao DataFrame completo ignoram os limites dos grupos. Se você calcular uma média móvel de 3 linhas em uma série temporal que intercala dois produtos, a janela atravessará incorretamente os limites dos produtos. Aplique a janela móvel dentro de um groupby().apply() para garantir que cada cálculo móvel permaneça dentro do próprio grupo — por exemplo, uma média móvel da receita de 3 meses por região.
def group_rolling_mean(group, window=3):
group = group.sort_values('order_date').copy()
group['rolling_revenue'] = group['revenue'].rolling(window, min_periods=1).mean()
return group
# df_ts has order_date column
# df_rolled = df_ts.groupby('region').apply(group_rolling_mean).reset_index(drop=True)
print('Rolling window per group applied inside apply()')Parâmetro include_groups (Pandas 2.2+)
No Pandas 2.2 e em versões posteriores, groupby().apply() gera um FutureWarning se as chaves do agrupamento aparecerem no DataFrame recebido pela função. Passe include_groups=False para excluir as colunas usadas no agrupamento do sub-DataFrame passado à função. Isso evita tanto o aviso quanto operações acidentais nas colunas de chave dentro do corpo da função.
def revenue_only(group):
# group does not include 'region' column when include_groups=False
return group['revenue'].sum()
# result = df.groupby('region').apply(revenue_only, include_groups=False)
print('include_groups=False avoids FutureWarning in Pandas 2.2+')Combinando resultados de apply() com reset_index
Quando groupby().apply() retorna um DataFrame por grupo, o resultado tem um MultiIndex que inclui a chave do grupo como nível externo. Use reset_index(drop=True) para transformar o índice novamente em um intervalo inteiro simples. Como alternativa, use reset_index(level=0) para promover a chave do grupo a uma coluna, deixando-a explícita na saída.
result = df.groupby('region').apply(top2_per_region)
print('With MultiIndex:')
print(result.head())
print('\nAfter reset_index:')
print(result.reset_index(drop=True))Quando preferir transform() a apply()
groupby().apply() serve para cálculos complexos no nível dos grupos que retornam uma forma diferente da entrada. groupby().transform() serve para cálculos que adicionam uma nova coluna alinhada ao índice original — como transmitir a média do grupo de volta para cada linha para fins de normalização. Use transform quando precisar que o resultado tenha a mesma forma do DataFrame original; use apply quando a forma do resultado for diferente.
# transform: adds group mean back to each row
df['group_mean_revenue'] = df.groupby('region')['revenue'].transform('mean')
# apply: computes one scalar per group
group_totals = df.groupby('region')['revenue'].apply(sum)
print(df[['region', 'revenue', 'group_mean_revenue']])Dica de desempenho: evite apply() para agregações simples
groupby().apply() é significativamente mais lento do que groupby().agg() para operações simples, porque apply() cria um objeto Python completo para cada grupo. Para somas, médias e contagens, use sempre agg(). Reserve apply() para casos que realmente exigem o DataFrame completo do grupo — lógica condicional que envolve várias colunas, estatísticas personalizadas ou filtragem dentro dos grupos.
# SLOW: apply for simple sum
slow = df.groupby('region').apply(lambda g: g['revenue'].sum())
# FAST: native agg
fast = df.groupby('region')['revenue'].sum()
print('Both produce the same result:')
print(fast.equals(slow))Verificação rápida
Teste sua compreensão dos conceitos de Análise de Dados desta lição.
Resumo da lição
Nesta lição, você aprendeu: a retornar escalares, Series e DataFrames de groupby().apply(), a calcular pontuações Z dentro dos grupos e estatísticas robustas personalizadas e a escolher entre apply(), agg() e transform() para operações no nível dos grupos. A seguir, exploraremos map() e applymap() para operações elemento a elemento em Series e DataFrames.
Perguntas Frequentes
A aula “apply() com GroupBy” é grátis?
Sim — o texto completo de “apply() com GroupBy” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Pandas & NumPy Academy, atualize para CoddyKit PRO. O curso de Pandas & NumPy Academy inclui 4 aulas no total.
O que vou aprender em “apply() com GroupBy”?
Passe uma função que opere sobre várias linhas a groupby().apply() para calcular resumos complexos por grupo que agg() não consegue expressar. Você pratica Pandas & NumPy Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Pandas & NumPy Academy?
Nenhuma experiência prévia é necessária. Pandas & NumPy Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.
Quanto tempo leva a aula “apply() com GroupBy”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Pandas & NumPy Academy?
Sim. Cada aula de Pandas & NumPy Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- apply() em colunas e linhas
- apply() com GroupBy
- map() e applymap() para operações elemento a elemento
- Encadeamento de métodos com pipe()