Documentos de Académico
Documentos de Profesional
Documentos de Cultura
• https://cran.r-project.org/web/packages/rpart/vignettes/longintro.pdf
library(tidyverse)
library(rpart)
library(rpart.plot)
library(caret)
• https://archive.ics.uci.edu/ml/datasets/Wine
Necesitamos descargar dos archivos. El primero contiene los datos que usaremos, y el
segundo contiene su descripción (metadatos), la cual nos será de gran utilidad más adelante.
# Datos
download.file("https://archive.ics.uci.edu/ml/machine-learning-databases/wine
/wine.data", "wine.data")
# Información
download.file("https://archive.ics.uci.edu/ml/machine-learning-databases/wine
/wine.names", "wine.names")
readLines("wine.data", n = 10)
## [1] "1,14.23,1.71,2.43,15.6,127,2.8,3.06,.28,2.29,5.64,1.04,3.92,1065"
## [2] "1,13.2,1.78,2.14,11.2,100,2.65,2.76,.26,1.28,4.38,1.05,3.4,1050"
## [3] "1,13.16,2.36,2.67,18.6,101,2.8,3.24,.3,2.81,5.68,1.03,3.17,1185"
## [4] "1,14.37,1.95,2.5,16.8,113,3.85,3.49,.24,2.18,7.8,.86,3.45,1480"
## [5] "1,13.24,2.59,2.87,21,118,2.8,2.69,.39,1.82,4.32,1.04,2.93,735"
## [6] "1,14.2,1.76,2.45,15.2,112,3.27,3.39,.34,1.97,6.75,1.05,2.85,1450"
## [7] "1,14.39,1.87,2.45,14.6,96,2.5,2.52,.3,1.98,5.25,1.02,3.58,1290"
## [8] "1,14.06,2.15,2.61,17.6,121,2.6,2.51,.31,1.25,5.05,1.06,3.58,1295"
## [9] "1,14.83,1.64,2.17,14,97,2.8,2.98,.29,1.98,5.2,1.08,2.85,1045"
## [10] "1,13.86,1.35,2.27,16,98,2.98,3.15,.22,1.85,7.22,1.01,3.55,1045"
El archivo de datos parece ser una tabla de datos rectangular, con columnas separadas por
comas. Entonces leer este archivo es fácil. El único inconveniente que tenemos es que nos
faltan los nombres de cada columna.
Podemos usar read_table() para leer este archivo. Esta función está diseñada para leer
tablas de datos, es decir, con estructura rectangular (renglones y columnas).
Para asegurarnos que los datos serán leídos de manera correcta, especificamos que el
separador de las columnas es una coma (sep = ",") y que no tenemos nombres de columna
en nuestro archivo (header = FALSE). Asignamos el resultado al objeto vino.
vino
## # A tibble: 178 x 14
## V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12
## <int> <dbl> <dbl> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 14.2 1.71 2.43 15.6 127 2.80 3.06 0.280 2.29 5.64 1.04
## 2 1 13.2 1.78 2.14 11.2 100 2.65 2.76 0.260 1.28 4.38 1.05
## 3 1 13.2 2.36 2.67 18.6 101 2.80 3.24 0.300 2.81 5.68 1.03
## 4 1 14.4 1.95 2.50 16.8 113 3.85 3.49 0.240 2.18 7.80 0.860
## 5 1 13.2 2.59 2.87 21.0 118 2.80 2.69 0.390 1.82 4.32 1.04
## 6 1 14.2 1.76 2.45 15.2 112 3.27 3.39 0.340 1.97 6.75 1.05
## 7 1 14.4 1.87 2.45 14.6 96 2.50 2.52 0.300 1.98 5.25 1.02
## 8 1 14.1 2.15 2.61 17.6 121 2.60 2.51 0.310 1.25 5.05 1.06
## 9 1 14.8 1.64 2.17 14.0 97 2.80 2.98 0.290 1.98 5.20 1.08
## 10 1 13.9 1.35 2.27 16.0 98 2.98 3.15 0.220 1.85 7.22 1.01
## # ... with 168 more rows, and 2 more variables: V13 <dbl>, V14 <int>
readLines("wine.names", n = 10)
## [1] "1. Title of Database: Wine recognition data"
## [2] "\tUpdated Sept 21, 1998 by C.Blake : Added attribute information"
## [3] ""
## [4] "2. Sources:"
## [5] " (a) Forina, M. et al, PARVUS - An Extendible Package for Data"
## [6] " Exploration, Classification and Correlation. Institute of Pha
rmaceutical"
## [7] " and Food Analysis and Technologies, Via Brigata Salerno, "
## [8] " 16147 Genoa, Italy."
## [9] ""
## [10] " (b) Stefan Aeberhard, email: stefan@coral.cs.jcu.edu.au"
Parece ser un archivo de texto común y corriente, pero con una extensión inusual. Podemos
crear una copia de este archivo con la extensión a .txt con file.copy() para leerlo
fácilmente en bloc de notas o cualquier aplicación similar. Después,
usamos file.show() para darle una lectura.
file.show("wine_names.txt")
A partir de lo que este documento explica, descubrimos que nuestros datos corresponden a
trece características químicas de tres tipos de vinos. Esto quiere decir que una de las
columnas de nuestros datos indica el tipo de vino y las otras trece son sus características.
Aunque es probable que la primera columna de nuestros datos sea la variable con el tipo de
vino, usamos summary() para asegurarnos
summary(vino)
## V1 V2 V3 V4
## Min. :1.000 Min. :11.03 Min. :0.740 Min. :1.360
## 1st Qu.:1.000 1st Qu.:12.36 1st Qu.:1.603 1st Qu.:2.210
## Median :2.000 Median :13.05 Median :1.865 Median :2.360
## Mean :1.938 Mean :13.00 Mean :2.336 Mean :2.367
## 3rd Qu.:3.000 3rd Qu.:13.68 3rd Qu.:3.083 3rd Qu.:2.558
## Max. :3.000 Max. :14.83 Max. :5.800 Max. :3.230
## V5 V6 V7 V8
## Min. :10.60 Min. : 70.00 Min. :0.980 Min. :0.340
## 1st Qu.:17.20 1st Qu.: 88.00 1st Qu.:1.742 1st Qu.:1.205
## Median :19.50 Median : 98.00 Median :2.355 Median :2.135
## Mean :19.49 Mean : 99.74 Mean :2.295 Mean :2.029
## 3rd Qu.:21.50 3rd Qu.:107.00 3rd Qu.:2.800 3rd Qu.:2.875
## Max. :30.00 Max. :162.00 Max. :3.880 Max. :5.080
## V9 V10 V11 V12
## Min. :0.1300 Min. :0.410 Min. : 1.280 Min. :0.4800
## 1st Qu.:0.2700 1st Qu.:1.250 1st Qu.: 3.220 1st Qu.:0.7825
## Median :0.3400 Median :1.555 Median : 4.690 Median :0.9650
## Mean :0.3619 Mean :1.591 Mean : 5.058 Mean :0.9574
## 3rd Qu.:0.4375 3rd Qu.:1.950 3rd Qu.: 6.200 3rd Qu.:1.1200
## Max. :0.6600 Max. :3.580 Max. :13.000 Max. :1.7100
## V13 V14
## Min. :1.270 Min. : 278.0
## 1st Qu.:1.938 1st Qu.: 500.5
## Median :2.780 Median : 673.5
## Mean :2.612 Mean : 746.9
## 3rd Qu.:3.170 3rd Qu.: 985.0
## Max. :4.000 Max. :1680.0
nombres <-
readLines("wine_names.txt")[58:70] %>%
gsub("[[:cntrl:]].*\\)", "", .) %>%
trimws() %>%
tolower() %>%
gsub(" |/", "_", .) %>%
# Agregamos el nombre "tipo", para nuestra primera columna con los tipos de
vino
c("tipo", .)
set.seed(1649)
vino_entrenamiento <- sample_frac(vino, .7)
arbol_1
## n= 125
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 125 78 1 (0.37600000 0.36000000 0.26400000)
## 2) proline>=755 49 4 1 (0.91836735 0.04081633 0.04081633) *
## 3) proline< 755 76 33 2 (0.02631579 0.56578947 0.40789474)
## 6) color_intensity< 4.02 38 0 2 (0.00000000 1.00000000 0.00000000) *
## 7) color_intensity>=4.02 38 7 3 (0.05263158 0.13157895 0.81578947)
## 14) flavanoids>=1.385 8 3 2 (0.25000000 0.62500000 0.12500000) *
## 15) flavanoids< 1.385 30 0 3 (0.00000000 0.00000000 1.00000000) *
Lo anterior muestra el esquema de nuestro árbol de clasificación. Cada inciso nos indica un
nodo y la regla de clasificación que le corresponde. Siguiendo estos nodos, podemos llegar
a las hojas del árbol, que corresponde a la clasificación de nuestros datos.
Todo lo anterior resulta mucho más claro si lo visualizamos, así que creamos una gráfica
usando nuestro modelo con la función rpart.plot() de rpart.plot.
rpart.plot(arbol_1)
En estos gráficos, cada uno de los rectángulos representa un nodo de nuestro árbol, con su
regla de clasificación.
Cada nodo está coloreado de acuerdo a la categoría mayoritaria entre los datos que agrupa.
Esta es la categoría que ha predicho el modelo para ese grupo.
Dentro del rectángulo de cada nodo se nos muestra qué proporción de casos pertenecen a
cada categoría y la proporción del total de datos que han sido agrupados allí. Por ejemplo,
el rectángulo en el extremo inferior izquierdo de la gráfica tiene 94% de casos en el tipo 1, y
4% en los tipos 2 y 3, que representan 39% de todos los datos.
Estas proporciones nos dan una idea de la precisión de nuestro modelo al hacer
predicciones. De este modo, las reglas que conducen al rectángulo que acabamos de
mencionar nos dan un 92% de clasificaciones correctas. En contraste, el tercer rectángulo,
de izquierda a derecha, de color gris, tuvo sólo 62% de clasificaciones correctas.
Además, podemos sentirnos contentos de que dos de las hojas de nuestro árbol de
clasificación han logrado un 100% de clasificaciones correctas, para los vinos de tipo 2 y 3.
Pero, por supuesto, necesitamos ser más sistemáticos para indagar qué tan bien hace
predicciones nuestro modelo.
Usamos la función precict() con nuestro set de prueba para generar un vector con los
valores predichos por el modelo que hemos entrenado, especificamos el parámetro type =
"class".
Cruzamos la predicción con los datos reales de nuestro set de prueba para generar una
matriz de confusión, usando confusionMatrix() de caret.
confusionMatrix(prediccion_1, vino_prueba[["tipo"]])
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3
## 1 12 2 4
## 2 0 24 2
## 3 0 0 9
##
## Overall Statistics
##
## Accuracy : 0.8491
## 95% CI : (0.7241, 0.9325)
## No Information Rate : 0.4906
## P-Value [Acc > NIR] : 5.793e-08
##
## Kappa : 0.7621
## Mcnemar's Test P-Value : 0.04601
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3
## Sensitivity 1.0000 0.9231 0.6000
## Specificity 0.8537 0.9259 1.0000
## Pos Pred Value 0.6667 0.9231 1.0000
## Neg Pred Value 1.0000 0.9259 0.8636
## Prevalence 0.2264 0.4906 0.2830
## Detection Rate 0.2264 0.4528 0.1698
## Detection Prevalence 0.3396 0.4906 0.1698
## Balanced Accuracy 0.9268 0.9245 0.8000
Nada mal. Tenemos una precisión (accuracy), Kappa y otros estadísticos con buenos
valores.
Sin embargo, no hemos terminado. Este árbol ha predicciones a partir de los datos de
entrenamiento que hemos proporcionado. ¿Recuerdas que el algoritmo busca la mejor
separación para crear grupos? Si nuestros datos cambian, la variable que hace la mejore
separación también puede cambiar. Y por lo tanto, los grupos que resulten de esta
separación, serán distintos, resultando en un modelo que puede ser muy distinto al que
hemos obtenido.
Generamos un segundo árbol, usando sets de entrenamiento y prueba diferentes.
set.seed(7439)
vino_entrenamiento_2 <- sample_frac(vino, .7)
rpart.plot(arbol_2)
confusionMatrix(prediccion_2, vino_prueba_2[["tipo"]])
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3
## 1 12 0 0
## 2 0 25 1
## 3 0 1 14
##
## Overall Statistics
##
## Accuracy : 0.9623
## 95% CI : (0.8702, 0.9954)
## No Information Rate : 0.4906
## P-Value [Acc > NIR] : 6.238e-14
##
## Kappa : 0.9399
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3
## Sensitivity 1.0000 0.9615 0.9333
## Specificity 1.0000 0.9630 0.9737
## Pos Pred Value 1.0000 0.9615 0.9333
## Neg Pred Value 1.0000 0.9630 0.9737
## Prevalence 0.2264 0.4906 0.2830
## Detection Rate 0.2264 0.4717 0.2642
## Detection Prevalence 0.2264 0.4906 0.2830
## Balanced Accuracy 1.0000 0.9623 0.9535
¡Oh! Esta vez hemos obtenido una precisión casi perfecta en nuestras predicciones. Con
este modelo hemos mejorado la predicción con respecto al anterior.
Sin embargo, es importante notar que este segundo modelo es diferente con respecto al
anterior en el orden que fueron hechas las particiones, pero es idéntico en cuanto a las
variables usadas para separar grupos.
Podemos cambiar los datos usados en los sets de entrenamiento y prueba y obtener
resultados distintos.
set.seed(8476)
vino_entrenamiento_3 <- sample_frac(vino, .7)
rpart.plot(arbol_3)
confusionMatrix(prediccion_3, vino_prueba_3[["tipo"]])
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3
## 1 8 0 0
## 2 0 24 2
## 3 4 2 13
##
## Overall Statistics
##
## Accuracy : 0.8491
## 95% CI : (0.7241, 0.9325)
## No Information Rate : 0.4906
## P-Value [Acc > NIR] : 5.793e-08
##
## Kappa : 0.758
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3
## Sensitivity 0.6667 0.9231 0.8667
## Specificity 1.0000 0.9259 0.8421
## Pos Pred Value 1.0000 0.9231 0.6842
## Neg Pred Value 0.9111 0.9259 0.9412
## Prevalence 0.2264 0.4906 0.2830
## Detection Rate 0.1509 0.4528 0.2453
## Detection Prevalence 0.1509 0.4906 0.3585
## Balanced Accuracy 0.8333 0.9245 0.8544
Esta ocasión hemos obtenido una precisión en nuestras predicciones similar al primer
modelo que generamos, pero ahora una de las variables usadas en la partición es diferente.
En todos estos ejemplos, lo único que hemos cambiado son nuestros sets de prueba y
entrenamiento, ningún otro parámetro ha cambiado. Esto ilustra la desventaja de que sea
Sistematizando el análisis
Por supuesto, podemos crear un grupo de funciones que para generar árboles de manera
repetida.
sets
}
arbol
}
diagnostico
}
set.seed(1986)
unarbol <- crear_arbol(vino, "tipo", mi_cp = 0.005)
unarbol[["diagnostico"]]
## $matriz
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3
## 1 22 1 0
## 2 0 11 2
## 3 0 1 16
##
## Overall Statistics
##
## Accuracy : 0.9245
## 95% CI : (0.8179, 0.9791)
## No Information Rate : 0.4151
## P-Value [Acc > NIR] : 7.065e-15
##
## Kappa : 0.884
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3
## Sensitivity 1.0000 0.8462 0.8889
## Specificity 0.9677 0.9500 0.9714
## Pos Pred Value 0.9565 0.8462 0.9412
## Neg Pred Value 1.0000 0.9500 0.9444
## Prevalence 0.4151 0.2453 0.3396
## Detection Rate 0.4151 0.2075 0.3019
## Detection Prevalence 0.4340 0.2453 0.3208
## Balanced Accuracy 0.9839 0.8981 0.9302
##
## $mincp
## CP.mínimo CP.original Podar
## 1 0.005 0.005 NO
Para concluir
Notarás que las funciones anteriores incluyen ajustes de parámetros que no hemos discutido
y que nos ayudan a perfeccionar nuestros modelos de predicción con árboles de
clasificación. Estos los revisaremos en otra ocasión.
Por lo pronto, en este documento ya revisamos qué son los árboles de decisión, sus ventajas
y desventajas, cómo implementarlos para hacer clasificación usando el paquete rpart de R
y, de paso, cómo resolver algunos problemas comunes al importar datos guardados en
archivos de formato no convencional.
Con esto creo que tendrás los elementos básicos para crear e interpretar tus propios árboles
de clasificación con distintos tipos de datos.