Untitled

Análisis Discriminante en RStudio

Cargar librerías y escalar

# install.packages("datasetsICR")

library(datasetsICR)
library(MASS)
library(tidyverse)
Warning: package 'tidyverse' was built under R version 4.3.3
Warning: package 'lubridate' was built under R version 4.3.3
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.3     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.4     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.0
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
✖ dplyr::select() masks MASS::select()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# Cargar los datos
data(seeds)

# Escalado de variables numéricas
semillas_escaladas <- seeds %>%
  mutate(across(where(is.numeric), scale))

# Vista previa
head(semillas_escaladas)
         area    perimeter  compactness length of kernel width of kernel
1  0.14175904  0.214948819 6.045733e-05       0.30349301       0.1413640
2  0.01116136  0.008204153 4.274938e-01      -0.16822270       0.1969616
3 -0.19160873 -0.359341919 1.438945e+00      -0.76181710       0.2075516
4 -0.34626388 -0.474200066 1.036904e+00      -0.68733567       0.3187467
5  0.44419577  0.329806966 1.371233e+00       0.06650665       0.8032397
6 -0.16067770 -0.267455401 1.019976e+00      -0.54740087       0.1413640
  asymmetry coefficient length of kernel groove variety
1            -0.9838010              -0.3826631    Kama
2            -1.7839036              -0.9198156    Kama
3            -0.6658882              -1.1863572    Kama
4            -0.9585276              -1.2270506    Kama
5            -1.5597684              -0.4742231    Kama
6            -0.8235144              -0.9198156    Kama

Se cargan los paquetes necesarios y se utiliza el dataset seeds que incluye mediciones morfológicas de tres variedades de semillas de trigo: Kama, Rosa y Canadiense.

Todas las variables numéricas se estandarizan con la función scale para que tengan media cero y desviación estándar uno. Esta normalización es fundamental para evitar que variables con escalas diferentes dominen el análisis, lo cual podría sesgar los resultados del modelo LDA.

Exploración inicial y Limpieza

# Verificar valores faltantes
colSums(is.na(semillas_escaladas))
                                                        variety 
      0       0       0       0       0       0       0       0 
# Visualizar posibles outliers por clase
semillas_escaladas %>%
  pivot_longer(cols = -variety) %>%
  ggplot(aes(x = variety, y = value)) +
  geom_boxplot() +
  facet_wrap(~ name, scales = "free") +
  theme_minimal() +
  labs(title = "Boxplots por variable - Datos escalados")

# Resumen estadístico
summary(semillas_escaladas)
       area.V1            perimeter.V1        compactness.V1   
 Min.   :-1.4632177   Min.   :-1.6457532   Min.   :-2.6618758  
 1st Qu.:-0.8858385   1st Qu.:-0.8494033   1st Qu.:-0.5966534  
 Median :-0.1692697   Median :-0.1832261   Median : 0.1037448  
 Mean   : 0.0000000   Mean   : 0.0000000   Mean   : 0.0000000  
 3rd Qu.: 0.8445808   3rd Qu.: 0.8849547   3rd Qu.: 0.7099807  
 Max.   : 2.1763334   Max.   : 2.0603364   Max.   : 2.0018026  
 length of kernel.V1   width of kernel.V1  asymmetry coefficient.V1
 Min.   :-1.6465662   Min.   :-1.6642328   Min.   :-1.952105       
 1st Qu.:-0.8267062   1st Qu.:-0.8329169   1st Qu.:-0.757338       
 Median :-0.2370616   Median :-0.0571987   Median :-0.067308       
 Mean   : 0.0000000   Mean   : 0.0000000   Mean   : 0.000000       
 3rd Qu.: 0.7927006   3rd Qu.: 0.8025778   3rd Qu.: 0.710681       
 Max.   : 2.3618888   Max.   : 2.0502135   Max.   : 3.163032       
 length of kernel groove.V1     variety  
 Min.   :-1.8089658         Kama    :70  
 1st Qu.:-0.7387301         Rosa    :70  
 Median :-0.3765590         Canadian:70  
 Mean   : 0.0000000                      
 3rd Qu.: 0.9541143                      
 Max.   : 2.3234463                      
# Pairs plot coloreado por clase
pairs(semillas_escaladas[1:7], col = semillas_escaladas$variety, pch = 19)

El conjunto de datos no presenta valores faltantes, lo cual permite trabajar directamente sin necesidad de imputación.

A través de los diagramas de caja por clase, se visualiza la distribución de cada variable. Algunas variables, como area y surco, muestran valores que podrían considerarse atípicos, aunque no de forma extrema. Esta exploración ayuda a identificar posibles distorsiones antes del análisis discriminante.

Ajuste del modelo LDA

# Ajustar modelo LDA
set.seed(123)
modelo_lda <- lda(variety ~ ., data = semillas_escaladas)
modelo_lda
Call:
lda(variety ~ ., data = semillas_escaladas)

Prior probabilities of groups:
     Kama      Rosa  Canadian 
0.3333333 0.3333333 0.3333333 

Group means:
               area  perimeter compactness `length of kernel` `width of kernel`
Kama     -0.1763396 -0.2029161   0.3839040         -0.2719163         -0.037002
Rosa      1.1983237  1.2071044   0.5297876          1.1725075          1.108799
Canadian -1.0219841 -1.0041884  -0.9136916         -0.9005912         -1.071797
         `asymmetry coefficient` `length of kernel groove`
Kama                 -0.68690313                -0.6528380
Rosa                 -0.03684659                 1.2462927
Canadian              0.72374972                -0.5934547

Coefficients of linear discriminants:
                                  LD1         LD2
