Capítulo 2 Preparar los datos para el modelo de aprendizaje
Antes de comenzar a usar modelos de pronóstico supervisados tenemos que preparar los datos de origen y realizar unas tareas que nos den el control del proceso y nos permitan verificar, al finalizar, la bondad y ajuste de nuestras previsiones.
Es una buena práctica y debemos empezar siempre por ella, dividir el conjunto de datos de origen en dos subconjuntos, uno de ellos nos servirá para el entrenamiento (train) del modelo y el otro para la comprobación -a posteriori- (test). Con este sistema de división de la muestra inicial de datos evitamos el overfitting tan preocupante en los modelo de predicción.
Otro de los trabajos preparatorios de importancia es la factorización o agrupamiento de los datos. En casos como el modelo de naive bayes, el uso de valores numéricos continuos en los datos de origen genera una sobredimensión exponencial de los casos posibles, por exceso de combinatoria entre variables que desborda el modelo y puede producir errores en los pronósticos.
En estos casos es siempre recomendable factorizar, agrupar y disminuir la dimensión origen de las variables de entrada agrupando valores.
En otros casos como el algoritmo knn, la medición de distancia entre variables es el eje del pronóstico, por lo que es necesario escalar los datos normalizarlos para equiparar la fórmula de distancia entre las variables, como veremos en los ejemplos.
2.1 Crear particiones de la muestra
El primer paso en todo análisis debe ser dividir la muestra de datos en dos conjuntos de datos uno para entrenamiento y otro para test. Esto se puede hacer a mano, por ejemplo usando la función sample
, o con la ayuda de algunos paquetes que llevan funciones incorporadas para las particiones de datos.
2.1.1 Ejemplo de partición a mano
Usaremos la base de datos de muestra de supervivientes del Titanic que se da como tabla en dataset
. Para ver todas las series y bases de datos disponibles en dataset escribiremos data()
.
Titanic es una tabla que indica casos y frecuencias de cada caso, por lo que para crear la tabla completa hay que expandir la tabla origen, y repetir cada caso el número de veces que indica la columna frecuencia.
# cargamos los datos
data("Titanic")
str(Titanic)
## 'table' num [1:4, 1:2, 1:2, 1:2] 0 0 35 0 0 0 17 0 118 154 ...
## - attr(*, "dimnames")=List of 4
## ..$ Class : chr [1:4] "1st" "2nd" "3rd" "Crew"
## ..$ Sex : chr [1:2] "Male" "Female"
## ..$ Age : chr [1:2] "Child" "Adult"
## ..$ Survived: chr [1:2] "No" "Yes"
class(Titanic)
## [1] "table"
# Transformamos los datos wn una dataframe
Titanic_df=as.data.frame(Titanic)
str(Titanic_df)
## 'data.frame': 32 obs. of 5 variables:
## $ Class : Factor w/ 4 levels "1st","2nd","3rd",..: 1 2 3 4 1 2 3 4 1 2 ...
## $ Sex : Factor w/ 2 levels "Male","Female": 1 1 1 1 2 2 2 2 1 1 ...
## $ Age : Factor w/ 2 levels "Child","Adult": 1 1 1 1 1 1 1 1 2 2 ...
## $ Survived: Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 1 1 1 1 ...
## $ Freq : num 0 0 35 0 0 0 17 0 118 154 ...
# Creamos una tabla de casos competos a partir de la frecuencia de cada uno
# Esto repite cada caso el num de veces que se ha dado
# según la frecuencia que está en la columna Freq de la tabla.
repetir_secuencia=rep.int(seq_len(nrow(Titanic_df)), Titanic_df$Freq)
# tenemos una serie con el numero de registro de la tabla original y las veces que se repite
# Creamos una nueva tabla repitiendo los casos según el modelo anterior.
Titanic_data=Titanic_df[repetir_secuencia,]
# Ya no necesitamos la columna de frecuencias y la borramos.
Titanic_data$Freq=NULL
head(Titanic_data)
## Class Sex Age Survived
## 3 3rd Male Child No
## 3.1 3rd Male Child No
## 3.2 3rd Male Child No
## 3.3 3rd Male Child No
## 3.4 3rd Male Child No
## 3.5 3rd Male Child No
# Como vemos todo son factores
str(Titanic_data)
## 'data.frame': 2201 obs. of 4 variables:
## $ Class : Factor w/ 4 levels "1st","2nd","3rd",..: 3 3 3 3 3 3 3 3 3 3 ...
## $ Sex : Factor w/ 2 levels "Male","Female": 1 1 1 1 1 1 1 1 1 1 ...
## $ Age : Factor w/ 2 levels "Child","Adult": 1 1 1 1 1 1 1 1 1 1 ...
## $ Survived: Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 1 1 1 1 ...
En este caso buscamos dividir la tabla en dos conjuntos uno de entrenamiento con el 75% de los registros y otro de comprobación o de test con el 25% restante de los registros que nos servirá para validar el modelo.
# -----------------------------------
# DIVISION A MANO
# contamos el num de registro de la base de datos del titanic
nrow(Titanic_data)
## [1] 2201
# calculamos el 75%
num_reg_entrenamiento<-as.integer(0.75*nrow(Titanic_data))
num_reg_entrenamiento
## [1] 1650
# Creamos una vector de 75% de los registros aleatorio
titanic_train <- sample(nrow(Titanic_data), num_reg_entrenamiento)
# Creamos el conjunto de registros de entrenamiento pasando ese vector a la tabla
d_titanic_train <- Titanic_data[titanic_train,]
head(d_titanic_train)
## Class Sex Age Survived
## 12.53 Crew Male Adult No
## 28.107 Crew Male Adult Yes
## 12.115 Crew Male Adult No
## 12.94 Crew Male Adult No
## 10.144 2nd Male Adult No
## 29.44 1st Female Adult Yes
# Creamos los datos de comprobación o test (notese el -)
d_titanic_test <- Titanic_data[-titanic_train,]
head(d_titanic_test)
## Class Sex Age Survived
## 3 3rd Male Child No
## 3.7 3rd Male Child No
## 3.8 3rd Male Child No
## 3.13 3rd Male Child No
## 3.25 3rd Male Child No
## 3.28 3rd Male Child No
Usando una formulación simple hemos creado dos conjuntos de muestra aleatorios excluyentes de entrenamiento y muestra.
2.1.2 Ejemplo de partición con library(caret)
Veamos otra forma de hacerlo usando la librería library(caret)
library(caret)
set.seed(987654321)
# creamos un vector de particion sobre la variable Survived
# el tamaño de muestra será de 75%
trainIndex=createDataPartition(Titanic_data$Survived, p=0.75)$Resample1
d_titanic_train=Titanic_data[trainIndex, ]
d_titanic_test= Titanic_data[-trainIndex, ]
2.2 Categorización de los datos origen
Existe un problema en el uso de las funciones de clasificación cuando las combinaciones posibles entre variables tienden a ser infinitas. Esto sucede cuando, por ejemplo, una de las variables es de tipo numérico, y tiene datos continuos.
La mayoría de los modelos de clasificación solo son capaces de trabajar un número limitado de categorías, y por lo tanto, es necesario agrupar los datos originales y reducir las opciones combinatorias, por lo que hay que evitar siempre el uso de varialbles continuas en los datos. Si no realizamos la reducción de categorías nos arriesgamos a obtener errores, incluso evidentes, en los pronósticos.
Un caso evidente es el uso de la función de naive_bayes
que maneja muy mal los datos continuos, pues está pensado para variables categorizadas.
La solución es realizar una categorización previa de los datos que evite el problema y a la vez simplifique el modelo de pronóstico. Categorizar significa agrupar los datos de las variables continuas en categorías próximas, simplificando las salidas y reduciendo las combinaciones.
Un ejemplo claro es redondear las salidas numéricas a números divisibles por 10 o por 5, o sustituir la variable por los cuantiles más representativos.
Para transformar las variables y categorizarlas podemos usar varias funiciones de R como:
- convertir a factor con la funcion
as.factor()
. * Las categorías de un factor se ven con la funciónlevels()
* los nombres de esas categorias los damos con la funcionlables()
- la funcion
table(tabla_1$hora)
cuenta y resumen los datos - las funciones
quantile()
ocut()
ayudan a dividir y categorizar variables continuas - fundiones de redondeo
Vamos a ver varios ejemplo con la dataset “women” que contiene las alturas y pesos de mujeres americanas de edad entre 30 y 39 años. Ambos datos de altura y peso son continuos y toman 15 posibles valores, por lo que las combinaciones cruzadas dan muchísimos casos posibles. Para simplificar las combinatorias vamos a categorizar los datos de varias maneras, como ejemplos:
# cargamos los datos de
data("women")
str(women)
## 'data.frame': 15 obs. of 2 variables:
## $ height: num 58 59 60 61 62 63 64 65 66 67 ...
## $ weight: num 115 117 120 123 126 129 132 135 139 142 ...
summary(women)
## height weight
## Min. :58.0 Min. :115.0
## 1st Qu.:61.5 1st Qu.:124.5
## Median :65.0 Median :135.0
## Mean :65.0 Mean :136.7
## 3rd Qu.:68.5 3rd Qu.:148.0
## Max. :72.0 Max. :164.0
length(unique(women$height)) # numero de registros unicos
## [1] 15
length(unique(women$weight))
## [1] 15
# opcion 1. creamos una funcion de redondeo
redondea5<-function(x,base=5){
as.integer(base*round(x/base))
}
# Copiamos la tabla con el nombre nuevo
mujeres_a<-women
# pasamos los datos a sistema internacional
mujeres_a$peso<-mujeres_a$weight*0.453592 # paso a kg
mujeres_a$peso<- redondea5(mujeres_a$peso,10)
length(unique(mujeres_a$peso))
## [1] 3
mujeres_a$altura<-mujeres_a$height*2.54 # paso a cm
mujeres_a$altura<- redondea5(mujeres_a$altura,10)
length(unique(mujeres_a$altura))
## [1] 4
head(mujeres_a)
## height weight peso altura
## 1 58 115 50 150
## 2 59 117 50 150
## 3 60 120 50 150
## 4 61 123 60 150
## 5 62 126 60 160
## 6 63 129 60 160
Con la simplificacion anterior hemos pasado de 15x15 casos combinatorios a solo 3x4.
Podríamos usar también factores para convertir los datos
#Ejemplo de transformacion a factor
mujeres_a$peso1<- factor(mujeres_a$peso,
levels = c(50, 60, 70),
labels = c( "flaca","media","gordita"))
# Ejemplo 2 de trans a factor:
mujeres_a$altura1 <- factor(mujeres_a$altura, levels = c(150,160,170,180), labels = c("Bajo","Medio","Alta", "muy alta"))
head(mujeres_a)
## height weight peso altura peso1 altura1
## 1 58 115 50 150 flaca Bajo
## 2 59 117 50 150 flaca Bajo
## 3 60 120 50 150 flaca Bajo
## 4 61 123 60 150 media Bajo
## 5 62 126 60 160 media Medio
## 6 63 129 60 160 media Medio
2.3 Manejo de NA
En todos los modelos, la existencia de registros con falta de datos o NA, anula el valor de dicha evidencia en el modelo de entrenamiento.
Una solución es completar estos casos con las funciones como impute()
del paquete e1071
que sustituye el NA por un valor estimado, que puede ser la media.
En la tabla de ejemplo donors
hay muchos datos de la edad de los clientes que faltan.
# Vemos cuantos datos de edad faltan.
set.seed(333)
datos<-data.frame(a=sample(1:10, 100,replace = T))
datos$a[c(1,3)] <- NA
head(datos)
## a
## 1 NA
## 2 1
## 3 NA
## 4 6
## 5 1
## 6 8
library(e1071)
# Imputamos la nuevos datos estimados de edad asignando usando impute
datos$imputed <- impute(datos)
head(datos)
## a a
## 1 NA 5
## 2 1 1
## 3 NA 5
## 4 6 6
## 5 1 1
## 6 8 8
# Otra forma es hacerlo manualmente
datos$imputed2<-ifelse(is.na(datos$a),5,datos$a)
head(datos)
## a a imputed2
## 1 NA 5 5
## 2 1 1 1
## 3 NA 5 5
## 4 6 6 6
## 5 1 1 1
## 6 8 8 8