Técnicas multivariantes de investigación en marketing
  • Presentación
  • Sesiones
    • Parte 1
    • Parte 2
    • Parte 3
    • Parte 4
    • Parte 5
    • Parte 6
  • Otros
    • Citar en Quarto
    • Acerca de …

Contenidos

  • Parte 5
    • Introducción al análisis de conglomerados
    • Un ejemplo intuitivo acerca del lenguaje
      • Matriz de datos original (frecuencia de coincidencia)
      • El historial de conglomeración
        • Interpretación del historial
        • Representación visual con dendrograma
    • Pasos y objetivos del análisis cluster
    • Adecuación de la estructura de datos
      • Detección de outliers
        • La naturaleza del outlier en el análisis de grupos
        • Distancia de Mahalanobis
        • El criterio de significación estadística \(\chi^2\)
        • Eliminar o mantener, ¿qué decisión tomar?
      • Medición de distancias
      • Estandarización
        • El método de estandarización en R
        • Ejemplo: cluster3.sav sin estandarizar
        • Ejemplo: cluster3.sav estandarizado
        • Consideraciones críticas en la estandarización
    • Análisis cluster, introducción
      • El método jerárquico: la exploración del número de grupos
      • El método no jerárquico (K-means): el ajuste de precisión
      • La estrategia combinada: lo mejor de ambos mundos
    • Cluster jerárquico
      • Métodos de vinculación (Linkage)
    • Implementación del cluster jerárquico con R
      • Determinación del número óptimo de grupos
      • Obtención de centroides finales
    • Cluster no jerárquico o K-means
      • La lógica del algoritmo K-means
    • Implementación del cluster no jerárquico (K-means) con R
      • Caracterización de los segmentos
      • Validación mediante ANOVA
      • Perfilado de los segmentos
    • Conclusión de la sesión

Parte 5

Introducción al análisis de conglomerados

El análisis de conglomerados es un conjunto de técnicas multivariantes utilizadas para clasificar a un conjunto de individuos en grupos homogéneos. A diferencia del análisis discriminante, en el análisis cluster los grupos son desconocidos a priori; el objetivo es precisamente determinarlos.

Este análisis presenta un marcado carácter exploratorio y se fundamenta en dos pilares:

  1. Interdependencia: no se distingue entre variables dependientes e independientes.
  2. Determinación de grupos: se busca la máxima homogeneidad intra-grupo (dentro del grupo) y la máxima heterogeneidad inter-grupo (entre grupos).

Un ejemplo intuitivo acerca del lenguaje

Para entender cómo funciona el algoritmo de forma visual, es posible imaginar que se compara cómo se escriben los números del 1 al 10 en 11 idiomas diferentes. El criterio de “distancia” o “similitud” es el número de veces que dos idiomas coinciden en la primera letra del número.

Matriz de datos original (frecuencia de coincidencia)

A continuación se recrea la matriz de similitudes que representa la semejanza de los idiomas entre sí:

# Similarity matrix: value = number of matching first letters across digits 1-10
languages_data <- data.frame(
  Language = c("EN", "NO", "DA", "DU", "GE", "FR", "SP", "IT", "PO", "HU", "FI"),
  EN = c(10, 8, 8, 3, 4, 4, 4, 4, 3, 1, 1),
  NO = c(8, 10, 9, 5, 6, 4, 4, 4, 3, 2, 1),
  DA = c(8, 9, 10, 4, 5, 4, 5, 5, 4, 2, 1),
  DU = c(3, 5, 4, 10, 5, 1, 1, 1, 0, 2, 1),
  GE = c(4, 6, 5, 5, 10, 3, 3, 3, 2, 1, 1),
  FR = c(4, 4, 4, 1, 3, 10, 8, 9, 5, 0, 1),
  SP = c(4, 4, 5, 1, 3, 8, 10, 9, 7, 0, 1),
  IT = c(4, 4, 5, 1, 3, 9, 9, 10, 6, 0, 1),
  PL = c(3, 3, 4, 0, 2, 5, 7, 6, 10, 0, 1),
  HU = c(1, 2, 2, 2, 1, 0, 0, 0, 0, 10, 2),
  FI = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 10)
)

cat("Similarity matrix:\n")
print(languages_data)
Similarity matrix:
   Language EN NO DA DU GE FR SP IT PL HU FI
1        EN 10  8  8  3  4  4  4  4  3  1  1
2        NO  8 10  9  5  6  4  4  4  3  2  1
3        DA  8  9 10  4  5  4  5  5  4  2  1
4        DU  3  5  4 10  5  1  1  1  0  2  1
5        GE  4  6  5  5 10  3  3  3  2  1  1
6        FR  4  4  4  1  3 10  8  9  5  0  1
7        SP  4  4  5  1  3  8 10  9  7  0  1
8        IT  4  4  5  1  3  9  9 10  6  0  1
9        PO  3  3  4  0  2  5  7  6 10  0  1
10       HU  1  2  2  2  1  0  0  0  0 10  2
11       FI  1  1  1  1  1  1  1  1  1  2 10

