AnĂ¡lisis discriminante lineal (LDA)

El AnĂ¡lisis Discriminante Lineal o Linear Discrimiant Analysis (LDA) es un mĂ©todo de clasificaciĂ³n supervisado de variables cualitativas en el que dos o mĂ¡s grupos son conocidos a priori y nuevas observaciones se clasifican en uno de ellos en funciĂ³n de sus caracterĂ­sticas. Haciendo uso del teorema de Bayes, LDA estima la probabilidad de que una observaciĂ³n, dado un determinado valor de los predictores, pertenezca a cada una de las clases de la variable cualitativa, \(P(Y=k|X=x)\). Finalmente se asigna la observaciĂ³n a la clase \(k\) para la que la probabilidad posterior es mayor.

Es una alternativa a la regresiĂ³n logĂ­stica cuando la variable cualitativa tiene mĂ¡s de dos niveles. Si bien existen extensiones de la regresiĂ³n logĂ­stica para mĂºltiples clases, LDA presenta una serie de ventajas:

Cuando se trata de un problema de clasificaciĂ³n con solo dos niveles, ambos mĂ©todos suelen llegar a resultados similares.

El proceso de un anĂ¡lisis discriminante puede resumirse en 6 pasos:

Teorema de Bayes para clasificaciĂ³n

ConsidĂ©rense dos eventos \(A\) y \(B\), sabemos que el teorema de Bayes establece que: \[ P(B|A)=\dfrac{P(AB)}{P(A)}\] SupĂ³ngase que se desea clasificar una nueva observaciĂ³n en una de las \(K\) clases de una variable cualitativa \(Y\), siendo \(K≥2\), a partir de un solo predictor \(X\). Se dispone de las siguientes definiciones:

  • Se define como probabilidad a priori \((Ï€_k)\) la probabilidad de que una observaciĂ³n aleatoria pertenezca a la clase \(k\).

  • Se define \(f_k(X)≡P(X=x|Y=k)\) como la funciĂ³n de densidad de probabilidad condicional de \(X\) para una observaciĂ³n que pertenece a la clase \(k\). Cuanto mayor sea \(f_k(X)\) mayor la probabilidad de que una observaciĂ³n de la clase \(k\) adquiera una valor de \(X≈x\).

  • Se define como probabilidad posterior \(P(Y=k|X=x)\) la probabilidad de que una observaciĂ³n pertenezca a la clase \(k\) siendo \(x\) el valor del predictor.

Aplicando del teorema de Bayes se pueden conocer la probabilidad posterior para cada clase:

\[P(Y=k|X=x)=\dfrac{Ï€_k \, f_k(x)}{\displaystyle\sum_{j=1}^k Ï€_jf_j(x)}\] Para que la clasificaciĂ³n basada en Bayes sea posible, se necesita conocer la probabilidad poblacional de que una observaciĂ³n cualquiera pertenezca a cada clase (\(Ï€_k\)) y la probabilidad poblacional de que una observaciĂ³n que pertenece a la clase \(k\) adquiera el valor \(x\) en el predictor (\(f_k(X)≡P(X=x|Y=k)\)). En la prĂ¡ctica, raramente se dispone de esta informaciĂ³n, por lo que los parĂ¡metros tienen que ser estimados a partir de la muestra. Como consecuencia, el clasificador LDA obtenido se aproxima al clasificador de Bayes.

EstimaciĂ³n de \(Ï€_k\) y \(f_k(X)\)

La capacidad del LDA para clasificar correctamente las observaciones depende de cĂ³mo de buenas sean las estimaciones de \(Ï€_k\) y \(f_k(X)\). Cuanto mĂ¡s cercanas al valor real, mĂ¡s se aproximarĂ¡ el clasificador LDA al clasificador de Bayes. En el caso de la prior probability (\(Ï€_k\)) la estimaciĂ³n suele ser sencilla, la probabilidad de que una observaciĂ³n cualquiera pertenezca a la clase \(k\) es igual al nĂºmero de observaciones de esa clase entre el nĂºmero total de observaciones \(\hat{Ï€}_k=\frac{n_k}{N}\).

La estimaciĂ³n de \(f_k(X)\) no es tan directa y para conseguirla se requiere de ciertas asunciones. Si se considera que \(f_k(X)\) se distribuye de forma normal en las \(K\) clases, entonces se puede estimar su valor a partir de la densidad normal.

Si ademĂ¡s se asume que la varianza es constante en todos los grupos \(σ^2_1=σ_2^2=\dots=σ^2_k=σ^2\), entonces, el sumatorio \(∑^K_{j=1} Ï€_jf_j(x)\) se simplifica en gran medida permitiendo calcular la probabilidad posterior segĂºn la ecuaciĂ³n:

