Substituindo dados missing com Machine Learning

Basicamente podemos separar o processo de machine learning em duas grandes etapas, sendo a primeira responsável pelo pré-processamento dos dados, e a segunda pela aplicação dos modelos e verificação dos resultados.

Ao utilizar um modelo de regressão para substituir dados missing, estamos aplicando a segunda etapa na primeira. Mas como isso é possível?

Dados Missing

Sabemos que nossa base de dados precisa estar livre de dados faltantes para que um modelo seja criado com precisão. Existem muitas maneiras de tratar esse problema, como simplesmente excluir as linhas com falhas, no caso de serem poucas, excluir as colunas, quando grande parte delas forem compostas de dados missing, ou ainda substituir os dados por algum outro valor, como a média ou mediana.

Porém, em todos esses casos, a solução é aplicada de maneira geral, tratando todos os dados faltantes da mesma forma.

Considere agora outra possibilidade de tratamento de dados missing:

Ao isolarmos uma variável com dados missing em nossa base, podemos torná-la nossa variável target, utilizando as demais como variáveis preditoras. Assim, a base será separada em dois grupos, onde um deles contém as linhas que possuem o valor da variável em questão preenchido, e o outro não.

Neste momento, o primeiro grupo criado se tornará nosso dataset de treino, ficando o segundo separado para as previsões futuras.

Agora este dataset será utilizado em um processo de machine learning, onde o problema a ser resolvido é a previsão de valores da variável target, que nesta base não possui nenhum dado faltante. Assim poderemos realizar as devidas separações entre dados de treino e teste, encontrar o melhor algoritmo de machine learning para este problema, e então aplicar o algoritmo no grupo onde a variável target está sem nenhum valor.

Cada observação da variável será então preenchida de maneira independente, com base nos demais dados da linha em questão.

Com as previsões feitas, podemos unificar as bases de dados novamente, voltando ao dataset inicial, mas com a variável que tinha dados missing totalmente preenchida.

Esta é uma abordagem muito mais sofisticada que as demais, e realmente pode apresentar melhores resultados do que a simples substituição das observações por um valor padrão, porém não há garantias que nos façam afirmar que essa opção sempre trará os melhores resultados.

Como de costume, cada análise possui suas peculiaridades e a comparação entre as abordagens é sempre válida.

Um exemplo prático

Neste link está disponível para download um dataset com dados sobre cervejas artesanais. Ele possui mais de 70 mil linhas e 23 variáveis, com muitos dados faltantes.

Vamos realizar as manipulações com a linguagem R, e é interessante alguns conhecimentos básicos para o entendimento do script. Caso você nunca tenha trabalhado com o R, recomendamos que assista o vídeo abaixo para obter algumas noções básicas, e, se preferir, confira nossos cursos de R básico e R para machine learning, totalmente gratuitos.

Em nosso curso de machine learning com R todos os conceitos utilizados nesse script são abordados em detalhes, de maneira teórica e prática, com muita didática e objetividade. No curso, cada linha é explicada por completo, para que não existam lacunas em seu aprendizado.

# Definir área de trabalho
setwd("C:/DidaticaTech")

# Carregar o dataset
df <- read.csv("recipeData.csv", na.strings = "N/A")

# filtrar linhas onde o style contém mais de 1000 observações
classes <- summary(df$Style, maxsum = 150)
classes <- data.frame(Style = names(classes[classes>1000]))
library(dplyr)
df <- semi_join(df,classes)

# Excluir variáveis
df$BeerID <- NULL
df$Name <- NULL
df$URL <- NULL
df$StyleID <- NULL

# Transforma a variável em numérica
df$BoilGravity <- as.numeric(df$BoilGravity)

# Percentual de dados faltantes
NAs <- round(colSums(is.na(df))*100/nrow(df), 2)
NAs[NAs>0]

No código acima estamos filtrando o dataset com base na variável “Style“, selecionando apenas os tipos de cerveja com mais de 1000 observações, e salvando os ajustes através de uma união, realizada com a função semi_join() do pacote dplyr. Após este filtro o dataset fica com 35424 linhas.

Na sequência excluímos algumas variáveis que não serão utilizadas, e transformamos o tipo da variável “BoilGravity” em numérico. Realizadas estas alterações na base, podemos então calcular o percentual de dados faltantes em cada uma das variáveis, imprimindo na tela aquelas com mais de 0%, conforme vemos abaixo.

  BoilGravity    MashThickness    PitchRate    PrimaryTemp 
3.67 40.92 55.19 31.80
PrimingMethod PrimingAmount UserId
91.84 94.19 69.32

Poderíamos aplicar algum algortimo de machine learning em cada uma dessas variáveis, de maneira a preencher suas lacunas com os dados previstos, mas para simplificar a análise faremos em apenas uma. Utilizaremos como variável target a variável “PitchRate“, que possui 55,19% de dados faltantes, mas antes de continuar temos outro problema a resolver.

Todas as demais variáveis serão utilizadas como variáveis preditoras, e, portanto, devem estar completamente preenchidas. Para seguir com nosso objetivo iremos apenas descartar as variáveis com percentual acima de 30%, e excluir as linhas que ainda contenham algum dado missing.

# Deletar variáveis com mais de 30% de dados faltantes, menos a PitchRate
NAs$PitchRate <- NULL
del <- names(NAs[NAs>30])
df <- select(df, -del)

# Deleta linhas onde a variável BoilGravity é NA
df <- df[!is.na(df$BoilGravity),]

Aqui nosso data frame “df” já está pronto para ser separado em dois grupos. Utilizaremos o primeiro deles para criação do modelo de machine learning, e então com o modelo devidamente criado, o aplicaremos no segundo grupo, gerando as previsões e eliminando os dados missing.

# Separa dados sem NAs
df_treino <- df[!is.na(df$PitchRate),]

# Pacote caret para criação do modelo
library(caret)

# Cria o modelo com um algoritmo de árvore de decisão
modelo_NA <- train(PitchRate ~ .,
data = df_treino,
method = "rpart",
trControl = trainControl(method = "cv",
number = 5))

# Utiliza o modelo criado para prever os valores e preencher no dataset
df$PitchRate[is.na(df$PitchRate)] <- predict(modelo_NA, df[is.na(df$PitchRate),])

Com a função train(), do pacote caret, realizamos a criação do modelo, utilizando o método “rpart” para aplicação de um algoritmo de árvore de decisão. Conforme explicamos no detalhamento do pacote caret, estamos utilizando a validação cruzada kfold na criação do modelo, não sendo necessário que realizemos a separação dos dados em treino e teste, uma vez que a própria validação cruzada realizará os processos.

Utilizamos então a função predict() para aplicar o modelo criado no segundo grupo de dados, aquele que contém apenas os dados faltantes, tendo como resultado a previsão de valor para cada linha da variável “PitchRate“.

Essas previsões são salvas no dataset original, que agora não terá mais nenhum dado faltante, estando assim pronto para ser utilizado em seu objetivo inicial.

Como continuar aprendendo

Para continuar esse aprendizado, tanto usando linguagem R como usando Python, conheça nossos cursos completos de machine learning. Temos diversos módulos que vão desde conceitos básicos para iniciantes até deep learning e algoritmos avançados, tudo com muita didática.

Leia também: