Pandas: Manipulando e Analisando Dados em Python
Guia completo do Pandas para carregar, limpar, filtrar, agregar e exportar dados tabulares em Python: Series, DataFrame, groupby, merge e datas.
InSkillCoach
Pandas: Manipulando e Analisando Dados em Python
Pandas é a biblioteca de referência para a manipulação de dados tabulares em Python. Construída sobre o NumPy, ela fornece duas estruturas de dados essenciais, a Series e o DataFrame, além de um arsenal completo para carregar arquivos, tratar valores ausentes, filtrar, agregar, unir e exportar dados. Este guia cobre o fluxo de trabalho completo de uma análise: da leitura de um CSV até a exportação dos resultados, passando pelas armadilhas clássicas a evitar.
Series e DataFrame: as duas estruturas fundamentais
A Series: uma coluna rotulada
Uma Series é um array unidimensional em que cada valor está associado a um rótulo de índice.
import pandas as pd
s = pd.Series([15, 12, 17, 9], index=["Alice", "Bob", "Chloé", "David"])
print(s)
# Alice 15
# Bob 12
# Chloé 17
# David 9
# dtype: int64
# Acesso por rótulo ou por posição
print(s["Chloé"]) # 17
print(s.iloc[0]) # 15
# As operações são vetorizadas, como no NumPy
print(s.mean()) # 13.25
print(s + 1) # cada nota aumentada em 1
O DataFrame: uma tabela de duas dimensões
Um DataFrame é um conjunto de colunas (Series) que compartilham o mesmo índice de linhas.
df = pd.DataFrame({
"nom": ["Alice", "Bob", "Chloé", "David"],
"age": [25, 32, 28, 41],
"ville": ["Paris", "Lyon", "Paris", "Nantes"],
"salaire": [42000, 38000, 51000, 46000],
})
print(df)
# nom age ville salaire
# 0 Alice 25 Paris 42000
# 1 Bob 32 Lyon 38000
# 2 Chloé 28 Paris 51000
# 3 David 41 Nantes 46000
# Uma coluna é uma Series
print(df["age"].dtype) # int64
print(df.shape) # (4, 4)
print(df.columns.tolist()) # ['nom', 'age', 'ville', 'salaire']
Leitura de arquivos com read_csv
read_csv é a função mais utilizada do Pandas. Suas opções cobrem quase todos os formatos CSV encontrados na prática.
# Leitura simples
df = pd.read_csv("ventes.csv")
# Opções comuns
df = pd.read_csv(
"ventes.csv",
sep=";", # separador (ponto e vírgula é comum na França)
decimal=",", # vírgula decimal
encoding="utf-8", # ou "latin-1" para arquivos antigos
usecols=["date", "produit", "montant"], # carregar apenas algumas colunas
dtype={"produit": "string"}, # forçar um tipo
parse_dates=["date"], # converter para datetime já na leitura
na_values=["N/A", "inconnu"], # valores a tratar como ausentes
nrows=1000, # limitar o número de linhas (útil para explorar)
)
O Pandas também lê muitos outros formatos: read_excel, read_json, read_parquet, read_sql. Para grandes volumes, o formato Parquet é nitidamente mais rápido e mais compacto que o CSV.
Inspeção: head, info, describe
Os três primeiros comandos a executar em qualquer novo conjunto de dados:
# Visão geral das primeiras linhas
print(df.head()) # 5 primeiras linhas (df.tail() para as últimas)
# Estrutura: tipos, valores não nulos, memória
df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 4 entries, 0 to 3
# Data columns (total 4 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 nom 4 non-null object
# 1 age 4 non-null int64
# 2 ville 4 non-null object
# 3 salaire 4 non-null int64
# Estatísticas descritivas das colunas numéricas
print(df.describe())
# age salaire
# count 4.000000 4.000000
# mean 31.500000 44250.000000
# std 6.952218 5560.275773
# min 25.000000 38000.000000
# max 41.000000 51000.000000
# Contagem dos valores distintos de uma coluna
print(df["ville"].value_counts())
# Paris 2
# Lyon 1
# Nantes 1
Seleção com loc e iloc
Dois acessores complementares: loc seleciona por rótulos, iloc por posições inteiras.
# loc[linhas, colunas] por rótulos
print(df.loc[0, "nom"]) # Alice
print(df.loc[0:2, ["nom", "ville"]]) # linhas 0 a 2 INCLUÍDAS, duas colunas
# iloc[linhas, colunas] por posições
print(df.iloc[0, 0]) # Alice
print(df.iloc[0:2, 0:2]) # linhas 0 e 1 (limite excluído), colunas 0 e 1
# Todas as linhas, uma coluna
print(df.loc[:, "salaire"])
# Última linha
print(df.iloc[-1])
Atenção à diferença de convenção: loc[0:2] inclui o limite 2 (lógica de rótulos), enquanto iloc[0:2] o exclui (lógica de posições, como nas listas do Python).
Filtragem booleana
# Uma condição
seniors = df[df["age"] > 30]
print(seniors)
# nom age ville salaire
# 1 Bob 32 Lyon 38000
# 3 David 41 Nantes 46000
# Condições combinadas: & (e), | (ou), ~ (não), parênteses obrigatórios
cible = df[(df["ville"] == "Paris") & (df["salaire"] >= 45000)]
print(cible)
# nom age ville salaire
# 2 Chloé 28 Paris 51000
# Pertencimento a uma lista
grandes_villes = df[df["ville"].isin(["Paris", "Lyon"])]
# Filtragem sobre texto
noms_a = df[df["nom"].str.startswith("A")]
# query: sintaxe alternativa e legível
resultat = df.query("age > 30 and salaire < 47000")
Colunas calculadas
# Nova coluna a partir das existentes
df["salaire_mensuel"] = df["salaire"] / 12
# Coluna condicional com numpy.where
import numpy as np
df["categorie"] = np.where(df["age"] < 30, "junior", "senior")
# Transformação por função (reservar para casos não vetorizáveis)
df["initiale"] = df["nom"].apply(lambda n: n[0])
# assign: criar colunas encadeando as operações
df2 = df.assign(
salaire_k=df["salaire"] / 1000,
age_en_2030=df["age"] + 4,
)
print(df2[["nom", "salaire_k", "age_en_2030"]].head(2))
# nom salaire_k age_en_2030
# 0 Alice 42.0 29
# 1 Bob 38.0 36
Prefira sempre as operações vetorizadas (df["a"] + df["b"], np.where, .str, .dt): apply com uma lambda costuma ser de 10 a 100 vezes mais lento.
Valores ausentes: isna, fillna, dropna
df_nan = pd.DataFrame({
"produit": ["A", "B", "C", "D"],
"prix": [10.0, None, 25.0, None],
"stock": [5, 3, None, 8],
})
# Detectar os valores ausentes
print(df_nan.isna())
print(df_nan.isna().sum()) # número de NaN por coluna
# produit 0
# prix 2
# stock 1
# Substituir por um valor fixo
df_rempli = df_nan.fillna({"prix": 0.0, "stock": 0})
# Substituir por uma estatística
df_nan["prix"] = df_nan["prix"].fillna(df_nan["prix"].mean())
print(df_nan["prix"])
# 0 10.0
# 1 17.5
# 2 25.0
# 3 17.5
# Remover as linhas que contêm pelo menos um NaN
df_propre = df_nan.dropna()
# Remover somente se uma coluna específica estiver ausente
df_propre = df_nan.dropna(subset=["stock"])
Escolha a estratégia conforme o contexto: remover linhas faz perder informação, e imputar uma média pode enviesar uma análise. Documente sempre a sua escolha.
groupby e agregações
O padrão “split-apply-combine”: dividir os dados por grupo, aplicar um cálculo e recombinar os resultados.
ventes = pd.DataFrame({
"region": ["Nord", "Sud", "Nord", "Sud", "Nord"],
"produit": ["A", "A", "B", "B", "A"],
"montant": [100, 150, 200, 120, 80],
})
# Agregação simples
print(ventes.groupby("region")["montant"].sum())
# region
# Nord 380
# Sud 270
# Várias estatísticas de uma só vez
print(ventes.groupby("region")["montant"].agg(["sum", "mean", "count"]))
# sum mean count
# region
# Nord 380 126.666667 3
# Sud 270 135.000000 2
# Agrupamento por várias colunas
print(ventes.groupby(["region", "produit"])["montant"].sum())
# region produit
# Nord A 180
# B 200
# Sud A 150
# B 120
# Agregações nomeadas: colunas de saída explícitas
resume = ventes.groupby("region").agg(
total=("montant", "sum"),
panier_moyen=("montant", "mean"),
)
print(resume)
# total panier_moyen
# region
# Nord 380 126.666667
# Sud 270 135.000000
Para transformar um resultado agrupado em tabela cruzada, pivot_table costuma ser mais direto:
tableau = ventes.pivot_table(index="region", columns="produit",
values="montant", aggfunc="sum", fill_value=0)
print(tableau)
# produit A B
# region
# Nord 180 200
# Sud 150 120
Combinando tabelas: merge e concat
merge: as junções no estilo SQL
clients = pd.DataFrame({
"client_id": [1, 2, 3],
"nom": ["Alice", "Bob", "Chloé"],
})
commandes = pd.DataFrame({
"client_id": [1, 1, 2, 4],
"montant": [50, 30, 70, 20],
})
# Junção interna: apenas as correspondências
interne = pd.merge(clients, commandes, on="client_id", how="inner")
print(interne)
# client_id nom montant
# 0 1 Alice 50
# 1 1 Alice 30
# 2 2 Bob 70
# Junção à esquerda: todos os clientes, mesmo sem pedido
gauche = pd.merge(clients, commandes, on="client_id", how="left")
# Chloé aparece com montant NaN
# how também aceita "right" e "outer"
concat: empilhar tabelas
janvier = pd.DataFrame({"produit": ["A", "B"], "ventes": [10, 20]})
fevrier = pd.DataFrame({"produit": ["A", "B"], "ventes": [15, 25]})
# Empilhamento vertical com reindexação
annee = pd.concat([janvier, fevrier], ignore_index=True)
print(annee)
# produit ventes
# 0 A 10
# 1 B 20
# 2 A 15
# 3 B 25
Ordenação
# Ordenação por uma coluna
print(df.sort_values("age"))
# Ordenação decrescente por várias colunas
print(df.sort_values(["ville", "salaire"], ascending=[True, False]))
# Ordenação pelo índice
print(df.sort_index())
# Os n maiores valores (mais rápido que uma ordenação completa)
print(df.nlargest(2, "salaire")[["nom", "salaire"]])
# nom salaire
# 2 Chloé 51000
# 3 David 46000
Datas: to_datetime e resample
journal = pd.DataFrame({
"date": ["2026-01-05", "2026-01-12", "2026-02-03", "2026-02-20", "2026-03-01"],
"ca": [120, 80, 200, 150, 90],
})
# Conversão para datetime
journal["date"] = pd.to_datetime(journal["date"])
print(journal["date"].dtype) # datetime64[ns]
# O acessor .dt expõe os componentes
journal["mois"] = journal["date"].dt.month
journal["jour_semaine"] = journal["date"].dt.day_name()
# resample: agregação temporal (requer um índice datetime)
serie = journal.set_index("date")["ca"]
mensuel = serie.resample("ME").sum() # "ME" = fim do mês (pandas >= 2.2)
print(mensuel)
# date
# 2026-01-31 200
# 2026-02-28 350
# 2026-03-31 90
# Filtrar um intervalo de datas
recentes = journal[journal["date"] >= "2026-02-01"]
Se o formato não for padrão, especifique-o: pd.to_datetime(s, format="%d/%m/%Y") evita ambiguidades entre dia e mês.
Exportação dos resultados
# CSV (index=False para não gravar a coluna de índice)
df.to_csv("resultats.csv", index=False, sep=";", encoding="utf-8")
# Excel (requer openpyxl)
df.to_excel("resultats.xlsx", sheet_name="Analyse", index=False)
# Parquet: rápido, compacto, tipos preservados (requer pyarrow)
df.to_parquet("resultats.parquet")
# JSON
df.to_json("resultats.json", orient="records", force_ascii=False)
Armadilhas comuns: o SettingWithCopyWarning
Esse warning aparece quando você modifica o resultado de uma seleção sobre a qual o Pandas não sabe se se trata de uma view ou de uma cópia.
# RUIM: indexação encadeada, a modificação pode ser perdida
sous_ensemble = df[df["ville"] == "Paris"]
sous_ensemble["salaire"] = 0 # SettingWithCopyWarning
# BOM: modificar o DataFrame original em uma única operação loc
df.loc[df["ville"] == "Paris", "salaire"] = 0
# BOM: trabalhar em uma cópia explícita e assumida
sous_ensemble = df[df["ville"] == "Paris"].copy()
sous_ensemble["salaire"] = 0 # nenhum warning, df original intacto
Outras armadilhas frequentes:
- Comparar com
NaN:df["col"] == np.nansempre retornaFalse; usedf["col"].isna(). - Tipos object silenciosos: uma coluna de números contendo uma string se torna
object; verifique comdf.dtypese converta compd.to_numeric(s, errors="coerce"). inplace=True: desaconselhado pela equipe do Pandas; prefira a reatribuiçãodf = df.dropna().
Boas práticas
- Inspecione sistematicamente cada novo conjunto de dados com
head,infoedescribeantes de qualquer transformação. - Use
loc/ilocexplicitamente para qualquer escrita e bana a indexação encadeada (df[...][...] = ...). - Vetorize: evite
applye loops quando existir uma operação sobre a coluna inteira. - Faça o parsing das datas já na leitura com
parse_dates, em vez de converter depois. - Nomeie suas agregações com
agg(total=("montant", "sum"))para obter colunas de saída claras. - Prefira Parquet ao CSV para arquivos intermediários: mais rápido, mais compacto e sem perda de tipos.
- Encadeie as transformações de forma legível com
assign,queryepipeem vez de multiplicar variáveis intermediárias.
Conclusão
O Pandas cobre todo o ciclo de vida de uma análise de dados: carregamento robusto com read_csv, exploração com info e describe, tratamento de valores ausentes, filtragem booleana, colunas calculadas, agregações com groupby, junções com merge, gerenciamento refinado de datas e exportação em vários formatos. Ao entender a distinção entre loc e iloc e evitar a indexação encadeada, você elimina a grande maioria dos bugs sutis. Os próximos passos naturais são a visualização dos seus DataFrames com o Matplotlib e a análise estatística com o SciPy, duas bibliotecas que se integram nativamente ao Pandas.
Sobre InSkillCoach
Especialista em treinamento e tecnologias
Coach especializado em tecnologias avançadas e IA, apoiado pela GNeurone Inc.
Certificações:
- AWS Certified Solutions Architect – Professional
- Certificações Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Comentários
Os comentários são alimentados pelo GitHub Discussions
Conecte-se com o GitHub para participar da discussão