Machine Learning. Taller 2: Clasificación con árboles de decisión.

1. Introducción

En esta práctica implementaremos árboles de decisión para clasificar el éxito de proyectos de software en tres niveles: Alto, Medio y Bajo. Se realizará:

  1. Construcción de árboles de decisión
  2. Poda del árbol para evitar sobreajuste
  3. Análisis de tres escenarios diferentes
  4. Evaluación y comparación de modelos

1.1 Librerías necesarias

library(tidyverse)      # Manipulación de datos
library(rpart)          # Árboles de decisión
library(rpart.plot)     # Visualización de árboles
library(caret)          # Matrices de confusión
library(knitr)          # Tablas bonitas

1.2 Carga y preparación de datos

# Cargar datos
datos <- read.csv("datos_proyectos_software_limpio.csv")
 
# Primero extraer los niveles existentes en los datos
niveles_exito <- sort(unique(datos$exito_proyecto))
niveles_metodologia <- sort(unique(datos$metodologia_num))
niveles_stack <- sort(unique(datos$stack_tecnologico_num))
niveles_pruebas <- sort(unique(datos$pruebas_automatizadas_num))


datos <-datos %>% 
mutate(
    exito_proyecto = factor(exito_proyecto, levels = niveles_exito),
    metodologia_num = factor(metodologia_num, levels = niveles_metodologia),
    stack_tecnologico_num = factor(stack_tecnologico_num, levels = niveles_stack),
    pruebas_automatizadas_num = factor(pruebas_automatizadas_num, 
                                       levels = niveles_pruebas)
  )

# Dividir datos en entrenamiento y prueba
set.seed(1987)
indice_train <- createDataPartition(datos$exito_proyecto, p = 0.7, list = FALSE)
datos_train <- datos[indice_train, ]
datos_test <- datos[-indice_train, ]

# Mostrar distribución de clases

print(prop.table(table(datos_train$exito_proyecto)))

     Alto      Bajo     Medio 
0.5786963 0.1717011 0.2496025 
print(prop.table(table(datos_test$exito_proyecto)))

     Alto      Bajo     Medio 
0.5805243 0.1685393 0.2509363 

2. Escenarios de modelado

2.1 Escenario 1: Todas las variables

# Definir todas las variables predictoras
todas_vars <- setdiff(names(datos), c("exito_proyecto", "id_proyecto", "fecha_inicio"))
formula_todas <- as.formula(paste("exito_proyecto ~", paste(todas_vars, collapse = " + ")))

# Entrenar árbol inicial
arbol_todas <- rpart(
  formula_todas, 
  data = datos_train,
  method = "class",
  parms = list(split = "information"), # usar ganancia de información
  control = rpart.control(cp = 0.001)  # árbol grande inicial
)

# Análisis de complejidad del árbol
printcp(arbol_todas)

Classification tree:
rpart(formula = formula_todas, data = datos_train, method = "class", 
    parms = list(split = "information"), control = rpart.control(cp = 0.001))

Variables actually used in tree construction:
[1] errores_por_kloc     metodologia_num      puntuacion_calidad  
[4] satisfaccion_cliente

Root node error: 265/629 = 0.4213

n= 629 

        CP nsplit rel error   xerror     xstd
1 0.430189      0  1.000000 1.000000 0.046731
2 0.286792      1  0.569811 0.603774 0.041217
3 0.139623      2  0.283019 0.290566 0.031020
4 0.098113      3  0.143396 0.147170 0.022824
5 0.018868      4  0.045283 0.056604 0.014440
6 0.001000      5  0.026415 0.064151 0.015347
plotcp(arbol_todas)

De todas las variables disponibles en el dataset, solo estas 4 (errores_por_kloc, metodologia_num, puntuacion_calidad, satisfaccion_cliente) fueron consideradas útiles por el algoritmo para hacer las divisiones.

De las 629 observaciones totales, 265 están mal clasificadas en el nodo raíz. Esto representa un error inicial del 42.13%. Este es el error antes de hacer cualquier división en el árbol.

