Nesta aula abordaremos
como transformar as variáveis quantitativas, particularmente, veremos técnicas de padronização e normalização das variáveis.
Também veremos como tratar dados faltantes.
Objetivo: Aplicar transformações nas variáveis com o intuito de deixá-las mais apropriadas para o modelo de AM.
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
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.
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\).
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.
\[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}\]
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
\[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.
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
Muitos métodos de AM não funcionam com características faltantes; sendo assim podemos optar por três opções:
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.
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
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 é:
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.