library(randomForest)
## randomForest 4.7-1.1
## Type rfNews() to see new features/changes/bug fixes.
library(e1071)
library(rpart)
library(caret)
## Loading required package: ggplot2
## 
## Attaching package: 'ggplot2'
## The following object is masked from 'package:randomForest':
## 
##     margin
## Loading required package: lattice
library(class)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following object is masked from 'package:randomForest':
## 
##     combine
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

Traemos nuestro dataset para trabajar con él

data_obesidad = read.csv("/Users/macbookair/Desktop/R (MASTER)/ObesityDataSet_raw_and_data_sinthetic.csv", header = T)

Truncamos

columnas_a_truncar = c('Age','NCP', 'FCVC','CH2O','FAF', 'TUE')  # Lista de columnas a truncar

# Crear un nuevo dataframe con valores truncados
tabla_truncada = data_obesidad
tabla_truncada[, columnas_a_truncar] = lapply(tabla_truncada[, columnas_a_truncar], trunc)

Factorizamos variables

variables_a_factor = c('FCVC', "NCP", "CH2O", 'FAF','TUE','SMOKE','SCC','NObeyesdad' )

# Utilizamos lapply para convertir las variables a factor
tabla_truncada[, variables_a_factor] = lapply(tabla_truncada[, variables_a_factor], as.factor)

Creamos la tabla con la que trabajaremos para entrenar los modelos

columnas_a_eliminar = c('Height','Weight','family_history_with_overweight','NCP','CH2O')
t_buena = tabla_truncada %>%
  select(-one_of(columnas_a_eliminar))

Clasificación múltiple de la variable NObeyesdad (Nivel o grado de obesidad / Talla)

Iniciaremos probando con un modelo de SVM

# Establecemos semilla para reproducibilidad
set.seed(123)

# Creamos un índice de partición (80% entrenamiento, 20% prueba)
index = createDataPartition(t_buena$SMOKE, p = 0.8, list = FALSE)

# Creamos conjuntos de entrenamiento y prueba
train_data = t_buena[index, ]
test_data = t_buena[-index, ]

# Ajustamos el modelo SVM
modelo_svm = svm(NObeyesdad ~ ., data = train_data, kernel = "linear", cost = 10)

# Realizamos predicciones en el conjunto de prueba
predicciones = predict(modelo_svm, newdata = test_data)

# Evaluamos el rendimiento del modelo
matriz_confusion = table(predicciones, test_data$NObeyesdad)

# Imprimimos la matriz de confusión 
print(matriz_confusion)
##                      
## predicciones          Insufficient_Weight Normal_Weight Obesity_Type_I
##   Insufficient_Weight                  39            14              0
##   Normal_Weight                        10            15              1
##   Obesity_Type_I                        1             4             44
##   Obesity_Type_II                       0             5             11
##   Obesity_Type_III                      0             2              0
##   Overweight_Level_I                    4             8             15
##   Overweight_Level_II                   0             3              1
##                      
## predicciones          Obesity_Type_II Obesity_Type_III Overweight_Level_I
##   Insufficient_Weight               0                0                  1
##   Normal_Weight                     3                0                  7
##   Obesity_Type_I                    2                0                 19
##   Obesity_Type_II                  60                0                  1
##   Obesity_Type_III                  0               60                  0
##   Overweight_Level_I                0                0                 33
##   Overweight_Level_II               0                0                  2
##                      
## predicciones          Overweight_Level_II
##   Insufficient_Weight                   0
##   Normal_Weight                        10
##   Obesity_Type_I                       10
##   Obesity_Type_II                      16
##   Obesity_Type_III                      0
##   Overweight_Level_I                    5
##   Overweight_Level_II                  15
# Calculamos la precisión
accuracy = sum(diag(matriz_confusion)) / sum(matriz_confusion)
print(paste("Accuracy:", accuracy))
## [1] "Accuracy: 0.631828978622328"

