PDF: https://github.com/CristinaGil/Estadistica_machine_learning_R


ANÁLISIS DISCRIMINANTE LINEAL


     El análisis discriminante lineal (Linear Discriminant Analysis o LDA) es un método alternativo más adecuado a la regresión logística cuando la variable cualitativa tiene más de dos niveles (K ≥ 2). Supone también un modelo más estable cuando el tamaño muestral n es pequeño y la distribución de los predictores es aproximadamente normal en cada una de sus clases. El propósito del LDA es encontrar la combinación lineal de las variables originales que permita la mejor separación entre grupos de un set de datos.


Teorema de Bayes para la clasificación


     El teorema de Bayes es útil para obtener probabilidades condicionales, ya que expresa la probabilidad condicional de un evento aleatorio A dado que otro evento B ya ha ocurrido. Esta probabilidad es igual a la probabilidad conjunta de A y B (P(AB)) entre la probabilidad marginal de B (P(B)).

donde

P(Aj│B) = probabilidad a posteriori (posterior probability)

P(Aj) = probabilidad a priori (prior probability)


     En el contexto de la clasificación, mediante el teorema de Bayes se calcula la probabilidad de que la variable respuesta Y pertenezca a cada uno de los posibles niveles, dados unos determinados valores de los predictores.

Suponiendo que \(\small\mathbf{π_k}\) representa la probabilidad a priori de que una observación al azar provenga de la clase k de la variable respuesta Y, y siendo \(\small\mathbf{f_k(X)}\) la función de densidad de probabilidad condicional de X para una observación que proviene de la clase k, el teorema de Bayes establece que


donde \(\small\mathbf{p_k(x)}\) o Pr(Y = k │X = x) representa la probabilidad a posteriori de que una observación X = x pertenezca a la clase k, es decir, la probabilidad dado el valor del predictor (probabilidad condicional). Cada observación se clasificará dentro del nivel que tiene la probabilidad \(\small{p_k(x)}\) más alta. Además, \(\small{f_k(x)}\) será mayor cuanto mayor sea la probabilidad de que la observación de la clase k adquiera un valor de X ≈ x. Por tanto, el LDA está basado en el clasificador Bayesiano.

     Estimar \(\small{π_k}\) es sencillo si disponemos de una muestra aleatoria de Y de la población: simplemente calculamos la fracción de las observaciones que pertenecen a la clase k


     También deberemos estimar \(\small{f_k(x)}\) a partir de la muestra, ya que raramente disponemos de los parámetros poblacionales. Esto hace que el clasificador LDA suponga una aproximación al clasificador de Bayes.


LDA con un predictor


     A la hora de estimar \(\small{f_k(x)}\) en un escenario con un solo predictor, deberemos contar con ciertas suposiciones sobre su forma. Suponiendo que asumimos que \(\small{f_k(x)}\) sigue una distribución normal, en un escenario unidimensional, la función de densidad normal tomaría la forma


donde \(\small\mathbf{µ_k}\) es la media y \(\small\mathbf{σ_k^2}\) la varianza para la clase k, que han de ser estimados:


donde n es el número total de observaciones, y \(\small\mathbf{n_k}\) el número de observaciones en la clase k.


     Si además asumimos que la varianza en torno a todas las clases K es igual \(\small{σ_1^2 = ...= σ_k^2}\), si incluimos la fórmula anterior de \(\small{f_k(x)}\) en la ecuación de Bayes, obtenemos


     Mediante la transformación logarítmica de los términos de la ecuación anterior, e incluyendo las estimaciones de todos los parámetros, podemos obtener una versión más simplificada:

donde \(\small\mathbf{δ_k(x)}\) es el log de \(\small{p_k(x)}\).


     Con esta última ecuación, el clasificador LDA asigna una observación X = x a la clase que maximiza \(\small{δ_k(x)}\). La función discriminante \(\small{δ_k(x)}\) es lineal respecto de x, por ello el término “lineal” en el nombre del LDA, y por ello generará límites de decisión lineales.


Límite de decisión de Bayes vs LDA


Representemos un ejemplo gráfico para entender los conceptos descritos hasta ahora:

     Supongamos que contamos con un predictor con K = 2, cuya función de densidad para cada una de las dos clases proviene de una distribución normal \(\small{f_1(x)}\) con media \(\small{µ_1}\) = - 1,25 y varianza \(\small{σ_1^2}\) = 1 y \(\small{f_2(x)}\) con media \(\small{µ_2}\) = -1,25 y varianza \(\small{σ_2^2}\) = 1. Los parámetros poblacionales en este caso son conocidos, así como su distribución. Además, la varianza es común para las dos clases, condición para la aplicación del LDA. Suponiendo además que la probabilidad a priori \(\small{π_k}\) de que una observación sea de una clase u otra es de 0,5 (en este caso \(\small{π_1 = π_2}\) = 0,5), el límite de decisión de Bayes se podría calcular como


\(\small{x = \frac{(-1,25 + 1,25)}{(2)}}\) = 0
library(ggplot2)
ggplot(data = data.frame(x = -4:4), aes(x = x)) +
# distribución de densidad de k = 1
stat_function(fun = dnorm, 
              args = list(mean = -1.25, sd = 1), 
              color = "green3") +
# distribución de densidad de k = 2
stat_function(fun = dnorm, 
              args = list(mean = 1.25, sd = 1), 
              color = "firebrick") +
# límite de decisión de Bayes
geom_vline(xintercept = 0, 
           linetype = "longdash") +
