Introducción

Una empresa de viajes y tours turísticos ofrece paquetes de seguro a sus clientes. Actualmente la empresa está desarrollando un nuevo paquete que incluirá una nueva cobertura, debido a esta nueva inclusión la compañía necesita saber qué clientes estarían interesados en comprarlo según el historial de su base de datos.

El seguro se ofreció a algunos de sus clientes en el año 2019 y los datos proporcionados se extrajeron del rendimiento / ventas del paquete durante ese período. Los datos recolectados son de casi 2000 de sus clientes y se requiere que construya un modelo estadístico que pueda predecir si el cliente estará interesado en comprar o no el paquete de seguro de viaje en función de ciertos parámetros.

Los datos se extrajeron de la reconocida comunidad de científicos de datos Kaggle y se hará uso del ecosistema de paquetes que ofrece Tidymodels para el desarrollo de los distintos modelos de aprendizaje supervisado.

Descripción de los datos

En total hay 9 columnas en los datos, las cuáles se describen a continuación:

  • Age: Edad del cliente.
  • Employment Type: El sector en el que está empleada el cliente.
  • Graduate or Not: Si el cliente es graduado universitario o no.
  • Annual Income: El ingreso anual del cliente en rupias indias.
  • Family Members: Número de miembros en la familia del cliente.
  • Chronic Disease: Si el cliente sufre de alguna enfermedad o condición importante como diabetes, presión arterial alta o asma, etc.
  • Frequent Flyer: Indica si el cliente ha realizado al menos cuatro viajes a diferentes locaciones en los últimos dos años (2017-2019).
  • Ever Travelled Abroad: Indica si el cliente ha realizado alguna vez un viaje a un país extranjero.
  • Travel Insurance: ¿El cliente compró un paquete de seguro de viaje durante la oferta de lanzamiento celebrada en el año 2019?.

Análisis exploratorio

Se realiza el análisis exploratorio en dos partes, una enfocada en visualizar la distribución porcentual de los clientes que aceptaron y los que no aceptaron el seguro a través de las distintas variables categóricas. La otra parte se enfocará en análisis las variables numéricas, realizando un análisis de correlación y de distribución de las variables segmentando por la variable respuesta (aceptación o rechazo del seguro).

Variables categóricas

Parece que conocer si el cliente sufre de alguna enfermedad o condición importante y, además, si es graduado universitario o no, es irrelevante para determinar si estaría interesado en adquirir el paquete de seguro o no. Existe una diferencia notable en conocer si es un viajero frecuente y si ha realizado viajes al extranjero para la adquisición de este paquete.

Variables numéricas

Para poder visualizar mejor el ingreso anual de los clientes se realizó una transformación logarítmica para estabilizar la varianza.

Se observa que las personas que aceptaron el seguro tienen un mayor ingreso anual con respecto a los que lo rechazaron, además, parece que las personas que rechazan el seguro en su mayoría son jóvenes. No se observa algún aspecto relevante sobre conocer la cantidad de miembros en la familia del cliente.

Verificar desbalanceo de clases

Normalmente en problemas de clasificación es común encontrarse con datos desbalanceadas, esto sucede cuando la mayoría de los datos se concentran en una sola de las dos clases que se desean estudia. A continuación, se verifica la proporción de personas que si aceptaron el seguro y de las que no lo aceptaron.

travel_insurance n prop
Yes 710 0.357
No 1277 0.643

Parece que alrededor del 35% en los datos acepto el seguro, se puede considerar este porcentaje como un desbalanceo leve.

Partición de los datos

Entrenamiento y prueba

Realizar modelos de aprendizaje supervisado haciendo uso de todos los datos disponibles no suele ser una buena opción debido a que esto suele causar problemas de “sobre ajuste”. Es mejor particionar los datos en dos conjuntos, una para entrenar los modelos y otro para probar o testear los modelos, esto nos permitirá saber el desempeño de los modelos en datos nuevos.