El acuracy de 63% no se ve tan prometedor, sin embargo, considerando que estamos trabajando con una variable multiclase con 7 clases, vale la pena revisar otras métricas que nos ayuden a entender mejor el rendimiento del modelo.

# Matriz de confusión
conf_matrix = table(predicciones, test_data$NObeyesdad)

# Convertimos la matriz de confusión a un objeto 
matrix = caret::confusionMatrix(conf_matrix)

# Precisión por clase
precision_por_clase = matrix$byClass[, "Pos Pred Value"]
print("Precisión por Clase:")
## [1] "Precisión por Clase:"
print(precision_por_clase)
## Class: Insufficient_Weight       Class: Normal_Weight 
##                  0.7222222                  0.3260870 
##      Class: Obesity_Type_I     Class: Obesity_Type_II 
##                  0.5500000                  0.6451613 
##    Class: Obesity_Type_III  Class: Overweight_Level_I 
##                  0.9677419                  0.5076923 
## Class: Overweight_Level_II 
##                  0.7142857
# Sensibilidad por clase
sensibilidad_por_clase = matrix$byClass[, "Sensitivity"]
print("Sensibilidad por Clase:")
## [1] "Sensibilidad por Clase:"
print(sensibilidad_por_clase)
## Class: Insufficient_Weight       Class: Normal_Weight 
##                  0.7222222                  0.2941176 
##      Class: Obesity_Type_I     Class: Obesity_Type_II 
##                  0.6111111                  0.9230769 
##    Class: Obesity_Type_III  Class: Overweight_Level_I 
##                  1.0000000                  0.5238095 
## Class: Overweight_Level_II 
##                  0.2678571
# F1-score por clase
f1_score_por_clase = 2 * (precision_por_clase * sensibilidad_por_clase) / (precision_por_clase + sensibilidad_por_clase)
print("F1-score por Clase:")
## [1] "F1-score por Clase:"
print(f1_score_por_clase)
## Class: Insufficient_Weight       Class: Normal_Weight 
##                  0.7222222                  0.3092784 
##      Class: Obesity_Type_I     Class: Obesity_Type_II 
##                  0.5789474                  0.7594937 
##    Class: Obesity_Type_III  Class: Overweight_Level_I 
##                  0.9836066                  0.5156250 
## Class: Overweight_Level_II 
##                  0.3896104
# Precisión macro
precision_macro = mean(precision_por_clase, na.rm = TRUE)
print(paste("Precisión Macro:", precision_macro))
## [1] "Precisión Macro: 0.633312918075491"
# Sensibilidad macro
sensibilidad_macro = mean(sensibilidad_por_clase, na.rm = TRUE)
print(paste("Sensibilidad Macro:", sensibilidad_macro))
## [1] "Sensibilidad Macro: 0.620313510019392"
# F1-score macro
f1_score_macro = mean(f1_score_por_clase, na.rm = TRUE)
print(paste("F1-score Macro:", f1_score_macro))
## [1] "F1-score Macro: 0.608397651290322"

Podemos ver que el modelo es mejor para predecir algunas clases frente a otras. Por ejemplo, Obesidad tipo III tiene muy buenas métricas (Presición: 93 | Sensibilidad: 100 | F1: 98) Mientras que la clase Peso Normal tiene unas métricas bastante malas (Presición: 32 | Sensibilidad: 29 | F1: 30 ) El resto de las clases tienen métricas intermedias. En resumen este modelo es bueno para predecir algunas clases, pero no para predecir otras, esto es probablemente causado nuevamnete por el debalance que existe en el número de registros por clase y que dificulta el aprendizaje del modelo para esas clases que tienen menos registros.

Inentaremos ahora hacer un modelo de Random Forest

# Ajustamos el modelo de Random Forest
modelo_rf = randomForest(NObeyesdad ~ ., data = train_data, ntree = 100)

