Profiling de un clustering sobre datos mixtos

Análisis de segmentación y caracterización de clústeres

Author

Sergi Ramirez Mitjans

1 Objetivo

Este documento realiza el profiling completo de un clustering jerárquico aplicado sobre una base de datos con variables mixtas (numéricas y categóricas). El flujo sigue estas fases:

  1. Carga y preparación de los datos.
  2. Cálculo de distancias de Gower para datos mixtos.
  3. Clustering jerárquico con criterio Ward.
  4. Asignación de individuos a clústeres.
  5. Fase 1 de profiling: detección de variables significativas.
  6. Cálculo de centroides/modas por clúster.
  7. Fase 2 de profiling: significación de modalidades mediante enfoque de Lebart.

2 Vista general de los datos

2.1 Dimensiones y estructura

Mostrar código
list(
  dimensiones = dim(dades),
  variables_numericas = varNum,
  variables_categoricas = varCat
)
$dimensiones
[1] 3900   18

$variables_numericas
[1] "Age"                   "Purchase.Amount..USD." "Review.Rating"        
[4] "Previous.Purchases"   

$variables_categoricas
 [1] "Gender"                 "Item.Purchased"         "Category"              
 [4] "Location"               "Size"                   "Color"                 
 [7] "Season"                 "Subscription.Status"    "Shipping.Type"         
[10] "Discount.Applied"       "Promo.Code.Used"        "Payment.Method"        
[13] "Frequency.of.Purchases"

2.2 Primeras filas

Mostrar código
head(dades)

2.3 Resumen descriptivo

Mostrar código
summary(dades)
  Customer.ID          Age           Gender     Item.Purchased
 Min.   :   1.0   Min.   :18.00   Female:1248   Blouse : 171  
 1st Qu.: 975.8   1st Qu.:31.00   Male  :2652   Jewelry: 171  
 Median :1950.5   Median :44.00                 Pants  : 171  
 Mean   :1950.5   Mean   :44.07                 Shirt  : 169  
 3rd Qu.:2925.2   3rd Qu.:57.00                 Dress  : 166  
 Max.   :3900.0   Max.   :70.00                 Sweater: 164  
                                                (Other):2888  
        Category    Purchase.Amount..USD.       Location    Size     
 Accessories:1240   Min.   : 20.00        Montana   :  96   L :1053  
 Clothing   :1737   1st Qu.: 39.00        California:  95   M :1755  
 Footwear   : 599   Median : 60.00        Idaho     :  93   S : 663  
 Outerwear  : 324   Mean   : 59.76        Illinois  :  92   XL: 429  
                    3rd Qu.: 81.00        Alabama   :  89            
                    Max.   :100.00        Minnesota :  88            
                                          (Other)   :3347            
     Color         Season    Review.Rating  Subscription.Status
 Olive  : 177   Fall  :975   Min.   :2.50   No :2847           
 Yellow : 174   Spring:999   1st Qu.:3.10   Yes:1053           
 Silver : 173   Summer:955   Median :3.70                      
 Teal   : 172   Winter:971   Mean   :3.75                      
 Green  : 169                3rd Qu.:4.40                      
 Black  : 167                Max.   :5.00                      
 (Other):2868                                                  
        Shipping.Type Discount.Applied Promo.Code.Used Previous.Purchases
 2-Day Shipping:627   No :2223         No :2223        Min.   : 1.00     
 Express       :646   Yes:1677         Yes:1677        1st Qu.:13.00     
 Free Shipping :675                                    Median :25.00     
 Next Day Air  :648                                    Mean   :25.35     
 Standard      :654                                    3rd Qu.:38.00     
 Store Pickup  :650                                    Max.   :50.00     
                                                                         
       Payment.Method    Frequency.of.Purchases
 Bank Transfer:612    Annually      :572       
 Cash         :670    Bi-Weekly     :547       
 Credit Card  :671    Every 3 Months:584       
 Debit Card   :636    Fortnightly   :542       
 PayPal       :677    Monthly       :553       
 Venmo        :634    Quarterly     :563       
                      Weekly        :539       

3 Clustering jerárquico sobre datos mixtos

3.1 Cálculo de distancias de Gower

Mostrar código
distancias <- cluster::daisy(dades, metric = "gower")
distancias2 <- distancias^2

cat("Número de observaciones:", nrow(dades), "\n")
Número de observaciones: 3900 
Mostrar código
cat("Número de distancias almacenadas:", length(distancias), "\n")
Número de distancias almacenadas: 7603050 

3.2 Construcción del dendrograma

Mostrar código
hc <- hclust(distancias2, method = "ward.D2")
plot(hc, main = "Dendrograma del clustering jerárquico", xlab = "", sub = "")

3.3 Dendrograma coloreado para k = 2

Mostrar código
as.dendrogram(hc) |>
  set("branches_k_color", k = 2) |>
  plot(main = "Dendrograma con ramas coloreadas (k = 2)")

3.4 Corte del árbol y asignación de clúster

Mostrar código
dades$cluster <- as.factor(cutree(hc, k = 2))

table(dades$cluster)

   1    2 
1675 2225 

3.5 Calidad interna básica del clustering

Mostrar código
fpc::cluster.stats(distancias2, clustering = as.numeric(dades$cluster))
$n
[1] 3900

$cluster.number
[1] 2

$cluster.size
[1] 1675 2225

$min.cluster.size
[1] 1675

$noisen
[1] 0

$diameter
[1] 0.7302687 0.7458370

$average.distance
[1] 0.2839729 0.2876841

$median.distance
[1] 0.2819710 0.2854016

$separation
[1] 0.07820618 0.07820618

$average.toother
[1] 0.4939339 0.4939339

$separation.matrix
           [,1]       [,2]
[1,] 0.00000000 0.07820618
[2,] 0.07820618 0.00000000

$ave.between.matrix
          [,1]      [,2]
[1,] 0.0000000 0.4939339
[2,] 0.4939339 0.0000000

$average.between
[1] 0.4939339

$average.within
[1] 0.2860902

$n.between
[1] 3726875

$n.within
[1] 3876175

$max.diameter
[1] 0.745837

$min.separation
[1] 0.07820618

$within.cluster.ss
[1] 171.1278

$clus.avg.silwidths
        1         2 
0.4186441 0.4107645 

$avg.silwidth
[1] 0.4141487

$g2
NULL

$g3
NULL

$pearsongamma
[1] 0.7335046

$dunn
[1] 0.1048569

$dunn2
[1] 1.716931

$entropy
[1] 0.6831698

$wb.ratio
[1] 0.5792075

$ch
[1] 3679.391

$cwidegap
[1] 0.1131343 0.1221295

$widestgap
[1] 0.1221295

$sindex
[1] 0.1153038

$corrected.rand
NULL

$vi
NULL

4 Profiling

4.1 Fase 1 del profiling: variables que diferencian los grupos

En esta fase se identifican las variables que discriminan significativamente entre clústeres.

  • Para variables numéricas:
    • Se evalúa normalidad con Shapiro-Wilk.
    • Si hay normalidad, se aplica ANOVA.
    • Si no hay normalidad, se aplica Kruskal-Wallis.
  • Para variables categóricas:
    • Se aplica Chi-cuadrado de independencia.

4.1.1 Test de Normalidad: Shapiro-Wilk

Los tests de normalidad sirven para comprobar si una variable sigue una distribución normal.

Esto es importante porque muchos métodos estadísticos (como ANOVA) requieren normalidad.

Hipótesis

  • \(H_{0}\): Los datos siguen una distribución Normal
  • \(H_{1}\): Los datos NO siguen una distribución Normal

Decisión

  • Si p-value > 0.05 \(\rightarrow\) No rechazamos \(H_{0}\) \(\rightarrow\) Los datos pueden considerarse normales
  • Si p-value ≤ 0.05 \(\rightarrow\) Rechazamos \(H_{0}\) \(\rightarrow\) Los datos no son normales

4.1.2 ANOVA (Analysis of Variance)

El ANOVA se utiliza para comparar la media de tres o más grupos.

Hipótesis

  • \(H_{0}\): Todas las medias son iguales

\[ \mu_{1} = \mu_{2} = \mu_{3} \] * \(H_{1}\): Al menos una media es diferente

Supuestos

Para aplicar ANOVA:

  1. Normalidad

  2. Independencia

  3. Homogeneidad de varianzas

Decisión

  • Si p-value > 0.05 \(\rightarrow\) No rechazamos \(H_{0}\) \(\rightarrow\) Las medias son similares
  • Si p-value ≤ 0.05 \(\rightarrow\) Rechazamos \(H_{0}\) \(\rightarrow\) Alguna media es distinta

4.1.3 Test de Kruskal-Wallis

El Kruskal-Wallis es la alternativa no paramétrica al ANOVA.

Se usa cuando:

  • Los datos no son normales

  • O las varianzas no son homogéneas.

En lugar de comparar medias, compara rangos.

Hipótesis

  • \(H_{0}\): Las distribuciones de los grupos son iguales
  • \(H_{1}\): Al menos un grupo es diferente

Decisión

  • Si p-value > 0.05 \(\rightarrow\) No rechazamos \(H_{0}\)
  • Si p-value ≤ 0.05 \(\rightarrow\) Rechazamos \(H_{0}\)

4.1.4 Test Chi-cuadrado de independencia

El test Chi-cuadrado sirve para analizar si dos variables categóricas están relacionadas.

Hipótesis

  • \(H_{0}\): Las variables son independientes
  • \(H_{1}\): Las variables están relacionadas

Decisión

  • Si p-value > 0.05 \(\rightarrow\) No rechazamos \(H_{0}\) \(\rightarrow\) No hay relación
  • Si p-value ≤ 0.05 \(\rightarrow\) Rechazamos \(H_{0}\) \(\rightarrow\) Existe relación
Mostrar código
columnasValidar <- setdiff(colnames(dades), c("Customer.ID", "cluster"))
variablesPasanFase1 <- c()
resultadosFase1 <- list()

for (cV in columnasValidar) {
  clase_var <- class(dades[[cV]])[1]
  
  # =====================
  # VARIABLES NUMÉRICAS
  # =====================
  if (clase_var %in% c("integer", "numeric")) {
    shapiro_ok <- FALSE
    p_shapiro <- NA_real_
    metodo <- NA_character_
    p_valor <- NA_real_
    detalle <- NULL
    
    # Shapiro falla si hay demasiados datos o valores constantes; controlamos eso.
    sh <- tryCatch(shapiro.test(dades[[cV]]), error = function(e) NULL)
    if (!is.null(sh)) {
      p_shapiro <- sh$p.value
      shapiro_ok <- p_shapiro > 0.05
    }
    
    if (!is.na(p_shapiro) && shapiro_ok) {
      test_obj <- aov(dades[[cV]] ~ dades$cluster)
      detalle <- summary(test_obj)
      p_valor <- detalle[[1]][["Pr(>F)"]][1]
      metodo <- "ANOVA"
    } else {
      test_obj <- kruskal.test(dades[[cV]] ~ dades$cluster)
      detalle <- test_obj
      p_valor <- test_obj$p.value
      metodo <- "Kruskal-Wallis"
    }
    
    if (!is.na(p_valor) && p_valor <= 0.05) {
      variablesPasanFase1 <- c(variablesPasanFase1, cV)
    }
    
    resultadosFase1[[cV]] <- list(
      tipo = "Numérica",
      metodo = metodo,
      p_shapiro = p_shapiro,
      p_valor = p_valor,
      detalle = detalle
    )
  }
  
  # =======================
  # VARIABLES CATEGÓRICAS
  # =======================
  if (clase_var %in% c("factor", "character")) {
    test_obj <- suppressWarnings(chisq.test(dades[[cV]], dades$cluster))
    p_valor <- test_obj$p.value
    
    if (!is.na(p_valor) && p_valor <= 0.05) {
      variablesPasanFase1 <- c(variablesPasanFase1, cV)
    }
    
    resultadosFase1[[cV]] <- list(
      tipo = "Categórica",
      metodo = "Chi-cuadrado",
      p_valor = p_valor,
      detalle = test_obj
    )
  }
}