El historial de conglomeración

El proceso jerárquico muestra cómo se van fusionando los casos. En las primeras etapas se unen los elementos con menor “distancia” (o mayor similitud):

  1. Etapa 1: se unen los idiomas con mayor parecido.
  2. Etapa 2: se unen nuevamente los idiomas con mayor parecido o se incorpora un nuevo idioma al grupo ya formado.
  3. Etapas sucesivas: los grupos pequeños se van integrando en estructuras mayores (ej. el grupo escandinavo: Noruego y Danés).
  4. Resultado final: un único grupo que engloba a todos los individuos.

Para que el algoritmo pueda trabajar con la matriz de similitudes, primero debe transformarse en una matriz de distancias. En este caso se resta la coincidencia máxima (10) menos el valor observado. Una distancia de 0 indica identidad total.

# 1. Convert similarity to distance (10 = maximum similarity → distance 0)
distances_lang <- stats::as.dist(10 - dplyr::select(languages_data, -Language))

cat("Distance matrix:\n")
print(distances_lang)

# 2. Hierarchical clustering with Ward's method
# 'ward.D2' matches SPSS exact algorithm for squared Euclidean distances
model_lang <- stats::hclust(distances_lang, method = "ward.D2")

# 3. Agglomeration schedule
agg_schedule <- data.frame(
  stage     = seq_len(length(model_lang$height)),
  element_1 = model_lang$merge[, 1],
  element_2 = model_lang$merge[, 2],
  distance  = model_lang$height
)

cat("\nAgglomeration schedule:\n")
print(agg_schedule)
Distance matrix:
   EN NO DA DU GE FR SP IT PL HU
NO  2                           
DA  2  1                        
DU  7  5  6                     
GE  6  4  5  5                  
FR  6  6  6  9  7               
SP  6  6  5  9  7  2            
IT  6  6  5  9  7  1  1         
PL  7  7  6 10  8  5  3  4      
HU  9  8  8  8  9 10 10 10 10   
FI  9  9  9  9  9  9  9  9  9  8

Agglomeration schedule:
   stage element_1 element_2  distance
1      1        -2        -3  1.000000
2      2        -6        -8  1.000000
3      3        -7         2  1.732051
4      4        -1         1  2.236068
5      5        -9         3  4.898979
6      6        -4        -5  5.000000
7      7         4         6  7.576279
8      8       -10       -11  8.000000
9      9         7         8 12.078316
10    10         5         9 13.614355

Interpretación del historial

La tabla anterior permite explicar la lógica secuencial del algoritmo:

  1. Etapas iniciales: se fusionan los elementos con los coeficientes más bajos (menor distancia). Por ejemplo, en la primera etapa se unen los idiomas con mayor coincidencia.
  2. Estructura del coeficiente: el coeficiente (distancia) aumenta en cada etapa. Un salto brusco en este valor suele indicar que se está forzando la unión de grupos que son muy diferentes entre sí.
  3. De casos a grupos: en las primeras etapas se unen casos (números negativos en R), y en etapas avanzadas aparecen fusiones con grupos ya formados (números positivos), indicando que un nuevo idioma se une a un grupo preexistente o que dos grupos se fusionan.

Representación visual con dendrograma

Para cerrar la explicación intuitiva, se visualiza el “árbol” resultante:

# Dendrogram of the language similarity example
plot(model_lang, main = "Dendrograma de idiomas", xlab = "", sub = "")

Pasos y objetivos del análisis cluster

Para que la segmentación sea exitosa, se deben seguir estos pasos técnicos:

  • Establecer un criterio de similaridad: generalmente mediante una matriz de distancias (euclídea, Manhattan, etc.).
  • Algoritmo de clasificación: elegir entre métodos jerárquicos (aglomerativos) o no jerárquicos (K-means).
  • Representación: uso de dendrogramas para visualizar las uniones y decidir el número óptimo de grupos.
  • Caracterización y perfilado: determinar qué variables definen cada grupo y validar si existen diferencias significativas mediante pruebas de contraste (ANOVA / Chi-cuadrado).

Adecuación de la estructura de datos

Detección de outliers

En la investigación de mercados, la presencia de valores atípicos es una de las mayores amenazas para la validez de una segmentación. Un outlier no es simplemente un “dato raro”; es una observación que procede de una estructura de población distinta a la que se desea modelizar o que introduce un sesgo desproporcionado en el cálculo de los centroides.

La naturaleza del outlier en el análisis de grupos