# Realizamos predicciones en el conjunto de prueba
predicciones = predict(modelo_rf, newdata = test_data)

# Matriz de confusión
conf_matriz = table(predicciones, test_data$NObeyesdad)
print("Matriz de Confusión:")
## [1] "Matriz de Confusión:"
print(conf_matriz)
##                      
## predicciones          Insufficient_Weight Normal_Weight Obesity_Type_I
##   Insufficient_Weight                  38             9              0
##   Normal_Weight                         5            26              1
##   Obesity_Type_I                        8            10             44
##   Obesity_Type_II                       0             3              6
##   Obesity_Type_III                      0             0              0
##   Overweight_Level_I                    2             3              6
##   Overweight_Level_II                   1             0             15
##                      
## predicciones          Obesity_Type_II Obesity_Type_III Overweight_Level_I
##   Insufficient_Weight               0               10                  2
##   Normal_Weight                     4               46                  8
##   Obesity_Type_I                    6                0                 23
##   Obesity_Type_II                  42                0                  1
##   Obesity_Type_III                  0                4                  0
##   Overweight_Level_I                2                0                 19
##   Overweight_Level_II              11                0                 10
##                      
## predicciones          Overweight_Level_II
##   Insufficient_Weight                   0
##   Normal_Weight                         6
##   Obesity_Type_I                       14
##   Obesity_Type_II                       5
##   Obesity_Type_III                      0
##   Overweight_Level_I                    0
##   Overweight_Level_II                  31
# Precisión por clase
precision_por_clase = diag(conf_matriz) / rowSums(conf_matriz)
print("Precisión por Clase:")
## [1] "Precisión por Clase:"
print(precision_por_clase)
## Insufficient_Weight       Normal_Weight      Obesity_Type_I     Obesity_Type_II 
##           0.6440678           0.2708333           0.4190476           0.7368421 
##    Obesity_Type_III  Overweight_Level_I Overweight_Level_II 
##           1.0000000           0.5937500           0.4558824
# Sensibilidad por clase
sensibilidad_por_clase = diag(conf_matriz) / colSums(conf_matriz)
print("Sensibilidad por Clase:")
## [1] "Sensibilidad por Clase:"
print(sensibilidad_por_clase)
## Insufficient_Weight       Normal_Weight      Obesity_Type_I     Obesity_Type_II 
##          0.70370370          0.50980392          0.61111111          0.64615385 
##    Obesity_Type_III  Overweight_Level_I Overweight_Level_II 
##          0.06666667          0.30158730          0.55357143
# F1-score por clase
f1_score_por_clase = 2 * (precision_por_clase * sensibilidad_por_clase) / (precision_por_clase + sensibilidad_por_clase)
print("F1-score por Clase:")
## [1] "F1-score por Clase:"
print(f1_score_por_clase)
## Insufficient_Weight       Normal_Weight      Obesity_Type_I     Obesity_Type_II 
##           0.6725664           0.3537415           0.4971751           0.6885246 
##    Obesity_Type_III  Overweight_Level_I Overweight_Level_II 
##           0.1250000           0.4000000           0.5000000
# Precisión Macro
precision_macro = mean(precision_por_clase, na.rm = TRUE)
print(paste("Precisión Macro:", precision_macro))
## [1] "Precisión Macro: 0.588631886742208"
# Sensibilidad Macro
sensibilidad_macro = mean(sensibilidad_por_clase, na.rm = TRUE)
print(paste("Sensibilidad Macro:", sensibilidad_macro))
## [1] "Sensibilidad Macro: 0.484656854194669"
# F1-score Macro
f1_score_macro = mean(f1_score_por_clase, na.rm = TRUE)
print(paste("F1-score Macro:", f1_score_macro))
## [1] "F1-score Macro: 0.462429657098132"

Definitivamente el modelo de SVM tuvo un desempeño mejor en casi todas las métricas, a nivel individual en cada clase y a nivel macro.