variablesPasanFase1 <- unique(variablesPasanFase1)
variablesPasanFase1
[1] "Gender"              "Subscription.Status" "Discount.Applied"   
[4] "Promo.Code.Used"    

4.1.5 Tabla resumen de significación

Mostrar código
tablaFase1 <- dplyr::bind_rows(lapply(names(resultadosFase1), function(v) {
  x <- resultadosFase1[[v]]
  data.frame(
    variable = v,
    tipo = x$tipo,
    metodo = x$metodo,
    p_shapiro = ifelse(is.null(x$p_shapiro), NA, x$p_shapiro),
    p_valor = x$p_valor,
    significativa = ifelse(!is.na(x$p_valor) & x$p_valor <= 0.05, "Sí", "No")
  )
}))

tablaFase1 <- tablaFase1 |>
  arrange(tipo, p_valor)

tablaFase1

4.2 Visualización de variables del profiling

4.2.1 Variables numéricas

Mostrar código
vars_num_a_graficar <- columnasValidar[sapply(dades[columnasValidar], function(x) class(x)[1] %in% c("integer", "numeric"))]
vars_num_a_graficar
[1] "Age"                   "Purchase.Amount..USD." "Review.Rating"        
[4] "Previous.Purchases"   
Mostrar código
for (cV in vars_num_a_graficar) {
  cat("### ", cV, "\n\n", sep = "")
  
  graficoBoxplot <- ggpubr::ggboxplot(dades, "cluster", cV, fill = "cluster")
  graficoHistograma <- ggpubr::gghistogram(
    dades, x = cV,
    add = "mean", rug = TRUE,
    color = "cluster", fill = "cluster"
  )
  
  grafico <- ggpubr::ggarrange(
    graficoBoxplot, graficoHistograma,
    heights = c(2, 0.7),
    ncol = 2, nrow = 1, align = "v"
  )
  
  print(grafico)
  cat("\n")
}

4.2.2 Age

### Purchase.Amount..USD.

### Review.Rating

### Previous.Purchases

4.2.3 Variables categóricas

Mostrar código
vars_cat_a_graficar <- columnasValidar[sapply(dades[columnasValidar], function(x) class(x)[1] %in% c("factor", "character"))]
vars_cat_a_graficar
 [1] "Gender"                 "Item.Purchased"         "Category"              
 [4] "Location"               "Size"                   "Color"                 
 [7] "Season"                 "Subscription.Status"    "Shipping.Type"         
[10] "Discount.Applied"       "Promo.Code.Used"        "Payment.Method"        
[13] "Frequency.of.Purchases"
Mostrar código
for (cV in vars_cat_a_graficar) {
  cat("### ", cV, "\n\n", sep = "")
  
  tabla <- data.frame(table(dades[[cV]], dades$cluster))
  colnames(tabla) <- c("modalidad", "cluster", "Freq")
  
  grafico <- ggplot(tabla, aes(x = modalidad, y = Freq, fill = cluster)) +
    geom_bar(stat = "identity", position = "dodge") +
    labs(title = paste("Distribución de", cV, "por clúster"), x = cV, y = "Frecuencia") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
  
  print(grafico)
  cat("\n")
}

4.2.4 Gender

### Item.Purchased

### Category

### Location

### Size

### Color

### Season

### Subscription.Status

### Shipping.Type

### Discount.Applied

### Promo.Code.Used

### Payment.Method

### Frequency.of.Purchases

4.3 Centroides y modas por clúster

4.3.1 Variables numéricas significativas

Mostrar código
datosAnalizarNum <- dades[, intersect(variablesPasanFase1, varNum), drop = FALSE]

if (ncol(datosAnalizarNum) > 0) {
  psych::describeBy(datosAnalizarNum, dades$cluster, mat = TRUE)
} else {
  cat("No hay variables numéricas significativas en la fase 1.")
}
No hay variables numéricas significativas en la fase 1.