\[P(Y=k|X=x)=\dfrac{Ï€_k\frac{1}{2Ï€}\exp\left(−\frac{1}{2σ^2}(x−μ_k)^2\right)}{\displaystyle\sum_{j=1}^k Ï€_j\frac{1}{2πσ}\exp \left(−\frac{1}{2σ^2}(x−μ_j)^2\right)}\] Esta ecuaciĂ³n se simplifica aun mĂ¡s mediante una transformaciĂ³n logarĂ­tmica de sus dos tĂ©rminos:

\[\log(P(Y=k|X=x))=x \frac{μ_k}{σ^2}−\frac{μ^2_k}{σ^2}+\log(Ï€_k)\] El tĂ©rmino lineal en el nombre AnĂ¡lisis discriminante lineal se debe al hecho de que la funciĂ³n discriminatoria es lineal respecto de \(X\).

En la prĂ¡ctica, a pesar de tener una certeza considerable de que \(X\) se distribuye de forma normal dentro de cada clase, los valores \(μ_1,\dots,μ_k\), \(Ï€_1,\dots,Ï€_k\) y \(σ^2\) se desconocen, por lo que tienen que ser estimados a partir de las observaciones. Las estimaciones empleadas en LDA son:

\[\begin{align*} \hat{μ}_k = & \frac{1}{n_k} \displaystyle\sum_{i:y_1} x_i \\ \hat{σ}_k = & \frac{1}{N−K} \displaystyle\sum_{k=1}^K \displaystyle\sum_{i:y_1} (x_i−\hat{μ}_k)^2 \\ \hat{\pi}_k = & \frac{n_k}{N} \end{align*}\]

\(\hat{μ}_k\) es la media de las observaciones del grupo \(k\), \(\hat{\sigma}_k\) es la media ponderada de las varianzas muestrales de las \(K\) clases y \(\hat{\pi}_k\) la proporciĂ³n de observaciones de la clase \(k\) respecto al tamaño total de la muestra.

La clasificaciĂ³n de Bayes consiste en asignar cada observaciĂ³n \(X = x\) a aquella clase para la que \(P(Y=k|X=x)\) sea mayor. En el caso particular de una variable cualitativa Y con solo dos niveles, se puede expresar la regla de clasificaciĂ³n como un cociente entre las dos probabilidades posteriores. Se asignarĂ¡ la observaciĂ³n a la clase 1 si \(\frac{P(Y=1|X=x)}{P(Y=2|X=x)}>1\), y a la clase 2 si es menor. En este caso particular el lĂ­mite de decisiĂ³n de Bayes viene dado por \(x=\frac{μ1+μ2}{2}\).

La siguiente imagen muestra dos grupos distribuidos de forma normal con medias \(μ_1= -1.25\), \(μ_2= 1.25\) y varianzas \(σ^2_1=σ^2_2= 1\). Dado que se conoce el valor real de las medias y varianzas poblacionales (esto en la realidad no suele ocurrir), se puede calcular el lĂ­mite de decisiĂ³n de Bayes $x== 0 (linea discontinua).

library(ggplot2)
## Warning in as.POSIXlt.POSIXct(Sys.time()): unable to identify current timezone 'H':
## please set environment variable 'TZ'
ggplot(data.frame(x = c(-4, 4)), aes(x)) +
stat_function(fun = dnorm, args = list(mean = -1.25, sd = 1),
              color = "firebrick") + 
stat_function(fun = dnorm, args = list(mean = 1.25, sd = 1), color = "green3") +
geom_vline(xintercept = 0, linetype = "longdash") +
theme_bw()

Si en lugar de conocer la verdadera distribuciĂ³n poblacional de cada grupo solo se dispone de muestras, escenario que suele ocurrir en los casos reales, el lĂ­mite de decisiĂ³n LDA se aproxima al verdadero lĂ­mite de decisiĂ³n de Bayes pero no es exacto. Cuanto mĂ¡s representativas sean las muestras mejor la aproximaciĂ³n.

set.seed(1981)
library(ggplot2)
n <-30
grupo_a <- rnorm(n, mean = -1.25, sd = 1)
grupo_b <- rnorm(n, mean = 1.25, sd = 1)
datos <- data.frame(valor = c(grupo_a, grupo_b),
                    grupo = rep(c("A","B"), each = n))

ggplot(data = datos, aes(x = valor, fill = grupo)) +
geom_histogram(alpha = 0.5, position = "identity") +
geom_vline(xintercept = 0, linetype = "longdash") +
geom_vline(xintercept = (mean(grupo_a) + mean(grupo_b))/2)  +
annotate(geom = "text", x = 1.5, y = 9, label = "LĂ­mite decisiĂ³n LDA") +
annotate(geom = "text", x = -1.5, y = 10, label = "LĂ­mite decisiĂ³n Bayes") +
theme_bw() + 
theme(legend.position = "top")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

ExtensiĂ³n del LDA para mĂºltiples predictores