theme_bw()

En este caso, el clasificador de Bayes asignará la observación a la clase k = 1 (verde) si x < 0, y a la clase k = 2 (rojo) si x > 0.

     En la práctica, no conocemos los parámetros poblacionales, aun teniendo cierta seguridad de que X se distribuye de forma normal para cada clase, por lo que no podemos calcular el límite de decisión de Bayes. En este caso, debemos obtener las estimaciones de los parámetros \(\small{µ_1 = ... = µ_k}\), \(\small{σ^2}\), y \(\small{π_1 = ... = π_k}\) a partir de la muestra.

     Supongamos en este caso que disponemos de una muestra aleatoria de 20 observaciones para cada clase (\(\small{n_1 = n_2}\) = 20). Dado que \(\small{n_1 = n_2}\), tenemos \(\small{π_1 = π_2}\), con lo que en esta situación el límite de decisión corresponderá al punto medio de la media de las dos clases


set.seed(1303)
muestra.verde <- rnorm(n = 20, mean = -1.25, sd = 1)
muestra.roja <- rnorm(n = 20, mean = 1.25, sd = 1)
datos.muestra <- data.frame(clase = rep(c("verde", "roja"), each = 20), 
                            valor = c(muestra.verde, muestra.roja))

ggplot(data = datos.muestra, aes(x = valor, fill = clase)) +
geom_histogram(alpha = 0.5, 
               position = "identity") +
scale_fill_manual(values = c("Firebrick", "Green3")) +
geom_vline(xintercept = 0, 
           linetype = "longdash") +
geom_vline(xintercept = (mean(muestra.verde) + mean(muestra.roja))/2, 
           color = "darkblue") +
geom_text(aes(label = "Límite decisión LDA", x = 1.2, y = 4), 
          size = 3, 
          colour = "darkblue") +
geom_text(aes(label = "Límite decisión Bayes", x = -1.2, y = 4.5), 
          size = 3, 
          colour = "black") +
theme_bw()

     En la figura podemos ver que el límite de decisión LDA está ligeramente desplazado hacia la derecha en relación al límite de decisión de Bayes, pero sigue siendo una aproximación bastante buena.


LDA con múltiples predictores


     Podemos extender el LDA para casos con más de un predictor. En este caso ha de asumirse que X = (\(\small{X_1 = X_2, ... = X_p}\)) siguen una distribución normal multivariante (los predictores se distribuyen de forma normal cuando se separan para cada clase de la variable que se quiere predecir), con media \(\small{µ_k}\) específica de la clase y varianza \(\small{σ^2}\) común entre clases. Esto también implica la susceptibilidad a posibles outliers. Es importante que p < n para evitar problemas de overfitting.

     Se utiliza el término X ~ N (µ, Σ) para indicar que una variable aleatoria X p-dimensional sigue una distribución normal multivariante, donde \(\small{µ}\) es su media y donde \(\small\mathbf{Σ}\) es su matriz de covarianza p x p común a todas las clases K. La ecuación se define como


     Introduciendo \(\small{f(x)}\) en la ecuación de Bayes, obtenemos un clasificador que asignará la observación X = x a la clase para la que se maximice


     De nuevo, desconociendo los parámetros poblacionales no podemos obtener el límite de decisión de Bayes, por lo que tendremos que estimar \(\small{µ_1 = ... = µ_k}\), \(\small{π_1 = ... = π_k}\) y \(\small\mathbf{Σ}\) para obtener los límites de decisión de LDA, que dividen el espacio del predictor en regiones correspondiente a las clases.


Podríamos representar una distribución normal bivariante (2 elementos) de la siguiente manera:

library(mvtnorm)
library(scatterplot3d)

sigma.zero <- matrix(c(1,0,0,1), ncol=2)
sigma.zero
##      [,1] [,2]
## [1,]    1    0
## [2,]    0    1
x1000 <- rmvnorm(n=1000, mean=c(0,0), sigma=sigma.zero)
head(x1000, 4)
##             [,1]        [,2]
## [1,]  0.26400733  0.63218681
## [2,] -1.33065099  0.02688882
## [3,]  1.04063632  1.31202380
## [4,] -0.03000208 -0.25002571
scatterplot3d(x1000[,1], x1000[,2], 
              dmvnorm(x1000, mean=c(0,0), sigma=sigma.zero), 
              highlight=TRUE, xlab = "x", ylab = "y", zlab = "z")


Precisión del modelo LDA


     El método LDA trata de aproximarse al clasificador de Bayes, el cual comete el menor ratio de error de todos los clasificadores, siempre que se cumpla la condición de normalidad.

     Al igual que en la regresión logística, es conveniente obtener la matriz de confusión para detectar la cantidad de verdaderos positivos (sensibilidad), verdaderos negativos, falsos positivos (error tipo I) y falsos negativos del clasificador LDA, y determinar el error que comete no solo de forma general, sino en cada dirección de predicción (el modelo puede ser mejor o peor prediciendo en una dirección que en otra).


     Si nos interesa cometer menos predicciones incorrectas, podemos considerar disminuir el límite probabilidad a posteriori para el límite de decisión. Por ejemplo, asignar una observación x a la clase \(\small{k_i}\) si Pr(K = \(\small{k_i}\) │ X = x) > 0,3, en lugar de Pr(K = \(\small{k_i}\) │ X = x) > 0,5 (límite por defecto). Todo dependerá de qué ratio de error nos interese más, o el coste asociado a cada uno. En resumen, nos interesa conocer el porcentaje de acierto de nuestro clasificador.

     La curva de ROC es un gráfico útil para representar los dos tipos de errores del modelo (global y por clasificación) para todos los posibles thresholds. Una curva de ROC ideal mostraría que para un alto ratio de verdaderos positivos le correspondería un bajo ratio de falsos positivos. El rendimiento global del modelo sobre todos los posibles thresholds se corresponde con el área bajo la curva (AUC). Cuanto mayor es AUC, mejor es el clasificador. Un clasificador con AUC = 0,5 no superaría lo esperado por azar (evaluado con datos de test).

     Para no obtener un porcentaje de error que subestime el verdadero porcentaje de error que puede cometer el modelo, es importante evaluarlo sobre un grupo de datos de test, y no utilizar los datos de entrenamiento que se han utilizado para entrenar el modelo.


