0% encontró este documento útil (0 votos)
35 vistas58 páginas

TEMA2

Este documento describe los árboles de decisión, incluyendo su definición, generación, ventajas e inconvenientes. Explica cómo se crean árboles de decisión para problemas de regresión y clasificación, utilizando métricas como el error de clasificación, índice de Gini y entropía. Incluye ejemplos prácticos utilizando conjuntos de datos como Iris y Boston para ilustrar cómo funcionan los árboles de decisión.

Cargado por

kamilzar1993
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
35 vistas58 páginas

TEMA2

Este documento describe los árboles de decisión, incluyendo su definición, generación, ventajas e inconvenientes. Explica cómo se crean árboles de decisión para problemas de regresión y clasificación, utilizando métricas como el error de clasificación, índice de Gini y entropía. Incluye ejemplos prácticos utilizando conjuntos de datos como Iris y Boston para ilustrar cómo funcionan los árboles de decisión.

Cargado por

kamilzar1993
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 58

TEMA 2.

ÁRBOLES DE DECISIÓN
Máxima Formación

Contents
1 Introducción: 3

1. Árbol de decisión: Definición. 3


1.1. Generación de un árbol de decisión. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2. Ejemplo práctico de creación de un árbol de decisión. . . . . . . . . . . . . . . . . . . . . . . . 5
1.3. Ventajas e Inconvenientes de los árboles de decisión: . . . . . . . . . . . . . . . . . . . . . . . . 9
1.4. Overfitting y Underfitting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5. Instalación de paquetes y Definición del Modelo. . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.6. Métricas de error: Regresión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.7. Métricas de error: Clasificación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.8. Parámetros función: rpart(). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

2. Árbol de decisión: Regresión. 15


2.1. Dataset: Iris. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2. Dataset: Boston . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.3. Poda del árbol: Error Promedio más una desviación típica. . . . . . . . . . . . . . . . . . . . . 26

3. Árbol de decisión: Clasificación. 30


3.1. Dataset: Iris. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.2. Dataset: Churn. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.3. Poda de árbol: Cp óptima. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.4. Grid Search: Selección de Hiperparámetros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

1
4. Paquete Caret: Validación Cruzada 52

Anexo 1: Categorizar variables. 55

2
1 Introducción:
Los árboles de decisión son uno de los algoritmos más utilizados en la minería de datos para problemas,
principalmente, de clasificación supervisada. Esto es debido a su fácil interpretación y visualización. Se
suelen utilizar en múltiples campos como medicina, economía o en seguros. Su capacidad predictiva es
superada por otros algoritmos, pero su fácil implementación e interpretación hacen que sea de uso frecuente.
Son modelos supervisados porque para que aprenda el modelo necesitamos una variable dependiente en el
conjunto de entrenamiento.

1. Árbol de decisión: Definición.


Se puede definir como un modelo de predicción que divide el espacio de los predictores agrupando ob-
servaciones con valores similares para la variable respuesta o dependiente. Divide el espacio muestral en
sub-regiones, mediante la aplicación de una serie de reglas o decisiones, buscando que cada subregión con-
tenga la mayor proporción posible de individuos de una de las poblaciones. Si una subregión contiene datos
de diferentes clases, es subdividida en regiones más pequeñas hasta dividir el espacio en sub-regiones menores
que contengan datos de la misma clase.
Cuando la variable a predecir o dependiente es numérica estaríamos ante un problema de regresión y si la
variable es categórica, tendríamos un problema de clasificación.
Los árboles de decisión, tanto de clasificación como de regresión, están formados por nodos. Su lectura se
realiza de arriba hacia abajo, donde el primer nodo es el nodo raíz, en el cual se realiza la primera división
en función de la variable más importante. Después de esta división nos encontramos con los nodos internos
o intermedios, que vuelven a dividir el conjunto de datos en función de las variables. En la parte inferior
tenemos los nodos terminales u hojas, donde se indica la clasificación definitiva. La profundidad de un árbol
es el número máximo de nodos de una rama.

Figure 1: Estructura de un Árbol

3
1.1. Generación de un árbol de decisión.

La creación de un árbol de decisión de un problema de clasificación se realiza por medio del algoritmo de
Hunt que se basa en la división en sub-conjuntos, buscando una separación óptima. Dado un conjunto de
registros de entrenamiento de un nodo, si pertenecen a la misma clase se considera un nodo terminal, pero
si pertenecen a varias clases, se dividen los datos en subconjuntos más pequeños en función de una variable
y se repite el proceso. Para seleccionar que variable elegir para obtener la mejor división se puede considerar
el Error de Clasificación, el índice Gini (rpart) o la Entropía (C50).
El índice de Gini mide el grado de pureza de un nodo. Nos mide la probabilidad de no sacar dos registros de
la misma clase del nodo. A mayor índice de Gini menor pureza, por lo que seleccionaremos la variable con
menor Gini ponderado. Suele seleccionar divisiones desbalanceadas, donde normalmente aísla en un nodo
una clase mayoritaria y el resto de clases los clasifica en otros nodos.
Se define el índice de Gini como:

n
X
GIN I(t) = 1 − (Pi )2
i=1

Donde Pi es la probabilidad de que un ejemplo sea de la clase i.


La entropía es una medida para cuantificar el desorden de un sistema. Si un nodo es puro su entropía es
0 y solo tiene observaciones de una clase, pero si la entropía es igual a 1, existe la misma frecuencia para
cada una de las clases de observaciones. La entropía tiende a crear nodos balanceados en el número de
observaciones. Relacionado con la entropía se define la Ganancia de Información que busca la división con
mayor ganancia de información, es decir, con menor entropía ponderada de la variable.
Se define la entropía como:

n
X
H=− Pi ∗ log2 Pi
i=1

Donde Pi es la probabilidad de que un ejemplo sea de la clase i.


En el caso de los árboles de decisión de un problema de regresión se utiliza el RSS (Residual Sum of Squares)
que es una medida de la discrepancia entre los datos reales y los predichos por el modelo. Un RSS bajo
indica un buen ajuste del modelo a los datos, es decir, se busca minimizar el RSS.
Se define el RSS como
n
X
RSS = (yi − ŷi )2
i=1

Donde yi es el valor real de la variable a predecir y ŷi es el valor predicho.

4
1.2. Ejemplo práctico de creación de un árbol de decisión.

Realizamos un ejemplo práctico de cómo se realiza un árbol de decisión con un conjunto de datos para la
concesión de un préstamo con datos simulados. Vamos a utilizar el criterio de la entropía aunque es muy
similar al criterio del Índice de Gini1 en la forma de desarrollarlo. Creamos un dataframe con tres variables
independientes cualitativas y una variable dependiente binaria, para facilitar el ejemplo y comprensión
Creamos nuestro conjunto de datos simulados.

(credito <- data.frame(Conceder = c("Si","No","Si","Si","Si","No","Si","Si","No","Si"),


Renta = c("Alta","Alta","Media","Media","Alta","Media","Baja","Media","Baja","Media"),
Nomina_Domiciliada = c("Si","No","No","Si","Si","No","No","No","No","Si"),
Estudios = c("Univ","Prim","Secun","Univ","Secun","Prim","Secun","Secun","Secun","Secun")))

## Conceder Renta Nomina_Domiciliada Estudios


## 1 Si Alta Si Univ
## 2 No Alta No Prim
## 3 Si Media No Secun
## 4 Si Media Si Univ
## 5 Si Alta Si Secun
## 6 No Media No Prim
## 7 Si Baja No Secun
## 8 Si Media No Secun
## 9 No Baja No Secun
## 10 Si Media Si Secun

Lo primero que vamos a hacer es calcular la entropía de nuestro dataset. Tenemos 7 casos de “conceder”
un préstamo y 3 casos de “no conceder” el préstamo, sobre un total de observaciones de 10. Como variables
explicativas tenemos tipo de renta con tres niveles (Alta, Media, Baja), nómina domiciliada con dos niveles
(Si, No) y clase de estudios con tres niveles (Primarios, Secundarios y Universitarios).
Utilizamos la fórmula de la entropía para nuestro conjunto de datos:

h = -sum((7/10) * log2(7/10) + (3/10) * log2(3/10))


paste("La Entropía de nuestro dataframe es: ", round(h,4))

## [1] "La Entropía de nuestro dataframe es: 0.8813"

Seleccionamos nuestra primera variable independiente Renta relacionándola con la variable Conceder:

table(credito$Renta,credito$Conceder)

##
## No Si
## Alta 1 2
## Baja 1 1
## Media 1 4
1 Si quisiéramos calcularlo por medio del Índice de Gini, se realizaría de forma similar. Ejemplo de la variable Estu-

dios: GINI(Estudios=Prim)= 1-(2/2)2-(0/10) 2=0; GINI(Estudios=Secun)= 1-(1/6)2-(5/6) 2= 0.2777; GINI(Estudios=Univ)=


1-(0/2)2-(2/2) 2=0; GINI (Estudios)= (2/10)x0 + (6/10)x0.2777+(2/10)x0= 0.16662.

5
Como vemos en la tabla superior, tenemos para el nivel de renta alta dos “conceder” y un “no conceder”,
para el nivel de renta baja tenemos un “conceder” y un “no conceder”, y por último el nivel medio cuatro
“conceder”" y un “no conceder”.
Calculamos la entropía para cada una de las posibilidades:

#H(Renta = Alta)
h_renta_alta <- -sum((1/3) * log2(1/3) + (2/3) * log2(2/3))
paste("H(Renta = Alta): ", round(h_renta_alta,4))

## [1] "H(Renta = Alta): 0.9183"

#H(Renta = Baja)
h_renta_baja <- -sum((1/2) * log2(1/2) + (1/2) * log2(1/2))
paste("H(Renta = Baja): ", round(h_renta_baja,4))

## [1] "H(Renta = Baja): 1"

#H(Renta = Media)
h_renta_media <- -sum((1/5) * log2(1/5) + (4/5) * log2(4/5))
paste("H(Renta = Media): ", round(h_renta_media,4))

## [1] "H(Renta = Media): 0.7219"

Ahora calculamos la entropía de la variable independiente que es el promedio de la entropía de cada valor
posible por la probabilidad de que variable tome dicho valor.

h_renta <- (3/10) * h_renta_alta + (2/10) * h_renta_baja + (5/10) * h_renta_media


paste("H(Renta): ", round(h_renta,4))

## [1] "H(Renta): 0.8365"

Calculamos la ganancia de información como la diferencia de la entropía de nuestro dataframe menos la


entropía de la variable Renta es:

gi_renta <- h - h_renta


paste("La Ganancia de Información de la variable Renta es: ", round(gi_renta,4))

## [1] "La Ganancia de Información de la variable Renta es: 0.0448"

Ahora realizamos los mismos cálculos para la variable Nomina_Domiciliada:

table(credito$Nomina_Domiciliada,credito$Conceder)

##
## No Si
## No 3 3
## Si 0 4

Calculamos la entropía para cada una de las dos posibilidades:

6
#H(Nomina_Domiciliada = No)
h_nomina_no <- -sum((3/6) * log2(3/6) + (3/6) * log2(3/6))
paste("H(Nomina_Domiciliada = No): ", round(h_nomina_no,4))

## [1] "H(Nomina_Domiciliada = No): 1"

#H(Nomina_Domiciliada = Si). Este caso la entropía es cero ya que clasifica


#todas las observaciones en la clase "Si"
h_nomina_si <- 0
paste("H(Nomina_Domiciliada = Si): ", round(h_nomina_si,4))

## [1] "H(Nomina_Domiciliada = Si): 0"

Calculamos la entropía de la variable Nomina_Domiciliada:

h_nomina <- (6/10) * h_nomina_no + (4/10) * h_nomina_si


paste("H(Nomina_Domiciliada): ", round(h_nomina,4))

## [1] "H(Nomina_Domiciliada): 0.6"

Calculamos la ganancia de información como la diferencia de la entropía de nuestro dataframe menos la


entropía de la variable Nomina_Domiciada es:

gi_nomina <- h - h_nomina


paste("La Ganancia de Información de la variable Nomina_Domiciliada es: ", round(gi_nomina,4))

## [1] "La Ganancia de Información de la variable Nomina_Domiciliada es: 0.2813"

Por último, hacemos los cálculos para la variable Estudios:

table(credito$Estudios,credito$Conceder)

##
## No Si
## Prim 2 0
## Secun 1 5
## Univ 0 2

Calculamos la entropía para cada una de las dos posibilidades:

#H(Estudios = Prim) La entropía es cero ya que clasifica todas las observaciones en la clase "No"
h_estudios_prim <- 0
paste("H(Estudios = Prim): ", round(h_estudios_prim,4))

## [1] "H(Estudios = Prim): 0"

7
#H(Estudios = Secun).
h_estudios_secun<- -sum((1/6) * log2(1/6) + (5/6) * log2(5/6))
paste("H(Estudios = Secun): ", round(h_estudios_secun,4))

## [1] "H(Estudios = Secun): 0.65"

#H(Estudios = Univ) La entropía es cero ya que clasifica todas las observaciones en la clase "Si"
h_estudios_univ <- 0
paste("H(Estudios=Univ): ", round(h_estudios_univ,4))

## [1] "H(Estudios=Univ): 0"

Calculamos la entropía de la variable Estudios:

h_estudios <- (2/10) * h_estudios_prim + (6/10) * h_estudios_secun + (2/10) * h_estudios_univ


paste("H(Estudios): ", round(h_estudios,4))

## [1] "H(Estudios): 0.39"

Calculamos la ganancia de información como la diferencia de la entropía de nuestro dataframe menos la


entropía de la variable Estudios es:

gi_estudios <- h - h_estudios


paste("La Ganancia de Información de la variable Nomina_Domiciliada es: ", round(gi_nomina,4))

## [1] "La Ganancia de Información de la variable Nomina_Domiciliada es: 0.2813"

Elegimos como variable para el nodo raíz aquella que tenga mayor Ganancia de Información:

paste("GI_Renta: ",gi_renta)

## [1] "GI_Renta: 0.0448381015706647"

paste("GI_Nomina: ",gi_nomina)

## [1] "GI_Nomina: 0.281290899230693"

paste("GI_Estudios: ",gi_estudios)

## [1] "GI_Estudios: 0.49127744624168"

La variable con mayor Ganancia de Información es “Estudios” con una ganancia de 0.4912774 por lo que
será nuestro nodo raíz, donde si tiene estudios universitarios se concede el préstamo (2 observaciones) y si
tiene estudios primarios no se concede el préstamo (2 observaciones). No se ha clasificado cuando el cliente
tiene estudios secundarios con 6 observaciones por definir. Tendríamos que volver a calcular la Ganancia de
Información para todas las variables y sus posibilidades, pero desde el nodo en el que “Estudios = Secun”,
es decir, solo para esas 6 observaciones pendientes.

8
(credito_secun <- subset(credito, credito$Estudios == "Secun"))

## Conceder Renta Nomina_Domiciliada Estudios


## 3 Si Media No Secun
## 5 Si Alta Si Secun
## 7 Si Baja No Secun
## 8 Si Media No Secun
## 9 No Baja No Secun
## 10 Si Media Si Secun

Podemos seguir con este proceso hasta clasificar cada observación en un nodo puro, en donde todas las
observaciones son de la misma clase (entropía = 0), pero podríamos caer en un problema de sobreajuste
(overfitting), es decir, que nuestro modelo se ajustará tanto a nuestro conjunto de entrenamiento que cuando
se enfrente a nuevos casos su nivel de precisión disminuye.

1.3. Ventajas e Inconvenientes de los árboles de decisión:

Las ventajas de utilizar árboles de decisión son:

• Son fáciles de construir, interpretar y de visualizar. Además, selecciona las variables más importantes
y no siempre utiliza para crear el árbol todos los predictores.
• Si faltan datos no podremos recorrer el árbol hasta un nodo terminal, pero podemos hacer predicciones
promediando las hojas del subárbol que alcancemos.
• No se necesita que se cumplan una serie de supuestos como en la regresión lineal (linealidad, normalidad
de los residuos, homogeneidad de la varianza. . . ).
• Sirve tanto para variable dependientes cualitativas como cuantitativas, así como para variables predic-
toras o independientes numéricas y categóricas. Además, no necesita variables dummys, aunque hay
veces que mejoran el modelo.
• Permiten relaciones no lineales entre las variables explicativas y la variable dependiente.
• Nos sirven para categorizar variables numéricas.

Los Inconvenientes de utilizar árboles de decisión son:

• Suelen tender hacia el sobreajuste u overfitting de los datos, por lo que al predecir nuevos casos el
modelo no estima con el mismo índice de acierto2 .
• Se ven influenciadas por los outliers, creando árboles con ramas muy profundas que no predicen bien
para nuevos casos. Se deben eliminar dichos outliers.
• No suelen ser muy eficientes con modelos de regresión.
• Al crear un árbol demasiado complejo puede que no se adapte bien a nuevos datos. Dicha complejidad
puede hacer que no sea fácil de interpretar.
• Se pueden crear árboles sesgados si una de las clases es más numerosa que otra.
• Cuando se utilizan para categorizar una variable numérica continua se pierde información.

1.4. Overfitting y Underfitting

El sobreajuste (Overfitting) surge cuando sobre-entrenamos nuestro modelo ajustándolo perfectamente a


los datos de entrenamiento. Este ajuste sobre el conjunto de datos de entrenamiento hace que se reduzca
la capacidad predictiva del modelo al enfrentarse a nuevos datos con los que no ha sido entrenado. El
2 Para
solucionar este problema existen modelos de múltiples árboles de decisión (random forest, bagging, GBM. . . ) y
técnicas de podado de árboles que veremos más adelante.

