Nesta aula abordaremos

1 Transformar as variáveis quantitativas

Objetivo: Aplicar transformações nas variáveis com o intuito de deixá-las mais apropriadas para o modelo de AM.

1.1 Padronização

Considere \(n\) observações, \(x_1, \ldots, x_n\), de uma variável \(X\). Além disso, seja \(\bar{x} = \frac{\sum_{i=1}^n x_{i}}{n}\) e \(s = \sqrt{\frac{\sum_{i=1}^n (x_i - \bar{x})^2}{n-1}}\) a média e desvio padrão, respectivamente. Para cada \(i = 1, \ldots, n\), defina: \[x_{i}^{(padr)}= \frac{x_{i} - \bar{x}}{s}.\] A transformação \(x_{i}^{(padr)}\) é chamada de padronização de \(x_{i}\). Note que \(\overline{x^{(padr)}} = 0\) (a média das observações padronizadas é 0) e \(s_{x^{(padr)}} = 1\) (o desvio padrão das observações padronizadas é 1).

library(caret)
library(tidyverse)
library(MASS)
data("Boston")
glimpse(Boston)
## Rows: 506
## Columns: 14
## $ crim    <dbl> 0.00632, 0.02731, 0.02729, 0.03237, 0.06905, 0.02985, 0.08829,…
## $ zn      <dbl> 18.0, 0.0, 0.0, 0.0, 0.0, 0.0, 12.5, 12.5, 12.5, 12.5, 12.5, 1…
## $ indus   <dbl> 2.31, 7.07, 7.07, 2.18, 2.18, 2.18, 7.87, 7.87, 7.87, 7.87, 7.…
## $ chas    <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ nox     <dbl> 0.538, 0.469, 0.469, 0.458, 0.458, 0.458, 0.524, 0.524, 0.524,…
## $ rm      <dbl> 6.575, 6.421, 7.185, 6.998, 7.147, 6.430, 6.012, 6.172, 5.631,…
## $ age     <dbl> 65.2, 78.9, 61.1, 45.8, 54.2, 58.7, 66.6, 96.1, 100.0, 85.9, 9…
## $ dis     <dbl> 4.0900, 4.9671, 4.9671, 6.0622, 6.0622, 6.0622, 5.5605, 5.9505…
## $ rad     <int> 1, 2, 2, 3, 3, 3, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,…
## $ tax     <dbl> 296, 242, 242, 222, 222, 222, 311, 311, 311, 311, 311, 311, 31…
## $ ptratio <dbl> 15.3, 17.8, 17.8, 18.7, 18.7, 18.7, 15.2, 15.2, 15.2, 15.2, 15…
## $ black   <dbl> 396.90, 396.90, 392.83, 394.63, 396.90, 394.12, 395.60, 396.90…
## $ lstat   <dbl> 4.98, 9.14, 4.03, 2.94, 5.33, 5.21, 12.43, 19.15, 29.93, 17.10…
## $ medv    <dbl> 24.0, 21.6, 34.7, 33.4, 36.2, 28.7, 22.9, 27.1, 16.5, 18.9, 15…
set.seed(12345)
index_training <- createDataPartition(y = Boston$medv, p =0.8)[[1]]
training <- Boston[index_training,]
testing <- Boston[-index_training,]

Padronização dos dados usando a função caret::preProcess()

# VC com k=10 folds e 3 repetições
ctrl <- trainControl(method = "repeatedcv", number = 10, repeats = 3)
# preditoras treino
X_train <- training[,-14]
# preditoras teste
X_test <- testing[,-14]

# Criando o pré-processamento
preProcScaled <- preProcess(X_train, method = c("center","scale"))

# Aplicando o pré-processamento ao conjunto de treino
trainingPadr <- training
trainingPadr[,-14] <- predict(preProcScaled,X_train)
# Aplicando o pré-processamento ao conjunto de teste 
testingPadr <- testing
testingPadr[,-14] <- predict(preProcScaled, X_test)
# Treinando um modelo knnRegressor  aos dados brutos
set.seed(12345)
model_knn_raw <-  train(medv ~ .,
                       data = training,
                       method = "knn",
                       trControl = ctrl)
