A técnica mais usada para preencher valores ausentes pode estar distorcendo silenciosamente a distribuição dos seus dados. A imputação pela média é o reflexo padrão de quase todo cientista de dados, e ela de fato resolve o erro imediato: o modelo volta a treinar. O que ela também faz, sem avisar, é concentrar centenas de valores em um único ponto, encolher a variância da coluna e atenuar as correlações que o seu modelo precisaria aprender.
Neste tutorial, você vai medir esse efeito com números, e não com intuição. A imputação de dados ausentes será tratada como um experimento controlado: vamos remover valores de um dataset completo, guardar o gabarito e comparar quatro estratégias do scikit-learn (média, mediana, KNN e imputação iterativa) em duas frentes: o erro de reconstrução contra os valores verdadeiros e o impacto de cada escolha no desempenho do modelo final. No caminho, um resultado inesperado: o KNN, em sua configuração ingênua, perde até para a média, e entender o motivo vale o artigo inteiro.
Código do Artigo
Acesse o código-fonte deste artigo gratuitamente.
Informe seu email para acessar o código:
✓ Seu código está pronto!
Abrir no Google Colab →Por que valores ausentes exigem mais do que um fillna?
Se você trabalha com dados reais, já conhece a cena: o fit falha, o traceback aponta para um NaN e a solução mais rápida é um fillna com a média ou um dropna sem cerimônia. Para a mecânica dessas operações no pandas, o guia de como tratar dados ausentes com Pandas cobre o essencial. O problema é que a decisão de como preencher raramente recebe a mesma atenção que o restante do pipeline, embora afete todas as etapas seguintes.
A literatura estatística trata o tema com mais rigor do que a prática costuma admitir. Desde o trabalho clássico de Donald Rubin, Inference and Missing Data (Biometrika, 1976), a ausência de dados é classificada em três mecanismos, e a estratégia adequada depende de qual deles está em jogo:
- MCAR (Missing Completely At Random): a probabilidade de um valor faltar não depende de nada, nem dos valores observados, nem dos ausentes. Formalmente,
. É o caso de um sensor que falha aleatoriamente. - MAR (Missing At Random): a ausência depende apenas de variáveis observadas,
. Pense em imóveis antigos com cadastro incompleto: a falta depende da idade do imóvel, que conhecemos. - MNAR (Missing Not At Random): a ausência depende do próprio valor que falta, como pessoas de renda alta que omitem a renda em pesquisas. É o cenário mais difícil, e nenhuma imputação puramente estatística o resolve por completo.
O livro Flexible Imputation of Missing Data, de Stef van Buuren, disponível gratuitamente online, é a referência moderna sobre esses mecanismos e suas consequências. Para o nosso experimento, vamos construir cenários MCAR e MAR de forma deliberada, porque só assim conseguimos medir o que cada imputador acerta.
Um experimento com gabarito
O dataset California Housing, disponível no próprio scikit-learn, descreve 20.640 distritos da Califórnia com oito variáveis numéricas, como renda mediana (MedInc), idade dos imóveis (HouseAge) e média de cômodos (AveRooms). Ele não possui valores ausentes, e é exatamente isso que o torna ideal aqui: nós mesmos removemos os valores, guardando os originais como gabarito. Para manter o experimento leve, trabalhamos com uma amostra de 5.000 distritos.
import numpy as np import pandas as pd from sklearn.datasets import fetch_california_housing RANDOM_STATE = 42 rng = np.random.default_rng(RANDOM_STATE) dados = fetch_california_housing(as_frame=True) df_completo = dados.frame.sample(n=5000, random_state=RANDOM_STATE).reset_index(drop=True) X_completo = df_completo.drop(columns="MedHouseVal") y = df_completo["MedHouseVal"]
Com os dados completos em mãos, injetamos a ausência segundo os dois mecanismos. Em MedInc, removemos 15% dos valores de forma puramente aleatória (MCAR). Em AveRooms, a probabilidade de remoção cresce com a idade do imóvel, de cerca de 5% nos mais novos a 35% nos mais antigos (MAR).
X = X_completo.copy() # MCAR: 15% dos valores de MedInc removidos ao acaso mascara_mcar = rng.random(len(X)) < 0.15 X.loc[mascara_mcar, "MedInc"] = np.nan # MAR: probabilidade de ausência em AveRooms cresce com HouseAge idade = X["HouseAge"] prob_mar = 0.05 + 0.30 * (idade - idade.min()) / (idade.max() - idade.min()) mascara_mar = rng.random(len(X)) < prob_mar X.loc[mascara_mar, "AveRooms"] = np.nan
O resultado são 752 valores ausentes em MedInc (15% da coluna) e 1.068 em AveRooms (21,4%). O diagnóstico visual abaixo confirma que o desenho funcionou: no painel da direita, a fração de AveRooms ausente cresce de forma consistente com a faixa de idade, de menos de 10% nos imóveis com até 10 anos a mais de 30% nos mais antigos. Esse é o retrato de um mecanismo MAR, e detectá-lo em dados reais é parte do trabalho de diagnóstico que antecede qualquer imputação.