9
sobreajuste en los modelos de árboles de decisión sucede cuando dejamos crecer demasiado nuestro árbol
(árboles muy profundos) creando estructuras complejas que pueden llegar a tener tantos nodos terminales
como observaciones tiene el conjunto de entrenamiento, clasificando cada una de las observaciones en un
nodo terminal.
Por el contrario, el subajuste (Underfitting) del modelo surge cuando el modelo no puede capturar correcta-
mente la estructura de los datos por falta de entrenamiento del modelo. Esto puede suceder por tener escasas
observaciones que no representan al conjunto de datos, falta de variables explicativas, excesivas limitaciones
de los parámetros del modelo, etc. Dicho modelo tendrá poco poder predictivo.
Para combatir el Overfitting tenemos dos alternativas:

1. Controlar el tamaño del árbol definiendo en el modelo parámetros (estrategia greedy) que lo limitan,
como son: la profundidad máxima del árbol, el máximo de nodos terminales, el número mínimo de
observaciones que tiene que tener un nodo para ser dividido, el número de observaciones que tiene que
tener como mínimo un nodo terminal o la reducción mínima del error que tiene que mejorar para que se
realice una nueva división. El inconveniente que tienen las estrategias greedy es que nosotros tenemos
que seleccionar dichos parámetros en función del conocimiento del problema o los objetivos marcados.
2. También podemos realizar un proceso de podado (estrategia no greedy). Creamos un modelo sin ningún
tipo de límite dejando crecer el árbol lo máximo posible para después podarlo (tree pruning). Se busca
por medio de cross-validation la selección del sub-árbol optimo que minimice la tasa de error. Existen
dos posibilidades:

• Utilizar el coste de complejidad que minimice el error promedio obtenido por Cross-Validation.
• Utilizar el coste de complejidad que suponga el menor error promedio obtenido por Cross-
Validation más la desviación típica.

1.5. Instalación de paquetes y Definición del Modelo.


Nosotros utilizaremos una serie de paquetes para realizar nuestros modelos de aprendizaje automático, pero
casi siempre hay más de uno para realizar el mismo algoritmo. Un ejemplo claro son los paquetes que
existen en R para realizar bosques aleatorios, que veremos en el tema 3, y que existen más de 20 paquetes,
diferenciándose entre ellos en el tiempo de computación, parámetros a ajustar o como se formulan el modelo.
Los paquetes que vamos a utilizar para crear árboles de decisión son rpart y rpart.plot por lo que vamos a
proceder a cargarlos.

#install.packages(c("rpart","rpart.plot"))

La función rpart() identifica el tipo de problema dependiendo si la variable dependiente es numérica o


categórica, pero podemos indicarle ante qué tipo de problema se encuentra con el parámetro method, in-
dicando “anova” para problemas de regresión, “class” para problemas de clasificación o “poisson” para
problemas de conteo. Utilizaremos rpart.plot() para realizar los gráficos de nuestros modelos, ya que se
pueden hacer gráficos más visuales.
Al existir varios paquetes para realizar el mismo modelo, nos encontramos con distintas formas de formularlos.
Los siguientes tres funciones producirán la misma salida de un modelo de regresión lineal múltiple formulados
con diferentes formas y paquetes.

• model_lm <- lm( VarD ~. , data = train)


• model_glm <- glm(VarD ~ VarIN1 + VarIN2+ . . . + VarINN, data = train)
• model_caret <- train (x= VarD, y = VarIN1 + VarIN2+ . . . + VarINN, data = train, method = “lm”)

Donde VarD es la variable dependiente o a predecir, VarIN1 + VarIN2+ . . . + VarINN son las variables
predictoras y data son el conjunto de entrenamiento.

10
1.6. Métricas de error: Regresión
Cuando nos enfrentamos con un problema de regresión podemos utilizar distintas métricas para evaluar la
precisión de nuestro modelo y poder comparar entre distintos modelos. Vamos a ver las más comunes:

• R2 : Es la proporción de la varianza en la variable dependiente o a predecir, que es explicada con las


variables independientes. Su objetivo es la maximización de dicha métrica.
• MSE: se denomina el error cuadrático medio. Mide la diferencia entre la estimación y el valor estimado.
Eleva los errores promedio al cuadrado. El objetivo de esta métrica es minimizarla.

n
1X
M SE = (yi − yˆi )2
n i=1
Donde yi es el valor de la variable respuesta y donde yˆi es la predicción de yi .

• RMSE: Se basa en el MSE, ya que se toma la raíz cuadrada del MSE, para que el error esté en las
mismas unidades que la variable respuesta. Como en el MSE, su objetivo es minimizar la métrica.

v
u n
u1 X
RM SE = t (yi − yˆi )2
n i=1

Donde yi es el valor de la variable respuesta y donde yˆi es la predicción de yi .

• RMSLE: Error logarítmico medio cuadrático. Cuando existen grandes errores pueden dominar las
métricas del MSE y RMSE, en cambio el RMSLE minimiza el impacto para que los valores respuesta
pequeños sean igual de significativos que los grandes. El objetivo es minimizar esta métrica.

v
u n
u1 X
RM SLE = t (log(yi + 1) − log(yi ˆ+ 1))2
n i=1

Donde yi es el valor de la variable respuesta y donde yˆi es la predicción de yi .

• MAE: Es la media de error absoluto. Es similar al MSE con la diferencia que no toma el error
cuadrático sino el valor absoluto de la media de los errores. El objetivo es minimizarlo. Puede tomar
valores negativos y se elige el modelo con menor MAE.

n
1X
M AE = (| yi − yˆi |)
n i=1
Donde yi es el valor de la variable respuesta y donde yˆi es la predicción de yi .

1.7. Métricas de error: Clasificación.


Dependiendo del problema que nos encontremos, la forma de medir la precisión de nuestro modelo de
clasificación puede ser diferente. Por ejemplo, un modelo puede tener una exactitud global alta (accuracy)
pero no ser adecuado debido a que los costes de los falsos negativos son muy altos.
Para entender las métricas que podemos utilizar en los problemas de clasificación tenemos que entender la
matriz de confusión. Para hacerlo más comprensible utilizaremos una matriz para una clasificación binaria
pero que podemos generalizar para un problema de más de dos clases.

11
Figure 2: Matriz de Confusión

En ella comparamos los valores predichos con los valores reales. En la diagonal de izquierda a derecha de
dicha matriz tenemos las predicciones correctas, tanto las clasificadas positivas como las negativas. A partir
de ella podemos definir las siguientes métricas:

• Accuracy: se puede definir como la exactitud del modelo, es decir, el número de predicciones que el
modelo realizó correctamente. El objetivo es maximizar el Accuracy.

VP +VN
Accuracy =
TP
Donde V P son los verdaderos positivos y V N son los verdaderos negativos.

• Precision: La precisión indica cuantas identificaciones positivas fueron correctas, es decir, los verdaderos
positivos entre el total de las predicciones positivas. Nos indica los positivos reales. Se utiliza cuando
determinar un falso positivo tiene un gran coste (indicar como correo no deseado un correo electrónico
que no lo es con la consecuente pérdida de dicho correo).

VP
P recision =
V P + FP
Donde V P son los verdaderos positivos y F P son los falsos positivos.

• Recall (sensibilidad): Nos indica los verdaderos positivos entre los verdaderos positivos y falsos nega-
tivos. El Recall es importante en aquellos casos que tienen un alto coste asociado a falsos negativos
(indicar que una transacción fraudulenta no lo es).

VP
Recall =
V P + FN
Donde V P son los verdaderos positivos y F N son los falsos negativos.

• Especificidad: Nos indica los verdaderos negativos entre los verdaderos negativos y los falsos positivos.
VN
Especif icidad =
V N + FP
Donde V N son los verdaderos negativos y F P son los falsos positivos.

12
• ROC: La curva ROC (Receiver Operating Characteristic) es una representación gráfica de la sensi-
bilidad frente a la especificidad para un sistema clasificador binario según se amplía el umbral de
discriminación. Otra forma de definir la curva ROC es la representación gráfica del ratio de verdaderos
positivos frente al ratio de falsos positivos según el umbral de discriminación o umbral desde el cuál
consideramos el caso como positivo. La interpretación de la curva ROC, sitúa el mejor método de
predicción en la esquina superior izquierda o coordenada (0,1) del espacio ROC, representado por un
100% de sensibilidad o ningún caso negativo) y un 100% de especificidad o ningún caso de falso pos-
itivo, que se denominaría clasificación perfecta. Una clasificación aleatoria daría un punto a lo largo
de la diagonal o línea de no discriminación que iría desde el punto (0,0) hasta el punto (1,1) pasando
por el punto (0.5,0.5). En general podemos decir que aquéllos modelos de predicción que están por
encima de la línea discriminante son mejores cuanto más separados están de la línea. Los modelos que
coinciden con la línea discriminante podemos clasificarlos como aleatorios y los que están por debajo
de la línea discriminante son peores o tienen algún error en la variable explicativa.

Figure 3: Esquema Curva ROC

• AUC: La elección de la mejor curva ROC se hace mediante la comparación del espacio bajo la curva
denominado AUC y que está comprendida entre 0,5 y 1, donde 1 representa la predicción perfecta o
que existe una probabilidad del 100% de que la predicción sea correcta y 0.5 indica una predicción
aleatoria. Si obtenemos valores inferiores a 0.5 tendremos que revisar la definición del problema ya que

13
puede haber un problema de concepto. Por lo tanto, siempre elegimos aquella curva que tenga mayor
AUC con respecto a otras.

1.8. Parámetros función: rpart().

Una vez que tenemos instalado los paquetes que vamos a utilizar vamos a ver los parámetros que podemos
utilizar con la función rpart()

• Formula: Variable dependiente en función de las variables respuesta, separadas por ” ∼ ”. Si después
de ” ∼ ” ponemos un “.” indica que utilizaremos todas las variables del dataset excepto la variable
dependiente.
• data: Conjunto de datos con los que entrenaremos el modelo.
• na.action: Elimina por defecto aquellas observaciones que les falta la variable dependiente y mantiene
aquellas que faltan 1 o más variables independientes.
• method: Puede ser “anova” para la regresión, “class” para clasificación o “poison” para conteo. Aunque
no se indique la función identifica la variable dependiente.

Además, podemos indicar el parámetro control = rpart.control() donde se pueden definir sus propios parámet-
ros:

• minsplit: Mínimo de observaciones de cada nodo para ser dividido. Por defecto es 20 observaciones.
• minbucket: Mínimo de observaciones en un nodo terminal. Por defecto es minsplit/3.
• cp: Parámetro de complejidad. Por defecto es 0.01.
• maxdepth: Máxima profundidad del árbol. Por defecto es de 30.
• xval: Número de validaciones cruzadas. Por defecto es 10.
• maxsurrogate: Número de divisiones alternativas que nos dará el árbol. Por defecto es 5. Si lo
reducimos el tiempo de computación será menor.

14
2. Árbol de decisión: Regresión.
Nos encontramos ante un problema de regresión cuando la variable dependiente es de forma cuantitativa,
mientras que las variables independientes o predictores pueden ser tanto cualitativas como cuantitativas. El
algoritmo se diferencia con respecto a los árboles de clasificación en que utiliza el Residual Sum of Squares
para realizar las divisiones óptimas.

2.1. Dataset: Iris.

Empezamos por realizar un primer ejemplo de árbol de decisión donde la variable a predecir es una variable
cuantitativa (Petal_Width), en función de tres variables numéricas y una variable categórica. Para ello
utilizamos el dataset de iris, que recopila 150 observaciones de tres tipos de flores relacionadas (Setosa,
Virginica y Versicolor) y que es uno de los dataset más utilizados para iniciarse en el machine learning3 .
Lo primero que vamos a hacer es cargar los datos, ver el tamaño de nuestros dataset, los estadísticos básicos
y los cinco primeras observaciones.

#Cargamos las librerías


library(rpart)
library(rpart.plot)

## Warning: package 'rpart.plot' was built under R version 3.6.3

#Cargamos los datos.


data("iris")
#Número de observaciones y variables que tenemos.
dim(iris)

## [1] 150 5

#Resumen de los principales estadísticos.


summary(iris)

## Sepal.Length Sepal.Width Petal.Length Petal.Width


## Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100
## 1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300
## Median :5.800 Median :3.000 Median :4.350 Median :1.300
## Mean :5.843 Mean :3.057 Mean :3.758 Mean :1.199
## 3rd Qu.:6.400 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800
## Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500
## Species
## setosa :50
## versicolor:50
## virginica :50
##
##
##

3 Si quieres más información sobre el dataset iris puede ir a su página web en Wikipedia Iris

15
#Cinco primeras observaciones del dataset.
head(iris,5)

## Sepal.Length Sepal.Width Petal.Length Petal.Width Species


## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa

Para poder evaluar nuestro modelo dividimos nuestro dataframe en un conjunto de datos de entrenamiento
(train) donde estarán el 80% de las observaciones y un conjunto de datos de validación (test) donde estará el
restante 20% de las observaciones. Cuando nos enfrentamos a un nuevo problema este paso es fundamental,
ya que el algoritmo aprenderá con una parte de los datos y lo evaluaremos con datos que no ha visto antes.

index <- sample(1:nrow(iris), size = 0.8 * nrow(iris))


train <- iris[index,]
test <- iris[-index,]

Predecimos la variable Petal.Width en función de las otras 4 variables, para ello utilizamos la función
rpart() con un cp= 0.054 y la representamos con la función rpart.plot(). Utilizamos nuestro conjunto de
entrenamiento para que aprenda nuestro modelo.

#Creamos el modelo.
model <- rpart(Petal.Width ~ Sepal.Width + Petal.Length + Sepal.Length + Species,
data = train,
control = rpart.control(cp=0.05))

#Podríamos crear el mismo modelo con la siguiente función:


#model <- rpart(Petal.Width ~ ., data = train, control = rpart.control(cp=0.05) )

#Representamos gráficamente el modelo.


rpart.plot(model, extra=1)
4 Factor de complejidad del árbol: método de penalización en función del número de nodos terminales. Rango entre 0 y 1

dependiendo del número de nodos terminales del modelo.

16
1.2
n=120
yes Petal.Length < 2.5 no

1.7
n=80
Species = versicolor

0.24 1.3 2
n=40 n=40 n=40

Los árboles de decisión están formados por nodos internos y nodos terminales. Su interpretación, como
ya hemos comentado, se realiza de arriba hacia abajo. En nuestro ejemplo la variable más importante
es Petal.Length que para valores menores a 2.5 nos indica que tienen un valor de 0.24 de Petal.Width y
clasifica en ese nodo terminal 40 observaciones. Para valores superiores a 2.5 pasamos al siguiente nodo
interno. La segunda variable más importante es Species. Cuando la variable Species se corresponde con
la categoría Versicolor nos indica que el valor es 1.3, clasificando en ese nodo terminal 40 observaciones y
cuando es distinta a Versicolor el valor es 2, clasificando las 40 restantes observaciones. Los valores predichos
corresponden a la media de la variable ancho de pétalos de las observaciones que se clasifican en dicho nodo.
model

## n= 120
##
## node), split, n, deviance, yval
## * denotes terminal node
##
## 1) root 120 69.73867 1.196667
## 2) Petal.Length< 2.45 40 0.43100 0.235000 *
## 3) Petal.Length>=2.45 80 13.81950 1.677500
## 6) Species=versicolor 40 1.50400 1.330000 *
## 7) Species=virginica 40 2.65500 2.025000 *

Al imprimir el objeto creado model nos muestra el número de observaciones (n = 120), el número de nodos
realizados, el criterio de división de cada nodo (redondeado a dos cifras), el número de observaciones de cada
nodo, la desvianza5 , el yval6 y nos indica también si dicho nodo es terminal marcándolo con un asterisco.
5 La desvianza es la suma de los cuadrados de los errores.
6 El yval es el valor promedio de las observaciones de dicho nodo.

17
Ahora predecimos con nuestro modelo con la función predict() sobre el conjunto de datos de validación.
Como es el primer ejemplo, con pocas observaciones y variables independientes, no vamos a podar el árbol7 .
Utilizaremos para evaluar el modelo creado el error cuadrático medio, que calcula el promedio de los errores
elevados al cuadrado.

predic <- predict(model, newdata = test)


(error_test <- mean((predic - test$Petal.Width)^2))

## [1] 0.05245

(raiz <- sqrt(error_test))

## [1] 0.2290196

Nuestro error cuadrático medio se sitúa en 0.05245, por lo que si tomamos la raíz cuadrada nos dice que
nuestras predicciones se alejan 0.2290196 centímetros del valor real.

2.2. Dataset: Boston

En este segundo ejemplo vamos a utilizar un dataset para predecir el precio promedio de las viviendas
(medv) de Boston a mediados de la década de 1970, en función de variables como: distancia a los centros de
trabajo de la ciudad de Boston, índice de acceso a autopistas radiales, ratio de alumno-profesor, promedio de
habitaciones por vivienda. . . Si queremos más información sobre el dataset podemoss escribir en la consola
“?Boston”. Los datos se encuentran dentro da librería MASS. Como la variable a predecir es numérica (valor
medio de las viviendas ocupadas divididas entre 1000$), estamos ante un problema de regresión.

#Cargamos los datos.


library(MASS)
data(Boston)
#Número de observaciones y variables que tenemos.
dim(Boston)