Donde:

  • CP (Complexity Parameter): Es el parámetro de complejidad que controla el tamaño del árbol. Un CP más alto significa un árbol más pequeño. Un CP más bajo permite más divisiones.

  • nsplit: Número de divisiones realizadas en el árbol. Va de 0 (árbol sin divisiones) a 5 (árbol con 5 divisiones).

  • rel error: Error relativo en los datos de entrenamiento. Comienza en 1 y va disminuyendo con cada división. Muestra cómo mejora el modelo en los datos de entrenamiento.

  • xerror: Error de validación cruzada. Es una estimación más realista del error verdadero Si aumenta, puede indicar sobreajuste.

  • xstd: Desviación estándar del error de validación cruzada. Indica la variabilidad del error en la validación cruzada.

Podemos observar que:

  1. El error relativo disminuye constantemente (de 1.0 a 0.026)

  2. La mejor solución podría ser usar 3 o 4 divisiones, ya que después:

    • El xerror no mejora significativamente

    • Incluso aumenta un poco (de 0.056 a 0.064)

    • Esto sugiere que más divisiones podrían llevar a sobreajuste

# Encontrar CP óptimo
cp_optimo_todas <- arbol_todas$cptable[which.min(arbol_todas$cptable[,"xerror"]), "CP"]

# Podar el árbol
arbol_todas_podado <- prune(arbol_todas, cp = cp_optimo_todas)

# Visualizar árbol podado
rpart.plot(arbol_todas_podado, 
           type = 1,
           extra = 104, 
           main = "Árbol de decisión - todas las variables")

En este árbol de clasificación, la variable más importante para separar las clases es satisfaccion_cliente (nivel de satisfacción mayor o igual a 80). El árbol se visualiza mediante rectángulos (nodos) de diferentes colores: anaranjado para la categoría “Alto”, gris para “Bajo” y verde para “Medio”. Cada nodo muestra tres tipos de información clave: la clase predicha en la primera línea (por ejemplo, “Alto”), seguida de tres números que representan la distribución de las clases (por ejemplo, 1.00 .00 .00 significa que el 100% son “Alto”, 0% “Bajo” y 0% “Medio”), y finalmente el porcentaje de datos totales en ese nodo (por ejemplo, 58%). El color de cada nodo se determina por la clase más frecuente en ese grupo de datos - por ejemplo, si vemos un nodo anaranjado con valores 1.00 .00 .00, significa que todos los datos en ese nodo son de la categoría “Alto” (100% de pureza), representando el 58% del total de datos. Esta visualización nos permite entender fácilmente cómo el árbol va tomando decisiones y clasificando los datos en grupos cada vez más puros.

# Evaluar modelo
pred_todas <- predict(arbol_todas_podado, datos_test, type = "class")
conf_matrix_todas <- confusionMatrix(pred_todas, as.factor(datos_test$exito_proyecto))
print("Resultados - modelo con todas las variables:")
[1] "Resultados - modelo con todas las variables:"
print(conf_matrix_todas)
Confusion Matrix and Statistics

          Reference
Prediction Alto Bajo Medio
     Alto   155    0     0
     Bajo     0   42     1
     Medio    0    3    66

Overall Statistics
                                          
               Accuracy : 0.985           
                 95% CI : (0.9621, 0.9959)
    No Information Rate : 0.5805          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.9738          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Alto Class: Bajo Class: Medio
Sensitivity               1.0000      0.9333       0.9851
Specificity               1.0000      0.9955       0.9850
Pos Pred Value            1.0000      0.9767       0.9565
Neg Pred Value            1.0000      0.9866       0.9949
Prevalence                0.5805      0.1685       0.2509
Detection Rate            0.5805      0.1573       0.2472
Detection Prevalence      0.5805      0.1610       0.2584
Balanced Accuracy         1.0000      0.9644       0.9850

El modelo de clasificación muestra un rendimiento excepcional, con una precisión global del 98.5% y un coeficiente Kappa de 0.9738, lo que indica un acuerdo casi perfecto más allá del azar. La matriz de confusión revela que el modelo es particularmente efectivo en identificar la clase “Alto” con un 100% de precisión y sensibilidad, mientras que mantiene un rendimiento muy sólido en las clases “Bajo” y “Medio” con tasas de acierto superiores al 93%. Los pocos errores de clasificación se limitan a confusiones menores entre las categorías “Bajo” y “Medio” (solo 4 casos mal clasificados de un total de 267), lo que demuestra la robustez y fiabilidad del modelo para esta tarea de clasificación. Las altas métricas en todas las categorías (sensibilidad, especificidad y valores predictivos) confirman que el modelo es equilibrado y efectivo en su capacidad de discriminación, independientemente de la clase que se esté prediciendo.

