A qualidade dos dados e a quantidade de informação útil que contém são fatores chaves que determinam o bem que pode aprender um modelo de aprendizado de máquina. Portanto, é imprescindível ter certeza de examinar e pré-processar um conjunto de dados antes de inserí-lo em um algoritmo de aprendizado.
Trataremos, técnicas fundamentais para o pré-processamento dos dados, que nos ajudarão a gerar bons modelos de aprendizado de máquina.
Os objetivos que abordaremos nesta fase de pré-processamento são:
Remover variáveis que não ajudam na predição:
Transformar as variáveis quantitativas
Remover ou imputar dados faltantes.
Codificar variáveis qualitativas e reduzir a dimensionalidade.
Objetivo: Remover variáveis com um único valor (variância zero) ou com uma frequência muito alta de um único valor (variância quase zero).
Para identificar variáveis preditoras com variância quase-zero,
utilizamos a função nearZeroVar() do pacote
caret. A função nearZeroVar() calcula as
seguintes duas métricas:
Critério de decisão nzv :
\[\text{Razão de Frequência} = \frac{\text{Frequência do valor mais comum}}{\text{Frequência do segundo valor mais comum}} > \text{freqCut}\] e
\[\text{Percentual de Valores Únicos} = \left( \frac{\text{número de valores distintos }}{\text{tamanho da amostra}}\right) \times 100 < \text{uniqueCut}.\] Então, a variável é classificada como nvz.
A ideia é que valores altos da razão de frequências indicam desbalanceamento extremo e valores muito baixos do percentual de valores únicos indicam falta de variabilidade.
Os pontos de corte por defeito da função nearZeroVar()
são: freqCut = 95/5 e uniqueCut = 10.
Para ilustração das técnicas de pré-processamento usaremos o
wdbc dataset.
# Carregando librarias
library(tidyverse)
library(caret)
# Link para o banco de dados
url <- "https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data"
# Lendo o banco de dados diretamente do link
wdbc <- read_csv(url, col_names = FALSE)
# Removendo a coluna de ID
wdbc <- wdbc[,-1]
# Adicionando nomes das colunas (são 31 no total)
features <- c("Diagnosis",
paste0(rep(c("radius", "texture", "perimeter", "area", "smoothness",
"compactness", "concavity", "concave_points", "symmetry", "fractal_dimension"), 3),
"_", rep(c("mean", "se", "worst"), each = 10)))
colnames(wdbc) <- features
# Substituir zeros por 0.001 para evitar problemas com log
wdbc[wdbc == 0] <- 0.001
# Transformando para log as variáveis preditoras
wdbc_log <- data.frame(Diagnosis = as.factor(wdbc$Diagnosis),
apply(wdbc[,-1],2,log))
glimpse(wdbc_log)
## Rows: 569
## Columns: 31
## $ Diagnosis <fct> M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M…
## $ radius_mean <dbl> 2.889816, 3.023834, 2.980111, 2.435366, 3.0101…
## $ texture_mean <dbl> 2.339881, 2.877512, 3.056357, 3.014554, 2.6630…
## $ perimeter_mean <dbl> 4.810557, 4.889597, 4.867534, 4.351310, 4.9060…
## $ area_mean <dbl> 6.908755, 7.189922, 7.092574, 5.956096, 7.1678…
## $ smoothness_mean <dbl> -2.133687, -2.468168, -2.210918, -1.948413, -2…
## $ compactness_mean <dbl> -1.281574, -2.542875, -1.833207, -1.259133, -2…
## $ concavity_mean <dbl> -1.203640, -2.442997, -1.622523, -1.421300, -1…
## $ concave_points_mean <dbl> -1.916643, -2.656834, -2.056507, -2.251892, -2…
## $ symmetry_mean <dbl> -1.419231, -1.708154, -1.575520, -1.348228, -1…
## $ fractal_dimension_mean <dbl> -2.541985, -2.870510, -2.813577, -2.328518, -2…
## $ radius_se <dbl> 0.09075436, -0.60972557, -0.29356602, -0.70198…
## $ texture_se <dbl> -0.099488899, -0.309382499, -0.239654103, 0.14…
## $ perimeter_se <dbl> 2.1504823, 1.2231870, 1.5227901, 1.2369239, 1.…
## $ area_se <dbl> 5.033049, 4.305146, 4.543614, 3.304319, 4.5479…
## $ smoothness_se <dbl> -5.051614, -5.254300, -5.091303, -4.698383, -4…
## $ compactness_se <dbl> -3.015119, -4.336671, -3.217377, -2.595883, -3…
## $ concavity_se <dbl> -2.923784, -3.984594, -3.261783, -2.871570, -2…
## $ concave_points_se <dbl> -4.143325, -4.312501, -3.883436, -3.980837, -3…
## $ symmetry_se <dbl> -3.505558, -4.276586, -3.794240, -2.819596, -4…
## $ fractal_dimension_se <dbl> -5.084336, -5.645891, -5.388023, -4.687683, -5…
## $ radius_worst <dbl> 3.233961, 3.218476, 3.159975, 2.702032, 3.1152…
## $ texture_worst <dbl> 2.852439, 3.153163, 3.239854, 3.277145, 2.8136…
## $ perimeter_worst <dbl> 5.218191, 5.067646, 5.027165, 4.593806, 5.0251…
## $ area_worst <dbl> 7.610358, 7.578657, 7.443664, 6.341593, 7.3620…
## $ smoothness_worst <dbl> -1.818925, -2.089088, -1.935168, -1.561601, -1…
## $ compactness_worst <dbl> -0.40706639, -1.67878799, -0.85684327, -0.1435…
## $ concavity_worst <dbl> -0.33981783, -1.42047181, -0.79761920, -0.3755…
## $ concave_points_worst <dbl> -1.326517, -1.682009, -1.414694, -1.356736, -1…
## $ symmetry_worst <dbl> -0.7763114, -1.2909842, -1.0180466, -0.4097744…
## $ fractal_dimension_worst <dbl> -2.129472, -2.418894, -2.435203, -1.754464, -2…
# Examinando preditoras com variância zero ou quase-zero
X <- wdbc_log[,-1]
nearZeroVar(X, saveMetrics = TRUE)
## freqRatio percentUnique zeroVar nzv
## radius_mean 1.333333 80.14060 FALSE FALSE
## texture_mean 1.000000 84.18278 FALSE FALSE
## perimeter_mean 1.000000 91.73989 FALSE FALSE
## area_mean 1.500000 94.72759 FALSE FALSE
## smoothness_mean 1.250000 83.30404 FALSE FALSE
## compactness_mean 1.000000 94.37610 FALSE FALSE
## concavity_mean 4.333333 94.37610 FALSE FALSE
## concave_points_mean 4.333333 95.25483 FALSE FALSE
## symmetry_mean 1.000000 75.92267 FALSE FALSE
## fractal_dimension_mean 1.000000 87.69772 FALSE FALSE
## radius_se 1.000000 94.90334 FALSE FALSE
## texture_se 1.000000 91.21265 FALSE FALSE
## perimeter_se 2.000000 93.67311 FALSE FALSE
## area_se 1.000000 92.79438 FALSE FALSE
## smoothness_se 1.000000 96.13357 FALSE FALSE
## compactness_se 1.000000 95.07909 FALSE FALSE
## concavity_se 6.500000 93.67311 FALSE FALSE
## concave_points_se 4.333333 89.10369 FALSE FALSE
## symmetry_se 1.333333 87.52197 FALSE FALSE
## fractal_dimension_se 1.000000 95.78207 FALSE FALSE
## radius_worst 1.250000 80.31634 FALSE FALSE
## texture_worst 1.000000 89.80668 FALSE FALSE
## perimeter_worst 1.000000 90.33392 FALSE FALSE
## area_worst 1.000000 95.60633 FALSE FALSE
## smoothness_worst 1.000000 72.23199 FALSE FALSE
## compactness_worst 1.000000 92.97012 FALSE FALSE
## concavity_worst 4.333333 94.72759 FALSE FALSE
## concave_points_worst 4.333333 86.46749 FALSE FALSE
## symmetry_worst 1.000000 87.87346 FALSE FALSE
## fractal_dimension_worst 1.500000 94.02460 FALSE FALSE
Neste caso, não temos nenhuma variável com variância zero o quase-zero, então não há necessidade de remoção.
Para alterar os pontos de corte podemos usar a função
nearZeroVar() com as opções freqCut e
uniqueCut como segue:
# "nonsense example" alterar freqCut = 1 e uniqueCut = 85
nearZeroVar(X,
saveMetrics = TRUE,
names = TRUE,
freqCut = 1,
uniqueCut = 85)
## freqRatio percentUnique zeroVar nzv
## radius_mean 1.333333 80.14060 FALSE TRUE
## texture_mean 1.000000 84.18278 FALSE FALSE
## perimeter_mean 1.000000 91.73989 FALSE FALSE
## area_mean 1.500000 94.72759 FALSE FALSE
## smoothness_mean 1.250000 83.30404 FALSE TRUE
## compactness_mean 1.000000 94.37610 FALSE FALSE
## concavity_mean 4.333333 94.37610 FALSE FALSE
## concave_points_mean 4.333333 95.25483 FALSE FALSE
## symmetry_mean 1.000000 75.92267 FALSE FALSE
## fractal_dimension_mean 1.000000 87.69772 FALSE FALSE
## radius_se 1.000000 94.90334 FALSE FALSE
## texture_se 1.000000 91.21265 FALSE FALSE
## perimeter_se 2.000000 93.67311 FALSE FALSE
## area_se 1.000000 92.79438 FALSE FALSE
## smoothness_se 1.000000 96.13357 FALSE FALSE
## compactness_se 1.000000 95.07909 FALSE FALSE
## concavity_se 6.500000 93.67311 FALSE FALSE
## concave_points_se 4.333333 89.10369 FALSE FALSE
## symmetry_se 1.333333 87.52197 FALSE FALSE
## fractal_dimension_se 1.000000 95.78207 FALSE FALSE
## radius_worst 1.250000 80.31634 FALSE TRUE
## texture_worst 1.000000 89.80668 FALSE FALSE
## perimeter_worst 1.000000 90.33392 FALSE FALSE
## area_worst 1.000000 95.60633 FALSE FALSE
## smoothness_worst 1.000000 72.23199 FALSE FALSE
## compactness_worst 1.000000 92.97012 FALSE FALSE
## concavity_worst 4.333333 94.72759 FALSE FALSE
## concave_points_worst 4.333333 86.46749 FALSE FALSE
## symmetry_worst 1.000000 87.87346 FALSE FALSE
## fractal_dimension_worst 1.500000 94.02460 FALSE FALSE
Se quisessemos remover as variáveis “nzv” podemos proceder como segue:
# ATENÇÃO:
# caso SOMENTE usado para ilustrar o
# funcionamento da função nearZeroVar com
# limiares alterados
nzv <- nearZeroVar(X,
saveMetrics = FALSE,
freqCut = 1,
uniqueCut = 85)
nzv
## [1] 1 5 21
X_sem_nvc <- X[, -nzv]
glimpse(X_sem_nvc)
## Rows: 569
## Columns: 27
## $ texture_mean <dbl> 2.339881, 2.877512, 3.056357, 3.014554, 2.6630…
## $ perimeter_mean <dbl> 4.810557, 4.889597, 4.867534, 4.351310, 4.9060…
## $ area_mean <dbl> 6.908755, 7.189922, 7.092574, 5.956096, 7.1678…
## $ compactness_mean <dbl> -1.281574, -2.542875, -1.833207, -1.259133, -2…
## $ concavity_mean <dbl> -1.203640, -2.442997, -1.622523, -1.421300, -1…
## $ concave_points_mean <dbl> -1.916643, -2.656834, -2.056507, -2.251892, -2…
## $ symmetry_mean <dbl> -1.419231, -1.708154, -1.575520, -1.348228, -1…
## $ fractal_dimension_mean <dbl> -2.541985, -2.870510, -2.813577, -2.328518, -2…
## $ radius_se <dbl> 0.09075436, -0.60972557, -0.29356602, -0.70198…
## $ texture_se <dbl> -0.099488899, -0.309382499, -0.239654103, 0.14…
## $ perimeter_se <dbl> 2.1504823, 1.2231870, 1.5227901, 1.2369239, 1.…
## $ area_se <dbl> 5.033049, 4.305146, 4.543614, 3.304319, 4.5479…
## $ smoothness_se <dbl> -5.051614, -5.254300, -5.091303, -4.698383, -4…
## $ compactness_se <dbl> -3.015119, -4.336671, -3.217377, -2.595883, -3…
## $ concavity_se <dbl> -2.923784, -3.984594, -3.261783, -2.871570, -2…
## $ concave_points_se <dbl> -4.143325, -4.312501, -3.883436, -3.980837, -3…
## $ symmetry_se <dbl> -3.505558, -4.276586, -3.794240, -2.819596, -4…
## $ fractal_dimension_se <dbl> -5.084336, -5.645891, -5.388023, -4.687683, -5…
## $ texture_worst <dbl> 2.852439, 3.153163, 3.239854, 3.277145, 2.8136…
## $ perimeter_worst <dbl> 5.218191, 5.067646, 5.027165, 4.593806, 5.0251…
## $ area_worst <dbl> 7.610358, 7.578657, 7.443664, 6.341593, 7.3620…
## $ smoothness_worst <dbl> -1.818925, -2.089088, -1.935168, -1.561601, -1…
## $ compactness_worst <dbl> -0.40706639, -1.67878799, -0.85684327, -0.1435…
## $ concavity_worst <dbl> -0.33981783, -1.42047181, -0.79761920, -0.3755…
## $ concave_points_worst <dbl> -1.326517, -1.682009, -1.414694, -1.356736, -1…
## $ symmetry_worst <dbl> -0.7763114, -1.2909842, -1.0180466, -0.4097744…
## $ fractal_dimension_worst <dbl> -2.129472, -2.418894, -2.435203, -1.754464, -2…
Objetivo: Remover variáveis preditoras com alta correlação.
Para avaliarmos a presença de variáveis preditoras com alta correlação podemos calcular a matriz de correlações \(R = (r_{ij})\), em que \(r_{ij} = Cor(X_i,X_j)\) é o coeficiente de correlação amostral entre \(X_i\) e \(X_j\).
# Matriz de correlação das preditoras
R_X <- cor(X)
summary(R_X[lower.tri(R_X)])
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -0.3645 0.1649 0.3635 0.3711 0.5808 0.9996
# Procurando por correlações quase-perfeitas
highCorr <- sum(abs(R_X[lower.tri(R_X)]) > 0.999)
highCorr
## [1] 2
findCorrelation(R_X, cutoff = 0.999, names = TRUE)
## [1] "radius_worst" "radius_mean"
Podemos observar que existem duas variáveis que estão
quase-perfeitamente correlacionadas, a saber: radius_worst
e radius_mean.
Vamos observar o efeito de remover preditoras com correlações absolutas acima de 75%.
# Preditoras altamente correlacionadas
highlyCorrPred <- findCorrelation(R_X, cutoff = 0.75)
# Eliminando da base as variáveis altamente correlacionadas
X2 <- X[,-highlyCorrPred]
colnames(X2)
## [1] "area_mean" "symmetry_mean" "fractal_dimension_mean"
## [4] "radius_se" "texture_se" "smoothness_se"
## [7] "symmetry_se" "fractal_dimension_se" "texture_worst"
## [10] "smoothness_worst" "symmetry_worst"
# Calculando matriz de correlações para os dados filtrados (sem pred. com alta correlação)
R_X2 <- cor(X2)
# Medidas resumo das correlações
summary(R_X2[lower.tri(R_X2)])
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -0.36448 0.07744 0.24420 0.22407 0.40629 0.71856
train()set.seed(12345)
index_train <- createDataPartition(y = wdbc_log$Diagnosis, p = 0.8)[[1]]
training <- wdbc_log[index_train,]
testing <- wdbc_log[-index_train,]
ctrl <- trainControl(preProcOptions = list(cutoff = 0.75))
model_svm <- train(Diagnosis ~ .,
data = training,
method = "svmLinear",
trControl = ctrl,
preProcess = c("nzv","corr"))
# Preditoras removidas com alta correlação
model_svm$preProcess$method$remove
## [1] "compactness_mean" "concave_points_mean"
## [3] "concavity_mean" "perimeter_worst"
## [5] "compactness_worst" "radius_worst"
## [7] "concavity_worst" "area_worst"
## [9] "concave_points_worst" "perimeter_mean"
## [11] "area_se" "compactness_se"
## [13] "concavity_se" "radius_mean"
## [15] "perimeter_se" "smoothness_mean"
## [17] "fractal_dimension_worst" "texture_mean"
# Aplicamos na amostra teste
predictions <- predict(model_svm,testing)
# Avaliando o desempenho
confusionMatrix(predictions, testing$Diagnosis, positive = "M")
## Confusion Matrix and Statistics
##
## Reference
## Prediction B M
## B 68 1
## M 3 41
##
## Accuracy : 0.9646
## 95% CI : (0.9118, 0.9903)
## No Information Rate : 0.6283
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.9249
##
## Mcnemar's Test P-Value : 0.6171
##
## Sensitivity : 0.9762
## Specificity : 0.9577
## Pos Pred Value : 0.9318
## Neg Pred Value : 0.9855
## Prevalence : 0.3717
## Detection Rate : 0.3628
## Detection Prevalence : 0.3894
## Balanced Accuracy : 0.9670
##
## 'Positive' Class : M
##
Objetivo: Remover variáveis preditoras linearmente dependentes.
Para ilustração considere a seguinte matriz de delineamento de um experimento com posto incompleto.
# Matriz de delineamento de posto incompleto
m.Design <- matrix(0, nrow=6, ncol=6)
m.Design[,1] <- c(1, 1, 1, 1, 1, 1)
m.Design[,2] <- c(1, 1, 1, 0, 0, 0)
m.Design[,3] <- c(0, 0, 0, 1, 1, 1)
m.Design[,4] <- c(1, 0, 0, 1, 0, 0)
m.Design[,5] <- c(0, 1, 0, 0, 1, 0)
m.Design[,6] <- c(0, 0, 1, 0, 0, 1)
m.Design
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 1 1 0 1 0 0
## [2,] 1 1 0 0 1 0
## [3,] 1 1 0 0 0 1
## [4,] 1 0 1 1 0 0
## [5,] 1 0 1 0 1 0
## [6,] 1 0 1 0 0 1
Se denotamos por \(\mathbf{c}^{(j)}\) a \(j\)-ésima coluna da matriz
m.Design, então
\[\begin{align}\mathbf{c}^{(3)} &= \mathbf{c}^{(1)} - \mathbf{c}^{(2)}\\ \mathbf{c}^{(6)} &= \mathbf{c}^{(1)} - \mathbf{c}^{(4)} - \mathbf{c}^{(5)}, \end{align}\]
ou seja a terceira e última coluna da matriz m.Design
são combinações lineares das outras. Portanto, podem ser removidas. Para
identificar as combinações lineares dentro de uma matriz podemos usar a
função findLinearCombos como segue:
comboInfo <- findLinearCombos(m.Design)
comboInfo
## $linearCombos
## $linearCombos[[1]]
## [1] 3 1 2
##
## $linearCombos[[2]]
## [1] 6 1 4 5
##
##
## $remove
## [1] 3 6
A matriz de delineamento com as colunas removidas pode ser obtiva por meio de:
m.Design[,-comboInfo$remove]
## [,1] [,2] [,3] [,4]
## [1,] 1 1 1 0
## [2,] 1 1 0 1
## [3,] 1 1 0 0
## [4,] 1 0 1 0
## [5,] 1 0 0 1
## [6,] 1 0 0 0
preProcess() para o
pré-processamentoO pré-processamento dos dados pode ser feito:
usando as funções próprias: nearZeroVar(),
findCorrelation(), etc.
dentro da função train().
usando a função caret::preProcess()
Veremos a continução como usar a função
caret::preProcess()
# Criando um pré-processamento para os dados de treino
preProcValues <- preProcess(training, method = c("nzv","corr"), cutoff = 0.75)
# Aplicando o pré-processamento aos dados de treino
trainTrandformed <- predict(preProcValues, training)
# Aplicando o pré-processamento aos dados de teste
testTrandformed <- predict(preProcValues, testing)
O mesmo pré-processamento aplicado aos dados de treinamenteo deve ser aplicado aos dados de teste.
O uso da função caret::preProcess() permite realizar
pré-processamento mais customizados para certos algoritmos de
aprendizado.