model_knn_raw
## k-Nearest Neighbors 
## 
## 407 samples
##  13 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 366, 366, 367, 366, 366, 365, ... 
## Resampling results across tuning parameters:
## 
##   k  RMSE      Rsquared   MAE     
##   5  6.369323  0.4928874  4.454254
##   7  6.479205  0.4698312  4.523299
##   9  6.541589  0.4589821  4.542649
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was k = 5.
# Avaliando o desempenho nos dados de teste
predictions_raw <- predict(model_knn_raw,testing)
postResample(predictions_raw,testing$medv)
##      RMSE  Rsquared       MAE 
## 6.2642194 0.6828784 4.1826263
# Treinando um modelo knnRegressor aos dados padronizados
set.seed(12345)
model_knn_padr <-  train(medv ~ .,
                       data = trainingPadr,
                       method = "knn",
                       trControl = ctrl)
model_knn_padr
## k-Nearest Neighbors 
## 
## 407 samples
##  13 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 366, 366, 367, 366, 366, 365, ... 
## Resampling results across tuning parameters:
## 
##   k  RMSE      Rsquared   MAE     
##   5  4.796563  0.7164748  3.033612
##   7  4.736312  0.7282537  3.017162
##   9  4.641584  0.7416338  2.990051
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was k = 9.
# Avaliando o desempenho nos dados de teste
predictions_padr <- predict(model_knn_padr,testingPadr)
# Comparando o desempenho
cbind("sem padronização" = postResample(predictions_raw,testing$medv),"com padronização" = postResample(predictions_padr,testingPadr$medv))
##          sem padronização com padronização
## RMSE            6.2642194        4.9952720
## Rsquared        0.6828784        0.8406562
## MAE             4.1826263        3.2433221

1.2 Normalização

Considere \(n\) observações, \(x_1, \ldots, x_n\), de uma variável \(X\). Sejam \(x_{(1)} < \cdots < x_{(n)}\) as estatísticas de ordem de \(X\). Defina: \[x_{i}^{(norm)} = \frac{x_i - x_{(1)}}{x_{(1)} - x_{(n)}}.\] A transformação \(x_i^{(norm)}\) é chamada de normalização de \(x_i\). Note que \(x_1^{(norm)}, \ldots, x_n^{(norm)}\) fica no intervalo [0,1].

# Criando o pré-processamento de normalização
preProcNorm <- preProcess(X_train, method = "range")

# Aplicando o pré-processamento ao conjunto de treino
trainingNorm <- training
trainingNorm[,-14] <- predict(preProcNorm, X_train)
# Aplicando o pré-processamento ao conjunto de teste 
testingNorm <- testing
testingNorm[,-14] <- predict(preProcNorm, X_test)
# Treinando um modelo knnRegressor dados brutos
set.seed(12345)
model_knn_raw <-  train(medv ~ .,
                       data = training,
                       method = "knn",
                       trControl = ctrl)
model_knn_raw
## k-Nearest Neighbors 
## 
## 407 samples
##  13 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 366, 366, 367, 366, 366, 365, ... 
## Resampling results across tuning parameters:
## 
##   k  RMSE      Rsquared   MAE     
##   5  6.369323  0.4928874  4.454254
##   7  6.479205  0.4698312  4.523299
##   9  6.541589  0.4589821  4.542649
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was k = 5.
# Avaliando o desempenho nos dados de teste
predictions_raw <- predict(model_knn_raw,testing)
postResample(predictions_raw,testing$medv)
##      RMSE  Rsquared       MAE 
## 6.2642194 0.6828784 4.1826263
# Treinando um modelo knnRegressor dados normalizados
set.seed(12345)
model_knn_norm <-  train(medv ~ .,
                       data = trainingNorm,
                       method = "knn",
                       trControl = ctrl)