Los conceptos anteriormente descritos empleando un Ăºnico predictor pueden generalizarse para introducir mĂºltiples predictores en el modelo. La diferencia reside en que \(X\), en lugar de ser un Ăºnico valor, es un vector formado por el valor de \(p\) predictores \(X=(X_1,X_2,\dots,X_n)\) y que, en lugar de proceder de una distribuciĂ³n normal, procede de una distribuciĂ³n normal multivariante.

Un vector sigue una distribuciĂ³n \(k\)-normal multivariante si cada uno de los elementos individuales que lo forman sigue una distribuciĂ³n normal y lo mismo para toda combinaciĂ³n lineal de sus \(k\) elementos. Las siguientes imĂ¡genes muestran representaciones grĂ¡ficas de distribuciones normales multivariante de 2 elementos (distribuciĂ³n normal bivariante).

mu1 <- 0 # set mean x1
mu2 <- 0 # set mean x2
s11 <- 10 # set variance x1
s22 <- 10 # set variance x2
s12 <- 15 # set covariance x1 and x2
rho <- 0.5 # set correlation coefficient  x1 and x2
x1 <- seq(-10,10,length = 41) # generate vector  x1
x2 <- x1 # copy x1 to x2

f <- function(x1,x2) # multivariate function
{
  term1 <- 1/(2 * pi * sqrt(s11*s22*(1 - rho^2)))
  term2 <- -1/(2 * (1 - rho^2))
  term3 <- (x1 - mu1)^2/s11
  term4 <- (x2 - mu2)^2/s22
  term5 <- -2*rho*((x1 - mu1)*(x2 - mu2))/(sqrt(s11)*sqrt(s22))
  term1*exp(term2*(term3 + term4 - term5))
} 

z <- outer(x1,x2,f) # calculate density values

persp(x1, x2, z, # 3-D plot
  main = "DistribuciĂ³n multivariante con dos predictores",
  col = "lightgreen",
  theta = 30, phi = 20,
  r = 50,
  d = 0.1,
  expand = 0.5,
  ltheta = 90, lphi = 180,
  shade = 0.75,
  ticktype = "simple",
  nticks = 5)

# Otra forma de representar una distribuciĂ³n bivariante
library(mvtnorm)
library(scatterplot3d)

ss <- matrix(c(1,0,0,1), ncol = 2) 
x1000 <- rmvnorm(n = 1000, mean = c(0,0), sigma = ss)
scatterplot3d(x1000[,1], x1000[,2],
              dmvnorm(x1000, mean = c(0,0), sigma = ss),
              highlight = TRUE, xlab = "x", ylab = "y", zlab = "z")

Condiciones de LDA

Las condiciones que se deben cumplir para que un AnĂ¡lisis Discriminante Lineal sea vĂ¡lido son:

  • Cada predictor que forma parte del modelo se distribuye de forma normal en cada una de las clases de la variable respuesta. En el caso de mĂºltiples predictores, las observaciones siguen una distribuciĂ³n normal multivariante en todas las clases.

  • La varianza del predictor es igual en todas las clases de la variable respuesta. En el caso de mĂºltiples predictores, la matriz de covarianza es igual en todas las clases. Si esto no se cumple se recurre a AnĂ¡lisis Discriminante CuadrĂ¡tico (QDA).

  • Cuando la condiciĂ³n de normalidad no se cumple, LDA pierde precisiĂ³n pero aun asĂ­ puede llegar a clasificaciones relativamente buenas.

Ejemplo datos insectos

Un equipo de biĂ³logos quiere generar un modelo estadĂ­stico que permita identificar a que especie \((a \text{ o } b)\) pertenece un determinado insecto. Para ello se han medido tres variables (longitud de las patas, diĂ¡metro del abdomen y diĂ¡metro del Ă³rgano sexual) en 10 individuos de cada una de las dos especies.