O que a imputação pela média faz com a distribuição?
Antes de comparar métodos, vale visualizar o problema que motivou o artigo. O gráfico abaixo sobrepõe a distribuição verdadeira de MedInc (que conhecemos, porque guardamos o gabarito) à distribuição da mesma coluna após a imputação pela média e após a imputação por KNN.

O pico estreito na curva laranja é a assinatura da imputação pela média: os 752 valores imputados caem todos no mesmo ponto, criando uma concentração artificial que não existe nos dados verdadeiros. As consequências práticas são a redução da variância da coluna e a atenuação das correlações com as demais variáveis, já que os valores imputados não carregam nenhuma informação sobre o restante da linha. A curva verde, do KNN, acompanha o formato da distribuição original de perto, porque cada valor imputado é calculado a partir de vizinhos reais no espaço de atributos.
Qual método reconstrói melhor os valores?
Como guardamos o gabarito, podemos medir a raiz do erro quadrático médio (RMSE) entre o valor imputado e o valor verdadeiro, apenas nas posições mascaradas. Comparamos quatro imputadores do scikit-learn, descritos na documentação oficial de imputação: SimpleImputer com média e com mediana, KNNImputer com 5 vizinhos e IterativeImputer, que modela cada coluna com ausentes como função das demais, em rodadas sucessivas de regressão.
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import SimpleImputer, KNNImputer, IterativeImputer
imputadores = {
"Média": SimpleImputer(strategy="mean"),
"Mediana": SimpleImputer(strategy="median"),
"KNN (k=5)": KNNImputer(n_neighbors=5),
"Iterativa": IterativeImputer(max_iter=10, random_state=RANDOM_STATE),
}
registros = []
for nome, imputador in imputadores.items():
X_imputado = pd.DataFrame(imputador.fit_transform(X), columns=X.columns)
for coluna, mascara in [("MedInc", mascara_mcar), ("AveRooms", mascara_mar)]:
verdadeiro = X_completo.loc[mascara, coluna]
imputado = X_imputado.loc[mascara, coluna]
rmse = np.sqrt(np.mean((verdadeiro - imputado) ** 2))
registros.append({"Imputador": nome, "Coluna": coluna, "RMSE": rmse})
A imputação iterativa vence com folga nas duas colunas: RMSE de 1,326 em MedInc contra 1,897 da média, e de 0,855 em AveRooms contra 1,584. Até aqui, nenhuma surpresa: um método que usa as relações entre as variáveis reconstrói melhor do que uma constante. A surpresa está no KNN, que registrou 2,021 em MedInc e 1,662 em AveRooms, perdendo para a média e para a mediana nas duas colunas.
A explicação está na métrica de distância. O KNNImputer calcula distâncias euclidianas sobre as colunas em suas escalas originais, e Population, com desvio padrão na casa dos milhares, domina completamente o cálculo. Na prática, os vizinhos escolhidos são os distritos de população parecida, e não os distritos de fato semelhantes no conjunto dos atributos. A correção é padronizar as colunas antes de imputar e desfazer a escala em seguida:
from sklearn.preprocessing import StandardScaler escala = StandardScaler() X_escalado = escala.fit_transform(X) imputado_escalado = KNNImputer(n_neighbors=5).fit_transform(X_escalado) X_knn_escala = pd.DataFrame(escala.inverse_transform(imputado_escalado), columns=X.columns)
Com a padronização, o RMSE do KNN recua de 2,021 para 1,522 em MedInc e de 1,662 para 1,004 em AveRooms, e o método assume o segundo lugar geral, atrás apenas da imputação iterativa. O gráfico consolida o quadro completo.

