Dados
O que “dados” significa em um contexto de ferramentas de ML
Em aprendizado de máquina (machine learning), dados não são apenas um arquivo que você carrega antes do treinamento — são uma sequência de artefatos e transformações que alimentam um pipeline de ML (ML pipeline) de ponta a ponta:
- Ingestão: coletar eventos brutos, tabelas, documentos, imagens, logs ou rótulos de sistemas operacionais
- Preparação: limpeza, normalização, desduplicação, junções e tratamento de valores ausentes
- Construção de características/alvo: transformar campos brutos em entradas prontas para o modelo e rótulos
- Divisão: criar conjuntos de treino/validação/teste sem vazamento
- Publicação: gravar conjuntos de dados curados e características em armazenamento para treinamento e/ou serving
- Monitoramento: acompanhar deriva de dados, mudanças de esquema e regressões de qualidade ao longo do tempo
Este artigo foca nas ferramentas que tornam essas etapas práticas — especialmente Pandas para trabalho em uma única máquina e Apache Spark para processamento distribuído — além do ecossistema ao redor (formatos como Parquet, Arrow, validação e padrões comuns de pipeline).
Processamento de dados em pipelines de ML: fundamentos e padrões
ETL vs ELT (e por que ML frequentemente se parece com ELT)
O armazenamento de dados tradicional distingue:
- ETL (Extrair → Transformar → Carregar): transformar antes de carregar no warehouse
- ELT (Extrair → Carregar → Transformar): carregar dados brutos primeiro e transformar dentro do warehouse/lake
Fluxos de trabalho de ML frequentemente se assemelham a ELT porque você quer:
- Retenção de dados brutos para reprodutibilidade e re-rotulagem
- Múltiplos conjuntos de características a jusante construídos a partir das mesmas fontes
- Linhagem auditável: “Qual snapshot bruto produziu este conjunto de treinamento?”
Na prática, muitas vezes você mantém:
- conjuntos de dados Bronze (bruto) → Silver (limpo/conformado) → Gold (pronto para características)
(comum em padrões de lakehouse, frequentemente implementado com Delta Lake/Iceberg/Hudi)
Lote vs streaming
A maioria dos pipelines de treinamento é em lote (snapshots por hora/diários), mas muitos produtos também precisam de características em streaming para inferência em tempo real. As mesmas preocupações centrais se aplicam:
- estabilidade de esquema
- correção de tempo do evento
- desduplicação
- tratamento de dados atrasados
- backfills e replay
O Spark suporta tanto lote quanto streaming; o Pandas é principalmente em lote.
A abstração de “DataFrame”
Um dos principais motivos de Pandas e Spark serem dominantes é o DataFrame: um modelo tabular com colunas nomeadas e valores tipados. Para ML, DataFrames são convenientes porque mapeiam bem para:
- características numéricas/categóricas
- preparação de dados com muitas junções (rótulos + entidades + eventos)
- transformações por coluna
- exportação para formatos de treinamento (arrays NumPy, TFRecords, Parquet etc.)
No entanto, DataFrames também incentivam erros (como vazamento acidental por junções ou mistura de tempos). Bons pipelines tratam DataFrames como conjuntos de dados tipados e versionados em vez de tabelas ad hoc.
Pandas em ML: pontos fortes, limites e boas práticas
Pandas é a ferramenta padrão para exploração, prototipagem e processamento em lote pequeno a médio em uma única máquina.
Quando o Pandas brilha
- iteração rápida em notebooks
- integração com um ecossistema rico (NumPy, scikit-learn, visualização)
- limpeza e remodelagem de dados expressivas
- ideal para conjuntos de dados que cabem confortavelmente na RAM (com folga)
Onde o Pandas tem dificuldade
- conjuntos de dados maiores do que a memória (ou perto dos limites de memória)
- junções/group-bys caros em escala
- paralelização (Pandas é majoritariamente de processo único, a menos que você adicione ferramentas)
- armadilhas de desempenho por dtype
object, loops em Python e cópias repetidas
Exemplo prático: de CSV → características → modelo scikit-learn
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
df = pd.read_csv("transactions.csv")
# Basic cleaning
df = df.drop_duplicates()
df["amount"] = df["amount"].clip(lower=0)
df["country"] = df["country"].fillna("UNK")
df["is_fraud"] = df["is_fraud"].astype(int)
# Avoid leakage: ensure splits respect time if this is event data
# (here we show a random split for simplicity)
X = df.drop(columns=["is_fraud"])
y = df["is_fraud"]
num_cols = ["amount", "account_age_days"]
cat_cols = ["country", "channel"]
preprocess = ColumnTransformer(
transformers=[
("num", StandardScaler(), num_cols),
("cat", OneHotEncoder(handle_unknown="ignore"), cat_cols),
],
remainder="drop",
)
model = Pipeline(
steps=[
("prep", preprocess),
("clf", LogisticRegression(max_iter=1000)),
]
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
model.fit(X_train, y_train)
proba = model.predict_proba(X_test)[:, 1]
print("AUC:", roc_auc_score(y_test, proba))
Ideia-chave: use um pipeline para que as mesmas transformações se apliquem no treinamento e na inferência. Isso também reduz o risco de vazamento por “pré-processar o conjunto de dados inteiro”.
Para mais sobre estimadores clássicos e pipelines, veja Bibliotecas de ML Clássico.
Boas práticas do Pandas para trabalho de dados em ML
- Prefira Parquet em vez de CSV por velocidade e preservação de tipos (veja “Formatos” abaixo).
- Mantenha as colunas em dtypes eficientes:
- evite
objectquando possível (usestring[pyarrow], categóricos, dtypes numéricos)
- evite
- Use operações vetorizadas (evite loops em Python).
- Seja explícito sobre:
- fusos horários e timestamps
- valores ausentes
- chaves de junção e cardinalidade de junção
- Trate a divisão treino/teste como uma etapa de primeira classe (frequentemente baseada em tempo).
Apache Arrow e Parquet: a infraestrutura que torna pipelines rápidos
Grande parte das ferramentas modernas de dados em Python depende do Apache Arrow, um formato colunar em memória que permite interoperabilidade com cópia zero/baixa entre sistemas.
Parquet (armazenamento colunar)
Parquet é o formato “cavalo de batalha” para análises e pipelines de ML:
- compressão colunar (ler apenas as colunas necessárias)
- predicate pushdown eficiente (pular grupos de linhas)
- tratamento de esquema mais estável em comparação com CSV/JSON
- funciona bem com Spark, Pandas, DuckDB, Polars e muitos warehouses
Na prática:
- O Pandas frequentemente lê/grava Parquet via
pyarrow(oufastparquet). - O Spark usa Parquet como formato padrão de “lake”.
Exemplo: escreva uma vez, leia muitas (Pandas ↔ Parquet)
import pandas as pd
df = pd.read_parquet("silver/features.parquet") # fast columnar load
df.to_parquet("gold/train_snapshot.parquet", index=False)
Além do Pandas: opções comuns para um único nó e “maior do que a memória”
Mesmo que seu pipeline comece em Pandas, vale conhecer as ferramentas adjacentes:
- Polars: biblioteca de DataFrame rápida com engine de consultas e execução preguiçosa (lazy execution); ótima para cargas locais focadas em desempenho.
- DuckDB: engine analítico de SQL embarcado; excelente para consultar arquivos Parquet localmente sem carregar tudo na memória.
- Dask: API de DataFrame paralela/distribuída semelhante ao Pandas; útil ao escalar código no estilo Pandas.
- Ray Data: processamento distribuído de dados integrado ao ecossistema Ray (frequentemente usado com treinamento distribuído).
Um padrão comum é: usar SQL (DuckDB/Spark) para junções/agregações pesadas e, depois, Pandas/NumPy para modelagem.
Exemplo: DuckDB consultando Parquet sem materialização completa
import duckdb
con = duckdb.connect()
df = con.execute("""
SELECT country, AVG(amount) AS avg_amount, COUNT(*) AS n
FROM 'silver/transactions/*.parquet'
WHERE event_time >= TIMESTAMP '2026-01-01'
GROUP BY country
ORDER BY n DESC
""").fetchdf()
print(df.head())
Isso é particularmente útil quando você quer extrações de características reproduzíveis usando semântica de SQL.
Spark para pipelines de ML: conceitos que importam
Apache Spark é o motor dominante de processamento em lote distribuído em muitas organizações. Ele suporta:
- APIs de SQL e DataFrame
- junções distribuídas, agregações e funções de janela
- leitura/gravação particionadas de Parquet/Delta/etc.
- (opcionalmente) streaming via Structured Streaming
Ideias centrais do Spark (as partes que afetam correção e custo em ML)
Execução preguiçosa e planejamento de consultas
O Spark constrói um plano lógico e o otimiza antes da execução. Isso pode ser uma grande vantagem (otimizações automáticas), mas também significa:
- problemas de desempenho aparecem no momento da ação (
count,write,collect) - depuração requer entender planos de execução (
explain())
Partições e shuffles
- Os dados são divididos em partições processadas em paralelo.
- Algumas operações causam shuffles (redistribuição pelo cluster), especialmente junções e group-bys.
- Shuffles dominam tempo de execução e custo em muitos pipelines de características para ML.
Junções: broadcast vs shuffle
O Spark pode fazer broadcast de uma tabela pequena para todos os workers para evitar uma junção com shuffle. Isso costuma ser crítico ao juntar:
- tabela grande de eventos + tabela pequena de dimensão (por exemplo, metadados de usuário)
Evite UDFs em Python quando possível
UDFs em Python podem quebrar otimizações e rodar mais lento do que funções nativas do Spark SQL. Prefira:
- expressões embutidas do Spark SQL
- pandas UDFs vetorizadas apenas quando necessário
Exemplo prático: engenharia de características no Spark e gravação em Parquet
from pyspark.sql import SparkSession, functions as F, Window
spark = SparkSession.builder.getOrCreate()
events = spark.read.parquet("bronze/events/")
labels = spark.read.parquet("silver/labels/")
# Basic cleaning and filtering
events = (events
.filter(F.col("event_time").isNotNull())
.withColumn("event_date", F.to_date("event_time"))
.filter(F.col("event_date") >= F.lit("2026-01-01")))
# Example: per-user rolling count over the last 7 days (window feature)
w = (Window
.partitionBy("user_id")
.orderBy(F.col("event_time").cast("timestamp"))
.rangeBetween(-7 * 86400, 0)) # seconds range window
features = (events
.withColumn("is_purchase", (F.col("event_type") == "purchase").cast("int"))
.withColumn("purchases_7d", F.sum("is_purchase").over(w)))
# Join labels (ensure you join correctly by entity and time keys!)
dataset = (labels
.join(features, on=["user_id"], how="left")
.select("user_id", "label", "event_time", "purchases_7d"))
# Write partitioned Parquet for efficient downstream reads
(dataset
.repartition("event_date")
.write
.mode("overwrite")
.partitionBy("event_date")
.parquet("gold/training_dataset/"))
Notas:
- Pipelines reais geralmente exigem junções sensíveis ao tempo (por exemplo, juntar características calculadas estritamente antes do tempo do rótulo) para evitar vazamento.
- A estratégia de particionamento importa: particionar por uma data mais grossa geralmente funciona bem para leituras incrementais.
Saída do Spark para treinamento de modelos
Caminhos comuns:
- Gravar Parquet e treinar com um carregador que saiba ler Parquet (Python + ecossistema Arrow).
- Converter para NumPy/Pandas para amostras menores (cuidado com
toPandas()). - Usar Spark MLlib para alguns pipelines (menos comum hoje para deep learning, mas ainda usado em algumas organizações).
Qualidade de dados, imposição de esquema e validação
Pipelines de ML falham em produção mais frequentemente por problemas de dados do que por problemas no código de modelagem. Modos de falha comuns:
- deriva de esquema (novas colunas, colunas renomeadas, tipos alterados)
- valores ausentes ou inválidos (NaNs, idades negativas, timestamps impossíveis)
- chaves de junção quebradas (mudanças súbitas de cardinalidade)
- mudança de distribuição (dados de treinamento não correspondem mais aos dados ao vivo)
Mitigações práticas:
- Contratos de esquema: definir colunas esperadas, tipos e restrições.
- Verificações de validação: afirmar contagens de linhas, taxas de nulos, faixas min/max, unicidade.
- Testes unitários de conjunto de dados: tratar transformações de dados como código testável.
Ferramentas populares incluem Great Expectations e AWS Deequ (focada em Spark), mas mesmo verificações leves são valiosas.
Exemplo de lógica simples de validação (ideia agnóstica de ferramenta):
def assert_non_null(df, cols):
for c in cols:
null_rate = df[c].isna().mean()
assert null_rate < 0.001, f"{c} null rate too high: {null_rate:.4f}"
Engenharia de características como um grafo de transformações reproduzível
Engenharia de características não é apenas “criar colunas” — é definir um grafo de transformações que deve ser consistente em:
- treinamento
- avaliação
- inferência em lote
- inferência online (se aplicável)
Dois padrões ajudam a evitar inconsistências:
Separação fit/transform
Calcular estatísticas (médias, vocabulários, encoders) apenas nos dados de treinamento e depois aplicar em validação/teste.Objetos de pipeline
Usar pipelines estruturados (por exemplo, scikit-learnPipeline, pipelines do Spark ML) para que parâmetros de transformação sejam capturados e reutilizados.
Isso está intimamente relacionado a prevenir vazamento de dados, quando informações do período de avaliação influenciam acidentalmente o treinamento.
Formatos e layout de armazenamento: desempenho é um problema de design de dados
Formatos comuns
- CSV: fácil, mas lento e com perda (tipos); use para intercâmbio, não para conjuntos “gold”.
- JSON: flexível, mas pesado; comum para logs, menos ideal para análises.
- Parquet/ORC: melhores para análises e tabelas de características.
- Avro: comum em streaming e ecossistemas com registro de esquema.
Particionamento e dimensionamento de arquivos
Para Spark (e muitas ferramentas de lake), o desempenho depende fortemente de:
- colunas de partição (frequentemente data ou outra chave incremental)
- evitar muitos arquivos pequenos (alto overhead de metadados)
- escolher tamanhos sensatos de row group / arquivo (frequentemente 128MB–1GB, dependendo do sistema)
Um anti-padrão típico: gravar milhares de arquivos Parquet minúsculos e depois pagar grande overhead para listá-los e abri-los.
Formatos de tabela lakehouse
Delta Lake, Apache Iceberg e Apache Hudi adicionam:
- transações ACID
- evolução de esquema
- time travel/versionamento
- compactação e otimizações de metadados
Isso ajuda fluxos de ML porque você consegue reproduzir dados de treinamento como “tabela na versão X”.
Reprodutibilidade: versionamento de conjunto de dados e linhagem
Um modelo só é tão reproduzível quanto o snapshot de seus dados de treinamento. Boas práticas incluem:
- Snapshots de treinamento imutáveis (ou referências de time travel)
- armazenar a consulta/código exatos usados para produzir um conjunto de dados
- rastrear identificadores de conjunto de dados junto com artefatos do modelo em ferramentas como Ferramentas de Experimento
- documentar definições de rótulos e política de anotação (frequentemente ligada a Ferramentas de Rotulagem)
Em configurações maduras, uma entrada no registro de modelos referencia:
- versão do código do modelo
- versão do pipeline de características
- ID do snapshot de dados (versão da tabela, intervalo de partições, hash de commit)
- relatórios de avaliação (veja Harnesses de Avaliação para padrões de ferramentas de avaliação)
Interfaciando pipelines de dados com frameworks de treinamento
Frameworks de treinamento (veja Frameworks) tipicamente consomem:
- arrays/tensores em memória
- mini-batches em streaming
- arquivos fragmentados (sharded) para treinamento distribuído
Pontes entre processamento de dados e treinamento incluem:
- exportar shards curados de Parquet/TFRecords/WebDataset
- usar leitores baseados em Arrow para carregamento colunar rápido
- construir classes de conjunto de dados (PyTorch
Dataset, TensorFlowtf.data) que reproduzam as mesmas transformações
Um padrão frequente em produção:
- transformações e junções pesadas no Spark
- gravar dados de treinamento “gold” em Parquet
- o job de treinamento lê shards Parquet e aplica augmentação/tokenização leve
Para aplicações de LLM, a camada de “dados” também inclui vetorização e repositórios de recuperação; veja Ferramentas para LLM para bancos de dados vetoriais e infraestrutura de RAG.
Escolhendo a ferramenta certa: um guia prático de decisão
Use Pandas quando
- o conjunto de dados cabe confortavelmente na RAM
- você precisa de iteração rápida e manipulação flexível
- você está fazendo análise exploratória, prototipagem ou jobs pequenos em lote
Use Spark quando
- você tem junções/agregações em grande escala (centenas de GB a PB)
- você precisa de execução distribuída e tolerância a falhas
- a organização já executa Spark em um cluster e os dados vivem em um lake
Considere Polars/DuckDB/Dask quando
- você está principalmente no ambiente local, mas atingindo limites de desempenho do Pandas
- você quer SQL sobre Parquet sem um cluster (DuckDB)
- você precisa de processamento multi-core em uma única máquina (Polars) ou um scale-out leve (Dask)
Em muitas equipes, a abordagem mais eficaz é híbrida:
- Spark para criação de conjuntos “silver → gold”
- Pandas/Polars para experimentos locais em dados amostrados
- armazenamento consistente (Parquet + Arrow) para reduzir atrito
Armadilhas comuns no processamento de dados para ML (e como evitá-las)
Vazamento de dados por junções: juntar características calculadas usando informação futura em relação ao tempo do rótulo
Mitigação: computação de características sensível ao tempo; impor correção ponto-no-tempo.Divisão aleatória em séries temporais/eventos: infla métricas e falha em produção
Mitigação: divisões baseadas em tempo ou divisões agrupadas (por usuário/sessão).Junções com skew no Spark: poucas chaves dominam e causam stragglers
Mitigação: salting, junções por broadcast ou redesenho de chaves/agregações.collect()/toPandas()em DataFrames Spark grandes: OOM no driver
Mitigação: amostrar primeiro, gravar em Parquet ou agregar antes de coletar.Arquivos pequenos demais em grande quantidade: leituras lentas e grande overhead de metadados
Mitigação: coalesce/compactar saídas; escolher cuidadosamente a estratégia de partição.Deriva silenciosa de esquema: treinamento e serving discordam sobre tipos/categorias
Mitigação: validação de esquema e contratos explícitos de características.
Relação com o restante do ecossistema de ferramentas de ML
Ferramentas de dados ficam a montante de praticamente todo o resto:
- Conjuntos de Dados & Hospedagem: onde os conjuntos de dados vivem, como são compartilhados e versionados
- Ferramentas de Experimento: rastrear qual snapshot de dados e código de características produziu qual modelo
- Hubs & Registros de Modelos: associar modelos à linhagem de dados/características para governança de implantação
- Bibliotecas de ML Clássico e Frameworks: consumir dados processados para treinamento
- Ferramentas de Rotulagem: produzir rótulos de alta qualidade e metadados de anotação usados em aprendizado supervisionado
- Ferramentas para LLM: estender “dados” para embeddings, índices de recuperação e pipelines de documentos
Um modelo mental prático: ferramentas de processamento de dados são o toolchain de compilação para ML — elas transformam sinais confusos do mundo real em entradas estáveis das quais modelos podem aprender de forma confiável, repetida e em escala.