input <- ("
especie pata abdomen organo_sexual 
a 191 131 53
a 185 134 50
a 200 137 52
a 173 127 50
a 171 128 49
a 160 118 47
a 188 134 54
a 186 129 51
a 174 131 52
a 163 115 47
b 186 107 49
b 211 122 49
b 201 144 47
b 242 131 54
b 184 108 43
b 211 118 51
b 217 122 49
b 223 127 51
b 208 125 50
b 199 124 46
")
datos <- read.table(textConnection(input), header = TRUE)

ExploraciĂ³n grĂ¡fica de los datos

library(ggplot2)
library(ggpubr)
## Loading required package: magrittr
p1 <- ggplot(data = datos, aes(x = pata, fill = especie)) +
      geom_histogram(position = "identity", alpha = 0.5)
p2 <- ggplot(data = datos, aes(x = abdomen, fill = especie)) +
      geom_histogram(position = "identity", alpha = 0.5)
p3 <- ggplot(data = datos, aes(x = organo_sexual, fill = especie)) +
      geom_histogram(position = "identity", alpha = 0.5)
ggarrange(p1, p2, p3, nrow = 3, common.legend = TRUE)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

A nivel individual, la longitud de la pata parece ser la variable que mĂ¡s se diferencia entre especies (menor solapamiento entre poblaciones).

pairs(x = datos[, c("pata","abdomen","organo_sexual")],
      col = c("blue", "green4")[datos$especie], pch = 19)

El par de variables abdomen-pata y el par pata-organo_sexual parecen separar bien las dos especies.

library(scatterplot3d)
scatterplot3d(datos$pata, datos$abdomen, datos$organo_sexual,
              color = c("firebrick", "green3")[datos$especie], pch = 19,
              grid = TRUE, xlab = "pata", ylab = "abdomen",
              zlab = "organo sexual", angle = 65, cex.axis = 0.6)
legend("topleft",
       bty = "n", cex = 0.8,
       title = "Especie",
       c("a", "b"), fill = c("firebrick", "green3"))

La representaciĂ³n de las tres variables de forma simultĂ¡nea parece indicar que las dos especies sĂ­ estĂ¡n bastante separadas en el espacio 3D generado.

Probabilidades a priori

Como no se dispone de informaciĂ³n sobre la abundancia relativa de las especies a nivel poblacional, se considera como probabilidad a pirori de cada especie el nĂºmero de observaciones de la especie entre el nĂºmero de observaciones totales.

\[\hat{Ï€}_a=\hat{Ï€}_b=\dfrac{10}{20}=0.5\]

Homogeneidad de Varianza

De entre los diferentes test que contrastan la homogeneidad de varianza, el mĂ¡s recomendable cuando solo hay un predictor, dado que se asume que se distribuye de forma normal, es el test de Barttlet. Cuando se emplean mĂºltiples predictores, se tiene que contrastar que la matriz de covarianzas \(∑\) es constante en todos los grupos, siendo recomendable comprobar tambiĂ©n la homogeneidad de varianza para cada predictor a nivel individual.

El test Box fue desarrollado por el matemĂ¡tico Box (1949) como una extensiĂ³n del test de Barttlet para escenarios multivariante y permite contrastar la igualdad de matrices entre grupos. El test Box es muy sensible a violaciones de la normalidad multivariante, por lo que esta debe ser contrastada con anterioridad.

# RepresentaciĂ³n mediante Histograma de cada variable para cada especie 
par(mfcol = c(2, 3))
for (k in 2:4) {
  j0 <- names(datos)[k]
  #br0 <- seq(min(datos[, k]), max(datos[, k]), le = 11)
  x0 <- seq(min(datos[, k]), max(datos[, k]), le = 50)
  for (i in 1:2) {
    i0 <- levels(datos$especie)[i]
    x <- datos[datos$especie == i0, j0]
    hist(x, proba = T, col = grey(0.8), main = paste("especie", i0),
    xlab = j0)
    lines(x0, dnorm(x0, mean(x), sd(x)), col = "red", lwd = 2)
  }
}

# RepresentaciĂ³n de cuantiles normales de cada variable para cada especie 
par(mfcol = c(2, 3))
for (k in 2:4) {
  j0 <- names(datos)[k]
  x0 <- seq(min(datos[, k]), max(datos[, k]), le = 50)
  for (i in 1:2) {
    i0 <- levels(datos$especie)[i]
    x <- datos[datos$especie == i0, j0]
    qqnorm(x, main = paste("especie", i0, j0), pch = 19, col = i + 1)
    qqline(x)
  }
}

# Contraste de normalidad Shapiro-Wilk para cada variable en cada especie
library(reshape2)
library(knitr)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
datos_tidy <- melt(datos, value.name = "valor")
## Using especie as id variables
kable(datos_tidy %>% group_by(especie, variable) %>% summarise(p_value_Shapiro.test = shapiro.test(valor)$p.value))
especie variable p_value_Shapiro.test
a pata 0.7763034
a abdomen 0.1845349
a organo_sexual 0.6430844
b pata 0.7985711
b abdomen 0.5538213
b organo_sexual 0.8217855
# Misma operaciĂ³n con aggregate
aggregate(formula = valor ~ especie + variable, data = datos_tidy,
          FUN = function(x){shapiro.test(x)$p.value})
##   especie      variable     valor
## 1       a          pata 0.7763034
## 2       b          pata 0.7985711
## 3       a       abdomen 0.1845349
## 4       b       abdomen 0.5538213
## 5       a organo_sexual 0.6430844
## 6       b organo_sexual 0.8217855

No hay evidencias de falta de normalidad univariante en ninguna de las variables empleadas como predictores en ninguno de los grupos.

El paquete MVN contiene funciones que permiten realizar los tres test de hipĂ³tesis comĂºnmente empleados para evaluar la normalidad multivariante (Mardia, Henze-Zirkler y Royston) y tambiĂ©n funciones para identificar outliers que puedan influenciar en el contraste. Para informaciĂ³n detallada de cada uno consultar https://cran.r-project.org/web/packages/MVN/vignettes/MVN.pdf.

library(MVN)
## Registered S3 method overwritten by 'GGally':
##   method from   
##   +.gg   ggplot2
## sROC 0.1-2 loaded
outliers <- mvn(data = datos[,-1], mvnTest = "hz", multivariateOutlierMethod = "quan")

royston_test <- mvn(data = datos[,-1], mvnTest = "royston", multivariatePlot = "qq")

royston_test$multivariateNormality
##      Test         H   p value MVN
## 1 Royston 0.4636176 0.9299447 YES
hz_test <- mvn(data = datos[,-1], mvnTest = "hz")
hz_test$multivariateNormality
##            Test        HZ    p value MVN
## 1 Henze-Zirkler 0.7870498 0.07666139 YES

A pesar de los 5 outliers detectados, ninguno de los dos test encuentran evidencias significativas \((α=0.05)\) de falta de normalidad multivariante.

Finalmente, mediante la funciĂ³n boxM() del paquete biotools se realiza el contraste de matrices de covarianza.

library(biotools)
## Loading required package: rpanel
## Loading required package: tcltk
## Package `rpanel', version 1.1-4: type help(rpanel) for summary information
## Loading required package: tkrplot
## Loading required package: MASS
## 
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
## 
##     select
## Loading required package: lattice
## Loading required package: SpatialEpi
## Loading required package: sp
## ---
## biotools version 3.1
## 
boxM(data = datos[, 2:4], grouping = datos[, 1])
## 
##  Box's M-test for Homogeneity of Covariance Matrices
## 
## data:  datos[, 2:4]
## Chi-Sq (approx.) = 9.831, df = 6, p-value = 0.132

Se puede aceptar que la matriz de covarianza es igual en todos los grupos.

EstimaciĂ³n de los parĂ¡metros de la funciĂ³n de densidad \((\hat{μ}(X),∑)\) y cĂ¡lculo de la funciĂ³n discriminante.

Estos dos pasos se realizan mediante la funciĂ³n lda del paquete MASS. lda realiza la clasificaciĂ³n mediante la aproximaciĂ³n de Fisher.

modelo_lda <- lda(formula = especie ~ pata + abdomen + organo_sexual,
                  data = datos)

Una vez obtenidas las funciones discriminantes, se puede clasificar un nuevo insecto en funciĂ³n de sus medidas. Por ejemplo, un nuevo espĂ©cimen cuyas medidas sean: pata = 194, abdomen = 124, organo_sexual = 49.

nuevas_observaciones <- data.frame(pata = 194, abdomen = 124,
                                   organo_sexual = 49)
predict(object = modelo_lda, newdata = nuevas_observaciones)
## $class
## [1] b
## Levels: a b
## 
## $posterior
##            a         b
## 1 0.05823333 0.9417667
## 
## $x
##         LD1
## 1 0.5419421

El resultado muestra que, segĂºn la funciĂ³n discriminante, la probabilidad posterior de que el espĂ©cimen pertenezca a la especie \(b\) es del \(94.2\%\) frente al \(5.8\%\) de que pertenezca a la especie \(a\).

EvaluaciĂ³n de los errores de clasificaciĂ³n

predicciones <- predict(object = modelo_lda, newdata = datos[, -1],
                        method = "predictive")
table(datos$especie, predicciones$class,
      dnn = c("Clase real", "Clase predicha"))
##           Clase predicha
## Clase real  a  b
##          a 10  0
##          b  0 10
trainig_error <- mean(datos$especie != predicciones$class) * 100
paste("trainig_error=", trainig_error, "%")
## [1] "trainig_error= 0 %"

Empleando las mismas observaciones con las que se ha generado el modelo discriminante (trainig data), la precisiĂ³n de clasificaciĂ³n es del \(100\%\). Evaluar un modelo con los mismos datos con los que se ha creado suele resultar en estimaciones de la precisiĂ³n demasiado optimistas (error muy bajo). Por lo tanto, la estimaciĂ³n del test error mediante validaciĂ³n cruzada es mĂ¡s adecuada para obtener una evaluaciĂ³n realista del modelo.

La siguiente imagen muestra la representaciĂ³n de las observaciones, coloreadas por la verdadera especie a la que pertenecen y acompañadas por una etiqueta con la especie que ha predicho el LDA.

with(datos, {
  s3d <- scatterplot3d(pata, abdomen, organo_sexual,
                       color = c("firebrick", "green3")[datos$especie],
                       pch = 19, grid = TRUE, xlab = "pata", ylab = "abdomen",
                       zlab = "organo sexual", angle = 65, cex.axis = 0.6)
  
  s3d.coords <- s3d$xyz.convert(pata, abdomen, organo_sexual)
  # convierte coordenadas 3D en proyecciones 2D
    
  text(s3d.coords$x, s3d.coords$y, # cordenadas x, y
       labels = datos$especie,     # texto
       cex = .8, pos = 4)   
    
  legend("topleft", 
         bty = "n", cex = 0.8,
         title = "Especie",
         c("a", "b"), fill = c("firebrick", "green3"))
})

Ejemplo con Iris data

data("iris")
kable(head(iris, n = 10))
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
5.1 3.5 1.4 0.2 setosa
4.9 3.0 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
5.0 3.6 1.4 0.2 setosa
5.4 3.9 1.7 0.4 setosa
4.6 3.4 1.4 0.3 setosa
5.0 3.4 1.5 0.2 setosa
4.4 2.9 1.4 0.2 setosa
4.9 3.1 1.5 0.1 setosa

probabilidades a priori

Como no se dispone de informaciĂ³n sobre la abundancia relativa de las especies a nivel poblacional, se considera como probabilidad previa de cada especie el nĂºmero de observaciones de la especie entre el nĂºmero de observaciones totales.

\[\hat{Ï€}_{\text{setosa}}=\hat{Ï€}_{\text{versicolor}}=\hat{Ï€}_{\text{virginica}}=\dfrac{50}{150}=\dfrac{1}{3}\]

Normalidad univariante, normalidad multivariante y homogeneidad de varianza

#representaciĂ³n mediante histograma de cada variable para cada especie 
par(mfcol = c(3, 4))
for (k in 1:4) {
  j0 <- names(iris)[k]
  x0 <- seq(min(iris[, k]), max(iris[, k]), le = 50)
  for (i in 1:3) {
    i0 <- levels(iris$Species)[i]
    x <- iris[iris$Species == i0, j0]
    hist(x, proba = T, col = grey(0.8), main = paste("especie", i0),
    xlab = j0)
    lines(x0, dnorm(x0, mean(x), sd(x)), col = "red", lwd = 2)
  }
}

#representaciĂ³n de cuantiles normales de cada variable para cada especie 
for (k in 1:4) {
  j0 <- names(iris)[k]
  x0 <- seq(min(iris[, k]), max(iris[, k]), le = 50)
  for (i in 1:3) {
    i0 <- levels(iris$Species)[i]
    x <- iris[iris$Species == i0, j0]
    qqnorm(x, main = paste(i0, j0), pch = 19, col = i + 1) 
    qqline(x)
  }
}

#Contraste de normalidad Shapiro-Wilk para cada variable en cada especie
library(reshape2)
library(knitr)
library(dplyr)
datos_tidy <- melt(iris, value.name = "valor")
## Using Species as id variables
kable(datos_tidy %>% group_by(Species, variable) %>% summarise(p_value_Shapiro.test = round(shapiro.test(valor)$p.value,5)))
Species variable p_value_Shapiro.test
setosa Sepal.Length 0.45951
setosa Sepal.Width 0.27153
setosa Petal.Length 0.05481
setosa Petal.Width 0.00000
versicolor Sepal.Length 0.46474
versicolor Sepal.Width 0.33800
versicolor Petal.Length 0.15848
versicolor Petal.Width 0.02728
virginica Sepal.Length 0.25831
virginica Sepal.Width 0.18090
virginica Petal.Length 0.10978
virginica Petal.Width 0.08695

Observamos que la variable petal.width no parece tener una distribuciĂ³n normal en los grupos setosa y versicolor.

Normalidad multivariante

library(MVN)
outliers <- mvn(data = iris[,-5], mvnTest = "hz", multivariateOutlierMethod = "quan")

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

royston_test$multivariateNormality
##      Test        H      p value MVN
## 1 Royston 50.39667 3.098229e-11  NO
hz_test <- mvn(data = iris[,-5], mvnTest = "hz")
hz_test$multivariateNormality
##            Test       HZ p value MVN
## 1 Henze-Zirkler 2.336394       0  NO

Ambos test muestran evidencias significativas de falta de normalidad multivariante. LDA tiene cierta robustez frente a la falta de normalidad multivariante, pero es importante tenerlo en cuenta en la conclusiĂ³n del anĂ¡lisis.

library(biotools)
boxM(data = iris[, -5], grouping = iris[, 5])
## 
##  Box's M-test for Homogeneity of Covariance Matrices
## 
## data:  iris[, -5]
## Chi-Sq (approx.) = 140.94, df = 20, p-value < 2.2e-16

El test Box muestra evidencias de que la matriz de covarianza no es constante en todos los grupos, lo que a priori descartarĂ­a el mĂ©todo LDA en favor del QDA. Sin embargo, como el test Box’s M es muy sensible a la falta de normalidad multivariante, con frecuencia resulta significativo no porque la matriz de covarianza no sea constante sino por la falta de normalidad, cosa que ocurre para los datos de Iris. Por esta razĂ³n se va a asumir que la matriz de covarianza sĂ­ es constante y que LDA puede alcanzar una buena precisiĂ³n en la clasificaciĂ³n. En la evaluaciĂ³n del modelo se verĂ¡ como de buena es esta aproximaciĂ³n. AdemĂ¡s, en las conclusiones se debe explicar la asunciĂ³n hecha.

FunciĂ³n discriminante

library(MASS)
modelo_lda <- lda(Species ~ Sepal.Width + Sepal.Length + Petal.Length +
                  Petal.Width, data = iris)
modelo_lda
## Call:
## lda(Species ~ Sepal.Width + Sepal.Length + Petal.Length + Petal.Width, 
##     data = iris)
## 
## Prior probabilities of groups:
##     setosa versicolor  virginica 
##  0.3333333  0.3333333  0.3333333 
## 
## Group means:
##            Sepal.Width Sepal.Length Petal.Length Petal.Width
## setosa           3.428        5.006        1.462       0.246
## versicolor       2.770        5.936        4.260       1.326
## virginica        2.974        6.588        5.552       2.026
## 
## Coefficients of linear discriminants:
##                     LD1         LD2
## Sepal.Width   1.5344731  2.16452123
## Sepal.Length  0.8293776  0.02410215
## Petal.Length -2.2012117 -0.93192121
## Petal.Width  -2.8104603  2.83918785
## 
## Proportion of trace:
##    LD1    LD2 
## 0.9912 0.0088

EvaluaciĂ³n de los errores de clasificaciĂ³n

predicciones <- predict(object = modelo_lda, newdata = iris[, -5])
table(iris$Species, predicciones$class, dnn = c("Clase real", "Clase predicha"))
##             Clase predicha
## Clase real   setosa versicolor virginica
##   setosa         50          0         0
##   versicolor      0         48         2
##   virginica       0          1        49
trainig_error <- mean(iris$Species != predicciones$class) * 100
paste("trainig_error =", trainig_error, "%")
## [1] "trainig_error = 2 %"

Solo 3 de las 150 predicciones que ha realizado el modelo han sido errĂ³neas. El error es muy bajo (\(2\%\)), lo que apunta a que el modelo es bueno. Sin embargo, para validarlo es necesario un nuevo set de datos con el que calcular el test error o recurrir a validaciĂ³n cruzada.

VisualizaciĂ³n de las clasificaciones

La funciĂ³n partimat() del paquete klar permite representar los lĂ­mites de clasificaciĂ³n de un modelo discriminante lineal o cuadrĂ¡tico para cada par de predictores. Cada color representa una regiĂ³n de clasificaciĂ³n acorde al modelo, se muestra el centroide de cada regiĂ³n y el valor real de las observaciones.

library(klaR)
partimat(Species ~ Sepal.Width + Sepal.Length + Petal.Length + Petal.Width,
         data = iris, method = "lda", prec = 200,
         image.colors = c("darkgoldenrod1", "snow2", "skyblue2"),
         col.mean = "firebrick")

AnĂ¡lisis Discriminante CuadrĂ¡tico

El clasificador cuadrĂ¡tico o Quadratic Discriminat Analysis (QDA) se asemeja en gran medida al LDA, con la Ăºnica diferencia de que el QDA considera que cada clase \(k\) tiene su propia matriz de covarianza (\(∑_k\)) y, como consecuencia, la funciĂ³n discriminante toma forma cuadrĂ¡tica:

\[\log(P(Y=k|X=x))=−\dfrac{1}{2}\log|Σ_k|−\dfrac{1}{2}(x−μ_k)^T Σ^{−1}_k(x−μ_k)+\log(π_k)\]

Para poder calcular la probabilidad posteriro a partir de esta ecuaciĂ³n discriminante es necesario estimar, para cada clase, \(∑_k\), \(μ_k\) y \(Ï€_k\) a partir de la muestra.

QDA genera lĂ­mites de decisiĂ³n curvos por lo que puede aplicarse a situaciones en las que la separaciĂ³n entre grupos no es lineal.

Ejemplo QDA con 2 predictores

Se dispone de los siguientes datos simulados.

set.seed(8558)
grupoA_x <- seq(from = -3, to = 4, length.out = 100)
grupoA_y <- 6 + 0.15 * grupoA_x - 0.3 * grupoA_x^2 + rnorm(100, sd = 1)
grupoA <- data.frame(variable_z = grupoA_x, variable_w = grupoA_y, grupo = "A")

grupoB_x <- rnorm(n = 100, mean = 0.5, sd = 0.8)
grupoB_y <- rnorm(n = 100, mean = 2, sd = 0.8)
grupoB <- data.frame(variable_z = grupoB_x, variable_w = grupoB_y, grupo = "B")

datos <- rbind(grupoA, grupoB)
plot(datos[, 1:2], col = datos$grupo, pch = 19)

La separaciĂ³n entre los grupos no es de tipo lineal, sino que muestra cierta curvatura. En este tipo de escenarios el mĂ©todo QDA es mĂ¡s adecuado que el LDA.

library(ggplot2)
library(ggpubr)
p1 <- ggplot(data = datos, aes(x = variable_z, fill = grupo)) +
      geom_histogram(position = "identity", alpha = 0.5)
p2 <- ggplot(data = datos, aes(x = variable_w, fill = grupo)) +
      geom_histogram(position = "identity", alpha = 0.5)
ggarrange(p1, p2, nrow = 2, common.legend = TRUE, legend = "bottom")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

La variable \(W\) permite discriminar entre grupos mejor que la variable \(z\).

Normalidad univariante, normalidad multivariante y homogeneidad de varianza

DistribuciĂ³n de los predictores de forma individual:

# RepresentaciĂ³n mediante histograma de cada variable para cada grupo 
par(mfcol = c(2, 2))
for (k in 1:2) {
  j0 <- names(datos)[k]
  x0 <- seq(min(datos[, k]), max(datos[, k]), le = 50)
  for (i in 1:2) {
    i0 <- levels(datos$grupo)[i]
    x <- datos[datos$grupo == i0, j0]
    hist(x, proba = T, col = grey(0.8), main = paste("grupo", i0),
    xlab = j0)
    lines(x0, dnorm(x0, mean(x), sd(x)), col = "red", lwd = 2)
  }
}

#representaciĂ³n de cuantiles normales de cada variable para cada grupo 
for (k in 1:2) {
  j0 <- names(datos)[k]
  x0 <- seq(min(datos[, k]), max(datos[, k]), le = 50)
  for (i in 1:2) {
    i0 <- levels(datos$grupo)[i]
    x <- datos[datos$grupo == i0, j0]
    qqnorm(x, main = paste(i0, j0), pch = 19, col = i + 1)
    qqline(x)
  }
}

#Contraste de normalidad Shapiro-Wilk para cada variable en cada grupo
library(reshape2)
datos_tidy <- melt(datos, value.name = "valor")
## Using grupo as id variables
library(dplyr)
datos_tidy %>%
  group_by(grupo, variable) %>% 
  summarise(p_value_Shapiro.test = round(shapiro.test(valor)$p.value,5))
## # A tibble: 4 x 3
## # Groups:   grupo [2]
##   grupo variable   p_value_Shapiro.test
##   <fct> <fct>                     <dbl>
## 1 A     variable_z              0.00172
## 2 A     variable_w              0.0932 
## 3 B     variable_z              0.625  
## 4 B     variable_w              0.810

La variable \(Z\) no se distribuye de forma normal en el grupo \(A\).

library(MVN)
outliers <- mvn(data = datos[,-3], mvnTest = "hz", multivariateOutlierMethod = "quan")

royston_test <- mvn(data = datos[,-3], mvnTest = "royston", multivariatePlot = "qq")

royston_test$multivariateNormality
##      Test        H     p value MVN
## 1 Royston 29.11024 4.77274e-07  NO
hz_test <- mvn(data = datos[,-3], mvnTest = "hz")
hz_test$multivariateNormality
##            Test       HZ      p value MVN
## 1 Henze-Zirkler 6.739874 5.317968e-14  NO

Ambos test muestran evidencias significativas de falta de normalidad multivariante. QDA tiene cierta robustez frente a la falta de normalidad multivariante, pero es importante tenerlo en cuenta en la conclusiĂ³n del anĂ¡lisis.

library(MASS)
modelo_qda <- qda(grupo ~ variable_z + variable_w, data = datos)
modelo_qda
## Call:
## qda(grupo ~ variable_z + variable_w, data = datos)
## 
## Prior probabilities of groups:
##   A   B 
## 0.5 0.5 
## 
## Group means:
##   variable_z variable_w
## A  0.5000000   4.615307
## B  0.4864889   1.992911
predicciones <- predict(object = modelo_qda, newdata = datos)
table(datos$grupo, predicciones$class,
      dnn = c("Clase real", "Clase predicha"))
##           Clase predicha
## Clase real  A  B
##          A 97  3
##          B  7 93
trainig_error <- mean(datos$grupo != predicciones$class) * 100
paste("trainig_error=",trainig_error,"%")
## [1] "trainig_error= 5 %"
library(klaR)
partimat(formula = grupo ~ variable_z + variable_w, data = datos,
         method = "qda", prec = 400,
         image.colors = c("darkgoldenrod1", "skyblue2"),
         col.mean = "firebrick")