## [1] 506 14

#Resumen de los principales estadísticos.


summary(Boston)

## crim zn indus chas


## Min. : 0.00632 Min. : 0.00 Min. : 0.46 Min. :0.00000
## 1st Qu.: 0.08204 1st Qu.: 0.00 1st Qu.: 5.19 1st Qu.:0.00000
## Median : 0.25651 Median : 0.00 Median : 9.69 Median :0.00000
## Mean : 3.61352 Mean : 11.36 Mean :11.14 Mean :0.06917
## 3rd Qu.: 3.67708 3rd Qu.: 12.50 3rd Qu.:18.10 3rd Qu.:0.00000
## Max. :88.97620 Max. :100.00 Max. :27.74 Max. :1.00000
## nox rm age dis
## Min. :0.3850 Min. :3.561 Min. : 2.90 Min. : 1.130
## 1st Qu.:0.4490 1st Qu.:5.886 1st Qu.: 45.02 1st Qu.: 2.100
## Median :0.5380 Median :6.208 Median : 77.50 Median : 3.207
## Mean :0.5547 Mean :6.285 Mean : 68.57 Mean : 3.795
7 Más adelante veremos ejemplos de podado con diferentes técnicas, que harán el árbol más sencillo manteniendo su precisión.

18
## 3rd Qu.:0.6240 3rd Qu.:6.623 3rd Qu.: 94.08 3rd Qu.: 5.188
## Max. :0.8710 Max. :8.780 Max. :100.00 Max. :12.127
## rad tax ptratio black
## Min. : 1.000 Min. :187.0 Min. :12.60 Min. : 0.32
## 1st Qu.: 4.000 1st Qu.:279.0 1st Qu.:17.40 1st Qu.:375.38
## Median : 5.000 Median :330.0 Median :19.05 Median :391.44
## Mean : 9.549 Mean :408.2 Mean :18.46 Mean :356.67
## 3rd Qu.:24.000 3rd Qu.:666.0 3rd Qu.:20.20 3rd Qu.:396.23
## Max. :24.000 Max. :711.0 Max. :22.00 Max. :396.90
## lstat medv
## Min. : 1.73 Min. : 5.00
## 1st Qu.: 6.95 1st Qu.:17.02
## Median :11.36 Median :21.20
## Mean :12.65 Mean :22.53
## 3rd Qu.:16.95 3rd Qu.:25.00
## Max. :37.97 Max. :50.00

Dividimos nuestra muestra en: 80% de las observaciones en nuestro conjunto de entrenamiento y el 20%
restante en nuestro conjunto de validación.

index <- sample(1:nrow(Boston), size = 0.8 * nrow(Boston))


train_house <- Boston[index,]
test_house <- Boston[-index,]

Nos quedamos con un conjunto de entrenamiento con 14 variables y 404 observaciones y un conjunto de
validación de 14 variables y 102 observaciones. Realizamos el modelo dejándole crecer lo máximo posible
para luego realizar un proceso de podado (pruning), para ello reducimos el cp a 0.005, con lo que conseguimos
un árbol complejo (más ramificaciones).

#Creamos el modelo:
model_house <- rpart(medv ~ ., control = rpart.control(cp=0.005),
method = "anova",
data = train_house)
#Realizamos gráfico del modelo
rpart.plot(model_house, extra = 1)

19
22
n=404
yes rm < 6.9 no
20 37
n=347 n=57
lstat >= 14 rm < 7.4
15 23 32 45
n=145 n=202 n=35 n=22
crim >= 5.8 rm < 6.5 lstat >= 9.1 ptratio >= 16

12 17 22 28
n=59 n=86 n=158 n=44
nox >= 0.61 nox >= 0.53 lstat >= 9.7 lstat >= 4.3
24
n=76
age < 83

23
n=69
rm < 6.1

11 17 16 20 20 20 24 32 27 34 25 34 41 48
n=50 n=9 n=64 n=22 n=82 n=18 n=51 n=7 n=37 n=7 n=7 n=28 n=10 n=12

La variable más importante es rm, tiene 13 nodos intermedios o internos y 14 nodos terminales. Si queremos
más información podemos utilizar la función summary() sobre el modelo que nos indica: número de obser-
vaciones utilizadas (n= 404), la tabla del error promedio calculada por Cross-Validation, las variables más
importantes y las diferentes alternativas a cada nodo.

summary(model_house)

## Call:
## rpart(formula = medv ~ ., data = train_house, method = "anova",
## control = rpart.control(cp = 0.005))
## n= 404
##
## CP nsplit rel error xerror xstd
## 1 0.437974199 0 1.0000000 1.0049827 0.09597831
## 2 0.168948111 1 0.5620258 0.6459328 0.07002972
## 3 0.065313395 2 0.3930777 0.4426885 0.05342493
## 4 0.038623453 3 0.3277643 0.3852489 0.05012920
## 5 0.032701727 4 0.2891408 0.3473680 0.05009368
## 6 0.014150283 5 0.2564391 0.3218974 0.04846085
## 7 0.012270910 7 0.2281385 0.2944910 0.04770646
## 8 0.009693722 8 0.2158676 0.2964278 0.04822003
## 9 0.007257225 9 0.2061739 0.2989541 0.04543267
## 10 0.007099824 10 0.1989167 0.2960097 0.04464219
## 11 0.006459203 11 0.1918169 0.3018163 0.04624317
## 12 0.005641907 12 0.1853577 0.3035812 0.04700165
## 13 0.005000000 13 0.1797158 0.2993457 0.04680726

20
##
## Variable importance
## rm lstat indus nox age dis ptratio tax crim rad
## 31 19 9 8 7 7 6 6 4 2
## zn black chas
## 1 1 1
##
## Node number 1: 404 observations, complexity param=0.4379742
## mean=22.27624, MSE=80.99849
## left son=2 (347 obs) right son=3 (57 obs)
## Primary splits:
## rm < 6.92 to the left, improve=0.4379742, (0 missing)
## lstat < 9.725 to the right, improve=0.4310758, (0 missing)
## indus < 6.66 to the right, improve=0.2565728, (0 missing)
## ptratio < 18.75 to the right, improve=0.2462218, (0 missing)
## nox < 0.6695 to the right, improve=0.2208619, (0 missing)
## Surrogate splits:
## lstat < 4.83 to the right, agree=0.891, adj=0.228, (0 split)
## ptratio < 14.55 to the right, agree=0.889, adj=0.211, (0 split)
## indus < 1.605 to the right, agree=0.874, adj=0.105, (0 split)
## zn < 87.5 to the left, agree=0.864, adj=0.035, (0 split)
## crim < 0.013355 to the right, agree=0.861, adj=0.018, (0 split)
##
## Node number 2: 347 observations, complexity param=0.1689481
## mean=19.86225, MSE=39.53319
## left son=4 (145 obs) right son=5 (202 obs)
## Primary splits:
## lstat < 14.3 to the right, improve=0.4030142, (0 missing)
## nox < 0.6695 to the right, improve=0.2817928, (0 missing)
## crim < 9.280695 to the right, improve=0.2385138, (0 missing)
## ptratio < 19.9 to the right, improve=0.2240478, (0 missing)
## age < 74.45 to the right, improve=0.2156940, (0 missing)
## Surrogate splits:
## age < 84.6 to the right, agree=0.810, adj=0.545, (0 split)
## nox < 0.5765 to the right, agree=0.787, adj=0.490, (0 split)
## dis < 2.5977 to the left, agree=0.781, adj=0.476, (0 split)
## indus < 16.57 to the right, agree=0.778, adj=0.469, (0 split)
## tax < 434.5 to the right, agree=0.767, adj=0.441, (0 split)
##
## Node number 3: 57 observations, complexity param=0.0653134
## mean=36.97193, MSE=81.98904
## left son=6 (35 obs) right son=7 (22 obs)
## Primary splits:
## rm < 7.443 to the left, improve=0.4573303, (0 missing)
## lstat < 4.685 to the right, improve=0.3408563, (0 missing)
## ptratio < 18.9 to the right, improve=0.2530082, (0 missing)
## black < 395.995 to the right, improve=0.1640935, (0 missing)
## crim < 1.92198 to the right, improve=0.1120459, (0 missing)
## Surrogate splits:
## lstat < 4.505 to the right, agree=0.789, adj=0.455, (0 split)
## black < 390.365 to the right, agree=0.684, adj=0.182, (0 split)
## chas < 0.5 to the left, agree=0.667, adj=0.136, (0 split)
## indus < 1.215 to the right, agree=0.649, adj=0.091, (0 split)
## crim < 0.110415 to the left, agree=0.632, adj=0.045, (0 split)

21
##
## Node number 4: 145 observations, complexity param=0.03270173
## mean=15.15103, MSE=19.47091
## left son=8 (59 obs) right son=9 (86 obs)
## Primary splits:
## crim < 5.76921 to the right, improve=0.3790310, (0 missing)
## nox < 0.657 to the right, improve=0.3428611, (0 missing)
## tax < 567.5 to the right, improve=0.3145248, (0 missing)
## dis < 2.0037 to the left, improve=0.2937167, (0 missing)
## ptratio < 19.65 to the right, improve=0.2883391, (0 missing)
## Surrogate splits:
## rad < 16 to the right, agree=0.917, adj=0.797, (0 split)
## tax < 567.5 to the right, agree=0.897, adj=0.746, (0 split)
## nox < 0.657 to the right, agree=0.786, adj=0.475, (0 split)
## dis < 2.2085 to the left, agree=0.752, adj=0.390, (0 split)
## ptratio < 20.15 to the right, agree=0.731, adj=0.339, (0 split)
##
## Node number 5: 202 observations, complexity param=0.03862345
## mean=23.24406, MSE=26.56524
## left son=10 (158 obs) right son=11 (44 obs)
## Primary splits:
## rm < 6.543 to the left, improve=0.23552900, (0 missing)
## lstat < 9.63 to the right, improve=0.20489510, (0 missing)
## chas < 0.5 to the left, improve=0.11471150, (0 missing)
## dis < 1.6141 to the right, improve=0.10024600, (0 missing)
## tax < 222.5 to the right, improve=0.07266003, (0 missing)
## Surrogate splits:
## lstat < 5.055 to the right, agree=0.847, adj=0.295, (0 split)
## crim < 0.017895 to the right, agree=0.802, adj=0.091, (0 split)
## zn < 87.5 to the left, agree=0.792, adj=0.045, (0 split)
## chas < 0.5 to the left, agree=0.787, adj=0.023, (0 split)
## dis < 10.648 to the left, agree=0.787, adj=0.023, (0 split)
##
## Node number 6: 35 observations, complexity param=0.01227091
## mean=32.11714, MSE=43.82485
## left son=12 (7 obs) right son=13 (28 obs)
## Primary splits:
## lstat < 9.1 to the right, improve=0.2617861, (0 missing)
## ptratio < 18.9 to the right, improve=0.1968474, (0 missing)
## rad < 7.5 to the right, improve=0.1425728, (0 missing)
## black < 395.995 to the right, improve=0.1375253, (0 missing)
## indus < 6.145 to the right, improve=0.1167917, (0 missing)
## Surrogate splits:
## nox < 0.6095 to the right, agree=0.886, adj=0.429, (0 split)
## crim < 0.724605 to the right, agree=0.857, adj=0.286, (0 split)
## dis < 1.6469 to the left, agree=0.829, adj=0.143, (0 split)
## rad < 16 to the right, agree=0.829, adj=0.143, (0 split)
## tax < 534.5 to the right, agree=0.829, adj=0.143, (0 split)
##
## Node number 7: 22 observations, complexity param=0.006459203
## mean=44.69545, MSE=45.55589
## left son=14 (10 obs) right son=15 (12 obs)
## Primary splits:
## ptratio < 16.15 to the right, improve=0.21089680, (0 missing)

22
## lstat < 4.54 to the right, improve=0.10932750, (0 missing)
## rm < 7.9845 to the right, improve=0.05184093, (0 missing)
## age < 73.2 to the right, improve=0.04500194, (0 missing)
## indus < 5.085 to the right, improve=0.02837462, (0 missing)
## Surrogate splits:
## zn < 10 to the left, agree=0.818, adj=0.6, (0 split)
## rad < 6.5 to the right, agree=0.818, adj=0.6, (0 split)
## lstat < 3.84 to the right, agree=0.818, adj=0.6, (0 split)
## crim < 0.530525 to the left, agree=0.727, adj=0.4, (0 split)
## indus < 5.085 to the right, agree=0.727, adj=0.4, (0 split)
##
## Node number 8: 59 observations, complexity param=0.007257225
## mean=11.87119, MSE=13.94341
## left son=16 (50 obs) right son=17 (9 obs)
## Primary splits:
## nox < 0.6055 to the right, improve=0.28867420, (0 missing)
## lstat < 26.52 to the right, improve=0.23900950, (0 missing)
## crim < 15.718 to the right, improve=0.16241470, (0 missing)
## dis < 2.0122 to the left, improve=0.12184810, (0 missing)
## rm < 5.911 to the left, improve=0.09805877, (0 missing)
## Surrogate splits:
## rm < 6.8285 to the left, agree=0.881, adj=0.222, (0 split)
## age < 73.75 to the right, agree=0.881, adj=0.222, (0 split)
##
## Node number 9: 86 observations, complexity param=0.007099824
## mean=17.40116, MSE=10.81988
## left son=18 (64 obs) right son=19 (22 obs)
## Primary splits:
## nox < 0.531 to the right, improve=0.2496807, (0 missing)
## age < 73.3 to the right, improve=0.2298866, (0 missing)
## crim < 0.614845 to the right, improve=0.2243728, (0 missing)
## tax < 305.5 to the right, improve=0.1813448, (0 missing)
## ptratio < 19.65 to the right, improve=0.1737905, (0 missing)
## Surrogate splits:
## indus < 8.005 to the right, agree=0.884, adj=0.545, (0 split)
## dis < 5.06645 to the left, agree=0.872, adj=0.500, (0 split)
## zn < 6.25 to the left, agree=0.826, adj=0.318, (0 split)
## age < 70.95 to the right, agree=0.826, adj=0.318, (0 split)
## tax < 300 to the right, agree=0.802, adj=0.227, (0 split)
##
## Node number 10: 158 observations, complexity param=0.01415028
## mean=21.92405, MSE=19.22613
## left son=20 (82 obs) right son=21 (76 obs)
## Primary splits:
## lstat < 9.69 to the right, improve=0.12889270, (0 missing)
## rm < 6.142 to the left, improve=0.09408525, (0 missing)
## crim < 6.13403 to the left, improve=0.09069601, (0 missing)
## dis < 1.68515 to the right, improve=0.08284590, (0 missing)
## nox < 0.6275 to the left, improve=0.07201507, (0 missing)
## Surrogate splits:
## nox < 0.519 to the right, agree=0.747, adj=0.474, (0 split)
## age < 40.2 to the right, agree=0.728, adj=0.434, (0 split)
## dis < 4.48025 to the left, agree=0.728, adj=0.434, (0 split)
## indus < 7.625 to the right, agree=0.709, adj=0.395, (0 split)

23
## crim < 0.09628 to the right, agree=0.703, adj=0.382, (0 split)
##
## Node number 11: 44 observations, complexity param=0.009693722
## mean=27.98409, MSE=24.19452
## left son=22 (37 obs) right son=23 (7 obs)
## Primary splits:
## lstat < 4.27 to the right, improve=0.29797460, (0 missing)
## tax < 269 to the right, improve=0.12208990, (0 missing)
## rm < 6.676 to the left, improve=0.07536219, (0 missing)
## age < 43.1 to the right, improve=0.06332321, (0 missing)
## ptratio < 15.25 to the right, improve=0.05176979, (0 missing)
##
## Node number 12: 7 observations
## mean=25.34286, MSE=69.17102
##
## Node number 13: 28 observations
## mean=33.81071, MSE=23.14739
##
## Node number 14: 10 observations
## mean=41.3, MSE=67.806
##
## Node number 15: 12 observations
## mean=47.525, MSE=9.400208
##
## Node number 16: 50 observations
## mean=11.02, MSE=7.936
##
## Node number 17: 9 observations
## mean=16.6, MSE=20.93111
##
## Node number 18: 64 observations
## mean=16.4375, MSE=7.792344
##
## Node number 19: 22 observations
## mean=20.20455, MSE=9.066798
##
## Node number 20: 82 observations
## mean=20.40854, MSE=6.344195
##
## Node number 21: 76 observations, complexity param=0.01415028
## mean=23.55921, MSE=27.9732
## left son=42 (69 obs) right son=43 (7 obs)
## Primary splits:
## age < 83.45 to the left, improve=0.2514387, (0 missing)
## crim < 0.92217 to the left, improve=0.2471433, (0 missing)
## dis < 2.6191 to the right, improve=0.2305092, (0 missing)
## indus < 13.935 to the left, improve=0.2289551, (0 missing)
## black < 368.405 to the right, improve=0.1969547, (0 missing)
## Surrogate splits:
## crim < 1.163695 to the left, agree=0.987, adj=0.857, (0 split)
## indus < 16.57 to the left, agree=0.987, adj=0.857, (0 split)
## nox < 0.589 to the left, agree=0.987, adj=0.857, (0 split)
## dis < 2.1517 to the right, agree=0.974, adj=0.714, (0 split)
## black < 367.195 to the right, agree=0.961, adj=0.571, (0 split)