El análisis de conglomerados (especialmente el jerárquico y K-means) se basa en el cálculo de centros gravitacionales (medias). Si se incluyen sujetos con comportamientos extremos, el algoritmo intenta “acomodar” esos puntos, resultando en:

  1. Desplazamiento de centros: el grupo no representará al cliente promedio, sino a un promedio distorsionado.
  2. Estructuras artificiales: se crearán grupos de \(N=1\) o \(N=2\) que carecen de valor comercial o estadístico.
  3. Reducción del tamaño del efecto: la varianza interna aumentará, haciendo que los grupos parezcan menos cohesionados de lo que realmente son.

Distancia de Mahalanobis

Mientras que la distancia euclídea es una medida de “línea recta” entre dos puntos, la Distancia de Mahalanobis (\(D^2\)) es una medida de distancia estadística. Su ventaja crítica reside en que es invariante a la escala y tiene en cuenta las correlaciones entre las variables.

Si dos variables de segmentación están muy correlacionadas (ej. Gasto en tienda y Gasto online), la distancia euclídea las contaría como dos dimensiones separadas, “inflando” la importancia de ese comportamiento. Mahalanobis, en cambio, utiliza la matriz de varianzas-covarianzas (\(\Sigma\)) para ponderar el espacio:

\[D^2 = (x - \bar{x})^T S^{-1} (x - \bar{x})\]

Donde \(S^{-1}\) es la inversa de la matriz de covarianza de la muestra. Conceptualmente, mide la distancia de un caso al centroide del grupo en unidades de desviación típica multivariante.

El criterio de significación estadística \(\chi^2\)

Para determinar si una \(D^2\) es excesiva, se recurre a la distribución Chi-cuadrado (\(\chi^2\)). Dado que la suma de variables normales al cuadrado sigue una distribución \(\chi^2\), puede asignarse una probabilidad a cada distancia observada.

  • Grados de libertad (\(df\)): se corresponden exactamente con el número de variables métricas introducidas en el análisis.
  • Umbral crítico (\(p < 0.001\)): a diferencia del estándar \(0.05\) usado en contrastes de hipótesis, en la detección de outliers multivariantes se debe ser mucho más estricto. Solo se eliminan casos donde la probabilidad de que el sujeto pertenezca a la misma distribución que el resto sea prácticamente nula.

Antes de abordar el análisis cluster, se valida la no existencia de atípicos multivariantes:

# Load the HATCO dataset
hatco <- expss::read_spss('data/hatco.sav')
utils::head(hatco, 10)

# Define segmentation variables and remove missing values
# Mahalanobis distance cannot be computed in the presence of NAs
hatco_subset <- hatco %>%
  dplyr::select("X1", "X2", "X3", "X4", "X5", "X6", "X7") %>%
  tidyr::drop_na()

# Mahalanobis distance for each observation relative to the multivariate centroid
mahalanobis_values <- stats::mahalanobis(
  x      = hatco_subset,
  center = colMeans(hatco_subset),
  cov    = stats::cov(hatco_subset)
)

cat("Mahalanobis distance summary:\n")
summary(mahalanobis_values)

# Visualize the distribution of distances across observations
mahalanobis_df <- data.frame(
  observation_index = seq_len(length(mahalanobis_values)),
  distance_value    = mahalanobis_values
)

ggplot2::ggplot(mahalanobis_df, ggplot2::aes(x = observation_index, y = distance_value)) +
  ggplot2::geom_line(color = "steelblue", linewidth = 1) +
  ggplot2::geom_point(color = "firebrick", size = 3) +
  ggplot2::theme_minimal() +
  ggplot2::labs(
    title = "Distribución de distancias de Mahalanobis",
    x     = "Índice del caso",
    y     = "Distancia de Mahalanobis"
  )

   ID  X1  X2  X3  X4  X5   X6  X7 X8 X9 X10 X11 X12 X13 X14
1   1 4.1 0.6 6.9 4.7 2.4 2.35 5.2  0 32 4.2   1   0   1   1
2   2 1.8 3.0 6.3 6.6 2.5 4.00 8.4  1 43 4.3   0   1   0   1
3   3 3.4 5.2 5.7 6.0 4.3 2.70 8.2  1 48 5.2   0   1   1   2
4   4 2.7 1.0 7.1 5.9 1.8 2.30 7.8  1 32 3.9   0   1   1   1
5   5 6.0 0.9 9.6 7.8 3.4 4.60 4.5  0 58 6.8   1   0   1   3
6   6 1.9 3.3 7.9 4.8 2.6 1.90 9.7  1 45 4.4   0   1   1   2
7   7 4.6 2.4 9.5 6.6 3.5 4.50 7.6  0 46 5.8   1   0   1   1
8   8 1.3 4.2 6.2 5.1 2.8 2.20 6.9  1 44 4.3   0   1   0   2
9   9 5.5 1.6 9.4 4.7 3.5 3.00 7.6  0 63 5.4   1   0   1   3
10 10 4.0 3.5 6.5 6.0 3.7 3.20 8.7  1 54 5.4   0   1   0   2
Mahalanobis distance summary:
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.108   4.322   5.912   6.930   7.531  35.391 