La partición se realizó mediante un muestreo estratificado debido a que los datos están levemente desbalanceados y presentan mayor cantidad de personas que rechazaron el seguro, este enfoque nos permitirá mantener el mismo porcentaje de aceptación en las distintas particiones.

# Partición 80% / 20%
set.seed(1234)
split <- initial_split(data = dataset, prop = 0.80, strata = travel_insurance)

# Datos de entrenamiento
train_set <- training(split)

train_set %>% 
  count(travel_insurance) %>% 
  mutate(prop = round(n / sum(n), 3)) %>% 
  gt()
travel_insurance n prop
Yes 568 0.357
No 1021 0.643
# Datos de prueba
test_set <- testing(split)

test_set %>% 
  count(travel_insurance) %>% 
  mutate(prop = round(n / sum(n), 3)) %>% 
  gt()
travel_insurance n prop
Yes 142 0.357
No 256 0.643

Validación cruzada

Aparte de particionar los datos en conjuntos de entrenamiento y prueba se particionara el conjunto de entrenamiento en 5 subconjuntos de datos, esto permite tener un mejor entendimiento del comportamiento de los modelos.

El proceso es ajustar los modelos en uno de los subconjuntos y utilizar los otros cuatros para computar métricas de rendimiento, este proceso se repite hasta que todos los subconjuntos sean utilizados para ajustar los modelos. Por lo general, luego se toma la media de las métricas de rendimiento de este proceso.

set.seed(1234)
folds <- vfold_cv(train_set, strata = travel_insurance, v = 5)

#folds

Ingeniería de variables

En base a lo observado en el análisis exploratorio se creará un récipe con el pre procesamiento haciendo uso de ingeniería de variables para mejorar el desempeño de los algoritmos de Machine Learning.

Récipe de preprocesamiento

Debido a que los datos en estudios se encuentran desbalanceados se realizan tres récipes con distintos enfoques:

  • El primero es un recibe básico que incluye la transformación logarítmica del ingreso anual del cliente, la conversión de las variables categóricas a Dummy y normalizar todas las variables numéricas.
  • El segundo récipe incluirá los pasos anteriores, pero, además, contempla un enfoque de sobremuestreo en la variable respuesta. Este enfoque aumenta las observaciones de la clase minoritaria hasta que tanto la clase minoritaria como la mayoritaria se distribuyan porcentualmente igual en los datos.
  • Por último, el tercer récipe contempla un enfoque de sub muestreo en la variable respuesta. Este enfoque elimina las observaciones de la clase mayoritaria hasta que tanto la clase minoritaria como la mayoritaria se distribuyan porcentualmente igual en los datos.
# récipe sin muestro
recipe <- recipe(travel_insurance ~ ., data = train_set) %>% 
  step_log(annual_income) %>% 
  step_normalize(all_numeric_predictors()) %>% 
  step_dummy(all_nominal_predictors())

# récipe con sobremuestro (upsampling)
recipe_up <- recipe %>% 
  step_upsample(travel_insurance, seed = 123)

# récipe con sub muestreo (downsampling)
recipe_down <- recipe %>% 
  step_downsample(travel_insurance, seed = 123)

Usando las funciones juice y prep se puede apreciar el resultado del pre procesamiento de los datos de entrenamiento.

# Resultado de preprocesamiento
juice(prep(recipe_down)) %>% 
  head() %>% 
  glimpse()
## Rows: 6
## Columns: 9
## $ age                                          <dbl> 0.4926244, -1.2368942, 0.~
## $ annual_income                                <dbl> 1.0195286, 1.0974505, 0.0~
## $ family_members                               <dbl> -1.0908980, 0.1512639, 0.~
## $ travel_insurance                             <fct> Yes, Yes, Yes, Yes, Yes, ~
## $ employment_type_Private.Sector.Self.Employed <dbl> 1, 1, 0, 0, 1, 1
## $ graduate_or_not_No                           <dbl> 0, 0, 0, 0, 0, 0
## $ chronic_diseases_No                          <dbl> 1, 1, 1, 1, 1, 0
## $ frequent_flyer_No                            <dbl> 0, 0, 1, 0, 1, 1
## $ ever_travelled_abroad_No                     <dbl> 0, 0, 1, 0, 1, 1