24
##
## Node number 22: 37 observations
## mean=26.81622, MSE=11.94298
##
## Node number 23: 7 observations
## mean=34.15714, MSE=43.63673
##
## Node number 42: 69 observations, complexity param=0.005641907
## mean=22.71449, MSE=9.622689
## left son=84 (18 obs) right son=85 (51 obs)
## Primary splits:
## rm < 6.062 to the left, improve=0.27806010, (0 missing)
## nox < 0.5125 to the right, improve=0.17330470, (0 missing)
## ptratio < 19.95 to the right, improve=0.17161860, (0 missing)
## lstat < 7.57 to the right, improve=0.11134460, (0 missing)
## rad < 2.5 to the left, improve=0.06685547, (0 missing)
## Surrogate splits:
## ptratio < 19.95 to the right, agree=0.841, adj=0.389, (0 split)
## crim < 0.346115 to the right, agree=0.783, adj=0.167, (0 split)
## nox < 0.496 to the right, agree=0.783, adj=0.167, (0 split)
## dis < 9.903 to the right, agree=0.783, adj=0.167, (0 split)
##
## Node number 43: 7 observations
## mean=31.88571, MSE=132.4927
##
## Node number 84: 18 observations
## mean=19.96111, MSE=5.417932
##
## Node number 85: 51 observations
## mean=23.68627, MSE=7.486674

Nos indica que el modelo tiene 14 nodos terminales, las variables más importantes son: rm, lstat, indus y
age. Además, nos detalla cada división de los nodos indicando cuantas observaciones quedan a derecha y a
izquierda de la variable y posibles alternativas

25
2.3. Poda del árbol: Error Promedio más una desviación típica.

Una de las formas de controlar el overfitting es por medio del proceso de podado (Tree Pruning) que consiste
en dejar crecer el árbol sin apenas condiciones, para luego podar el árbol, aplicando distintos cp para obtener
el menor xerror con Cross-Validation (error obtenido con validación cruzada). En nuestro modelo hemos
reducido el cp a 0.005 para obtener un árbol profundo con muchos nodos terminales.
En nuestro ejemplo vamos a podar para reducir la complejidad del modelo. Para ello solicitamos al objeto
model_house la tabla de los errores por Cross-Validation, en función de la complejidad y el número de
divisiones.

model_house$cptable

## CP nsplit rel error xerror xstd


## 1 0.437974199 0 1.0000000 1.0049827 0.09597831
## 2 0.168948111 1 0.5620258 0.6459328 0.07002972
## 3 0.065313395 2 0.3930777 0.4426885 0.05342493
## 4 0.038623453 3 0.3277643 0.3852489 0.05012920
## 5 0.032701727 4 0.2891408 0.3473680 0.05009368
## 6 0.014150283 5 0.2564391 0.3218974 0.04846085
## 7 0.012270910 7 0.2281385 0.2944910 0.04770646
## 8 0.009693722 8 0.2158676 0.2964278 0.04822003
## 9 0.007257225 9 0.2061739 0.2989541 0.04543267
## 10 0.007099824 10 0.1989167 0.2960097 0.04464219
## 11 0.006459203 11 0.1918169 0.3018163 0.04624317
## 12 0.005641907 12 0.1853577 0.3035812 0.04700165
## 13 0.005000000 13 0.1797158 0.2993457 0.04680726

No seleccionamos el árbol en función de la tasa de error relativo que siempre disminuye según aumentamos
las divisiones o reducimos el cp. El cross-validation se utiliza para obtener una tasa de error con validación
cruzada, a partir de la cual se selecciona el árbol óptimo. La validación cruzada consiste en la creación de
X subconjuntos aleatorios de los datos originales, dejando una porción a un lado como conjunto de prueba,
construyendo un modelo para las porciones X-1 restantes, y evaluando el árbol usando la porción de prueba.
Esto se repite para todas las partes, y se evalúa una estimación del error. El modelo que arroja la tasa de
error por Cross-Validation más baja (xerror) se selecciona como el árbol que mejor se ajusta a los datos. En
nuestro modelo se consigue cuando el cp=0.005, donde se realizan 10 divisiones y el xerror se sitúa en 0.296,
como podemos ver en la tabla de la función.

model_house$cptable[10,]

## CP nsplit rel error xerror xstd


## 0.007099824 10.000000000 0.198916692 0.296009670 0.044642191

Realizamos el proceso de podado y en este caso elegimos aquel error validado por validación cruzada que se
encuentra a una desviación típica del error mínimo para reducir la complejidad de nuestro árbol.

xerror_house <- 0.2960097


xstd_house <- 0.04464219
paste("Elegimos aquel cp cuyo xerror se encuentre por debajo de: ",xerror_house+xstd_house)

## [1] "Elegimos aquel cp cuyo xerror se encuentre por debajo de: 0.34065189"

26
En nuestro ejemplo es:

model_house$cptable[6,]

## CP nsplit rel error xerror xstd


## 0.01415028 5.00000000 0.25643911 0.32189745 0.04846085

Podamos nuestro árbol en función de dicho cp y lo representamos:

model_house_des<- prune(model_house, cp = 0.014150283)


rpart.plot(model_house_des, extra = 1, type = 2,digits = 2)

22
n=404
yes rm < 6.9 no

20 37
n=347 n=57
lstat >= 14 rm < 7.4

15 23
n=145 n=202
crim >= 5.8 rm < 6.5

12 17 22 28 32 45
n=59 n=86 n=158 n=44 n=35 n=22

Nuestro árbol ha pasado de tener 16 nodos terminales a solo 6 nodos. Ahora vamos a ver cómo ha variado
nuestro error.
Predecimos en nuestro conjunto de validación con los dos árboles para poder comparar el error de los dos
árboles:

#Arbol Original:
predicciones_house <- predict(model_house,
newdata = test_house,
type = "vector")
paste("El error del árbol sin podarse sitúa en: ",
sqrt(mean((predicciones_house -test_house$medv)^2)))

## [1] "El error del árbol sin podarse sitúa en: 3.74847772999809"

27
#Árbol Podado:
predicciones_house_des <- predict(model_house_des,
newdata = test_house,
type = "vector")
paste("El error del árbol podado se sitúa en: ",
sqrt(mean((predicciones_house_des-test_house$medv)^2)))

## [1] "El error del árbol podado se sitúa en: 4.56486582989976"

El error no ha mejorado pasando de un 3,74 a 4,56, pero hemos reducido el número de nodos que utiliza
nuestro árbol de una forma considerable, reduciendo su complejidad. Si quisieramos poner el modelo en
producción, sería mucho más fácil de utilizar el árbol podado que el original, siendo el error entre ambos
mínimo.
Otra manera de evaluar nuestro modelo es por medio de los residuos. Obtenemos los residuos de nuestra
predicción, comparando el valor real en el conjunto de validación con el valor que hemos predecido.

observaciones_house = test_house$medv
prediccion_house = predicciones_house_des
residuos_house = observaciones_house - prediccion_house
data.frame(Valor_Real = observaciones_house,
Prediccion = prediccion_house,
Residuos = residuos_house)[1:10,]

## Valor_Real Prediccion Residuos


## 3 34.7 32.11714 2.5828571
## 5 36.2 32.11714 4.0828571
## 25 15.6 17.40116 -1.8011628
## 36 18.9 21.92405 -3.0240506
## 39 24.7 21.92405 2.7759494
## 40 30.8 27.98409 2.8159091
## 45 21.2 21.92405 -0.7240506
## 51 19.7 21.92405 -2.2240506
## 54 23.4 21.92405 1.4759494
## 62 16.0 17.40116 -1.4011628

qqnorm(residuals(model_house_des))
qqline(residuals(model_house_des),lty = 3, col = "red")

28
Normal Q−Q Plot
30
20
Sample Quantiles

10
0
−10
−20

−3 −2 −1 0 1 2 3

Theoretical Quantiles

shapiro.test(residuals(model_house_des))

##
## Shapiro-Wilk normality test
##
## data: residuals(model_house_des)
## W = 0.85877, p-value < 0.00000000000000022

Como vemos en el gráfico y con el test de Shapiro, los residuos no siguen una distribución normal. Los
árboles de decisión no tienen que cumplir ninguna condición, pero que los residuos sigan una distribución
normal haría que nuestro modelo fuera más fiable.

29
3. Árbol de decisión: Clasificación.
Los árboles de clasificación se utilizan cuando la variable dependiente es cualitativa. Busca minimizar la
tasa de error de clasificación, asignandole a cada observación la clase más común en su región del espacio de
predictores.

3.1. Dataset: Iris.

Utilizamos el mismo dataset que el primer ejemplo de árbol de regresión, pero ahora vamos a predecir la
clase de flor (Setosa, Virginica y Versicolor) en función del largo y ancho del sépalo y pétalo.
Volvemos a cargar los datos, ver el tamaño del dataset, los estadísticos básicos y las cinco primeras observa-
ciones.

#Cargamos los datos.


data("iris")
#Número de observaciones y variables que tenemos.
dim(iris)

## [1] 150 5

#Resumen de los principales estadísticos.


summary(iris)

## Sepal.Length Sepal.Width Petal.Length Petal.Width


## Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100
## 1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300
## Median :5.800 Median :3.000 Median :4.350 Median :1.300
## Mean :5.843 Mean :3.057 Mean :3.758 Mean :1.199
## 3rd Qu.:6.400 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800
## Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500
## Species
## setosa :50
## versicolor:50
## virginica :50
##
##
##

#Seis primeras observaciones del dataset.


head(iris,5)

## Sepal.Length Sepal.Width Petal.Length Petal.Width Species


## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa

Dividimos nuestro dataframe en un conjunto de datos de entrenamiento (train) donde estarán el 80% de las
observaciones y un conjunto de datos de validación (test) donde estarán el 20% restante de las observaciones.

30
index <- sample(1:nrow(iris), size = 0.8 * nrow(iris))
train <- iris[index,]
test <- iris[-index,]

Creamos nuestro modelo con todas las variables disponibles (Petal.Length, Petal.Width, Setal.Length, Se-
tal.Width) para predecir la variable Species. Además, añadimos el parámetro control para que la división
mejore el ajuste por lo menos un 0.001. Indicamos el que es un problema de clasificación con method=“class”
(opcional).

#Creamos el modelo.
model_iris_class <- rpart(Species ~ ., data = train,
control = rpart.control(cp=0.001),
method="class" )
#Representamos gráficamente el modelo.
rpart.plot(model_iris_class, extra=1)

setosa
versicolor versicolor
40 42 38 virginica
yes Petal.Length < 2.5 no

versicolor
0 42 38
Petal.Length < 4.8

setosa versicolor virginica


40 0 0 0 38 1 0 4 37

En el gráfico podemos ver que la variable más importante es Petal.Length, que cuando es menor de 2.5
centímetros clasifica 40 observaciones como Setosa. Cuando la variable es mayor de 2.5, nos realiza otra
división en el punto 4.8 de la variable Petal.Length. Si Petal.Length es mayor de 2.5 y menor de 4.8 nos
clasifica 38 como Versicolor y 1 Virginica y si es mayor o igual a 4.8 nos clasifica 37 casos Virginica y 4 como
Versicolor.

model_iris_class

## n= 120

31
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 120 78 versicolor (0.33333333 0.35000000 0.31666667)
## 2) Petal.Length< 2.45 40 0 setosa (1.00000000 0.00000000 0.00000000) *
## 3) Petal.Length>=2.45 80 38 versicolor (0.00000000 0.52500000 0.47500000)
## 6) Petal.Length< 4.75 39 1 versicolor (0.00000000 0.97435897 0.02564103) *
## 7) Petal.Length>=4.75 41 4 virginica (0.00000000 0.09756098 0.90243902) *

Al imprimir en objeto creado model nos muestra el número de observaciones (n=120), el criterio de división
de cada nodo (redondeado a dos cifras), el número de observaciones de cada rama, la desvianza, la predicción
promedio de esa rama y si el nodo es terminal marcándolo con un asterisco.
Predecimos con nuestro modelo por medio de la función predict() sobre el conjunto de datos de validación. La
diferencia con los problemas de regresión es que a la función predict() debemos indicarle que es un problema
de clasificación con el argumento type=“class”.

#Predicciones sobre los datos de validación


predic <- predict(model_iris_class,
newdata = test,
type = "class")
#Tasa de error y acierto.
table(predic,test$Species)

##
## predic setosa versicolor virginica
## setosa 10 0 0
## versicolor 0 6 0
## virginica 0 2 12

La diagonal nos indica los aciertos del modelo: 10 Setosas, 6 Versicolor y 12 Virginica, es decir de 30
observaciones nuevas ha clasificado bien 28, un 93,3% y ha errado en 2 un 6,6%.

3.2. Dataset: Churn.

En nuestro tercer ejemplo de árboles de decisión de clasificación, utilizaremos un dataset sobre el abandono
de clientes en una empresa de telecomunicaciones, en el que la columna churn nos indica si un cliente va
a abandonar o no la compañía. Tenemos un dataset de entrenamiento con 3.333 clientes y un dataset de
validación con 1.667 clientes. Cada uno de ellos cuenta con 19 variables independientes como son: estado,
longitud de cuenta, plan internacional, etc.

#Cargamos los datos.


churnTrain <- read.csv("Datasets/churnTrain.csv")
churnTest <- read.csv("Datasets/churnTest.csv")
#Número de observaciones y variables que tenemos.
dim(churnTrain); dim(churnTest)

## [1] 3333 20

## [1] 1667 20

32
#Resumen de los principales estadísticos.
summary(churnTrain)

## state account_length area_code international_plan


## WV : 106 Min. : 1.0 area_code_408: 838 no :3010
## MN : 84 1st Qu.: 74.0 area_code_415:1655 yes: 323
## NY : 83 Median :101.0 area_code_510: 840
## AL : 80 Mean :101.1
## OH : 78 3rd Qu.:127.0
## OR : 78 Max. :243.0
## (Other):2824
## voice_mail_plan number_vmail_messages total_day_minutes total_day_calls
## no :2411 Min. : 0.000 Min. : 0.0 Min. : 0.0
## yes: 922 1st Qu.: 0.000 1st Qu.:143.7 1st Qu.: 87.0
## Median : 0.000 Median :179.4 Median :101.0
## Mean : 8.099 Mean :179.8 Mean :100.4
## 3rd Qu.:20.000 3rd Qu.:216.4 3rd Qu.:114.0
## Max. :51.000 Max. :350.8 Max. :165.0
##
## total_day_charge total_eve_minutes total_eve_calls total_eve_charge
## Min. : 0.00 Min. : 0.0 Min. : 0.0 Min. : 0.00
## 1st Qu.:24.43 1st Qu.:166.6 1st Qu.: 87.0 1st Qu.:14.16
## Median :30.50 Median :201.4 Median :100.0 Median :17.12
## Mean :30.56 Mean :201.0 Mean :100.1 Mean :17.08
## 3rd Qu.:36.79 3rd Qu.:235.3 3rd Qu.:114.0 3rd Qu.:20.00
## Max. :59.64 Max. :363.7 Max. :170.0 Max. :30.91
##
## total_night_minutes total_night_calls total_night_charge total_intl_minutes
## Min. : 23.2 Min. : 33.0 Min. : 1.040 Min. : 0.00
## 1st Qu.:167.0 1st Qu.: 87.0 1st Qu.: 7.520 1st Qu.: 8.50
## Median :201.2 Median :100.0 Median : 9.050 Median :10.30
## Mean :200.9 Mean :100.1 Mean : 9.039 Mean :10.24
## 3rd Qu.:235.3 3rd Qu.:113.0 3rd Qu.:10.590 3rd Qu.:12.10
## Max. :395.0 Max. :175.0 Max. :17.770 Max. :20.00
##
## total_intl_calls total_intl_charge number_customer_service_calls churn
## Min. : 0.000 Min. :0.000 Min. :0.000 no :2850
## 1st Qu.: 3.000 1st Qu.:2.300 1st Qu.:1.000 yes: 483
## Median : 4.000 Median :2.780 Median :1.000
## Mean : 4.479 Mean :2.765 Mean :1.563
## 3rd Qu.: 6.000 3rd Qu.:3.270 3rd Qu.:2.000
## Max. :20.000 Max. :5.400 Max. :9.000
##

#Seis primeras observaciones del dataset.


head(churnTrain)

## state account_length area_code international_plan voice_mail_plan


## 1 KS 128 area_code_415 no yes
## 2 OH 107 area_code_415 no yes
## 3 NJ 137 area_code_415 no no
## 4 OH 84 area_code_408 yes no
## 5 OK 75 area_code_415 yes no

33
## 6 AL 118 area_code_510 yes no
## number_vmail_messages total_day_minutes total_day_calls total_day_charge
## 1 25 265.1 110 45.07
## 2 26 161.6 123 27.47
## 3 0 243.4 114 41.38
## 4 0 299.4 71 50.90
## 5 0 166.7 113 28.34
## 6 0 223.4 98 37.98
## total_eve_minutes total_eve_calls total_eve_charge total_night_minutes
## 1 197.4 99 16.78 244.7
## 2 195.5 103 16.62 254.4
## 3 121.2 110 10.30 162.6
## 4 61.9 88 5.26 196.9
## 5 148.3 122 12.61 186.9
## 6 220.6 101 18.75 203.9
## total_night_calls total_night_charge total_intl_minutes total_intl_calls
## 1 91 11.01 10.0 3
## 2 103 11.45 13.7 3
## 3 104 7.32 12.2 5
## 4 89 8.86 6.6 7
## 5 121 8.41 10.1 3
## 6 118 9.18 6.3 6
## total_intl_charge number_customer_service_calls churn
## 1 2.70 1 no
## 2 3.70 1 no
## 3 3.29 0 no
## 4 1.78 2 no
## 5 2.73 3 no
## 6 1.70 0 no

