Unidad 6 - Práctica: los segmentos de Cóndor
Aplicación guiada en clase · Semana 13 · 06327-ECO
Reglas de juego
Hoy nadie tiene la respuesta correcta — ni siquiera yo
Tres semanas prediciendo con hoja de respuestas; hoy se acabaron. Vas a descubrir grupos en los mismos 8.000 clientes de Cóndor, sin target, y a defenderlos con perfilamiento. Y al final los cruzas con lo que construiste en las semanas 11 y 12: el riesgo de irse y el valor en riesgo.
- 👥 En parejas: uno escribe, el otro audita cifras y lee los perfiles en voz alta. Roten en el Momento 4.
- 🖥️ RStudio con tu proyecto
proyecto_condor. Paquetes:dplyr,ggplot2ycluster—clusterviene instalado con R. - 📁 De la semana 12 necesitas
output/valor_en_riesgo.csv. ¿No lo tienes? Corre completos tus scripts de las semanas 10, 11 y 12 antes de empezar. - 🎯 La misión de Marketing y Growth: “Queremos segmentos de clientes con personalidad, para diseñar una acción distinta para cada uno. Y queremos saber en cuál de ellos está la plata en riesgo.”
Cómo funciona cada momento. Una pregunta de analista, lo que necesitas de la teoría en dos líneas (la versión completa, en la teoría de la semana), el código, el checkpoint 🎯 y el hallazgo.
El setup ≈ 5 min
Abre proyecto_condor.Rproj y crea el script scripts/13_segmentos_condor.R:
##============================================================================##
# 13_segmentos_condor.R — ¿qué segmentos hay en Cóndor y dónde está la plata en riesgo?
#------------------------------------------------------------------------------#
# Analítica para los negocios (06327-ECO) · Semana 13 · práctica guiada
# Lee input/base_clientes.csv y, de la semana 12, output/valor_en_riesgo.csv. Produce:
# - output/fig_codo_s13.png (el codo: WCSS según el número de segmentos)
# - output/segmentos_condor.csv (grano: cliente; su segmento)
# - output/perfil_segmentos.csv (grano: segmento; perfil, abandono y valor en riesgo)
# Nombre(s): _______________
##============================================================================##
##============================================================================##
##=== 0. Configuración inicial ===##
##============================================================================##
rm(list = ls())
require(pacman)
p_load(dplyr, ggplot2, cluster)
## la base de Cóndor (grano: cliente) y el valor en riesgo de la semana 12
clientes <- read.csv("input/base_clientes.csv")
valor <- read.csv("output/valor_en_riesgo.csv")
dim(clientes)Checkpoint 🎯: 8000 filas y 29 columnas.
Momento 1 — ¿Con qué variables se segmenta? ≈ 5 min
Lo que necesitas
Segmentar es agrupar sin target: la pregunta decide las variables. k-means agrupa por distancia: cada cliente es un punto y los grupos se arman alrededor de centroides — así que cada variable pesa según su escala.
La pregunta de la teoría —¿qué perfiles de clientes tenemos según frecuencia, monto y recencia?— es la de hoy: entran las tres variables del bloque RFM de la base. Quedan fuera abandono (lo guardamos para después: ¿los segmentos lo separan sin haberlo visto?) y gasto_proximo_trim (del futuro).
##============================================================================##
##=== 1. Las variables del segmento (grano: cliente) ===##
##============================================================================##
## las tres variables RFM: recencia, frecuencia y monto
rfm <- clientes %>%
select(recencia_dias, frecuencia_tx, monto_total)
head(rfm, 3)
## la escala de cada variable
round(sapply(rfm, sd), 1)Checkpoint 🎯: desviaciones de 63.4 días (recencia), 21.0 transacciones (frecuencia) y 623.353.1 pesos (monto).
Hallazgo 1
Una variable no es como las otras: el monto habla en cientos de miles de pesos, la recencia y la frecuencia en decenas. Guarda ese dato: es la bomba del Momento 2.
Momento 2 — ¿Qué pasa si no se escala? ≈ 8 min
Lo que necesitas
kmeans(datos, centers = k, nstart = 25) arma k grupos; nstart = 25 lo arranca 25 veces y se queda con el mejor. La semilla, en la línea anterior, hace que todos obtengan los mismos grupos.
La teoría advierte que escalar es obligatorio. Pero una advertencia no duele — un experimento sí. Corramos k-means sin escalar, como lo haría alguien de afán:
##============================================================================##
##=== 2. El experimento del ingenuo ===##
##============================================================================##
## k-means SIN escalar (a propósito)
set.seed(13) # en la línea inmediatamente anterior a kmeans()
km_crudo <- kmeans(rfm, centers = 4, nstart = 25)
## el perfil de los grupos crudos, con los datos originales
clientes %>%
mutate(grupo = km_crudo$cluster) %>%
group_by(grupo) %>%
summarise(clientes = n(),
recencia = median(recencia_dias),
frecuencia = median(frecuencia_tx),
monto = median(monto_total))
## ¿dónde quedaron los que llevan más de 90 días sin transar?
table(grupo = km_crudo$cluster, mas_de_90_dias = clientes$recencia_dias > 90)Checkpoint 🎯: cuatro grupos ordenados por plata: montos medianos de 202.500, 700.900, 1.442.800 y 2.656.750 pesos. Y de los 886 clientes que llevan más de 90 días sin transar, 872 quedaron en el grupo más grande —4.358 clientes, con recencia mediana de apenas 30 días— y los otros 14 en otro grupo.
Hallazgo 2
La distancia la secuestró el monto: su desviación es unas 10.000 veces la de la recencia y 30.000 la de la frecuencia, así que para el algoritmo una diferencia de 10.000 pesos pesa más que la de un cliente de ayer contra uno de hace un año. Resultado: franjas de plata… y los clientes dormidos —el grupo de mayor riesgo de la semana 6— no aparecen por ningún lado.
Momento 3 — ¿Cómo se pone a todos en la misma cancha? ≈ 3 min
Lo que necesitas
scale() le resta a cada variable su media y la divide por su desviación: todas quedan con media 0 y desviación 1, y pesan igual en la distancia.
##============================================================================##
##=== 3. La misma cancha para todos ===##
##============================================================================##
## escalar: media 0, desviación 1
rfm_esc <- scale(rfm)
## verificar
round(colMeans(rfm_esc), 3)
round(apply(rfm_esc, 2, sd), 3)Checkpoint 🎯: medias 0, 0, 0 y desviaciones 1, 1, 1. (scale() devuelve una matriz, no un data frame: para kmeans() da lo mismo.)
Momento 4 — ¿Cuántos segmentos? ≈ 14 min
Lo que necesitas
WCSS: la suma de las distancias al cuadrado de cada cliente a su centroide (compactación). Siempre baja al subir k, así que se busca el codo, donde deja de comprar. Silueta: de −1 a 1, qué tan bien está cada cliente en su grupo frente al grupo vecino; se busca el máximo.
Nadie sabe cuántos segmentos hay en Cóndor. Corremos k-means para k = 1…8 y dejamos que las dos métricas acoten la respuesta.
##============================================================================##
##=== 4. ¿Cuántos segmentos? El codo y la silueta ===##
##============================================================================##
## el codo: WCSS para k = 1..8
set.seed(13) # una semilla para todo el barrido
wss <- sapply(1:8, function(k) kmeans(rfm_esc, centers = k, nstart = 25)$tot.withinss)
codo <- data.frame(k = 1:8, wss = round(wss))
codo
ggplot(codo, aes(x = k, y = wss)) +
geom_line(color = "#1F4E79") +
geom_point(color = "#E87722", size = 3) +
scale_x_continuous(breaks = 1:8) +
labs(title = "El codo se dobla entre 3 y 4 segmentos",
x = "Número de segmentos (k)", y = "WCSS") +
theme_minimal()
ggsave("output/fig_codo_s13.png", width = 7, height = 4)
## la silueta promedio para k = 2..8 (las distancias se calculan UNA vez)
distancias <- dist(rfm_esc)
set.seed(13)
sil <- sapply(2:8, function(k) {
km <- kmeans(rfm_esc, centers = k, nstart = 25)
mean(silhouette(km$cluster, distancias)[, 3])
})
data.frame(k = 2:8, silueta = round(sil, 3))Checkpoint 🎯: la WCSS cae 23.997 → 13.732 → 8.112 → 5.690 → 4.506 y de ahí gotea (3.508, 3.030, 2.645): el tercer segmento compra −5.620 de compactación, el cuarto −2.422 y el quinto apenas −1.184. La silueta: 0.502, 0.545, 0.461, 0.439… — máximo en k = 3.
Hallazgo 4
Esta vez las métricas no votan lo mismo: el codo dice 3 o 4 y la silueta dice 3. La regla práctica de la teoría: perfila ambos y quédate con el que produzca segmentos más claros y accionables.
## perfila ambos: tamaño, monto mediano y abandono de cada segmento
set.seed(13)
km3 <- kmeans(rfm_esc, centers = 3, nstart = 25)
clientes %>%
mutate(segmento = km3$cluster) %>%
group_by(segmento) %>%
summarise(clientes = n(), monto = median(monto_total), abandono = round(mean(abandono), 3))
set.seed(13)
km4 <- kmeans(rfm_esc, centers = 4, nstart = 25)
clientes %>%
mutate(segmento = km4$cluster) %>%
group_by(segmento) %>%
summarise(clientes = n(), monto = median(monto_total), abandono = round(mean(abandono), 3))Checkpoint 🎯: con k = 3, el segmento de más valor tiene 1.554 clientes, con monto mediano de 1.415.950 y abandono de 6.8 %. Con k = 4, la parte alta se reorganiza en dos: 2.261 clientes con 928.800 y 549 con 2.094.800 de monto mediano.
La decisión: k = 4
Con k = 4 aparecen los VIP: 549 clientes con más del doble de monto que los activos, que merecen una acción propia (un ejecutivo de cuenta, el “tratamiento aparte” de la semana 12). Dos segmentos que recibirían acciones distintas deben ser dos segmentos: k = 4 es más accionable, aunque la silueta prefiera 3.
Momento 5 — ¿Quiénes son? ≈ 12 min
Lo que necesitas
El centroide de un segmento es el promedio de sus clientes, en la escala estandarizada. El perfil se lee con los datos originales, en las unidades que entiende el negocio — y con cola larga, con medianas (la lección de la semana 6).
##============================================================================##
##=== 5. Los cuatro segmentos y su perfil (grano: segmento) ===##
##============================================================================##
## el k-means final
set.seed(13)
km_final <- kmeans(rfm_esc, centers = 4, nstart = 25)
table(km_final$cluster)
## a mano: el centroide es el promedio del segmento, en la escala estandarizada
round(km_final$centers, 2)
data.frame(rfm_esc) %>%
mutate(segmento = km_final$cluster) %>%
group_by(segmento) %>%
summarise(recencia = round(mean(recencia_dias), 2),
frecuencia = round(mean(frecuencia_tx), 2),
monto = round(mean(monto_total), 2))
## el perfil con los datos originales, y el abandono real con su margen
clientes <- clientes %>%
mutate(segmento = km_final$cluster)
perfil <- clientes %>%
group_by(segmento) %>%
summarise(clientes = n(),
recencia = median(recencia_dias),
frecuencia = median(frecuencia_tx),
monto = median(monto_total),
abandono = mean(abandono)) %>%
mutate(margen = round(2 * sqrt(abandono * (1 - abandono) / clientes), 3),
abandono = round(abandono, 3))
perfilCheckpoint 🎯: segmentos de 582, 2.261, 4.608 y 549 clientes, y la cuenta a mano da exactamente los centroides. El perfil:
# A tibble: 4 × 7
segmento clientes recencia frecuencia monto abandono margen
<int> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 582 204. 2 54850 0.687 0.038
2 2 2261 7 36 928800 0.093 0.012
3 3 4608 22 12 276850 0.247 0.013
4 4 549 4 72 2094800 0.046 0.018Hallazgo 5
Los segmentos se armaron sin mirar el abandono… y lo separan: del 68.7 % en el segmento 1 al 4.6 % en el 4, diferencias muchas veces mayores que los márgenes. El RFM contiene el enganche que la semana 6 encontró.
Bautizar los segmentos (en parejas, 3 min): con el perfil, completen para cada segmento un nombre específico (nada de “grupo 1”) y una acción distinta. Si dos segmentos recibirían la misma acción, quizá deberían ser uno.
| Segmento | Rasgo dominante | Nombre | Acción |
|---|---|---|---|
| 1 | |||
| 2 | |||
| 3 | |||
| 4 |
Momento 6 — ¿Dónde está la plata en riesgo? ≈ 10 min
Lo que necesitas
El valor en riesgo de la semana 12 = probabilidad de irse (semana 11) × gasto esperado (semana 12). Solo existe para los 1.600 clientes del sobre, los que tienen un pronóstico honesto.
##============================================================================##
##=== 6. El valor en riesgo por segmento (los 1.600 del test) ===##
##============================================================================##
riesgo_segmento <- valor %>%
left_join(select(clientes, cliente_id, segmento), by = "cliente_id") %>%
group_by(segmento) %>%
summarise(clientes_test = n(),
riesgo_medio = round(mean(prob_abandono), 3),
valor_en_riesgo = round(sum(valor_en_riesgo))) %>%
mutate(parte = round(valor_en_riesgo / sum(valor_en_riesgo), 3))
riesgo_segmento
## export data
write.csv(select(clientes, cliente_id, segmento), "output/segmentos_condor.csv", row.names = F)
write.csv(left_join(perfil, riesgo_segmento, by = "segmento"), "output/perfil_segmentos.csv", row.names = F)Checkpoint 🎯:
# A tibble: 4 × 5
segmento clientes_test riesgo_medio valor_en_riesgo parte
<int> <int> <dbl> <dbl> <dbl>
1 1 110 0.637 736440 0.012
2 2 467 0.113 32544130 0.51
3 3 925 0.275 22690842 0.356
4 4 98 0.067 7785733 0.122Hallazgo 6 — la lección de la semana
La plata en riesgo no está donde está el riesgo. El segmento 1 tiene la probabilidad de irse más alta (0.637), pero pone solo el 1.2 % del valor en riesgo: sus clientes casi no gastan. El segmento 2 se va poco (0.113) y concentra la mitad del valor en riesgo. Una campaña que solo mire el riesgo llamaría a quienes menos plata ponen en juego.
## la decisión (comentario, cuatro líneas): una acción para cada segmento,
## con dos cifras de hoy. Pistas: ¿reactivación barata y automática para los
## dormidos? ¿retención personalizada donde está la mitad de la plata?
## ¿un ejecutivo de cuenta para los VIP?¿Terminaste antes? Dos retos
Reto 1 — La ruleta de la inicialización. Corre kmeans(rfm_esc, centers = 4, nstart = 1) tres veces sin semilla y compara tot.withinss entre corridas. ¿Cambia? Esa es la lotería de la que nstart = 25 te protege (25 boletos en vez de 1).
Reto 2 — ¿Y el uso de la app? Agrega num_sesiones_ult30d a las variables del segmento, escala, y repite el k-means con k = 4. ¿Cambian los segmentos? ¿Aparece un grupo que el RFM no veía? Documenta lo que observes como comentario.
Cierre
De la semana 6 a la 13, la misma base y la misma pregunta de fondo: describiste a los clientes (EDA), juzgaste reglas, predijiste quién se va y cuánto gasta, y hoy los agrupaste sin target. La visión completa del problema de retención cabe en una tabla: quiénes son, cuánto riesgo tienen y cuánta plata ponen en juego — y una acción distinta para cada segmento.
El Taller 13 te lleva a un gimnasio: CaliFit te entrega su base de socios y la segmentación que dejó un practicante. Tu primer trabajo será auditarla (pista: el practicante no leyó el Momento 2 de hoy). Misma disciplina de siempre: toda cifra sale de tu script.