4.3.2 Variables categóricas significativas: moda por clúster

Mostrar código
variablesAnalizarCAT <- intersect(variablesPasanFase1, varCat)
listaModa <- list()

if (length(variablesAnalizarCAT) > 0) {
  for (vC in variablesAnalizarCAT) {
    tabla <- data.frame(table(dades[[vC]], dades$cluster))
    colnames(tabla) <- c("modalidad", "cluster", "Freq")
    
    calModa <- tabla |>
      dplyr::group_by(cluster) |>
      dplyr::filter(Freq == max(Freq)) |>
      dplyr::select(modalidad, cluster, Freq) |>
      as.data.frame()
    
    listaModa[[vC]] <- calModa
  }
  
  listaModa
} else {
  cat("No hay variables categóricas significativas en la fase 1.")
}
$Gender
  modalidad cluster Freq
1      Male       1 1675
2    Female       2 1248

$Subscription.Status
  modalidad cluster Freq
1       Yes       1 1053
2        No       2 2225

$Discount.Applied
  modalidad cluster Freq
1       Yes       1 1674
2        No       2 2222

$Promo.Code.Used
  modalidad cluster Freq
1       Yes       1 1674
2        No       2 2222

4.4 Fase 2 del profiling: significación de modalidades (Lebart)

La segunda fase profundiza en la interpretación de los clústeres identificando:

  • Qué medias numéricas caracterizan más a cada grupo.
  • Qué modalidades categóricas están sobrerrepresentadas o infrarrepresentadas.

4.4.1 Test de Lebart

El test de Lebart es un contraste estadístico utilizado para comparar proporciones o medias observadas con valores teóricos o esperados.

Se utiliza frecuentemente en análisis de datos y minería de datos para verificar si un grupo presenta un comportamiento significativamente diferente del esperado.

Existen dos versiones habituales:

  • Test de Lebart para proporciones

  • Test de Lebart para medias

4.4.1.1 Test de Lebart para proporciones

Este test se utiliza para comprobar si la proporción observada en un grupo es significativamente diferente de una proporción esperada.

Hipótesis

  • \(H_{0}\): La proporción observada es igual a la proporción esperada

\[ p = p_{0} \]

  • \(H_{1}\): La proporción observada es diferente de la esperada

\[ p \neq p_{0} \] Estadístico del test

El test utiliza un estadístico basado en la distribución normal:

\[ Z = \frac{p - p_{0}}{\sqrt{\frac{p_{0}(1-p_{0})}{n}}} \]

donde:

  • \(p\): proporción observada
  • \(p_{0}\): proporción esperada
  • \(n\): tamaño de la muestra

Decisión

  • Si p-value > 0.05 \(\rightarrow\) No rechazamos \(H_{0}\)
  • Si p-value ≤ 0.05 \(\rightarrow\) Rechazamos \(H_{0}\)

4.4.1.2 Test de Lebart para medias

El test de Lebart para medias se utiliza para comprobar si la media observada de un grupo es diferente de una media teórica o esperada.

Este contraste es conceptualmente similar a un test Z para medias.

Hipótesis

  • \(H_{0}\): La media observada es igual a la media esperada

\[ \mu = \mu_{0} \]

  • \(H_{1}\): La media observada es diferente de la esperada

\[ \mu \neq \mu_{0} \] Estadístico del test

El test utiliza un estadístico basado en la distribución normal:

\[ Z = \frac{\bar{x} - \mu_{0}}{\frac{s}{\sqrt{n}}} \]

donde:

  • \(\bar{x}\): media observada
  • \(\mu_{0}\): media esperada
  • \(s\): desviación estándar
  • \(n\): tamaño de la muestra

Decisión

  • Si p-value > 0.05 \(\rightarrow\) No rechazamos \(H_{0}\)
  • Si p-value ≤ 0.05 \(\rightarrow\) Rechazamos \(H_{0}\)

4.4.2 Resultados globales con catdes()