2.2 Escenario 2: Variables numéricas

# Variables numericas continuas unicamente
vars_numericas <- c("tamano_equipo", 
                  "duracion_meses",
                 "errores_por_kloc", 
                 "puntuacion_calidad",
                 "satisfaccion_cliente")
formula_numericas <- as.formula(paste("exito_proyecto ~", paste(vars_numericas, collapse = " + ")))

# Entrenar árbol inicial
arbol_numericas <- rpart(
  formula_numericas, 
  data = datos_train,
  method = "class",
  parms = list(split = "information"),
  control = rpart.control(cp = 0.001)
)

# Análisis de complejidad y poda
plotcp(arbol_numericas)

printcp(arbol_numericas)

Classification tree:
rpart(formula = formula_numericas, data = datos_train, method = "class", 
    parms = list(split = "information"), control = rpart.control(cp = 0.001))

Variables actually used in tree construction:
[1] errores_por_kloc     puntuacion_calidad   satisfaccion_cliente
[4] tamano_equipo       

Root node error: 265/629 = 0.4213

n= 629 

         CP nsplit rel error   xerror     xstd
1 0.4301887      0  1.000000 1.000000 0.046731
2 0.2867925      1  0.569811 0.584906 0.040783
3 0.0981132      2  0.283019 0.298113 0.031363
4 0.0566038      3  0.184906 0.207547 0.026734
5 0.0188679      5  0.071698 0.094340 0.018489
6 0.0018868      6  0.052830 0.098113 0.018840
7 0.0010000     10  0.045283 0.098113 0.018840
cp_optimo_numericas <- arbol_numericas$cptable[which.min(arbol_numericas$cptable[,"xerror"]), "CP"]
arbol_numericas <- prune(arbol_numericas, cp = cp_optimo_numericas)

# Visualizar árbol podado
rpart.plot(arbol_numericas, 
           type = 1,
           extra = 104, 
           main = "Árbol de Decisión - variables numéricas")

# Evaluar modelo
pred_numericas <- predict(arbol_numericas, datos_test, type = "class")
conf_matrix_numericas <- confusionMatrix(pred_numericas, as.factor(datos_test$exito_proyecto))
print("Resultados - Modelo con variables numéricas")
[1] "Resultados - Modelo con variables numéricas"
print(conf_matrix_numericas)
Confusion Matrix and Statistics

          Reference
Prediction Alto Bajo Medio
     Alto   155    0     5
     Bajo     0   42     1
     Medio    0    3    61

Overall Statistics
                                         
               Accuracy : 0.9663         
                 95% CI : (0.937, 0.9845)
    No Information Rate : 0.5805         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.9403         
                                         
 Mcnemar's Test P-Value : NA             

Statistics by Class:

                     Class: Alto Class: Bajo Class: Medio
Sensitivity               1.0000      0.9333       0.9104
Specificity               0.9554      0.9955       0.9850
Pos Pred Value            0.9688      0.9767       0.9531
Neg Pred Value            1.0000      0.9866       0.9704
Prevalence                0.5805      0.1685       0.2509
Detection Rate            0.5805      0.1573       0.2285
Detection Prevalence      0.5993      0.1610       0.2397
Balanced Accuracy         0.9777      0.9644       0.9477

En este segundo árbol de clasificación, la variable principal sigue siendo satisfaccion_cliente con un umbral de 80, pero ahora se observa una estructura más compleja con más niveles de decisión. Los nodos mantienen el mismo esquema de colores (anaranjado para “Alto”, gris para “Bajo” y verde para “Medio”) y la misma estructura de información: clase predicha, distribución de probabilidades y porcentaje del total de datos. En este segundo árbol de clasificación, aunque tiene una estructura más compleja con múltiples niveles de decisión basados en distintos umbrales de satisfacción del cliente (80, 82, 75), errores por kloc, puntuación de calidad, mantiene un rendimiento excepcional. El modelo alcanza una precisión global del 96.63% (con un intervalo de confianza del 95% entre 93.7% y 98.45%) y un coeficiente Kappa de 0.940, indicando un acuerdo casi perfecto.

2.3 Escenario 3: Variables correlacionadas

# Variables correlacionadas segun analisis previo