model_knn_norm
## k-Nearest Neighbors 
## 
## 407 samples
##  13 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 366, 366, 367, 366, 366, 365, ... 
## Resampling results across tuning parameters:
## 
##   k  RMSE      Rsquared   MAE     
##   5  5.220675  0.6656998  3.259323
##   7  5.365978  0.6485708  3.395420
##   9  5.248956  0.6677875  3.357613
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was k = 5.
# Avaliando o desempenho nos dados de teste
predictions_norm <- predict(model_knn_norm,testingNorm)
cbind("sem normalização"=postResample(predictions_raw,testing$medv),"com normalização"=postResample(predictions_norm,testingNorm$medv))
##          sem normalização com normalização
## RMSE            6.2642194        4.8413826
## Rsquared        0.6828784        0.8331679
## MAE             4.1826263        3.1064646

Como pode-se observar, tivemos uma melhora no desempenho do modelo ao padronizar e normalizar os dados.

Transformações de potências nas variáveis

Muitos modelos de AM apresentam um melhor desempenho quando os dados têm uma distribuição normal ou são simétricos. A continuação, apresentaremos outros tipos de normalização (ou transformação) de variáveis que visam deixar os dados mais “parecidos” com a distribuição normal ou mais simétricos.

1.2.1 Transformação de Box-Cox

Box e Cox consideraram uma família de transformações de potências para variáveis unidimensionais: \[x^{(\lambda)} = \begin{cases} \frac{x^\lambda - 1}{\lambda} & \lambda \neq 0 \\ \ln(x) & \lambda = 0\end{cases}\]

que são contínuas em \(\lambda\) para \(x > 0\).

  • Transformação de Box-Cox usando a função caret::preProcess()
# Criar o pré-processamento de Box-Cox 
preProcBoxCox <- preProcess(X_train, method = "BoxCox")

# Aplicando o pré-processanto de Box-COx ao conjunto de treino
trainingBoxCox <- training
trainingBoxCox[,-14] <- predict(preProcBoxCox, X_train)
# Aplicando o pré-processanto de Box-COx ao conjunto de teste
testingBoxCox <- testing
testingBoxCox[,-14] <- predict(preProcBoxCox, X_test)
# Treinando um modelo lm aos dados brutos
set.seed(12345)
model_lm_raw <- train(medv ~ .,
                       data = training,
                       method = "lm",
                       trControl = ctrl)
model_lm_raw
## Linear Regression 
## 
## 407 samples
##  13 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 366, 366, 367, 366, 366, 365, ... 
## Resampling results:
## 
##   RMSE      Rsquared   MAE    
##   4.929817  0.6976078  3.44944
## 
## Tuning parameter 'intercept' was held constant at a value of TRUE
# Avaliando o desempenho nos dados de teste
predictions_raw <- predict(model_lm_raw, testing)
postResample(predictions_raw,testing$medv)
##      RMSE  Rsquared       MAE 
## 4.4226051 0.8330442 3.3215093
# Treinando um modelo lm aos dados transformados BoxCox
set.seed(12345)
model_lm_BoxCox <- train(medv ~ .,
                       data = trainingBoxCox,
                       method = "lm",
                       trControl = ctrl)
model_lm_BoxCox
## Linear Regression 
## 
## 407 samples
##  13 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 366, 366, 367, 366, 366, 365, ... 
## Resampling results:
## 
##   RMSE      Rsquared   MAE     
##   4.413004  0.7533667  3.223896
## 
## Tuning parameter 'intercept' was held constant at a value of TRUE
# Avaliando o desempenho nos dados de teste
predictions_BoxCox <- predict(model_lm_BoxCox, testingBoxCox)
# Comparando o desempenho
cbind("sem BoxCox" = postResample(predictions_raw,testing$medv),"com BoxCox"=postResample(predictions_BoxCox, testingBoxCox$medv))
##          sem BoxCox com BoxCox
## RMSE      4.4226051  4.3131466
## Rsquared  0.8330442  0.8378512
## MAE       3.3215093  3.3032565

Como pode-se observar, obtivemos uma leve melhora no desempenho do modelo após aplicar a transformação de Box-Cox.

1.2.2 Transformação de Yeo-Johnson