Se calcula ahora la significación de esa medida de distancia y se obtiene su correspondiente gráfico:

# Convert Mahalanobis distances to chi-square p-values
# df = number of metric variables used in the distance calculation
n_vars <- 7

p_values <- stats::pchisq(
  mahalanobis_values,
  df         = n_vars,
  lower.tail = FALSE
)

cat("Chi-square significance values summary:\n")
summary(p_values)

p_values_df <- data.frame(
  observation_index = seq_len(length(p_values)),
  distance_value    = p_values
)

ggplot2::ggplot(p_values_df, ggplot2::aes(x = observation_index, y = distance_value)) +
  ggplot2::geom_line(color = "steelblue", linewidth = 1) +
  ggplot2::geom_point(color = "firebrick", size = 3) +
  ggplot2::theme_minimal() +
  ggplot2::labs(
    title = "Distribución de valores de significación",
    x     = "Índice del caso",
    y     = "p-value"
  )

Chi-square significance values summary:
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
9.440e-06 3.758e-01 5.501e-01 5.336e-01 7.420e-01 9.536e-01 

A continuación se muestra el resultado para identificar y retener los casos válidos:

# Flag observations where p < 0.001 as multivariate outliers
outlier_report <- hatco_subset %>%
  dplyr::mutate(
    mahalanobis = mahalanobis_values,
    p_value     = p_values,
    is_outlier  = dplyr::if_else(p_value < 0.001, "Yes", "No")
  )

cat("Outlier count:\n")
table(outlier_report$is_outlier)

# Cases flagged for potential exclusion
outliers_detected <- outlier_report %>%
  dplyr::filter(is_outlier == "Yes")
print(outliers_detected)

# Retain only valid cases for the segmentation phase
hatco_subset <- outlier_report %>%
  dplyr::filter(is_outlier == "No")
Outlier count:

 No Yes 
 98   2 
   X1  X2  X3  X4  X5  X6  X7 mahalanobis      p_value is_outlier
1 3.4 0.4 8.3 2.5 1.2 1.7 5.2    35.39056 9.443981e-06        Yes
2 3.8 0.8 8.7 2.9 1.6 2.1 5.6    35.20278 1.024445e-05        Yes

Eliminar o mantener, ¿qué decisión tomar?

No todos los outliers deben borrarse automáticamente. El analista debe considerar:

  • Error de datos: si es un error de grabación, se corrige o elimina.
  • Sujetos excepcionales: si el sujeto es real pero único (ej. una empresa con una facturación que duplica al segundo), su inclusión romperá la lógica de los grupos pequeños. En segmentación de mercados, es preferible tratar a los outliers como un segmento separado (“Segmento de excepciones”) y realizar el análisis cluster con el resto de la muestra para obtener grupos accionables y estables.

Medición de distancias

  • Distancia euclídea: es recomendable cuando los datos están distribuidos de manera relativamente uniforme, no hay valores atípicos extremos que puedan distorsionar los resultados y se busca una medida de distancia más balanceada.
  • Distancia euclídea al cuadrado: es recomendable cuando hay valores atípicos individuales de los que se desea minimizar la influencia, se busca una mayor separación entre los clusters y se quiere dar más peso a las diferencias grandes entre observaciones (es el estándar en el método de Ward).

Estandarización

Una vez detectados y tratados los valores atípicos mediante la distancia de Mahalanobis, el siguiente paso imperativo antes de ejecutar el algoritmo de segmentación es la estandarización de las variables para medir sus distancias.

  • Las medidas de distancia son muy sensibles a las escalas de medición.
  • Trabajar con diferentes escalas hace que las distancias entre grupos se vean muy afectadas por la métrica (ej. euros vs. años) y no resulte clara la diferenciación.
  • Por tanto, es obligatorio estandarizar si las variables no están medidas en la misma escala.

El método de estandarización en R

Se utiliza la función scale(), que transforma todas las variables a una escala común con media 0 y desviación típica 1:

\[Z = \frac{x - \bar{x}}{s}\]

Ejemplo: cluster3.sav sin estandarizar

Para evidenciar las diferencias entre trabajar con estandarización o sin ella, se calcula primero una matriz de distancias del fichero cluster3.sav utilizando los datos brutos:

# Load example dataset with variables on very different scales
data_cluster3 <- expss::read_spss('data/cluster3.sav')
utils::head(data_cluster3, 10)

# Squared Euclidean distance matrix WITHOUT standardization
distances_raw <- stats::dist(
  dplyr::select(data_cluster3, activos, trabajadores),
  method = "euclidean"
)^2

cat("Distance matrix (raw, non-standardized):\n")
print(format(as.matrix(distances_raw), scientific = FALSE))
  id   activos trabajadores
