0%
Pandas: Manipulando e Analisando Dados em Python

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.

I

InSkillCoach

· min

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.nan sempre retorna False; use df["col"].isna().
  • Tipos object silenciosos: uma coluna de números contendo uma string se torna object; verifique com df.dtypes e converta com pd.to_numeric(s, errors="coerce").
  • inplace=True: desaconselhado pela equipe do Pandas; prefira a reatribuição df = df.dropna().

Boas práticas

  • Inspecione sistematicamente cada novo conjunto de dados com head, info e describe antes de qualquer transformação.
  • Use loc/iloc explicitamente para qualquer escrita e bana a indexação encadeada (df[...][...] = ...).
  • Vetorize: evite apply e 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, query e pipe em 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.

InSkillCoach

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+
1.4k
316

Comentários

Os comentários são alimentados pelo GitHub Discussions

Conecte-se com o GitHub para participar da discussão

Link copiado!