\[x^{(\lambda)} = \begin{cases} \frac{(x + 1)^\lambda - 1}{\lambda}, & \text{se } x \ge 0, \lambda \ne 0 \\ \log(x + 1), & \text{se } x \ge 0, \lambda = 0 \\ -\frac{(-x + 1)^{2 - \lambda} - 1}{2 - \lambda}, & \text{se } x < 0, \lambda \ne 2 \\ -\log(-x + 1), & \text{se } x < 0, \lambda = 2 \end{cases}\]

  • Transformação de Yeo-Johnson usando a função caret::preProcess()
# Criar o pré-processamento de Yeo-Johnson 
preProcYeoJohnson <- preProcess(X_train, method = "YeoJohnson")

# Aplicando o pré-processanto de Yeo-Johnson ao conjunto de treino
trainingYeoJohnson <- training
trainingYeoJohnson[,-14] <- predict(preProcYeoJohnson, X_train)
# Aplicando o pré-processanto de YeoJohnson ao conjunto de teste
testingYeoJohnson <- testing
testingYeoJohnson[,-14] <- predict(preProcYeoJohnson, X_test)
# Treinando um modelo lm aos dados transformados YeoJohnson
set.seed(12345)
model_lm_YeoJohnson <- train(medv ~ .,
                       data = trainingYeoJohnson,
                       method = "lm",
                       trControl = ctrl)
model_lm_YeoJohnson
## Linear Regression 
## 
## 407 samples
##  13 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 366, 366, 367, 366, 366, 365, ... 
## Resampling results:
## 
##   RMSE      Rsquared   MAE     
##   4.350426  0.7609648  3.173235
## 
## Tuning parameter 'intercept' was held constant at a value of TRUE
# Avaliando o desempenho nos dados de teste
predictions_YeoJohnson <- predict(model_lm_YeoJohnson, testingYeoJohnson)
# Comparando o desempenho
cbind("sem YeoJohnson" = postResample(predictions_raw,testing$medv),"com YeoJohnson"=postResample(predictions_YeoJohnson, testingYeoJohnson$medv))
##          sem YeoJohnson com YeoJohnson
## RMSE          4.4226051      4.2678657
## Rsquared      0.8330442      0.8418768
## MAE           3.3215093      3.2517371

Como pode-se observar, obtivemos uma leve melhora no desempenho do modelo após aplicar a transformação de Yeo-Johnson

1.2.3 Transformação exponencial de Manley

\[x^{(\lambda)} = \begin{cases} \frac{e^{\lambda x} - 1}{\lambda}, & \text{se } \lambda \ne 0 \\ x, & \text{se } \lambda = 0 \end{cases}\]

Observação. No caso multivariado \([X_1, \ldots, X_p]^\top\) devemos escolher um valor \(\lambda_1,\ldots ,\lambda_p\) para cada uma das \(p\) variáveis.

  • Transformação exponencial de Manley usando a função caret::preProcess()
# Criar o pré-processamento de ExpoManley 
preProcExpoManley <- preProcess(X_train, method = "expoTrans")

# Aplicando o pré-processanto de ExpoManley ao conjunto de treino
trainingExpoManley <- training
trainingExpoManley[,-14] <- predict(preProcExpoManley, X_train)
# Aplicando o pré-processanto de ExpoManley ao conjunto de teste
testingExpoManley <- testing
testingExpoManley[,-14] <- predict(preProcExpoManley, X_test)
# Treinando um modelo lm aos dados transformados YeoJohnson
set.seed(12345)
model_lm_ExpoManley <- train(medv ~ .,
                       data = trainingExpoManley,
                       method = "lm",
                       trControl = ctrl)
model_lm_ExpoManley
## Linear Regression 
## 
## 407 samples
##  13 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 366, 366, 367, 366, 366, 365, ... 
## Resampling results:
## 
##   RMSE      Rsquared   MAE     
##   4.574783  0.7358235  3.392041
## 
## Tuning parameter 'intercept' was held constant at a value of TRUE
# Avaliando o desempenho nos dados de teste
predictions_ExpoManley <- predict(model_lm_ExpoManley, testingExpoManley)
# Comparando o desempenho
cbind("sem ExpoManley" = postResample(predictions_raw,testing$medv),"com ExpoManley"=postResample(predictions_ExpoManley, testingExpoManley$medv))
##          sem ExpoManley com ExpoManley
## RMSE          4.4226051      4.6124749
## Rsquared      0.8330442      0.8172854
## MAE           3.3215093      3.5676237

