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.
En total hay 9 columnas en los datos, las cuáles se describen a continuación:
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).
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.
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.
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.
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 |
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
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.
Debido a que los datos en estudios se encuentran desbalanceados se realizan tres récipes con distintos enfoques:
# 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
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
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.
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.