Condiciones para aplicar LDA


     Para que el modelo de análisis discriminante lineal se acepte como válido, ha de cumplirse que las observaciones dentro de cada clase siguen una distribución aproximadamente normal N (\(\small{µ_k}\), \(\small{σ^2}\)) para casos con un solo predictor, o normal multivariante N (\(\small{µ_k}\), \(\small{Σ}\)) para casos con más de un predictor, con media \(\small{µ_k}\) específica para cada clase y varianza \(\small{σ^2}\) o matriz de covarianza Σ común entre todas las clases. Ante la falta de normalidad multivariante, el LDA puede tener cierta robustez, pero es importante tenerlo en cuenta en las conclusiones del análisis.

IMPORTANTE que p < n para evitar problemas de overfitting.

     Si no se cumple la condición de \(\small{σ^2}\) o \(\small{Σ}\) común, se recurre al Análisis Discriminante Cuadrático.


ANÁLISIS DISCRIMINANTE CUADRÁTICO


     El análisis discriminante cuadrático (Quadratic Discriminant Analysis o QDA) supone una alternativa al LDA cuando cada clase tiene su propia matriz de covarianza (\(\small{Σ_k}\)). Al igual que LDA, QDA también asume que las observaciones de cada clase siguen una distribución normal multivariante, así como también introduce las estimaciones de los parámetros en la ecuación del teorema de Bayes para obtener las predicciones.

En este caso, una observación de la clase k sigue la forma

X ~ N (\(\small{µ_k}\), \(\small{Σ_k}\))


El clasificador asigna una observación X = x a la clase para la que la probabilidad a posteriori

sea mayor.


     En este caso los parámetros a estimar a partir de las muestras son \(\small{µ_k}\), \(\small{σ^2}\) y \(\small{Σ}\) y la ecuación no es una función lineal de x, sino cuadrática (de donde deriva el nombre del método “cuadrático”). Debido a esta propiedad, el QDA genera límites de decisión curvos, no lineales, por lo que es aplicable para casos en los que la separación entre grupos no es lineal. Por ello también, QDA es mucho más flexible que LDA.


EJEMPLO EN R: LDA y QDA


     En este ejemplo trabajaremos con el set de datos Auto del paquete ISLR y ajustaremos un modelo de LDA y QDA para predecir si el rendimiento del combustible (gas mileage) de un automóvil es alto o bajo en función del resto de predictores del set de datos.

     Consideraremos como “alto” un valor mayor a la mediana. Para ello, crearemos una variable binaria a partir de la variable binaria mpg01 que contenga “1” si el valor de mpg está por encima de la mediana, y “0” si se encuentra por debajo. Al hacer esto, será importante codificar la variable respuesta como factor. Como en este caso contamos con un predictor con K = 2, la regresión logística también sería una opción.


1. Análisis exploratorio de los datos


head() -> Muestra las primeras observaciones de un vector, matriz, tabla, data frame o función

str() -> Muestra de manera compacta la estructura interna de un objeto

summary() -> Resumen de los datos

cor() -> Calcula los coeficientes de correlación entre variables (solo acepta vectores numéricos)

ggpairs() -> Combina en un único gráfico diagramas de dispersión, distribución de las variables y los valores de correlación

pairs() -> Matriz con gráficos de dispersión de las variables cuantitativas

scatterplot3d()-> Nube de puntos tridimensional

kable()-> Genera una tabla sencilla a partir de una matriz o data frame

ggplot()