Mostrar código
res_catdes <- FactoMineR::catdes(dades, num.var = ncol(dades))
res_catdes

Link between the cluster variable and the categorical variables (chi-square test)
=================================================================================
                          p.value df
Subscription.Status  0.000000e+00  1
Discount.Applied     0.000000e+00  1
Promo.Code.Used      0.000000e+00  1
Gender              2.066222e-302  1

Description of each cluster by the categories
=============================================
$`1`
                             Cla/Mod      Mod/Cla    Global     p.value
Promo.Code.Used=Yes      99.82110912  99.94029851 43.000000 0.000000000
Discount.Applied=Yes     99.82110912  99.94029851 43.000000 0.000000000
Subscription.Status=Yes 100.00000000  62.86567164 27.000000 0.000000000
Gender=Male              63.15987934 100.00000000 68.000000 0.000000000
Location=Indiana         56.96202532   2.68656716  2.025641 0.012006802
Item.Purchased=Blouse    33.91812865   3.46268657  4.384615 0.014098571
Item.Purchased=Socks     32.70440252   3.10447761  4.076923 0.007237583
Location=Kansas          23.80952381   0.89552239  1.615385 0.001587204
Promo.Code.Used=No        0.04498426   0.05970149 57.000000 0.000000000
Discount.Applied=No       0.04498426   0.05970149 57.000000 0.000000000
Subscription.Status=No   21.84755883  37.13432836 73.000000 0.000000000
Gender=Female             0.00000000   0.00000000 32.000000 0.000000000
                           v.test
Promo.Code.Used=Yes           Inf
Discount.Applied=Yes          Inf
Subscription.Status=Yes       Inf
Gender=Male                   Inf
Location=Indiana         2.511944
Item.Purchased=Blouse   -2.454742
Item.Purchased=Socks    -2.685710
Location=Kansas         -3.158248
Promo.Code.Used=No           -Inf
Discount.Applied=No          -Inf
Subscription.Status=No       -Inf
Gender=Female                -Inf

$`2`
                            Cla/Mod     Mod/Cla    Global     p.value    v.test
Promo.Code.Used=No       99.9550157  99.8651685 57.000000 0.000000000       Inf
Discount.Applied=No      99.9550157  99.8651685 57.000000 0.000000000       Inf
Subscription.Status=No   78.1524412 100.0000000 73.000000 0.000000000       Inf
Gender=Female           100.0000000  56.0898876 32.000000 0.000000000       Inf
Location=Kansas          76.1904762   2.1573034  1.615385 0.001587204  3.158248
Item.Purchased=Socks     67.2955975   4.8089888  4.076923 0.007237583  2.685710
Item.Purchased=Blouse    66.0818713   5.0786517  4.384615 0.014098571  2.454742
Location=Indiana         43.0379747   1.5280899  2.025641 0.012006802 -2.511944
Promo.Code.Used=Yes       0.1788909   0.1348315 43.000000 0.000000000      -Inf
Discount.Applied=Yes      0.1788909   0.1348315 43.000000 0.000000000      -Inf
Subscription.Status=Yes   0.0000000   0.0000000 27.000000 0.000000000      -Inf
Gender=Male              36.8401207  43.9101124 68.000000 0.000000000      -Inf


Link between the cluster variable and the quantitative variables
================================================================
                 Eta2 P-value
Customer.ID 0.7341974       0

Description of each cluster by quantitative variables
=====================================================
$`1`
              v.test Mean in category Overall mean sd in category Overall sd
Customer.ID -53.5036          838.671       1950.5       484.5845   1125.833
            p.value
Customer.ID       0

$`2`
             v.test Mean in category Overall mean sd in category Overall sd
Customer.ID 53.5036         2787.495       1950.5       643.2368   1125.833
            p.value
Customer.ID       0

4.4.3 Gráfico de variables cuantitativas

Mostrar código
plot(res_catdes, show = "quanti", col.upper = "red", col.lower = "blue", 
     barplot = TRUE, cex.names = 1)

4.4.4 Gráfico de variables cualitativas