En este caso al cargar el dataset ya está dividido en el conjunto entrenamiento y validación, por lo que
pasamos directamente a realizar el modelo en función de todas las variables. Seleccionamos con el parámetro
control, que para crear una nueva división de un nodo mínimo tiene que tener 150 observaciones y que el
ajuste mejore un 0.01 el modelo.

model_churn <- rpart(churn ~ .,


data = churnTrain,
method = "class",
control = rpart.control(minsplit = 150,
cp = 0.01))
rpart.plot(model_churn, extra = 1, type = 2,digits = 2)

34
no
2850 483
yes total_day_minutes < 264 no

no yes
2766 356 84 127
number_customer_service_calls < 4 voice_mail_plan = yes

no yes yes
2642 229 124 127 37 121
international_plan = no total_day_minutes >= 160 total_eve_minutes < 188

no no
2476 128 166 101
total_day_minutes < 223 total_intl_calls >= 3

no no
315 68 166 50
total_eve_minutes < 260 total_intl_minutes < 13

no no yes no yes yes no yes no no yes


2161 60 298 34 17 34 158 7 8 43 0 51 111 38 13 89 47 6 32 25 5 96

model_churn

## n= 3333
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 3333 483 no (0.85508551 0.14491449)
## 2) total_day_minutes< 264.45 3122 356 no (0.88597053 0.11402947)
## 4) number_customer_service_calls< 3.5 2871 229 no (0.92023685 0.07976315)
## 8) international_plan=no 2604 128 no (0.95084485 0.04915515)
## 16) total_day_minutes< 223.25 2221 60 no (0.97298514 0.02701486) *
## 17) total_day_minutes>=223.25 383 68 no (0.82245431 0.17754569)
## 34) total_eve_minutes< 259.8 332 34 no (0.89759036 0.10240964) *
## 35) total_eve_minutes>=259.8 51 17 yes (0.33333333 0.66666667) *
## 9) international_plan=yes 267 101 no (0.62172285 0.37827715)
## 18) total_intl_calls>=2.5 216 50 no (0.76851852 0.23148148)
## 36) total_intl_minutes< 12.75 165 7 no (0.95757576 0.04242424) *
## 37) total_intl_minutes>=12.75 51 8 yes (0.15686275 0.84313725) *
## 19) total_intl_calls< 2.5 51 0 yes (0.00000000 1.00000000) *
## 5) number_customer_service_calls>=3.5 251 124 yes (0.49402390 0.50597610)
## 10) total_day_minutes>=160.2 149 38 no (0.74496644 0.25503356) *
## 11) total_day_minutes< 160.2 102 13 yes (0.12745098 0.87254902) *
## 3) total_day_minutes>=264.45 211 84 yes (0.39810427 0.60189573)
## 6) voice_mail_plan=yes 53 6 no (0.88679245 0.11320755) *
## 7) voice_mail_plan=no 158 37 yes (0.23417722 0.76582278)

35
## 14) total_eve_minutes< 187.75 57 25 no (0.56140351 0.43859649) *
## 15) total_eve_minutes>=187.75 101 5 yes (0.04950495 0.95049505) *

Empezamos leyendo el árbol por el nodo raíz donde la variable total_day_minutes es la más importante
dividiendo si el cliente habla más o menos de 264 minutos. Si sólo tuviéramos esta variable en cuenta
podríamos decir que, de los 3.333 clientes, aquellos que hablan igual o más de 264 minutos son propensos
al abandono (483 clientes) y los que consumen menos de 264 minutos no van abandonar la compañía (2.850
clientes). El árbol se subdivide por el lado de la izquierda con la variable voice_mail_plan, indicando que si
no tiene plan de voz y además consume más o igual de 264 minutos abandonaran la compañía 127 clientes
y no abandonaran 84. Si añadimos una división más con la variable total_eve_minutes, nos indica que si es
superior a 188 minutos abandonaran 121 clientes la compañía y no abandonaran 37 clientes, llegando a un
nodo terminal u hoja. Así podríamos ir interpretando el árbol con todos los nodos intermedios hasta llegar
a los nodos terminales.
Evaluamos el modelo que hemos hecho para ver que tal predice con los datos de validación, para ello
utilizamos la función predict() y le indicamos el argumento type=“class” para indicar que estamos antes un
problema de clasificación:

predicciones_churn <- predict(model_churn, newdata = churnTest, type = "class")


table(predicciones_churn,churnTest$churn)

##
## predicciones_churn no yes
## no 1431 92
## yes 12 132

Vemos que de 1.667 casos ha predicho correctamente 1.563 clientes (132 + 1431), lo que nos da una tasa
de acierto de 93,76% y una tasa de error de 6,23% (92 + 12 casos). En este caso vamos a evaluar nuestro
modelo por medio de la función confusionMatrix() del paquete caret que nos da la matriz de confusión y
además, nos calcula distintas métricas como la tasa de acierto (Accuracy).

#install.packages("caret")
#install.packages("e1071")
library(caret)
confusionMatrix(predicciones_churn, churnTest$churn)

## Confusion Matrix and Statistics


##
## Reference
## Prediction no yes
## no 1431 92
## yes 12 132
##
## Accuracy : 0.9376
## 95% CI : (0.9249, 0.9487)
## No Information Rate : 0.8656
## P-Value [Acc > NIR] : < 0.00000000000000022
##
## Kappa : 0.6842
##
## Mcnemar's Test P-Value : 0.00000000000000944
##
## Sensitivity : 0.9917

36
## Specificity : 0.5893
## Pos Pred Value : 0.9396
## Neg Pred Value : 0.9167
## Prevalence : 0.8656
## Detection Rate : 0.8584
## Detection Prevalence : 0.9136
## Balanced Accuracy : 0.7905
##
## 'Positive' Class : no
##

Ahora vamos a crear un modelo que pueda crecer sin limitaciones con el parámetro maxdepth, con el que
podemos indicar la profundidad máxima que puede tener el árbol (Máximo 30. Valor por defecto es 5),
indicamos una complejidad del 0.0001 y no ponemos límite en el número de observaciones para dividir un
nodo (minsplit = 0).

model_churn_max <- rpart(churn ~.,


data = churnTrain,
method = "class",
control = rpart.control(maxdepth = 30, cp = 0.0001,minsplit = 0))
rpart.plot(model_churn_max, extra = 1, type = 2,digits = 2)

## Warning: labs do not fit even at cex 0.15, there may be some overplotting

no
2850 483
yes total_day_minutes < 264 no

no yes
2766 356 84 127
number_customer_service_calls < 4 voice_mail_plan = yes

no yes no yes
2642 229 124 127 47 6 37 121
international_plan = no total_day_minutes >= 160
state = AK,AR,AZ,CA,CO,CT,DC,FL,IA,ID,IN,KY,LA,MD,MI,MN,MO,MS,MT,NC,NY,OH,OK,PA,SD,TN,TX,VA,VT,WV
total_eve_minutes < 188
no no no yes yes no yes
2476 128 166 101 111 38 13 89 3 6 32 25 5 96
total_day_minutes < 223 international_plan = no
state = AL,AZ,CA,CO,DC,DE,FL,GA,HI,ID,IN,LA,MA,MD,NC,ND,NE,NM,OK,RI,SC,VA,VT,WI,WV,WY
total_intl_calls >= 3 state = CO,HI,IA,IL,OH,OR,VA,VT,WI,WY total_night_minutes < 127
state = AK,AR,AZ,CO,CT,FL,HI,IL,IN,KS,LA,MD,ND,NM,NV,NY,OH,UT,WA,WI,WV
no no no no no no no no yes no yes
2161 60 315 68 166 50 78 8 33 30 13 9 3 1 31 8 1 17 4 3 1 93
state = AK,AL,AR,AZ,CT,DE,FL,GA,HI,IA,IL,KS,KY,MA,MD,MI,MN,MO,ND,NH,NV,NY,OK,OR,TN,VT,WA,WI,WV,WY total_eve_minutes < 260 total_intl_minutes < 13
total_eve_minutes
total_eve_minutes
>= 135 >= 200 number_vmail_messages
total_eve_minutes >= 227 total_day_minutes
< 30 total_day_minutes
< 311
state = MS state<=277
ID
no no no yes no no no no yes no yes no yes no
1309 13 852 47 298 34 17 34 166 7 75 5 3 3 23 6 10 24 10 1 3 8 31 4 1 2 1 1
account_length < 220 statenumber_vmail_messages < 47 state = AK,AL,AZ,CA,CT,DC,DE,FL,GA,HI,ID,IL,IN,KS,KY,LA,MD,MI,MN,MO,MS,NC,ND,NE,NH,NJ,NM,NV,NY,OK,OR,PA,RI,SC,SD,TN,UT,VT,WA,WI,WV,WY
voice_mail_plan = yes total_day_calls
= AK,AL,CO,DC,DE,GA,HI,IL,IN,KS,KY,LA,ME,MI,MN,MS,MT,NC,ND,NE,NH,NM,NV,NY,OK,RI,SD,TN,TX,UT,VA,VT,WA,WI,WV,WY
state = total_day_minutes
< 142 total_day_minutes
>= 209
total_day_minutes
>= 190 =>=
AK,AR,CT,IA,KS,KY,ME,MI,MO,NJ,NY,OH,TN,TX
state 121
CO,IL account_length
account_length
< 168 account_length
>= 86 >= 94

no no no no no yes no no no yes no yes no no yes


1308 12 1 1 852 46 231 9 67 25 6 34 21 7 75 4 22 1 1 5 8 5 2 19 3 1 30 2 1 2
state = AK,AL,AR,CT,DE,FL,HI,IA,IL,KS,KY,MA,MI,NV,OK,OR,TN,WV
state = WY statetotal_eve_minutes < 344 total_day_minutes
= AK,AL,CO,DC,DE,GA,HI,IL,KY,LA,ME,MS,MT,NC,NE,NH,NM,NV,OK,RI,SD,TN,TX,UT,VA,VT,WA < 256
state total_day_minutestotal_eve_minutes
= DE,HI,KS,OR,PA < 239 account_length
total_day_calls
< 350 <=33
< 139area_code
total_eve_minutes
area_code area_code_408
>= 135
= area_code_510
total_eve_calls
state>= 75
= NV
no no no no no yes no no yes no no
535 12 850 44 2 2 81 9 65 17 2 8 6 5 20 2 1 5 75 3 30 1
total_night_calls < 126 number_vmail_messages < 42 total_day_calls < 105 account_length >= 26 total_night_minutes
total_eve_minutes <total_night_minutes
state < total_day_calls
165 account_length
< 223 >= 93 >= 165
225 = AL,AZ,CA,CO,DC,DE,FL,GA,HI,ID,IN,LA,MA,NC,ND,NE,OK,SC,VA,VT,WI,WV total_night_minutes < 283

no no no no no yes no no no yes no
489 7 46 5 843 42 7 2 80 7 1 2 55 7 10 10 6 2 1 2 12 3
total_night_minutes <total_night_calls
277 >= 128 state = CA,CO,ID,IN,LA,NC,NE,NM,OH,PA,RI,SC,SD,UT,VA state = IN,MS,NM,OH,SC account_length < 179
state = NY total_day_calls >= 73 total_intl_minutes
total_day_minutes < 236 state = OH total_day_minutes < 232
< 13
no no no no no no yes no no yes yes no
457 4 32 3 40 2 6 3 612 23 231 19 1 2 80 6 54 5 1 2 3 10 12 1
total_day_minutesstate
>= 90= GA,MN,MO,ND,NH,VT,WA,WI,WY
state
total_eve_minutes = MN,MO,NH
< 293 total_intl_calls < 15 account_length < 74
total_night_minutes < 248 total_day_minutes < 262 total_day_minutes
total_night_minutes
>= =224
state CT < 173 account_length < 142

no no no no yes no no no no no no no
422 2 35 2 32 2 8 2 1 3 610 22 2 1 205 11 26 8 77 4 3 2 54 4
account_length < 154
total_day_minutes
total_night_minutes
<total_eve_minutes
90 account_length
>= 278 < 220>= 169 account_length < 92 state = NM total_day_minutes < 195 = area_code_415,area_code_510
area_code account_length
total_day_calls < 106
state >= 82
= AR,AZ,CA,IA,ID,MA,MD,MO,OH,OR,PA,SC
no no no no no no no no yes no no
42 2 35 1 32 1 252 3 358 19 168 5 37 6 24 4 2 4 29 4 13 4
stateaccount_length >= 155
= AZ,GA,MD,MN,MO,ND,NH,NY,VT,WI,WY
total_eve_calls < 131 number_vmail_messages < 40 account_length >= 93 total_day_minutes >=total_night_calls
109 >= 74
total_day_minutes
total_intl_minutes < 15 < 162 total_day_calls >= 107 total_night_calls >= 88

no no no no no no no no no no no no no
42 1 1 1 1 1 249 2 3 1 354 17 4 2 147 2 21 3 37 4 24 2 29 3 13 2
account_lengthstate
>= 51= MD state = CA,CO,ID,IN,LA,NE,NM,OH,PA,RI,SC,SD,VA
state = AZ,GA,MD,MN,MO,ND,NH,NY,VT,WA,WI state = CO,NE,NM total_night_minutes < 354 total_day_calls
state =<ME,MS,MT,NJ,TX
total_intl_calls 6 total_eve_calls < 114 total_day_calls
< 127 >= 82 total_eve_minutes < 250 total_night_calls < 125

no no no no no no no yes no no no
2 1 36 2 353 16 1 1 17 2 3 3 36 2 1 2 3 2 29 2 13 1
account_length >= 162 total_day_minutes < 213 state = PA,SD,UT,VA account_length >= 140
total_eve_calls >= 69account_length
total_night_calls < 106
total_eve_minutes < 243< 100
total_intl_minutes >= 11 state = IN,KS,MI,ND,WI,WV,WY total_day_minutes >= 228
no no no no no no
36 1 252 16 17 1 5 2 5 2 1 1
total_intl_minutes < 14 total_day_minutes < 222 account_length >=total_day_calls
45 >= 104 total_day_calls >= 110 state = NJ
no no no no yes
2 1 251 15 1 1 1 1 1 2
account_length >= 66 total_intl_calls < 4 state = SC account_length < 18 account_length >= 143

no no
111 2 140 13
total_eve_minutes
total_intl_minutes
>= 83 < 8.9
no no
111 1 92 13
number_vmail_messages < 33 = area_code_510
area_code
no no
9 1 68 13
account_length < 192
area_code = area_code_415,area_code_510

no
68 12
total_eve_minutes < 169
no
47 12
total_eve_minutes >= 227
no no
24 1 23 11
state =<CO,ID,NC,NM,RI
total_eve_calls 143
no no
13 2 10 9
total_day_calls < 139
number_customer_service_calls >= 2
no yes
13 1 5 9
total_eve_minutes
total_eve_minutes
>= 170 >= 205
no yes
4 2 1 7
state = NE,OH
state = LA
no
1 1
account_length < 137

no no no noyesyesno noyesyesno noyesyesyesno noyesno noyesnoyesno no noyesyesnoyesno no noyesyesno no no noyesnoyesyesno noyesnoyesyesyesnoyesnoyesnoyesnoyesno no noyesyesno noyesno noyesnoyesyesno noyesyesnoyesno noyesnoyesyesno no no no noyesyesyesnoyesyesnoyesno no noyesyesyesyesnoyesno noyesnoyesno noyesyesyesno no noyesnoyesyesyesno noyesyesyesyesnoyesnoyesnoyesnoyesnoyesnoyesnoyesyesyesno noyesyesnoyesyesnoyesyesnoyesyesnoyesnoyesyes
773
380
0400 20 0 1
0 34
1 10 0 10 31
1 10 0 10 10 32
1 80 0 25 01 0 31 0 213
1 340 20 0 1
0 13 0 101
1 102
0 90 0 1
0 48
1 24
0 21
0 24
0 00 13
1 00 1
0 15 04 0 21 0 10 60 1 0 1 0 1 4 0 2 0 130
1 160 10 0 1
0 18
1 30 0 31
3 50 0 21 0 20 21
2 30 0 20 2 0 4 6 01 0 2 0 2 0 150
1 48024
0 40 01 0 20 10 13 0 20 1 0 412 12
0 10 0 10 10 20 1 0 2 7 03 00 10
2 0 11
8 60 0 20 03 145
29 190 10 0 2
1 0 05 43
0 51
63 12
0 00 1
0 2
0 10 13 0 22
3 00 1 0 58 0 52 00 19
10 00 1
3 0 1
0 07 80
44 30 0 1
0 30
5 00 1
0 1 0 2 0 41 0 02 15
4 0 31 0 01 92

37
model_churn_max

