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
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? |
skim(edutravel)
Name | edutravel |
Number of rows | 2389 |
Number of columns | 41 |
_______________________ | |
Column type frequency: | |
character | 16 |
numeric | 25 |
________________________ | |
Group variables | None |
Variable type: character
skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
---|---|---|---|---|---|---|---|
Program.Code | 0 | 1.00 | 2 | 3 | 0 | 28 | 0 |
GroupState | 0 | 1.00 | 2 | 14 | 0 | 54 | 0 |
TransportType | 0 | 1.00 | 1 | 1 | 0 | 4 | 0 |
SpecialPay | 1919 | 0.20 | 2 | 2 | 0 | 3 | 0 |
Poverty.Code | 599 | 0.75 | 1 | 1 | 0 | 6 | 0 |
Region | 0 | 1.00 | 5 | 19 | 0 | 6 | 0 |
SchoolType | 0 | 1.00 | 3 | 21 | 0 | 4 | 0 |
Low.Grade | 68 | 0.97 | 1 | 2 | 0 | 12 | 0 |
Income.Level | 62 | 0.97 | 1 | 2 | 0 | 22 | 0 |
Product.Type | 0 | 1.00 | 7 | 13 | 0 | 6 | 0 |
New.Existing | 0 | 1.00 | 3 | 8 | 0 | 2 | 0 |
GradeLow | 0 | 1.00 | 4 | 10 | 0 | 4 | 0 |
GradeHigh | 0 | 1.00 | 4 | 10 | 0 | 4 | 0 |
DepartureMonth | 0 | 1.00 | 3 | 8 | 0 | 6 | 0 |
AggregatedCode | 0 | 1.00 | 1 | 1 | 0 | 4 | 0 |
Size | 91 | 0.96 | 1 | 3 | 0 | 4 | 0 |
Variable type: numeric
skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
---|---|---|---|---|---|---|---|---|---|---|
FromGrade | 127 | 0.95 | 7.27 | 1.39 | 3.0 | 7.00 | 8.00 | 8.00 | 12.00 | ▁▂▇▁▁ |
ToGrade | 150 | 0.94 | 7.91 | 1.56 | 3.0 | 8.00 | 8.00 | 8.00 | 12.00 | ▁▁▇▁▁ |
IsNonAnnual | 0 | 1.00 | 0.15 | 0.36 | 0.0 | 0.00 | 0.00 | 0.00 | 1.00 | ▇▁▁▁▂ |
Days | 0 | 1.00 | 4.58 | 1.43 | 1.0 | 4.00 | 5.00 | 5.00 | 12.00 | ▂▇▂▁▁ |
Price | 0 | 1.00 | 1615.22 | 645.10 | 79.0 | 1174.00 | 1700.00 | 2048.00 | 4200.00 | ▃▆▇▁▁ |
FRP.Active | 0 | 1.00 | 16.87 | 16.94 | 0.0 | 6.00 | 12.00 | 23.00 | 257.00 | ▇▁▁▁▁ |
FRP.Cancelled | 0 | 1.00 | 3.31 | 3.68 | 0.0 | 1.00 | 2.00 | 4.00 | 45.00 | ▇▁▁▁▁ |
FRP.Percentage | 0 | 1.00 | 0.57 | 0.23 | 0.0 | 0.46 | 0.60 | 0.73 | 1.00 | ▂▂▇▇▃ |
Cancelled.Pax | 0 | 1.00 | 4.81 | 4.66 | 0.0 | 2.00 | 4.00 | 6.00 | 39.00 | ▇▂▁▁▁ |
Extra.Pax | 0 | 1.00 | 2.95 | 2.88 | 0.0 | 1.00 | 2.00 | 4.00 | 47.00 | ▇▁▁▁▁ |
CRMSegment | 4 | 1.00 | 6.92 | 2.75 | 1.0 | 5.00 | 6.00 | 10.00 | 11.00 | ▁▇▂▁▇ |
Meeting.Flag | 0 | 1.00 | 0.86 | 0.35 | 0.0 | 1.00 | 1.00 | 1.00 | 1.00 | ▁▁▁▁▇ |
High.Grade | 68 | 0.97 | 8.39 | 1.75 | 1.0 | 8.00 | 8.00 | 8.00 | 12.00 | ▁▁▁▇▂ |
School.Enrollment | 91 | 0.96 | 648.36 | 411.73 | 19.0 | 360.00 | 597.00 | 825.75 | 3990.00 | ▇▂▁▁▁ |
EZ.Pay | 0 | 1.00 | 0.21 | 0.16 | 0.0 | 0.10 | 0.20 | 0.29 | 1.75 | ▇▂▁▁▁ |
School.Sponsor | 0 | 1.00 | 0.11 | 0.31 | 0.0 | 0.00 | 0.00 | 0.00 | 1.00 | ▇▁▁▁▁ |
FPP | 0 | 1.00 | 31.30 | 29.13 | 2.0 | 12.00 | 23.00 | 41.00 | 286.00 | ▇▁▁▁▁ |
Group.Revenue | 0 | 1.00 | 1615.22 | 645.10 | 79.0 | 1174.00 | 1700.00 | 2048.00 | 4200.00 | ▃▆▇▁▁ |
Meetings | 0 | 1.00 | 1.10 | 0.61 | 0.0 | 1.00 | 1.00 | 1.00 | 2.00 | ▂▁▇▁▃ |
FirstMeeting | 337 | 0.86 | 262.08 | 79.52 | -204.0 | 208.00 | 250.00 | 287.00 | 749.00 | ▁▁▇▂▁ |
LastMeeting | 337 | 0.86 | 228.98 | 53.64 | -204.0 | 196.75 | 233.00 | 261.00 | 749.00 | ▁▁▇▁▁ |
SingleGrade | 0 | 1.00 | 0.56 | 0.50 | 0.0 | 0.00 | 1.00 | 1.00 | 1.00 | ▆▁▁▁▇ |
FPP.Enrollment | 91 | 0.96 | 0.07 | 0.08 | 0.0 | 0.02 | 0.05 | 0.09 | 2.05 | ▇▁▁▁▁ |
FPP.Pax | 0 | 1.00 | 0.90 | 0.05 | 0.6 | 0.88 | 0.91 | 0.93 | 1.00 | ▁▁▁▇▆ |
Retained.in.2020 | 0 | 1.00 | 0.61 | 0.49 | 0.0 | 0.00 | 1.00 | 1.00 | 1.00 | ▅▁▁▁▇ |
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))
## Program.Code FromGrade ToGrade GroupState IsNonAnnual Days
## 1 HS 4 4 CA 0 1
## 2 HC 8 8 AZ 0 7
## 3 HD 8 8 FL 0 3
## 4 HN 9 12 VA 1 3
## 5 HD 6 8 FL 0 6
## 6 HC 10 12 LA 0 4
## TransportType SpecialPay Price FRP.Active FRP.Cancelled FRP.Percentage
## 1 A NA_filled 424 25 3 0.424
## 2 A CP 2350 9 9 0.409
## 3 A NA_filled 1181 17 6 0.708
## 4 B NA_filled 376 0 0 0.000
## 5 Others.TransportType NA_filled 865 40 8 0.494
## 6 A NA_filled 2025 9 4 0.900
## Cancelled.Pax Extra.Pax Poverty.Code Region CRMSegment
## 1 3 4 B Southern California 4
## 2 11 3 C Other 10
## 3 6 3 C Other 10
## 4 1 0 NA_filled Other 7
## 5 9 8 D Other 10
## 6 3 1 C Other 8
## SchoolType Meeting.Flag Low.Grade High.Grade School.Enrollment
## 1 PUBLIC 1 K 5 927
## 2 PUBLIC 1 7 8 850
## 3 PUBLIC 1 6 8 955
## 4 CHD 0 NA_filled 0 0
## 5 PUBLIC 1 6 8 720
## 6 PUBLIC 1 Others.Low.Grade 12 939
## Income.Level EZ.Pay School.Sponsor Product.Type New.Existing FPP
## 1 Q 0.170 1 CA History EXISTING 59
## 2 A 0.091 0 East Coast EXISTING 22
## 3 O 0.042 0 East Coast EXISTING 24
## 4 NA_filled 0.000 0 East Coast EXISTING 18
## 5 C 0.383 0 East Coast EXISTING 81
## 6 I 0.100 0 East Coast NEW 10
## Group.Revenue Meetings FirstMeeting LastMeeting GradeLow GradeHigh
## 1 424 1 155 155 Elementary Elementary
## 2 2350 2 423 140 Middle Middle
## 3 1181 1 124 124 Middle Middle
## 4 376 0 0 0 High High
## 5 865 1 145 145 Middle Middle
## 6 2025 1 91 91 High High
## DepartureMonth AggregatedCode SingleGrade FPP.Enrollment FPP.Pax
## 1 Others.DepartureMonth H 1 0.06364617 0.9365079
## 2 Others.DepartureMonth H 1 0.02588235 0.8800000
## 3 Others.DepartureMonth H 1 0.02513089 0.8888889
## 4 Others.DepartureMonth H 0 0.00000000 1.0000000
## 5 Others.DepartureMonth H 0 0.11250000 0.9101124
## 6 Others.DepartureMonth H 0 0.01064963 0.9090909
## Size Retained.in.2020 Low.High FromGrade_filledNA ToGrade_filledNA
## 1 L 1 5 0 0
## 2 L 1 8 0 0
## 3 L 1 8 0 0
## 4 NA_filled 0 NA_filled 0 0
## 5 M-L 0 8 0 0
## 6 L 1 12 0 0
## SpecialPay_filledNA Poverty.Code_filledNA CRMSegment_filledNA
## 1 1 0 0
## 2 0 0 0
## 3 1 0 0
## 4 1 1 0
## 5 1 0 0
## 6 1 0 0
## Low.Grade_filledNA High.Grade_filledNA School.Enrollment_filledNA
## 1 0 0 0
## 2 0 0 0
## 3 0 0 0
## 4 1 1 1
## 5 0 0 0
## 6 0 0 0
## Income.Level_filledNA FirstMeeting_filledNA LastMeeting_filledNA
## 1 0 0 0
## 2 0 0 0
## 3 0 0 0
## 4 1 1 1
## 5 0 0 0
## 6 0 0 0
## FPP.Enrollment_filledNA Size_filledNA Low.High_filledNA
## 1 0 0 0
## 2 0 0 0
## 3 0 0 0
## 4 1 1 1
## 5 0 0 0
## 6 0 0 0
# indicamos semilla para consistencia
set.seed(2021)
# separamos datos (80% entrenamiento y 20% prueba)
Partition <- createDataPartition(y = edutravelNA$Retained.in.2020,
p = 0.8, list = FALSE)
# sepramos en objetos
training <- edutravelNA[ Partition,]
testing <- edutravelNA[ -Partition,]
Cómo leer el gráfico del árbol de decisión generado por
CTREE
El gráfico representa un árbol de decisión que muestra cómo las
variables predictoras explican la variable respuesta
(Retained.in.2020
, en este caso, la retención de grupos al
siguiente año). Cada componente del árbol tiene un significado
específico. A continuación, se explica cómo interpretar sus
elementos:
Componentes del gráfico:
SingleGrade
, IsNonAnnual
,
New.Existing
, etc.SingleGrade = 1
, el árbol sigue por la
rama derecha; si SingleGrade = 0
, sigue
por la rama izquierda.Ejemplo de lectura del gráfico:
SingleGrade
):
SingleGrade
divide los datos en dos grandes
ramas:
SingleGrade = 0
).SingleGrade = 1
).IsNonAnnual
):
IsNonAnnual
):
IsNonAnnual = 0
).IsNonAnnual = 1
).Nodo 4
):
SingleGrade = 0
, IsNonAnnual = 0
, y el
grupo es EXISTING (New.Existing = EXISTING
), los grupos
terminan en el Nodo 4.n = 261
y la barra negra ocupa el 80%,
aproximadamente 209 grupos fueron retenidos.New.Existing
):
SingleGrade = 1
) y
participa en un programa anual, se evalúa si el grupo es nuevo o
existente:
FRP.Active
.Pasos para interpretar cualquier nodo:
n
) y las barras de proporción
para entender la probabilidad de éxito o fracaso en retención.Ejemplo práctico:
SingleGrade = 0
(múltiples grados).IsNonAnnual = 0
(programa anual).New.Existing = NEW
(nuevo grupo).FRP.Active > 14
(muchos seguros comprados).Notas finales:
- El árbol prioriza divisiones que maximicen la diferencia en probabilidades entre los grupos.
# indicmaos modelo
tree.ctree = ctree(training$Retained.in.2020 ~ .-GroupState,data=training)
# graficamos
# Now let's graph and adjust the font size
plot(tree.ctree, gp = gpar(fontsize = 7))
Interpretación del árbol de decisión
(CTREE
)
El árbol de decisión generado con el paquete CTREE
y
basado en la variable de respuesta Retained.in.2020
(indicador binario de éxito de retención al año siguiente) analiza cómo
las variables predictoras afectan esta retención. A continuación, una
interpretación detallada:
Nodo Raíz:
1. SingleGrade
(p < 0.001): - El
modelo evalúa si el grupo contiene estudiantes de un solo grado
(SingleGrade
) como primer punto de división.
Rama Izquierda (Grupos con múltiples grados):
IsNonAnnual
(p < 0.001):IsNonAnnual
).- Si es 1 (programa no anual), se mueve a la derecha.
- Si es 0 (programa anual), se mueve a la izquierda.
New.Existing
(p < 0.001):
Para los programas anuales, el árbol evalúa si el grupo es nuevo
(NEW
) o ya ha participado previamente
(EXISTING
).
Grupos EXISTING:
Grupos NEW:
FRP.Active
(p = 0.015):
Rama Derecha (Grupos de un solo grado): 5.
IsNonAnnual
(p < 0.001): - Evalúa
nuevamente si el programa es no anual.
New.Existing
(p < 0.001):
Análisis similar para grupos de programas no anuales.
Grupos EXISTING:
FRP.Active
(p < 0.001):
Poverty.Code
, p = 0.01).
Grupos NEW:
FRP.Active
.Conclusiones:
Factores principales:
- SingleGrade
y IsNonAnnual
son los
primeros determinantes de retención.
Impacto del seguro
(FRP.Active
):
Pobreza (Poverty.Code
):
Programas no anuales:
Sugerencias: Puedes usar esta información para diseñar estrategias focalizadas, como ofrecer incentivos adicionales a grupos nuevos o en áreas de mayor pobreza para mejorar la retención.
Para predecir la probabilidad usamos el modelo de árbol de decisión. Para ellos usamos el conjunto de datos de prueva y adicional, que queremos como outcome la probabilidad.
prob.ctree<-predict(tree.ctree,newdata=testing,type="prob")
head(prob.ctree)
## 0 1
## 9 0.05991736 0.9400826
## 10 0.05991736 0.9400826
## 11 0.05991736 0.9400826
## 21 0.05991736 0.9400826
## 29 0.28352490 0.7164751
## 30 0.28352490 0.7164751
Con el anterior codigo creamos una tabla que tiene dos categorías:
La anterior tabla de datos tiene 477 observaciones, las cuales clasificaremos según un umbral. Para ello, necesitamos el valor de corte. Generalmente, dicho umbral se calcula en función de una función de costos/utilidad.
Como no tenemos dicha función de costos/utilidad, calculemos el valor de corte como la probabilidad promedio de retención
mean(as.integer(edutravelNA$Retained.in.2020))
## [1] 1.607367
Podemos observar que la media es 1,607 lo que nos debe llamar la atención porque es mayor que 1. Cuando echamos un vistazo a la dependiente notamos que R guarda con los valores 1 y 2 en lugar de 0 y 1.
Así que todo lo que tenemos que hacer es restar 1 a la media,y obtenemos que la probabilidad media de ser retenido es 0.607, que es lo que usaremos como valor de corte
Primero vamos a crear un vector de 477 unos, el tamaño de la base de prueba
classification.ctree<- rep("1",477)
head(classification.ctree)
## [1] "1" "1" "1" "1" "1" "1"
Ahora asignaremos cero para aquellas observaciones con una retención menor a 0.607
classification.ctree[prob.ctree[,2]<0.607]= "0"
Primero convertirmeos el modelo a factoe, ya que la función que utilizaremos para crear la Matriz de Confusion requiere que los datos sean factores con el mismo numero de niveles, en nuestro caso dos nivels 0 y 1
classification.ctree<- as.factor(classification.ctree)
Aplicamos función para matriz de confusión
confusionMatrix(classification.ctree,testing$Retained.in.2020, positive = "1")
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 138 37
## 1 49 253
##
## Accuracy : 0.8197
## 95% CI : (0.7822, 0.8532)
## No Information Rate : 0.608
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.6174
##
## Mcnemar's Test P-Value : 0.2356
##
## Sensitivity : 0.8724
## Specificity : 0.7380
## Pos Pred Value : 0.8377
## Neg Pred Value : 0.7886
## Prevalence : 0.6080
## Detection Rate : 0.5304
## Detection Prevalence : 0.6331
## Balanced Accuracy : 0.8052
##
## 'Positive' Class : 1
##
Cálculos de los errores:
Este paso básicamente almacena la información necesaria para evaluar qué tan bien las predicciones del modelo coinciden con los valores reales.
prediction.ctree.ROC <- prediction(prob.ctree[,2], testing$Retained.in.2020)
La función prediction() crea un objeto que contiene las predicciones del modelo y los valores verdaderos. Este objeto es necesario para calcular métricas como la sensibilidad, la especificidad y la curva ROC.
Posteriormente evaluamos el desempeño: performance(): Calcula métricas de rendimiento del modelo.
Argumentos:
- prediction.ctree.ROC: Objeto con las predicciones y valores verdaderos.
- “tpr”: Tasa de verdaderos positivos (True Positive Rate, Sensibilidad). Se usará en el eje y.
- “fpr”: Tasa de falsos positivos (False Positive Rate, 1 - Especificidad). Se usará en el eje x.
ROC.ctree <- performance(prediction.ctree.ROC,"tpr","fpr")
Graficamos datos
plot(ROC.ctree)
La curva ROC muestra el equilibrio entre sensibilidad (o TPR) y especificidad (1 - FPR). Los clasificadores cuya curva se acerca más a la esquina superior izquierda indican un mejor rendimiento. Como referencia, se espera que un clasificador aleatorio tenga puntos a lo largo de la diagonal (FPR = TPR). Cuanto más cerca esté la curva ROC de la diagonal de 45 grados, menos preciso será el modelo. Aunque en este momento esta curva pueda no parecer significativa, será muy útil como herramienta de comparación cuando tengamos al menos dos modelos.
Interpretación práctica del AUC
Guía para interpretar los valores del AUC:
El AUC es especialmente útil para comparar la precisión de modelos diferentes, como determinar si una regresión logística tiene un mejor rendimiento predictivo que un árbol de clasificación.
Calculamos el área bajo la curva usando la función `performance`, especificando que queremos calcular auc (area under the curve)
AUC.temp <- performance(prediction.ctree.ROC,"auc")
Obtenemos el valor numérico indicando el y valor
Diferencia entre $
y
@
:
$
: Se utiliza para acceder a elementos en listas,
dataframes o clases más simples.
@
: Se utiliza para acceder a slots en objetos de
clases S4, que son más complejas.
AUC.ctree <- as.numeric(AUC.temp@y.values)
AUC.ctree
## [1] 0.8530242
print(paste("El área bajo la curva es", AUC.ctree*100,"%"))
## [1] "El área bajo la curva es 85.3024156371012 %"
cp (complexity parameter): Controla el grado de complejidad del árbol.
Interpretación: Este parámetro define el cambio mínimo en el criterio de ajuste (por ejemplo, Gini para clasificación) que debe ocurrir para que una división adicional se realice en el árbol.
Un valor más bajo de cp permite que el árbol sea más complejo (es decir, tenga más nodos), mientras que un valor más alto lo restringe y lo hace más simple.
rpart.cp = rpart.control(cp = 0.0005)
tree.rpart<-rpart(Retained.in.2020 ~.,data=training,
method="class", control=rpart.cp)
rpart.plot(tree.rpart)
Podemos observar que el nivel de complejidad del árbol impide la
interpretación y fácilmente se podría pensar en que hay un sobre ajuste
del modelo. Por tal motivo, debemos encontrar el valor optimo de
cp
# Impriimos valores
printcp(tree.rpart)
##
## Classification tree:
## rpart(formula = Retained.in.2020 ~ ., data = training, method = "class",
## control = rpart.cp)
##
## Variables actually used in tree construction:
## [1] Cancelled.Pax CRMSegment DepartureMonth Extra.Pax
## [5] FPP FPP.Enrollment FromGrade FRP.Active
## [9] FRP.Percentage GroupState Income.Level IsNonAnnual
## [13] LastMeeting Low.Grade New.Existing Price
## [17] Program.Code Region School.Enrollment SingleGrade
## [21] Size
##
## Root node error: 751/1912 = 0.39278
##
## n= 1912
##
## CP nsplit rel error xerror xstd
## 1 0.30359521 0 1.00000 1.00000 0.028435
## 2 0.07523302 1 0.69640 0.69640 0.025955
## 3 0.00985353 3 0.54594 0.54061 0.023812
## 4 0.00865513 8 0.49667 0.58855 0.024546
## 5 0.00732357 10 0.47936 0.59254 0.024604
## 6 0.00665779 12 0.46471 0.60053 0.024719
## 7 0.00621394 18 0.42477 0.60586 0.024794
## 8 0.00532623 23 0.39015 0.61252 0.024887
## 9 0.00399467 27 0.36884 0.62716 0.025088
## 10 0.00332889 30 0.35686 0.62850 0.025105
## 11 0.00310697 39 0.32490 0.63116 0.025141
## 12 0.00266312 42 0.31558 0.64048 0.025264
## 13 0.00199734 45 0.30759 0.66045 0.025520
## 14 0.00177541 47 0.30360 0.66312 0.025554
## 15 0.00133156 50 0.29827 0.66312 0.025554
## 16 0.00066578 52 0.29561 0.67643 0.025718
## 17 0.00050000 58 0.29161 0.67244 0.025669
# Graficamos valores
plotcp(tree.rpart)
Podemos observar que con cp=0.027
se reduce el error
antes de que vuelva a incrementar marginalmente.
Elementos del gráfico:
Colores:
pruned.tree <-prune(tree.rpart, cp=0.027)
rpart.plot(pruned.tree)
El gráfico muestra un árbol de decisión optimizado con un parámetro de complejidad (cp=0.027) que clasifica instancias basándose en las variables SingleGrade, IsNonAnnual y New.Existing. La raíz del árbol divide los datos en función de si SingleGrade = 0 o no, donde el 61% de los datos iniciales pertenecen a la clase 1. A medida que se desciende por el árbol, las divisiones adicionales como IsNonAnnual = 1 y New.Existing = NEW permiten afinar las predicciones. Los nodos terminales muestran las probabilidades y proporciones de pertenencia a cada clase, con colores que indican la clase predominante (verde para 1 y azul para 0). Esto refleja cómo las combinaciones de variables afectan la probabilidad de pertenecer a una clase específica.
El siguiente código nos dará como resultado dos columnas. La columna cero es la probabilidad de no retención y la columna uno es la probabilidad de retención
prob.rpart<-predict(
pruned.tree, # Idicamos el modelo de árbol creado
newdata=testing, # Indicamos que es con los datos de prueba
type="prob" # Indicamos que queremos por tipo de probabilidad
)
Ahora, realizamos la calificación del árbol. Para ello creamos un vector de “1” que tenga la misma cantidad de observaciones que el conjunto de datos de prueba (En este caso 477)
classification.rpart<-rep("1",477)
Posteriormente, asignamos los valores equivalentes a cero con base al umbral que habíamos construido anteriormente. Para ello, tomamos los valores de probabilidad respectivamente asignados, específicamente de la columna dos, que son los que indican “1” (retención)
classification.rpart[prob.rpart[,2]<0.607]="0"
Para poder categorizar en la matriz de confusión es importante deja la variable en tipo factor:
classification.rpart<- as.factor(classification.rpart)
Ahora si elaboramos la matriz de confusión
confusionMatrix(classification.rpart, # indicamos los valores de probabilidad
testing$Retained.in.2020, # indicamos los valores del conjunto de prueba
positive = "1" # indicamos los caso positivos son para aquellos igual a 1
)
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 131 34
## 1 56 256
##
## Accuracy : 0.8113
## 95% CI : (0.7733, 0.8455)
## No Information Rate : 0.608
## P-Value [Acc > NIR] : < 2e-16
##
## Kappa : 0.5957
##
## Mcnemar's Test P-Value : 0.02686
##
## Sensitivity : 0.8828
## Specificity : 0.7005
## Pos Pred Value : 0.8205
## Neg Pred Value : 0.7939
## Prevalence : 0.6080
## Detection Rate : 0.5367
## Detection Prevalence : 0.6541
## Balanced Accuracy : 0.7916
##
## 'Positive' Class : 1
##
En esta parte vamos a graficar la curva ROC del modelo
“rpart
” y compararemos la curva con el modelo realizado con
el modelo “ctree
”.
Primero, calculamos el error del modelo “rpart
”
prediction.rpart.ROC <- prediction(prob.rpart[,2], # Columna de unos
testing$Retained.in.2020)
Segundo, estimamos los valores de la curva ROC
ROC.rpart <- performance(prediction.rpart.ROC, # errores de la clasificación
"tpr", # tasa de verdaderos positivos
"fpr" # tasa de falsos positivos
)
# Graficamos la curva
plot(ROC.rpart)
Tercero, compramos la anterior curva con el modelo
“ctree
”.
plot(ROC.ctree)
No hace falta calcular el area bajo la curva, podemos ver que el
modelo “ctree
” tiene un mejor desempeño
#plot(ROC.ctree, # Indicamos el modelo
# add=TRUE, # no borramos el grafico anterior
# col="red" # Indicamos que el la nueva curva sea roja
#)
Cuarto, calculamos el AUC (area under the curve) para poder comparar los modelos.
# Creamos AUC datos
AUC.temp.rpart <- performance(prediction.rpart.ROC,"auc") # Create AUC data
# Extraemos valores numericos
AUC.rpart <- as.numeric(AUC.temp.rpart@y.values)
AUC.rpart
## [1] 0.8157662
# Ahora comparemoslo con el AUC de ctree
AUC.ctree
## [1] 0.8530242
Podemos ver que ambos modelos tien un un buen AUC. Sn embaergo, el el
modelo “ctree
” es ligeramente mejor.