Mostrar código
plot(res_catdes, show = "quali", col.upper = "red", col.lower = "blue", 
     barplot = FALSE, cex.names = 1.2)

4.4.5 Gráfico conjunto

Mostrar código
plot(res_catdes, show = "all", col.upper = "red", col.lower = "blue", 
     barplot = FALSE, cex.names = 1.2)

4.4.6 Test de Lebart para variables numéricas

Mostrar código
dadesF <- FactoClass::Fac.Num(dades)
dades_num <- cbind(dadesF$numeric, dadesF$integer)
class_cluster <- dadesF$factor$cluster

if (ncol(dades_num) > 0) {
  prueba_num <- cluster.carac(
    dades_num, class_cluster,
    tipo.v = "co", v.lim = 1.96, neg = TRUE
  )
  prueba_num
} else {
  cat("No hay variables numéricas disponibles para el test de Lebart de medias.")
}
class: 1
            Test.Value Class.Mean Frequency Global.Mean
Customer.ID    -53.504    838.671      1675      1950.5
------------------------------------------------------------ 
class: 2
            Test.Value Class.Mean Frequency Global.Mean
Customer.ID     53.504   2787.495      2225      1950.5

4.4.7 Test de Lebart para variables categóricas

Mostrar código
dades_cat <- dadesF$factor |> dplyr::select(-cluster)
class_cluster <- dadesF$factor$cluster

if (ncol(dades_cat) > 0) {
  prueba_cat <- cluster.carac(
    dades_cat, class_cluster,
    tipo.v = "ca", v.lim = 1.96, neg = FALSE
  )
  prueba_cat
} else {
  cat("No hay variables categóricas disponibles para el test de Lebart de proporciones.")
}
class: 1
                        Test.Value p.Value Class.Cat Cat.Class Global Weight
Gender.Male                    Inf   0.000      63.2     100.0   68.0   2652
Subscription.Status.Yes        Inf   0.000     100.0      62.9   27.0   1053
Discount.Applied.Yes           Inf   0.000      99.8      99.9   43.0   1677
Promo.Code.Used.Yes            Inf   0.000      99.8      99.9   43.0   1677
Location.Indiana             2.657   0.008      57.0       2.7    2.0     79
Item.Purchased.Hat           2.022   0.043      50.0       4.6    3.9    154
------------------------------------------------------------ 
class: 2
                       Test.Value p.Value Class.Cat Cat.Class Global Weight
Gender.Female                 Inf   0.000     100.0      56.1   32.0   1248
Subscription.Status.No        Inf   0.000      78.2     100.0   73.0   2847
Discount.Applied.No           Inf   0.000     100.0      99.9   57.0   2223
Promo.Code.Used.No            Inf   0.000     100.0      99.9   57.0   2223
Location.Kansas             3.251   0.001      76.2       2.2    1.6     63
Item.Purchased.Socks        2.840   0.005      67.3       4.8    4.1    159
Item.Purchased.Blouse       2.627   0.009      66.1       5.1    4.4    171
Color.Pink                  2.018   0.044      64.1       4.4    3.9    153

4.5 Conclusiones automáticas del profiling

4.5.1 Variables significativas detectadas

Mostrar código
sig <- tablaFase1 |> dplyr::filter(significativa == "Sí")
sig

4.5.2 Resumen interpretativo

Mostrar código
cat("Número total de variables analizadas:", length(columnasValidar), "\n")
Número total de variables analizadas: 17 
Mostrar código
cat("Variables significativas en fase 1:", length(variablesPasanFase1), "\n\n")
Variables significativas en fase 1: 4 
Mostrar código
if (length(variablesPasanFase1) > 0) {
  cat("Variables que mejor diferencian los clústeres:\n")
  cat(paste0("- ", variablesPasanFase1, collapse = "\n"))
} else {
  cat("No se han detectado variables significativas con el umbral p <= 0.05.")
}
Variables que mejor diferencian los clústeres:
- Gender
- Subscription.Status
- Discount.Applied
- Promo.Code.Used

Aquesta web està creada por Dante Conti y Sergi Ramírez, (c) 2026