Skip to content

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:

regionproductrevenue
NorthWidget1200
NorthGadget800
SouthWidget1500
SouthGadget950
NorthWidget1400
SouthWidget1600

Tabela dinâmica (soma de revenue por region e product):

regionGadgetWidget
North8002600
South9503100

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âmetroDescriçãoPadrão
dataO DataFrame a ser resumidoObrigatório
valuesColuna(s) a agregarNone (todas as colunas numéricas)
indexColuna(s) para usar como rótulos de linhaNone
columnsColuna(s) para usar como cabeçalhos de colunaNone
aggfuncFunção(ões) de agregação: 'mean', 'sum', 'count', 'min', 'max', ou uma função/dict/list'mean'
fill_valueValor para substituir NaN no resultadoNone
marginsAdicionar totais de linha/coluna (subtotais)False
margins_nameNome para a linha/coluna de margens'All'
dropnaExcluir colunas cujas entradas são todas NaNTrue
observedMostrar apenas categorias observadas para colunas categóricasFalse
sortOrdenar o resultadoTrue

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      4330

Isso é 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    2600

Agora 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      2

O 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.0

Revenue é 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  12900

A 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.0

Cada 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     400

Outras 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.3

Isso 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:

Recursopivot_table()pivot()groupby()crosstab()
AgregaçãoSim (qualquer função)Não (gera erro com duplicatas)Sim (qualquer função)Sim (limitado)
Lida com duplicatasSim, via aggfuncNãoSimSim
Subtotais (margins)SimNãoNão (manual)Sim
Preenche valores ausentesSim (fill_value)NãoNãoSim (fill_value)
EntradaDataFrameDataFrameDataFrameSeries/arrays
SaídaDataFrameDataFrameDataFrame/SeriesDataFrame
Multi-level indexSimSimSimSim
Melhor paraResumir dados com agregaçãoReestruturar dados de chave-valor únicosAnálise flexível por gruposTabelas de frequência
Função padrãomeanN/AN/Acount

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))  # True

A 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.0

Agora 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 datasetTempo 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:

  1. Reduza os dados antes do pivot — filtre linhas e selecione apenas as colunas necessárias antes de chamar pivot_table().
  2. Use dtypes categóricos — converta colunas string para dtype category para agrupamento mais rápido.
  3. 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 optimized

Visualize 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, index e columns para definir o que resumir, como agrupar linhas e como agrupar colunas.
  • Use aggfunc para 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=True para adicionar subtotais — o equivalente a “Grand Total” no Excel.
  • Use fill_value para substituir combinações ausentes por um valor padrão (tipicamente 0).
  • Prefira pivot_table() em vez de groupby().unstack() por legibilidade, especialmente quando você precisa de margins ou fill values.
  • Prefira pivot() em vez de pivot_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.

📚