library(tidyverse)
<- read_csv("data/descuento.csv") %>%
descuento mutate(zona = parse_factor(zona), mujer = as.factor(mujer))
Tema 10. Ejemplo 1
Introducción
En este ejemplo, utilizamos el conjunto de datos descuento.csv
. Tenemos información de las ventas realizadas a un cliente y algunas características de éste:
Variable | Tipo | Descripción |
---|---|---|
ventas | Numérica | Ventas registradas (en euros). |
renta | Numérica | Renta disponible del cliente (en euros). |
descuento | Categórica | ¿Ha recibido descuento? (1 = sí, 0 = no). |
zona | Categórica | Zona de residencia: “ciudad”, “capital” o “pueblo”. |
edad | Numérica | Edad del cliente (en años). |
mujer | Categórica | Género del cliente (1 = mujer, 0 = hombre). |
educ | Numérica | Nivel educativo del cliente (en años). |
Carga de datos
- Cargamos los datos y les damos el tipo de variable adecuado
Modelos kNN
Paso 0: Partición en Entrenamiento y Prueba
- Usamos
initial_split()
para generar el objeto que almacena las dos particiones
library(tidymodels)
set.seed(123)
<- initial_split(descuento, prop = 0.8) descuentoPart
Paso 1: Preparar los datos y Especificación
Para kNN, debemos estandarizar las variables numéricas continuas. NO tenemos que incluir transformaciones no lineales (ni es necesario discretizar), puesto que este es un método no paramétrico que permite una relación general entre la variable dependiente y los predictores, sin suponer una forma funcional específica.
Respecto a las variables categóricas, debemos crear una variable binaria (dummy) para cada categoría (es decir, no tenemos un grupo omitido),
<- descuentoPart %>% training() %>%
recetakNN1 recipe(ventas ~ renta + descuento + zona + edad + mujer + educ) %>%
step_normalize(all_numeric_predictors()) %>%
step_dummy(all_nominal_predictors(), one_hot = TRUE)
<- recetakNN1 %>%
recetakNN2 step_log(ventas)
Paso 2: Entrenamiento
Paso 2.A: Definición del modelo
Definimos un modelo de kNN, preparando para ajustar los hiperparámetros. Es igual para ambas especificaciones.
Con la biblioteca
kknn
, podemos ajustar el hiperparámetro de número de vecinos. También podríamos probar distintas formas de medir la distancia entre los puntos. En este caso usamos la distancia al cuadrado,dist_power = 2
; también se puede usar la distancia como valor absoluto,dist_power = 1
.
<- nearest_neighbor(mode= "regression", engine = "kknn",
modelo_knn neighbors = tune(), dist_power = 2)
Paso 2.B: Creación del flujo de trabajo
Creamos los flujos de trabajo combinando la receta y modelo
<- workflow() %>%
flujo_knn1 add_recipe(recetakNN1) %>%
add_model(modelo_knn)
<- workflow() %>%
flujo_knn2 add_recipe(recetakNN2) %>%
add_model(modelo_knn)
Paso 2.C: Estimación del flujo
Paso 2.C.1: Ajuste del hiperparámetros
- Definimos las particiones de validación cruzada en la muestra de entrenamiento que vamos a utilizar, para ambos casos:
set.seed(9753)
<- descuentoPart %>% training() %>%
descuento_entrenCV vfold_cv(v=10)
Proceso de ajuste para la especificación en niveles
- Deberíamos realizar una búsqueda probando varios rangos y valores para el hiperparámetro.
<- grid_regular(neighbors(range = c(1, 20), trans = NULL),
knn_grid1 levels = 20)
<- flujo_knn1 %>%
flujo_knn1_ajust tune_grid(resamples = descuento_entrenCV,
metrics = metric_set(rmse, mae),
grid = knn_grid1 )
%>% autoplot() flujo_knn1_ajust
- Elegimos el mejor hiperparámetro según el error cuadrático medio
%>% show_best(metric = "rmse")
flujo_knn1_ajust <- flujo_knn1_ajust %>% select_best(metric = "rmse") mejor_nn1
Proceso de ajuste para la especificación en logaritmos
<- grid_regular(neighbors(range = c(1, 20), trans = NULL),
knn_grid2 levels = 20)
<- flujo_knn2 %>%
flujo_knn2_ajust tune_grid(resamples = descuento_entrenCV,
metrics = metric_set(rmse, mae),
grid = knn_grid2 )
%>% autoplot() flujo_knn2_ajust
%>% show_best(metric = "rmse")
flujo_knn2_ajust <- flujo_knn2_ajust %>% select_best(metric = "rmse") mejor_nn2
Paso 2.C.2: Finalizando y estimando
- Finalizamos los flujos
<- flujo_knn1 %>%
flujo_knn1_final finalize_workflow(mejor_nn1)
<- flujo_knn2 %>%
flujo_knn2_final finalize_workflow(mejor_nn2)
- En este caso, NO tiene mucho sentido estimar el modelo del flujo finalizado con los datos de entrenamiento. Como este modelo es no paramétrico NO tenemos coeficientes estimados que mostrar. Tampoco hay una forma fácil de calcular la importancia de las variables u otras formas de interpretar el modelo.
<- flujo_knn1_final %>%
flujo_knn1_final_est fit(data = descuentoPart %>% training())
<- flujo_knn2_final %>%
flujo_knn2_final_est fit(data = descuentoPart %>% training())
Evaluación de modelos
- Usamos
final_fit()
en cada modelo para calcular las métricas de error:
<- flujo_knn1_final %>%
knn1_final_fit last_fit(split = descuentoPart,
metrics = metric_set(rmse, mae))
<- flujo_knn2_final %>%
knn2_final_fit last_fit(split = descuentoPart,
metrics = metric_set(rmse, mae))
- Recopilamos las métricas y las presentamos en una tabla
library(kableExtra)
<- knn1_final_fit %>% collect_metrics() %>%
knn1 select(.metric, .estimate) %>% rename(knn1 = .estimate)
<- knn2_final_fit %>% collect_metrics() %>%
knn2 select(.metric, .estimate) %>% rename(knn2 = .estimate)
%>% inner_join(knn2) %>%
knn1 kbl() %>% kable_classic()
.metric | knn1 | knn2 |
---|---|---|
rmse | 762.4162 | 0.2499795 |
mae | 541.0776 | 0.1467610 |
Podemos comparar estos resultados con los obtenidos anteriormente en el mismo conjunto de datos usando otros métodos; en concreto, hemos estimados árboles de decisión y “random forests” y podríamos haber estimado también modelos de regresión lineal y LASSO.
Nuevamente, el modelo con la variable dependiente en logaritmos predice mejor que en niveles. Sin embargo, kNN predice peor que los otros métodos.