Como pode-se observar, obtivemos uma leve piora no desempenho do modelo após aplicar a transformação exponencial de Manley. Isso mostra que nem sempre aplicar uma transformação nos dados trará um benefício no desempenho do modelo.

A seguir, resumimos o desempenho do modelo em relação às tranformações aplicadas:

# Comparação entre transformações 
cbind("sem transformação" = postResample(predictions_raw,testing$medv),
      "com BoxCox" = postResample(predictions_BoxCox, testingBoxCox$medv),
      "com YeoJohnson" = postResample(predictions_YeoJohnson, testingYeoJohnson$medv),
      "com ExpoManley" = postResample(predictions_ExpoManley, testingExpoManley$medv))
##          sem transformação com BoxCox com YeoJohnson com ExpoManley
## RMSE             4.4226051  4.3131466      4.2678657      4.6124749
## Rsquared         0.8330442  0.8378512      0.8418768      0.8172854
## MAE              3.3215093  3.3032565      3.2517371      3.5676237

2 Remover ou imputar dados faltantes

Muitos métodos de AM não funcionam com características faltantes; sendo assim podemos optar por três opções:

  1. Remover as unidades de investigação com dados faltantes (linhas da matriz de preditoras).
  2. Remover as variáveis com dados faltantes (colunas da matriz de preditoras).
  3. Definir os valores para algum valor (zero, média, mediana, moda, etc.)

2.1 Dados faltantes para a variável alvo

Caso encontremos um dado faltante na variável alvo, a linha correspondente deve ser removida, uma vez que não podemos imputar valores para ela.

2.2 Dados faltantes para as preditoras

Quando temos dados faltantes nas preditoras, podemos remover as linhas onde encontrarmos uma observação ausente, ou imputar o valor faltante.

Mostraremos como fazer isso com o banco de dados mammalsleep do pacote mice.

# carregando pacotes
library(caret)
library(tidyverse)
library(mice)
library(naniar)
# carregar o banco de dados sleep
data(mammalsleep, package = "mice")
gg_miss_var(mammalsleep)

#vis_miss(mammalsleep)
miss_var_summary(mammalsleep)
## # A tibble: 11 × 3
##    variable n_miss pct_miss
##    <chr>     <int>    <num>
##  1 sws          14    22.6 
##  2 ps           12    19.4 
##  3 ts            4     6.45
##  4 mls           4     6.45
##  5 gt            4     6.45
##  6 species       0     0   
##  7 bw            0     0   
##  8 brw           0     0   
##  9 pi            0     0   
## 10 sei           0     0   
## 11 odi           0     0
N <- dim(mammalsleep)[1]
set.seed(12345)
index_train <- createDataPartition(1:N, p = 0.8)[[1]] 
training <- mammalsleep[index_train,]
testing <- mammalsleep[-index_train,]
# dados ausentes por linha no conjunto treino
miss_case_summary(training)
## # A tibble: 50 × 3
##     case n_miss pct_miss
##    <int>  <int>    <dbl>
##  1    25      3     27.3
##  2    50      3     27.3
##  3     1      2     18.2
##  4     3      2     18.2
##  5    10      2     18.2
##  6    11      2     18.2
##  7    18      2     18.2
##  8    21      2     18.2
##  9    34      2     18.2
## 10    38      2     18.2
## # ℹ 40 more rows
vis_miss(training)

# dados ausentes por linha no conjunto teste
miss_case_summary(testing)
## # A tibble: 12 × 3
##     case n_miss pct_miss
##    <int>  <int>    <dbl>
##  1     1      3     27.3
##  2     5      2     18.2
##  3     6      2     18.2
##  4    12      2     18.2
##  5     2      0      0  
##  6     3      0      0  
##  7     4      0      0  
##  8     7      0      0  
##  9     8      0      0  
## 10     9      0      0  
## 11    10      0      0  
## 12    11      0      0
vis_miss(testing)

