Librerias base a utilizar
library(tidyverse)
## Warning: package 'ggplot2' was built under R version 4.4.3
## Warning: package 'tibble' was built under R version 4.4.3
## Warning: package 'purrr' was built under R version 4.4.3
## Warning: package 'lubridate' was built under R version 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.2 ✔ tibble 3.3.0
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.4
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidymodels)
## Warning: package 'tidymodels' was built under R version 4.4.3
## ── Attaching packages ────────────────────────────────────── tidymodels 1.3.0 ──
## ✔ broom 1.0.8 ✔ rsample 1.3.0
## ✔ dials 1.4.0 ✔ tune 1.3.0
## ✔ infer 1.0.9 ✔ workflows 1.2.0
## ✔ modeldata 1.4.0 ✔ workflowsets 1.1.1
## ✔ parsnip 1.3.2 ✔ yardstick 1.3.2
## ✔ recipes 1.3.1
## Warning: package 'broom' was built under R version 4.4.3
## Warning: package 'dials' was built under R version 4.4.3
## Warning: package 'scales' was built under R version 4.4.3
## Warning: package 'infer' was built under R version 4.4.3
## Warning: package 'modeldata' was built under R version 4.4.3
## Warning: package 'parsnip' was built under R version 4.4.3
## Warning: package 'recipes' was built under R version 4.4.3
## Warning: package 'rsample' was built under R version 4.4.3
## Warning: package 'tune' was built under R version 4.4.3
## Warning: package 'workflows' was built under R version 4.4.3
## Warning: package 'workflowsets' was built under R version 4.4.3
## Warning: package 'yardstick' was built under R version 4.4.3
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## ✖ scales::discard() masks purrr::discard()
## ✖ dplyr::filter() masks stats::filter()
## ✖ recipes::fixed() masks stringr::fixed()
## ✖ dplyr::lag() masks stats::lag()
## ✖ yardstick::spec() masks readr::spec()
## ✖ recipes::step() masks stats::step()
Se llama a la base de datos con la que se trabajara el proyecto de knn.
clientes_segmentacion <- read_delim("clientes segmentacion.csv",
delim = ";", escape_double = FALSE, col_types = cols(Age = col_integer(),
`Annual_Income_(k$)` = col_number(), `Spending_Score)` = col_number()), trim_ws = TRUE)
Se verifica las dimensiones del data frame clientes_segmentación
dim(clientes_segmentacion)
## [1] 200 5
Se tiene 200 filas y 5 columnas (variables), verificar el tipo de variables
glimpse(clientes_segmentacion)
## Rows: 200
## Columns: 5
## $ CustomerID <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15…
## $ Genre <chr> "Male", "Male", "Female", "Female", "Female", "Fe…
## $ Age <int> 19, 21, 20, 23, 31, 22, 35, 23, 64, 30, 67, 35, 5…
## $ `Annual_Income_(k$)` <dbl> 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 19, 2…
## $ `Spending_Score)` <dbl> 39, 81, 6, 77, 40, 76, 6, 94, 3, 72, 14, 99, 15, …
CustomerID Tipo: dbl (numérico/decimal). Descripción: Identificador único del cliente. Genre Tipo: chr (cadena de texto/categórica). Descripción: Género del individuo con dos categorías: “Male” (masculino) y “Female” (femenino). Age Tipo: int (entero). Descripción: Edad del individuo en años. Annual_Income_(k$) Tipo: dbl (numérico/decimal). Descripción: Ingreso anual del individuo en miles de dólares (ejemplo: 15 = 15,000 USD). Correción del nombre de la columan
names(clientes_segmentacion)[4] <- "Annual_Income_USD_k"
Spending_Score) Tipo: dbl (numérico/decimal). Descripción: Puntuación de gasto del individuo (normalmente escalada entre 0-100, donde valores altos indican mayor gasto). se corrige el nombre la variable
names(clientes_segmentacion)[5]<-"Spending_Score"
Verificando si existen NAs
colSums(is.na(clientes_segmentacion))
## CustomerID Genre Age Annual_Income_USD_k
## 0 0 0 0
## Spending_Score
## 0
No existen NAs en el data frame. se procede a verificar si existen duplicados
sum(duplicated(clientes_segmentacion))
## [1] 0
La única salida es cero, no tenemos filas dulicadas. Análisis descriptivo de la variable Genre (genero)
clientes_segmentacion%>%select(Genre)%>%ggplot(aes(Genre,fill=Genre))+
geom_bar()+geom_text(aes(label = after_stat(count)),stat = "count",vjust=-0.1,
size = 5,color="black")+
labs(title = "Distribución del Genero",x="Genero",y="número de clientes")+
scale_fill_manual(values = c("Male" = "#1f77b4", "Female" = "#ff7f0e"))+theme_minimal()
En la muestra se tiene 112 Mujeres (female), lo que representa el 56% y
88 varones (male), el cual representa el 44% de la muestra. Analisis
descriptivo de las variable Age (edad)
summary(clientes_segmentacion$Age)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 18.00 28.75 36.00 38.85 49.00 70.00
la variable Age (edad), tiene como minino 18, este valor indica la edad en la cual los individuos empiezan su vida financiera, un maximo de 70, personas probablemente jubiladas, con relación al sesgo, este es positvo, la media cuyo es de 38.85 es mayor que la median 36, la población de estudio es mayoritariamente adulto joven, esto se comprueba con los cuartiles, el rango intercurtilico es de 20.25, el primer cuartil 28.75 indica que el 25% de los individuos tiene una edad menor a 29 años, en cambio el cuartil 3 es de 49, indica que el 75% de las individuos es menor a 49 años, por ende, el 50% de la muestra es una población adulta joven.
library(gridExtra)
##
## Adjuntando el paquete: 'gridExtra'
## The following object is masked from 'package:dplyr':
##
## combine
clientes<-clientes_segmentacion
boxplot_age<-ggplot(clientes, aes(y = Age)) +
geom_boxplot(fill = "#4E79A7", color = "black", alpha = 0.7, outlier.color = "red") +
labs(title = "Distribución de Edades - Boxplot",
y = "Edad (años)") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank()) +
scale_y_continuous(limits = c(10, 80), breaks = seq(10, 80, by = 10))
#histograma
hist_age<-ggplot(clientes, aes(x = Age)) +
geom_histogram(aes(y = ..density..),
bins = 20,
fill = "#E15759",
color = "white",
alpha = 0.8) +
geom_density(color = "#76B7B2", size = 1.2) +
stat_function(fun = dnorm,
args = list(mean = mean(clientes$Age), sd = sd(clientes$Age)),
color = "black", size = 1, linetype = "dashed") +
labs(title = "Distribución de Edades - Histograma",
x = "Edad (años)",
y = "Densidad") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, face = "bold")) +
annotate("text", x = 60, y = 0.025,
label = sprintf("N(μ = %.1f, σ = %.1f)", mean(clientes$Age), sd(clientes$Age)),
color = "black", size = 4) +
scale_x_continuous(limits = c(10, 80), breaks = seq(10, 80, by = 10))
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# Combinar gráficos
grid.arrange(boxplot_age, hist_age, ncol = 2)
## Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(density)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).
El gráfico de caja, muestra la ausencia de valores atipicos en la
muestra, mientras, el histograma muestra un ligero sesgo hacia la
izquierda, tambien muestra que la varibale edad esta segmentada en
grupos que comprende las edades de 18 a 29, de 30 a 39, de 40 a 49 y
mayores de 50 años. Se construye la variable GrupoEdad.
clientes$GrupoEdad <- c("18-29", "30-39", "40-49", "50+")[
findInterval(clientes$Age, c(-Inf, 30, 40, 50, Inf))]
round(prop.table(table(clientes$Genre,clientes$GrupoEdad),margin = 1)*100,digits = 2)
##
## 18-29 30-39 40-49 50+
## Female 25.89 33.04 21.43 19.64
## Male 29.55 27.27 17.05 26.14
La tabla muestra la distribución del genero en los distinto segmentos de edad, en los segmentos de 18-29 y 50+ se aprecia poca diferencia entre generos, caso contrario en los grupos de edad laborales, se aprecia una predominancia del sexo femenino, esto nos hace pensar que el mercado laboral esta dominado por mujeres y que su edad promedio podria ser mayor.
ggplot(clientes, aes(x = GrupoEdad, fill = Genre)) +
geom_bar(position = "dodge") +
geom_text(
aes(label = after_stat(count)),
stat = "count",
position = position_dodge(width = 0.9),
vjust = -0.5
) +
labs(
title = "Brecha de Género por Grupo de Edad",
x = "Grupo de Edad",
y = "Número de Individuos"
) +
scale_fill_manual(values = c("Female" = "#FF6B6B", "Male" = "#4ECDC4")) +
theme_minimal()
Este Gráfico, contribuye a la interpretación anterior, donde existe
predominancia del sexo femenino en el mercado laboral.
clientes%>%group_by(Genre)%>%summarise(Promedio_edad=mean(Age),Mediana_edad=median(Age),
Desviacion_estandar=sd(Age))
Analisis descriptivo de la variable ingreso Anual (Annual_Income_USD_k)
summary(clientes$Annual_Income_USD_k)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 15.00 41.50 61.50 60.56 78.00 137.00
La Variable ingreso anual. Estadístico Valor (k$) Interpretación Mínimo 15 Ingreso anual mínimo: $15,000 USD ($1,250/mes). 1er Cuartil (Q1) 41.5 25% gana ≤ $41,500 USD ($3,458/mes). Mediana 61.5 50% gana ≤ $61,500 USD ($5,125/mes). Media 60.56 Similar a la mediana sugiera una distribución simétrica. 3er Cuartil (Q3) 78 75% gana ≤ $78,000 USD ($6,500/mes). Máximo 137 Ingreso anual máximo: $137,000 USD ($11,417/mes). Brecha significativa: Ratio máximo/mínimo = 9.13x (137/15), indicando alta desigualdad económica.
ggplot(clientes, aes(x =Annual_Income_USD_k)) +
geom_histogram(aes(y = ..density..), bins = 15, fill = "#69b3a2", alpha = 0.7,col="black") +
geom_density(color = "#e9c46a", linewidth = 1) +
geom_vline(xintercept = c(41.5, 61.5, 78), linetype = "dashed", color = "red") +
labs(title = "Distribucion del Ingreso Anual", x = "Ingreso Anual (miles de USD)", y = "Densidad") +
theme_minimal()
El histograma muestra una distibución unimodal (con un solo pico),
alrededir de a mdiana, esto indica que la concentración de los datos
esta centra, lo que se verifica con la similitud con los valores de la
media y mediana, tambien, hay presencia de valores atipicos, dado que el
objetivo es la segmentacion de clientes, estos valores atipicos no seran
removidos debido que son valores reales y no derivados de errore de
tipeo. En base al histograma, se puede codificar la variable ingreso en,
Bajo, Media_Bajo, Medio-ALto, Alto
clientes <- clientes%>%
mutate(income_group = cut(Annual_Income_USD_k,
breaks = c(14, 30, 50, 80, 137),
labels = c("Bajo", "Medio-Bajo", "Medio-Alto", "Alto")))
table(clientes$GrupoEdad,clientes$income_group)
##
## Bajo Medio-Bajo Medio-Alto Alto
## 18-29 13 11 26 5
## 30-39 10 7 23 21
## 40-49 3 8 21 7
## 50+ 6 16 18 5
La tabla revela que los adultos de 30-39 años concentran los mayores ingresos (21 en categoría “Alto”), destacándose como el grupo con mayor poder adquisitivo. Los jóvenes (18-29) y mayores (50+) predominan en ingresos bajos y medios-bajos, sugiriendo menor capacidad económica. La categoría “Medio-Alto” es dominante en todos los grupos, excepto en mayores de 50 años, donde los ingresos parecen reducirse. Esto indica que la etapa de 30-39 años es clave para estrategias premium, mientras que los extremos etarios requieren enfoques accesibles. La segmentación por edad e ingreso permite personalizar ofertas y anticipar comportamientos de consumo. Analisis descritivo de la variable Spending_Score
summary(clientes$Spending_Score)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1.00 34.75 50.00 50.20 73.00 99.00
El análisis del Spending_Score revela tres patrones de consumo claros: el 25% inferior (≤34.75) muestra bajo gasto, el 50% central (35-73) un gasto moderado, y el 25% superior (>73) alto gasto. La simetría (media=50.2 ≈ mediana=50) sugiere una distribución equilibrada, pero el amplio rango (1-99) destaca diferencias extremas: desde consumidores muy conservadores hasta altamente dispuestos a gastar. Relación del Spending Score y el Ingreso anual
ggplot(clientes, aes(x =Annual_Income_USD_k, y = Spending_Score)) +
geom_point(alpha = 0.5,col="blue") +
labs(title = "Relacion Ingresos vs Spending Score")
El gráfico de dispersión entre Annual_Income_USD_k (Ingreso Anual) y
Spending_Score (Puntuación de Gasto) revela una clara segmentación en
3-4 grupos naturales, lo que sugiere distintos perfiles de
comportamiento del consumidor.
ggplot(clientes, aes(x = Annual_Income_USD_k, y = Spending_Score)) +
geom_point(color = "#4CAF50", alpha = 0.6, size = 3) +
labs(
title = "Segmentacion Natural: Ingresos vs. Gasto",
x = "Ingreso Anual (miles de USD)",
y = "Puntuacion de Gasto"
) +
theme_minimal() +
geom_vline(xintercept = c(50, 80), linetype = "dashed", color = "blue") +
geom_hline(yintercept = c(40, 70), linetype = "dashed", color = "black")
como ya se sugiero anteriormente hay varios grupos o cluster naturales,
el lado negativo es la cantidad de cluster dificultad su interpretación.
Una forma de definir la cantidad idonea de cluster es a tráves del
metodo del codo, primero se selecionan las variables a utilizar y su
normalización.
#receta
receta_cluster <- recipe(~ Annual_Income_USD_k+ Spending_Score,
data = clientes) %>%
step_normalize(all_numeric())%>%prep()
#Preprocesar los datos
datos_preprocesados <-juice(receta_cluster)
library(cluster)
library(factoextra)
## Warning: package 'factoextra' was built under R version 4.4.3
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
fviz_nbclust(datos_preprocesados ,kmeans, method = "wss") +
geom_vline(xintercept = 3, linetype = 2)
El grafico es un poco ambiguo, no define exactamente el numero de
cluster, ya que bien podria estar entre 3 y 6.
plots <- map(3:6, ~{
km <- kmeans(datos_preprocesados, centers = .x)
fviz_cluster(km, data = datos_preprocesados,
main = paste("k =", .x))
})
grid.arrange(grobs = plots, ncol= 2)
En el primer grafico se observa que co el cluster k=3. no hay
solapamiento entre los grupos, por ende se utilizara k=3.
km_tidy <- kmeans(datos_preprocesados, centers = 3)
clientes$cluster<-as.factor(km_tidy$cluster)
Caracterización de clusters
head(clientes[,5:8])
clientes%>%
group_by(cluster) %>%
summarise(
promedio_ingreso = mean(Annual_Income_USD_k),
promedio_spending= mean(Spending_Score),
Frecuencia = n()
)
El análisis de clusters revela tres perfiles clave de clientes: 1) Conservadores de Bajos Ingresos (ingreso promedio $28k, gasto $23.3), compuesto por 26 personas con bajo consumo, ideal para ofertas económicas; 2) Gastadores Activos de Ingresos Medios (ingreso $58.3k, gasto $63.9), el grupo más grande (134 clientes) y principal target para estrategias de fidelización y productos premium; y 3) Ahorradores de Altos Ingresos (ingreso $89.2k, gasto $21.6), 40 clientes con alto poder adquisitivo pero bajo gasto, clave para servicios de inversión. Esta segmentación permite personalizar campañas según el comportamiento financiero de cada grupo, maximizando su potencial de conversión. La disparidad entre ingresos y gastos destaca la importancia de estrategias diferenciadas para cada perfil.
clientes <- clientes %>%
mutate(segmento = case_when(
cluster == 1 ~ "Conservadores",
cluster == 2 ~ "Consumidores",
cluster == 3 ~ "Ahorradores"
))
table(clientes$GrupoEdad,clientes$segmento)
##
## Ahorradores Conservadores Consumidores
## 18-29 7 3 45
## 30-39 11 7 43
## 40-49 14 8 17
## 50+ 8 8 29
library(ggrepel)
## Warning: package 'ggrepel' was built under R version 4.4.3
# Datos para etiquetas
etiquetas <- clientes %>%
group_by(segmento) %>%
summarise(
x = mean(Annual_Income_USD_k),
y = mean(Spending_Score)
)
# Gráfico
ggplot() +
# Puntos con todos los datos
geom_point(
data = clientes,
aes(x = Annual_Income_USD_k, y = Spending_Score,
color = segmento, size = Age),
alpha = 0.6
) +
# Etiquetas
geom_label_repel(
data = etiquetas,
aes(x = x, y = y, label = segmento),
color = "black"
) +
labs(title = "Grafico de las distribucion de los cluster") +
theme_minimal()
El análisis combinado de clusters y grupos de edad revela tres perfiles
financieros clave con comportamientos diferenciados por etapa de vida:
Conservadores (Cluster 1): Representan solo 26 clientes (5.2% del
total), concentrados principalmente en jóvenes de 18-29 años (7 casos) y
adultos de 40-49 (14 casos), con ingresos bajos ($28k) y gasto mínimo
($23.3), ideal para productos básicos y educación financiera.
Consumidores (Cluster 2): El grupo más numeroso (134 clientes, 67%)
domina en las edades 18-39 años (88 casos combinados), con ingresos
medios ($58k) y alto gasto ($64), siendo el target principal para
créditos, promociones y productos de consumo frecuente. Ahorradores
(Cluster 3): 40 clientes (20%) destacan en mayores de 40 años (22
casos), con ingresos altos ($89k) pero bajo gasto ($22), requiriendo
estrategias de inversión y servicios patrimoniales. La segmentación por
edad refuerza estos patrones: los jóvenes (18-39 años) son
predominantemente consumidores (88 casos), mientras los mayores de 40
muestran mayor equilibrio entre ahorro (22) y consumo (46). Esta
dualidad sugiere que las estrategias comerciales deben combinar impulsos
al gasto para jóvenes con opciones de acumulación de patrimonio para
adultos maduros. La presencia de conservadores en todos los grupos
etarios (3-14 casos) indica un nicho constante de clientes
ultra-prudentes que valoran seguridad sobre rentabilidad.
Analisis de clasificación. En el ámbito del aprendizaje automático, las técnicas de clasificación supervisada son fundamentales cuando el objetivo es predecir la pertenencia de una observación a una categoría o grupo predefinido. Esto se logra a partir de un conjunto de datos donde las etiquetas de los grupos ya son conocidas. Entre los algoritmos más robustos y ampliamente utilizados para estas tareas de clasificación se encuentran las Máquinas de Vectores de Soporte (SVM), los Bosques Aleatorios (Random Forest) y la Regresión Logística Multinomial. Cada uno de estos modelos posee características distintivas, ofreciendo ventajas y desventajas específicas que los hacen más o menos adecuados dependiendo de la naturaleza del problema y los datos.
Comparación de Modelos Supervisados (SVM, Random Forest y Regresión Logística Multinomial): A continuación, se presenta una comparación de los tres modelos supervisados, destacando sus características clave, ventajas y desventajas:
Ventajas: Efectividad en Espacios de Alta Dimensión: Son muy efectivas en espacios con un alto número de dimensiones. Manejo de Casos No Lineales: Gracias a las funciones de kernel, pueden modelar relaciones complejas y no lineales entre las características. Robustez a Overfitting (con margen amplio): Si se elige un buen margen de separación, pueden generalizar bien. Menos Sensibles a Outliers: Los vectores de soporte son los únicos que influyen en la definición del hiperplano, lo que las hace menos sensibles a puntos alejados.
Desventajas: Sensibilidad a la Elección del Kernel y Parámetros: El rendimiento depende en gran medida de la correcta elección del kernel y el ajuste de sus parámetros (e.g., C, gamma). Lento para Grandes Conjuntos de Datos: El tiempo de entrenamiento puede ser considerablemente alto con grandes volúmenes de datos. Dificultad de Interpretación: El “por qué” de una clasificación puede ser difícil de interpretar, especialmente con kernels complejos. Problemas con Datos Ruidosos: Si el conjunto de datos es muy ruidoso y con clases superpuestas, el rendimiento puede ser bajo.
Ventajas:
Alta Precisión y Robustez: Generalmente ofrece una alta precisión y es menos propenso al overfitting que un solo árbol de decisión. Manejo de Datos Faltantes: Puede manejar datos faltantes sin imputación explícita (aunque esto depende de la implementación). Selección de Características Implícita: Proporciona una medida de la importancia de las características, lo que ayuda en la selección. Menos Sensible a Escala de Características: No requiere escalado de características. Versátil: Adecuado tanto para tareas de clasificación como de regresión.
Desventajas: Menos Interpretable que un Árbol Individual: Aunque sus componentes (árboles) son interpretables, el conjunto completo (bosque) es más como una “caja negra”. Computacionalmente Intensivo: Puede ser costoso computacionalmente, tanto en tiempo de entrenamiento como en memoria, para un gran número de árboles o características. Puede Ser Lento en Producción: La predicción puede ser más lenta que con modelos más simples debido a la necesidad de evaluar múltiples árboles.
Ventajas: Simple y Fácil de Interpretar: Los coeficientes del modelo pueden interpretarse como el cambio en el log-odds de una categoría con respecto a una categoría de referencia. Rápido y Eficiente: Es computacionalmente eficiente y rápido de entrenar, incluso con grandes conjuntos de datos. Proporciona Probabilidades: Genera probabilidades de pertenencia a cada clase, lo cual es útil para la toma de decisiones. Buen Punto de Partida: A menudo sirve como un buen modelo de referencia (baseline) debido a su simplicidad.
Desventajas: Asunción de Linealidad: Asume una relación lineal entre las variables predictoras y el log-odds de la variable de respuesta, lo que puede limitar su rendimiento en relaciones complejas. Sensibilidad a Outliers y Multicolinealidad: Puede ser sensible a valores atípicos y a la multicolinealidad entre las variables predictoras. No Maneja Relaciones Complejas: Su rendimiento puede ser inferior en problemas donde las fronteras de decisión son altamente no lineales. Requiere Preprocesamiento: A menudo requiere preprocesamiento de datos (escalado de características, manejo de valores atípicos, etc.) para un rendimiento óptimo.
Selección de variables. Tenemos 3 variables numéricas, se hara un analisis con el fin de determinar si las 3 variables discriminan de manera eficiente a los distintos segmentos. Variable Edad
ggplot(clientes, aes(x = Age, fill = segmento, color = segmento))+geom_density(alpha = 0.5) +labs(title = "Distribución de la Edad por Segmento (Densidad)",x = "Edad")+theme_minimal()
En el gráfico de densidad se observa que las distribuciones estan
solapadas, esto indica que la variable Age (edad) no discrimina de
manera efectiva a los segmetos. Variables Ingreso Anual
ggplot(clientes, aes(x = Annual_Income_USD_k, fill = segmento, color = segmento))+geom_density(alpha = 0.5) +labs(title = "Distribución Ingreso Anual por Segmento (Densidad)",x = "Ingreso Anual")+theme_minimal()
Este gráfico mustra como los diferentes colores (segmentos) tienden a
concentrarse en rangos distintos de ingreso anual, esto significa que
Annual_Income_USD_k es una variable que discrimina bien entre las
clases. Variable Spending Score
ggplot(clientes,aes(Spending_Score,fill = segmento,col=segmento))+geom_density(alpha=0.6)+
labs(title = "Distribucion del Spending Score por segmento",x="Spending_Score")+theme_minimal()
Al igual que variable Ingreso Anual, este grafico muestra como los colores tienden agruparse, esto indica que la variable Spending_Score discimina de manera eficiente a los distintos segmentos, cabe resaltar que si se compara con Ingreso Anual, esta ultima discrimina de manera mas eficiente. Division del Data Frame se seleccionan las variables a utilizar en nuestro analisis, el nuevo data frame se llamara cliente_comparacion, este tendra las variables Ingreso anual (Annual_Income_USD_k),el Spending_Score, Segmento, la variable segmento se convertira a factor.
clientes_comparacion<-clientes%>%select(Annual_Income_USD_k,Spending_Score,segmento)%>%
mutate(segmento=as.factor(segmento))
Se divida el data set en dos, una para entrenar al modelo (Entrenamiento) con el 60% de las observaciones, y otro para probar el modelo (Prueba) con el 40% de las observaciones, el muestreo se hara por estratificacion con la variable segmento.
set.seed(123)
division<-initial_split(clientes_comparacion,prop = 0.6,strata = segmento)
Se extrae el data set para Entrenamiento con training, para prueba con testing.
Entrenamiento<-training(division)
prueba<-testing(division)
Suport vector Machine
library(e1071)
## Warning: package 'e1071' was built under R version 4.4.1
##
## Adjuntando el paquete: 'e1071'
## The following object is masked from 'package:tune':
##
## tune
## The following object is masked from 'package:rsample':
##
## permutations
## The following object is masked from 'package:parsnip':
##
## tune
library(doParallel)
## Warning: package 'doParallel' was built under R version 4.4.3
## Cargando paquete requerido: foreach
## Warning: package 'foreach' was built under R version 4.4.1
##
## Adjuntando el paquete: 'foreach'
## The following objects are masked from 'package:purrr':
##
## accumulate, when
## Cargando paquete requerido: iterators
## Warning: package 'iterators' was built under R version 4.4.1
## Cargando paquete requerido: parallel
Configuracion del paralelo para optimizar recurso computacional
all_cores <- parallel::detectCores(logical = FALSE)
registerDoParallel(cores = all_cores)
Definición del Resampling (Validación Cruzada)
datos_fold <- vfold_cv(Entrenamiento, v = 10, strata = segmento)
Creación de la Receta (Recipe): Aquí se define los pasos de preprocesamiento que se aplicarán a tus datos. Para SVM.
receta <-recipe(segmento ~ ., data = Entrenamiento) %>%step_normalize(all_numeric_predictors())
Especificación del Modelo SVM
svm_especificacion <-svm_rbf(cost = tune(), rbf_sigma = tune())%>% set_mode("classification") %>%
set_engine("kernlab")
Creación del workflow
svm_workflow <-workflow()%>%add_recipe(receta)%>%add_model(svm_especificacion)
Ajuste de los hiperparametro.
library(kernlab)
## Warning: package 'kernlab' was built under R version 4.4.1
##
## Adjuntando el paquete: 'kernlab'
## The following object is masked from 'package:dials':
##
## buffer
## The following object is masked from 'package:scales':
##
## alpha
## The following object is masked from 'package:purrr':
##
## cross
## The following object is masked from 'package:ggplot2':
##
## alpha
svm_grid <- grid_regular(cost(),rbf_sigma(), levels = c(cost = 6,rbf_sigma = 6))
set.seed(123)
svm_tune_results <-
tune_grid(
svm_workflow,
resamples = datos_fold,
grid = svm_grid,
metrics = metric_set(roc_auc, accuracy, sens,yardstick::spec),
control = control_grid(save_pred = TRUE, verbose = TRUE))
## Warning: ! tune detected a parallel backend registered with foreach but no backend
## registered with future.
## ℹ Support for parallel processing with foreach was soft-deprecated in tune
## 1.2.1.
## ℹ See ?parallelism (`?tune::parallelism()`) to learn more.
Evaluación de los resultados del Tuning
show_best(svm_tune_results, metric = "roc_auc")
Seleccion del conjunto final de hiperparametro
best_svm_params <- select_best(svm_tune_results, metric = "roc_auc")
Finalizar el workflow con los mejores hiperparámetros.
final_svm_workflow <-svm_workflow %>%finalize_workflow(best_svm_params)
Entrenamiento del modelo y Evaluacion final. Entrenar el modelo final en todo el conjunto de entrenamiento
final_svm_fit <- fit(final_svm_workflow, data = Entrenamiento)
## maximum number of iterations reached 0.0003540845 -0.0003538131
Hacer predicciones en el conjunto de prueba
svm_predictions <-predict(final_svm_fit, new_data = prueba) %>%bind_cols(predict(final_svm_fit, new_data = prueba, type = "prob")) %>%bind_cols(prueba %>% select(segmento))
Evaluar las metricas en el conjunto prueba
metrics_svm <-svm_predictions %>%metrics(truth = segmento, estimate = .pred_class)
print(metrics_svm)
## # A tibble: 2 × 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 accuracy multiclass 0.667
## 2 kap multiclass 0
Matriz de confusión
conf_mat(svm_predictions, truth = segmento, estimate = .pred_class)
## Truth
## Prediction Ahorradores Conservadores Consumidores
## Ahorradores 0 0 0
## Conservadores 0 0 0
## Consumidores 16 11 54
metricas <- function(actual, predicted) {
cm <- table(actual, predicted)
total <- sum(cm)
# Accuracy (suma diagonal / total)
accuracy <- sum(diag(cm)) / total
# Prevalencia por clase (proporción real de cada clase)
prevalence <- rowSums(cm) / total
# Precision por clase (TP / (TP + FP))
precision <- diag(cm) / colSums(cm)
# Recall (Sensibilidad) por clase (TP / (TP + FN))
recall <- diag(cm) / rowSums(cm)
# F1-score por clase
f1 <- 2 * (precision * recall) / (precision + recall)
specificity <- sapply(1:nrow(cm), function(i) {
tn <- sum(cm[-i, -i])
fp <- sum(cm[-i, i])
tn / (tn + fp)
})
metrics <- data.frame(
Class = colnames(cm),
Precision = precision,
Recall = recall,
Specificity = specificity,
F1 = f1,
Prevalence = prevalence
)
metrics <- rbind(metrics, data.frame(
Class = "Overall",
Precision = NA,
Recall = NA,
Specificity = NA,
F1 = NA,
Prevalence = NA
))
metrics$Accuracy <- c(rep(NA, nrow(cm)), accuracy)
return(metrics)
}
De la matriz de confusión se calculan las siguientes metricas.
metricas(svm_predictions$segmento,svm_predictions$.pred_class)
Si bien es cierto el Acurracy es de 0.609761, es decir el modelo discrimina bien el 60% de las veces, esta metrica podria ser enganosa en este contexto, debido que la clase Consumidires al ser predominante este sesgando al modelo, los clasifica a todos de igual forma.
table(Entrenamiento$segmento)
##
## Ahorradores Conservadores Consumidores
## 24 15 80
En el data frame entrenamiento, el 68% pertenece a la clase consumidores.
table(prueba$segmento)
##
## Ahorradores Conservadores Consumidores
## 16 11 54
De manera similar ocurre con el date frame prueba, para poder solucionar este problema se hara una sobre muestreo con el fin de equilibrar los clases. Para ello se utiliza la libreria themis y la función step_smote().
library(themis)
## Warning: package 'themis' was built under R version 4.4.3
# En la receta, se anexara step_smote(), para equilibrar las clases
receta_nueva<-recipe(segmento ~ ., data=Entrenamiento)%>%step_normalize(all_numeric_predictors()) %>%step_dummy(all_nominal_predictors()) %>%
step_smote(segmento, skip = TRUE)
EL proceso se realiza nuevamente.
svm_rbf_workflow_balanceado <-workflow() %>%add_recipe(receta_nueva)%>%add_model(svm_especificacion)
Se tunea nuevamente los hiperparámetros.
svm_grid <- grid_regular(cost(),rbf_sigma(), levels = c(cost = 6,rbf_sigma = 6))
set.seed(123)
svm_tune_resultado_balanceado<-
tune_grid(
svm_rbf_workflow_balanceado,
resamples = datos_fold,
grid = svm_grid,
metrics = metric_set(roc_auc, accuracy, sens,yardstick::spec),
control = control_grid(save_pred = TRUE, verbose = TRUE))
## Warning: ! tune detected a parallel backend registered with foreach but no backend
## registered with future.
## ℹ Support for parallel processing with foreach was soft-deprecated in tune
## 1.2.1.
## ℹ See ?parallelism (`?tune::parallelism()`) to learn more.
mejores resultado
show_best(svm_tune_resultado_balanceado,metric = "roc_auc")
Selección del mejor hiperparámetro.
mejor_parametro<-select_best(svm_tune_resultado_balanceado,metric = "roc_auc")
final_svm_mejor<-svm_rbf_workflow_balanceado%>%finalize_workflow(mejor_parametro)
Entrenamiento del modelo y predicciones.
final_svm_balanceado_fit<-fit(final_svm_mejor,data=Entrenamiento)
svm_predictions_balanceado<-predict(final_svm_balanceado_fit, new_data = prueba) %>%bind_cols(predict(final_svm_balanceado_fit, new_data = prueba, type = "prob")) %>%bind_cols(prueba %>% select(segmento))
Matriz de confusión.
conf_mat(svm_predictions_balanceado,truth = segmento,estimate = .pred_class)
## Truth
## Prediction Ahorradores Conservadores Consumidores
## Ahorradores 14 0 0
## Conservadores 0 11 0
## Consumidores 2 0 54
Metricas.
metricas(svm_predictions_balanceado$segmento,svm_predictions_balanceado$.pred_class)
La disparidad inicial en las clases afectó gravemente la capacidad del SVM para aprender patrones equilibrados. Al aplicar el balanceo de clases, la capacidad predictiva del SVM mejoró significativamente en todas las métricas. Esta mejora demuestra que el balanceo es crucial para que el SVM maneje efectivamente datos desequilibrados. Así, el preprocesamiento adecuado transformó al SVM en un modelo considerablemente más robusto. Random Forest
library(randomForest)
## Warning: package 'randomForest' was built under R version 4.4.3
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
##
## Adjuntando el paquete: 'randomForest'
## The following object is masked from 'package:gridExtra':
##
## combine
## The following object is masked from 'package:dplyr':
##
## combine
## The following object is masked from 'package:ggplot2':
##
## margin
library(ranger)
## Warning: package 'ranger' was built under R version 4.4.3
##
## Adjuntando el paquete: 'ranger'
## The following object is masked from 'package:randomForest':
##
## importance
Especificacion del modelo Random Forest
rf_spec <-rand_forest(mtry = tune(), trees = tune(), min_n = tune()) %>%
set_mode("classification")%>%set_engine("ranger")
Explicación de los hiperparámetros:
mtry: El número de variables seleccionadas aleatoriamente en cada división del árbol. Para clasificación, un buen punto de partida es la raíz cuadrada del número total de predictores. tune() lo explorará.
trees: El número de árboles en el bosque. Un número mayor generalmente mejora el rendimiento pero aumenta el tiempo de cómputo. tune() lo explorará.
min_n: El número mínimo de puntos de datos que deben existir en un nodo para que se intente una división. Controla la profundidad de los árboles y puede ayudar a prevenir el sobreajuste. Creacion del workflow
rf_workflow <-workflow() %>%add_recipe(receta) %>% add_model(rf_spec)
Ajuste de hiperparametros
rf_grid <- grid_regular(
mtry(range = c(2, ncol(Entrenamiento) - 1)), # Rango de mtry, ajusta según el número real de predictores
trees(range = c(500, 1500)), # Rango de número de árboles
min_n(range = c(2, 40)), # Rango del tamaño mínimo del nodo
levels = 5 # Genera 5 niveles para cada hiperparámetro
)
# Ajustar los hiperparámetros usando tune_grid
set.seed(123)
rf_tune_results <-
tune_grid(
rf_workflow,
resamples = datos_fold,
grid = rf_grid,
metrics = metric_set(roc_auc, accuracy, sens, yardstick::spec),
control = control_grid(save_pred = TRUE, verbose = TRUE)
)
## Warning: ! tune detected a parallel backend registered with foreach but no backend
## registered with future.
## ℹ Support for parallel processing with foreach was soft-deprecated in tune
## 1.2.1.
## ℹ See ?parallelism (`?tune::parallelism()`) to learn more.
Evaluacion de los Resultados
# Mostrar las mejores combinaciones de hiperparámetros
show_best(rf_tune_results, metric = "roc_auc")
# Seleccionar el conjunto final de hiperparámetros
best_rf_params <- select_best(rf_tune_results, metric = "roc_auc")
# Finalizar el workflow con los mejores hiperparámetros
final_rf_workflow <-
rf_workflow %>%
finalize_workflow(best_rf_params)
Entrenamiento del modelo
final_rf_fit <- fit(final_rf_workflow, data = Entrenamiento)
Prediccion Usando el modelo
rf_predictions <-
predict(final_rf_fit, new_data = prueba) %>%
bind_cols(predict(final_rf_fit, new_data =prueba, type = "prob")) %>%
bind_cols(prueba %>% select(segmento))
Matriz de Confusion.
conf_mat(rf_predictions, truth = segmento, estimate = .pred_class)
## Truth
## Prediction Ahorradores Conservadores Consumidores
## Ahorradores 16 0 2
## Conservadores 0 11 0
## Consumidores 0 0 52
Metricas para el Random forest.
metricas(rf_predictions$segmento,rf_predictions$.pred_class)
Estas metricas muestras que el modelo de random forest es lo sufucientemente robusto para obtener resultados significativos sin necesidad de realizar un balanceo de clases a como se ha hecho con el SVM. Regresión Multimodal
library(nnet)
## Warning: package 'nnet' was built under R version 4.4.3
creacion de las especificaciones del modelo y workflow
log_reg_spec <-multinom_reg() %>%set_mode("classification") %>%set_engine("nnet")
log_reg_workflow <-
workflow() %>%
add_recipe(receta) %>%
add_model(log_reg_spec)
Entrenamiento del modelo
final_log_reg_fit <- fit(log_reg_workflow, data = Entrenamiento)
Predicciones en el conjunto Prueba
log_reg_predictions <-
predict(final_log_reg_fit, new_data = prueba) %>%
bind_cols(predict(final_log_reg_fit, new_data =prueba, type = "prob")) %>%
bind_cols(prueba %>% select(segmento))
Matriz de Confusion
conf_mat(log_reg_predictions, truth = segmento, estimate = .pred_class)
## Truth
## Prediction Ahorradores Conservadores Consumidores
## Ahorradores 16 0 0
## Conservadores 0 11 0
## Consumidores 0 0 54
Similar a la de Random Forest, esta matriz indica que la regresión logistica multimodal ha clasificado de forma correcta a todas las observaciones. metricas.
metricas(log_reg_predictions$segmento,log_reg_predictions$.pred_class)
Estas metricas muestran que el modelo de regresión logistica multimodal tiene un rendimient excepcional, capaz de clasificar correctamente el total de los datos.
Conclusiones. El objetivo de este análisis ha sido identificar el modelo de clasificación supervisada más adecuado para segmentar clientes, utilizando las características disponibles y una variable segmento previamente definida. Hemos explorado y comparado tres tipos de modelos principales: Máquinas de Vectores de Soporte (SVM) con kernel lineal y RBF, Random Forest, y Regresión Logística Multinomial. Todos los modelos fueron preprocesados consistentemente con una receta que incluyó normalización de predictores numéricos, creación de variables dummy para categóricas, y la eliminación de la variable edad por su demostrada falta de poder discriminatorio.
Análisis de Rendimiento:
Regresión Logística Multinomial: Ha demostrado ser el modelo con el mejor rendimiento en este estudio. Este resultado es notable y sugiere que las fronteras de decisión entre los segmentos de clientes, basadas en características como Annual_Income_USD_k y Spending Score, son en gran medida linealmente separables o adecuadamente aproximables por un modelo lineal. La simplicidad inherente de la Regresión Logística y la interpretabilidad de sus coeficientes la convierten en una opción altamente ventajosa para la aplicación práctica en este contexto de negocio, además de generar probabilidades de predicción bien calibradas.
Random Forest: Este modelo mostró un rendimiento superior inicial al SVM sin balancear, pero fue superado por la Regresión Logística Multinomial. Aunque Random Forest es robusto y excelente para capturar no linealidades, la complejidad adicional de un modelo basado en árboles de conjunto no se tradujo en una ventaja de rendimiento superior sobre la aproximación lineal más simple en este problema específico.
Máquina de Vectores de Soporte (SVM):
Sin Balanceo de Clases: Inicialmente, el rendimiento del SVM (tanto con kernel lineal como RBF) fue catastrófico, con una precisión (accuracy) del 0%. Esto confirmó la hipótesis de que el severo desequilibrio de clases en los datos de entrenamiento impedía al modelo aprender patrones significativos, sesgándolo completamente hacia la clase mayoritaria.
Con Balanceo de Clases (Kernel Lineal y RBF): Tras la aplicación de técnicas de balanceo como SMOTE, la capacidad predictiva del SVM mejoró drásticamente, alcanzando un rendimiento comparable al de Random Forest. Este resultado valida la importancia crítica del preprocesamiento de balanceo para los SVM y demuestra que, una vez abordado el sesgo, pueden encontrar fronteras de decisión efectivas. Sin embargo, el kernel RBF no aportó una ventaja significativa sobre el lineal, sugiriendo que la no linealidad compleja no es clave aquí.
Los hallazgos de este estudio subrayan la importancia crítica del preprocesamiento, especialmente el balanceo de clases, para el éxito de los modelos de clasificación. Si bien el SVM inicialmente fracasó rotundamente debido al desequilibrio, el balanceo lo transformó en un modelo altamente competitivo, similar al Random Forest. No obstante, la Regresión Logística Multinomial se posiciona como el modelo más eficaz y eficiente, demostrando que la naturaleza lineal de las relaciones subyacentes en tus datos de cliente permite una separación clara y precisa de los segmentos, lo cual es invaluable para su interpretabilidad y aplicación práctica.