Introdução ao Pandas: Estrutura e Conceitos Fundamentais
O Pandas é a biblioteca mais utilizada em Python para manipulação e análise de dados. Ele fornece estruturas de dados poderosas e ferramentas que tornam o trabalho com dados tabulares (como planilhas Excel ou bases de dados) intuitivo e eficiente. Quando você trabalha com dados no mundo real, raramente encontra informações limpas e organizadas — é aqui que o Pandas se torna indispensável.
A filosofia do Pandas é trazer a experiência familiar de trabalhar com DataFrames (similar aos data frames do R) para o ecossistema Python. Você carrega dados de diversas fontes, executa transformações complexas com poucas linhas de código e extrai insights rapidamente. Neste artigo, vamos percorrer desde a criação básica de estruturas até técnicas avançadas de análise exploratória.
DataFrames: A Coluna Vertebral do Pandas
O que é um DataFrame?
Um DataFrame é uma estrutura de dados bidimensional com linhas e colunas, similar a uma tabela em um banco de dados ou uma planilha. Cada coluna pode ter um tipo de dado diferente (números, strings, datas, etc.), e há um índice que identifica uniquely cada linha. Diferentemente de uma lista de listas, um DataFrame oferece operações vetorizadas que são muito mais rápidas.
import pandas as pd
import numpy as np
# Criando um DataFrame a partir de um dicionário
dados = {
'nome': ['Alice', 'Bruno', 'Carlos', 'Diana'],
'idade': [28, 34, 29, 31],
'salario': [5000, 7500, 6200, 6800],
'departamento': ['TI', 'RH', 'TI', 'Financeiro']
}
df = pd.DataFrame(dados)
print(df)
Output:
nome idade salario departamento
0 Alice 28 5000 TI
1 Bruno 34 7500 RH
2 Carlos 29 6200 TI
3 Diana 31 6800 Financeiro
Inspecionando DataFrames
Antes de iniciar qualquer análise, você precisa entender a estrutura dos seus dados. As funções info(), describe() e shape são suas melhores amigas. A função info() mostra tipos de dados e valores ausentes, enquanto describe() apresenta estatísticas descritivas para colunas numéricas.
# Informações gerais sobre o DataFrame
print(df.info())
# Output mostra: tipos de dados, não-nulos, uso de memória
# Estatísticas descritivas
print(df.describe())
# Output inclui: count, mean, std, min, 25%, 50%, 75%, max
# Dimensões
print(f"Linhas: {df.shape[0]}, Colunas: {df.shape[1]}")
# Output: Linhas: 4, Colunas: 4
# Primeiras e últimas linhas
print(df.head(2)) # Primeiras 2 linhas
print(df.tail(1)) # Última linha
Seleção e Indexação
Acessar dados específicos em um DataFrame é fundamental. Python oferece várias formas de fazer isso, e escolher a correta muda tudo em termos de desempenho e legibilidade.
# Seleção por nome de coluna (retorna Series)
print(df['nome'])
# Seleção de múltiplas colunas (retorna DataFrame)
print(df[['nome', 'salario']])
# Seleção por posição (iloc - integer location)
print(df.iloc[0]) # Primeira linha
print(df.iloc[1, 2]) # Segunda linha, terceira coluna (valor 7500)
# Seleção por rótulo (loc - label-based)
print(df.loc[0, 'nome']) # Linha 0, coluna 'nome' (valor 'Alice')
# Seleção condicional (booleana)
maiores_30 = df[df['idade'] > 30]
print(maiores_30)
Limpeza de Dados: Tratamento de Valores Ausentes e Inconsistências
Identificação e Tratamento de Valores Ausentes
Dados do mundo real frequentemente contêm valores ausentes (NaN, None, strings vazias). Deixá-los sem tratamento compromete análises posteriores. O primeiro passo é identificar onde eles estão e decidir como lidar com eles.
# Criando um DataFrame com dados ausentes
dados_incompletos = {
'produto': ['A', 'B', None, 'D', 'E'],
'venda': [100, np.nan, 150, 200, None],
'categoria': ['X', 'Y', 'X', None, 'Y']
}
df_limpo = pd.DataFrame(dados_incompletos)
# Encontrando valores ausentes
print(df_limpo.isnull()) # DataFrame booleano
print(df_limpo.isnull().sum()) # Contagem por coluna
# Removendo linhas com qualquer valor ausente
df_sem_nulos = df_limpo.dropna()
# Removendo linhas onde coluna específica é nula
df_sem_nulos_venda = df_limpo.dropna(subset=['venda'])
# Preenchendo valores ausentes
df_preenchido = df_limpo.fillna({'venda': 0, 'categoria': 'Indefinido'})
print(df_preenchido)
Detecção e Correção de Duplicatas
Duplicatas podem distorcer análises e devem ser identificadas cuidadosamente. Nem sempre você quer removê-las — às vezes, apenas registrá-las é necessário.
# Criando dados com duplicatas
dados_dup = {
'id': [1, 2, 2, 3, 1],
'nome': ['Alice', 'Bruno', 'Bruno', 'Carlos', 'Alice'],
'email': ['alice@email.com', 'bruno@email.com', 'bruno@email.com', 'carlos@email.com', 'alice@email.com']
}
df_dup = pd.DataFrame(dados_dup)
# Identificando duplicatas (True para duplicadas, False para primeira ocorrência)
print(df_dup.duplicated())
# Contando duplicatas
duplicatas = df_dup.duplicated().sum()
print(f"Total de duplicatas: {duplicatas}")
# Removendo duplicatas (mantém primeira ocorrência por padrão)
df_unico = df_dup.drop_duplicates()
# Removendo duplicatas considerando apenas colunas específicas
df_unico_id = df_dup.drop_duplicates(subset=['id'], keep='first')
print(df_unico_id)
Transformação de Tipos de Dados
Colunas frequentemente chegam com tipos incorretos. Uma coluna que deveria ser data chega como string, ou um ID numérico chega como inteiro quando deveria ser categórico. Corrigir isso é essencial.
# Exemplo de dados com tipos incorretos
dados_tipos = {
'data': ['2024-01-15', '2024-02-20', '2024-03-10'],
'preco': ['100.50', '250.00', '75.99'],
'categoria_id': [1, 2, 1]
}
df_tipos = pd.DataFrame(dados_tipos)
# Verificando tipos atuais
print(df_tipos.dtypes)
# Convertendo string para datetime
df_tipos['data'] = pd.to_datetime(df_tipos['data'])
# Convertendo string para float
df_tipos['preco'] = df_tipos['preco'].astype(float)
# Convertendo inteiro para categórico
df_tipos['categoria_id'] = df_tipos['categoria_id'].astype('category')
print(df_tipos.dtypes)
print(df_tipos)
Análise Exploratória de Dados (EDA)
Operações Grupais e Agregação
Agrupar dados por uma ou mais colunas e aplicar operações agregadas é uma tarefa comum. Isso permite resumir grandes volumes de dados em informações significativas.
# Dados de vendas
vendas = {
'departamento': ['TI', 'TI', 'RH', 'RH', 'TI', 'Financeiro'],
'mes': [1, 2, 1, 2, 1, 1],
'valor': [5000, 6000, 3000, 3500, 7000, 4500]
}
df_vendas = pd.DataFrame(vendas)
# Agrupando por departamento e somando valores
resumo = df_vendas.groupby('departamento')['valor'].sum()
print(resumo)
# Múltiplas agregações
resumo_multi = df_vendas.groupby('departamento').agg({
'valor': ['sum', 'mean', 'count', 'std']
})
print(resumo_multi)
# Agrupando por múltiplas colunas
resumo_duplo = df_vendas.groupby(['departamento', 'mes'])['valor'].sum()
print(resumo_duplo)
# Criando novos nomes para colunas agregadas
resumo_nomes = df_vendas.groupby('departamento')['valor'].agg(
total='sum',
media='mean',
quantidade='count'
)
print(resumo_nomes)
Estatísticas Descritivas e Correlações
Compreender o comportamento estatístico dos seus dados revela padrões importantes. Correlações indicam relacionamentos entre variáveis que podem ser investigados mais profundamente.
# Dados de desempenho de estudantes
performance = {
'hora_estudo': [2, 5, 3, 6, 4, 7, 2.5],
'nota_prova': [50, 85, 60, 90, 70, 95, 55],
'frequencia': [0.7, 0.95, 0.8, 0.98, 0.75, 0.99, 0.6]
}
df_perf = pd.DataFrame(performance)
# Estatísticas descritivas completas
print(df_perf.describe())
# Correlação entre variáveis (matriz de correlação)
correlacao = df_perf.corr()
print(correlacao)
# Correlação de Pearson entre duas colunas específicas
pearson = df_perf['hora_estudo'].corr(df_perf['nota_prova'])
print(f"Correlação de Pearson: {pearson:.3f}")
# Desvio padrão por coluna
desvios = df_perf.std()
print(desvios)
# Quantis (percentis)
quantis = df_perf.quantile([0.25, 0.5, 0.75])
print(quantis)
Transformação e Criação de Novas Colunas
Frequentemente você precisa criar novas colunas derivadas de dados existentes. Isso pode ser uma simples multiplicação ou uma lógica condicional complexa.
# Dados de e-commerce
pedidos = {
'produto': ['Notebook', 'Mouse', 'Teclado', 'Monitor', 'Webcam'],
'preco_unitario': [3000, 50, 200, 800, 150],
'quantidade': [2, 10, 5, 1, 3],
'desconto_percentual': [0.1, 0, 0.05, 0.15, 0]
}
df_pedidos = pd.DataFrame(pedidos)
# Criando coluna de valor bruto
df_pedidos['valor_bruto'] = df_pedidos['preco_unitario'] * df_pedidos['quantidade']
# Criando coluna de desconto em reais
df_pedidos['desconto_reais'] = df_pedidos['valor_bruto'] * df_pedidos['desconto_percentual']
# Criando coluna de valor líquido
df_pedidos['valor_liquido'] = df_pedidos['valor_bruto'] - df_pedidos['desconto_reais']
# Lógica condicional com np.where
df_pedidos['categoria_preco'] = np.where(
df_pedidos['preco_unitario'] > 500,
'Premium',
'Padrão'
)
# Lógica condicional mais complexa com pd.cut (categorização por bins)
df_pedidos['faixa_valor'] = pd.cut(
df_pedidos['valor_liquido'],
bins=[0, 500, 2000, float('inf')],
labels=['Baixo', 'Médio', 'Alto']
)
print(df_pedidos)
Análise de Distribuições e Outliers
Identificar valores extremos (outliers) é crítico para evitar conclusões enviesadas. Nem sempre outliers são erros — às vezes são observações legítimas e importantes.
# Dados de salários
salarios = {
'area': ['TI', 'TI', 'TI', 'TI', 'TI', 'RH', 'RH', 'RH', 'RH'],
'salario': [5000, 5200, 5100, 5300, 25000, 3000, 3200, 3100, 3150]
}
df_sal = pd.DataFrame(salarios)
# Calculando quartis e IQR (Interquartile Range)
Q1 = df_sal['salario'].quantile(0.25)
Q3 = df_sal['salario'].quantile(0.75)
IQR = Q3 - Q1
# Limites para outliers (método IQR)
limite_inferior = Q1 - 1.5 * IQR
limite_superior = Q3 + 1.5 * IQR
# Identificando outliers
df_sal['outlier'] = (df_sal['salario'] < limite_inferior) | (df_sal['salario'] > limite_superior)
print(f"Q1: {Q1}, Q3: {Q3}, IQR: {IQR}")
print(f"Limites: [{limite_inferior}, {limite_superior}]")
print(df_sal[df_sal['outlier']])
# Calculando escore Z (quantos desvios padrão do mean)
from scipy import stats
df_sal['z_score'] = np.abs(stats.zscore(df_sal['salario']))
outliers_z = df_sal[df_sal['z_score'] > 3]
print(outliers_z)
Reformatação de Dados com Pivot e Melt
Às vezes você precisa reorganizar dados de forma radicalmente diferente. Essas operações (pivot e melt) transformam a estrutura do DataFrame de forma eficiente.
# Dados em formato longo (tidy)
vendas_longo = {
'loja': ['Loja A', 'Loja A', 'Loja A', 'Loja B', 'Loja B', 'Loja B'],
'mes': [1, 2, 3, 1, 2, 3],
'valor': [10000, 12000, 11500, 8000, 9500, 9800]
}
df_longo = pd.DataFrame(vendas_longo)
# Convertendo para formato largo com pivot
df_largo = df_longo.pivot(index='loja', columns='mes', values='valor')
print("Formato Largo:")
print(df_largo)
# Adicionando sufixo às colunas
df_largo.columns.name = None
df_largo = df_largo.rename(columns={1: 'jan', 2: 'fev', 3: 'mar'})
# Convertendo de volta para formato longo com melt
df_revertido = df_largo.reset_index().melt(id_vars='loja', var_name='mes', value_name='valor')
print("\nFormato Longo (após melt):")
print(df_revertido)
Análise Prática: Case Completo
Para consolidar os conceitos, vamos executar uma análise exploratória completa em um dataset simulado de e-commerce.
# Criando dataset realista
np.random.seed(42)
n_transacoes = 100
dados_ecommerce = {
'data': pd.date_range('2024-01-01', periods=n_transacoes, freq='D'),
'cliente_id': np.random.randint(1000, 1050, n_transacoes),
'categoria': np.random.choice(['Eletrônicos', 'Moda', 'Livros', 'Casa'], n_transacoes),
'valor_transacao': np.random.uniform(50, 500, n_transacoes),
'metodo_pagamento': np.random.choice(['Cartão', 'PIX', 'Boleto'], n_transacoes)
}
df_ecommerce = pd.DataFrame(dados_ecommerce)
# 1. Limpeza: adicionar alguns nulos propositalmente e limpar
df_ecommerce.loc[np.random.choice(df_ecommerce.index, 5, replace=False), 'cliente_id'] = np.nan
df_ecommerce = df_ecommerce.dropna(subset=['cliente_id'])
# 2. Transformação: arredondar valores, criar semana
df_ecommerce['valor_transacao'] = df_ecommerce['valor_transacao'].round(2)
df_ecommerce['semana'] = df_ecommerce['data'].dt.isocalendar().week
df_ecommerce['mes'] = df_ecommerce['data'].dt.month
# 3. Análise exploratória
print("=== RESUMO DO DATASET ===")
print(f"Total de transações: {len(df_ecommerce)}")
print(f"Período: {df_ecommerce['data'].min().date()} a {df_ecommerce['data'].max().date()}")
print(f"\nValor médio de transação: R$ {df_ecommerce['valor_transacao'].mean():.2f}")
print(f"Valor total: R$ {df_ecommerce['valor_transacao'].sum():.2f}")
print("\n=== ANÁLISE POR CATEGORIA ===")
por_categoria = df_ecommerce.groupby('categoria').agg({
'valor_transacao': ['sum', 'mean', 'count']
}).round(2)
por_categoria.columns = ['Total (R$)', 'Ticket Médio (R$)', 'Quantidade']
print(por_categoria)
print("\n=== MÉTODO DE PAGAMENTO ===")
por_pagamento = df_ecommerce['metodo_pagamento'].value_counts()
print(por_pagamento)
print("\n=== CLIENTES ===")
clientes_unicos = df_ecommerce['cliente_id'].nunique()
print(f"Total de clientes únicos: {clientes_unicos}")
# Cliente que mais gastou
cliente_maior_gasto = df_ecommerce.groupby('cliente_id')['valor_transacao'].sum().idxmax()
maior_gasto = df_ecommerce.groupby('cliente_id')['valor_transacao'].sum().max()
print(f"Cliente com maior gasto: ID {int(cliente_maior_gasto)} (R$ {maior_gasto:.2f})")
Conclusão
Neste artigo, você aprendeu os três pilares da análise de dados com Pandas: primeiro, como estruturar e explorar DataFrames através de operações de seleção e inspeção; segundo, como identificar e corrigir problemas comuns em dados reais como valores ausentes, duplicatas e tipos incorretos; terceiro, como extrair insights através de agregações, estatísticas e transformações de dados. A prática regular com esses conceitos — começando em datasets pequenos e evoluindo para maiores — é o caminho mais seguro para dominar a bibliotec. O Pandas não é apenas uma ferramenta: é o idioma padrão para comunicar-se com dados em Python.