area                       1.23306839 -12.2071106
perimeter                 -4.96159833  11.1082183
compactness               -0.14006876   2.0553411
`length of kernel`         2.65315093   3.4694860
`width of kernel`         -0.01399365  -0.2697275
`asymmetry coefficient`    0.06773106  -0.4830235
`length of kernel groove` -1.53247351  -3.3980221

Proportion of trace:
   LD1    LD2 
0.6814 0.3186 

El modelo de Análisis Discriminante Lineal (LDA) se ajusta utilizando todas las variables como predictoras y la variedad de semilla como variable de clasificación.

El resumen del modelo muestra que se generan dos funciones discriminantes (LD1 y LD2), que explican la mayor parte de la variabilidad entre clases. LD1 concentra la mayor proporción de información discriminante, mientras que LD2 añade una contribución menor pero útil.

Proyección en funciones discriminantes

proyeccion_lda <- predict(modelo_lda)$x %>% as.data.frame() %>%
  mutate(clase = semillas_escaladas$variety)

ggplot(proyeccion_lda, aes(LD1, LD2, color = clase)) +
  geom_point(size = 2) +
  labs(title = "Proyección LDA", x = "LD1", y = "LD2") +
  theme_minimal()

La representación en el plano definido por LD1 y LD2 permite observar una clara separación entre las tres variedades de semillas.

LD1 diferencia principalmente la clase Kama de las demás, mientras que LD2 contribuye a separar las clases Rosa y Canadiense. Esta proyección evidencia que las combinaciones lineales obtenidas por el modelo son efectivas para distinguir entre grupos.

Evaluación del modelo

# Predicciones del modelo (sin validación cruzada)
predicciones <- predict(modelo_lda)$class

# Matriz de confusión
matriz_confusion <- table(Real = semillas_escaladas$variety, Predicho = predicciones)
matriz_confusion
          Predicho
Real       Kama Rosa Canadian
  Kama       66    1        3
  Rosa        0   70        0
  Canadian    3    0       67
# Exactitud del modelo
exactitud <- sum(diag(matriz_confusion)) / sum(matriz_confusion)
exactitud
[1] 0.9666667

La matriz de confusión muestra una clasificación altamente precisa, con muy pocos errores.

La exactitud del modelo supera el 95 %, lo cual refleja un excelente desempeño al clasificar correctamente las semillas según su variedad. Cabe destacar que esta evaluación se realiza sobre los mismos datos usados para entrenar el modelo, por lo que los resultados pueden estar optimizados para esta muestra específica.

Análisis Discriminante en Python

Carga y limpieza (Python)

# %pip install pandas numpy scikit-learn matplotlib seaborn

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler

# Cargar el dataset Seeds desde UCI
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/00236/seeds_dataset.txt"
columnas = [
    "area", "perimetro", "compacidad", "longitud",
    "ancho", "asimetria", "surco", "clase"
]
semillas = pd.read_csv(url, sep="\s+", names=columnas)

# Mapear las clases a nombres descriptivos en español
mapa_clases = {1: "Kama", 2: "Rosa", 3: "Canadiense"}
semillas["variedad"] = semillas["clase"].map(mapa_clases)

# Escalar las variables numéricas
X = semillas.iloc[:, 0:7]
escalador = StandardScaler()
X_esc = escalador.fit_transform(X)

# Variable respuesta
y = semillas["variedad"]

Se cargan las librerías necesarias y se importa el dataset directamente desde el repositorio UCI.

Se renombran las columnas con nombres más descriptivos y se convierte la variable de clase de numérica a categórica (Kama, Rosa,Canadiense). Esto facilita la interpretación posterior. El resultado es un dataset limpio y preparado para el análisis discriminante.

Ajuste del modelo LDA

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

# Ajustar modelo LDA
modelo_lda = LinearDiscriminantAnalysis()
X_lda = modelo_lda.fit_transform(X_esc, y)

Se entrena el modelo LDA usando las variables morfológicas de las semillas como predictoras, y la variedad como objetivo.

La transformación del dataset original a través de fit_transform() genera nuevas variables (LD1 y LD2) que maximizan la separación entre las clases. Esta reducción permite visualizar mejor las diferencias entre grupos.

Proyección en LD1 y LD2

import matplotlib.pyplot as plt

# Construir DataFrame con las componentes
df_proj = pd.DataFrame({
    "LD1": X_lda[:, 0],
    "LD2": X_lda[:, 1],
    "variedad": y
})

# Graficar la proyección
plt.figure()
for clase, grupo in df_proj.groupby("variedad"):
    plt.scatter(grupo["LD1"], grupo["LD2"], label=clase, s=30)
plt.title("Proyección LDA (Python)")
plt.xlabel("LD1")
plt.ylabel("LD2")
plt.legend()
plt.grid(True)
plt.show()

Se crea una gráfica de dispersión utilizando las dos funciones discriminantes obtenidas.

El resultado muestra una clara separación entre las variedades de semillas, donde LD1 distingue principalmente la clase Kama, mientras LD2 ayuda a separar Rosa de Canadiense. La visualización confirma que el modelo fue capaz de encontrar ejes óptimos de discriminación.

Evaluación del modelo

from sklearn.metrics import confusion_matrix, accuracy_score

# Predicción dentro de muestra
y_pred = modelo_lda.predict(X_esc)

# Matriz de confusión y exactitud
print("Matriz de confusión:\n", confusion_matrix(y, y_pred))
Matriz de confusión:
 [[67  3  0]
 [ 3 66  1]
 [ 0  0 70]]
print("\nExactitud:", accuracy_score(y, y_pred))

Exactitud: 0.9666666666666667

Se calcula la matriz de confusión y la exactitud global del modelo, con base en las predicciones realizadas sobre los mismos datos de entrenamiento.

El modelo alcanza una tasa de clasificación correcta superior al 96%, lo que indica un excelente rendimiento.