0Pricing
Pandas & NumPy Academy · Aula

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

  1. apply() em colunas e linhas
  2. apply() com GroupBy
  3. map() e applymap() para operações elemento a elemento
  4. Encadeamento de métodos com pipe()
← Voltar para Pandas & NumPy Academy