vars_correlacionada <- c("satisfaccion_cliente", "puntuacion_calidad","metodologia_num")
formula_correlacionada <- as.formula(paste("exito_proyecto ~", paste(vars_correlacionada, collapse = " + ")))

# Entrenar árbol inicial
arbol_correlacionada <- rpart(
  formula_correlacionada, 
  data = datos_train,
  method = "class",
  parms = list(split = "information"),
  control = rpart.control(cp = 0.001)
)

# Análisis de complejidad y poda
plotcp(arbol_correlacionada)

cp_optimo_correlacionada <- arbol_correlacionada$cptable[which.min(arbol_correlacionada$cptable[,"xerror"]), "CP"]
arbol_correlacionada <- prune(arbol_correlacionada, cp = cp_optimo_correlacionada)

# Visualizar árbol podado
rpart.plot(arbol_correlacionada, 
           type = 1,
           extra = 104,
           main = "Árbol de Decisión - variables correlacionadas")

# Evaluar modelo
pred_correlacionada <- predict(arbol_correlacionada, datos_test, type = "class")
conf_matrix_correlacionada <- confusionMatrix(pred_correlacionada, as.factor(datos_test$exito_proyecto))
print("Resultados - Modelo con variables correlacionadas:")
[1] "Resultados - Modelo con variables correlacionadas:"
print(conf_matrix_correlacionada)
Confusion Matrix and Statistics

          Reference
Prediction Alto Bajo Medio
     Alto   155    0     0
     Bajo     0   42     1
     Medio    0    3    66

Overall Statistics
                                          
               Accuracy : 0.985           
                 95% CI : (0.9621, 0.9959)
    No Information Rate : 0.5805          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.9738          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Alto Class: Bajo Class: Medio
Sensitivity               1.0000      0.9333       0.9851
Specificity               1.0000      0.9955       0.9850
Pos Pred Value            1.0000      0.9767       0.9565
Neg Pred Value            1.0000      0.9866       0.9949
Prevalence                0.5805      0.1685       0.2509
Detection Rate            0.5805      0.1573       0.2472
Detection Prevalence      0.5805      0.1610       0.2584
Balanced Accuracy         1.0000      0.9644       0.9850

3. Comparación de modelos

# Crear tabla comparativa
comparacion_modelos <- data.frame(
  Modelo = c("Todas las variables", "Variables numéricas", "Variables correlacionadas"),
  Accuracy = c(
    conf_matrix_todas$overall["Accuracy"],
    conf_matrix_numericas$overall["Accuracy"],
    conf_matrix_correlacionada$overall["Accuracy"]
  ),
  Kappa = c(
    conf_matrix_todas$overall["Kappa"],
    conf_matrix_numericas$overall["Kappa"],
    conf_matrix_correlacionada$overall["Kappa"]
  ),
  Complejidad_CP = c(
    cp_optimo_todas,
    cp_optimo_numericas,
    cp_optimo_correlacionada
  ),
  Nodos_Terminales = c(
    sum(arbol_todas_podado$frame$var == "<leaf>"),
    sum(arbol_numericas$frame$var == "<leaf>"),
    sum(arbol_correlacionada$frame$var == "<leaf>")
  )
)

# Mostrar tabla
kable(comparacion_modelos, 
      caption = "Comparación de modelos",
      digits = 3)
Comparación de modelos
Modelo Accuracy Kappa Complejidad_CP Nodos_Terminales
Todas las variables 0.985 0.974 0.019 5
Variables numéricas 0.966 0.940 0.019 6
Variables correlacionadas 0.985 0.974 0.001 5
# Convertir a formato largo para visualización
comparacion_larga <- comparacion_modelos %>%
  select(Modelo, Accuracy, Kappa) %>%
  pivot_longer(
    cols = c(Accuracy, Kappa),
    names_to = "Metrica",
    values_to = "Valor"
  )

# Visualizar comparación
ggplot(comparacion_larga, aes(x = Modelo, y = Valor, fill = Metrica)) +
  geom_bar(stat = "identity", position = "dodge") +
  labs(title = "Comparación de modelos por métrica",
       y = "Valor",
       x = "Modelo") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  scale_fill_brewer(palette = "Set2")

4. Análisis de variables importantes