1 e1 1.000e+10          100
2 e2 1.005e+10           90
3 e3 1.000e+10          200
4 e4 1.005e+10          190
5 e5 2.000e+10          200
6 e6 2.005e+10          190
7 e7 2.000e+10          100
8 e8 2.005e+10           90
Distance matrix (raw, non-standardized):
  1                       2                       3                      
1 "                    0" "     2500000000000100" "                10000"
2 "     2500000000000100" "                    0" "     2500000000012100"
3 "                10000" "     2500000000012100" "                    0"
4 "     2500000000008101" "                10000" "     2500000000000100"
5 "100000000000000000000" " 99002500000000000000" "100000000000000000000"
6 "101002500000000000000" "100000000000000000000" "101002500000000000000"
7 "100000000000000000000" " 99002500000000000000" "100000000000000000000"
8 "101002500000000000000" "100000000000000000000" "101002500000000000000"
  4                       5                       6                      
1 "     2500000000008101" "100000000000000000000" "101002500000000000000"
2 "                10000" " 99002500000000000000" "100000000000000000000"
3 "     2500000000000100" "100000000000000000000" "101002500000000000000"
4 "                    0" " 99002500000000000000" "100000000000000000000"
5 " 99002500000000000000" "                    0" "     2500000000000100"
6 "100000000000000000000" "     2500000000000100" "                    0"
7 " 99002500000000000000" "                10000" "     2500000000008101"
8 "100000000000000000000" "     2500000000012100" "                10000"
  7                       8                      
1 "100000000000000000000" "101002500000000000000"
2 " 99002500000000000000" "100000000000000000000"
3 "100000000000000000000" "101002500000000000000"
4 " 99002500000000000000" "100000000000000000000"
5 "                10000" "     2500000000012100"
6 "     2500000000008101" "                10000"
7 "                    0" "     2500000000000100"
8 "     2500000000000100" "                    0"

Ejemplo: cluster3.sav estandarizado

A continuación se aplica el cálculo de los Z-scores:

# Standardize variables to Z-scores (mean = 0, sd = 1)
data_cluster3$z_activos      <- scale(data_cluster3$activos)
data_cluster3$z_trabajadores <- scale(data_cluster3$trabajadores)

cat("Extended data table (with Z-scores):\n")
print(data_cluster3)

# Squared Euclidean distance matrix WITH standardization
distances_std <- stats::dist(
  dplyr::select(data_cluster3, z_activos, z_trabajadores),
  method = "euclidean"
)^2

cat("\nDistance matrix (standardized):\n")
print(as.matrix(distances_std))
Extended data table (with Z-scores):
  id   activos trabajadores  z_activos z_trabajadores
1 e1 1.000e+10          100 -0.9400797     -0.8376949
2 e2 1.005e+10           90 -0.9307256     -1.0238493
3 e3 1.000e+10          200 -0.9400797      1.0238493
4 e4 1.005e+10          190 -0.9307256      0.8376949
5 e5 2.000e+10          200  0.9307256      1.0238493
6 e6 2.005e+10          190  0.9400797      0.8376949
7 e7 2.000e+10          100  0.9307256     -0.8376949
8 e8 2.005e+10           90  0.9400797     -1.0238493

Distance matrix (standardized):
           1          2          3          4          5          6          7
1 0.00000000 0.03474096 3.46534653 2.80701819 6.96525904 6.34192982 3.49991250
2 0.03474096 0.00000000 4.19315680 3.46534653 7.65807018 6.96525904 3.49965434
3 3.46534653 4.19315680 0.00000000 0.03474096 3.49991250 3.56965259 6.96525904
4 2.80701819 3.46534653 0.03474096 0.00000000 3.49965434 3.49991250 6.27193157
5 6.96525904 7.65807018 3.49991250 3.49965434 0.00000000 0.03474096 3.46534653
6 6.34192982 6.96525904 3.56965259 3.49991250 0.03474096 0.00000000 2.80701819
7 3.49991250 3.49965434 6.96525904 6.27193157 3.46534653 2.80701819 0.00000000
8 3.56965259 3.49991250 7.72806843 6.96525904 4.19315680 3.46534653 0.03474096
           8
1 3.56965259
2 3.49991250
3 7.72806843
4 6.96525904
5 4.19315680
6 3.46534653
7 0.03474096
8 0.00000000

Consideraciones críticas en la estandarización

  • Ponderación implícita: al estandarizar se otorga el mismo peso específico a todas las variables en la formación de los grupos. Si desde el análisis de negocio se considera que una variable es el doble de importante que el resto, deberían multiplicarse sus puntuaciones Z por 2 tras la estandarización.
  • Interpretación: es fundamental recordar que el cluster se ejecuta sobre los datos estandarizados, pero para la caracterización o perfilado final de los grupos siempre se debe volver a las métricas originales para que las medias de los segmentos tengan sentido de negocio (ej. “el grupo 1 gasta 400€”, en lugar de “el grupo 1 tiene un Z de 0.8”).

