Tabela Dinâmica no Pandas: Resuma e Reestruture Dados como no Excel (Guia)
Updated on
Todo analista que já trabalhou com Excel conhece tabelas dinâmicas. Você arrasta um campo para a área de linhas, outro para a área de colunas, escolhe uma função de resumo, e uma planilha densa de transações brutas vira um resumo limpo mostrando totais por categoria, médias por região ou contagens por mês. Agora você precisa fazer a mesma coisa em Python, mas escrever chamadas groupby aninhadas com unstack parece desajeitado e difícil de ler.
A frustração real aparece quando seus dados têm múltiplos níveis de agrupamento, você precisa de subtotais, ou quer aplicar várias funções de agregação em colunas diferentes ao mesmo tempo. Encadear groupby, agg e unstack produz um código frágil que quebra sempre que os dados mudam de formato.
A função pandas pivot_table traz todo o poder das tabelas dinâmicas no estilo Excel para o Python com uma API limpa e declarativa. Uma única chamada lida com agrupamento, agregação, indexação em múltiplos níveis, subtotais e tratamento de valores ausentes. Este guia cobre cada parâmetro, passa por exemplos práticos e compara pivot_table com groupby, pivot e crosstab para você saber exatamente qual ferramenta usar.
O que pd.pivot_table() faz
pd.pivot_table() cria uma tabela de resumo no estilo planilha a partir de um DataFrame. Você especifica quais colunas usar como rótulos de linha (index), quais usar como cabeçalhos de coluna, quais valores agregar e qual função de agregação aplicar. O resultado é um novo DataFrame em que cada célula contém uma estatística de resumo.
Aqui vai um exemplo simples de antes e depois:
Dados brutos:
| region | product | revenue |
|---|---|---|
| North | Widget | 1200 |
| North | Gadget | 800 |
| South | Widget | 1500 |
| South | Gadget | 950 |
| North | Widget | 1400 |
| South | Widget | 1600 |
Tabela dinâmica (soma de revenue por region e product):
| region | Gadget | Widget |
|---|---|---|
| North | 800 | 2600 |
| South | 950 | 3100 |
Cada célula contém a soma de revenue para aquela combinação region-product.
Sintaxe e parâmetros de pd.pivot_table()
pd.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean',
fill_value=None, margins=False, dropna=True, margins_name='All',
observed=False, sort=True)Referência de parâmetros
| Parâmetro | Descrição | Padrão |
|---|---|---|
data | O DataFrame a ser resumido | Obrigatório |
values | Coluna(s) a agregar | None (todas as colunas numéricas) |
index | Coluna(s) para usar como rótulos de linha | None |
columns | Coluna(s) para usar como cabeçalhos de coluna | None |
aggfunc | Função(ões) de agregação: 'mean', 'sum', 'count', 'min', 'max', ou uma função/dict/list | 'mean' |
fill_value | Valor para substituir NaN no resultado | None |
margins | Adicionar totais de linha/coluna (subtotais) | False |
margins_name | Nome para a linha/coluna de margens | 'All' |
dropna | Excluir colunas cujas entradas são todas NaN | True |
observed | Mostrar apenas categorias observadas para colunas categóricas | False |
sort | Ordenar o resultado | True |
Dados de exemplo para todos os exemplos
Todo exemplo abaixo usa este dataset de vendas:
import pandas as pd
import numpy as np
sales = pd.DataFrame({
'date': pd.to_datetime(['2025-01-15', '2025-01-20', '2025-02-10', '2025-02-18',
'2025-01-12', '2025-02-22', '2025-01-25', '2025-02-14',
'2025-01-30', '2025-02-05', '2025-01-18', '2025-02-28']),
'region': ['North', 'North', 'North', 'North', 'South', 'South',
'South', 'South', 'East', 'East', 'East', 'East'],
'product': ['Widget', 'Gadget', 'Widget', 'Gadget',
'Widget', 'Gadget', 'Widget', 'Gadget',
'Widget', 'Gadget', 'Widget', 'Gadget'],
'salesperson': ['Alice', 'Alice', 'Bob', 'Bob',
'Charlie', 'Charlie', 'Diana', 'Diana',
'Eve', 'Eve', 'Frank', 'Frank'],
'revenue': [1200, 800, 1400, 850, 1500, 950, 1100, 780, 1300, 900, 1250, 870],
'units': [10, 8, 12, 9, 15, 10, 11, 8, 13, 9, 12, 9]
})
print(sales)Tabela dinâmica básica: soma de revenue por region
table = pd.pivot_table(sales, values='revenue', index='region', aggfunc='sum')
print(table)Saída:
revenue
region
East 4320
North 4250
South 4330Isso é equivalente a sales.groupby('region')['revenue'].sum(), mas retorna um DataFrame em vez de uma Series.
Adicionando cabeçalhos de coluna
Adicione o parâmetro columns para detalhar o resumo por uma segunda variável:
table = pd.pivot_table(sales, values='revenue', index='region',
columns='product', aggfunc='sum')
print(table)Saída:
product Gadget Widget
region
East 1770 2550
North 1650 2600
South 1730 2600Agora cada célula mostra a receita total para um par específico region-product.
Múltiplas funções de agregação
Passe uma lista de funções para aggfunc para calcular várias estatísticas de uma vez:
table = pd.pivot_table(sales, values='revenue', index='region',
columns='product', aggfunc=['sum', 'mean', 'count'])
print(table)Saída:
sum mean count
product Gadget Widget Gadget Widget Gadget Widget
region
East 1770 2550 885.0 1275.0 2 2
North 1650 2600 825.0 1300.0 2 2
South 1730 2600 865.0 1300.0 2 2O resultado tem um MultiIndex nas colunas, com a função de agregação no nível superior e o produto no segundo nível.
Funções de agregação diferentes por coluna
Use um dicionário em aggfunc para aplicar funções diferentes a colunas de valores diferentes:
table = pd.pivot_table(sales, values=['revenue', 'units'], index='region',
columns='product',
aggfunc={'revenue': 'sum', 'units': 'mean'})
print(table)Saída:
revenue units
product Gadget Widget Gadget Widget
region
East 1770 2550 9.0 12.5
North 1650 2600 8.5 11.0
South 1730 2600 9.0 13.0Revenue é somado enquanto units é calculado pela média — exatamente o que você faria em uma tabela dinâmica do Excel com diferentes funções de resumo por campo.
Adicionando subtotais com margins
O parâmetro margins adiciona uma linha e uma coluna mostrando os totais gerais:
table = pd.pivot_table(sales, values='revenue', index='region',
columns='product', aggfunc='sum', margins=True)
print(table)Saída:
product Gadget Widget All
region
East 1770 2550 4320
North 1650 2600 4250
South 1730 2600 4330
All 5150 7750 12900A linha All mostra o total de revenue por produto. A coluna All mostra o total de revenue por região. A célula no canto inferior direito é o total geral.
Você pode personalizar o rótulo com margins_name:
table = pd.pivot_table(sales, values='revenue', index='region',
columns='product', aggfunc='sum',
margins=True, margins_name='Total')
print(table)Tratando valores ausentes com fill_value
Quando certas combinações não existem nos seus dados, a tabela dinâmica contém NaN. Use fill_value para substituí-los:
# Remove one row to create a missing combination
sales_missing = sales.drop(index=0)
table = pd.pivot_table(sales_missing, values='revenue', index='region',
columns='product', aggfunc='sum', fill_value=0)
print(table)Isso substitui NaN por 0, o que fica mais limpo para exibição e evita problemas em cálculos posteriores.
Índice em múltiplos níveis (agrupamento de linhas)
Passe uma lista de colunas em index para criar rótulos de linha hierárquicos:
table = pd.pivot_table(sales, values='revenue',
index=['region', 'salesperson'],
columns='product', aggfunc='sum')
print(table)Saída:
product Gadget Widget
region salesperson
East Eve 900.0 1300.0
Frank 870.0 1250.0
North Alice 800.0 1200.0
Bob 850.0 1400.0
South Charlie 950.0 1500.0
Diana 780.0 1100.0Cada salesperson fica aninhado sob sua região, oferecendo uma visão de drill-down dos dados.
Colunas em múltiplos níveis
Da mesma forma, passe uma lista em columns para cabeçalhos de coluna hierárquicos:
sales['month'] = sales['date'].dt.month_name()
table = pd.pivot_table(sales, values='revenue', index='region',
columns=['product', 'month'], aggfunc='sum', fill_value=0)
print(table)Isso cria um cabeçalho de coluna em dois níveis com product no topo e month no segundo nível.
Usando funções de agregação personalizadas
Você pode passar qualquer callable para aggfunc, incluindo funções lambda e funções do NumPy:
# Range (max - min) of revenue by region
table = pd.pivot_table(sales, values='revenue', index='region',
columns='product', aggfunc=lambda x: x.max() - x.min())
print(table)Saída:
product Gadget Widget
region
East 30 50
North 50 200
South 170 400Outras agregações personalizadas úteis:
# Coefficient of variation
table = pd.pivot_table(sales, values='revenue', index='region',
aggfunc=lambda x: x.std() / x.mean() * 100)Exemplo do mundo real: análise de notas de alunos
students = pd.DataFrame({
'student': ['Alice', 'Alice', 'Alice', 'Bob', 'Bob', 'Bob',
'Charlie', 'Charlie', 'Charlie', 'Diana', 'Diana', 'Diana'],
'subject': ['Math', 'Science', 'English'] * 4,
'semester': ['Fall', 'Fall', 'Fall', 'Fall', 'Fall', 'Fall',
'Spring', 'Spring', 'Spring', 'Spring', 'Spring', 'Spring'],
'score': [92, 88, 95, 78, 85, 72, 90, 93, 88, 85, 79, 91]
})
# Average score by subject and semester
table = pd.pivot_table(students, values='score', index='subject',
columns='semester', aggfunc='mean', margins=True)
print(table.round(1))Saída:
semester Fall Spring All
subject
English 83.5 89.5 86.5
Math 85.0 87.5 86.2
Science 86.5 86.0 86.2
All 85.0 87.7 86.3Isso mostra imediatamente que as médias do semestre Spring são ligeiramente maiores no geral, com English apresentando a maior melhora.
Exemplo do mundo real: relatório mensal de vendas
# Create a monthly sales summary
sales['month'] = sales['date'].dt.strftime('%Y-%m')
report = pd.pivot_table(sales, values=['revenue', 'units'],
index='region', columns='month',
aggfunc={'revenue': 'sum', 'units': 'sum'},
margins=True, fill_value=0)
print(report)Isso produz o mesmo tipo de relatório mensal resumido que você criaria no Excel, com totais para cada região e mês.
pivot_table vs pivot vs groupby vs crosstab
Essas quatro funções se sobrepõem em capacidade, mas cada uma tem um caso de uso distinto:
| Recurso | pivot_table() | pivot() | groupby() | crosstab() |
|---|---|---|---|---|
| Agregação | Sim (qualquer função) | Não (gera erro com duplicatas) | Sim (qualquer função) | Sim (limitado) |
| Lida com duplicatas | Sim, via aggfunc | Não | Sim | Sim |
| Subtotais (margins) | Sim | Não | Não (manual) | Sim |
| Preenche valores ausentes | Sim (fill_value) | Não | Não | Sim (fill_value) |
| Entrada | DataFrame | DataFrame | DataFrame | Series/arrays |
| Saída | DataFrame | DataFrame | DataFrame/Series | DataFrame |
| Multi-level index | Sim | Sim | Sim | Sim |
| Melhor para | Resumir dados com agregação | Reestruturar dados de chave-valor únicos | Análise flexível por grupos | Tabelas de frequência |
| Função padrão | mean | N/A | N/A | count |
Quando usar cada uma
Use pivot_table() quando você precisa de resumos no estilo Excel com agregação, subtotais ou múltiplas funções de agregação. É a opção mais poderosa e legível para criar tabelas de resumo.
Use pivot() quando seus dados têm combinações únicas de valores de index e columns e você só quer reestruturar sem agregação. Ele é mais rápido que pivot_table() porque pula a etapa de agregação.
Use groupby() quando você precisa de cálculos por grupo, mas não precisa de um resultado em formato wide. groupby retorna saída em formato long por padrão. Você pode alcançar uma saída tipo pivot_table com groupby().unstack(), mas pivot_table é mais legível.
Use crosstab() quando você estiver calculando tabelas de frequência ou tabelas cruzadas de variáveis categóricas. crosstab() aceita Series ou arrays diretamente (não apenas DataFrames) e por padrão faz contagem.
Exemplo de equivalência
Estas três linhas produzem o mesmo resultado:
# pivot_table approach
result1 = pd.pivot_table(sales, values='revenue', index='region',
columns='product', aggfunc='sum')
# groupby + unstack approach
result2 = sales.groupby(['region', 'product'])['revenue'].sum().unstack()
# Both produce the same table
print(result1.equals(result2)) # TrueA versão com pivot_table é mais legível, especialmente quando você adiciona margens, valores de preenchimento ou múltiplas funções de agregação.
Achatando colunas MultiIndex
Depois de criar uma tabela dinâmica com múltiplas funções de agregação, você frequentemente obtém colunas MultiIndex que são difíceis de usar:
table = pd.pivot_table(sales, values='revenue', index='region',
columns='product', aggfunc=['sum', 'mean'])
# Flatten the column MultiIndex
table.columns = ['_'.join(col).strip() for col in table.columns.values]
print(table)Saída:
sum_Gadget sum_Widget mean_Gadget mean_Widget
region
East 1770 2550 885.0 1275.0
North 1650 2600 825.0 1300.0
South 1730 2600 865.0 1300.0Agora as colunas são strings “planas”, mais fáceis de referenciar.
Ordenando e filtrando tabelas dinâmicas
Tabelas dinâmicas são DataFrames comuns, então você pode ordenar e filtrar:
table = pd.pivot_table(sales, values='revenue', index='region',
columns='product', aggfunc='sum', margins=True)
# Sort by total revenue (All column), descending
sorted_table = table.sort_values('All', ascending=False)
print(sorted_table)
# Filter to show only regions with Widget revenue > 2500
filtered = table[table['Widget'] > 2500]
print(filtered)Exportando tabelas dinâmicas
Salve sua tabela dinâmica em Excel (onde stakeholders esperam) ou CSV:
table = pd.pivot_table(sales, values='revenue', index='region',
columns='product', aggfunc='sum', margins=True)
# Export to Excel
table.to_excel('sales_pivot.xlsx', sheet_name='Revenue Summary')
# Export to CSV
table.to_csv('sales_pivot.csv')Dicas de performance
pivot_table() chama groupby internamente, então a performance é similar. Para datasets grandes:
| Tamanho do dataset | Tempo esperado |
|---|---|
| 100K linhas, 2 cols de grupo | ~5 ms |
| 1M linhas, 2 cols de grupo | ~50 ms |
| 10M linhas, 3 cols de grupo | ~500 ms |
Estratégias de otimização:
- Reduza os dados antes do pivot — filtre linhas e selecione apenas as colunas necessárias antes de chamar
pivot_table(). - Use dtypes categóricos — converta colunas string para dtype
categorypara agrupamento mais rápido. - Evite aggfuncs com lambda — nomes internos em string (
'sum','mean') usam código C otimizado. Funções lambda caem em loops Python mais lentos.
# Faster: use categorical dtypes
sales['region'] = sales['region'].astype('category')
sales['product'] = sales['product'].astype('category')
# Faster: use string name instead of lambda
table = pd.pivot_table(sales, values='revenue', index='region',
columns='product', aggfunc='sum') # 'sum' is optimizedVisualize tabelas dinâmicas com PyGWalker
Embora pd.pivot_table() seja ótimo para resumos numéricos, às vezes você precisa visualizar os padrões nos seus dados de forma interativa. PyGWalker (opens in a new tab) é uma biblioteca Python open-source que permite criar tabelas dinâmicas, gráficos de barras, heatmaps e mais por meio de uma interface visual de arrastar e soltar — sem precisar escrever código após a configuração inicial.
import pandas as pd
import pygwalker as pyg
sales = pd.DataFrame({
'region': ['North', 'North', 'South', 'South', 'East', 'East'] * 2,
'product': ['Widget', 'Gadget'] * 6,
'revenue': [1200, 800, 1500, 950, 1300, 900, 1400, 850, 1100, 780, 1250, 870],
'units': [10, 8, 15, 10, 13, 9, 12, 9, 11, 8, 12, 9]
})
# Launch interactive pivot table and visualization
walker = pyg.walk(sales)O PyGWalker oferece uma interface no estilo Tableau em que você pode arrastar region para linhas, product para colunas e revenue para valores para criar uma tabela dinâmica visualmente. Você pode alternar entre visualização em tabela e em gráfico instantaneamente, testar diferentes funções de agregação e exportar o resultado — tudo sem escrever código adicional.
Experimente o PyGWalker no Google Colab (opens in a new tab), Kaggle (opens in a new tab), ou instale com
pip install pygwalker.
FAQ
Qual é a diferença entre pivot e pivot_table no pandas?
pivot() reestrutura dados sem agregação — ele exige combinações únicas de valores de index e columns e gera erro se existirem duplicatas. pivot_table() lida com duplicatas agregando-as com uma função como sum ou mean. Use pivot() para reestruturações simples e pivot_table() quando você precisar de agregação ou subtotais.
Como adiciono totais (margins) a uma tabela dinâmica do pandas?
Defina margins=True na chamada pivot_table(): pd.pivot_table(df, values='revenue', index='region', columns='product', aggfunc='sum', margins=True). Isso adiciona uma linha e uma coluna All com subtotais. Personalize o rótulo com margins_name='Total'.
Posso usar múltiplas funções de agregação em uma tabela dinâmica?
Sim. Passe uma lista para aggfunc: aggfunc=['sum', 'mean', 'count']. Isso cria um cabeçalho de coluna MultiIndex com um nível para a função e um nível para as colunas de valor. Você também pode passar um dicionário para aplicar funções diferentes a colunas diferentes: aggfunc={'revenue': 'sum', 'units': 'mean'}.
Em que pivot_table é diferente de groupby no pandas?
Ambos agregam dados, mas pivot_table() produz um resultado em formato wide (com cabeçalhos de coluna vindos de uma das variáveis de agrupamento), enquanto groupby() produz um resultado em formato long por padrão. pivot_table() também suporta margins (subtotais) e fill values nativamente. Internamente, pivot_table() usa groupby().
Como lidar com valores NaN em uma tabela dinâmica?
Use o parâmetro fill_value para substituir NaN por um valor específico: pd.pivot_table(df, ..., fill_value=0). O parâmetro dropna=True (padrão) exclui colunas em que todas as entradas são NaN.
Posso criar uma tabela dinâmica com valores percentuais?
Sim. Crie a tabela dinâmica com contagens ou somas primeiro e depois divida pelo total. Você também pode usar normalize em pd.crosstab() para tabelas cruzadas baseadas em percentuais. Para tabelas dinâmicas, calcule percentuais manualmente: table = table.div(table.sum(axis=1), axis=0) * 100.
Conclusão
A função pivot_table() do pandas é a ferramenta mais versátil para criar tabelas de resumo em Python. Aqui estão os pontos-chave para lembrar:
- Use
values,indexecolumnspara definir o que resumir, como agrupar linhas e como agrupar colunas. - Use
aggfuncpara especificar a função de agregação. Passe uma lista para múltiplas funções ou um dicionário para funções por coluna. - Use
margins=Truepara adicionar subtotais — o equivalente a “Grand Total” no Excel. - Use
fill_valuepara substituir combinações ausentes por um valor padrão (tipicamente 0). - Prefira
pivot_table()em vez degroupby().unstack()por legibilidade, especialmente quando você precisa de margins ou fill values. - Prefira
pivot()em vez depivot_table()quando seus dados têm combinações de chaves únicas e você não precisa de agregação. - Prefira
crosstab()para tabelas de frequência simples de variáveis categóricas.
Para exploração interativa das suas tabelas dinâmicas, o PyGWalker (opens in a new tab) oferece uma interface visual de arrastar e soltar que espelha a experiência de tabela dinâmica do Excel dentro do Jupyter Notebook.