## n= 3333
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 3333 483 no (0.855085509 0.144914491)
## 2) total_day_minutes< 264.45 3122 356 no (0.885970532 0.114029468)
## 4) number_customer_service_calls< 3.5 2871 229 no (0.920236851 0.079763149)
## 8) international_plan=no 2604 128 no (0.950844854 0.049155146)
## 16) total_day_minutes< 223.25 2221 60 no (0.972985142 0.027014858)
## 32) state=AK,AL,AR,AZ,CT,DE,FL,GA,HI,IA,IL,KS,KY,MA,MD,MI,MN,MO,ND,NH,NV,NY,OK,OR,TN,
## 64) account_length< 220 1320 12 no (0.990909091 0.009090909)
## 128) state=AK,AL,AR,CT,DE,FL,HI,IA,IL,KS,KY,MA,MI,NV,OK,OR,TN,WV 773 0 no (1.000
## 129) state=AZ,GA,MD,MN,MO,ND,NH,NY,VT,WA,WI,WY 547 12 no (0.978062157 0.021937843
## 258) total_night_calls< 125.5 496 7 no (0.985887097 0.014112903)
## 516) total_night_minutes< 277.1 461 4 no (0.991323210 0.008676790)
## 1032) total_day_minutes>=90.2 424 2 no (0.995283019 0.004716981)
## 2064) account_length< 153.5 380 0 no (1.000000000 0.000000000) *
## 2065) account_length>=153.5 44 2 no (0.954545455 0.045454545)
## 4130) account_length>=154.5 43 1 no (0.976744186 0.023255814)
## 8260) state=AZ,GA,MD,MN,MO,ND,NH,NY,VT,WA,WI 40 0 no (1.000000000 0.0
## 8261) state=WY 3 1 no (0.666666667 0.333333333)
## 16522) account_length>=161.5 2 0 no (1.000000000 0.000000000) *
## 16523) account_length< 161.5 1 0 yes (0.000000000 1.000000000) *
## 4131) account_length< 154.5 1 0 yes (0.000000000 1.000000000) *
## 1033) total_day_minutes< 90.2 37 2 no (0.945945946 0.054054054)
## 2066) total_day_minutes< 89.85 36 1 no (0.972222222 0.027777778)
## 4132) state=AZ,GA,MD,MN,MO,ND,NH,NY,VT,WI,WY 34 0 no (1.000000000 0.000
## 4133) state=WA 2 1 no (0.500000000 0.500000000)
## 8266) account_length>=50.5 1 0 no (1.000000000 0.000000000) *
## 8267) account_length< 50.5 1 0 yes (0.000000000 1.000000000) *
## 2067) total_day_minutes>=89.85 1 0 yes (0.000000000 1.000000000) *
## 517) total_night_minutes>=277.1 35 3 no (0.914285714 0.085714286)
## 1034) total_eve_minutes< 293.05 34 2 no (0.941176471 0.058823529)
## 2068) total_night_minutes>=277.55 33 1 no (0.969696970 0.030303030)
## 4136) total_eve_calls< 131 31 0 no (1.000000000 0.000000000) *
## 4137) total_eve_calls>=131 2 1 no (0.500000000 0.500000000)
## 8274) state=MD 1 0 no (1.000000000 0.000000000) *
## 8275) state=MN 1 0 yes (0.000000000 1.000000000) *
## 2069) total_night_minutes< 277.55 1 0 yes (0.000000000 1.000000000) *
## 1035) total_eve_minutes>=293.05 1 0 yes (0.000000000 1.000000000) *
## 259) total_night_calls>=125.5 51 5 no (0.901960784 0.098039216)
## 518) total_night_calls>=127.5 42 2 no (0.952380952 0.047619048)
## 1036) state=GA,MN,MO,ND,NH,VT,WA,WI,WY 32 0 no (1.000000000 0.000000000) *
## 1037) state=AZ,NY 10 2 no (0.800000000 0.200000000)
## 2074) total_eve_minutes< 219.55 8 0 no (1.000000000 0.000000000) *
## 2075) total_eve_minutes>=219.55 2 0 yes (0.000000000 1.000000000) *
## 519) total_night_calls< 127.5 9 3 no (0.666666667 0.333333333)
## 1038) state=MN,MO,NH 5 0 no (1.000000000 0.000000000) *
## 1039) state=GA,ND,WI 4 1 yes (0.250000000 0.750000000)
## 2078) account_length>=169 1 0 no (1.000000000 0.000000000) *

38
## 2079) account_length< 169 3 0 yes (0.000000000 1.000000000) *
## 65) account_length>=220 2 1 no (0.500000000 0.500000000)
## 130) state=WY 1 0 no (1.000000000 0.000000000) *
## 131) state=MI 1 0 yes (0.000000000 1.000000000) *
## 33) state=CA,CO,DC,ID,IN,LA,ME,MS,MT,NC,NE,NJ,NM,OH,PA,RI,SC,SD,TX,UT,VA 899 47 no (
## 66) number_vmail_messages< 47 898 46 no (0.948775056 0.051224944)
## 132) total_eve_minutes< 344.3 894 44 no (0.950782998 0.049217002)
## 264) number_vmail_messages< 41.5 885 42 no (0.952542373 0.047457627)
## 528) state=CA,CO,ID,IN,LA,NC,NE,NM,OH,PA,RI,SC,SD,UT,VA 635 23 no (0.96377952
## 1056) total_intl_calls< 14.5 632 22 no (0.965189873 0.034810127)
## 2112) account_length< 91.5 255 3 no (0.988235294 0.011764706)
## 4224) number_vmail_messages< 39.5 251 2 no (0.992031873 0.007968127)
## 8448) state=CA,CO,ID,IN,LA,NE,NM,OH,PA,RI,SC,SD,VA 213 0 no (1.000000
## 8449) state=NC,UT 38 2 no (0.947368421 0.052631579)
## 16898) total_day_minutes< 213.1 37 1 no (0.972972973 0.027027027)
## 33796) total_intl_minutes< 13.8 34 0 no (1.000000000 0.000000000)
## 33797) total_intl_minutes>=13.8 3 1 no (0.666666667 0.333333333)
## 67594) account_length>=65.5 2 0 no (1.000000000 0.000000000) *
## 67595) account_length< 65.5 1 0 yes (0.000000000 1.000000000) *
## 16899) total_day_minutes>=213.1 1 0 yes (0.000000000 1.000000000) *
## 4225) number_vmail_messages>=39.5 4 1 no (0.750000000 0.250000000)
## 8450) state=CO,NE,NM 3 0 no (1.000000000 0.000000000) *
## 8451) state=PA 1 0 yes (0.000000000 1.000000000) *
## 2113) account_length>=91.5 377 19 no (0.949602122 0.050397878)
## 4226) account_length>=92.5 371 17 no (0.954177898 0.045822102)
## 8452) total_night_minutes< 353.55 369 16 no (0.956639566 0.043360434)
## 16904) state=PA,SD,UT,VA 101 0 no (1.000000000 0.000000000) *
## 16905) state=CA,CO,ID,IN,LA,NC,NE,NM,OH,RI,SC 268 16 no (0.940298507
## 33810) total_day_minutes< 222.1 266 15 no (0.943609023 0.056390977)
## 67620) total_intl_calls< 3.5 113 2 no (0.982300885 0.017699115)
## 135240) total_eve_minutes>=82.55 112 1 no (0.991071429 0.008928
## 270480) number_vmail_messages< 32.5 102 0 no (1.000000000 0.0
## 270481) number_vmail_messages>=32.5 10 1 no (0.900000000 0.10
## 540962) area_code=area_code_415,area_code_510 9 0 no (1.000
## 540963) area_code=area_code_408 1 0 yes (0.000000000 1.0000
## 135241) total_eve_minutes< 82.55 1 0 yes (0.000000000 1.0000000
## 67621) total_intl_calls>=3.5 153 13 no (0.915032680 0.084967320)
## 135242) total_intl_minutes< 8.85 48 0 no (1.000000000 0.0000000
## 135243) total_intl_minutes>=8.85 105 13 no (0.876190476 0.123809
## 270486) area_code=area_code_510 24 0 no (1.000000000 0.000000
## 270487) area_code=area_code_408,area_code_415 81 13 no (0.8395
## 540974) account_length< 191.5 80 12 no (0.850000000 0.150000
## 1081948) total_eve_minutes< 168.5 21 0 no (1.000000000 0.0
## 1081949) total_eve_minutes>=168.5 59 12 no (0.796610169 0.2
## 2163898) total_eve_minutes>=227.1 25 1 no (0.960000000 0
## 4327796) total_eve_calls< 142.5 24 0 no (1.000000000 0
## 4327797) total_eve_calls>=142.5 1 0 yes (0.000000000 1
## 2163899) total_eve_minutes< 227.1 34 11 no (0.676470588 0
## 4327798) state=CO,ID,NC,NM,RI 15 2 no (0.866666667 0.1
## 8655596) total_day_calls< 139 14 1 no (0.928571429 0
## 17311192) total_eve_minutes>=169.55 13 0 no (1.0000
## 17311193) total_eve_minutes< 169.55 1 0 yes (0.0000
## 8655597) total_day_calls>=139 1 0 yes (0.000000000 1
## 4327799) state=CA,IN,LA,NE,OH,SC 19 9 no (0.526315789

39
## 8655598) number_customer_service_calls>=1.5 5 0 no (
## 8655599) number_customer_service_calls< 1.5 14 5 yes
## 17311198) total_eve_minutes>=204.65 6 2 no (0.66666
## 34622396) state=NE,OH 4 0 no (1.000000000 0.00000
## 34622397) state=IN,LA 2 0 yes (0.000000000 1.0000
## 17311199) total_eve_minutes< 204.65 8 1 yes (0.1250
## 34622398) state=LA 2 1 no (0.500000000 0.50000000
## 69244796) account_length< 136.5 1 0 no (1.00000
## 69244797) account_length>=136.5 1 0 yes (0.0000
## 34622399) state=CA,NE,OH,SC 6 0 yes (0.000000000
## 540975) account_length>=191.5 1 0 yes (0.000000000 1.000000
## 33811) total_day_minutes>=222.1 2 1 no (0.500000000 0.500000000)
## 67622) state=SC 1 0 no (1.000000000 0.000000000) *
## 67623) state=NM 1 0 yes (0.000000000 1.000000000) *
## 8453) total_night_minutes>=353.55 2 1 no (0.500000000 0.500000000)
## 16906) account_length>=140 1 0 no (1.000000000 0.000000000) *
## 16907) account_length< 140 1 0 yes (0.000000000 1.000000000) *
## 4227) account_length< 92.5 6 2 no (0.666666667 0.333333333)
## 8454) total_intl_calls< 5.5 4 0 no (1.000000000 0.000000000) *
## 8455) total_intl_calls>=5.5 2 0 yes (0.000000000 1.000000000) *
## 1057) total_intl_calls>=14.5 3 1 no (0.666666667 0.333333333)
## 2114) state=NM 2 0 no (1.000000000 0.000000000) *
## 2115) state=SD 1 0 yes (0.000000000 1.000000000) *
## 529) state=DC,ME,MS,MT,NJ,TX 250 19 no (0.924000000 0.076000000)
## 1058) total_night_minutes< 247.85 216 11 no (0.949074074 0.050925926)
## 2116) total_day_minutes< 195.4 173 5 no (0.971098266 0.028901734)
## 4232) total_day_minutes>=109.1 149 2 no (0.986577181 0.013422819)
## 8464) state=ME,MS,MT,NJ,TX 130 0 no (1.000000000 0.000000000) *
## 8465) state=DC 19 2 no (0.894736842 0.105263158)
## 16930) total_eve_calls>=69 18 1 no (0.944444444 0.055555556)
## 33860) account_length>=45 16 0 no (1.000000000 0.000000000) *
## 33861) account_length< 45 2 1 no (0.500000000 0.500000000)
## 67722) account_length< 17.5 1 0 no (1.000000000 0.000000000) *
## 67723) account_length>=17.5 1 0 yes (0.000000000 1.000000000) *
## 16931) total_eve_calls< 69 1 0 yes (0.000000000 1.000000000) *
## 4233) total_day_minutes< 109.1 24 3 no (0.875000000 0.125000000)
## 8466) total_eve_calls< 113.5 18 0 no (1.000000000 0.000000000) *
## 8467) total_eve_calls>=113.5 6 3 no (0.500000000 0.500000000)
## 16934) total_night_calls< 105.5 3 0 no (1.000000000 0.000000000) *
## 16935) total_night_calls>=105.5 3 0 yes (0.000000000 1.000000000) *
## 2117) total_day_minutes>=195.4 43 6 no (0.860465116 0.139534884)
## 4234) total_night_calls>=74 41 4 no (0.902439024 0.097560976)
## 8468) total_day_calls< 127 38 2 no (0.947368421 0.052631579)
## 16936) total_eve_minutes< 243.3 31 0 no (1.000000000 0.000000000) *
## 16937) total_eve_minutes>=243.3 7 2 no (0.714285714 0.285714286)
## 33874) total_day_calls>=104 5 0 no (1.000000000 0.000000000) *
## 33875) total_day_calls< 104 2 0 yes (0.000000000 1.000000000) *
## 8469) total_day_calls>=127 3 1 yes (0.333333333 0.666666667)
## 16938) account_length< 100 1 0 no (1.000000000 0.000000000) *
## 16939) account_length>=100 2 0 yes (0.000000000 1.000000000) *
## 4235) total_night_calls< 74 2 0 yes (0.000000000 1.000000000) *
## 1059) total_night_minutes>=247.85 34 8 no (0.764705882 0.235294118)
## 2118) area_code=area_code_415,area_code_510 28 4 no (0.857142857 0.142857
## 4236) total_intl_minutes< 15.15 26 2 no (0.923076923 0.076923077)

40
## 8472) total_day_calls>=81.5 21 0 no (1.000000000 0.000000000) *
## 8473) total_day_calls< 81.5 5 2 no (0.600000000 0.400000000)
## 16946) total_intl_minutes>=11.4 3 0 no (1.000000000 0.000000000) *
## 16947) total_intl_minutes< 11.4 2 0 yes (0.000000000 1.000000000) *
## 4237) total_intl_minutes>=15.15 2 0 yes (0.000000000 1.000000000) *
## 2119) area_code=area_code_408 6 2 yes (0.333333333 0.666666667)
## 4238) total_day_minutes< 162.3 2 0 no (1.000000000 0.000000000) *
## 4239) total_day_minutes>=162.3 4 0 yes (0.000000000 1.000000000) *
## 265) number_vmail_messages>=41.5 9 2 no (0.777777778 0.222222222)
## 530) state=IN,MS,NM,OH,SC 6 0 no (1.000000000 0.000000000) *
## 531) state=UT,VA 3 1 yes (0.333333333 0.666666667)
## 1062) account_length< 73.5 1 0 no (1.000000000 0.000000000) *
## 1063) account_length>=73.5 2 0 yes (0.000000000 1.000000000) *
## 133) total_eve_minutes>=344.3 4 2 no (0.500000000 0.500000000)
## 266) total_day_calls< 105 2 0 no (1.000000000 0.000000000) *
## 267) total_day_calls>=105 2 0 yes (0.000000000 1.000000000) *
## 67) number_vmail_messages>=47 1 0 yes (0.000000000 1.000000000) *
## 17) total_day_minutes>=223.25 383 68 no (0.822454308 0.177545692)
## 34) total_eve_minutes< 259.8 332 34 no (0.897590361 0.102409639)
## 68) state=AK,AL,CO,DC,DE,GA,HI,IL,IN,KS,KY,LA,ME,MI,MN,MS,MT,NC,ND,NE,NH,NM,NV,NY,O
## 136) state=AK,AL,CO,DC,DE,GA,HI,IL,KY,LA,ME,MS,MT,NC,NE,NH,NM,NV,OK,RI,SD,TN,TX,UT
## 137) state=IN,KS,MI,MN,ND,NY,WI,WV,WY 90 9 no (0.900000000 0.100000000)
## 274) account_length>=25.5 87 7 no (0.919540230 0.080459770)
## 548) account_length< 178.5 86 6 no (0.930232558 0.069767442)
## 1096) total_day_minutes< 261.6 81 4 no (0.950617284 0.049382716)
## 2192) total_day_calls< 105.5 48 0 no (1.000000000 0.000000000) *
## 2193) total_day_calls>=105.5 33 4 no (0.878787879 0.121212121)
## 4386) total_day_calls>=106.5 32 3 no (0.906250000 0.093750000)
## 8772) total_eve_minutes< 250.4 31 2 no (0.935483871 0.064516129)
## 17544) state=IN,KS,MI,ND,WI,WV,WY 24 0 no (1.000000000 0.000000000)
## 17545) state=MN,NY 7 2 no (0.714285714 0.285714286)
## 35090) total_day_calls>=110 4 0 no (1.000000000 0.000000000) *
## 35091) total_day_calls< 110 3 1 yes (0.333333333 0.666666667)
## 70182) account_length>=143 1 0 no (1.000000000 0.000000000) *
## 70183) account_length< 143 2 0 yes (0.000000000 1.000000000) *
## 8773) total_eve_minutes>=250.4 1 0 yes (0.000000000 1.000000000) *
## 4387) total_day_calls< 106.5 1 0 yes (0.000000000 1.000000000) *
## 1097) total_day_minutes>=261.6 5 2 no (0.600000000 0.400000000)
## 2194) account_length>=81.5 3 0 no (1.000000000 0.000000000) *
## 2195) account_length< 81.5 2 0 yes (0.000000000 1.000000000) *
## 549) account_length>=178.5 1 0 yes (0.000000000 1.000000000) *
## 275) account_length< 25.5 3 1 yes (0.333333333 0.666666667)
## 550) state=NY 1 0 no (1.000000000 0.000000000) *
## 551) state=KS,WI 2 0 yes (0.000000000 1.000000000) *
## 69) state=AR,AZ,CA,CT,FL,IA,ID,MA,MD,MO,NJ,OH,OR,PA,SC 92 25 no (0.728260870 0.271
## 138) total_day_minutes< 256.35 82 17 no (0.792682927 0.207317073)
## 276) total_eve_minutes< 225.25 62 7 no (0.887096774 0.112903226)
## 552) total_day_calls>=73 59 5 no (0.915254237 0.084745763)
## 1104) total_day_minutes>=223.75 58 4 no (0.931034483 0.068965517)
## 2208) state=AR,AZ,CA,IA,ID,MA,MD,MO,OH,OR,PA,SC 41 0 no (1.000000000 0.00
## 2209) state=CT,FL,NJ 17 4 no (0.764705882 0.235294118)
## 4418) total_night_calls>=87.5 15 2 no (0.866666667 0.133333333)
## 8836) total_night_calls< 125 14 1 no (0.928571429 0.071428571)
## 17672) total_day_minutes>=227.55 12 0 no (1.000000000 0.000000000) *