# Función para extraer importancia de variables
obtener_importancia <- function(modelo, nombre) {
  # Verificar si el modelo tiene variables importantes
  if(is.null(modelo$variable.importance) || length(modelo$variable.importance) == 0) {
    cat("No se encontraron variables importantes para el modelo:", nombre, "\n")
    return(NULL)
  }
  
  # Crear dataframe con las variables importantes
  data.frame(
    Modelo = rep(nombre, length(modelo$variable.importance)),
    Variable = names(modelo$variable.importance),
    Importancia = as.numeric(modelo$variable.importance),
    stringsAsFactors = FALSE
  ) %>%
    arrange(desc(Importancia))
}

# Obtener importancia para cada modelo
imp_todas <- obtener_importancia(arbol_todas_podado, "Todas las variables")
imp_numericas <- obtener_importancia(arbol_numericas, "Variables numéricas")
imp_correlacionada <- obtener_importancia(arbol_correlacionada, "Variables correlacionadas")

# Combinar resultados, excluyendo NULLs
importancia_total <- bind_rows(
  list(imp_todas, imp_numericas, imp_correlacionada)
)

# Visualizar importancia si hay datos
if(!is.null(importancia_total) && nrow(importancia_total) > 0) {
  # Crear gráfico
  plot_importancia <- ggplot(importancia_total, 
                            aes(x = reorder(Variable, Importancia), 
                                y = Importancia)) +
    geom_bar(stat = "identity", fill = "steelblue") +
    facet_wrap(~Modelo, scales = "free_y") +
    coord_flip() +
    labs(title = "Importancia de variables por modelo",
         x = "Variable",
         y = "Importancia") +
    theme_minimal()
  
  # Mostrar gráfico
  print(plot_importancia)
  
  # Mostrar tabla
  kable(importancia_total %>% 
          arrange(Modelo, desc(Importancia)),
        caption = "Importancia de variables por modelo",
        digits = 3)
} else {
  cat("No se encontraron variables importantes en ninguno de los modelos\n")
}

Importancia de variables por modelo
Modelo Variable Importancia
Todas las variables satisfaccion_cliente 420.678
Todas las variables metodologia_num 331.067
Todas las variables puntuacion_calidad 149.865
Todas las variables errores_por_kloc 4.273
Todas las variables tamano_equipo 3.831
Variables correlacionadas satisfaccion_cliente 420.678
Variables correlacionadas metodologia_num 331.067
Variables correlacionadas puntuacion_calidad 149.865
Variables numéricas satisfaccion_cliente 455.830
Variables numéricas puntuacion_calidad 210.524
Variables numéricas errores_por_kloc 7.314
Variables numéricas tamano_equipo 3.831
Variables numéricas duracion_meses 1.014

Al analizar los tres modelos de árboles de decisión (todas las variables, variables correlacionadas y variables numéricas), se observa un patrón consistente en la importancia de las variables:

  • La variable satisfaccion_cliente es claramente la más influyente en los tres modelos, manteniendo el nivel más alto de importancia (aproximadamente 400 unidades) en todas las versiones.

  • En el modelo de “todas las variables”:

    • metodologia_num es la segunda más importante.

    • puntuacion_calidad tiene una importancia moderada.

    • errores_por_kloc y tamano_equipo tienen una importancia muy baja.

  • Para el modelo de “variables correlacionadas”:

    • metodologia_num mantiene una importancia alta.

    • puntuacion_calidad muestra una importancia moderada,

  • En el modelo de “variables numéricas”:

    • puntuacion_calidad es la segunda más importante.

    • Las demás variables (errores_por_kloc, tamano_equipo, duracion_meses) tienen una importancia mínima.

Esto sugiere que la satisfacción del cliente es el factor más determinante para la clasificación, seguido por la metodología utilizada y la puntuación de calidad, independientemente del conjunto de variables consideradas.

5. Selección de mejor modelo

Ahora obtendremos y guardaremos el mejor modelo de árbol de clasificación.

# Identificar y guardar mejor modelo
nombres_modelos <- c("Árbol con todas las variables", 
                   "Árbol con variables numéricas",
                   "Árbol con variables correlacionadas")

indice_mejor <- which.max(comparacion_modelos$Accuracy)

mejor_modelo <- list(
 arbol_todas_podado,
 arbol_numericas,
 arbol_correlacionada
)[[indice_mejor]]

