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
data_obesidad = read.csv("/Users/macbookair/Desktop/R (MASTER)/ObesityDataSet_raw_and_data_sinthetic.csv", header = T)
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)
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)
columnas_a_eliminar = c('Height','Weight','family_history_with_overweight','NCP','CH2O')
t_buena = tabla_truncada %>%
select(-one_of(columnas_a_eliminar))
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.