library(ISLR)
head(Auto, 3)
##   mpg cylinders displacement horsepower weight acceleration year origin
## 1  18         8          307        130   3504         12.0   70      1
## 2  15         8          350        165   3693         11.5   70      1
## 3  18         8          318        150   3436         11.0   70      1
##                        name
## 1 chevrolet chevelle malibu
## 2         buick skylark 320
## 3        plymouth satellite
str(Auto)
## 'data.frame':    392 obs. of  9 variables:
##  $ mpg         : num  18 15 18 16 17 15 14 14 14 15 ...
##  $ cylinders   : num  8 8 8 8 8 8 8 8 8 8 ...
##  $ displacement: num  307 350 318 304 302 429 454 440 455 390 ...
##  $ horsepower  : num  130 165 150 150 140 198 220 215 225 190 ...
##  $ weight      : num  3504 3693 3436 3433 3449 ...
##  $ acceleration: num  12 11.5 11 12 10.5 10 9 8.5 10 8.5 ...
##  $ year        : num  70 70 70 70 70 70 70 70 70 70 ...
##  $ origin      : num  1 1 1 1 1 1 1 1 1 1 ...
##  $ name        : Factor w/ 304 levels "amc ambassador brougham",..: 49 36 231 14 161 141 54 223 241 2 ...
summary(Auto)
##       mpg          cylinders      displacement     horsepower        weight    
##  Min.   : 9.00   Min.   :3.000   Min.   : 68.0   Min.   : 46.0   Min.   :1613  
##  1st Qu.:17.00   1st Qu.:4.000   1st Qu.:105.0   1st Qu.: 75.0   1st Qu.:2225  
##  Median :22.75   Median :4.000   Median :151.0   Median : 93.5   Median :2804  
##  Mean   :23.45   Mean   :5.472   Mean   :194.4   Mean   :104.5   Mean   :2978  
##  3rd Qu.:29.00   3rd Qu.:8.000   3rd Qu.:275.8   3rd Qu.:126.0   3rd Qu.:3615  
##  Max.   :46.60   Max.   :8.000   Max.   :455.0   Max.   :230.0   Max.   :5140  
##                                                                                
##   acceleration        year           origin                      name    
##  Min.   : 8.00   Min.   :70.00   Min.   :1.000   amc matador       :  5  
##  1st Qu.:13.78   1st Qu.:73.00   1st Qu.:1.000   ford pinto        :  5  
##  Median :15.50   Median :76.00   Median :1.000   toyota corolla    :  5  
##  Mean   :15.54   Mean   :75.98   Mean   :1.577   amc gremlin       :  4  
##  3rd Qu.:17.02   3rd Qu.:79.00   3rd Qu.:2.000   amc hornet        :  4  
##  Max.   :24.80   Max.   :82.00   Max.   :3.000   chevrolet chevette:  4  
##                                                  (Other)           :365
attach(Auto)
# Vector de “0”s (rendimiento bajo) con la misma longitud que la variable mpg
mpg01 <- rep(0, length(mpg))
# Sustitución de “0”s por “1”s (rendimiento alto) si mpg > mediana(mpg)
mpg01[mpg > median(mpg)] <- 1
Auto <-  data.frame(Auto, mpg01)

head(Auto, 3)
##   mpg cylinders displacement horsepower weight acceleration year origin
## 1  18         8          307        130   3504         12.0   70      1
## 2  15         8          350        165   3693         11.5   70      1
## 3  18         8          318        150   3436         11.0   70      1
##                        name mpg01
## 1 chevrolet chevelle malibu     0
## 2         buick skylark 320     0
## 3        plymouth satellite     0
# Correlación entre variables (excluyendo la variable cualitativa “name”)
cor(Auto[, -9], method = "pearson")
##                     mpg  cylinders displacement horsepower     weight
## mpg           1.0000000 -0.7776175   -0.8051269 -0.7784268 -0.8322442
## cylinders    -0.7776175  1.0000000    0.9508233  0.8429834  0.8975273
## displacement -0.8051269  0.9508233    1.0000000  0.8972570  0.9329944
## horsepower   -0.7784268  0.8429834    0.8972570  1.0000000  0.8645377
## weight       -0.8322442  0.8975273    0.9329944  0.8645377  1.0000000
## acceleration  0.4233285 -0.5046834   -0.5438005 -0.6891955 -0.4168392
## year          0.5805410 -0.3456474   -0.3698552 -0.4163615 -0.3091199
## origin        0.5652088 -0.5689316   -0.6145351 -0.4551715 -0.5850054
## mpg01         0.8369392 -0.7591939   -0.7534766 -0.6670526 -0.7577566
##              acceleration       year     origin      mpg01
## mpg             0.4233285  0.5805410  0.5652088  0.8369392
## cylinders      -0.5046834 -0.3456474 -0.5689316 -0.7591939
## displacement   -0.5438005 -0.3698552 -0.6145351 -0.7534766
## horsepower     -0.6891955 -0.4163615 -0.4551715 -0.6670526
## weight         -0.4168392 -0.3091199 -0.5850054 -0.7577566
## acceleration    1.0000000  0.2903161  0.2127458  0.3468215
## year            0.2903161  1.0000000  0.1815277  0.4299042
## origin          0.2127458  0.1815277  1.0000000  0.5136984
## mpg01           0.3468215  0.4299042  0.5136984  1.0000000
library(dplyr)
require(GGally)

ggpairs(select(Auto, - name), 
        lower = list(continuous = "smooth"), 
        diag = list(continuous = "bar"), 
        axisLabels = "none")

par(mfrow=c(2,3))
boxplot(cylinders ~ mpg01, data = Auto, main = "Cylinders vs mpg01")
boxplot(displacement ~ mpg01, data = Auto, main = "Displacement vs mpg01")
boxplot(horsepower ~ mpg01, data = Auto, main = "Horsepower vs mpg01")
boxplot(weight ~ mpg01, data = Auto, main = "Weight vs mpg01")
boxplot(acceleration ~ mpg01, data = Auto, main = "Acceleration vs mpg01")
boxplot(year ~ mpg01, data = Auto, main = "Year vs mpg01")

par(mfrow= c(1,1))
# Asociación entre variables cuantitativas más correlacionadas. Se colorean los casos en función del rendimiento
pairs(x = Auto[, c("displacement", "weight", "horsepower")], 
      col= ifelse(Auto$mpg01==0, "red", "green"), 
      pch = 4)

# Representación de distribución en histogramas
library(ggplot2)
library(gridExtra)