Veremos la importancia de cada variable en el modelo SVM para ver si podemos mejorar el rendimiento a través de eliminar algunas de las variables.

# Ajustar el modelo SVM
modelo_svm = randomForest(NObeyesdad ~ ., data = train_data)

# Obtener importancia de variables
importancia = importance(modelo_svm)

# Visualizar importancia
print(importancia)
##        MeanDecreaseGini
## Gender       100.249425
## Age          236.577484
## FAVC          48.222645
## FCVC         146.902305
## CAEC         101.018506
## SMOKE          9.022168
## SCC           23.834808
## FAF          103.319728
## TUE           68.291978
## CALC          90.386321
## MTRANS        74.333798

De acuerdo al índice Gini, las variables SMOKE, SCC, FAVC e incluso TUE están agregando ruido al modelo y no contribuyen tanto a la predicción de NObeyesidad. Hace sentido que SMOKE Y SCC no sean tan buenas para el modelo debido a su fuerte desbalance de clases. Vamos a crear un nuevo dataset excluyendo estas variables para ver si el modelo funciona mejor.

# Eliminamos las variables SMOKE, SCC, FAVC y TUE
t_fit = subset(t_buena, select = -c(SMOKE, SCC, FAVC, TUE))

# Establecemos semilla para reproducibilidad
set.seed(123)

# Creamos un índice de partición (80% entrenamiento, 20% prueba)
index = createDataPartition(t_fit$CALC, p = 0.8, list = FALSE)
## Warning in createDataPartition(t_fit$CALC, p = 0.8, list = FALSE): Some classes
## have a single record ( Always ) and these will be selected for the sample
# Creamos conjuntos de entrenamiento y prueba
train_datafit = t_fit[index, ]
test_datafit = t_fit[-index, ]

# Ajusta el modelo SVM
modelo_svmfit = svm(NObeyesdad ~ ., data = train_datafit, kernel = "linear", cost = 10)

# Realizamos predicciones en el conjunto de prueba
prediccionesfit = predict(modelo_svmfit, newdata = test_datafit)

# Evaluamos el rendimiento del modelo
matriz_fit = table(predicciones, test_datafit$NObeyesdad)

# Imprime la matriz de confusión 
print(matriz_fit)
##                      
## predicciones          Insufficient_Weight Normal_Weight Obesity_Type_I
##   Insufficient_Weight                  37             9              1
##   Normal_Weight                         4            24              6
##   Obesity_Type_I                       10             6             43
##   Obesity_Type_II                       0             1              7
##   Obesity_Type_III                      0             0              0
##   Overweight_Level_I                    5             3              7
##   Overweight_Level_II                   3             4             14
##                      
## predicciones          Obesity_Type_II Obesity_Type_III Overweight_Level_I
##   Insufficient_Weight               0                9                  0
##   Normal_Weight                     3               46                  7
##   Obesity_Type_I                    6                1                 23
##   Obesity_Type_II                  36                6                  2
##   Obesity_Type_III                  0                4                  0
##   Overweight_Level_I                1                0                 15
##   Overweight_Level_II              11                0                 10
##                      
## predicciones          Overweight_Level_II
##   Insufficient_Weight                   3
##   Normal_Weight                         6
##   Obesity_Type_I                       16
##   Obesity_Type_II                       5
##   Obesity_Type_III                      0
##   Overweight_Level_I                    1
##   Overweight_Level_II                  26
# Calcula la precisión
accuracyfit = sum(diag(matriz_fit)) / sum(matriz_fit)
print(paste("Accuracy:", accuracy))
## [1] "Accuracy: 0.631828978622328"

A pesar de quitar las variables que parecían no aportar mucho al modelo, seguimos teniendo un accuracy del 63%. En resumen, creemos que un modelo de SVM puede funcionar bien si queremos hacer predicciones respecto a algunas de las clases dentro del nivel de obesidad. Y del mismo modo el modelo no es muy bueno para predecir otras.