Librerias necesarias
library(readxl) # Importar datos de excel
library(skimr) # Resumen estadísticos
library(readr) # Importar datos csv
library(caret) # Dividir en conjunto de entrenamiento y prueba
library(partykit) # Arboles de decisión
library(e1071) # Matriz de confusión
library(ROCR) # Curva ROC
library(rpart) # Arbol de decision Particionamiento recursivo
library(rpart.plot) # Graficos para rpart
library(randomForest) # Para modelos de bosques aleatorios
library(dplyr) # Manipulacion de datos
library(ggplot2) # Visualizacion de datos
El obejetivo de este documento es cubrir la construcion y evaluacion de un bosque aleatorio. Para ello, es importante que cubramos algunos apsectos iportantes del modelo:
Importance parameter: El parametro de importancia, es una medida de que tanto la precicon varia segun se añade o elimina una variable
Proximity Parameter: El parametro de proximidad mide la cercania entre pares de casos. La proximidad se calcula para cada par de puntos de muestra. Si dos casos ocupan el mismo nodo terminal atravs de un arbol , su aproximidad aumenta en uno. Despues de ejecutar el modelo con todos los arboles, las proximidades se normalizan dividiendolas por las cantidade de arboles. Las proximidades se utilizar para remplaxar datos faltantes, localizar valores atipicos y determinar puntos de baja dimension en los datos
Variable | Ejemplo | Descripción |
---|---|---|
ID | 1 | Identificador único de cada registro. |
Program.Code | HD | Código granular que describe el destino y las actividades del viaje (por ejemplo, “HD” para un programa de historia). |
FromGrade | 8 | El grado más bajo en la escuela de un participante en el programa. |
ToGrade | 8 | El grado más alto en la escuela de un participante en el programa. |
GroupState | IN | Abreviatura del estado donde se ubica la escuela de origen. “OTHER” se usa para geografías raras. |
IsNonAnnual | 1 | Indica si el grupo de esta escuela típicamente salta un año entre programas. |
Days | 3 | Número de días que el grupo estuvo en el programa con uno de los instructores. |
TransportType | A | Modo de transporte desde la ubicación de origen al punto de inicio (A = Avión, B = Bus, T = Tren). |
SpecialPay | NA | Indica si el maestro recauda todo el dinero para remitirlo a EduTravel en lugar de hacerlo individualmente. |
Price | 1174 | Precio pagado por participante para asistir al programa. |
FRP.Active | 72 | Número de participantes que compraron seguro de cancelación del viaje. |
Income.Level | P | Indicador del nivel de ingresos de los padres (A = bajo, Q = alto, Z = no clasificado). |
EZ.Pay | 0.2286 | Porcentaje de participantes que se inscriben en un plan de pago automático. |
School.Sponsor | 0 | Indicador binario de si la escuela patrocinó oficialmente el viaje. |
Product.Type | East Coast | Nivel de agrupación del tipo de programa turístico. |
New.Existing | EXISTING | Indica si el grupo ha viajado previamente con EduTravel (“NEW” si no lo ha hecho antes). |
FPP | 105 | Número total de participantes que pagaron completamente para asistir al programa. |
Group.Revenue | 125735.4 | Monto total pagado por todos los participantes del grupo. |
Meetings | 0 | Número de reuniones con los padres antes del viaje. |
GradeLow | Elementary | Tipo de grado más bajo de la escuela. |
GradeHigh | Elementary | Tipo de grado más alto de la escuela. |
DepartureMonth | January | Mes de partida. |
Poverty.Code | A | Código de pobreza del área de origen (A = bajo porcentaje bajo la línea de pobreza, D = alto). |
CRM Segment | 1 | Tipo de segmento de cliente definido en el sistema CRM (números entre 1 y 11). |
SchoolType | PUBLIC | Indica si la escuela es pública o privada. |
Meeting.Flag | 1 | Indica si hubo una reunión con los padres, señalando compromiso con el viaje. |
Low.Grade | 7 | Grado más bajo en la escuela de origen. |
High.Grade | 8 | Grado más alto en la escuela de origen. |
School.Enrollment | 955 | Total de estudiantes inscritos en la escuela. |
MajorProgramCode | H | Agregación del código del programa; la primera letra del código granular del programa. |
SingleGrade | 1 | Indicador de si el viaje incluye estudiantes del mismo grado. |
FPP.enrollment | 0.06346 | Proporción de participantes en el programa respecto al total de la inscripción escolar. |
FPP.PAX | 0.93650794 | Proporción de participantes que pagaron completamente en comparación con el total de pasajeros. |
Size | L | Tamaño de la escuela (S, M, L, S-M, M-L) por quintiles de tamaño. |
Retained.in.2020 | 1 | Indicador de éxito (1/0): ¿el grupo volvió al año siguiente? |
edutravel$Program.Code <- as.factor(edutravel$Program.Code)
edutravel$FromGrade <- as.factor(edutravel$FromGrade)
edutravel$ToGrade <- as.factor(edutravel$ToGrade)
edutravel$GroupState <- as.factor(edutravel$GroupState)
edutravel$IsNonAnnual <- as.factor(edutravel$IsNonAnnual)
edutravel$Days <- as.factor(edutravel$Days)
edutravel$TransportType <- as.factor(edutravel$TransportType)
edutravel$SpecialPay <- as.factor(edutravel$SpecialPay)
edutravel$Poverty.Code <- as.factor(edutravel$Poverty.Code)
edutravel$Region <- as.factor(edutravel$Region )
edutravel$CRMSegment <- as.factor(edutravel$CRMSegment)
edutravel$SchoolType <- as.factor(edutravel$SchoolType)
edutravel$Meeting.Flag <- as.factor(edutravel$Meeting.Flag)
edutravel$Income.Level <- as.factor(edutravel$Income.Level)
edutravel$Product.Type <- as.factor(edutravel$Product.Type)
edutravel$New.Existing <- as.factor(edutravel$New.Existing)
edutravel$GradeLow <- as.factor(edutravel$GradeLow)
edutravel$GradeHigh <- as.factor(edutravel$GradeHigh)
edutravel$DepartureMonth <- as.factor(edutravel$DepartureMonth)
edutravel$Meetings <- as.factor(edutravel$Meetings)
edutravel$AggregatedCode <- as.factor(edutravel$AggregatedCode)
edutravel$SingleGrade <- as.factor(edutravel$SingleGrade)
edutravel$Size <- as.factor(edutravel$Size)
edutravel$Low.Grade <- as.factor(edutravel$Low.Grade)
edutravel$Low.High <- as.factor(edutravel$High.Grade)
edutravel$Retained.in.2020 <- as.factor(edutravel$Retained.in.2020)
# definimos funcion
FillNAs<-function(data_frame){
fill_number<-0
fill_factor<-"NA_filled"
fill_character<-"NA_filled"
fill_date<-as.Date("1900-01-01")
# Make a loop in the columns of the data frame and according to the
# data type, fill the respective value and create a surrogate column
for (i in 1 : ncol(data_frame)){
if (class(data_frame[,i]) %in% c("numeric","integer")) {
if (any(is.na(data_frame[,i]))){
data_frame[,paste0(colnames(data_frame)[i],"_filledNA")]<- as.factor(ifelse(is.na(data_frame[,i]),"1","0"))
data_frame[is.na(data_frame[,i]),i]<-fill_number
}
} else
if (class(data_frame[,i]) %in% c("factor")) {
if (any(is.na(data_frame[,i]))){
data_frame[,i]<-as.character(data_frame[,i])
data_frame[,paste0(colnames(data_frame)[i],"_filledNA")]<-as.factor(ifelse(is.na(data_frame[,i]),"1","0"))
data_frame[is.na(data_frame[,i]),i]<-fill_factor
data_frame[,i]<-as.factor(data_frame[,i])
}
} else {
if (class(data_frame[,i]) %in% c("character")) {
if (any(is.na(data_frame[,i]))){
data_frame[,paste0(colnames(data_frame)[i],"_filledNA")]<- as.factor(ifelse(is.na(data_frame[,i]),"1","0"))
data_frame[is.na(data_frame[,i]),i]<-afill_character
}
} else {
if (class(data_frame[,i]) %in% c("Date")) {
if (any(is.na(data_frame[,i]))){
data_frame[,paste0(colnames(data_frame)[i],"_filledNA")]<-as.factor(ifelse(is.na(data_frame[,i]),"1","0"))
data_frame[is.na(data_frame[,i]),i]<-fill_date
}
}
}
}
}
return(data_frame)
}
# aplicamos función
edutravelNA <-FillNAs(edutravel)
# combinamos categorías
El propósito de esta función, en un contexto práctico, es simplificar y mejorar la calidad de los análisis de datos categóricos, especialmente en situaciones donde:
Reducción de complejidad en las categorías:
En un dataset con muchas categorías, algunas pueden tener muy pocas observaciones, lo que hace que los análisis sean menos significativos y más complejos de interpretar. Combinar estas categorías poco representadas en una genérica llamada “Others” ayuda a reducir el ruido en los datos y enfocar los análisis en las categorías principales. Facilitar la visualización de datos:
Al crear gráficos (por ejemplo, gráficos de barras, de pastel o tablas resumen), un exceso de categorías hace que la visualización sea desordenada y difícil de leer. Agrupar las categorías con pocos datos bajo “Others” mejora la claridad visual y destaca las categorías más relevantes. Mejorar los modelos estadísticos o de machine learning:
En modelos de regresión, clasificación o cualquier algoritmo que utilice variables categóricas, demasiadas categorías con baja frecuencia pueden llevar a problemas de sobreajuste (overfitting) o resultados inestables. Agrupar las categorías pequeñas en “Others” permite que los modelos se centren en las categorías más significativas, mejorando su rendimiento. Normalización de los datos para reportes:
En informes o reportes donde se presenten resultados agrupados, es común agrupar valores menores en una categoría “Otros” para que los resultados sean más concisos y comprensibles. Análisis exploratorio de datos:
Durante el análisis inicial de un dataset, agrupar categorías poco representadas facilita la identificación de patrones significativos en las categorías principales sin distracciones innecesarias.
### Combining Categories
# Definimos funcion
CombineCategories <-function(data_frame,mincount){
for (i in 1 : ncol(data_frame)){
a<-data_frame[,i]
replace <- names(which(table(a) < mincount))
levels(a)[levels(a) %in% replace] <- paste("Others",colnames(data_frame)[i],sep=".")
data_frame[,i]<-a }
return(data_frame)
}
# aplicamos funcion
# dataframe with a mincount of 10
#head(CombineCategories(edutravelNA, 10))
# indicamos semilla para consistencia
set.seed(2021)
# separamos datos (80% entrenamiento y 20% prueba)
Partition <- createDataPartition(y = edutravelNA$Retained.in.2020,
p = 0.8, # porcentaje de entrenamiento.
list = FALSE)
# sepramos en objetos
training <- edutravelNA[ Partition,]
testing <- edutravelNA[ -Partition,]
El parámetro cutoff en randomForest ajusta el umbral de votos necesario para asignar una clase en problemas de clasificación, siendo especialmente útil en casos con clases desbalanceadas. Permite priorizar la detección de la clase minoritaria o ajustar el trade-off entre sensibilidad y especificidad. Por ejemplo, en un modelo para predecir fraudes, puedes usar cutoff = c(0.4, 0.6) para dar más peso a la clase minoritaria. Esto mejora el rendimiento en escenarios donde es crítico identificar correctamente ciertos casos, configurándose directamente al crear el modelo.
En este ejemplo usamos el tipo de clasificacion, ya que tenemos datos dicotomicos. Sin embargo, podriamos usar el tipo regresion para datos numericos no binarios.
# Creamos el modelo de bosque aleatorio
RF.model <- randomForest(Retained.in.2020~ . -GroupState, # Todas las variables menos "GroupState"
data=training, # datos de entrenamiento
importance=TRUE, proximity=TRUE, # Incluimos parametros
cutoff = c(0.5, 0.5), # Indicamos el punto de corte en 0.5
type="classification" # se puede indicar clasificacion o regresion
)
print(RF.model)
##
## Call:
## randomForest(formula = Retained.in.2020 ~ . - GroupState, data = training, importance = TRUE, proximity = TRUE, cutoff = c(0.5, 0.5), type = "classification")
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 7
##
## OOB estimate of error rate: 20.03%
## Confusion matrix:
## 0 1 class.error
## 0 497 254 0.3382157
## 1 129 1032 0.1111111
Interpretación del resultado
Type of random forest: classification
:
Confirma que el modelo es de clasificación.
Number of trees: 500
: Indica que el
bosque aleatorio contiene 500 árboles. Este es un número común para
garantizar estabilidad en las predicciones.
No. of variables tried at each split: 7
:
Cada árbol considera aleatoriamente 7 variables candidatas para realizar
divisiones en cada nodo. Este número es comúnmente \(\sqrt{p}\), donde \(p\) es el número total de
predictores.
OOB estimate of error rate: 20.03%
:
Confusion matrix
: La matriz de
confusión resume las predicciones del modelo:
plot(RF.model)
Podemos observar como despues de 60 arboles aproximadamente tenemos un error cercano al 0.2 (20%) y se maniene constante. Exto implicam que no importa cuantos arboles nuevos agrgemos no lograremos reducir el error, despues de estu numero de arboles
Interpretación y análisis del gráfico generado por
plot(RF.model)
Este gráfico muestra cómo evoluciona la tasa de error a medida que se incrementa el número de árboles en el modelo de bosque aleatorio. Aquí se destacan los aspectos principales:
classwt
).mtry
).Analizamos las variables de mayor importancia
#importance(RF.model)
La función importance() en el paquete randomForest se utiliza para calcular y extraer medidas de importancia de las variables predictoras en el modelo. Esta función ayuda a identificar cuáles variables contribuyen más al poder predictivo del modelo.
Graficamos
La función varImpPlot(RF.model) genera un gráfico que visualiza la importancia de las variables predictoras en un modelo de bosque aleatorio. Utiliza las métricas calculadas por importance(), como MeanDecreaseAccuracy (reducción en la precisión al permutar una variable) o MeanDecreaseGini (reducción acumulada en el índice de Gini). El gráfico ordena las variables de mayor a menor importancia, mostrando en el eje Y los nombres de las variables y en el eje X sus valores de importancia. Este gráfico es útil para identificar visualmente las variables más influyentes en el modelo y facilitar la selección o eliminación de predictores con bajo impacto.
varImpPlot(RF.model)
Interpretación y Análisis del Gráfico
El gráfico presentado es un resultado típico de la función
varImpPlot()
del paquete randomForest
en R y
muestra dos métricas clave de la importancia de las variables
predictoras en un modelo de bosque aleatorio:
Panel izquierdo: MeanDecreaseAccuracy
IsNonAnnual
tiene el mayor impacto en la
precisión del modelo, siendo la más importante según esta métrica.New.Existing
,
SingleGrade
, y FPP
.Panel derecho: MeanDecreaseGini
IsNonAnnual
es la variable más importante
según esta métrica, seguida por Income.Level
,
SingleGrade
, y New.Existing
.Análisis General del Gráfico
IsNonAnnual
,
New.Existing
, y SingleGrade
, son
consistentemente importantes en ambas métricas, lo que refuerza su
relevancia en el modelo.GradeHigh
, tienen bajos valores
en ambas métricas, lo que sugiere que podrían ser eliminadas sin afectar
significativamente el rendimiento del modelo.Conclusiones
Este gráfico es una herramienta poderosa para identificar las
variables más y menos relevantes en el modelo. Las variables como
IsNonAnnual
y New.Existing
son cruciales para
el modelo, mientras que otras como GradeHigh
podrían no ser
necesarias. Esto permite refinar el modelo enfocándose en las variables
más importantes y simplificarlo eliminando predictores redundantes o
irrelevantes.
Primero, identificamos el punto de corte. Para ello, calculamos el promedio de la tasa de retencion
edutravelNA %>%
mutate(Retained.in.2020 = as.numeric(as.character(Retained.in.2020))) %>% # Conversiond de variable
summarise(media_retencion = mean(Retained.in.2020, na.rm = TRUE)) # calculamos el promedio
## media_retencion
## 1 0.6073671
Obtenemos que la probabilidad media de ser retenido es 0.607, que es lo que usaremos como valor de corte
prob.rf<-predict(RF.model, # indicamos el tipo de modelo
newdata=testing, # indicamos los datos de prueba
type="prob" # indicamos el tipo de prediccion
)
classification.RF<-rep("1",477) # Creamos un vector de solo unos "1"
classification.RF[prob.rf[,2]<0.6073671]="0" # Indicamos que es cero, aquellos menor al punto de corte
classification.RF = as.factor(classification.RF) # Convertimos a tipo factor
Calculamos matriz de confusion
confusionMatrix(classification.RF,testing$Retained.in.2020,
positive = "1")
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 162 65
## 1 25 225
##
## Accuracy : 0.8113
## 95% CI : (0.7733, 0.8455)
## No Information Rate : 0.608
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.6187
##
## Mcnemar's Test P-Value : 3.94e-05
##
## Sensitivity : 0.7759
## Specificity : 0.8663
## Pos Pred Value : 0.9000
## Neg Pred Value : 0.7137
## Prevalence : 0.6080
## Detection Rate : 0.4717
## Detection Prevalence : 0.5241
## Balanced Accuracy : 0.8211
##
## 'Positive' Class : 1
##
Graficamos matriz
# Calcular matriz de confusión
cm <- confusionMatrix(classification.RF, testing$Retained.in.2020, positive = "1")
# Convertir la tabla de la matriz de confusión en un DataFrame
conf_matrix <- as.data.frame(cm$table)
colnames(conf_matrix) <- c("Predicted", "Actual", "Freq")
# Crear el gráfico con ggplot2
ggplot(conf_matrix, aes(x = Actual, y = Predicted, fill = Freq)) +
geom_tile(color = "white") +
geom_text(aes(label = Freq), vjust = 1) +
scale_fill_gradient(low = "white", high = "blue") +
labs(title = "Matriz de Confusión", x = "Clase Real", y = "Clase Predicha") +
theme_minimal()
Matriz de confusión
Clase Real / Predicha | 0 (Negativo) | 1 (Positivo) |
---|---|---|
0 (Negativo) | 162 | 65 |
1 (Positivo) | 25 | 225 |
Métricas clave del modelo
Análisis
Recomendaciones
La curva ROC (Receiver Operating Characteristic) es una herramienta gráfica utilizada para evaluar el desempeño de un modelo de clasificación binaria. Representa la relación entre la tasa de verdaderos positivos (sensibilidad o recall) y la tasa de falsos positivos (1 - especificidad) para diferentes umbrales de decisión.
# Compramos los valores de probabilidad vs el cojunto de datos de prueba
prediction.RF.ROC <- prediction(prob.rf[,2], # Indicamos la probabilidad de la columna de 1
testing$Retained.in.2020 # Indicamos datos de prueba
)
# Calculamos la curva ROC
ROC.RF <- performance(prediction.RF.ROC, # Valores de probabilidad
"tpr", # Tasa de verdaderos positivos
"fpr" # Tasa de falsos positivos
)
# Graficamos la curva
plot(ROC.RF)
# Estimamos el desempeño del modelo
AUC.RF.temp <- performance(prediction.RF.ROC,
"auc" #Calculamos metrica auc
)
# Extrameos el valor numerico de AUC
AUC.RF<- as.numeric(AUC.RF.temp@y.values)
AUC.RF
## [1] 0.8986262