Análisis cluster, introducción

A la hora de clasificar, el analista debe decidir qué tipo de algoritmo utilizar. Aunque ambos buscan crear grupos homogéneos, su funcionamiento y objetivos son diferentes. En la práctica real de la investigación de mercados, se suelen utilizar de forma secuencial.

El método jerárquico: la exploración del número de grupos

Este algoritmo es de naturaleza ascendente y exploratoria. Al principio, cada cliente es un “grupo de una sola persona”. El algoritmo busca a los dos clientes más parecidos y los une. Luego busca la siguiente pareja más cercana, y así sucesivamente hasta que todos los individuos acaban formando parte de un único gran grupo.

  • ¿Cuál es su gran ventaja? No es necesario saber de antemano cuántos grupos crear. El análisis ofrece un “árbol de fusiones” (dendrograma) que permite visualizar cómo se estructuran los datos y decidir el número óptimo de segmentos.
  • Criterios de unión: existen diferentes formas de medir la “cercanía” entre grupos (Vecino más cercano, Vinculación promedio, Método de Ward).

El método no jerárquico (K-means): el ajuste de precisión

A diferencia del anterior, este método es confirmatorio. El analista debe indicar al software exactamente cuántos grupos desea crear.

  1. Inicio: el algoritmo coloca “centros” provisionales en el espacio.
  2. Asignación: cada cliente se asigna al centro que tiene más cerca.
  3. Recálculo: los centros se mueven para situarse en el promedio real de las personas asignadas a ese grupo.
  4. Optimización: el proceso se itera hasta que los grupos son lo más compactos posible y ningún caso cambia de conglomerado.

La estrategia combinada: lo mejor de ambos mundos

En un flujo de trabajo profesional, lo habitual es seguir este orden de tres pasos:

  1. Fase exploratoria (Jerárquico): ejecutar un cluster jerárquico para determinar cuántos grupos naturales existen en el mercado.
  2. Fijación de centros: una vez decidido el número de grupos (por ejemplo, 3), se calculan los centros exactos de esos grupos.
  3. Fase de optimización (K-means): utilizar esos centros como semillas para ejecutar un análisis de K-means, “recolocando” a los clientes situados en fronteras dudosas para lograr segmentos finales estables.

Cluster jerárquico

Como se ha visto, surge una pregunta clave: ¿cuándo se decide que dos grupos son “vecinos” y deben unirse? La respuesta depende del método de agrupación (o linkage) elegido.

Métodos de vinculación (Linkage)

  • Vínculo simple (Single Linkage): utiliza al “vecino más cercano”. Basta con que dos casos de grupos distintos estén cerca para que los grupos se fusionen. Suele producir un problema llamado “encadenamiento”.
  • Vínculo completo (Complete Linkage): utiliza al “vecino más lejano”. La distancia se define por los dos individuos más alejados entre sí. Es muy exigente y tiende a producir grupos compactos.
  • Vínculo promedio (Average Linkage): calcula la distancia media entre todos los pares de individuos de dos grupos. Tiende a combinar grupos con varianzas pequeñas y es muy equilibrado.
  • Método de Ward: es el más utilizado en investigación de mercados. A diferencia de los anteriores, minimiza la varianza total dentro de los grupos. Produce grupos extremadamente homogéneos y de tamaños muy similares.

Implementación del cluster jerárquico con R

Se utiliza la función estándar stats::hclust(), indicando el método deseado en el argumento method. La función stats::cutree() permite obtener la pertenencia de cada caso al grupo a partir de la solución jerárquica.

# Re-load HATCO dataset (fresh copy for the cluster analysis pipeline)
hatco <- expss::read_spss('data/hatco.sav')

# 1. Squared Euclidean distance matrix (required for Ward's method)
distances <- stats::dist(
  dplyr::select(hatco, X1, X2, X3, X4, X5, X6, X7),
  method = "euclidean"
)^2

# 2. Hierarchical models
# 'ward.D2' aligns with SPSS squared Euclidean implementation
model_ward <- stats::hclust(distances, method = "ward.D2")
model_avg  <- stats::hclust(distances, method = "average")

# 3. Two-group solutions
cat("RESULTS: WARD'S METHOD\n\n")
cat("Frequency table — 2-group solution:\n")
table(stats::cutree(model_ward, k = 2))

cat("\nRESULTS: AVERAGE LINKAGE\n\n")
cat("Frequency table — 2-group solution:\n")
table(stats::cutree(model_avg, k = 2))

# Assign group membership to the dataset
hatco$cluster_ward <- stats::cutree(model_ward, k = 2)
hatco$cluster_avg  <- stats::cutree(model_avg,  k = 2)

# Cross-tabulation to assess method stability
cat("\nMethod stability cross-tabulation:\n")
table(hatco$cluster_ward, hatco$cluster_avg)