p1 <- ggplot(data = Auto, aes(x = displacement)) + 
geom_histogram(position = "identity", 
               alpha = 0.5, 
               aes(fill = as.factor(mpg01)))+
labs(fill = "mpg01")

p2 <- ggplot(data = Auto, aes(x = horsepower)) + 
geom_histogram(position = "identity", 
               alpha = 0.5, 
               aes(fill = as.factor(mpg01)))+
labs(fill = "mpg01")

p3 <- ggplot(data = Auto, aes(x = weight)) + 
geom_histogram(position = "identity", 
               alpha = 0.5, 
               aes(fill = as.factor(mpg01)))+
labs(fill = "mpg01")

grid.arrange(p1, p2, p3)

# Representación tridimensional
library(scatterplot3d)

scatterplot3d(Auto$displacement, Auto$horsepower, Auto$weight, pch = 19, 
              grid = TRUE, tick.marks = FALSE, angle = 65, 
              xlab = "displacement", ylab = "horsepower", zlab = "weight", 
              color = ifelse(Auto$mpg01 == 0, yes = "red", no = "green3"))
legend("topleft", bty = "n", cex = .6, 
       title = "Rendimiento", c("alto", "bajo"), 
       fill = c("green3", "red"))


     Las variables que muestran mayor asociación con mpg01 y que parecen ser útiles en separar los automóviles según el rendimiento (y que por tanto podrían ayudar a predecir esta variable respuesta) son: displacement, weight, horsepower y cylinders. Sin embargo, hay que tener en cuenta, según se aprecia en los gráficos, que la clase 0 de menor rendimiento solapa con la clase 1 de mayor rendimiento, lo que puede afectar la capacidad predictiva del modelo.

library(dplyr)
library(knitr)
# Subgrupo con las variables seleccionadas para el modelo
Auto2 <- select(Auto, cylinders, displacement, horsepower, weight, mpg01)
Auto2$mpg01 <- as.factor(mpg01)
kable(head(Auto2, 3), align = "c")
cylinders displacement horsepower weight mpg01
8 307 130 3504 0
8 350 165 3693 0
8 318 150 3436 0


2. Análisis de normalidad (univariante y multivariante) y homogeneidad de varianza


     Combinando resultados numéricos de distintos tests junto con métodos gráficos (histogramas, Q-Q plots) podremos llegar a conclusiones más robustas.


Normalidad univariante


hist() y curve()/lines() -> Histograma y curva

qqnorm() y qqline()-> Gráfico Q-Q normal de los valores de “y” y línea teórica normal

shapiro.test()-> Test de normalidad de Shapiro-Wilk


par(mfrow= c(1,3))

# Distribución "displacement"
hist(Auto2$displacement, proba = T, col = grey(0.8), main = "")
curve(dnorm(x, mean=mean(Auto2$displacement), sd=sd(Auto2$displacement)), 
      add=TRUE, 
      col = "red")
# Distribución "horsepower"
hist(Auto2$horsepower, proba = T, col = grey(0.8), main = "")
curve(dnorm(x, mean=mean(Auto2$horsepower), sd=sd(Auto2$horsepower)), 
      add=TRUE, 
      col = "red")
# Distribución "weight"
hist(Auto2$weight, proba = T, col = grey(0.8), main = "")
curve(dnorm(x, mean=mean(Auto2$weight), sd=sd(Auto2$weight)), 
      add=TRUE, 
      col = "red")


Los histogramas anteriores se pueden representar de forma automática para cada mpg01 con un for loop:

par(mfrow=c(2,3))

for (k in 2:4) {
     v <- names(Auto2)[k]
     for (i in 1:2) {
         l <- levels(Auto2$mpg01)[i]
         x <- Auto2[Auto2$mpg01 == l, v]
         hist(x, proba = T, col = grey(0.8), main = paste("mpg01", l), xlab = v)
         x0 <- seq(min(Auto2[, k]), max(Auto2[, k]), le = 50)
         lines(x0, dnorm(x0, mean(x), sd(x)), col = "red", lwd = 2)
}
}

par(mfrow= c(2,3))

# Gráficos Q-Q de cada variable para la clase mpg01 = 0 (bajo rendimiento)
qqnorm(Auto2[mpg01 == 0, 2], 
       col = "grey", 
       main = "Displacement (mpg = bajo)")
qqline(Auto2[mpg01 == 0, 2], col = "red")

qqnorm(Auto2[mpg01 == 0, 3], 
       col = "grey", 
       main = "Horsepower (mpg = bajo)")
qqline(Auto2[mpg01 == 0, 3], col = "red")

qqnorm(Auto2[mpg01 == 0, 4], 
       col = "grey", 
       main = "Weight (mpg = bajo)")
qqline(Auto2[mpg01 == 0, 4], col = "red")

# Gráficos Q-Q de cada variable para la clase mpg01 = 1 (alto rendimiento)
qqnorm(Auto2[mpg01 == 1, 2], 
       col = "lightgreen", 
       main = "Displacement (mpg = alto)")
qqline(Auto2[mpg01 == 1, 2], col = "red")

qqnorm(Auto2[mpg01 == 1, 3], 
       col = "lightgreen", 
       main = "Horsepower (mpg = alto)")
qqline(Auto2[mpg01 == 1, 3], col = "red")

qqnorm(Auto2[mpg01 == 1, 4], 
       col = "lightgreen", 
       main = "Weight (mpg = alto)")