# Métricas del mejor modelo
metricas_mejor <- data.frame(
 Metrica = c("Modelo Seleccionado", "Accuracy", "Kappa", "Nodos Terminales", "CP Óptimo"),
 Valor = c(
   nombres_modelos[indice_mejor],
   max(comparacion_modelos$Accuracy),
   comparacion_modelos$Kappa[indice_mejor],
   comparacion_modelos$Nodos_Terminales[indice_mejor],
   comparacion_modelos$Complejidad_CP[indice_mejor]
 )
)

kable(metricas_mejor, 
     caption = "Métricas del mejor modelo",
     align = c('l', 'r'))  # Alineación: texto a la izquierda, números a la derecha
Métricas del mejor modelo
Metrica Valor
Modelo Seleccionado Árbol con todas las variables
Accuracy 0.98501872659176
Kappa 0.973763081609591
Nodos Terminales 5
CP Óptimo 0.0188679245283019

El análisis de los tres modelos de árboles de decisión revela que el “Árbol con todas las variables” fue el de mejor rendimiento. Este modelo alcanzó una precisión (Accuracy) excepcional del 98.5% y un coeficiente Kappa de 0.974, lo que indica un acuerdo casi perfecto más allá del azar. La estructura del árbol es relativamente simple, con solo 5 nodos terminales, lo que sugiere un buen balance entre complejidad y capacidad predictiva. El parámetro de complejidad (CP) óptimo de 0.019 indica que el modelo fue podado adecuadamente para evitar el sobreajuste mientras mantiene su alto rendimiento. Estos resultados sugieren que considerar todas las variables disponibles proporcionó la mejor capacidad predictiva sin sacrificar la interpretabilidad del modelo.

Reglas del mejor modelo

# Visualización de reglas de decisión
 
# Obtener y mostrar reglas
reglas_texto <- capture.output(rpart.rules(mejor_modelo, style = "wide"))

# Crear tabla de reglas
reglas_df <- data.frame(
 "Regla N°" = seq_along(reglas_texto[-1]),
 "Descripción" = reglas_texto[-1]
)

# Mostrar tabla
kable(reglas_df,
     caption = "Reglas de decisión del modelo",
     col.names = c("Regla N°", "Descripción de la regla"),
     align = c('c', 'l'))
Reglas de decisión del modelo
Regla N° Descripción de la regla
1 Alto [ .98 .00 .02] when satisfaccion_cliente >= 80 & metodologia_num is 1 or 3
2 Bajo [ .00 1.00 .00] when satisfaccion_cliente < 75 & puntuacion_calidad >= 55
3 Medio [ .00 .16 .84] when satisfaccion_cliente < 75 & puntuacion_calidad < 55
4 Medio [ .00 .00 1.00] when satisfaccion_cliente >= 80 & metodologia_num is 2
5 Medio [ .00 .00 1.00] when satisfaccion_cliente is 75 to 80

Preparación para siguientes prácticas

El mejor modelo generado en esta práctica servirá como base para: - Práctica 4: Desarrollo de una aplicación Shiny para predicciones.

# 1. Obtener importancia del mejor modelo
importancia_mejor <- obtener_importancia(mejor_modelo, "Mejor modelo") %>%
  arrange(desc(Importancia))

# 2. Preparar información necesaria para el modelo
modelo_info <- list(
    modelo = mejor_modelo,
    variables = names(mejor_modelo$variable.importance),
    niveles_metodologia = niveles_metodologia,
    niveles_stack = niveles_stack,
    niveles_pruebas = niveles_pruebas,
    niveles_exito = niveles_exito,
    metricas = metricas_mejor,
    importancia_variables = importancia_mejor
)

# 3. Guardar toda la información
# Guardar mejor modelo
saveRDS(modelo_info, "mejor_modelo_arbol.rds")

# 4. Verificar que se guardó correctamente
modelo_cargado <- readRDS("mejor_modelo_arbol.rds")

# 5. Mostrar importancia de variables guardadas
kable(modelo_cargado$importancia_variables,
      caption = "Importancia de variables en el modelo guardado",
      digits = 3)
Importancia de variables en el modelo guardado
Modelo Variable Importancia
Mejor modelo satisfaccion_cliente 420.678
Mejor modelo metodologia_num 331.067
Mejor modelo puntuacion_calidad 149.865
Mejor modelo errores_por_kloc 4.273
Mejor modelo tamano_equipo 3.831