# Dendrograms
plot(model_ward, main = "Dendrograma - Método Ward",     xlab = "", sub = "")
plot(model_avg,  main = "Dendrograma - Vínculo Promedio", xlab = "", sub = "")
  RESULTS: WARD'S METHOD
  
  Frequency table — 2-group solution:
  
   1  2 
  54 46 
  
  RESULTS: AVERAGE LINKAGE
  
  Frequency table — 2-group solution:
  
   1  2 
  54 46 
  
  Method stability cross-tabulation:
     
       1  2
    1 54  0
    2  0 46

Determinación del número óptimo de grupos

Para trascender la inspección visual del dendrograma, se utilizan criterios objetivos. El paquete NbClust ejecuta simultáneamente hasta 30 índices diferentes (como Calinski-Harabasz o Silhouette) y aplica una “regla de mayoría” para sugerir el número óptimo de conglomerados.

# Consensus of 30 cluster validity indices to determine the optimal k
cat("Optimal number of groups — NbClust consensus:\n")
res_nb <- NbClust::NbClust(
  data     = dplyr::select(hatco, X1, X2, X3, X4, X5, X6, X7),
  distance = "euclidean",
  min.nc   = 2,
  max.nc   = 6,
  method   = "ward.D2",
  index    = "all"
)

Optimal number of groups — NbClust consensus:
*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 
*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 
******************************************************************* 
* Among all indices:                                                
* 12 proposed 2 as the best number of clusters 
* 3 proposed 3 as the best number of clusters 
* 5 proposed 4 as the best number of clusters 
* 1 proposed 5 as the best number of clusters 
* 3 proposed 6 as the best number of clusters 

                   ***** Conclusion *****                            
 
* According to the majority rule, the best number of clusters is  2 
 
 
******************************************************************* 

Obtención de centroides finales

Una vez que el análisis jerárquico ha ayudado a decidir el número de grupos (por ejemplo, \(k=2\)), el siguiente paso estratégico es calcular los centroides. Estos vectores promedio servirán como “semilla” para inicializar el algoritmo K-means, garantizando una solución mucho más estable.

# Calculate mean of each metric variable per Ward group to establish initial centroids
initial_centroids <- hatco %>%
  dplyr::group_by(cluster_ward) %>%
  dplyr::summarise(
    X1 = mean(X1, na.rm = TRUE),
    X2 = mean(X2, na.rm = TRUE),
    X3 = mean(X3, na.rm = TRUE),
    X4 = mean(X4, na.rm = TRUE),
    X5 = mean(X5, na.rm = TRUE),
    X6 = mean(X6, na.rm = TRUE),
    X7 = mean(X7, na.rm = TRUE)
  ) %>%
  dplyr::select(X1, X2, X3, X4, X5, X6, X7)  # Keep only metric values

cat("Initial centroids (group means from hierarchical solution):\n")
print(round(initial_centroids, 2))
Initial centroids (group means from hierarchical solution):
# A tibble: 2 × 7
     X1    X2    X3    X4    X5    X6    X7
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1  4.31  1.63  8.88  4.96  2.95  2.53  5.94
2  2.58  3.22  6.73  5.58  2.88  2.82  8.19

Cluster no jerárquico o K-means

La lógica del algoritmo K-means

El proceso K-means sigue una lógica de reasignación constante:

  1. Selección de semillas: el algoritmo coloca \(k\) puntos en el espacio de datos (idealmente, los centroides calculados en la fase jerárquica).
  2. Asignación de casos: cada sujeto de la muestra se asigna al centroide más cercano.
  3. Recálculo de centros: los nuevos centros se recalculan basándose en la media real de los sujetos asignados.
  4. Optimización iterativa: el proceso se repite hasta alcanzar el criterio de convergencia (cuando ningún sujeto cambia de grupo o el movimiento de los centros es estadísticamente insignificante).

Implementación del cluster no jerárquico (K-means) con R

Se procede a realizar el análisis confirmatorio alimentando el algoritmo con los centros iniciales. La función stats::kmeans() ejecuta las iteraciones necesarias hasta la convergencia.

# K-means initialized with the centroids from the hierarchical solution
# 'iter.max' defines the maximum number of passes to ensure convergence
set.seed(12345)  # Set seed for perfect reproducibility
model_km <- stats::kmeans(
  x        = dplyr::select(hatco, X1, X2, X3, X4, X5, X6, X7),
  centers  = initial_centroids,
  iter.max = 99
)

# Append optimized cluster membership to the dataset
hatco <- hatco %>%
  dplyr::mutate(cluster_km = as.factor(model_km$cluster))

cat("Final segment distribution (K-means):\n")
table(hatco$cluster_km)
Final segment distribution (K-means):

 1  2 
52 48 

Caracterización de los segmentos

La caracterización consiste en analizar los centros finales del K-means. Estos valores indican la “identidad” del grupo: qué lo define y lo diferencia del resto.