41
## 17673) total_day_minutes< 227.55 2 1 no (0.500000000 0.500000000)
## 35346) state=NJ 1 0 no (1.000000000 0.000000000) *
## 35347) state=FL 1 0 yes (0.000000000 1.000000000) *
## 8837) total_night_calls>=125 1 0 yes (0.000000000 1.000000000) *
## 4419) total_night_calls< 87.5 2 0 yes (0.000000000 1.000000000) *
## 1105) total_day_minutes< 223.75 1 0 yes (0.000000000 1.000000000) *
## 553) total_day_calls< 73 3 1 yes (0.333333333 0.666666667)
## 1106) state=CT 1 0 no (1.000000000 0.000000000) *
## 1107) state=ID,MA 2 0 yes (0.000000000 1.000000000) *
## 277) total_eve_minutes>=225.25 20 10 no (0.500000000 0.500000000)
## 554) total_day_minutes< 235.95 7 0 no (1.000000000 0.000000000) *
## 555) total_day_minutes>=235.95 13 3 yes (0.230769231 0.769230769)
## 1110) total_night_minutes< 172.5 3 0 no (1.000000000 0.000000000) *
## 1111) total_night_minutes>=172.5 10 0 yes (0.000000000 1.000000000) *
## 139) total_day_minutes>=256.35 10 2 yes (0.200000000 0.800000000)
## 278) total_night_minutes< 165.4 2 0 no (1.000000000 0.000000000) *
## 279) total_night_minutes>=165.4 8 0 yes (0.000000000 1.000000000) *
## 35) total_eve_minutes>=259.8 51 17 yes (0.333333333 0.666666667)
## 70) voice_mail_plan=yes 11 0 no (1.000000000 0.000000000) *
## 71) voice_mail_plan=no 40 6 yes (0.150000000 0.850000000)
## 142) state=DE,HI,KS,OR,PA 11 5 no (0.545454545 0.454545455)
## 284) total_night_minutes< 223.25 8 2 no (0.750000000 0.250000000)
## 568) total_intl_minutes< 13.15 6 0 no (1.000000000 0.000000000) *
## 569) total_intl_minutes>=13.15 2 0 yes (0.000000000 1.000000000) *
## 285) total_night_minutes>=223.25 3 0 yes (0.000000000 1.000000000) *
## 143) state=AR,CA,CT,IN,KY,MD,MI,MN,MS,MT,ND,NE,NJ,NM,NV,NY,OH,SC,SD,TX,VT,WA,WV 29
## 9) international_plan=yes 267 101 no (0.621722846 0.378277154)
## 18) total_intl_calls>=2.5 216 50 no (0.768518519 0.231481481)
## 36) total_intl_minutes< 13.1 173 7 no (0.959537572 0.040462428)
## 72) state=AK,AL,AZ,CA,CT,DC,DE,FL,GA,HI,ID,IL,IN,KS,KY,LA,MD,MI,MN,MO,MS,NC,ND,NE,N
## 73) state=AR,CO,MA,MT,OH,TX,VA 28 7 no (0.750000000 0.250000000)
## 146) total_day_minutes< 238.95 22 2 no (0.909090909 0.090909091)
## 292) total_day_calls>=93 19 0 no (1.000000000 0.000000000) *
## 293) total_day_calls< 93 3 1 yes (0.333333333 0.666666667)
## 586) state=OH 1 0 no (1.000000000 0.000000000) *
## 587) state=MA,TX 2 0 yes (0.000000000 1.000000000) *
## 147) total_day_minutes>=238.95 6 1 yes (0.166666667 0.833333333)
## 294) account_length>=164.5 1 0 no (1.000000000 0.000000000) *
## 295) account_length< 164.5 5 0 yes (0.000000000 1.000000000) *
## 37) total_intl_minutes>=13.1 43 0 yes (0.000000000 1.000000000) *
## 19) total_intl_calls< 2.5 51 0 yes (0.000000000 1.000000000) *
## 5) number_customer_service_calls>=3.5 251 124 yes (0.494023904 0.505976096)
## 10) total_day_minutes>=160.2 149 38 no (0.744966443 0.255033557)
## 20) state=AL,AZ,CA,CO,DC,DE,FL,GA,HI,ID,IN,LA,MA,MD,NC,ND,NE,NM,OK,RI,SC,VA,VT,WI,WV,WY
## 40) total_eve_minutes>=135.3 80 5 no (0.937500000 0.062500000)
## 80) total_day_calls< 142 79 4 no (0.949367089 0.050632911)
## 160) total_eve_minutes< 349.7 78 3 no (0.961538462 0.038461538)
## 320) state=AL,AZ,CA,CO,DC,DE,FL,GA,HI,ID,IN,LA,MA,NC,ND,NE,OK,SC,VA,VT,WI,WV 63
## 321) state=MD,RI,WY 15 3 no (0.800000000 0.200000000)
## 642) total_day_minutes< 232.25 13 1 no (0.923076923 0.076923077)
## 1284) account_length< 141.5 12 0 no (1.000000000 0.000000000) *
## 1285) account_length>=141.5 1 0 yes (0.000000000 1.000000000) *
## 643) total_day_minutes>=232.25 2 0 yes (0.000000000 1.000000000) *
## 161) total_eve_minutes>=349.7 1 0 yes (0.000000000 1.000000000) *

42
## 81) total_day_calls>=142 1 0 yes (0.000000000 1.000000000) *
## 41) total_eve_minutes< 135.3 6 3 no (0.500000000 0.500000000)
## 82) total_day_minutes>=209.25 3 0 no (1.000000000 0.000000000) *
## 83) total_day_minutes< 209.25 3 0 yes (0.000000000 1.000000000) *
## 21) state=AK,AR,CT,IA,IL,KS,KY,ME,MI,MN,MO,MS,NH,NJ,NV,NY,OH,OR,TN,TX,UT,WA 63 30 no (
## 42) total_eve_minutes>=200.25 29 6 no (0.793103448 0.206896552)
## 84) state=AK,AR,CT,IA,KS,KY,ME,MI,MO,NJ,NY,OH,TN,TX 23 1 no (0.956521739 0.043478
## 168) total_day_calls< 138.5 22 0 no (1.000000000 0.000000000) *
## 169) total_day_calls>=138.5 1 0 yes (0.000000000 1.000000000) *
## 85) state=MN,NH,OR,WA 6 1 yes (0.166666667 0.833333333)
## 170) account_length< 32.5 1 0 no (1.000000000 0.000000000) *
## 171) account_length>=32.5 5 0 yes (0.000000000 1.000000000) *
## 43) total_eve_minutes< 200.25 34 10 yes (0.294117647 0.705882353)
## 86) total_day_minutes>=190.35 13 5 no (0.615384615 0.384615385)
## 172) total_eve_minutes>=135.1 8 0 no (1.000000000 0.000000000) *
## 173) total_eve_minutes< 135.1 5 0 yes (0.000000000 1.000000000) *
## 87) total_day_minutes< 190.35 21 2 yes (0.095238095 0.904761905)
## 174) area_code=area_code_408 2 0 no (1.000000000 0.000000000) *
## 175) area_code=area_code_415,area_code_510 19 0 yes (0.000000000 1.000000000) *
## 11) total_day_minutes< 160.2 102 13 yes (0.127450980 0.872549020)
## 22) state=CO,HI,IA,IL,OH,OR,VA,VT,WI,WY 22 9 no (0.590909091 0.409090909)
## 44) total_eve_minutes>=226.55 11 1 no (0.909090909 0.090909091)
## 88) total_day_minutes>=120.5 10 0 no (1.000000000 0.000000000) *
## 89) total_day_minutes< 120.5 1 0 yes (0.000000000 1.000000000) *
## 45) total_eve_minutes< 226.55 11 3 yes (0.272727273 0.727272727)
## 90) state=CO,IL 4 1 no (0.750000000 0.250000000)
## 180) area_code=area_code_510 3 0 no (1.000000000 0.000000000) *
## 181) area_code=area_code_408 1 0 yes (0.000000000 1.000000000) *
## 91) state=HI,OR,VT,WI,WY 7 0 yes (0.000000000 1.000000000) *
## 23) state=AK,AL,AR,AZ,CA,DC,DE,FL,GA,ID,IN,KS,KY,LA,MD,ME,MI,MN,MO,MS,MT,NC,NH,NM,NV,NY
## 3) total_day_minutes>=264.45 211 84 yes (0.398104265 0.601895735)
## 6) voice_mail_plan=yes 53 6 no (0.886792453 0.113207547)
## 12) state=AK,AR,AZ,CA,CO,CT,DC,FL,IA,ID,IN,KY,LA,MD,MI,MN,MO,MS,MT,NC,NY,OH,OK,PA,SD,TN,T
## 13) state=AL,KS,ME,NH,NV,WY 9 3 yes (0.333333333 0.666666667)
## 26) international_plan=no 4 1 no (0.750000000 0.250000000)
## 52) number_vmail_messages< 30 3 0 no (1.000000000 0.000000000) *
## 53) number_vmail_messages>=30 1 0 yes (0.000000000 1.000000000) *
## 27) international_plan=yes 5 0 yes (0.000000000 1.000000000) *
## 7) voice_mail_plan=no 158 37 yes (0.234177215 0.765822785)
## 14) total_eve_minutes< 187.75 57 25 no (0.561403509 0.438596491)
## 28) state=AK,AR,AZ,CO,CT,FL,HI,IL,IN,KS,LA,MD,ND,NM,NV,NY,OH,UT,WA,WI,WV 39 8 no (0.7
## 56) total_day_minutes< 311.2 35 4 no (0.885714286 0.114285714)
## 112) account_length< 168 32 2 no (0.937500000 0.062500000)
## 224) total_eve_calls>=74.5 31 1 no (0.967741935 0.032258065)
## 448) total_night_minutes< 282.75 30 0 no (1.000000000 0.000000000) *
## 449) total_night_minutes>=282.75 1 0 yes (0.000000000 1.000000000) *
## 225) total_eve_calls< 74.5 1 0 yes (0.000000000 1.000000000) *
## 113) account_length>=168 3 1 yes (0.333333333 0.666666667)
## 226) state=NV 1 0 no (1.000000000 0.000000000) *
## 227) state=MD,UT 2 0 yes (0.000000000 1.000000000) *
## 57) total_day_minutes>=311.2 4 0 yes (0.000000000 1.000000000) *
## 29) state=DC,IA,KY,MA,MN,MS,NE,NJ,OK,PA,RI,SD,TX 18 1 yes (0.055555556 0.944444444)
## 58) state=MS 3 1 yes (0.333333333 0.666666667)
## 116) account_length>=86 1 0 no (1.000000000 0.000000000) *

43
## 117) account_length< 86 2 0 yes (0.000000000 1.000000000) *
## 59) state=DC,IA,KY,MA,MN,NE,NJ,OK,PA,RI,SD,TX 15 0 yes (0.000000000 1.000000000) *
## 15) total_eve_minutes>=187.75 101 5 yes (0.049504950 0.950495050)
## 30) total_night_minutes< 127 7 3 no (0.571428571 0.428571429)
## 60) total_day_minutes< 277.15 4 0 no (1.000000000 0.000000000) *
## 61) total_day_minutes>=277.15 3 0 yes (0.000000000 1.000000000) *
## 31) total_night_minutes>=127 94 1 yes (0.010638298 0.989361702)
## 62) state=ID 2 1 no (0.500000000 0.500000000)
## 124) account_length>=94 1 0 no (1.000000000 0.000000000) *
## 125) account_length< 94 1 0 yes (0.000000000 1.000000000) *
## 63) state=AL,AR,CA,CO,CT,DE,FL,GA,HI,IN,KS,KY,MA,MD,ME,MI,MN,MO,MS,MT,NC,NE,NH,NJ,NV,

Con el nuevo modelo hemos pasado de tener 11 hojas o nodos terminales a tener un árbol casi con una hoja
por cada observación, por lo que podríamos entender que clasifica mejor el segundo modelo que el primero,
al utilizar más divisiones. Lo comprobamos con los datos de test y comparamos la tasa de acierto y error de
cada uno de ellos.

predicciones_churn_max <- predict(model_churn_max, newdata = churnTest, type = "class")


confusionMatrix(predicciones_churn_max, churnTest$churn)

## Confusion Matrix and Statistics


##
## Reference
## Prediction no yes
## no 1371 72
## yes 72 152
##
## Accuracy : 0.9136
## 95% CI : (0.8991, 0.9267)
## No Information Rate : 0.8656
## P-Value [Acc > NIR] : 0.0000000007643
##
## Kappa : 0.6287
##
## Mcnemar's Test P-Value : 1
##
## Sensitivity : 0.9501
## Specificity : 0.6786
## Pos Pred Value : 0.9501
## Neg Pred Value : 0.6786
## Prevalence : 0.8656
## Detection Rate : 0.8224
## Detection Prevalence : 0.8656
## Balanced Accuracy : 0.8143
##
## 'Positive' Class : no
##

Nuestra tasa de acierto ha pasado de un 93.7% a 91.3% y la tasa de error ha crecido desde el 6.23% hasta
el 8.64%, empeorando la predicción en nuestro conjunto de datos de validación. Esta pérdida en la tasa
de acierto es debida a que hemos sobreajustado nuestro modelo a los datos de entrenamiento y cuando se
enfrenta a nuevos datos no es capaz de mejorar los resultados.

44
3.3. Poda de árbol: Cp óptima.

Podamos el árbol para conseguir el mínimo error, para ello utilizamos la función printcp(). Lo primero que
nos indica es el modelo que hemos creado, las variables que ha utilizado para la construcción del árbol, el
root node error8 , cp, nsplit (número de divisiones), el error relativo 9 , el xerror10 y xstd11 . Según se realizan
mayor número de divisiones en el árbol, el error relativo se reduce, pero el xerror y el xstd llega un momento
que no disminuye y empieza a crecer (sobreajuste).

model_churn$cptable

## CP nsplit rel error xerror xstd


## 1 0.08902692 0 1.0000000 1.0000000 0.04207569
## 2 0.08488613 1 0.9109731 0.9834369 0.04178430
## 3 0.07867495 2 0.8260870 0.9503106 0.04118935
## 4 0.05279503 4 0.6687371 0.7763975 0.03777026
## 5 0.01759834 7 0.4906832 0.7039337 0.03617662
## 6 0.01449275 9 0.4554865 0.7142857 0.03641121
## 7 0.01000000 10 0.4409938 0.7163561 0.03645783

El óptimo se sitúa en un cp= 0.017598 con 7 divisiones, a partir de este punto el error promedio por validación
cruzada crece.

model_churn$cptable[5,]

## CP nsplit rel error xerror xstd


## 0.01759834 7.00000000 0.49068323 0.70393375 0.03617662

printcp(model_churn)

##
## Classification tree:
## rpart(formula = churn ~ ., data = churnTrain, method = "class",
## control = rpart.control(minsplit = 150, cp = 0.01))
##
## Variables actually used in tree construction:
## [1] international_plan number_customer_service_calls
## [3] total_day_minutes total_eve_minutes
## [5] total_intl_calls total_intl_minutes
## [7] voice_mail_plan
##
## Root node error: 483/3333 = 0.14491
##
## n= 3333
##
## CP nsplit rel error xerror xstd
## 1 0.089027 0 1.00000 1.00000 0.042076
## 2 0.084886 1 0.91097 0.98344 0.041784
8 Es la tasa de error si solo tenemos en cuenta el nodo raíz (un solo nodo). Nos sirve para comparar modelos. También

podemos interpretarlo como la varianza total.


9 Residuo.
10 Es la tasa de error validada con cross validation.
11 Es la desviación estándar del error a través de los conjuntos de cross validation.

45
## 3 0.078675 2 0.82609 0.95031 0.041189
## 4 0.052795 4 0.66874 0.77640 0.037770
## 5 0.017598 7 0.49068 0.70393 0.036177
## 6 0.014493 9 0.45549 0.71429 0.036411
## 7 0.010000 10 0.44099 0.71636 0.036458

También lo podemos ver en un gráfico en función del R2

par(mfrow=c(1,2))
rsq.rpart(model_churn)

##
## Classification tree:
## rpart(formula = churn ~ ., data = churnTrain, method = "class",
## control = rpart.control(minsplit = 150, cp = 0.01))
##
## Variables actually used in tree construction:
## [1] international_plan number_customer_service_calls
## [3] total_day_minutes total_eve_minutes
## [5] total_intl_calls total_intl_minutes
## [7] voice_mail_plan
##
## Root node error: 483/3333 = 0.14491
##
## n= 3333
##
## CP nsplit rel error xerror xstd
## 1 0.089027 0 1.00000 1.00000 0.042076
## 2 0.084886 1 0.91097 0.98344 0.041784
## 3 0.078675 2 0.82609 0.95031 0.041189
## 4 0.052795 4 0.66874 0.77640 0.037770
## 5 0.017598 7 0.49068 0.70393 0.036177
## 6 0.014493 9 0.45549 0.71429 0.036411
## 7 0.010000 10 0.44099 0.71636 0.036458