qqline(Auto2[mpg01 == 1, 4], col = "red")


Se puede volver a automatizar esta representación mediante un for loop:

par(mfrow=c(2,3))
for (k in 2:4) {
v <- names(Auto2)[k]
for (i in 1:2) {
l <- levels(Auto2$mpg01)[i]
x <- Auto2[Auto2$mpg01 == l, v]
qqnorm(x, main = paste("mpg01", l, v), pch = 19, col = i + 1)
qqline(x)
}
}

NOTA: La representación de histogramas y cuantiles anterior también se pueden obtener con la función mvn, cuyo uso se ejemplifica más adelante.


library(reshape2)
library(knitr)
library(dplyr)
# Contraste de normalidad Shapiro-Wilk para cada variable en cada rendimiento
Auto2.tidy <- melt(Auto2, value.name = "valor")

kable(Auto2.tidy %>% group_by(mpg01, variable) %>% summarise(p_value_Shapiro.test = shapiro.test(valor)$p.value), align = "c")
mpg01 variable p_value_Shapiro.test
0 cylinders 0.0000000
0 displacement 0.0010302
0 horsepower 0.0000002
0 weight 0.0554957
1 cylinders 0.0000000
1 displacement 0.0000000
1 horsepower 0.0029960
1 weight 0.0000003


Conclusión:

En conjunto hay evidencias de falta de normalidad univariante en todas las variables empleadas como predictores, menos la variable weight para la clase mpg01 = 0 (rendimiento bajo).


Normalidad multivariante


mvn() -> Función del paquete MVN, que incluye argumentos para llevar a cabo tests y gráficos de normalidad multivariante, detección de outliers multivariantes, tests y gráficos de normalidad univariante


     Además de la normalidad univariante, se requiere evaluar la normalidad multivariante. La presencia de valores atípicos puede ser causa de no cumplir esta condición. Por ello, es conveniente verificar si los datos tienen outliers multivariantes (valores extremos para combinaciones de variables) antes de comenzar con el análisis multivariante. Con el paquete MVN podemos evaluar la normalidad multivariante con tres de los test comúnmente utilizados, como el de Mardia, Royston y Henze-Zirkler, así como identificar los outliers multivariantes que puedan influir en el contraste.

library(MVN)


Detección de outliers multivariantes


     La el argumento multivariateOutlierMethod emplea el método de cuantiles basado en la distancia de Mahalanobis y la distancia de Mahalanobis ajustada.

par(mfrow = c(1, 2))
# Distancia de Mahalanobis
outliers <- mvn(data = Auto2[,2:4], multivariateOutlierMethod = "quan")
# Distancia ajustada de Mahalanobis
outliers.adj <- mvn(data = Auto2[,2:4], multivariateOutlierMethod = "adj")


En torno al 40% de las observaciones se consideran outliers multivariantes (excluyendo la variable continua discreta cylinders), un porcentaje muy alto. Hay que tener en cuenta de lo anterior que la normalidad univariante no se cumple.


Test MVN de Royston


     NOTA: no se aconseja usar este test si los datos cuentan con más de 5000 o menos de 3 observaciones, ya que depende del test de Shapiro Wilk.

royston <- mvn(data = Auto2[,-5], mvnTest = "royston", multivariatePlot = "qq")

names(royston)
## [1] "multivariateNormality" "univariateNormality"   "Descriptives"
royston
## $multivariateNormality
##      Test        H      p value MVN
## 1 Royston 128.4618 9.127413e-29  NO
## 
## $univariateNormality
##           Test     Variable Statistic   p value Normality
## 1 Shapiro-Wilk  cylinders      0.7507  <0.001      NO    
## 2 Shapiro-Wilk displacement    0.8818  <0.001      NO    
## 3 Shapiro-Wilk  horsepower     0.9041  <0.001      NO    
## 4 Shapiro-Wilk    weight       0.9415  <0.001      NO    
## 
## $Descriptives
##                n        Mean    Std.Dev Median  Min  Max    25th    75th
## cylinders    392    5.471939   1.705783    4.0    3    8    4.00    8.00
## displacement 392  194.411990 104.644004  151.0   68  455  105.00  275.75
## horsepower   392  104.469388  38.491160   93.5   46  230   75.00  126.00
## weight       392 2977.584184 849.402560 2803.5 1613 5140 2225.25 3614.75
##                   Skew   Kurtosis
## cylinders    0.5042273 -1.4038700
## displacement 0.6963083 -0.7949853
## horsepower   1.0790191  0.6541069
## weight       0.5156160 -0.8253788

El uso de la función mvn también nos da a conocer que la normalidad univariante para cada variable, sin distinguir entre clases de mpg01 tampoco se cumple.


Test MVN de Mardia


     El test de Mardia calcula los coeficientes de asimetría y curtosis y la correspondiente significancia estadística, combinando ambos para dar un resultado global de normalidad multivariante (MVN):

mardia <- mvn(data = Auto2[,-5], mvnTest = "mardia")
mardia$multivariateNormality
##              Test        Statistic              p value Result
## 1 Mardia Skewness 415.192051331094 1.43400374737941e-75     NO
## 2 Mardia Kurtosis 16.5440533744995                    0     NO
## 3             MVN             <NA>                 <NA>     NO


Test MVN de Henze-Zirkler
hz <- mvn(data = Auto2[,-5], mvnTest = "hz")
hz$multivariateNormality
##            Test      HZ p value MVN
## 1 Henze-Zirkler 12.3492       0  NO