Removendo as linhas com dados faltantes

# Removendo os NAs
training_sem_na <- na.omit(training)
testing_sem_na <- na.omit(testing)
miss_case_summary(training_sem_na)
## # A tibble: 34 × 3
##     case n_miss pct_miss
##    <int>  <int>    <dbl>
##  1     1      0        0
##  2     2      0        0
##  3     3      0        0
##  4     4      0        0
##  5     5      0        0
##  6     6      0        0
##  7     7      0        0
##  8     8      0        0
##  9     9      0        0
## 10    10      0        0
## # ℹ 24 more rows
miss_case_summary(testing_sem_na)
## # A tibble: 8 × 3
##    case n_miss pct_miss
##   <int>  <int>    <dbl>
## 1     1      0        0
## 2     2      0        0
## 3     3      0        0
## 4     4      0        0
## 5     5      0        0
## 6     6      0        0
## 7     7      0        0
## 8     8      0        0

Imputando os valores ausentes pela mediana

# Criando a imputação dos dados pela mediana
imputed <- preProcess(training, method = "medianImpute")
# Aplicando a imputação aos dados de treino
trainingPreprocessed <- predict(imputed, training)
# Aplicando a imputação aos dados de teste
testingPreprocessed <- predict(imputed,testing)

miss_var_summary(trainingPreprocessed)
## # A tibble: 11 × 3
##    variable n_miss pct_miss
##    <chr>     <int>    <num>
##  1 species       0        0
##  2 bw            0        0
##  3 brw           0        0
##  4 sws           0        0
##  5 ps            0        0
##  6 ts            0        0
##  7 mls           0        0
##  8 gt            0        0
##  9 pi            0        0
## 10 sei           0        0
## 11 odi           0        0
miss_var_summary(testingPreprocessed)
## # A tibble: 11 × 3
##    variable n_miss pct_miss
##    <chr>     <int>    <num>
##  1 species       0        0
##  2 bw            0        0
##  3 brw           0        0
##  4 sws           0        0
##  5 ps            0        0
##  6 ts            0        0
##  7 mls           0        0
##  8 gt            0        0
##  9 pi            0        0
## 10 sei           0        0
## 11 odi           0        0

2.3 Modelo dos K-vizinhos mais próximos (K-nearest neighbor, KNN)

De maneira geral, o modelo dos K-vizinhos mais próximos é um método que permite estimar a distribuição condicional de uma variável resposta \(Y \in \{1,\ldots,C\}\) dados os valores das variáveis preditoras \(\mathbf{X} = [\mathbf{x}^{(1)} \cdots \mathbf{x}^{(p)}]\), em que \(\mathbf{x}^{(j)} = [x_{1j}, \ldots, x_{nj}]^\top\), \(j = 1, \ldots, p\).

O algoritmo associado ao modelo é:

  1. fixe \(K > 0\) e uma observação do conjunto de dados de teste, \(\mathbf{x}_0\);
  2. identifique \(K\) pontos do conjunto de dados de treinamento que sejam os mais próximos segundo alguma medida de distância (tipicamente, a distância euclidiana); denote esse conjunto por \(V_0\);
  3. estime a probabilidade condicional de que o elemento selecionado do conjunto de dados pertença à classe \(C_j\) como a proporção dos pontos de \(V_0\) cujos valores de \(Y\) sejam iguais a \(j\), ou seja, \[P(Y = j\mid \mathbf{X}= \mathbf{x}_0) = \frac{1}{K} \sum_{i \in V_0} I(y_i = j);\]
  4. classifique esse elemento com valor \(\mathbf{x}_0\) na classe associada à maior probabilidade.

2.3.1 KNN como método para imputação de dados

Quando queremos usar o KNN para imputação de dados ausentes, substituimos o valor faltante pela média de seus K-vizinhos mais próximos. Mais precisamente, cada valor ausente de uma variável é imputado pela média dos valores das variáveis dos K-vizinhos mais próximos que possuem um valor dessa variável.