## Warning in rsq.rpart(model_churn): may not be applicable for this method

46
1.0

1.1
Apparent
X Relative
0.8

1.0
X Relative Error
R−square

0.6

0.9
0.8
0.4

0.7
0.2

0.6
0.0

0 2 4 6 8 10 0 2 4 6 8 10

Number of Splits Number of Splits

Ahora que ya hemos visto donde se encuentra el mínimo error (cp=0.01759834, nsplit=7) Vamos a podar
nuestro árbol para conseguir el mínimo error, en función de este cp (xerror).

#Buscamos el mínimo del cp


(min_cp <- model_churn$cptable[which.min(model_churn$cptable[,"xerror"]),"CP"])

## [1] 0.01759834

#Creamos el modelo en función del cp mínimo.


model_churn_min_cp <- prune(model_churn, cp = min_cp)
#Una forma más fácil es:
#model_churn_min_cp <- prune(model_churn, cp = 0.01759834)
rpart.plot(model_churn_min_cp, extra = 1, type = 2,digits = 2)

47
no
2850 483
yes total_day_minutes < 264 no

no yes
2766 356 84 127
number_customer_service_calls < 4 voice_mail_plan = yes
no yes
2642 229 124 127
international_plan = no total_day_minutes >= 160
no
166 101
total_intl_calls >= 3
no
166 50
total_intl_minutes < 13

no no yes yes no yes no yes


2476 128 158 7 8 43 0 51 111 38 13 89 47 6 37 121

Como ya sabíamos por la función printcp(), tenemos 7 divisiones o nodos y 8 nodos terminales. Comprobamos
si este modelo mejora la tasa de acierto de clasificación en comparación con los anteriores modelos.

predicciones_churn_min_cp <- predict(model_churn_min_cp, newdata = churnTest, type = "class")


confusionMatrix(predicciones_churn_min_cp, churnTest$churn)

## Confusion Matrix and Statistics


##
## Reference
## Prediction no yes
## no 1413 83
## yes 30 141
##
## Accuracy : 0.9322
## 95% CI : (0.9191, 0.9438)
## No Information Rate : 0.8656
## P-Value [Acc > NIR] : < 0.00000000000000022
##
## Kappa : 0.6763
##
## Mcnemar's Test P-Value : 0.0000009994
##
## Sensitivity : 0.9792
## Specificity : 0.6295
## Pos Pred Value : 0.9445
## Neg Pred Value : 0.8246

48
## Prevalence : 0.8656
## Detection Rate : 0.8476
## Detection Prevalence : 0.8974
## Balanced Accuracy : 0.8043
##
## 'Positive' Class : no
##

Nuestra predicción con el modelo podado ha obtenido una tasa de predicción muy similar al modelo original,
pero ha reducido os nodos intermedios de 10 a 7 y los nodos terminales de 11 a 8 hojas, por lo que nos
quedaríamos con este último modelo al ser más sencillo (principio de parsimonia).

3.4. Grid Search: Selección de Hiperparámetros.

Cuando no sabemos cómo iniciar los parámetros de un modelo de árboles es utilizar la librería e1071 que
por medio de la función tune.rpart() nos permite realizar combinaciones calculando el error y la dispersión
para cada una de las posibilidades. También podemos indicarle distintos valores para los diferentes parámet-
ros (mínimo de observaciones de cada nodo, mínimo de observaciones de un nodo terminal o la máxima
profundidad del árbol) y así encontrar la combinación óptima de los parámetros.
Seguimos con nuestro ejemplo con los datos de churn, donde vamos a elegir distintos parámetros:

• Profundidad (maxdepth): Elegimos los valores 10, 20 y 30.


• Mínimo de observaciones de cada nodo (minsplit): Elegimos los valores 20, 60 y 150.
• Mínimo de observaciones de un nodo terminal (minbucket): con valores de 100, 150 y 300.

#Cargamos la librería.
library(e1071)
#Realizamos modelo con las distintas posibilidades.
model_tune_churn_rpar <- tune.rpart(churn~.,
data = churnTrain,
minsplit = c(20,60,150),
maxdepth = c(10,20,30),
minbucket = c(100,150,300))

Hay que tener cuidado con el número de opciones que se solicitan a la función tune.rpart(), ya que calcula
un modelo por cada opción y coste computacional puede ser alto. En nuestro ejemplo son 27 modelos los
que va a calcular.

model_tune_churn_rpar

##
## Parameter tuning of 'rpart.wrapper':
##
## - sampling method: 10-fold cross validation
##
## - best parameters:
## minsplit minbucket maxdepth
## 20 100 10
##
## - best performance: 0.1167053

49
Si solicitamos el objeto creado model_tune_churn nos da que el mejor modelo lo obtenemos con 20 ob-
servaciones mínimo para ser dividido un nodo, 100 observaciones mínimo en los nodos terminales y 10 de
profundidad, con un error de 0.11. Si queremos información más detallada de las distintas opciones podemos
utilizar la función summary()

summary(model_tune_churn_rpar)

##
## Parameter tuning of 'rpart.wrapper':
##
## - sampling method: 10-fold cross validation
##
## - best parameters:
## minsplit minbucket maxdepth
## 20 100 10
##
## - best performance: 0.1167053
##
## - Detailed performance results:
## minsplit minbucket maxdepth error dispersion
## 1 20 100 10 0.1167053 0.01796625
## 2 60 100 10 0.1167053 0.01796625
## 3 150 100 10 0.1167053 0.01796625
## 4 20 150 10 0.1404075 0.01598327
## 5 60 150 10 0.1404075 0.01598327
## 6 150 150 10 0.1404075 0.01598327
## 7 20 300 10 0.1449030 0.01371604
## 8 60 300 10 0.1449030 0.01371604
## 9 150 300 10 0.1449030 0.01371604
## 10 20 100 20 0.1167053 0.01796625
## 11 60 100 20 0.1167053 0.01796625
## 12 150 100 20 0.1167053 0.01796625
## 13 20 150 20 0.1404075 0.01598327
## 14 60 150 20 0.1404075 0.01598327
## 15 150 150 20 0.1404075 0.01598327
## 16 20 300 20 0.1449030 0.01371604
## 17 60 300 20 0.1449030 0.01371604
## 18 150 300 20 0.1449030 0.01371604
## 19 20 100 30 0.1167053 0.01796625
## 20 60 100 30 0.1167053 0.01796625
## 21 150 100 30 0.1167053 0.01796625
## 22 20 150 30 0.1404075 0.01598327
## 23 60 150 30 0.1404075 0.01598327
## 24 150 150 30 0.1404075 0.01598327
## 25 20 300 30 0.1449030 0.01371604
## 26 60 300 30 0.1449030 0.01371604
## 27 150 300 30 0.1449030 0.01371604

Nos indica los distintos modelos con su error y dispersión. Si solo hubiéramos solicitado las distintas combi-
naciones de dos parámetros podríamos realizar un gráfico con las distintas opciones.

#Creamos el modelo con dos parámetros minsplit y maxdepth.


model_tune_churn_rpar2 <- tune.rpart(churn~.,

50
data = churnTrain,
minsplit = c(20,60,150),
maxdepth = c(10,20,30))
#Mejores parámetros
model_tune_churn_rpar2

##
## Parameter tuning of 'rpart.wrapper':
##
## - sampling method: 10-fold cross validation
##
## - best parameters:
## minsplit maxdepth
## 20 10
##
## - best performance: 0.07111723

#Dibujamos el error
plot(model_tune_churn_rpar2)

Performance of `rpart.wrapper'

30
0.105

0.100
25
0.095
maxdepth

20 0.090

0.085

15 0.080

0.075

10 0.070
20 40 60 80 100 120 140

minsplit

Donde las zonas más oscuras son las que menos error tienen.

51
4. Paquete Caret: Validación Cruzada
Otra forma de obtener el parámetro cp es por medio del paquete caret que además nos permite realizar
validación cruzada. Vamos a desarrollar primero un modelo y luego modificaremos los parámetros para
ajustarlos a los obtenidos con la función tune.rpart(). El parámetro metric le podemos indicar que métricas
utilizar para calcular el error del modelo. Por defecto es “RMSE” y “Rsquared” para regresión, “Accuracy”
y “Kappa” para clasificación. Nosotros vamos a utilizar el Accuracy para el cálculo del error y comparación
entre los distintos modelos.

set.seed(1979)
control <- trainControl(method = "cv", #Método: Validación cruzada.
number = 10) #Número de conjuntos por validación cruzada.

model_rpart_cv <- train(churn ~ .,


method = "rpart", #Modelo: rpart
trControl = control, #Parámetros de control
metric = "Accuracy", #Métrica.
data = churnTrain)
model_rpart_cv

## CART
##
## 3333 samples
## 19 predictor
## 2 classes: 'no', 'yes'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 3000, 2999, 2999, 3000, 3000, 3000, ...
## Resampling results across tuning parameters:
##
## cp Accuracy Kappa
## 0.07867495 0.8853887 0.40316951
## 0.08488613 0.8658746 0.22099210
## 0.08902692 0.8538859 0.05704152
##
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was cp = 0.07867495.

Dividiendo nuestros datos en 10 grupos por medio de validación cruzada, nos dice que nuestra tasa de acierto
es del 88% para un cp del 0.07867495.
Predecimos con el modelo creado:

predict_rpart_cv <- predict(model_rpart_cv, churnTest)


confusionMatrix(predict_rpart_cv, churnTest$churn)

## Confusion Matrix and Statistics


##
## Reference
## Prediction no yes
## no 1419 164
## yes 24 60

52
##
## Accuracy : 0.8872
## 95% CI : (0.8711, 0.902)
## No Information Rate : 0.8656
## P-Value [Acc > NIR] : 0.00464
##
## Kappa : 0.3413
##
## Mcnemar's Test P-Value : < 0.0000000000000002
##
## Sensitivity : 0.9834
## Specificity : 0.2679
## Pos Pred Value : 0.8964
## Neg Pred Value : 0.7143
## Prevalence : 0.8656
## Detection Rate : 0.8512
## Detection Prevalence : 0.9496
## Balanced Accuracy : 0.6256
##
## 'Positive' Class : no
##

Nuestra predicción en el conjunto de datos de validación es similar al obtenido por validación cruzada.
Si queremos utilizar los hiperparámetros que hemos calculado con la función tune.rpart(), tenemos que
indicarlos manualmente con el parámetro control.

set.seed(1979)
control_train <- trainControl(method = "cv", #Método: Validación cruzada.
number = 10) #Número de conjuntos por validación cruzada.

model_rpart_cv_op <- train(churn ~ .,


method = "rpart", #Modelo: rpart
trControl = control_train, #Parámetros de control
metric = "Accuracy", #Métrica.
control = rpart.control(minsplit = 20,
minbucket = 100, maxdepth = 10),
data = churnTrain)
model_rpart_cv_op

## CART
##
## 3333 samples
## 19 predictor
## 2 classes: 'no', 'yes'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 3000, 2999, 2999, 3000, 3000, 3000, ...
## Resampling results across tuning parameters:
##
## cp Accuracy Kappa
## 0.07867495 0.8784881 0.3946049
## 0.08488613 0.8559880 0.1179807

53
## 0.08902692 0.8520841 0.0277636
##
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was cp = 0.07867495.

Nuestra tasa de acierto se ha situado en el 87% con un cp de 0.07867495.


Predecimos con el segundo modelo creado:

predict_rpart_cv_op <- predict(model_rpart_cv_op, churnTest)


confusionMatrix(predict_rpart_cv_op, churnTest$churn)

## Confusion Matrix and Statistics


##
## Reference
## Prediction no yes
## no 1398 163
## yes 45 61
##
## Accuracy : 0.8752
## 95% CI : (0.8584, 0.8907)
## No Information Rate : 0.8656
## P-Value [Acc > NIR] : 0.1323
##
## Kappa : 0.3101
##
## Mcnemar's Test P-Value : 0.0000000000000004959
##
## Sensitivity : 0.9688
## Specificity : 0.2723
## Pos Pred Value : 0.8956
## Neg Pred Value : 0.5755
## Prevalence : 0.8656
## Detection Rate : 0.8386
## Detection Prevalence : 0.9364
## Balanced Accuracy : 0.6206
##
## 'Positive' Class : no
##

Nuestra tasa de acierto se ha situado en el 87% en el conjunto de validación, similar al obtenido por validación
cruzada en el conjunto de entrenamiento.

54
Anexo 1: Categorizar variables.
Una de las utilidades que tienen los árboles de decisión es la de categorizar variables numéricas. Vamos a
utilizar el dataset de Iris con observaciones sobre el sépalo y pétalo de 3 especies. El dataset está compuesto
por 150 observaciones, 4 variables continuas y una categórica (Species).

#Cargamos librerías y datos.


library(rpart)
library(rpart.plot)
data("iris")

Visualizamos la variable Sepal.Length en función de las diferentes categorías de la variable Species.

library(sm)
sm.density.compare(iris$Sepal.Length,iris$Species,xlab ="Sepal.Length")
title(main = "Distribución Sepal.Length ~ Species")
legend("topright",levels(iris$Species),
fill = 2 +(0:nlevels(iris$Species)))

Distribución Sepal.Length ~ Species


1.0

setosa
versicolor
virginica
0.8
0.6
Density

0.4
0.2
0.0

4 5 6 7 8 9

Sepal.Length

Del estudio del gráfico podemos observar que hay distintos puntos en los que se diferencian las diferentes
especies, como pueden ser aproximadamente el 5.4, 5.7 y el 6.2.
Creamos un modelo para categorizar la variable Sepal.Length.

55
model_Sepal.Length_cate<- rpart(Species~Sepal.Length,data=iris,
method ="class", control = rpart.control(minsplit = 15,cp=0))
summary(model_Sepal.Length_cate)

## Call:
## rpart(formula = Species ~ Sepal.Length, data = iris, method = "class",
## control = rpart.control(minsplit = 15, cp = 0))
## n= 150
##
## CP nsplit rel error xerror xstd
## 1 0.44 0 1.00 1.19 0.04959167
## 2 0.18 1 0.56 0.63 0.06044833
## 3 0.00 2 0.38 0.46 0.05647418
##
## Variable importance
## Sepal.Length
## 100
##
## Node number 1: 150 observations, complexity param=0.44
## predicted class=setosa expected loss=0.6666667 P(node) =1
## class counts: 50 50 50
## probabilities: 0.333 0.333 0.333
## left son=2 (52 obs) right son=3 (98 obs)
## Primary splits:
## Sepal.Length < 5.45 to the left, improve=34.16405, (0 missing)
##
## Node number 2: 52 observations
## predicted class=setosa expected loss=0.1346154 P(node) =0.3466667
## class counts: 45 6 1
## probabilities: 0.865 0.115 0.019
##
## Node number 3: 98 observations, complexity param=0.18
## predicted class=virginica expected loss=0.5 P(node) =0.6533333
## class counts: 5 44 49
## probabilities: 0.051 0.449 0.500
## left son=6 (43 obs) right son=7 (55 obs)
## Primary splits:
## Sepal.Length < 6.15 to the left, improve=8.938422, (0 missing)
##
## Node number 6: 43 observations
## predicted class=versicolor expected loss=0.3488372 P(node) =0.2866667
## class counts: 5 28 10
## probabilities: 0.116 0.651 0.233
##
## Node number 7: 55 observations
## predicted class=virginica expected loss=0.2909091 P(node) =0.3666667
## class counts: 0 16 39
## probabilities: 0.000 0.291 0.709

rpart.plot(model_Sepal.Length_cate, extra = 1)

56
setosa
setosa versicolor
50 50 50 virginica
yes Sepal.Length < 5.5 no

virginica
5 44 49
Sepal.Length < 6.2

setosa versicolor virginica


45 6 1 5 28 10 0 16 39

Podemos podar el árbol para obtener el óptimo de cortes.

Optimo <- model_Sepal.Length_cate$cptable[


which.min(model_Sepal.Length_cate$cptable[,"xerror"]),"CP"]
model_Sepal.Length_cate_podado <- prune(model_Sepal.Length_cate,Optimo)
rpart.plot(model_Sepal.Length_cate_podado, extra = 1)

57
setosa
setosa versicolor
50 50 50 virginica
yes Sepal.Length < 5.5 no

virginica
5 44 49
Sepal.Length < 6.2

setosa versicolor virginica


45 6 1 5 28 10 0 16 39

Como vemos, el árbol podado por el óptimo del xerror, nos da los mismos cortes que el árbol que hemos
realizado con anterioridad, por lo que vamos a proceder a categorizar la variable en los puntos 5.5 y 6.2.

iris_cate <- iris[,c(2:5)]


iris_cate$Sepal.Length[iris$Sepal.Length<= 5.4]= 0
iris_cate$Sepal.Length[iris$Sepal.Length> 5.4]= 1
iris_cate$Sepal.Length[iris$Sepal.Length> 6.2]= 2
iris_cate$Sepal.Length<- as.factor(iris_cate$Sepal.Length)
table(iris_cate$Sepal.Length)

##
## 0 1 2
## 52 47 51

Ya tenemos categorizada nuestra variable Sepal.Length en tres categorías a las cuales las hemos nombrado
con los niveles 0, 1 y 2.

58

También podría gustarte