Conclusión:

     Todos los test llevados a cabo muestran evidencias significativas (α = 0,05) de que los datos no siguen una distribución normal multivariante (podríamos deducir que el problema se debe a la falta de normalidad univariante), por lo que habrá que tener en cuenta que tendrá implicaciones en la precisión del LDA/QDA.


Matrices de covarianza


boxM() -> Realiza el test M de Box (1949) para determinar la homogeneidad de las matrices de covarianza obtenidas a partir de datos normales multivariados según uno o más factores de clasificación. Tiene como hipótesis nula que las matrices de covarianza son iguales. IMP: sensible a la falta de normalidad multivariante


library(biotools)
## ---
## biotools version 3.1
boxM(data = Auto2[, 1:4], grouping = Auto2[, 5])
## 
##  Box's M-test for Homogeneity of Covariance Matrices
## 
## data:  Auto2[, 1:4]
## Chi-Sq (approx.) = 292.01, df = 10, p-value < 2.2e-16

     El test M de Box muestra evidencias significativas de que la matriz de covarianza no es constante para todos los grupos, lo que haría apropiado aplicar QDA en lugar de LDA, pero en este caso ante la falta de normalidad multivariante en los datos, el test podría haberse visto afectado por ello.


3. Cálculo de la función discriminante


     A modo de ejemplo y a pesar de la falta de normalidad multivariante e igualdad entre matrices de covarianza, ajustaremos los modelos LDA y QDA.

     Primero dividiremos el set de datos en entrenamiento (80%) para ajustar el modelo, y en test (20%) para evaluarlo.

set.seed(1)
entrenamiento <- sample(x = nrow(Auto), size = nrow(Auto)*0.8, replace = FALSE)
# Subgrupo de datos de entrenamiento
Auto.train <- Auto[entrenamiento,]
# Subgrupo de datos de test
Auto.test <- Auto[-entrenamiento,]
# Comprobamos que la suma de observaciones de cada subgrupo iguala al set de datos original
nrow(Auto.train)
## [1] 313
nrow(Auto.test)
## [1] 79
nrow(Auto.train) + nrow(Auto.test) == nrow(Auto)
## [1] TRUE


Paquete MASS


lda() -> Función para ajustar el modelo discriminante lineal


library(MASS)
# Modelo LDA con los datos de entrenamiento
modelo.lda <- lda(formula = mpg01 ~ cylinders + displacement + horsepower + weight, data = Auto.train)
modelo.lda
## Call:
## lda(mpg01 ~ cylinders + displacement + horsepower + weight, data = Auto.train)
## 
## Prior probabilities of groups:
##        0        1 
## 0.514377 0.485623 
## 
## Group means:
##   cylinders displacement horsepower   weight
## 0  6.677019     269.8323  128.71429 3601.379
## 1  4.210526     117.4145   78.57895 2350.526
## 
## Coefficients of linear discriminants:
##                        LD1
## cylinders    -0.4183687107
## displacement -0.0017457149
## horsepower    0.0028180950
## weight       -0.0009283838


     El modelo calcula automáticamente las probabilidades a priori (\(\small{π_0}\) = 0,514, \(\small{π_1}\) = 0,485), y el promedio de cada predictor dentro de cada clase, usados por el modelo como estimadores de µk. Los coeficientes proporcionan la combinación de los predictores (- 0,4183cylinders - 0,0017displacement + 0,0028 – 0,0009) para generar los discriminantes lineales para cada una de las observaciones de entrenamiento.

# Modelo QDA con los datos de entrenamiento
modelo.qda <- qda(formula = mpg01 ~ cylinders + displacement + horsepower + weight, data = Auto.train)
modelo.qda
## Call:
## qda(mpg01 ~ cylinders + displacement + horsepower + weight, data = Auto.train)
## 
## Prior probabilities of groups:
##        0        1 
## 0.514377 0.485623 
## 
## Group means:
##   cylinders displacement horsepower   weight
## 0  6.677019     269.8323  128.71429 3601.379
## 1  4.210526     117.4145   78.57895 2350.526


     A diferencia del LDA, el QDA no contiene los coeficientes de los discriminantes lineales, puesto que el clasificador QDA se basa en una función cuadrática de los predictores, no lineal.


Paquete caret


train() -> Función para ajuste de modelos


     El paquete caret contiene funciones aplicadas a machine learning. A modo de ejemplo, se ajusta sólo el modelo LDA con este paquete:

library(caret)

modelo.lda.caret <- train(as.factor(mpg01) ~ cylinders + displacement + horsepower + weight, 
                          method ='lda', 
                          data=Auto.train)

modelo.lda.caret
## Linear Discriminant Analysis 
## 
## 313 samples
##   4 predictor
##   2 classes: '0', '1' 
## 
## No pre-processing
## Resampling: Bootstrapped (25 reps) 
## Summary of sample sizes: 313, 313, 313, 313, 313, 313, ... 
## Resampling results:
## 
##   Accuracy  Kappa    
##   0.876216  0.7519744


4. Evaluación del modelo


predict()-> Predicciones a partir de los resultados de diversas funciones de ajuste de modelos

table()-> Tabla de contingencia de los conteos en cada combinación de niveles de factores


Modelo LDA:

# Evaluación del modelo LDA con los datos de test
lda.pred <-  predict(object = modelo.lda, newdata = Auto.test)
names(lda.pred)
## [1] "class"     "posterior" "x"


     La función predict() ha calculado las probabilidades a posteriori de que cada nueva observación pertenezca a la clase con menor rendimiento (mpg01 = 0). A continuación, calculamos el porcentaje de observaciones incorrectamente clasificadas comparando la predicción con la verdadera clase del set de datos de test, obteniendo así el test error rate del modelo discriminante:

# Matriz de confusion del modelo LDA
table(lda.pred$class, Auto.test$mpg01, dnn = c("Clase predicha", "Clase real"))
##               Clase real
## Clase predicha  0  1
##              0 32  1
##              1  3 43
# Aplicando un threshold del 50% a las probabilidades a posterior podemos recrear las predicciones del modelo. 
sum(lda.pred$posterior[,1] >= 0.5)
## [1] 33
sum(lda.pred$posterior[,1] < 0.5)
## [1] 46
# Test error rate del modelo LDA
mean(lda.pred$class != Auto.test$mpg01)
## [1] 0.05063291
# Porcentaje de aciertos del modelo LDA 
mean(lda.pred$class == Auto.test$mpg01)
## [1] 0.9493671


     El test error rate es muy bajo con este modelo (5%). En prácticamente el 95% de los casos las observaciones son correctamente predichas.

     NOTA: Podríamos cambiar el threshold de decisión si las predicciones no fueran lo bastante buenas en el sentido que nos interesa.


Modelo QDA:

qda.pred <-  predict(object = modelo.qda, newdata = Auto.test)
table(qda.pred$class, Auto.test$mpg01, dnn = c("Clase predicha", "Clase real"))
##               Clase real
## Clase predicha  0  1
##              0 32  4
##              1  3 40
mean(qda.pred$class != Auto.test$mpg01)
## [1] 0.08860759


     El test error rate del modelo QDA es ligeramente superior (8,8%), por lo que en este caso optaríamos por el LDA, que cuenta con más porcentaje de aciertos globales.


Paquete caret


confusionMatrix()-> Calcula una tabulación cruzada de clases observadas y predichas con estadísticos asociados


library(caret)
confusionMatrix(as.factor(Auto.test$mpg01), predict(modelo.lda.caret, Auto.test))
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  0  1
##          0 32  3
##          1  1 43
##                                          
##                Accuracy : 0.9494         
##                  95% CI : (0.8754, 0.986)
##     No Information Rate : 0.5823         
##     P-Value [Acc > NIR] : 1.196e-13      
##                                          
##                   Kappa : 0.8968         
##  Mcnemar's Test P-Value : 0.6171         
##                                          
##             Sensitivity : 0.9697         
##             Specificity : 0.9348         
##          Pos Pred Value : 0.9143         
##          Neg Pred Value : 0.9773         
##              Prevalence : 0.4177         
##          Detection Rate : 0.4051         
##    Detection Prevalence : 0.4430         
##       Balanced Accuracy : 0.9522         
##                                          
##        'Positive' Class : 0              
## 


5. Visualización del modelo


partimat()-> Proporciona una matriz de figuras múltiples que muestra la clasificación de las observaciones basadas en métodos de clasificación (por ejemplo, lda, qda) para cada combinación de dos variables. Además, se muestran los límites de clasificación y las tasas de error aparentes se dan en cada título

plot()


# LDA respecto a los datos de entrenamiento usados en el modelo
plot(modelo.lda)

library(klaR)
# Representación del LDA respecto a los datos de test
partimat(formula = as.factor(mpg01) ~ displacement + horsepower + weight, data = Auto.test, method = "lda", prec = 400, image.colors = c("darkgoldenrod1", "skyblue2"), col.mean = "firebrick", nplots.vert = 2)

# Representación del QDA respecto a los datos de test
partimat(formula = as.factor(mpg01) ~ displacement + horsepower + weight, data = Auto.test, method = "qda", prec = 400, image.colors = c("darkgoldenrod1", "skyblue2"), col.mean = "firebrick", nplots.vert = 2)

     En base a las representaciones podemos observar que la función discriminante consigue separar mejor la clase 1, existiendo superposición respecto a la clase 0.


COMPARACIÓN ENTRE LDA Y QDA


     LDA es un método mucho menos flexible que QDA y sufre de menos varianza. Ello puede suponer una mejora en la predicción, pero hay un inconveniente: si la asunción del LDA de que todas las clases comparten la misma matriz de covarianza no es correcta en realidad, el LDA puede sufrir un alto bias o sesgo. Visto de otra manera, LDA suele ser mejor que QDA si contamos con relativamente pocas observaciones de entrenamiento y reducir la varianza es importante. Por el contrario, se recomienda QDA si el set de observaciones de entrenamiento es muy grande y la varianza del clasificador no supone un problema, o si el supuesto de una matriz de covarianza común entre las clases claramente no se cumple.

     Si el verdadero límite de Bayes es lineal, LDA será una aproximación más precisa que QDA. Si por el contrario no es lineal, QDA será una mejor opción.


BIBLIOGRAFÍA


An Introduction to Statistical Learning: with Applications in R (Springer Texts in Statistics)

Andrea J., Amón I., Técnicas para detección de outliers multivariantes. Revista en Telecomunicaciones e Informática, Vol. 3, No. 5 p. 11-25 (2013)

Korkmaz S., Goksuluk D., Zararsiz G., MVN: An R Package for Assessing Multivariate Normality. Trakya University

http://halweb.uc3m.es/esp/Personal/personas/jmmarin/esp/AMult/tema6am.pdf