dados.X <- c(1,2,NA,3,4,3,NA,6,5,8,8,7)
X <- matrix(dados.X, 4, 3,byrow = TRUE)
X
##      [,1] [,2] [,3]
## [1,]    1    2   NA
## [2,]    3    4    3
## [3,]   NA    6    5
## [4,]    8    8    7
library(vegan)
vegan::vegdist(X, method = "euclidean", na.rm = TRUE)
##          1        2        3
## 2 2.828427                  
## 3 4.000000 2.828427         
## 4 9.219544 7.549834 2.828427

Se fixamos, por exemplo \(K=2\), temos que os dois vizinhos mais próximos do primeiro individuo são o segunto e terceiro, já do terceiro são o segundo e o quarto.

Assim, imputariamos os valores faltantes como segue:

\[\begin{align} X[1,3] &= \frac{3+5}{2} = 4;\\ X[3,1] &= \frac{3+8}{2} = 5,5. \end{align}\]

# Matriz  com os dados imputados via KNN
X.imputed <- X
X.imputed[1,3] <- 4
X.imputed[3,1] <- 5.5
X.imputed
##      [,1] [,2] [,3]
## [1,]  1.0    2    4
## [2,]  3.0    4    3
## [3,]  5.5    6    5
## [4,]  8.0    8    7

Podemos usar também, a função caret::preProcess(), no entanto, ela só funciona para variáveis numéricas. Se quisermos usar para variáveis categóricas precisamos de uma função alternativa, falaremos disso mais tarde. A seguir mostramos como proceder:

library(caret)
X_df <- as.data.frame(X)
colnames(X_df) <- c("X1","X2","X3")
# Criar a imputação via knn (com k=2)
imputed <- preProcess(X_df, method = "knnImpute", k = 2)
# Aplicar a imputaçao via knn (com k=2)
X_imputed <- predict(imputed,X_df)
# Por defeito knnImpute padroniza os dados
X_imputed
##           X1         X2 X3
## 1 -0.8320503 -1.1618950  0
## 2 -0.2773501 -0.3872983 -1
## 3  0.4160251  0.3872983  0
## 4  1.1094004  1.1618950  1
# Desfazendo a padronização
X_imputed <- sweep(X_imputed,2,imputed$std, FUN = "*")
X_imputed <- sweep(X_imputed,2,imputed$mean, FUN = "+")
X_imputed
##    X1 X2 X3
## 1 1.0  2  5
## 2 3.0  4  3
## 3 5.5  6  5
## 4 8.0  8  7

Voltando aos dados mammalsleep a imputação pelo método knn é como segue:

# Criar a imputação via knn (com k = 5 é o valor por defeito)
imputedKnn <- preProcess(training, method = "knnImpute")
# Aplicar a imputaçao via knn aos dados de treino  
trainingImputedKnn <- predict(imputedKnn, training)
# Aplicar a imputaçao via knn aos dados de teste
testingImputedKnn <- predict(imputedKnn, testing)

miss_var_summary(trainingImputedKnn)
## # A tibble: 11 × 3
##    variable n_miss pct_miss
##    <chr>     <int>    <num>
##  1 species       0        0
##  2 bw            0        0
##  3 brw           0        0
##  4 sws           0        0
##  5 ps            0        0
##  6 ts            0        0
##  7 mls           0        0
##  8 gt            0        0
##  9 pi            0        0
## 10 sei           0        0
## 11 odi           0        0
miss_var_summary(testingImputedKnn)
## # A tibble: 11 × 3
##    variable n_miss pct_miss
##    <chr>     <int>    <num>
##  1 species       0        0
##  2 bw            0        0
##  3 brw           0        0
##  4 sws           0        0
##  5 ps            0        0
##  6 ts            0        0
##  7 mls           0        0
##  8 gt            0        0
##  9 pi            0        0
## 10 sei           0        0
## 11 odi           0        0

Observação. Os métodos de imputação de dados aceitos pela função preProcess só lidam com dados numéricos, caso o nosso conjunto de dados possua uma variável categórica com dados faltantes, precisamos de uma alternativa.

Uma alternativa para imputação de dados categóricos é, por exemplo, substituir o valor faltante pela moda. Para mostrar isso, usaremos as funções recipe e bake do pacote recipes e aplicaremos nos dados mlbench::Soybean que contém várias variáveis categóricas.