# Final cluster centers — the 'identity card' of each segment
cat("Final K-means centroids:\n")
print(round(model_km$centers, 2))
Final K-means centroids:
    X1   X2  X3   X4   X5   X6   X7
1 4.38 1.58 8.9 4.93 2.96 2.53 5.90
2 2.57 3.21 6.8 5.60 2.87 2.82 8.13

Validación mediante ANOVA

Para asegurar que la caracterización es estadísticamente sólida, se realiza un análisis de varianza (ANOVA) sobre cada variable activa. Si \(p < 0.05\), la variable discrimina correctamente entre grupos.

# ANOVA for each segmenting variable: tests whether the variable
# significantly differentiates between the identified clusters
cat("ANOVA — X1:\n")
summary(stats::aov(X1 ~ cluster_km, data = hatco))

cat("\nANOVA — X2:\n")
summary(stats::aov(X2 ~ cluster_km, data = hatco))
ANOVA — X1:
            Df Sum Sq Mean Sq F value   Pr(>F)    
cluster_km   1  81.56   81.56   87.72 2.88e-15 ***
Residuals   98  91.12    0.93                     
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

ANOVA — X2:
            Df Sum Sq Mean Sq F value   Pr(>F)    
cluster_km   1  66.46   66.46   86.75 3.73e-15 ***
Residuals   98  75.07    0.77                     
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Perfilado de los segmentos

El perfilado es la fase final donde se cruza la pertenencia a los grupos (variable cluster_km) con variables descriptivas o de clasificación que no participaron en la creación del cluster:

  1. Variables categóricas: se utiliza la prueba de Chi-cuadrado (\(\chi^2\)).
  2. Variables métricas: se utiliza el ANOVA.

Para obtener tablas con formato profesional se emplea el paquete expss, que incluye la significación estadística de forma integrada mediante letras de contraste.

# Descriptive label for the cluster variable
hatco <- expss::apply_labels(hatco, cluster_km = "K-means cluster")

# --- Categorical variable profiling (Chi-square) ---
# tab_last_sig_cases() adds significance letters between column proportions
cat("--- CATEGORICAL VARIABLES PROFILING ---\n")
hatco %>%
  expss::tab_cells(X8, X11) %>%
  expss::tab_cols(cluster_km) %>%
  expss::tab_stat_cases() %>%
  expss::tab_last_sig_cases() %>%
  expss::tab_pivot() %>%
  expss::set_caption("Categorical profiling — Chi-square significance")

# --- Metric variable profiling (ANOVA / T-test) ---
cat("\n--- METRIC VARIABLES PROFILING ---\n")
hatco %>%
  expss::tab_cells(X9, X10) %>%
  expss::tab_cols(cluster_km) %>%
  expss::tab_stat_mean_sd_n() %>%
  expss::tab_last_sig_means() %>%
  expss::tab_pivot() %>%
  expss::set_caption("Metric profiling — mean comparison significance")
--- CATEGORICAL VARIABLES PROFILING ---
Categorical profiling — Chi-square significance
 K-means cluster 
 1   2 
 tamaño de la empresa 
   pequeña  50.0  10.0 
   grande  2.0  38.0 
   #Chi-squared p-value  <0.05 
   #Total cases  52.0  48.0 
 especificación de las compras 
   especificacion  2.0  38.0 
   valor total  50.0  10.0 
   #Chi-squared p-value  <0.05 
   #Total cases  52.0  48.0 

--- METRIC VARIABLES PROFILING ---
Metric profiling — mean comparison significance
 K-means cluster 
 1     2 
 A     B 
 nivel de uso 
   Mean  49.2 B   42.7 
   Std. dev.  9.0     7.7 
   Unw. valid N  52.0     48.0 
 nivel de satisfacción 
   Mean  5.1 B   4.4 
   Std. dev.  0.8     0.8 
   Unw. valid N  52.0     48.0 

Al interpretar estas tablas, las letras de significación son el dato clave. Si un porcentaje o media tiene una letra diferente a la de otra columna, existe una diferencia estadísticamente significativa entre esos grupos.

Conclusión de la sesión

Con este proceso se ha completado el flujo de trabajo profesional del análisis de conglomerados:

  1. Limpieza: detección de atípicos (Mahalanobis) para garantizar la calidad de los datos de partida.
  2. Preparación: estandarización de variables para que las distancias no dependan de la escala de medición.
  3. Exploración: cluster jerárquico (Ward) para determinar el número natural de grupos presentes en el mercado.
  4. Optimización: K-means inicializado con los centroides jerárquicos para obtener segmentos finales estables y bien delimitados.
  5. Caracterización: análisis de los centros finales del K-means para identificar la “identidad” de cada segmento mediante las variables activas.
  6. Perfilado: descripción de cada segmento mediante variables de clasificación que no intervinieron en la construcción del cluster, con contraste de significación estadística integrado.