A lição vale para além deste dataset: todo método baseado em distância é sensível à escala das variáveis, e o KNNImputer não padroniza nada por conta própria. Uma configuração ingênua não degrada o método de forma visível: ele continua rodando e produzindo números plausíveis, e é justamente por isso que o erro passa despercebido.
E o impacto no modelo final?
O erro de reconstrução conta apenas metade da história. Em um projeto real, ninguém conhece o gabarito: o que se observa é o desempenho do modelo treinado sobre os dados imputados. Avaliamos cinco estratégias com validação cruzada de 5 folds, usando um Random Forest como regressor. A primeira estratégia é o descarte de linhas (listwise deletion); as demais imputam dentro de um Pipeline, garantindo que as estatísticas de imputação sejam aprendidas apenas no conjunto de treino de cada fold. Esse cuidado evita o vazamento de dados que discutimos em detalhe no tutorial de pipelines no scikit-learn.
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import Pipeline
modelo = RandomForestRegressor(n_estimators=100, random_state=RANDOM_STATE, n_jobs=-1)
desempenho = {}
# Estratégia 1: descartar linhas com valores ausentes
completos = X.dropna()
scores_drop = cross_val_score(
modelo, completos, y.loc[completos.index],
cv=5, scoring="neg_root_mean_squared_error", n_jobs=-1
)
desempenho["Descarte"] = -scores_drop
# Demais estratégias: imputação dentro de um pipeline
pipelines = {
"Média": Pipeline([("imputacao", SimpleImputer(strategy="mean")), ("modelo", modelo)]),
"Mediana": Pipeline([("imputacao", SimpleImputer(strategy="median")), ("modelo", modelo)]),
"KNN + escala": Pipeline([
("escala", StandardScaler()),
("imputacao", KNNImputer(n_neighbors=5)),
("modelo", modelo),
]),
"Iterativa": Pipeline([
("imputacao", IterativeImputer(max_iter=10, random_state=RANDOM_STATE)),
("modelo", modelo),
]),
}
for nome, pipe in pipelines.items():
scores = cross_val_score(pipe, X, y, cv=5, scoring="neg_root_mean_squared_error", n_jobs=-1)
desempenho[nome] = -scores
O descarte de linhas elimina todas as observações com pelo menos um valor ausente e reduz o dataset de 5.000 para 3.347 linhas: um terço dos dados é descartado antes do primeiro fit. O resultado da validação cruzada está no gráfico abaixo.

A leitura honesta dos números contraria a expectativa criada pela seção anterior. A mediana e a média produziram os melhores RMSE médios (0,572 e 0,573), seguidas pelo descarte (0,587), pela imputação iterativa (0,592) e pelo KNN com escala (0,599). As diferenças, porém, são pequenas diante do desvio padrão entre folds, na faixa de 0,022 a 0,031: nenhum método se separa dos demais com significância confortável neste dataset.
Dois pontos merecem registro. Primeiro, a qualidade da reconstrução não se traduz automaticamente em ganho no modelo final: o Random Forest particiona o espaço de atributos em regiões e convive bem com os valores constantes da imputação simples, extraindo pouco proveito da reconstrução mais fiel. Com modelos lineares, ou com frações maiores de ausência, o quadro tende a mudar. Segundo, o descarte carrega um custo que a métrica não mostra por inteiro: além de treinar com um terço a menos de dados, um modelo avaliado apenas nas linhas completas aprende com uma amostra enviesada sempre que o mecanismo não é MCAR, como no nosso cenário MAR, em que as linhas descartadas são desproporcionalmente as de imóveis antigos.
A escolha do imputador, portanto, não é uma busca pelo vencedor universal. Se o objetivo é apenas alimentar um modelo de árvores, a mediana dentro de um Pipeline é um padrão difícil de criticar. Se as colunas imputadas alimentam análises estatísticas, relatórios ou modelos sensíveis à distribuição, os métodos multivariados preservam propriedades que a constante destrói, como a variância e as correlações que vimos no gráfico de densidade. E se a suspeita é de MNAR, nenhum imputador substitui uma conversa séria sobre a origem dos dados. Esse tipo de decisão, que combina estatística, contexto e ceticismo, é parte central do ofício descrito no guia de como se tornar um cientista de dados.
O notebook completo do experimento, com todo o código executado e os gráficos deste artigo, está disponível no botão de material no início do texto.
Takeaways
- A imputação pela média distorce a distribuição: ela concentra os valores imputados em um único ponto, reduz a variância e atenua correlações. O efeito é visível na curva de densidade de
MedInc. - Conheça o mecanismo antes de escolher o método: MCAR, MAR e MNAR exigem estratégias diferentes, e o diagnóstico do padrão de ausência é a primeira etapa, não a última.
- Métodos baseados em distância exigem padronização: o
KNNImputersem escala perdeu até para a média (RMSE 2,021 contra 1,897 emMedInc); com padronização, passou ao segundo melhor resultado (1,522). - A imputação iterativa venceu a reconstrução: RMSE de 1,326 em
MedInce 0,855 emAveRooms, com folga sobre os demais métodos. - Erro de reconstrução e desempenho do modelo são métricas diferentes: com Random Forest, média e mediana empataram na frente, e as diferenças ficaram dentro do desvio padrão entre folds.
- Impute dentro de um Pipeline: as estatísticas de imputação devem ser aprendidas apenas no conjunto de treino de cada fold, exatamente como qualquer outra transformação.

