library(mlbench)
library(recipes)
data("Soybean")
glimpse(Soybean)
## Rows: 683
## Columns: 36
## $ Class           <fct> diaporthe-stem-canker, diaporthe-stem-canker, diaporth…
## $ date            <fct> 6, 4, 3, 3, 6, 5, 5, 4, 6, 4, 6, 4, 3, 6, 6, 5, 6, 4, …
## $ plant.stand     <ord> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ precip          <ord> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ temp            <ord> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 1, …
## $ hail            <fct> 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, …
## $ crop.hist       <fct> 1, 2, 1, 1, 2, 3, 2, 1, 3, 2, 1, 1, 1, 3, 1, 3, 0, 2, …
## $ area.dam        <fct> 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 2, 3, 3, 3, 2, 2, …
## $ sever           <fct> 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ seed.tmt        <fct> 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, …
## $ germ            <ord> 0, 1, 2, 1, 2, 1, 0, 2, 1, 2, 0, 1, 0, 0, 1, 2, 0, 1, …
## $ plant.growth    <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ leaves          <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ leaf.halo       <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ leaf.marg       <fct> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
## $ leaf.size       <ord> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
## $ leaf.shread     <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ leaf.malf       <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ leaf.mild       <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ stem            <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ lodging         <fct> 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, …
## $ stem.cankers    <fct> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ canker.lesion   <fct> 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, …
## $ fruiting.bodies <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ ext.decay       <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ mycelium        <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ int.discolor    <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, …
## $ sclerotia       <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ fruit.pods      <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ fruit.spots     <fct> 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, …
## $ seed            <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ mold.growth     <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ seed.discolor   <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ seed.size       <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ shriveling      <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ roots           <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
# Verificando os dados faltantes
miss_var_summary(Soybean)
## # A tibble: 36 × 3
##    variable        n_miss pct_miss
##    <chr>            <int>    <num>
##  1 hail               121     17.7
##  2 sever              121     17.7
##  3 seed.tmt           121     17.7
##  4 lodging            121     17.7
##  5 germ               112     16.4
##  6 leaf.mild          108     15.8
##  7 fruiting.bodies    106     15.5
##  8 fruit.spots        106     15.5
##  9 seed.discolor      106     15.5
## 10 shriveling         106     15.5
## # ℹ 26 more rows
vis_miss(Soybean)

set.seed(12345)
index_train <- createDataPartition(y = Soybean$Class, p = 0.8)[[1]]
training <- Soybean[index_train,]
testing <- Soybean[index_train,]
# Criar a receita (pré-processamento) de imutação pela moda
rec <- recipe(Class ~., data = training) %>%
  step_impute_mode(all_nominal_predictors()) %>%
  prep()
# Aplicar aos dados de treino
trainingImputedModa <- bake(rec, new_data=training)
testingImputedModa <- bake(rec, new_data=testing)
miss_var_summary(trainingImputedModa)
## # A tibble: 36 × 3
##    variable    n_miss pct_miss
##    <chr>        <int>    <num>
##  1 date             0        0
##  2 plant.stand      0        0
##  3 precip           0        0
##  4 temp             0        0
##  5 hail             0        0
##  6 crop.hist        0        0
##  7 area.dam         0        0
##  8 sever            0        0
##  9 seed.tmt         0        0
## 10 germ             0        0
## # ℹ 26 more rows
miss_var_summary(testingImputedModa)
## # A tibble: 36 × 3
##    variable    n_miss pct_miss
##    <chr>        <int>    <num>
##  1 date             0        0
##  2 plant.stand      0        0
##  3 precip           0        0
##  4 temp             0        0
##  5 hail             0        0
##  6 crop.hist        0        0
##  7 area.dam         0        0
##  8 sever            0        0
##  9 seed.tmt         0        0
## 10 germ             0        0
## # ℹ 26 more rows

Observação. Outras alternativas encontram-se no pacote mlr que possui diversos métodos para imputação de dados. Para mais detalhes consulte a documentação do pacote.