Rendimiendo en validación cruzada

A continuación, se procede a realizar el entrenamiento de los modelos en la validación cruzada.

Modelos a utilizar:

# Bosque Aleatorio
rf_spec <- rand_forest(trees = 1000) %>% 
  set_engine("ranger", num.threads = 7, importance = "impurity") %>% 
  set_mode("classification")

# Regresión logística
glm_spec <- logistic_reg() %>% 
  set_engine("glm")

# XGboost
xg_spec <- boost_tree(trees = 1000) %>% 
  set_engine("xgboost") %>% 
  set_mode("classification")

Se crea un objeto de tipo “workflow_set” para establecer todos los récipes y modelos a utilizar para evaluar cual otorga un mejor rendimiento. Para medir el rendimiento de los modelos se computarán varias métricas como lo son la Tasa de Verdaderos Positivos, Tasa de Verdaderos Negativos, métrica F1 (promedio armónico entre la TVP y TVN) y la Curva ROC AUC.

Para datos desbalanceados es recomendable usar la métrica F1 / F2.

metricas <- metric_set(f_meas, roc_auc, sensitivity, specificity)

modelos <- workflow_set(
  preproc = list(
    simple = recipe, up = recipe_up, down = recipe_down), 
  models = list(
    RandomF = rf_spec, LogisticGLM = glm_spec, Xgboost = xg_spec),     
  cross = TRUE
  )

#modelos

A continuación, se procede con el ajuste de todos los modelos con los distintos récipes. Se utiliza computación paralela para acelerar el proceso.

doParallel::registerDoParallel()

fit_models <- modelos %>% 
  workflow_map(
    fn = "fit_resamples", resamples = folds, 
    metrics = metricas, verbose = TRUE, seed = 123
  )

#fit_models

Resultados

Se visualiza resultado de modelos en la validación cruzada.

El modelo Random Forest otorga los mejores resultados en la validación cruzada para todas las métricas, seguido por XGBoost y, por último, la regresión logística. Para acceder a cuál récipe genera el mejor resultado podemos aplicar la función rank_results bajo la métrica de interés.

modelo récipe rank f_meas roc_auc sensitivity specificity
RandomF up 1 0.7176755 0.8027453 0.6303835 0.9304352
RandomF simple 2 0.7164499 0.8030278 0.5899084 0.9696365
RandomF down 3 0.6955398 0.7985249 0.6198261 0.9098852
Xgboost simple 4 0.6615582 0.7858983 0.6426797 0.8334912
Xgboost up 5 0.6571607 0.7776775 0.6532992 0.8148733
Xgboost down 6 0.6352617 0.7795293 0.6990219 0.7218269
LogisticGLM down 7 0.6327207 0.7675544 0.6374476 0.7894500
LogisticGLM up 8 0.6186032 0.7644032 0.6021736 0.8080488
LogisticGLM simple 9 0.5869641 0.7677944 0.4735755 0.9226399

El modelo Random Forest junto con el récipe de procesamiento que contempla un enfoque de sobremuestreo nos otorga los resultados más parsimonios en la validación cruzada, por ende, sería buena idea proseguir con este modelo como el definitivo.

Rendimiendo en partición de prueba

A continuación, se realiza el último ajuste esta vez a los datos de pruebas para determinar la capacidad predictiva del modelo seleccionado.

# Modelo y récipe definitivo
comp_fitted <- last_fit(
  workflow(recipe_up, rf_spec), 
  split, 
  metrics = metricas
  )

Métricas de rendimiento:

.metric .estimate
f_meas 0.6857143
sensitivity 0.5915493
specificity 0.9257812
roc_auc 0.8100105

Matriz de confusión:

Parece que el modelo hace un buen trabajo en predecir que clientes no estarán interesadas en adquirir el paquete que ofrece la compañía.