Examen práctico módulo 4: análisis de datos multivariantes II

DESCRIPCIÓN DATASET PARA EJERCICIOS 1, 2 Y 3

El dataset para las preguntas 1, 2 y 3 incluye información a nivel de las secciones censales de la ciudad de Santander, relativa a los censos de 2001 y 2011. Dicho dataset dispone de las siguientes variables:

  • COD SECC: código de sección censal.
  • 75_2001, 75_2011: porcentaje de población de 75 años y más.
  • HOG_64_2001, HOG_64_2011: porcentaje de hogares unipersonales de mayores de 64 años.
  • HOG_MONO_2001, HOG_MONO_2011: porcentaje de hogares con un adulto y un menor o más.
  • EXT_2001, EXT_2011: porcentaje de población extranjera.
  • EXT_MEN_2001, EXT_MEN_2011: porcentaje de población extranjera infantil.

EXTRACCIÓN DE DATOS 2011, ANÁLISIS EXPLORATORIO Y MATRIZ DE CORRELACIONES

En primer lugar se cargan las librerías necesarias para el trabajo solicitado.

library(ggplot2)
library(esquisse)
library(formattable)
library(corrplot)
library(knitr)
library(kableExtra)
library(psych)
library(cluster)
library(kableExtra)
library(fpc)
library(plm)
library(MASS)
library(ggrepel)
library(RGCCA)
library(ca)
library(FactoMineR)

A continuación, se procede a leer el archivo crudo que se propone para esta prueba y se hace una primera aproximación exploratoria observando el encabezado y la cola del dataset, así como un análisis inicial somero de las caracteristicas de las variables.

datos_raw <- read.csv2("C:/Users/Oscar/OneDrive/Escritorio/Proyectos_R/Modulo_4/EVALUACION/Indicador_SOCIODEMO.csv", stringsAsFactors = FALSE,
  check.names = FALSE
)

formattable(head(datos_raw))
COD SECC 75_2001 75_2011 HOG_64_2001 HOG_64_2011 HOG_MONO_2001 HOG_MONO_2011 EXT_2001 EXT_2011 EXT_MEN_2001 EXT_MEN_2011
3907501001 14.20 8.33 14.77 12.73 1.87 3.01 5.27 30.83 6.93 0.0
3907501002 14.17 18.45 11.46 14.12 1.04 0.00 1.55 14.00 1.98 0.0
3907501003 19.15 12.80 12.42 22.46 1.35 5.32 1.77 6.37 5.06 0.0
3907501004 21.11 27.78 15.56 27.99 1.11 0.00 2.04 9.36 0.00 0.0
3907501005 17.17 11.16 17.03 26.78 1.39 5.39 1.62 39.31 0.00 25.9
3907501006 12.15 8.86 11.87 13.85 2.04 12.01 3.25 23.00 3.97 23.0
formattable(tail(datos_raw))
COD SECC 75_2001 75_2011 HOG_64_2001 HOG_64_2011 HOG_MONO_2001 HOG_MONO_2011 EXT_2001 EXT_2011 EXT_MEN_2001 EXT_MEN_2011
139 3907508020 2.58 1.60 2.17 5.57 2.99 10.94 1.87 9.95 0.90 27.77
140 3907508021 6.49 3.04 5.79 4.15 1.38 4.56 1.30 1.24 1.15 0.00
141 3907508022 1.49 2.83 0.89 5.48 2.84 3.91 2.27 1.50 1.94 0.00
142 3907508023 9.60 2.48 1.10 5.79 2.75 5.07 1.61 1.88 0.77 0.00
143 3907508024 5.21 5.56 4.49 0.00 1.22 5.83 1.16 0.73 1.40 0.00
144 3907508025 2.17 2.05 2.45 0.00 2.45 4.13 2.17 5.37 0.00 23.81
dim(datos_raw)
## [1] 144  11
str(datos_raw)
## 'data.frame':    144 obs. of  11 variables:
##  $ COD SECC     : num  3.91e+09 3.91e+09 3.91e+09 3.91e+09 3.91e+09 ...
##  $ 75_2001      : num  14.2 14.2 19.1 21.1 17.2 ...
##  $ 75_2011      : num  8.33 18.45 12.8 27.78 11.16 ...
##  $ HOG_64_2001  : num  14.8 11.5 12.4 15.6 17 ...
##  $ HOG_64_2011  : num  12.7 14.1 22.5 28 26.8 ...
##  $ HOG_MONO_2001: num  1.87 1.04 1.35 1.11 1.39 2.04 1.8 1.86 0.55 0.75 ...
##  $ HOG_MONO_2011: num  3.01 0 5.32 0 5.39 ...
##  $ EXT_2001     : num  5.27 1.55 1.77 2.04 1.62 3.25 3.86 3.85 2.59 5.27 ...
##  $ EXT_2011     : num  30.83 14 6.37 9.36 39.31 ...
##  $ EXT_MEN_2001 : num  6.93 1.98 5.06 0 0 3.97 3.03 3.03 3.85 4.35 ...
##  $ EXT_MEN_2011 : num  0 0 0 0 25.9 ...

Como puede comprobarse, el dataset consta de 144 filas, que se corresponden con el total de secciones censales existentes en la ciudad de Santander, mientras que hay un total de 11 columnas correspondientes a las variables demográficas registradas. Todas las variables son de tipo numérico, informando sobre porcentajes poblacionales

A continuación, se procede a extraer y prepar los datos de 2011 para el análisis.

# Variables de 2011 seleccionadas
datos_2011 <- data.frame(
  COD_SECC = datos_raw[["COD SECC"]],
  P75 = datos_raw[["75_2011"]],
  HOG_64 = datos_raw[["HOG_64_2011"]],
  HOG_MONO = datos_raw[["HOG_MONO_2011"]],
  EXT = datos_raw[["EXT_2011"]],
  EXT_MEN = datos_raw[["EXT_MEN_2011"]]
)

# Matriz numérica para los análisis
X2011 <- datos_2011[, c("P75", "HOG_64", "HOG_MONO", "EXT", "EXT_MEN")]

# Comprobación de estructura
str(datos_2011)
## 'data.frame':    144 obs. of  6 variables:
##  $ COD_SECC: num  3.91e+09 3.91e+09 3.91e+09 3.91e+09 3.91e+09 ...
##  $ P75     : num  8.33 18.45 12.8 27.78 11.16 ...
##  $ HOG_64  : num  12.7 14.1 22.5 28 26.8 ...
##  $ HOG_MONO: num  3.01 0 5.32 0 5.39 ...
##  $ EXT     : num  30.83 14 6.37 9.36 39.31 ...
##  $ EXT_MEN : num  0 0 0 0 25.9 ...
summary(X2011)
##       P75             HOG_64          HOG_MONO           EXT        
##  Min.   : 0.000   Min.   : 0.000   Min.   : 0.000   Min.   : 0.000  
##  1st Qu.: 6.763   1st Qu.: 6.655   1st Qu.: 2.663   1st Qu.: 1.827  
##  Median :12.330   Median :12.860   Median : 4.645   Median : 5.545  
##  Mean   :12.387   Mean   :13.315   Mean   : 5.143   Mean   : 8.550  
##  3rd Qu.:16.233   3rd Qu.:18.840   3rd Qu.: 7.470   3rd Qu.:12.338  
##  Max.   :31.930   Max.   :35.020   Max.   :19.010   Max.   :46.010  
##     EXT_MEN      
##  Min.   : 0.000  
##  1st Qu.: 0.000  
##  Median : 0.000  
##  Mean   : 9.128  
##  3rd Qu.:18.247  
##  Max.   :75.000

Se continúa comprobando si hay datos perdidos e identificadores de sección repetidos en el dataset.

# Comprobación NA
colSums(is.na(datos_2011))
## COD_SECC      P75   HOG_64 HOG_MONO      EXT  EXT_MEN 
##        0        0        0        0        0        0
# Comprobación de identificadores duplicados
anyDuplicated(datos_2011[["COD SECC"]])
## [1] 0

Como se desprende de los reultados, no aparecen valores perdidos y no hay códigos de sección censal duplicados. Por tanto, no es necesario imputar datos ni eliminar registros en esta fase.

El siguiente paso será realizar un análisis exploratorio de las variables del dataset.

# Estadísticos descriptivos básicos
resumen <- data.frame(
  Media = sapply(X2011, mean),
  Desv_tipica = sapply(X2011, sd),
  Minimo = sapply(X2011, min),
  Q1 = sapply(X2011, quantile, probs = 0.25),
  Mediana = sapply(X2011, median),
  Q3 = sapply(X2011, quantile, probs = 0.75),
  Maximo = sapply(X2011, max)
)

formattable(round(resumen, 2))
Media Desv_tipica Minimo Q1 Mediana Q3 Maximo
P75 12.39 7.26 0 6.76 12.33 16.23 31.93
HOG_64 13.32 7.68 0 6.65 12.86 18.84 35.02
HOG_MONO 5.14 4.19 0 2.66 4.64 7.47 19.01
EXT 8.55 9.52 0 1.83 5.54 12.34 46.01
EXT_MEN 9.13 14.84 0 0.00 0.00 18.25 75.00
# Revisión de ceros
ceros <- colSums(X2011 == 0)

porcentaje_ceros <- round(ceros / nrow(X2011) * 100, 1)

tabla_ceros <- data.frame(
  Variable = names(ceros),
  Numero_ceros = as.numeric(ceros),
  Porcentaje_ceros = porcentaje_ceros
)

formattable(tabla_ceros)
Variable Numero_ceros Porcentaje_ceros
P75 P75 1 0.7
HOG_64 HOG_64 5 3.5
HOG_MONO HOG_MONO 30 20.8
EXT EXT 21 14.6
EXT_MEN EXT_MEN 93 64.6
# Histogramas
par(mfrow = c(2, 3))
for (v in names(X2011)) {
  hist(X2011[[v]],
       main = paste("Histograma de", v),
       xlab = v,
       col = "blue",
       border = "white")
}
par(mfrow = c(1, 1))

# Boxplots para detectar dispersión y posibles valores extremos
boxplot(X2011,
        main = "Distribución de variables sociodemográficas 2011",
        las = 2,
        col = "blue")

Tras la revisión específica de valores iguales a cero en las variables seleccionadas para 2011, los resultados muestran una presencia muy reducida de ceros en las variables de envejecimiento, con solo un caso en P75 y cinco en HOG_64. En cambio, las variables vinculadas a hogares monoparentales y población extranjera presentan una mayor frecuencia de valores nulos, especialmente EXT_MEN, con 93 secciones censales con valor cero. Esto sugiere una distribución muy asimétrica de la población extranjera infantil, concentrada solo en determinadas secciones. Esta comprobación no debe confundirse con un análisis de valores perdidos, ya que los ceros representan porcentajes nulos y pueden ser valores perfectamente válidos desde el punto de vista sociodemográfico. Por ello, se mantienen los ceros en el análisis, pero se advierte que esta estructura puede influir en las correlaciones y en la interpretación posterior del ACP y del análisis factorial.

En cuanto a la exploración gráfica, se evidencian diferencias importantes en la forma de las distribuciones. Las variables vinculadas al envejecimiento, P75 y HOG_64, presentan distribuciones relativamente más equilibradas, aunque con cierta cola hacia valores altos. Esto sugiere que la presencia de población mayor y de hogares unipersonales de mayores de 64 años está relativamente extendida entre las secciones censales, aunque con distinta intensidad. Por el contrario, HOG_MONO, EXT y especialmente EXT_MEN presentan distribuciones claramente asimétricas hacia la derecha, con una elevada concentración de secciones en valores bajos y algunos casos con valores elevados. Esta situación es especialmente visible en la variable de población extranjera infantil, donde se aprecia una fuerte acumulación de ceros y varios valores extremos.

El gráfico de boxplot confirma esta interpretación, mostrando mayor dispersión y presencia de valores atípicos en las variables relacionadas con población extranjera, especialmente en EXT_MEN. Estos valores no deben considerarse automáticamente errores, ya que pueden reflejar la concentración territorial de determinados perfiles sociodemográficos. No obstante, conviene tenerlos en cuenta porque pueden influir en las correlaciones, en el análisis de componentes principales, en el análisis factorial y en la posterior clasificación mediante clúster.

Para finalizar con la preparación de los procesos previos a los análisis ACP y AF, se genera una matriz de correlación de las cinco variables de la que consta el dataset.

# Matriz de correlaciones
cor_2011 <- cor(X2011, use = "pairwise.complete.obs")

# Nombres más descriptivos para la tabla y el gráfico
nombres_variables <- c(
  P75      = "Población ≥ 75 años",
  HOG_64   = "Hogares unipersonales ≥ 64",
  HOG_MONO = "Hogares monoparentales",
  EXT      = "Población extranjera",
  EXT_MEN  = "Población extranjera infantil"
)

# Aplicamos nombres descriptivos
cor_tabla <- round(cor_2011, 3)
rownames(cor_tabla) <- nombres_variables[rownames(cor_tabla)]
colnames(cor_tabla) <- nombres_variables[colnames(cor_tabla)]

# Tabla formateada
kable(
  cor_tabla,
  format = "html",
  caption = "Matriz de correlaciones entre variables sociodemográficas. Año 2011",
  align = "c"
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed", "responsive")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  ) %>%
  column_spec(
    1,
    bold = TRUE,
    width = "16em"
  )
Matriz de correlaciones entre variables sociodemográficas. Año 2011
Población ≥ 75 años Hogares unipersonales ≥ 64 Hogares monoparentales Población extranjera Población extranjera infantil
Población ≥ 75 años 1.000 0.636 -0.217 0.088 -0.057
Hogares unipersonales ≥ 64 0.636 1.000 -0.154 0.175 0.051
Hogares monoparentales -0.217 -0.154 1.000 -0.063 0.104
Población extranjera 0.088 0.175 -0.063 1.000 0.374
Población extranjera infantil -0.057 0.051 0.104 0.374 1.000
# Correlograma visual
corrplot(
  cor_2011,
  method = "color",
  type = "lower",
  order = "hclust",
  addCoef.col = "black",
  number.cex = 0.75,
  tl.col = "#1F2937",
  tl.srt = 35,
  tl.cex = 0.85,
  diag = FALSE,
  col = colorRampPalette(c("#C0392B", "#F7F7F7", "#1F77B4"))(200),
  mar = c(0, 0, 2, 0),
  title = "Correlograma de variables sociodemográficas. Año 2011"
)

La matriz de correlaciones muestra una relación positiva destacada entre la población de 75 años o más y los hogares unipersonales de mayores de 64 años. Esta asociación sugiere una dimensión común vinculada al envejecimiento demográfico y a la posible vulnerabilidad residencial. También se observa una correlación positiva moderada entre población extranjera total y población extranjera infantil. El resto de relaciones son débiles, lo que indica que las variables no son redundantes y pueden aportar información diferenciada en los análisis multivariantes posteriores.

Bloque Relación Intensidad
Envejecimiento / hogares mayores Población ≥ 75 años – Hogares unipersonales ≥ 64 0.636
Extranjería / infancia extranjera Población extranjera – Población extranjera infantil 0.374
Resto de relaciones Valores bajos o débiles Entre -0.217 y 0.175

Ejercicio 1 - Análisis de componentes principales

Dada la variabilidad detectada en la dispersión de las variables durante el análisis exploratorio, se procede a realizar una transformación para la estandarización de las variables, como paso previo al análisis de Componentes Principales (ACP). Esto evitará que las variables con mayor dispersión, como la población extranjera infantil, dominen artificialmente los análisis posteriores. En el ACP se trabajará con la matriz de correlaciones, lo que es equivalente a utilizar las variables estandarizadas.

# Datos estandarizados
X2011_z <- scale(X2011)

# Comprobación: medias cercanas a 0 y desviaciones típicas cercanas a 1
round(colMeans(X2011_z), 3)
##      P75   HOG_64 HOG_MONO      EXT  EXT_MEN 
##        0        0        0        0        0
round(apply(X2011_z, 2, sd), 3)
##      P75   HOG_64 HOG_MONO      EXT  EXT_MEN 
##        1        1        1        1        1
# KMO: adecuación muestral
KMO(cor_2011)
## Kaiser-Meyer-Olkin factor adequacy
## Call: KMO(r = cor_2011)
## Overall MSA =  0.53
## MSA for each item = 
##      P75   HOG_64 HOG_MONO      EXT  EXT_MEN 
##     0.52     0.53     0.67     0.53     0.48
# Test de Bartlett
cortest.bartlett(cor_2011, n = nrow(X2011))
## $chisq
## [1] 109.4898
## 
## $p.value
## [1] 6.761642e-19
## 
## $df
## [1] 10

Antes de aplicar el análisis de componentes principales, se comprueba la adecuación de la matriz de correlaciones mediante el índice KMO y el test de esfericidad de Bartlett. El índice KMO global obtiene un valor de 0.53, lo que indica una adecuación muestral baja, aunque ligeramente superior al umbral mínimo habitualmente aceptado de 0.50. Por tanto, los datos pueden ser utilizados para un análisis exploratorio de reducción de dimensionalidad, si bien la estructura subyacente no parece especialmente robusta.

El análisis de los MSA individuales muestra que la mayoría de las variables se sitúan en valores próximos al mínimo aceptable, especialmente P75, HOG_64 y EXT. La variable HOG_MONO presenta la mejor adecuación relativa, con un MSA de 0.67. En cambio, EXT_MEN obtiene un MSA de 0.48, por debajo del umbral de 0.50, por lo que debe considerarse una variable menos integrada en la estructura común del conjunto.

Por su parte, el test de esfericidad de Bartlett resulta estadísticamente significativo, \(\chi^2 = 109.49\), \(p < 0.001\), por lo que se rechaza la hipótesis de que la matriz de correlaciones sea una matriz identidad. Esto confirma que existen asociaciones suficientes entre las variables para justificar la aplicación de un PCA. En conjunto, los resultados apoyan la realización del análisis de componentes principales con finalidad exploratoria, aunque aconsejan interpretar con cautela los resultados, especialmente si se pretende derivar de ellos una estructura factorial latente.

El siguiente paso será aplicar el Análisis de Componentes Principales (ACP) sobre la matriz previamente estandarizada X2011_z, por lo que no se vuelve a centrar ni escalar dentro de prcomp().

# Se realiza el análisis PCA
acp_2011 <- prcomp(
  X2011_z,
  center = FALSE,
  scale. = FALSE
)
acp_2011
## Standard deviations (1, .., p=5):
## [1] 1.3357658 1.1693186 0.9528702 0.7672378 0.5931343
## 
## Rotation (n x k) = (5 x 5):
##                  PC1         PC2         PC3         PC4         PC5
## P75       0.63338859 -0.20120285  0.23938618  0.04749359  0.70623999
## HOG_64    0.64355196 -0.05491227  0.30395817  0.04497481 -0.69886495
## HOG_MONO -0.31249283  0.23951156  0.88641325 -0.23490293  0.06383256
## EXT       0.28127333  0.61752500 -0.25378972 -0.68703320  0.05589586
## EXT_MEN   0.08884431  0.71958262  0.01288453  0.68449077  0.07492609
summary(acp_2011)
## Importance of components:
##                           PC1    PC2    PC3    PC4     PC5
## Standard deviation     1.3358 1.1693 0.9529 0.7672 0.59313
## Proportion of Variance 0.3568 0.2735 0.1816 0.1177 0.07036
## Cumulative Proportion  0.3568 0.6303 0.8119 0.9296 1.00000
# Cálculo de autovalores a partir de las desviaciones típicas del ACP
desv_tipica <- acp_2011$sdev
autovalores <- desv_tipica^2
prop_varianza <- autovalores / sum(autovalores)
varianza_acumulada <- cumsum(prop_varianza)

# Tabla resumen
tabla_acp <- data.frame(
  Componente = paste0("PC", seq_along(desv_tipica)),
  `Desviación típica` = round(desv_tipica, 4),
  Autovalor = round(autovalores, 4),
  `Proporción de varianza` = round(prop_varianza, 4),
  `Varianza acumulada` = round(varianza_acumulada, 4)
)

# Mostrar tabla formateada
kable(
  tabla_acp,
  caption = "Autovalores y varianza explicada por los componentes principales",
  align = "c"
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Autovalores y varianza explicada por los componentes principales
Componente Desviación.típica Autovalor Proporción.de.varianza Varianza.acumulada
PC1 1.3358 1.7843 0.3569 0.3569
PC2 1.1693 1.3673 0.2735 0.6303
PC3 0.9529 0.9080 0.1816 0.8119
PC4 0.7672 0.5887 0.1177 0.9296
PC5 0.5931 0.3518 0.0704 1.0000
# Datos para el gráfico de sedimentación
datos_scree <- data.frame(
  Componente = paste0("PC", seq_along(autovalores)),
  Numero = seq_along(autovalores),
  Autovalor = autovalores
)

# Gráfico de sedimentación
ggplot(datos_scree, aes(x = Numero, y = Autovalor)) +
  geom_col(fill = "#4E79A7", width = 0.6) +
  geom_line(linewidth = 0.8, color = "#333333") +
  geom_point(size = 2.8, color = "#333333") +
  geom_hline(
    yintercept = 1,
    linetype = "dashed",
    color = "#B2182B"
  ) +
  scale_x_continuous(
    breaks = datos_scree$Numero,
    labels = datos_scree$Componente
  ) +
  labs(
    title = "Gráfico de sedimentación del ACP",
    subtitle = "Autovalores de los componentes principales",
    x = "Componentes principales",
    y = "Autovalor"
  ) +
  theme_minimal(base_size = 12)

Los resultados del ACP muestran que los dos primeros componentes explican conjuntamente el 63.03 % de la varianza total.

El primer componente obtiene un autovalor de 1.7843 y explica el 35.68 % de la varianza, estando definido principalmente por las variables de población de 75 años o más y hogares unipersonales de mayores de 64 años, por lo que puede interpretarse como una dimensión de envejecimiento y vulnerabilidad residencial.

El segundo componente obtiene un autovalor de 1.3673 y explica el 27.35 % adicional, presentando cargas elevadas en población extranjera total y población extranjera infantil, por lo que puede interpretarse como una dimensión de presencia de población extranjera.

El tercer componente obtiene un autovalor de 0.9080 y explica un 18.16 % adicional de la varianza y está fuertemente asociado a los hogares monoparentales, lo que sugiere que esta variable constituye una dimensión parcialmente diferenciada. Este componente obtiene un autovalor inferior a uno, por lo que siguiendo

El resto de componentes tienen autovalores bajos y explican proporciones muy marginales de la varianza total.

Justificación de la selección de componentes

Para decidir cuántos componentes principales conservar, se han utilizado dos criterios complementarios: el criterio de los autovalores y el gráfico de sedimentación.

Dado que el ACP se ha realizado sobre variables estandarizadas, cada variable original aporta inicialmente una unidad de varianza. Por tanto, siguiendo el criterio de Kaiser, deben conservarse aquellos componentes cuyo autovalor sea superior a 1, ya que explican más varianza que una variable original individual.

De acuerdo con este criterio, se retienen los dos primeros componentes, ya que son los únicos con autovalores superiores a 1. El primer componente explica el 35.68 % de la varianza total y el segundo añade un 27.35 %, alcanzando conjuntamente una varianza acumulada del 63.03 %. Este nivel total de varianza explicada por estos dos componentes se consideraría suficiente para variables demográficas que se pueden considerar propias de ámbito de las ciencias sociales.

Por otro lado, el gráfico de sedimentación confirma esta decisión. Se observa que los dos primeros componentes concentran la mayor parte de la varianza explicada y que, a partir del tercer componente, la aportación marginal disminuye. Aunque el tercer componente todavía explica un 18.16 % de la varianza, su autovalor es inferior a 1, por lo que no se retiene en la solución principal según el criterio de Kaiser.

Por tanto, se opta por una solución de dos componentes principales, que permite reducir las cinco variables originales a dos dimensiones sintéticas, conservando una proporción razonable de la información total del dataset. Esta reducción facilita la interpretación posterior de la estructura sociodemográfica, aunque debe considerarse exploratoria debido a la adecuación muestral moderada-baja observada previamente mediante el índice KMO.

Interpretación de los coeficientes de los dos componentes seleccionados.

Atendiendo a los coeficientes de la matriz Rotation, los dos primeros componentes son bastante interpretables y tienen una lógica sociodemográfica clara.

Interpretación del PC1

Los coeficientes del primer componente son:

Variable Coeficiente PC1
P75 0.633
HOG_64 0.644
HOG_MONO -0.312
EXT 0.281
EXT_MEN 0.089

El PC1 está dominado por dos variables: P75 y HOG_64. Ambas presentan coeficientes positivos elevados y muy similares. Esto indica que el componente recoge principalmente una dimensión asociada al envejecimiento demográfico y a la presencia de hogares unipersonales de personas mayores.

La variable HOG_MONO aparece con coeficiente negativo moderado. Esto sugiere que las secciones con mayor puntuación en este componente tienden a estar más asociadas a población mayor y hogares unipersonales de mayores, y relativamente menos a hogares monoparentales.

Por tanto, el PC1 puede interpretarse como:

Componente de envejecimiento y vulnerabilidad residencial de personas mayores.

Una sección censal con puntuación alta en PC1 sería una sección con mayor peso relativo de población de 75 años o más y de hogares unipersonales formados por personas mayores.

Interpretación del PC2

Los coeficientes del segundo componente son:

Variable Coeficiente PC2
P75 -0.201
HOG_64 -0.055
HOG_MONO 0.240
EXT 0.618
EXT_MEN 0.720

El PC2 está definido principalmente por EXT y EXT_MEN. Ambas variables tienen coeficientes positivos elevados, especialmente EXT_MEN, que es la variable con mayor peso en este componente.

Esto indica que el segundo componente recoge una dimensión vinculada a la presencia de población extranjera, especialmente de población extranjera infantil. La variable HOG_MONO tiene un peso positivo, pero bastante menor, por lo que puede considerarse secundaria en la interpretación.

Las variables de envejecimiento tienen coeficientes negativos o muy bajos, especialmente HOG_64, lo que refuerza la idea de que este segundo eje representa una dimensión distinta del envejecimiento.

Por tanto, el PC2 puede interpretarse como:

Componente de presencia de población extranjera y población extranjera infantil.

Una sección censal con puntuación alta en PC2 sería una sección con mayor presencia relativa de población extranjera, especialmente menor de edad.

MATIZ METODOLÓGICO: el signo de los componentes en ACP es arbitrario. Lo importante no es que los coeficientes sean positivos o negativos en sí mismos, sino qué variables pesan más en cada componente y si lo hacen en el mismo sentido o en sentido opuesto.

Creación de un nuevo dataset con las nuevas variables retenidas y realizar un análisis estadístico descriptivo de las dos variables principales obtenidas.

# NUEVO DATASET CON LOS DOS COMPONENTES RETENIDOS

# Extraer puntuaciones de los dos primeros componentes
componentes_2011 <- as.data.frame(acp_2011$x[, 1:2])

# Renombrar variables para facilitar la interpretación
colnames(componentes_2011) <- c("PC1_Envejecimiento", "PC2_Extranjeria")

# Añadir identificador de sección censal
componentes_2011$COD_SECC <- datos_2011$COD_SECC

# Reordenar columnas
componentes_2011 <- componentes_2011[, c("COD_SECC", 
                                         "PC1_Envejecimiento", 
                                         "PC2_Extranjeria")]

# Mostrar primeras filas
formattable(head(componentes_2011))
COD_SECC PC1_Envejecimiento PC2_Extranjeria
3907501001 0.3594631 0.9969772
3907501002 1.0858510 -0.5565985
3907501003 0.6703931 -0.6506197
3907501004 2.9252472 -1.2151805
3907501005 2.0121944 2.7593977
3907501006 -0.2647118 2.0955259

En la anterior tabla puede verse una muestra con las primeras seis secciones censales del nuevo dataset generado con las dos variables basasdas en los dos componentes principales extraídos del dataset original.

Las puntuaciones de las seis primeras secciones censales en los dos componentes principales permiten caracterizar sus perfiles sociodemográficos relativos. El primer componente representa principalmente una dimensión de envejecimiento y hogares unipersonales de personas mayores, mientras que el segundo componente se asocia a la presencia de población extranjera, especialmente extranjera infantil. Las secciones 3907501002, 3907501003 y especialmente 3907501004 presentan puntuaciones positivas en el primer componente y negativas en el segundo, lo que sugiere un perfil más envejecido y con menor presencia extranjera. Por el contrario, la sección 3907501006 presenta baja puntuación en envejecimiento y alta puntuación en extranjería. La sección 3907501005 destaca por obtener puntuaciones elevadas en ambos componentes, lo que apunta a un perfil más complejo, con coexistencia de envejecimiento y presencia extranjera. Por tanto, puede decirse que. en conjunto, las puntuaciones de los componentes permiten sintetizar y comparar los perfiles de las secciones censales de forma más clara que las variables originales.

Seguidamente, se realiza un análisis descriptivo de las dos nuevas variables que componen el nuevo dataset.

Análisis descriptivo de las dos nuevas variables

# Seleccionar solo los componentes
componentes_numericos <- componentes_2011[, c("PC1_Envejecimiento", 
                                              "PC2_Extranjeria")]

# Tabla descriptiva
tabla_desc_componentes <- data.frame(
  Variable = names(componentes_numericos),
  Media = round(sapply(componentes_numericos, mean), 4),
  Desv_tipica = round(sapply(componentes_numericos, sd), 4),
  Minimo = round(sapply(componentes_numericos, min), 4),
  Q1 = round(sapply(componentes_numericos, quantile, probs = 0.25), 4),
  Mediana = round(sapply(componentes_numericos, median), 4),
  Q3 = round(sapply(componentes_numericos, quantile, probs = 0.75), 4),
  Maximo = round(sapply(componentes_numericos, max), 4)
)

# Eliminar nombres de fila para evitar que se repitan las variables
rownames(tabla_desc_componentes) <- NULL

kable(
  tabla_desc_componentes,
  caption = "Análisis descriptivo de los dos componentes principales retenidos",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Análisis descriptivo de los dos componentes principales retenidos
Variable Media Desv_tipica Minimo Q1 Mediana Q3 Maximo
PC1_Envejecimiento 0 1.3358 -3.0207 -1.1532 0.1182 0.8852 3.4919
PC2_Extranjeria 0 1.1693 -1.9530 -0.8835 -0.4501 0.8168 3.6795
# Histogramas
par(mfrow = c(2, 3))
for (v in names(componentes_numericos)) {
  hist(componentes_numericos[[v]],
       main = paste("Histograma de", v),
       xlab = v,
       col = "blue",
       border = "white")
}
par(mfrow = c(1, 1))

# Boxplots para detectar dispersión y posibles valores extremos
boxplot(componentes_numericos,
        main = "Distribución de variables sociodemográficas 2011",
        las = 2,
        col = "blue")

El análisis descriptivo de los dos componentes principales muestra que ambas variables tienen medias próximas a cero, como consecuencia de haberse obtenido a partir de variables estandarizadas. El primer componente, asociado al envejecimiento y a los hogares unipersonales de personas mayores, presenta una mayor dispersión que el segundo, lo que es coherente con su mayor capacidad explicativa dentro del ACP. Su distribución es relativamente equilibrada, aunque con secciones situadas en ambos extremos del eje de envejecimiento. En cambio, el segundo componente, vinculado a la presencia de población extranjera y extranjera infantil, muestra una distribución más asimétrica hacia la derecha, con muchas secciones en valores bajos o moderados y algunos casos con puntuaciones elevadas. Esto sugiere que la dimensión de extranjería está más concentrada territorialmente en un número menor de secciones censales.

En conjunto, los dos componentes permiten sintetizar dos patrones diferenciados: uno más extendido y gradual, vinculado al envejecimiento, y otro más concentrado espacialmente, vinculado a la presencia de población extranjera.

Correlación entre los dos componentes

# Matriz de correlación entre los componentes retenidos
cor_componentes <- cor(componentes_numericos) 

# Redondeo de los componentes para simplificar
tabla_cor_componentes <- round(cor_componentes, 4)

# Generamos una tabla con los resultados de correlación
kable(
  tabla_cor_componentes,
  caption = "Matriz de correlación entre los componentes principales retenidos",
  align = "c"
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Matriz de correlación entre los componentes principales retenidos
PC1_Envejecimiento PC2_Extranjeria
PC1_Envejecimiento 1 0
PC2_Extranjeria 0 1

La matriz de correlación entre los dos componentes principales retenidos muestra una correlación nula entre PC1_Envejecimiento y PC2_Extranjeria. Este resultado es coherente con la lógica del análisis de componentes principales, ya que los componentes obtenidos son ortogonales por construcción. Por tanto, las dos nuevas variables sintetizan dimensiones independientes de la variabilidad del dataset: una primera dimensión asociada al envejecimiento y los hogares unipersonales de personas mayores, y una segunda dimensión vinculada a la presencia de población extranjera y extranjera infantil.

Ejercicio 2 - Análisis factorial

El análisis paralelo se atiende también como criterio complementario al criterio de Kaiser y a los gráficos de sedimentación.

fa.parallel(cor_2011,n.obs = 144,fm = "pa")

## Parallel analysis suggests that the number of factors =  2  and the number of components =  2

El gráfico de análisis paralelo confirma la conveniencia de retener dos componentes principales y dos factores. En el caso del ACP, los dos primeros componentes presentan autovalores reales superiores a los obtenidos a partir de datos simulados, mientras que desde el tercer componente los valores reales quedan por debajo de los simulados. Esto respalda la decisión de conservar dos componentes principales, en coherencia con el criterio de Kaiser y con el porcentaje de varianza explicada acumulada.

En el caso del análisis factorial, el gráfico muestra igualmente que los dos primeros factores reales superan a los factores simulados, mientras que los factores posteriores no aportan una estructura diferenciada respecto a la esperable por azar. Por tanto, el análisis paralelo apoya la solución bifactorial adoptada. Esta solución resulta además interpretable desde el punto de vista sustantivo, al distinguir un primer factor asociado al envejecimiento y los hogares unipersonales de personas mayores, y un segundo factor vinculado a la presencia de población extranjera.

En conjunto, el análisis paralelo refuerza la elección de dos dimensiones principales en ambos análisis y permite justificar que la reducción del conjunto original de variables a dos componentes o factores no responde solo a criterios descriptivos, sino también a una comparación con la estructura esperable en datos aleatorios.

Análisis Factorial Exploratorio

Se realiza un análisis factorial exploratorio como una técnica de reducción de la dimensionalidad orientada a identificar posibles factores latentes que expliquen las relaciones observadas entre las variables sociodemográficas seleccionadas para el año 2011. A diferencia del análisis de componentes principales, cuyo objetivo principal es sintetizar la varianza total de las variables originales, el análisis factorial busca explicar la varianza común compartida entre ellas.

En este ejercicio se utiliza la misma base de datos de 2011 y se parte de la matriz de correlaciones entre las variables originales. Esta matriz se obtiene mediante la función cor(), utilizando las correlaciones de Pearson, que son las que R calcula por defecto. Posteriormente, se aplica un análisis factorial exploratorio con la librería psych, empleando el método de ejes principales (fm = "pa"), adecuado cuando el interés se centra en estimar factores comunes a partir de la estructura correlacional de los datos.

Dado que el análisis previo de componentes principales sugería la existencia de dos dimensiones principales —una vinculada al envejecimiento y los hogares unipersonales de personas mayores, y otra asociada a la presencia de población extranjera—, se propone inicialmente una solución de dos factores. No obstante, teniendo en cuenta que el índice KMO obtenido previamente mostraba una adecuación moderada-baja, los resultados del análisis factorial se interpretan con finalidad exploratoria y con la debida cautela.

# Análisis factorial exploratorio con ejes principales
fa_2011 <- fa(
  r = cor_2011,
  nfactors = 2,
  fm = "pa",
  rotate = "varimax",
  n.obs = nrow(X2011)
)


fa_2011
## Factor Analysis using method =  pa
## Call: fa(r = cor_2011, nfactors = 2, n.obs = nrow(X2011), rotate = "varimax", 
##     fm = "pa")
## Standardized loadings (pattern matrix) based upon correlation matrix
##            PA1  PA2    h2   u2 com
## P75       0.86 0.04 0.738 0.26 1.0
## HOG_64    0.73 0.18 0.564 0.44 1.1
## HOG_MONO -0.25 0.05 0.065 0.93 1.1
## EXT       0.11 0.49 0.249 0.75 1.1
## EXT_MEN  -0.12 0.79 0.634 0.37 1.0
## 
##                        PA1  PA2
## SS loadings           1.36 0.90
## Proportion Var        0.27 0.18
## Cumulative Var        0.27 0.45
## Proportion Explained  0.60 0.40
## Cumulative Proportion 0.60 1.00
## 
## Mean item complexity =  1.1
## Test of the hypothesis that 2 factors are sufficient.
## 
## df null model =  10  with the objective function =  0.78 with Chi Square =  109.49
## df of  the model are 1  and the objective function was  0.01 
## 
## The root mean square of the residuals (RMSR) is  0.02 
## The df corrected root mean square of the residuals is  0.06 
## 
## The harmonic n.obs is  144 with the empirical chi square  0.87  with prob <  0.35 
## The total n.obs was  144  with Likelihood Chi Square =  1.86  with prob <  0.17 
## 
## Tucker Lewis Index of factoring reliability =  0.913
## RMSEA index =  0.077  and the 90 % confidence intervals are  0 0.252
## BIC =  -3.11
## Fit based upon off diagonal values = 1
## Measures of factor score adequacy             
##                                                    PA1  PA2
## Correlation of (regression) scores with factors   0.90 0.82
## Multiple R square of scores with factors          0.81 0.68
## Minimum correlation of possible factor scores     0.61 0.35
print(fa_2011$loadings, cutoff = 0.30)
## 
## Loadings:
##          PA1    PA2   
## P75       0.858       
## HOG_64    0.728       
## HOG_MONO              
## EXT              0.487
## EXT_MEN          0.788
## 
##                  PA1   PA2
## SS loadings    1.355 0.895
## Proportion Var 0.271 0.179
## Cumulative Var 0.271 0.450
fa_2011$Vaccounted
##                             PA1       PA2
## SS loadings           1.3552668 0.8953015
## Proportion Var        0.2710534 0.1790603
## Cumulative Var        0.2710534 0.4501137
## Proportion Explained  0.6021887 0.3978113
## Cumulative Proportion 0.6021887 1.0000000
fa.diagram(fa_2011)

El análisis factorial exploratorio mediante el método de ejes principales y rotación varimax permite identificar una solución de dos factores con una interpretación sustantiva clara. El primer factor, PA1, presenta cargas elevadas en la población de 75 años o más y en los hogares unipersonales de mayores de 64 años, por lo que puede interpretarse como una dimensión de envejecimiento demográfico y vulnerabilidad residencial de personas mayores. El segundo factor, PA2, muestra cargas más elevadas en la población extranjera infantil y, en menor medida, en la población extranjera total, por lo que puede interpretarse como una dimensión asociada a la presencia de población extranjera.

La solución factorial explica conjuntamente el 45,01 % de la varianza total, correspondiendo el 27,11 % al primer factor y el 17,91 % al segundo. Aunque este porcentaje es inferior al obtenido en el ACP, debe tenerse en cuenta que el análisis factorial trata de explicar la varianza común entre las variables, no la varianza total. Los indicadores de ajuste son razonables: el RMSR es bajo, el TLI se sitúa por encima de 0.90, el BIC es negativo y el contraste de suficiencia de dos factores no resulta significativo, por lo que no se rechaza la adecuación de la solución bifactorial.

No obstante, la interpretación debe realizarse con cautela. La variable HOG_MONO presenta una comunalidad muy baja y no carga de forma relevante en ninguno de los dos factores, lo que indica que los hogares monoparentales no quedan bien representados por la estructura factorial obtenida. En conjunto, el análisis factorial permite resumir la información en dos dimensiones principales —envejecimiento y extranjería—, aunque la solución debe entenderse como exploratoria.

Se indica qué procedimiento se ha utilizado para obtener la matriz de correlación.

La matriz de correlación se obtuvo a partir de las variables sociodemográficas seleccionadas para el año 2011 mediante la función cor() de R. Concretamente, se utilizó el objeto X2011, que contiene únicamente las variables numéricas del análisis, excluyendo el identificador de sección censal. El procedimiento aplicado fue:

cor_2011 <- cor(X2011, use = “pairwise.complete.obs”)

Al no especificar el argumento method, la función cor() calcula por defecto la matriz de correlaciones de Pearson. Por tanto, la matriz utilizada en el análisis factorial recoge las asociaciones lineales entre las variables originales de 2011.

El argumento use = "pairwise.complete.obs" indica que, en caso de existir valores perdidos, cada correlación se calcularía utilizando todos los pares de observaciones disponibles para las dos variables implicadas. No obstante, en este dataset no se detectaron valores perdidos, por lo que la matriz se calculó sobre el conjunto completo de secciones censales.

Esta matriz de correlaciones fue posteriormente utilizada como entrada del análisis factorial exploratorio con la función fa() de la librería psych, aplicando el método de ejes principales.

Obtención de las puntuaciones factoriales

Para obtener puntuaciones factoriales necesitamos aplicar fa() sobre la matriz de datos, no solo sobre la matriz de correlaciones. Como ya tenemos las variables del dataset original estandarizadas en X2011_z, usaremos este objeto como base para esta tarea.

# Comprobar que X2011_z no contiene el código de sección censal
colnames(X2011_z)
## [1] "P75"      "HOG_64"   "HOG_MONO" "EXT"      "EXT_MEN"
# Análisis factorial sobre los datos estandarizados para obtener puntuaciones
fa_2011_scores <- fa(
  r = X2011_z,
  nfactors = 2,
  fm = "pa",
  rotate = "varimax",
  scores = "regression"
)
## maximum iteration exceeded
# Comprobar nombres de las puntuaciones factoriales
colnames(fa_2011_scores$scores)
## [1] "PA1" "PA2"
# Crear directamente el nuevo dataset de puntuaciones
puntuaciones_factores <- data.frame(
  COD_SECC = datos_2011$COD_SECC,
  F1_Envejecimiento = fa_2011_scores$scores[, 1],
  F2_Extranjeria = fa_2011_scores$scores[, 2]
)

head(puntuaciones_factores)
##     COD_SECC F1_Envejecimiento F2_Extranjeria
## 1 3907501001        -0.2127151     0.04248055
## 2 3907501002         0.7120695    -0.32247211
## 3 3907501003         0.4653397    -0.33637468
## 4 3907501004         2.0834454    -0.22251810
## 5 3907501005         0.4270473     1.67110942
## 6 3907501006        -0.4197269     0.99483423

Se realiza un análisis estadístico de las puntuaciones y se indica el método utilizado para obtener los nuevos factores.

El análisis estadístico descriptivo de estas puntuaciones permite conocer la posición relativa de las secciones censales en cada dimensión factorial. Las medias de las puntuaciones suelen situarse próximas a cero, ya que proceden de variables estandarizadas, mientras que la dispersión indica el grado de diferenciación existente entre secciones. Valores altos en el primer factor indican secciones con mayor peso relativo de población de 75 años o más y hogares unipersonales de mayores de 64 años. Valores altos en el segundo factor indican secciones con mayor presencia relativa de población extranjera, especialmente población extranjera infantil.

# Seleccionar únicamente las puntuaciones factoriales
factores_numericos <- puntuaciones_factores[
  , c("F1_Envejecimiento", "F2_Extranjeria")
]

# Tabla descriptiva
tabla_desc_factores <- data.frame(
  Factor = names(factores_numericos),
  Media = round(sapply(factores_numericos, mean), 4),
  Desv_tipica = round(sapply(factores_numericos, sd), 4),
  Minimo = round(sapply(factores_numericos, min), 4),
  Q1 = round(sapply(factores_numericos, quantile, probs = 0.25), 4),
  Mediana = round(sapply(factores_numericos, median), 4),
  Q3 = round(sapply(factores_numericos, quantile, probs = 0.75), 4),
  Maximo = round(sapply(factores_numericos, max), 4)
)

rownames(tabla_desc_factores) <- NULL

kable(
  tabla_desc_factores,
  caption = "Análisis descriptivo de las puntuaciones factoriales por sección censal",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Análisis descriptivo de las puntuaciones factoriales por sección censal
Factor Media Desv_tipica Minimo Q1 Mediana Q3 Maximo
F1_Envejecimiento 0 0.8974 -1.6986 -0.7147 0.0662 0.5430 2.5942
F2_Extranjeria 0 0.8220 -0.8139 -0.5767 -0.4168 0.5414 2.9116
# Histogramas
par(mfrow = c(2, 3))
for (v in names(factores_numericos)) {
  hist(factores_numericos[[v]],
       main = paste("Histograma de", v),
       xlab = v,
       col = "blue",
       border = "white")
}
par(mfrow = c(1, 1))

# Boxplots para detectar dispersión y posibles valores extremos
boxplot(factores_numericos,
        main = "Distribución de variables sociodemográficas 2011",
        las = 2,
        col = "blue")

Para obtener las puntuaciones factoriales de cada sección censal se aplicó el análisis factorial exploratorio sobre la matriz de datos estandarizados correspondiente al año 2011. La extracción de los factores se realizó mediante el método de ejes principales (fm = "pa"), manteniendo una solución de dos factores con rotación varimax. La rotación se utilizó para facilitar la interpretación de la estructura factorial, permitiendo diferenciar con mayor claridad una dimensión asociada al envejecimiento y otra vinculada a la presencia de población extranjera.

Las puntuaciones factoriales se obtuvieron mediante el método de regresión (scores = "regression"). Este procedimiento estima, para cada sección censal, una puntuación en cada uno de los factores retenidos. De este modo, cada sección queda caracterizada por dos nuevas variables: una puntuación en el factor de envejecimiento y vulnerabilidad residencial de personas mayores, y otra puntuación en el factor de presencia de población extranjera.

En consecuencia, las puntuaciones factoriales permiten sintetizar la información original en dos dimensiones interpretables y comparar las secciones censales de Santander según su perfil sociodemográfico.

Identificación de las 10 secciones más vulnerables demográficamente de Santander.

Para identificar las secciones censales más vulnerables demográficamente se utiliza la puntuación factorial correspondiente al factor de envejecimiento. Este factor presenta cargas elevadas en la población de 75 años o más y en los hogares unipersonales de mayores de 64 años, por lo que valores altos en este factor indican secciones con mayor vulnerabilidad demográfica asociada al envejecimiento y a la posible soledad residencial de personas mayores. A partir de este criterio, se ordenaron las secciones censales de mayor a menor puntuación en F1_Envejecimiento y se seleccionaron las diez primeras.

Estas secciones no deben interpretarse como las más vulnerables en términos generales, sino específicamente como las más vulnerables desde el punto de vista demográfico asociado al envejecimiento y a los hogares unipersonales de personas mayores.

# Ordenar secciones según el factor de envejecimiento
top10_vulnerables <- puntuaciones_factores[
  order(puntuaciones_factores$F1_Envejecimiento, decreasing = TRUE),
][1:10, ]

# Incorporar columna con el puesto en la clasificación
top10_vulnerables <- data.frame(
  Puesto = 1:nrow(top10_vulnerables),
  top10_vulnerables
)

# Eliminar nombres de fila
rownames(top10_vulnerables) <- NULL

# Mostrar tabla
kable(
  top10_vulnerables,
  caption = "Diez secciones censales con mayor vulnerabilidad demográfica considerada como mayor puntuación en F1_Envejecimiento",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Diez secciones censales con mayor vulnerabilidad demográfica considerada como mayor puntuación en F1_Envejecimiento
Puesto COD_SECC F1_Envejecimiento F2_Extranjeria
1 3907506010 2.594247 -0.3193468
2 3907506011 2.159575 0.9529416
3 3907502009 2.102381 1.0565200
4 3907501004 2.083445 -0.2225181
5 3907505001 1.995947 -0.3164033
6 3907502015 1.904030 -0.1742827
7 3907502012 1.749885 -0.4649740
8 3907502026 1.737979 0.5081624
9 3907507003 1.678074 -0.3263296
10 3907502013 1.558041 -0.6638004

Los resultados muestran que la sección censal 3907506010 ocupa el primer lugar, con una puntuación de 2.59 en F1_Envejecimiento, seguida de las secciones 3907506011´, ´3907502009 y 3907501004, todas ellas con puntuaciones superiores a 2. Estos valores indican que dichas secciones se sitúan claramente por encima de la media del conjunto de Santander en la dimensión de envejecimiento, por lo que pueden considerarse las áreas con mayor vulnerabilidad demográfica desde este criterio.

La segunda puntuación factorial, F2_Extranjeria, permite matizar el perfil de estas secciones. Algunas de ellas, como 3907506010, 3907501004, 3907505001, 3907502015, 3907502012, 3907507003 y 3907502013, presentan valores negativos en el factor de extranjería, lo que sugiere que su vulnerabilidad demográfica se asocia principalmente al envejecimiento y no a una presencia elevada de población extranjera. En contrapunto a las anteriores, las secciones 3907506011, 3907502009 y 3907502026 combinan puntuaciones altas en envejecimiento con valores positivos en extranjería, por lo que presentan un perfil sociodemográfico más mixto.

Se vuelve a recordar que, en conjunto, estas diez secciones no deben interpretarse como las más vulnerables en términos generales, sino específicamente como las más vulnerables desde la perspectiva del envejecimiento demográfico y de la posible soledad residencial de personas mayores. El ranking obtenido permitiría priorizar territorialmente aquellas secciones en las que el peso relativo de la población mayor y de los hogares unipersonales de mayores es más elevado.

Ejercicio 3 - Análisis cluster

Justificación del método de agrupamiento

Se utiliza un análisis de clúster jerárquico aglomerativo a partir de las puntuaciones factoriales obtenidas en el análisis factorial exploratorio. La distancia utilizada es la distancia Manhattan, tal como solicita el enunciado. Para seleccionar el método de agrupamiento más adecuado se comparan distintos métodos de enlace —simple, completo, promedio y McQuitty— mediante la correlación cofenética. Este criterio permite evaluar en qué medida el dendrograma conserva las distancias originales entre observaciones. Se seleccionará el método con mayor correlación cofenética, al ser el que reproduce con mayor fidelidad la estructura de distancias del conjunto de secciones censales.

No se utiliza el método de Ward porque está más directamente vinculado a la distancia euclídea y a la minimización de la varianza interna de los grupos, mientras que, en este ejercicio, el enunciado impone el uso de distancia Manhattan.

# 1. DATOS DE PARTIDA: PUNTUACIONES FACTORIALES

# Seleccionamos las puntuaciones factoriales obtenidas en el ejercicio 2
datos_cluster <- puntuaciones_factores[, c("F1_Envejecimiento", "F2_Extranjeria")]

# Estandarizamos las puntuaciones para que ambos factores tengan el mismo peso en la distancia
datos_cluster_z <- scale(datos_cluster)

# Añadimos los códigos de sección censal como nombres de fila
rownames(datos_cluster_z) <- puntuaciones_factores$COD_SECC


# 2. MATRIZ DE DISTANCIAS: MANHATTAN

dist_manhattan <- dist(datos_cluster_z, method = "manhattan")


# 3. COMPARACIÓN DE MÉTODOS DE AGRUPAMIENTO

metodos <- c("single", "complete", "average", "mcquitty")

modelos_hc <- lapply(
  metodos,
  function(m) hclust(dist_manhattan, method = m)
)

names(modelos_hc) <- metodos

# Correlación cofenética para comparar métodos
cor_cophenetica <- sapply(
  modelos_hc,
  function(modelo) cor(dist_manhattan, cophenetic(modelo))
)

tabla_metodos <- data.frame(
  Metodo = names(cor_cophenetica),
  Correlacion_cofenetica = round(cor_cophenetica, 4)
)

kable(
  tabla_metodos,
  caption = "Comparación de métodos de agrupamiento mediante correlación cofenética",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Comparación de métodos de agrupamiento mediante correlación cofenética
Metodo Correlacion_cofenetica
single 0.7278
complete 0.6936
average 0.7752
mcquitty 0.7034
# Seleccionamos automáticamente el método con mayor correlación cofenética
metodo_elegido <- names(which.max(cor_cophenetica))
metodo_elegido
## [1] "average"
cluster_hc <- modelos_hc[[metodo_elegido]]

Cómo puede observarse, el resultado del análisis de las correlaciones cofenéticas de los cuatro métodos de enlace testeados da como mejor opción el método del promedio o average, puesto que da el indice más elevado de las cuatro opciones, lo que significa que dicho método garantiza que el dendograma reproduzca con la mayor fidelidad la estructura de distancias del conjunto de secciones censales.

Selección del número de clústeres

Se utiliza el método de la silueta media (o average silhouette method) para seleccionar el número de clústeres que represente mejor la calidad de las diferentes posibilidades de agrupamiento y ayude a identificar el número óptimo de clústers para un conjunto de datos.

# 4. Selección del número de clústeres mediante silueta media

ks <- 2:8

silueta_media <- sapply(
  ks,
  function(k) {
    grupos <- cutree(cluster_hc, k = k)
    mean(silhouette(grupos, dist_manhattan)[, 3])
  }
)

tabla_silueta <- data.frame(
  Numero_clusters = ks,
  Silueta_media = round(silueta_media, 4)
)

kable(
  tabla_silueta,
  caption = "Silueta media según el número de clústeres",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Silueta media según el número de clústeres
Numero_clusters Silueta_media
2 0.5668
3 0.3355
4 0.4591
5 0.4185
6 0.4154
7 0.4037
8 0.5244
# Número óptimo de clústeres según la mayor silueta media
k_optimo <- ks[which.max(silueta_media)]
k_optimo
## [1] 2
# Gráfico sencillo de silueta media
ggplot(tabla_silueta, aes(x = Numero_clusters, y = Silueta_media)) +
  geom_line(color = "#333333", linewidth = 0.8) +
  geom_point(color = "#1F4E79", size = 3) +
  scale_x_continuous(breaks = ks) +
  labs(
    title = "Selección del número de clústeres",
    subtitle = "Criterio: mayor silueta media",
    x = "Número de clústeres",
    y = "Silueta media"
  ) +
  theme_minimal(base_size = 12)

La selección inicial de una solución de dos clústeres se justifica a partir del criterio de silueta media, ya que el valor máximo se alcanza para \(k = 2\). Esto indica que, desde el punto de vista de la separación interna de los grupos, la partición en dos conglomerados es la que ofrece mejor equilibrio entre cohesión dentro de los clústeres y diferenciación entre ellos.

Dendrograma con el número de clústeres seleccionado

plot(
  cluster_hc,
  labels = FALSE,
  hang = -1,
  main = paste("Dendrograma de secciones censales - método", metodo_elegido),
  xlab = "Secciones censales",
  sub = ""
)

rect.hclust(
  cluster_hc,
  k = k_optimo,
  border = 2:(k_optimo + 1)
)

El dendrograma obtenido mediante distancia Manhattan y método de enlace average refuerza la primera lectura recibida de la interpretación de la silueta media, ya que muestra una separación temprana entre un grupo muy reducido (sección única) y el conjunto principal de secciones censales. El corte en dos grupos permite, por tanto, identificar una estructura básica de diferenciación: por un lado, la mayoría de secciones con perfiles relativamente próximos entre sí y, por otro, una sección o grupo muy singular que se separa claramente del resto.

No obstante, esta solución debe interpretarse con cautela, ya que la partición en dos clústeres parece estar muy influida por la presencia de un caso extremo o perfil atípico. Por ello, aunque \(k = 2\) constituye la primera opción desde el punto de vista del índice de silueta, más adelante convendría valorar soluciones alternativas con mayor número de clústeres si el objetivo es obtener una segmentación territorial más rica e interpretable.

Inclusión en la base de datos de resultados del cluster al que pertenece cada sección censal.

# 5. ASIGNACIÓN DE CLUSTER A CADA SECCIÓN CENSAL

grupo_cluster <- cutree(cluster_hc, k = k_optimo)

resultados_cluster <- data.frame(
  COD_SECC = puntuaciones_factores$COD_SECC,
  F1_Envejecimiento = puntuaciones_factores$F1_Envejecimiento,
  F2_Extranjeria = puntuaciones_factores$F2_Extranjeria,
  Cluster = factor(grupo_cluster)
)

# Ordenar por clúster y sección censal
resultados_cluster <- resultados_cluster[
  order(resultados_cluster$Cluster, resultados_cluster$COD_SECC),
]

rownames(resultados_cluster) <- NULL


# Generar tabla con los 20 primeros distritos mostrando el clúster al que se le ha asignado
kable(
  head(resultados_cluster, 20),
  caption = "Primeras secciones censales con clúster asignado",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Primeras secciones censales con clúster asignado
COD_SECC F1_Envejecimiento F2_Extranjeria Cluster
3907501001 -0.2127151 0.0424805 1
3907501002 0.7120695 -0.3224721 1
3907501003 0.4653397 -0.3363747 1
3907501004 2.0834454 -0.2225181 1
3907501005 0.4270473 1.6711094 1
3907501006 -0.4197269 0.9948342 1
3907501007 0.3397056 -0.5631617 1
3907501008 0.3504110 0.5325480 1
3907501009 0.5025992 1.3618291 1
3907501010 0.0956864 2.4225122 1
3907501011 0.6822947 -0.4145090 1
3907502001 0.1324954 -0.6135221 1
3907502002 0.7362813 0.2552141 1
3907502003 0.5684337 -0.7065813 1
3907502004 0.0335640 1.4586857 1
3907502005 0.4110843 -0.4412259 1
3907502006 -0.2603096 1.7597144 1
3907502007 0.3000120 0.5832659 1
3907502008 0.6783090 1.7870969 1
3907502009 2.1023808 1.0565200 1
set.seed(123)  # Opcional: permite reproducir siempre la misma muestra aleatoria

kable(
  resultados_cluster[sample(nrow(resultados_cluster), 20), ],
  caption = "Veinte secciones censales seleccionadas aleatoriamente con clúster asignado",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Veinte secciones censales seleccionadas aleatoriamente con clúster asignado
COD_SECC F1_Envejecimiento F2_Extranjeria Cluster
3907502003 0.5684337 -0.7065813 1
3907503012 0.0563326 -0.5878335 1
3907507023 -1.5689434 0.6974677 1
3907503005 -1.0135151 -0.3788434 1
3907507010 -1.6986427 2.9115508 2
3907508024 -1.1151951 -0.7957002 1
3907506008 -0.8350645 2.1697439 1
3907506009 0.1547858 0.4986670 1
3907508019 -1.0469771 -0.6548585 1
3907506010 2.5942471 -0.3193468 1
3907507003 1.6780744 -0.3263296 1
3907505005 0.2932821 -0.4538180 1
3907502015 1.9040301 -0.1742827 1
3907501007 0.3397056 -0.5631617 1
3907505011 0.1391006 1.0036618 1
3907505014 0.6916878 0.3694950 1
3907508023 -1.1362141 -0.6754554 1
3907507007 -0.6915658 -0.6254734 1
3907507022 -1.2437003 -0.6945225 1
3907505009 -0.3163578 -0.7189534 1

Perfil medio de cada clúster

Para ayudar a una mejor interpretación los grupos generados al clusterizar, se analiza el perfil medio de las secciones que componen cada uno de los dos clústers en los que se ha particionado inicialmente el total de secciones censales.

# Número de secciones por clúster
tabla_tamano_cluster <- as.data.frame(table(resultados_cluster$Cluster))
colnames(tabla_tamano_cluster) <- c("Cluster", "Numero_secciones")

# Perfil medio de los clústeres
perfil_cluster <- aggregate(
  resultados_cluster[, c("F1_Envejecimiento", "F2_Extranjeria")],
  by = list(Cluster = resultados_cluster$Cluster),
  FUN = mean
)

perfil_cluster <- merge(
  perfil_cluster,
  tabla_tamano_cluster,
  by = "Cluster"
)

kable(
  perfil_cluster,
  caption = "Perfil medio de los clústeres según las puntuaciones factoriales",
  align = "c",
  row.names = FALSE,
  digits = 3
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Perfil medio de los clústeres según las puntuaciones factoriales
Cluster F1_Envejecimiento F2_Extranjeria Numero_secciones
1 0.012 -0.020 143
2 -1.699 2.912 1

La solución de dos clústeres muestra una partición muy desequilibrada. El clúster 1 agrupa 143 secciones censales y presenta valores medios prácticamente nulos en ambos factores (F1_Envejecimiento = 0.012; F2_Extranjeria = -0.020), por lo que representa el perfil general o mayoritario del municipio. En cambio, el clúster 2 está formado por una única sección censal, con bajo envejecimiento relativo (F1 = -1.699) y una puntuación muy elevada en extranjería (F2 = 2.912).

Por tanto, aunque la solución de dos grupos es la mejor según el criterio de silueta media, su interpretación debe realizarse con cautela, ya que no segmenta el territorio en dos perfiles amplios, sino que identifica una sección claramente atípica frente al resto. Esta solución es útil para detectar un caso singular de alta extranjería y bajo envejecimiento, pero puede resultar limitada si el objetivo del análisis es obtener una tipología territorial más rica y equilibrada.

En resumen, parece que situar el agrupamiento en \(k = 2\) maximiza la silueta porque detecta muy bien un outlier territorial, no porque existan dos grandes grupos homogéneos y comparables.

Gráfico de clústeres sobre los dos factores

ggplot(
  resultados_cluster,
  aes(
    x = F1_Envejecimiento,
    y = F2_Extranjeria,
    color = Cluster
  )
) +
  geom_point(size = 2.5, alpha = 0.85) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray60") +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray60") +
  labs(
    title = "Clústeres de secciones censales de Santander",
    subtitle = "Agrupación sobre puntuaciones factoriales. Distancia Manhattan",
    x = "Factor 1: Envejecimiento",
    y = "Factor 2: Extranjería",
    color = "Clúster"
  ) +
  theme_minimal(base_size = 12)

Tras los análisis realizados en base a una solución inicial de dos clústeres, se observa que dicha clusterización genera una partición muy desequilibrada, con un primer grupo formado por 143 secciones censales y un segundo grupo compuesto por una única sección. Aunque esta solución puede ser estadísticamente posible, su interpretación debe realizarse con cautela, ya que no refleja propiamente una tipología territorial de dos grupos, sino la separación de una sección censal con un perfil claramente atípico.

El segundo clúster presenta una puntuación negativa en el factor de envejecimiento y una puntuación muy elevada en el factor de extranjería, lo que sugiere que se trata de una sección singular, caracterizada por baja vulnerabilidad asociada al envejecimiento y alta presencia relativa de población extranjera. Por tanto, más que un grupo territorial amplio, este clúster puede interpretarse como un caso extremo.

En consecuencia, para seleccionar el número final de clústeres no debería atenderse únicamente a la silueta media, sino que también debe dársele importancia a la interpretabilidad de la solución y al tamaño de los grupos resultantes. Si una solución alternativa con tres o cuatro clústeres ofrece grupos más equilibrados y perfiles sociodemográficos diferenciados, resultaría preferible para construir una clasificación útil de las secciones censales de Santander.

Clusterización alternativa

Para realizar una exploración de la mejor opción respecto al número de grupos, se analizan soluciones alternativas entre 3 y 8 clústeres, considerando no solo la silueta media, sino también el tamaño de los grupos, la estructura observada en el dendrograma y la interpretabilidad de los perfiles resultantes.

# Comparación de soluciones entre 2 y 8 clústeres

ks <- 2:8

diagnostico_clusters <- data.frame(
  Numero_clusters = integer(),
  Silueta_media = numeric(),
  Minimo_secciones_cluster = integer(),
  Maximo_secciones_cluster = integer()
)

for (k in ks) {
  
  grupos_k <- cutree(cluster_hc, k = k)
  sil_k <- silhouette(grupos_k, dist_manhattan)
  tam_k <- table(grupos_k)
  
  diagnostico_clusters <- rbind(
    diagnostico_clusters,
    data.frame(
      Numero_clusters = k,
      Silueta_media = round(mean(sil_k[, 3]), 4),
      Minimo_secciones_cluster = min(tam_k),
      Maximo_secciones_cluster = max(tam_k)
    )
  )
}

kable(
  diagnostico_clusters,
  caption = "Diagnóstico de soluciones alternativas de clústeres",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Diagnóstico de soluciones alternativas de clústeres
Numero_clusters Silueta_media Minimo_secciones_cluster Maximo_secciones_cluster
2 0.5668 1 143
3 0.3355 1 131
4 0.4591 1 84
5 0.4185 1 84
6 0.4154 1 84
7 0.4037 1 84
8 0.5244 1 44

Solución alternativa propuesta: 4 clústeres

La solución de 4 clústeres se considera una alternativa más útil, al permitir diferenciar perfiles sociodemográficos más matizados sin reducir el análisis a la separación de un único caso extremo.

Esta solución mantiene la distancia Manhattan y el método jerárquico aglomerativo con enlace promedio (average). El método average resulta adecuado porque ofrece una agrupación menos sensible a casos extremos que el enlace simple y más equilibrada que otros métodos, conservando una estructura interpretable de distancias entre secciones.

En consecuencia, se adopta una solución alternativa de cuatro clústeres, no porque maximice estrictamente la silueta media, sino porque ofrece una segmentación territorial más informativa y operativamente útil de las secciones censales de Santander.

# SELECCIÓN ALTERNATIVA DE 4 CLÚSTERES

k_alt <- 4

grupo_cluster_alt <- cutree(cluster_hc, k = k_alt)

# Base de resultados con clúster asignado
resultados_cluster_alt <- data.frame(
  COD_SECC = puntuaciones_factores$COD_SECC,
  F1_Envejecimiento = puntuaciones_factores$F1_Envejecimiento,
  F2_Extranjeria = puntuaciones_factores$F2_Extranjeria,
  Cluster = factor(grupo_cluster_alt)
)

# Ordenar por clúster y sección
resultados_cluster_alt <- resultados_cluster_alt[
  order(resultados_cluster_alt$Cluster, resultados_cluster_alt$COD_SECC),
]

rownames(resultados_cluster_alt) <- NULL

kable(
  resultados_cluster_alt[sample(nrow(resultados_cluster_alt), 20), ],
  caption = "Primeras secciones censales con clúster asignado. Solución alternativa de 4 clústeres",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Primeras secciones censales con clúster asignado. Solución alternativa de 4 clústeres
COD_SECC F1_Envejecimiento F2_Extranjeria Cluster
3907507003 1.6780744 -0.3263296 3
3907506006 0.0689589 0.4946518 1
3907507019 1.0430058 -0.3055600 2
3907502002 0.7362813 0.2552141 1
3907502026 1.7379789 0.5081624 3
3907503014 -0.4330144 -0.4706365 2
3907505006 -0.6661586 0.9425082 1
3907502003 0.5684337 -0.7065813 2
3907502013 1.5580412 -0.6638004 3
3907505013 1.5275409 -0.3790136 3
3907506009 0.1547858 0.4986670 1
3907503006 0.1195073 -0.6265003 2
3907503012 0.0563326 -0.5878335 2
3907504001 0.2256610 -0.5565671 2
3907502023 -1.0568410 -0.6041982 2
3907506013 0.0634585 -0.3685095 2
3907508011 -0.3501716 -0.7557587 2
3907505008 -1.5447086 -0.7020211 2
3907505011 0.1391006 1.0036618 1
3907507013 0.3249312 0.9343292 1
# PERFIL MEDIO DE CADA UNO DE LOS 4 CLÚSTERES

# Tamaño de cada clúster
tamano_cluster_alt <- as.data.frame(table(resultados_cluster_alt$Cluster))
colnames(tamano_cluster_alt) <- c("Cluster", "Numero_secciones")

# Perfil medio de cada clúster
perfil_cluster_alt <- aggregate(
  resultados_cluster_alt[, c("F1_Envejecimiento", "F2_Extranjeria")],
  by = list(Cluster = resultados_cluster_alt$Cluster),
  FUN = mean
)

perfil_cluster_alt <- merge(
  perfil_cluster_alt,
  tamano_cluster_alt,
  by = "Cluster"
)

kable(
  perfil_cluster_alt,
  caption = "Perfil medio de los clústeres según las puntuaciones factoriales. Solución de 4 clústeres",
  align = "c",
  row.names = FALSE,
  digits = 3
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Perfil medio de los clústeres según las puntuaciones factoriales. Solución de 4 clústeres
Cluster F1_Envejecimiento F2_Extranjeria Numero_secciones
1 -0.136 0.951 47
2 -0.172 -0.559 84
3 1.883 -0.055 12
4 -1.699 2.912 1
Clúster Nº secciones Perfil medio Interpretación
1 47 F1 bajo-medio, F2 alto Secciones con mayor presencia de población extranjera, pero sin especial envejecimiento
2 84 F1 bajo-medio, F2 bajo Perfil mayoritario, con menor presencia extranjera y envejecimiento no elevado
3 12 F1 alto, F2 cercano a cero Secciones más envejecidas, con vulnerabilidad demográfica asociada a mayores
4 1 F1 muy bajo, F2 muy alto Caso singular o atípico, con muy alta extranjería y bajo envejecimiento

Dendrograma con 4 clústeres

plot(
  cluster_hc,
  labels = FALSE,
  hang = -1,
  main = "Dendrograma de secciones censales - método average",
  xlab = "Secciones censales",
  sub = ""
)

rect.hclust(
  cluster_hc,
  k = k_alt,
  border = 2:(k_alt + 1)
)

Gráfico de los clústeres sobre los factores

ggplot(
  resultados_cluster_alt,
  aes(
    x = F1_Envejecimiento,
    y = F2_Extranjeria,
    color = Cluster
  )
) +
  geom_point(size = 2.5, alpha = 0.85) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray60") +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray60") +
  labs(
    title = "Clústeres de secciones censales de Santander",
    subtitle = "Solución alternativa de 4 clústeres. Distancia Manhattan y método average",
    x = "Factor 1: Envejecimiento",
    y = "Factor 2: Extranjería",
    color = "Clúster"
  ) +
  theme_minimal(base_size = 12)

La solución alternativa de cuatro clústeres permite obtener una segmentación más informativa de las secciones censales de Santander que la solución inicial de dos grupos. Aunque la solución de dos clústeres presentaba una silueta media elevada, generaba una partición muy desequilibrada, separando una única sección censal del resto. Por ello, se optó por explorar una solución de cuatro clústeres, más útil desde el punto de vista interpretativo.

El clúster 2 agrupa la mayoría de las secciones censales y representa un perfil sociodemográfico medio, con puntuaciones bajas en extranjería y sin un envejecimiento especialmente elevado. El clúster 1 reúne secciones con mayor presencia relativa de población extranjera, pero sin destacar por envejecimiento. El clúster 3 identifica las secciones con mayor vulnerabilidad demográfica asociada al envejecimiento, al presentar una media claramente elevada en el factor de envejecimiento. Finalmente, el clúster 4 está formado por una única sección, caracterizada por una puntuación muy alta en extranjería y baja en envejecimiento, por lo que debe interpretarse como un caso singular o atípico.

En conjunto, esta clasificación permite diferenciar cuatro perfiles territoriales: secciones con mayor extranjería, secciones de perfil medio-bajo en ambas dimensiones, secciones envejecidas y una sección atípica con alta extranjería. Esta solución resulta más adecuada para segmentar las secciones censales de Santander, ya que combina criterios estadísticos con interpretabilidad sustantiva.

Validación interna de las solución de 4 grupos

Además de la silueta media, se utilizan otros índices de validación interna para evaluar la calidad de la solución de clústeres. Para ello se calculan índice de Dunn, que mide la calidad de una partición en clusters comparando dos magnitudes: la separación entre clusters (distancia mínima entre puntos de clusters distintos) y la compacidad interna (diámetro máximo, es decir, la mayor distancia entre dos puntos dentro del mismo cluster). Valores altos indican clusters bien separados y compactos, lo que se considera una buena solución. También se mide la versión robusta de este idicador (Dunn2), que sustituye los operadores sensibles a outliers por promedios: en lugar de tomar la distancia mínima entre clusters usa la distancia media entre puntos de clusters distintos, y en lugar del diámetro máximo usa la distancia intra-cluster promedio.Al igual que Dunn, valores más altos indican una mejor solución: clusters compactos y bien separados.

También se calcula el índice Pearson gamma, el cual se usa para medir la calidad de una clusterización jerárquica comparando cuántas relaciones entre pares de elementos quedan mejor explicadas por la partición en clústeres que por una estructura aleatoria. En esencia, evalúa si los grupos formados son más coherentes internamente y más separados entre sí.

Índice Qué mide Criterio
Silueta media Cohesión interna y separación entre grupos Más alto es mejor
Dunn Separación mínima entre clústeres frente a diámetro interno Más alto es mejor
Dunn2 Variante menos estricta del índice de Dunn Más alto es mejor
Pearson gamma Asociación entre distancias y pertenencia a grupos Más alto es mejor
# Estadísticos de validación interna
validacion_cluster <- cluster.stats(
  d = dist_manhattan,
  clustering = grupo_cluster_alt
)

# Tabla comparativa de estadísticos

ks <- 2:8

tabla_validacion <- data.frame(
  Numero_clusters = integer(),
  Silueta_media = numeric(),
  Dunn = numeric(),
  Dunn2 = numeric(),
  Pearson_gamma = numeric(),
  Minimo_secciones_cluster = integer(),
  Maximo_secciones_cluster = integer()
)

for (k in ks) {
  
  grupos_k <- cutree(cluster_hc, k = k)
  
  stats_k <- cluster.stats(
    d = dist_manhattan,
    clustering = grupos_k
  )
  
  tam_k <- table(grupos_k)
  
  tabla_validacion <- rbind(
    tabla_validacion,
    data.frame(
      Numero_clusters = k,
      Silueta_media = round(stats_k$avg.silwidth, 4),
      Dunn = round(stats_k$dunn, 4),
      Dunn2 = round(stats_k$dunn2, 4),
      Pearson_gamma = round(stats_k$pearsongamma, 4),
      Minimo_secciones_cluster = min(tam_k),
      Maximo_secciones_cluster = max(tam_k)
    )
  )
}

kable(
  tabla_validacion,
  caption = "Comparación de soluciones de clústeres mediante índices de validación interna",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Comparación de soluciones de clústeres mediante índices de validación interna
Numero_clusters Silueta_media Dunn Dunn2 Pearson_gamma Minimo_secciones_cluster Maximo_secciones_cluster
2 0.5668 0.2722 2.5828 0.3072 1 143
3 0.3355 0.0711 1.6384 0.4177 1 131
4 0.4591 0.0904 1.6835 0.6578 1 84
5 0.4185 0.0966 1.5019 0.6593 1 84
6 0.4154 0.0895 1.6344 0.6534 1 84
7 0.4037 0.0895 1.6439 0.6529 1 84
8 0.5244 0.0925 1.5401 0.6112 1 44

La comparación de soluciones de clústeres muestra que la partición en dos grupos obtiene la mayor silueta media y los valores más elevados en los índices de Dunn. Sin embargo, esta solución genera una distribución extremadamente desequilibrada, con un clúster formado por 143 secciones censales y otro compuesto por una única sección. Por tanto, aunque estadísticamente detecta una separación clara, su utilidad para segmentar territorialmente Santander es limitada, ya que refleja principalmente la presencia de un caso atípico.

Por este motivo, la elección final del número de clústeres no se basa únicamente en la maximización de la silueta media, sino en una valoración conjunta de distintos criterios: calidad interna del agrupamiento, tamaño de los grupos, estructura del dendrograma e interpretabilidad sustantiva. La solución de cuatro clústeres presenta una silueta media aceptable y un valor elevado de Pearson_gamma, muy próximo al máximo observado. Además, permite diferenciar perfiles sociodemográficos más útiles: secciones con mayor presencia extranjera, secciones de perfil medio, secciones envejecidas y una sección singular o atípica.

En consecuencia, se selecciona la solución de cuatro clústeres como alternativa más adecuada para la segmentación de las secciones censales de Santander. Esta elección sacrifica parcialmente la maximización estricta de algunos índices, pero mejora la utilidad interpretativa y territorial del análisis.

Ejercicio 4 - Multidimensional Scaling

Para este ejercicio cargamos el conjunto de datos “Produc”, que proviene de la librería ““plm” y contiene información económica de distintos estados de Estados Unidos. Trabajaremos con los datos del año 1986 y con las siguientes variables:

  • pcap: stock de capital público
  • hwy: infraestructuras viarias
  • water: infraestructuras de agua y saneamiento
  • util: otras infraestructuras públicas
  • pc: stock de capital privado
  • gsp: producto bruto estatal
  • emp: empleo no agrícola
  • unemp: tasa de desempleo
# Cargar el dataset Produc
data("Produc", package = "plm")

# Ver las primeras filas
head(Produc)
##     state year region     pcap     hwy   water    util       pc   gsp    emp
## 1 ALABAMA 1970      6 15032.67 7325.80 1655.68 6051.20 35793.80 28418 1010.5
## 2 ALABAMA 1971      6 15501.94 7525.94 1721.02 6254.98 37299.91 29375 1021.9
## 3 ALABAMA 1972      6 15972.41 7765.42 1764.75 6442.23 38670.30 31303 1072.3
## 4 ALABAMA 1973      6 16406.26 7907.66 1742.41 6756.19 40084.01 33430 1135.5
## 5 ALABAMA 1974      6 16762.67 8025.52 1734.85 7002.29 42057.31 33749 1169.8
## 6 ALABAMA 1975      6 17316.26 8158.23 1752.27 7405.76 43971.71 33604 1155.4
##   unemp
## 1   4.7
## 2   5.2
## 3   4.7
## 4   3.9
## 5   5.5
## 6   7.7

Preparación de datos:

# Seleccionar solo el año 1986 y las variables solicitadas
Produc_1986 <- subset(
  Produc,
  year == 1986,
  select = c(
    state,
    year,
    pcap,
    hwy,
    water,
    util,
    pc,
    gsp,
    emp,
    unemp
  )
)

# Revisar el resultado
head(Produc_1986)
##           state year      pcap      hwy    water     util        pc    gsp
## 17      ALABAMA 1986  19723.37  8813.24  2308.99  8601.14  61628.88  48409
## 34      ARIZONA 1986  20212.42  6553.40  2923.15 10735.88  44189.19  46058
## 51     ARKANSAS 1986   9787.20  4652.71   960.66  4173.84  32889.30  28168
## 68   CALIFORNIA 1986 139042.68 43350.54 24592.33 71099.80 363779.70 464550
## 85     COLORADO 1986  18889.54  6148.80  4340.24  8400.50  47485.05  51781
## 102 CONNECTICUT 1986  17290.61  6878.14  3314.55  7097.92  40413.22  61750
##         emp unemp
## 17   1463.3   9.8
## 34   1337.8   6.9
## 51    813.8   8.7
## 68  11258.0   6.7
## 85   1408.3   7.4
## 102  1604.2   3.8
str(Produc_1986)
## 'data.frame':    48 obs. of  10 variables:
##  $ state: Factor w/ 48 levels "ALABAMA","ARIZONA",..: 1 2 3 4 5 6 7 8 9 10 ...
##  $ year : int  1986 1986 1986 1986 1986 1986 1986 1986 1986 1986 ...
##  $ pcap : num  19723 20212 9787 139043 18890 ...
##  $ hwy  : num  8813 6553 4653 43351 6149 ...
##  $ water: num  2309 2923 961 24592 4340 ...
##  $ util : num  8601 10736 4174 71100 8400 ...
##  $ pc   : num  61629 44189 32889 363780 47485 ...
##  $ gsp  : int  48409 46058 28168 464550 51781 61750 10072 152269 88827 11672 ...
##  $ emp  : num  1463 1338 814 11258 1408 ...
##  $ unemp: num  9.8 6.9 8.7 6.7 7.4 3.8 4.3 5.7 5.9 8.7 ...
summary(Produc_1986)
##          state         year           pcap             hwy       
##  ALABAMA    : 1   Min.   :1986   Min.   :  2936   Min.   : 1830  
##  ARIZONA    : 1   1st Qu.:1986   1st Qu.:  9461   1st Qu.: 4137  
##  ARKANSAS   : 1   Median :1986   Median : 19306   Median : 8179  
##  CALIFORNIA : 1   Mean   :1986   Mean   : 27393   Mean   :10736  
##  COLORADO   : 1   3rd Qu.:1986   3rd Qu.: 29882   3rd Qu.:12292  
##  CONNECTICUT: 1   Max.   :1986   Max.   :139043   Max.   :43351  
##  (Other)    :42                                                  
##      water              util               pc              gsp        
##  Min.   :  335.5   Min.   :  770.8   Min.   :  6939   Min.   :  7585  
##  1st Qu.: 1023.5   1st Qu.: 3042.7   1st Qu.: 28129   1st Qu.: 21183  
##  Median : 2926.7   Median : 8315.6   Median : 50303   Median : 47956  
##  Mean   : 4366.6   Mean   :12290.5   Mean   : 72260   Mean   : 75459  
##  3rd Qu.: 4867.9   3rd Qu.:13173.6   3rd Qu.: 83594   3rd Qu.: 87571  
##  Max.   :24592.3   Max.   :75237.1   Max.   :363780   Max.   :464550  
##                                                                       
##       emp              unemp       
##  Min.   :  196.3   Min.   : 2.800  
##  1st Qu.:  579.6   1st Qu.: 5.300  
##  Median : 1373.2   Median : 6.750  
##  Mean   : 2049.9   Mean   : 6.929  
##  3rd Qu.: 2586.4   3rd Qu.: 8.275  
##  Max.   :11258.0   Max.   :13.000  
## 
dim(Produc_1986)
## [1] 48 10

El dataset filtrado contiene 48 observaciones, correspondientes a distintos estados de Estados Unidos en el año 1986, y 10 variables. La variable state actúa como identificador territorial y la variable year toma un único valor, 1986, por lo que no aporta variabilidad al análisis. Las variables restantes son de tipo económico y recogen información sobre capital público (pcap), infraestructuras viarias (hwy), infraestructuras de agua y saneamiento (water), otras infraestructuras públicas (util), capital privado (pc), producto bruto estatal (gsp), empleo no agrícola (emp) y tasa de desempleo (unemp).

La exploración descriptiva muestra que las variables económicas presentan escalas muy diferentes y una elevada dispersión. Algunas variables, como pcap, pc, gsp y emp, tienen valores máximos muy superiores a sus medianas, lo que sugiere una distribución asimétrica y la presencia de estados con un tamaño económico claramente mayor. En cambio, unemp se expresa como tasa y tiene una escala mucho más reducida. Por este motivo, antes de aplicar el escalado multidimensional resulta necesario transformar y estandarizar las variables numéricas, evitando que las distancias entre estados queden dominadas por las variables de mayor magnitud.

Antes de aplicar el escalado multidimensional se realiza una transformación de los datos para evitar que las distancias entre estados quedaran dominadas por las variables de mayor escala. En primer lugar, se excluirán del análisis las variables state y year, conservando state únicamente como identificador territorial. Dado que las variables económicas de volumen presentan fuertes diferencias de magnitud y asimetría positiva, se aplicará una transformación logarítmica a las variables de capital, infraestructuras, producto bruto estatal y empleo. La tasa de desempleo se mantiene en su escala original al tratarse de una variable porcentual. Finalmente, todas las variables son estandarizadas para que contribuyeran de forma comparable al cálculo de la matriz de distancias. Esta matriz de distancias constituye la entrada del escalado multidimensional.

# Variables económicas seleccionadas
Produc_1986_num <- Produc_1986[, c(
  "pcap",
  "hwy",
  "water",
  "util",
  "pc",
  "gsp",
  "emp",
  "unemp"
)]

# Asignar nombres de los estados como identificadores de fila
rownames(Produc_1986_num) <- Produc_1986$state

# Transformación logarítmica para variables de tamaño o volumen
Produc_1986_transf <- Produc_1986_num

variables_log <- c(
  "pcap",
  "hwy",
  "water",
  "util",
  "pc",
  "gsp",
  "emp"
)

Produc_1986_transf[, variables_log] <- log(
  Produc_1986_transf[, variables_log]
)

# La variable unemp se mantiene en escala original porque ya es una tasa
# Después se estandarizan todas las variables
Produc_1986_scaled <- scale(Produc_1986_transf)

# Comprobación
summary(Produc_1986_scaled)
##       pcap              hwy               water              util        
##  Min.   :-1.9268   Min.   :-1.79322   Min.   :-1.8459   Min.   :-2.0375  
##  1st Qu.:-0.6818   1st Qu.:-0.78623   1st Qu.:-0.8257   1st Qu.:-0.7796  
##  Median : 0.0801   Median : 0.05191   Median : 0.1363   Median : 0.1472  
##  Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.0000   Mean   : 0.0000  
##  3rd Qu.: 0.5457   3rd Qu.: 0.55844   3rd Qu.: 0.6018   3rd Qu.: 0.5682  
##  Max.   : 2.1846   Max.   : 2.11510   Max.   : 2.0843   Max.   : 2.1704  
##        pc                gsp                emp               unemp        
##  Min.   :-2.11378   Min.   :-1.76622   Min.   :-1.84821   Min.   :-1.8575  
##  1st Qu.:-0.58863   1st Qu.:-0.75777   1st Qu.:-0.78053   1st Qu.:-0.7329  
##  Median : 0.04485   Median : 0.04445   Median : 0.07235   Median :-0.0806  
##  Mean   : 0.00000   Mean   : 0.00000   Mean   : 0.00000   Mean   : 0.0000  
##  3rd Qu.: 0.59865   3rd Qu.: 0.63577   3rd Qu.: 0.69772   3rd Qu.: 0.6054  
##  Max.   : 2.20139   Max.   : 2.27419   Max.   : 2.15033   Max.   : 2.7309
round(apply(Produc_1986_scaled, 2, mean), 3)
##  pcap   hwy water  util    pc   gsp   emp unemp 
##     0     0     0     0     0     0     0     0
round(apply(Produc_1986_scaled, 2, sd), 3)
##  pcap   hwy water  util    pc   gsp   emp unemp 
##     1     1     1     1     1     1     1     1

Una vez aplicadas las transformaciones, se presenta el resumen descriptivo de las variables que se utilizarán en el escalado multidimensional. Las variables económicas de volumen fueron transformadas mediante logaritmos para reducir la asimetría y suavizar el efecto de los estados con mayor tamaño económico. Posteriormente, todas las variables fueron estandarizadas, por lo que sus medias se sitúan próximas a 0 y sus desviaciones típicas son iguales a 1. Esta transformación permite que todas las variables contribuyan de forma comparable al cálculo de las distancias entre estados.

Presentación de una tabla descriptiva de las variables empleadas una vez transformadas.

# Convertir la matriz estandarizada en data frame
Produc_1986_scaled_df <- as.data.frame(Produc_1986_scaled)

# Tabla descriptiva de variables transformadas y estandarizadas
tabla_desc_transformadas <- data.frame(
  Variable = names(Produc_1986_scaled_df),
  Media = round(sapply(Produc_1986_scaled_df, mean), 4),
  Desv_tipica = round(sapply(Produc_1986_scaled_df, sd), 4),
  Minimo = round(sapply(Produc_1986_scaled_df, min), 4),
  Q1 = round(sapply(Produc_1986_scaled_df, quantile, probs = 0.25), 4),
  Mediana = round(sapply(Produc_1986_scaled_df, median), 4),
  Q3 = round(sapply(Produc_1986_scaled_df, quantile, probs = 0.75), 4),
  Maximo = round(sapply(Produc_1986_scaled_df, max), 4)
)

# Eliminar nombres de fila
rownames(tabla_desc_transformadas) <- NULL

# Mostrar tabla formateada
kable(
  tabla_desc_transformadas,
  caption = "Resumen descriptivo de las variables transformadas y estandarizadas",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Resumen descriptivo de las variables transformadas y estandarizadas
Variable Media Desv_tipica Minimo Q1 Mediana Q3 Maximo
pcap 0 1 -1.9268 -0.6818 0.0801 0.5457 2.1846
hwy 0 1 -1.7932 -0.7862 0.0519 0.5584 2.1151
water 0 1 -1.8459 -0.8257 0.1363 0.6018 2.0843
util 0 1 -2.0375 -0.7796 0.1472 0.5682 2.1704
pc 0 1 -2.1138 -0.5886 0.0449 0.5986 2.2014
gsp 0 1 -1.7662 -0.7578 0.0445 0.6358 2.2742
emp 0 1 -1.8482 -0.7805 0.0724 0.6977 2.1503
unemp 0 1 -1.8575 -0.7329 -0.0806 0.6054 2.7309

Como puede verse en la anterior tabla, tras la transformación y estandarización de las variables, todas presentan media 0 y desviación típica 1, lo que permite compararlas en una escala común. Este paso resulta necesario antes de aplicar el escalado multidimensional, ya que las variables originales tenían magnitudes muy diferentes y podían distorsionar el cálculo de distancias. Los rangos observados muestran que la transformación logarítmica ha suavizado la dispersión de las variables económicas de volumen, situando la mayoría de los valores entre aproximadamente -2 y 2. La tasa de desempleo, aunque no fue transformada logarítmicamente, también se estandarizó y muestra un valor máximo más elevado, lo que sugiere la presencia de algún estado con desempleo relativamente alto. En conjunto, la tabla confirma que las variables están preparadas para construir una matriz de distancias equilibrada entre estados.

Análisis de la estructura de relación entre variables con una representación adecuada.

Para atender a esta cuestión, se considera que lo más adecuado es analizar la matriz de correlaciones entre las variables transformadas y estandarizadas, y representarla mediante un correlograma. Así puedes ver qué variables se mueven juntas y si existen bloques de variables relacionadas.

# Convertir a data frame las variables transformadas y estandarizadas
Produc_1986_scaled_df <- as.data.frame(Produc_1986_scaled)

# Matriz de correlaciones
cor_produc_1986 <- cor(
  Produc_1986_scaled_df,
  use = "pairwise.complete.obs"
)

# Tabla de correlaciones
kable(
  round(cor_produc_1986, 3),
  caption = "Matriz de correlaciones entre variables transformadas y estandarizadas",
  align = "c"
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Matriz de correlaciones entre variables transformadas y estandarizadas
pcap hwy water util pc gsp emp unemp
pcap 1.000 0.977 0.972 0.985 0.958 0.978 0.966 0.144
hwy 0.977 1.000 0.934 0.929 0.958 0.951 0.932 0.238
water 0.972 0.934 1.000 0.957 0.928 0.979 0.974 0.039
util 0.985 0.929 0.957 1.000 0.929 0.962 0.956 0.091
pc 0.958 0.958 0.928 0.929 1.000 0.954 0.929 0.275
gsp 0.978 0.951 0.979 0.962 0.954 1.000 0.994 0.071
emp 0.966 0.932 0.974 0.956 0.929 0.994 1.000 0.030
unemp 0.144 0.238 0.039 0.091 0.275 0.071 0.030 1.000
# Correlograma
corrplot(
  cor_produc_1986,
  method = "color",
  type = "lower",
  order = "hclust",
  addCoef.col = "black",
  number.cex = 0.70,
  tl.col = "#1F2937",
  tl.srt = 35,
  tl.cex = 0.85,
  diag = FALSE,
  col = colorRampPalette(c("#B2182B", "white", "#2166AC"))(200),
  mar = c(0, 0, 2, 0),
  title = "Estructura de relación entre variables económicas. Año 1986"
)

La matriz de correlaciones muestra una estructura relacional muy marcada. Las variables vinculadas al capital público, infraestructuras, capital privado, producto bruto estatal y empleo presentan correlaciones positivas muy elevadas entre sí. Esto indica que comparten una dimensión común relacionada con el tamaño económico y la dotación infraestructural de los estados. En particular, variables como gsp y emp, o pcap y util, muestran asociaciones prácticamente lineales, lo que evidencia una elevada redundancia informativa entre estos indicadores.

En cambio, la tasa de desempleo presenta correlaciones bajas con el resto de variables, lo que sugiere que mide una dimensión distinta, más vinculada a la situación del mercado laboral que al volumen económico o infraestructural de cada estado. Por tanto, unemp puede aportar una fuente de diferenciación adicional dentro del análisis.

De cara al escalado multidimensional, esta estructura implica que las distancias entre estados estarán muy condicionadas por el bloque de variables económicas altamente correlacionadas. En consecuencia, la representación MDS tenderá a ordenar los estados principalmente según una dimensión de tamaño económico e infraestructural, mientras que la tasa de desempleo podrá introducir diferencias secundarias entre estados que, aun siendo similares en tamaño económico, presenten situaciones laborales distintas.

Matiz metodológico:La alta correlación entre varias variables no impide aplicar el MDS, pero sí debe tenerse en cuenta en la interpretación, ya que las variables redundantes pueden reforzar en exceso una misma dimensión latente dentro del cálculo de distancias.

Se aplican MDS clásico con cmdscale(), MDS no métrico con isoMDS() y Sammon con sammon().

A continuación, se realiza el análisis de escalado multidimensional utilizando tres metodologías distintas: un MDS clásico, un MDS no métrico y, por último un MDS mediante el método de Sammon, una variante del escalado no métrico que busca minimizar una función de error o Stress con una característica particular: otorga un mayor peso a las distancias pequeñas originales entre los datos. Esto permite que el mapa resultante preserve con mayor precisión las relaciones de proximidad local entre los individuos u objetos analizados.

Posteriormente, una vez obtenidos los resultados se mide la bondad de los mismos con distintas medidas de bondad (o maldad):

El GOF y las medidas de stress son indicadores utilizados para evaluar la bondad del ajuste en el escalamiento multidimensional, es decir, para comprobar hasta qué punto el mapa obtenido representa adecuadamente las disimilitudes o distancias originales entre los casos.

El GOF, o Goodness of Fit, mide la bondad del ajuste desde la perspectiva contraria: indica qué proporción de la información original queda bien representada en la solución obtenida. Se utiliza especialmente en el MDS clásico y suele calcularse a partir de los autovalores. En este caso, cuanto más alto es el valor, mejor es el ajuste. Valores cercanos a 1 indican que la representación es muy buena.

Las medidas de stress indican el grado de “maldad”, es decir, de error o desajuste de la solución. Comparan las distancias representadas en el espacio reducido con las disimilitudes originales. Por tanto, cuanto menor es el stress, mejor es el ajuste. Un valor cercano a 0 indica una representación muy buena, mientras que valores elevados indican que el mapa no reproduce bien la estructura original de los datos.

En resumen, el stress mide el error de la solución y debe ser bajo, mientras que el GOF mide la calidad de la representación y debe ser alto. Además, el ajuste también puede evaluarse visualmente mediante el diagrama de Shepard, donde una buena solución se observa cuando los puntos siguen aproximadamente una línea diagonal.

# 1. MATRIZ DE DATOS PARA MDS

# Usamos los estados como nombres de fila
rownames(Produc_1986_scaled_df) <- Produc_1986$state

# Matriz de distancias euclídeas
dist_mds <- dist(Produc_1986_scaled_df, method = "euclidean")

# 2. MDS CLÁSICO CON cmdscale()

mds_clasico <- cmdscale(
  dist_mds,
  k = 2,
  eig = TRUE
)

coord_clasico <- as.data.frame(mds_clasico$points)
colnames(coord_clasico) <- c("Dim1", "Dim2")
coord_clasico$state <- rownames(Produc_1986_scaled_df)
coord_clasico$Metodo <- "MDS clásico"

# Bondad de ajuste del MDS clásico
mds_clasico$GOF
## [1] 0.9756712 0.9756712
# 3. MDS NO MÉTRICO CON isoMDS()

mds_iso <- isoMDS(
  dist_mds,
  y = mds_clasico$points,
  k = 2,
  maxit = 100
)
## initial  value 1.683819 
## iter   5 value 1.457087
## final  value 1.452029 
## converged
coord_iso <- as.data.frame(mds_iso$points)
colnames(coord_iso) <- c("Dim1", "Dim2")
coord_iso$state <- rownames(Produc_1986_scaled_df)
coord_iso$Metodo <- "MDS no métrico"

# Stress del MDS no métrico
mds_iso$stress
## [1] 1.452029
# 4. MDS DE SAMMON CON sammon()

mds_sammon <- sammon(
  dist_mds,
  y = mds_clasico$points,
  k = 2,
  niter = 100
)
## Initial stress        : 0.00371
## stress after  10 iters: 0.00228, magic = 0.500
## stress after  20 iters: 0.00228, magic = 0.500
coord_sammon <- as.data.frame(mds_sammon$points)
colnames(coord_sammon) <- c("Dim1", "Dim2")
coord_sammon$state <- rownames(Produc_1986_scaled_df)
coord_sammon$Metodo <- "Sammon"

# Stress de Sammon
mds_sammon$stress
## [1] 0.002282167
# Tabla comparativa de ajuste

resumen_mds <- data.frame(
  Metodo = c(
    "MDS clásico - cmdscale()",
    "MDS no métrico - isoMDS()",
    "Sammon - sammon()"
  ),
  Indicador = c(
    "GOF",
    "Stress",
    "Stress"
  ),
  Valor = c(
    round(mds_clasico$GOF[1], 4),
    round(mds_iso$stress, 4),
    round(mds_sammon$stress, 4)
  )
)

kable(
  resumen_mds,
  caption = "Indicadores de ajuste de los métodos de escalado multidimensional",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Indicadores de ajuste de los métodos de escalado multidimensional
Metodo Indicador Valor
MDS clásico - cmdscale() GOF 0.9757
MDS no métrico - isoMDS() Stress 1.4520
Sammon - sammon() Stress 0.0023

Los indicadores de ajuste obtenidos muestran que la representación bidimensional mediante escalado multidimensional es adecuada. En el MDS clásico, el valor de bondad de ajuste es muy elevado, \(GOF = 0.9757\), lo que indica que las dos dimensiones retenidas conservan una proporción muy alta de la estructura de distancias original entre los estados. Por su parte, el MDS no métrico presenta un valor de stress bajo, 1.4520, lo que sugiere que la ordenación relativa de las disimilitudes entre estados se reproduce correctamente en el espacio bidimensional.

El método de Sammon obtiene un stress especialmente reducido, 0.0023, lo que indica una representación muy fiel de las distancias, especialmente entre estados próximos. En conjunto, los tres métodos ofrecen resultados consistentes y apoyan la utilización de una solución bidimensional para representar las semejanzas y diferencias económicas entre los estados en 1986.

No obstante, los indicadores no son directamente comparables entre sí, ya que el MDS clásico utiliza una medida de bondad de ajuste, mientras que isoMDS() y sammon() utilizan medidas de stress. En el primer caso, valores más altos indican mejor ajuste; en los otros dos, valores más bajos indican mejor representación.

Se genera un gráfico comparativo de las soluciones obtenidas.

Se realiza un gráfico comparativo que permita observar la estructura de proximidad entre estados bajo los tres métodos de escalado multidimensional previamente ejecutados. La interpretación debe centrarse en la cercanía relativa entre estados y en la formación de agrupaciones, más que en el valor absoluto de los ejes, ya que la orientación y escala de las dimensiones puede variar entre métodos. En conjunto, la comparación permite valorar si las soluciones obtenidas son consistentes en la representación de las similitudes económicas entre estados.

# Gráfico comparativo
coord_todos_mds <- rbind(
  coord_clasico,
  coord_iso,
  coord_sammon
)

ggplot(
  coord_todos_mds,
  aes(
    x = Dim1,
    y = Dim2,
    label = state
  )
) +
  geom_point(
    size = 2.2,
    alpha = 0.85,
    color = "#1F4E79"
  ) +
  geom_text(
    size = 2.3,
    vjust = -0.6,
    check_overlap = TRUE
  ) +
  facet_wrap(
    ~ Metodo,
    scales = "free"
  ) +
  labs(
    title = "Comparación de soluciones de escalado multidimensional",
    subtitle = "MDS clásico, MDS no métrico y método de Sammon",
    x = "Dimensión 1",
    y = "Dimensión 2"
  ) +
  theme_minimal(base_size = 12)

Los tres mapas muestran una estructura muy parecida. Esto es importante porque indica que la solución es estable y no depende excesivamente del método utilizado.

En los tres gráficos se observa que algunos estados aparecen claramente alejados del núcleo central. Por ejemplo, California y New York se sitúan hacia la derecha, lo que probablemente refleja su mayor tamaño económico, mayor empleo, mayor producto bruto estatal y mayor dotación de capital e infraestructuras. Esto es coherente con la matriz de correlaciones, donde las variables económicas de volumen estaban muy fuertemente asociadas entre sí.

También aparecen agrupaciones de estados más pequeños o con menor volumen económico hacia la zona izquierda del gráfico. En la parte inferior aparecen estados como Mississippi, West Virginia o Louisiana, que se diferencian del resto en la segunda dimensión. Esta segunda dimensión podría estar recogiendo diferencias vinculadas a la tasa de desempleo o a una estructura económica menos alineada con el eje principal de tamaño económico.

No obstante, conviene recordar que en MDS los ejes no se interpretan de forma directa como variables originales. Lo relevante es la proximidad relativa entre estados, no tanto la orientación exacta del gráfico. Además, los mapas pueden rotarse o reflejarse sin cambiar su significado.

Se selecciona una solución principal, interpretando económicamente las dos dimensiones del mapa.

Se selecciona como solución principal el MDS clásico mediante cmdscale(), dado que presenta una bondad de ajuste muy elevada (GOF = 0.9757) y ofrece una representación bidimensional clara y consistente con las soluciones obtenidas mediante MDS no métrico y Sammon. Además, desde el criterio de parsimonia, resulta preferible al no requerir procedimientos iterativos de optimización ni transformaciones adicionales de las distancias, proporcionando una solución estable y fácilmente interpretable que reproduce adecuadamente la estructura de proximidades observada en los datos. Aunque el método de Sammon obtiene un valor de stress extremadamente reducido y el MDS no métrico también ofrece un ajuste satisfactorio, ambos generan configuraciones muy similares a la obtenida mediante el MDS clásico, sin aportar una mejora sustancial en la interpretación económica de los resultados. Por ello, se opta por el modelo más sencillo que explica adecuadamente la estructura de los datos.

La primera dimensión del mapa puede interpretarse como un eje de tamaño económico y dotación de capital e infraestructuras, separando a los estados de mayor volumen económico, como California, New York o Texas, de estados de menor dimensión económica. La segunda dimensión introduce una diferenciación adicional vinculada al perfil económico-laboral de los estados, separando algunos estados del sur y de menor dinamismo relativo de otros estados con perfiles económicos más favorables. En conjunto, el mapa permite representar de forma sintética las semejanzas y diferencias económicas entre los estados en 1986.

La interpretación de los ejes en MDS debe hacerse con prudencia, ya que las dimensiones no son variables observadas directamente. Lo más relevante es la posición relativa entre estados y la formación de agrupaciones o separaciones dentro del mapa.

Se identifican aquellos estados que aparecen como más próximos y cuáles son los más alejados.

Para contestar esta cuestión de forma rigurosa, lo mejor es no identificar los estados visualmente observando el gráfico, sino calcular las distancias entre estados en la solución MDS seleccionada, que en nuestro caso ha sido el MDS clásico.

# Top 10 pares de estados más cercanos y más alejados en la solución MDS clásica

# Coordenadas de la solución principal
coord_principal <- coord_clasico[, c("Dim1", "Dim2")]
rownames(coord_principal) <- coord_clasico$state

# Matriz de distancias euclídeas en el mapa MDS
dist_mapa <- as.matrix(dist(coord_principal))

# Eliminar la diagonal
diag(dist_mapa) <- NA

# Convertir matriz de distancias a tabla
tabla_distancias <- as.data.frame(as.table(dist_mapa))

colnames(tabla_distancias) <- c("Estado_1", "Estado_2", "Distancia")

# Eliminar NA
tabla_distancias <- tabla_distancias[!is.na(tabla_distancias$Distancia), ]

# Eliminar pares duplicados simétricos
tabla_distancias <- tabla_distancias[
  as.character(tabla_distancias$Estado_1) < as.character(tabla_distancias$Estado_2),
]

# Top 10 estados más cercanos

pares_mas_cercanos <- tabla_distancias[
  order(tabla_distancias$Distancia),
][1:10, ]

# Añadir columna de posición
pares_mas_cercanos <- data.frame(
  Puesto = 1:nrow(pares_mas_cercanos),
  pares_mas_cercanos
)

rownames(pares_mas_cercanos) <- NULL

kable(
  pares_mas_cercanos,
  caption = "Diez pares de estados más cercanos en la solución MDS clásica",
  align = "c",
  row.names = FALSE,
  digits = 3
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Diez pares de estados más cercanos en la solución MDS clásica
Puesto Estado_1 Estado_2 Distancia
1 INDIANA WISCONSIN 0.043
2 MINNESOTA NORTH_CAROLINA 0.118
3 NORTH_CAROLINA VIRGINIA 0.147
4 ALABAMA KENTUCKY 0.183
5 ARIZONA COLORADO 0.204
6 MINNESOTA VIRGINIA 0.248
7 KANSAS SOUTH_CAROLINA 0.255
8 DELAWARE SOUTH_DAKOTA 0.291
9 OKLAHOMA OREGON 0.292
10 DELAWARE RHODE_ISLAND 0.298
# Top 10 estados más alejados

pares_mas_alejados <- tabla_distancias[
  order(tabla_distancias$Distancia, decreasing = TRUE),
][1:10, ]

# Añadir columna de posición
pares_mas_alejados <- data.frame(
  Puesto = 1:nrow(pares_mas_alejados),
  pares_mas_alejados
)

rownames(pares_mas_alejados) <- NULL

kable(
  pares_mas_alejados,
  caption = "Diez pares de estados más alejados en la solución MDS clásica",
  align = "c",
  row.names = FALSE,
  digits = 3
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Diez pares de estados más alejados en la solución MDS clásica
Puesto Estado_1 Estado_2 Distancia
1 CALIFORNIA VERMONT 10.729
2 NEW_YORK VERMONT 10.042
3 TEXAS VERMONT 9.759
4 CALIFORNIA DELAWARE 9.640
5 CALIFORNIA RHODE_ISLAND 9.558
6 CALIFORNIA SOUTH_DAKOTA 9.558
7 CALIFORNIA IDAHO 9.430
8 CALIFORNIA NEW_HAMPSHIRE 9.381
9 CALIFORNIA NORTH_DAKOTA 9.288
10 CALIFORNIA WYOMING 9.132

Ejercicio 5 - Correlación Canónica

Para este ejercicio cargamos el conjunto de datos “Snmesp”, que proviene de la librería ““plm” y contiene información de empresas españolas. Trabajaremos con los datos del año 1990 y con las siguientes variables:

  • n: logaritmo del empleo,
  • w: logaritmo de los salarios,
  • y: logaritmo de la producción real,
  • i: logaritmo de los consumos intermedios,
  • k: logaritmo del capital real,
  • f: flujo de caja real.

Se definen los siguientes bloques de variables:

  • Bloque de producción: y, i, k.
  • Bloque laboral-financiero: n, w, f.
# Cargar dataset
data("Snmesp", package = "plm")

# Filtrar año 1990 y seleccionar variables
Snmesp_1990 <- subset(
  Snmesp,
  year == 1990,
  select = c(firm, year, y, i, k, n, w, f)
)

# Eliminar posibles valores perdidos
Snmesp_1990 <- na.omit(Snmesp_1990)

# Definir bloques
bloque_produccion <- Snmesp_1990[, c("y", "i", "k")]
bloque_laboral_financiero <- Snmesp_1990[, c("n", "w", "f")]

# Estandarizar bloques
X_prod <- scale(bloque_produccion)
Y_labfin <- scale(bloque_laboral_financiero)

# Comprobación
dim(Snmesp_1990)
## [1] 738   8
summary(Snmesp_1990)
##       firm            year            y                i         
##  Min.   :  1.0   Min.   :1990   Min.   : 3.730   Min.   : 2.763  
##  1st Qu.:185.2   1st Qu.:1990   1st Qu.: 6.168   1st Qu.: 5.662  
##  Median :369.5   Median :1990   Median : 7.131   Median : 6.646  
##  Mean   :369.5   Mean   :1990   Mean   : 7.148   Mean   : 6.678  
##  3rd Qu.:553.8   3rd Qu.:1990   3rd Qu.: 8.068   3rd Qu.: 7.604  
##  Max.   :738.0   Max.   :1990   Max.   :11.807   Max.   :11.621  
##        k                n               w                 f           
##  Min.   : 1.701   Min.   :2.303   Min.   :-0.7453   Min.   :-1750.87  
##  1st Qu.: 5.368   1st Qu.:3.951   1st Qu.: 0.5174   1st Qu.:   13.38  
##  Median : 6.560   Median :4.868   Median : 0.7483   Median :   48.43  
##  Mean   : 6.563   Mean   :4.897   Mean   : 0.7382   Mean   :  217.17  
##  3rd Qu.: 7.615   3rd Qu.:5.732   3rd Qu.: 0.9829   3rd Qu.:  173.54  
##  Max.   :12.207   Max.   :8.796   Max.   : 1.8965   Max.   : 7441.86

Se trabaja únicamente con las observaciones correspondientes al año 1990. Las variables se dividen en dos bloques: un bloque de producción, formado por y, i y k, y un bloque laboral-financiero, formado por n, w y f. Antes de aplicar la correlación canónica, las variables se estandarizan para evitar que las diferencias de escala afecten a la estimación de las combinaciones lineales.

Descripción inicial de ambos bloques (variables previo escalado)

# Función descriptiva
tabla_descriptiva <- function(datos) {
  data.frame(
    Variable = names(datos),
    Media = round(sapply(datos, mean), 4),
    Desv_tipica = round(sapply(datos, sd), 4),
    Minimo = round(sapply(datos, min), 4),
    Q1 = round(sapply(datos, quantile, probs = 0.25), 4),
    Mediana = round(sapply(datos, median), 4),
    Q3 = round(sapply(datos, quantile, probs = 0.75), 4),
    Maximo = round(sapply(datos, max), 4),
    row.names = NULL
  )
}

desc_produccion <- tabla_descriptiva(bloque_produccion)
desc_labfin <- tabla_descriptiva(bloque_laboral_financiero)

kable(
  desc_produccion,
  caption = "Resumen descriptivo del bloque de producción",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#1F4E79")
Resumen descriptivo del bloque de producción
Variable Media Desv_tipica Minimo Q1 Mediana Q3 Maximo
y 7.1476 1.4145 3.7302 6.1680 7.1313 8.0681 11.8069
i 6.6780 1.4759 2.7628 5.6620 6.6462 7.6039 11.6209
k 6.5628 1.7754 1.7015 5.3678 6.5605 7.6147 12.2068
kable(
  desc_labfin,
  caption = "Resumen descriptivo del bloque laboral-financiero",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#1F4E79")
Resumen descriptivo del bloque laboral-financiero
Variable Media Desv_tipica Minimo Q1 Mediana Q3 Maximo
n 4.8968 1.2226 2.3026 3.9512 4.8675 5.7317 8.7956
w 0.7382 0.3676 -0.7453 0.5174 0.7483 0.9829 1.8965
f 217.1690 683.5622 -1750.8730 13.3779 48.4316 173.5415 7441.8620
# GRÁFICOS BLOQUE PRODUCCIÓN

# Histogramas
par(mfrow = c(2, 3))
for (v in names(bloque_produccion)) {
  hist(bloque_produccion[[v]],
       main = paste("Histograma de", v),
       xlab = v,
       col = "blue",
       border = "white")
}
par(mfrow = c(1, 1))

# Boxplots para detectar dispersión y posibles valores extremos
boxplot(bloque_produccion,
        main = "Distribución de variables bloque producción",
        las = 2,
        col = "blue")

# GRÁFICOS BLOQUE LABORAL-FINANCIERO

# Histogramas
par(mfrow = c(2, 3))
for (v in names(bloque_laboral_financiero)) {
  hist(bloque_laboral_financiero[[v]],
       main = paste("Histograma de", v),
       xlab = v,
       col = "blue",
       border = "white")
}
par(mfrow = c(1, 1))

# Boxplots para detectar dispersión y posibles valores extremos
boxplot(bloque_laboral_financiero,
        main = "Distribución de variables bloque laboral-financiero",
        las = 2,
        col = "blue")

La descripción inicial muestra que las variables del bloque de producción presentan medias y medianas relativamente próximas, lo que sugiere distribuciones moderadamente equilibradas, aunque con diferencias apreciables entre empresas. Dado que y, i y k están expresadas en logaritmos, las diferencias reflejan principalmente variaciones relativas en producción, consumos intermedios y capital real. En el bloque laboral-financiero, el empleo y los salarios presentan también una estructura relativamente estable, especialmente en el caso de w, cuya dispersión es menor.

La variable f, correspondiente al flujo de caja real, presenta un comportamiento claramente diferente. Su media es muy superior a la mediana y la desviación típica es elevada, con presencia tanto de valores negativos como de valores máximos muy altos. Esto indica una fuerte asimetría y la existencia de empresas con flujos de caja extremos. Por este motivo, aunque se mantiene en escala original en la descripción inicial, para la correlación canónica se estandarizan todas las variables. Además, debe interpretarse con cautela el papel de f, ya que sus valores extremos pueden influir en las correlaciones y en las combinaciones canónicas.

Como comprobación de robustez, podría considerarse una transformación arco seno hiperbólico como \(asinh(f)\), que permite tratar valores negativos y reduce la influencia de valores extremos, manteniendo una interpretación similar a una transformación logarítmica para valores elevados.

Se analizan las correlaciones cruzadas entre los dos grupos de variables o bloques.

# CORRELACIONES CRUZADAS ENTRE LOS DOS BLOQUES DE VARIABLES

# Bloque de producción: y, i, k
# Bloque laboral-financiero: n, w, f


# Calcular matriz de correlaciones cruzadas
cor_cruzadas <- cor(
  X_prod,
  Y_labfin,
  use = "pairwise.complete.obs"
)


# TABLA FORMATEADA

kable(
  round(cor_cruzadas, 3),
  caption = "Correlaciones cruzadas entre el bloque de producción y el bloque laboral-financiero",
  align = "c"
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  ) %>%
  column_spec(
    1,
    bold = TRUE
  )
Correlaciones cruzadas entre el bloque de producción y el bloque laboral-financiero
n w f
y 0.900 0.520 0.496
i 0.855 0.492 0.472
k 0.820 0.484 0.441
# GRÁFICO DE CORRELACIONES CRUZADAS


corrplot(
  cor_cruzadas,
  method = "color",
  is.corr = FALSE,
  addCoef.col = "black",
  number.cex = 0.85,
  tl.col = "#1F2937",
  tl.srt = 35,
  tl.cex = 0.9,
  col = colorRampPalette(
    c("#B2182B", "white", "#2166AC")
  )(200),
  mar = c(0, 0, 2, 0),
  title = "Correlaciones cruzadas entre bloques"
)

La matriz de correlaciones cruzadas muestra asociaciones positivas entre todas las variables del bloque de producción y las variables del bloque laboral-financiero. Las relaciones más intensas se observan entre las variables productivas y el empleo (n), especialmente entre producción real y empleo (r = 0.900), consumos intermedios y empleo (r = 0.855) y capital real y empleo (r = 0.820). Esto indica que las empresas con mayor dimensión productiva tienden también a presentar mayores niveles de empleo.

Las correlaciones con los salarios (w) y con el flujo de caja real (f) son también positivas, aunque de intensidad moderada. Por tanto, las empresas más productivas tienden a mostrar mayores salarios y mayor flujo de caja, pero estas relaciones son menos directas que la existente con el empleo. En conjunto, los resultados anticipan la existencia de una relación multivariante relevante entre ambos bloques, que será sintetizada posteriormente mediante la correlación canónica.

Correlación Canónica clásica con cancor().

Se aplica una correlación canónica clásica mediante cancor() sobre los dos bloques previamente estandarizados. El objetivo es obtener combinaciones lineales del bloque de producción y del bloque laboral-financiero que maximicen la correlación entre ambos conjuntos de variables. Las correlaciones canónicas indican la intensidad de la relación entre cada par de variables canónicas, mientras que las cargas permiten interpretar qué variables contribuyen más a cada dimensión.

# Aplicar correlación canónica clásica
cca_1990 <- cancor(
  x = X_prod,
  y = Y_labfin
)

# Ver resultados principales
cca_1990
## $cor
## [1] 0.970441254 0.034395533 0.006036322
## 
## $xcoef
##           [,1]        [,2]        [,3]
## y -0.093068210  0.07691265 -0.25899607
## i  0.060269887 -0.01226041  0.25278788
## k -0.002497595 -0.07145545  0.02071595
## 
## $ycoef
##           [,1]        [,2]        [,3]
## n -0.030464156 -0.00300201  0.02904165
## w -0.010004692 -0.02618981 -0.02717680
## f -0.003901059  0.03310731 -0.02409966
## 
## $xcenter
##             y             i             k 
##  2.503173e-16  8.988947e-17 -7.223431e-17 
## 
## $ycenter
##             n             w             f 
## -1.696891e-16 -1.852605e-17  1.129686e-17
# Graficamos las correlaciones canónicas
cor_can <- cca_1990$cor

barplot(
  cor_can,
  names.arg = paste0("Dimensión ", seq_along(cor_can)),
  ylim = c(0, 1),
  col = "#1F4E79",
  main = "Correlaciones canónicas",
  ylab = "Correlación canónica"
)

abline(h = 0.5, lty = 2, col = "grey40")

Los resultados de la correlación canónica clásica muestran que la relación entre el bloque productivo (y,i,k) y el bloque laboral-financiero (n,w,f) se concentra casi exclusivamente en la primera dimensión canónica. La primera correlación canónica alcanza un valor muy elevado, \(\rho_1 = 0.970\), lo que indica una asociación lineal muy intensa entre una combinación de las variables productivas y una combinación de las variables laborales-financieras. En cambio, la segunda y tercera correlación canónica, con valores de 0.034 y 0.006, son prácticamente nulas, por lo que no aportan dimensiones interpretativas relevantes adicionales.

En cuanto a los coeficientes canónicos, en el bloque productivo la primera función canónica está principalmente determinada por y e i, mientras que k tiene un peso directo muy reducido. En el bloque laboral-financiero, la primera función canónica aparece dominada por n, seguida de forma mucho más débil por w y f. Esto sugiere que la asociación principal entre ambos bloques se articula especialmente a través de la relación entre la dinámica productiva y la variable laboral n.

No obstante, los coeficientes canónicos deben interpretarse con cautela, ya que pueden verse afectados por la escala de las variables y por la colinealidad interna dentro de cada bloque. Por ello, la interpretación sustantiva debe complementarse con las cargas canónicas y los mapas de variables. En conjunto, el resultado permite concluir que existe una estructura común muy fuerte entre ambos grupos de variables, pero que dicha estructura es esencialmente unidimensional.

Por lo tanto, existe una relación canónica muy fuerte entre producción y variables laborales-financieras, y las otras dos dimensiones son irrelevantes desde el punto de vista interpretativo. El primer eje parece estar asociado sobre todo a la conexión entre y/i en el bloque productivo y n en el bloque laboral-financiero, es decir entre producción real y consumos intermedios ,desde el bloque productivo, con la variable empleo en el bloque laboral-financiero.

Obtención e interpretación de las correlaciones canónicas.

# Correlaciones canónicas

cor_can <- cca_1990$cor

tabla_cor_can <- data.frame(
  Funcion_canonica = paste0("Par canónico ", seq_along(cor_can)),
  Correlacion_canonica = round(cor_can, 4),
  Correlacion_cuadrada = round(cor_can^2, 4)
)

kable(
  tabla_cor_can,
  caption = "Correlaciones canónicas entre el bloque de producción y el bloque laboral-financiero",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Correlaciones canónicas entre el bloque de producción y el bloque laboral-financiero
Funcion_canonica Correlacion_canonica Correlacion_cuadrada
Par canónico 1 0.9704 0.9418
Par canónico 2 0.0344 0.0012
Par canónico 3 0.0060 0.0000
# Coeficientes canónicos

coef_prod <- as.data.frame(round(cca_1990$xcoef, 4))
coef_labfin <- as.data.frame(round(cca_1990$ycoef, 4))

colnames(coef_prod) <- paste0("U", 1:ncol(coef_prod))
colnames(coef_labfin) <- paste0("V", 1:ncol(coef_labfin))

coef_prod$Variable <- rownames(coef_prod)
coef_labfin$Variable <- rownames(coef_labfin)

coef_prod <- coef_prod[, c("Variable", paste0("U", 1:(ncol(coef_prod) - 1)))]
coef_labfin <- coef_labfin[, c("Variable", paste0("V", 1:(ncol(coef_labfin) - 1)))]

rownames(coef_prod) <- NULL
rownames(coef_labfin) <- NULL

kable(
  coef_prod,
  caption = "Coeficientes canónicos del bloque de producción",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Coeficientes canónicos del bloque de producción
Variable U1 U2 U3
y -0.0931 0.0769 -0.2590
i 0.0603 -0.0123 0.2528
k -0.0025 -0.0715 0.0207
kable(
  coef_labfin,
  caption = "Coeficientes canónicos del bloque laboral-financiero",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Coeficientes canónicos del bloque laboral-financiero
Variable V1 V2 V3
n -0.0305 -0.0030 0.0290
w -0.0100 -0.0262 -0.0272
f -0.0039 0.0331 -0.0241
# Obtener las variables canónicas


U_prod <- as.matrix(X_prod) %*% cca_1990$xcoef
V_labfin <- as.matrix(Y_labfin) %*% cca_1990$ycoef

colnames(U_prod) <- paste0("U", 1:ncol(U_prod))
colnames(V_labfin) <- paste0("V", 1:ncol(V_labfin))

# Comprobar correlaciones entre pares canónicos
cor(U_prod, V_labfin)
##               V1            V2            V3
## U1  9.704413e-01  6.358396e-16 -7.788648e-16
## U2 -2.460754e-16  3.439553e-02  2.296426e-16
## U3 -9.622802e-16 -1.446228e-16  6.036322e-03
# Cargas canónicas
# Correlación entre variables originales y variables canónicas

cargas_prod <- cor(X_prod, U_prod)
cargas_labfin <- cor(Y_labfin, V_labfin)

kable(
  round(cargas_prod, 3),
  caption = "Cargas canónicas del bloque de producción",
  align = "c"
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Cargas canónicas del bloque de producción
U1 U2 U3
y -0.967 0.102 0.235
i -0.918 0.140 0.371
k -0.883 -0.430 0.190
kable(
  round(cargas_labfin, 3),
  caption = "Cargas canónicas del bloque laboral-financiero",
  align = "c"
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Cargas canónicas del bloque laboral-financiero
V1 V2 V3
n -0.959 0.085 0.271
w -0.557 -0.531 -0.639
f -0.527 0.700 -0.481

A partir de los resultados, la correlación canónica clásica muestra una relación multivariante muy fuerte entre el bloque de producción y el bloque laboral-financiero, pero esa relación queda concentrada casi por completo en el primer par canónico.

  1. Correlaciones canónicas

La primera correlación canónica es muy elevada:

  • Par canónico 1: \(\rho = 0.9704\)
  • \(\rho^2 = 0.9418\)

Esto significa que la primera combinación lineal del bloque de producción está fuertemente asociada con la primera combinación lineal del bloque laboral-financiero. La correlación cuadrada indica que ambas variables canónicas comparten aproximadamente un 94,18 % de varianza común.

En cambio, el segundo y tercer par canónico son prácticamente irrelevantes:

  • Par canónico 2: \(\rho = 0.0344\)
  • Par canónico 3: \(\rho = 0.0060\)

Por tanto, la interpretación debe centrarse casi exclusivamente en la primera función canónica.

  1. Interpretación de las cargas canónicas

Las cargas del bloque de producción sobre la primera variable canónica son todas muy elevada, siendo dominante la producción (y), seguido de cerca de los consumos intermedios (i) y con posterioridad del capital (k):

  • y = -0.967
  • i = -0.918
  • k = -0.883

Esto indica que la primera variable canónica del bloque productivo representa una dimensión general de tamaño o escala productiva de la empresa, asociada simultáneamente con producción real, consumos intermedios y capital real.

En el bloque laboral-financiero, las cargas sobre la primera variable canónica son:

  • n = -0.959
  • w = -0.557
  • f = -0.527

La variable más importante es el empleo (n), seguida de salarios (w) y flujo de caja (f), con cargas moderadas. Por tanto, la primera variable canónica del segundo bloque puede interpretarse como una dimensión de escala laboral-financiera, especialmente dominada por el empleo.

Es importante aclarar metodológicamente que la interpretación del signo negativo no implica una relación inversa, ya que en correlación canónica, el signo de las variables canónicas es arbitrario: se podrían multiplicar ambas variables por -1 y la interpretación sería la misma. Lo relevante es que las variables de ambos bloques cargan en la misma dirección y que la correlación entre las dos variables canónicas es positiva.

Representación gráfica del primer par de variables canónicas y comentario de su significado.

# Representación gráfica del primer par de variables canónicas


colnames(U_prod) <- paste0("U", 1:ncol(U_prod))
colnames(V_labfin) <- paste0("V", 1:ncol(V_labfin))

# Base para el gráfico
datos_can <- data.frame(
  firm = Snmesp_1990$firm,
  U1_Produccion = U_prod[, 1],
  V1_Laboral_Financiero = V_labfin[, 1]
)

# Correlación del primer par canónico
cor_U1_V1 <- cor(
  datos_can$U1_Produccion,
  datos_can$V1_Laboral_Financiero
)

# Gráfico
ggplot(
  datos_can,
  aes(
    x = U1_Produccion,
    y = V1_Laboral_Financiero
  )
) +
  geom_point(
    size = 2.2,
    alpha = 0.70,
    color = "#1F4E79"
  ) +
  geom_smooth(
    method = "lm",
    se = FALSE,
    linewidth = 0.9,
    color = "#B2182B"
  ) +
  geom_vline(
    xintercept = 0,
    linetype = "dashed",
    color = "gray70"
  ) +
  geom_hline(
    yintercept = 0,
    linetype = "dashed",
    color = "gray70"
  ) +
  labs(
    title = "Primer par de variables canónicas",
    subtitle = paste0(
      "Correlación canónica = ",
      round(cor_U1_V1, 3)
    ),
    x = "Primera variable canónica del bloque de producción",
    y = "Primera variable canónica del bloque laboral-financiero"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold", size = 15),
    plot.subtitle = element_text(size = 11, color = "gray30"),
    panel.grid.minor = element_blank()
  )

El gráfico confirma visualmente el resultado numérico. La nube de puntos aparece claramente alineada en sentido positivo, con una correlación canónica de aproximadamente 0.97.

Esto significa que las empresas con mayor puntuación en la dimensión productiva tienden también a presentar mayor puntuación en la dimensión laboral-financiera. En términos sustantivos, las empresas con mayor producción, mayor consumo intermedio y mayor capital suelen estar asociadas con mayor empleo, mayores salarios y mayor flujo de caja.

El gráfico refuerza la idea de que el primer par canónico recoge una dimensión general de tamaño empresarial o escala económica de la empresa.

Construcción de un mapa de variables o representación equivalente que ayude a interpretar las cargas de cada bloque.

# MAPA DE VARIABLES SEGÚN CARGAS CANÓNICAS

# 1. Preparar datos para el mapa de variables

mapa_prod <- data.frame(
  Variable = rownames(cargas_prod),
  Dim1 = cargas_prod[, 1],
  Dim2 = cargas_prod[, 2],
  Bloque = "Producción"
)

mapa_labfin <- data.frame(
  Variable = rownames(cargas_labfin),
  Dim1 = cargas_labfin[, 1],
  Dim2 = cargas_labfin[, 2],
  Bloque = "Laboral-financiero"
)

mapa_variables <- rbind(mapa_prod, mapa_labfin)

# Círculo de referencia
circulo <- data.frame(
  x = cos(seq(0, 2 * pi, length.out = 200)),
  y = sin(seq(0, 2 * pi, length.out = 200))
)

# 2. Mapa de variables según cargas canónicas

ggplot() +
  geom_path(
    data = circulo,
    aes(x = x, y = y),
    color = "gray70",
    linewidth = 0.6
  ) +
  geom_hline(
    yintercept = 0,
    linetype = "dashed",
    color = "gray70"
  ) +
  geom_vline(
    xintercept = 0,
    linetype = "dashed",
    color = "gray70"
  ) +
  geom_segment(
    data = mapa_variables,
    aes(
      x = 0,
      y = 0,
      xend = Dim1,
      yend = Dim2,
      color = Bloque
    ),
    arrow = arrow(length = unit(0.18, "cm")),
    linewidth = 0.8
  ) +
  geom_point(
    data = mapa_variables,
    aes(
      x = Dim1,
      y = Dim2,
      color = Bloque
    ),
    size = 3
  ) +
  geom_text_repel(
    data = mapa_variables,
    aes(
      x = Dim1,
      y = Dim2,
      label = Variable,
      color = Bloque
    ),
    size = 4,
    max.overlaps = 20
  ) +
  coord_equal(
    xlim = c(-1, 1),
    ylim = c(-1, 1)
  ) +
  facet_wrap(~ Bloque) +
  labs(
    title = "Mapa de variables según cargas canónicas",
    subtitle = "Cargas de las variables en las dos primeras funciones canónicas",
    x = "Primera dimensión canónica",
    y = "Segunda dimensión canónica",
    color = "Bloque"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold", size = 15),
    plot.subtitle = element_text(size = 11, color = "gray30"),
    strip.text = element_text(face = "bold"),
    panel.grid.minor = element_blank()
  )

El mapa de variables muestra que producción (y), consumos intermedios (i) y capital (k) aparecen muy próximas entre sí y alejadas del origen en la primera dimensión. Esto confirma que el bloque de producción tiene una estructura muy coherente: las tres variables participan conjuntamente en la definición del primer eje canónico.

En el bloque laboral-financiero, empleo (n) aparece también muy alejada del origen en la primera dimensión, lo que confirma su papel central; sin embargro, las variables salarios (w) y flujo de caja (f), pese a que también contribuyen a la primera dimensión, lo hacen con una menor intensidad.

En cuanto a la segunda dimensión, el aspecto más remarcable que puede observarse en el mapa muestra cierta oposición entre flujo de caja (f) y salarios (w), es decir, a mayor flujo de caja menor gasto en salarios tiene una empresa y vicebersa. Esta interpretación tiene especial significado de forma interna en el bloque laboral-financiero. Sin embargo, como la segunda correlación canónica es muy baja (\(\rho =0.0344\)), esa dimensión no debe interpretarse como una relación multivariante sustantiva entre bloques. Por tanto, esta segunda dimensión puede ser útil para describir variación interna dentro de los bloques, pero no una asociación relevante entre los bloques comparados: producción y bloque laboral-financiero.

Por otro lado, interpretando la segunda dimensión del bloque de producción parece estar asociada principalmente al capital (k), que presenta la carga más relevante y de signo opuesto a producción (y) y consumos intermedios (i). Esto, al igual que con el bloque anterior, debería interpretarse como una diferenciación interna entre empresas más intensivas en capital y empresas cuya dimensión productiva se refleja más en producción y consumos intermedios. No obstante, como ya dijimos antes, dado que la segunda correlación canónica es muy baja (r = 0.0344), esta dimensión no debe considerarse una relación sustantiva entre bloques, sino un patrón secundario de interpretación limitada intrabloque.

Se comparan los resultados con una solución mediante RGCCA, indicando si la interpretación principal se mantiene.

La solución RGCCA se especifica con \(tau = 0\) porque el objetivo que establece el enunciado del ejercicio es comparar sus resultados con los obtenidos mediante una correlación canónica clásica. En RGCCA, \(tau = 0\) orienta el modelo a maximizar la correlación entre los componentes de los bloques, mientras que valores más próximos a 1 desplazan el criterio hacia la maximización de la covarianza. Por ello, \(tau = 0\) permite construir una solución conceptualmente más próxima a cancor() y evaluar de forma directa si la interpretación principal se mantiene.

Esta elección no implica que \(tau = 0\) sea la única especificación posible de RGCCA, sino que es la más adecuada cuando el objetivo es contrastar la estabilidad de la solución canónica clásica. Una especificación alternativa, como tau = 1 o tau = “optimal”, respondería a una pregunta ligeramente distinta: si la interpretación principal se mantiene bajo una solución más regularizada o más orientada a covarianza.

# COMPARACIÓN CCA CLÁSICA vs RGCCA

# 1. Definir bloques para RGCCA

bloques_rgcca <- list(
  Produccion = as.data.frame(X_prod),
  Laboral_Financiero = as.data.frame(Y_labfin)
)

# Matriz de conexión entre bloques
conexion <- matrix(
  c(0, 1,
    1, 0),
  nrow = 2,
  byrow = TRUE
)

rownames(conexion) <- colnames(conexion) <- names(bloques_rgcca)


# 2. Aplicar RGCCA
# tau = 0 aproxima una lógica más cercana a CCA clásica
# ncomp = 1 porque la CCA clásica mostró que solo el primer par es relevante

rgcca_1990 <- rgcca(
  blocks = bloques_rgcca,
  connection = conexion,
  tau = c(0, 0),
  ncomp = 1,
  scheme = "factorial",
  scale = FALSE,
  verbose = FALSE
)

summary(rgcca_1990)
## Call: method='rgcca', superblock=FALSE, scale=FALSE, scale_block='inertia',
## init='svd', bias=TRUE, tol=1e-08, NA_method='na.ignore', ncomp=c(1,1),
## response=NULL, comp_orth=TRUE 
## There are J = 2 blocks.
## The design matrix is:
##                    Produccion Laboral_Financiero
## Produccion                  0                  1
## Laboral_Financiero          1                  0
## 
## The factorial scheme is used.
## Sum_{j,k} c_jk g(cov(X_j a_j, X_k a_k) = 1.8835 
## 
## The regularization parameter used for Produccion is: 0
## The regularization parameter used for Laboral_Financiero is: 0
# 3. Extraer componentes RGCCA

comp_prod_rgcca <- rgcca_1990$Y$Produccion[, 1]
comp_labfin_rgcca <- rgcca_1990$Y$Laboral_Financiero[, 1]


# 4. Variables canónicas clásicas

U_prod <- as.matrix(X_prod) %*% cca_1990$xcoef
V_labfin <- as.matrix(Y_labfin) %*% cca_1990$ycoef

colnames(U_prod) <- paste0("U", 1:ncol(U_prod))
colnames(V_labfin) <- paste0("V", 1:ncol(V_labfin))


# 5. Alinear signos
# El signo de los componentes es arbitrario


if (cor(comp_prod_rgcca, U_prod[, 1]) < 0) {
  comp_prod_rgcca <- -comp_prod_rgcca
}

if (cor(comp_labfin_rgcca, V_labfin[, 1]) < 0) {
  comp_labfin_rgcca <- -comp_labfin_rgcca
}


# 6. Comparar correlaciones entre componentes

cor_cca_1 <- cca_1990$cor[1]
cor_rgcca_1 <- cor(comp_prod_rgcca, comp_labfin_rgcca)

cor_prod_cca_rgcca <- cor(U_prod[, 1], comp_prod_rgcca)
cor_labfin_cca_rgcca <- cor(V_labfin[, 1], comp_labfin_rgcca)

tabla_comparacion <- data.frame(
  Indicador = c(
    "Correlación primer par CCA clásica",
    "Correlación componentes RGCCA",
    "Correlación componente producción CCA-RGCCA",
    "Correlación componente laboral-financiero CCA-RGCCA"
  ),
  Valor = round(
    c(
      cor_cca_1,
      cor_rgcca_1,
      cor_prod_cca_rgcca,
      cor_labfin_cca_rgcca
    ),
    4
  )
)

kable(
  tabla_comparacion,
  caption = "Comparación entre CCA clásica y RGCCA",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Comparación entre CCA clásica y RGCCA
Indicador Valor
Correlación primer par CCA clásica 0.9704
Correlación componentes RGCCA 0.9704
Correlación componente producción CCA-RGCCA 1.0000
Correlación componente laboral-financiero CCA-RGCCA 1.0000

A continuación se comparan las cargas de ambos métodos

# CARGAS CCA CLÁSICA vs CARGAS RGCCA

# Cargas CCA clásica
cargas_prod_cca <- cor(X_prod, U_prod[, 1])
cargas_labfin_cca <- cor(Y_labfin, V_labfin[, 1])

# Cargas RGCCA
cargas_prod_rgcca <- cor(X_prod, comp_prod_rgcca)
cargas_labfin_rgcca <- cor(Y_labfin, comp_labfin_rgcca)

# Tabla bloque producción
tabla_cargas_prod <- data.frame(
  Variable = colnames(X_prod),
  Carga_CCA = round(as.vector(cargas_prod_cca), 3),
  Carga_RGCCA = round(as.vector(cargas_prod_rgcca), 3)
)

# Tabla bloque laboral-financiero
tabla_cargas_labfin <- data.frame(
  Variable = colnames(Y_labfin),
  Carga_CCA = round(as.vector(cargas_labfin_cca), 3),
  Carga_RGCCA = round(as.vector(cargas_labfin_rgcca), 3)
)

kable(
  tabla_cargas_prod,
  caption = "Comparación de cargas: bloque de producción",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Comparación de cargas: bloque de producción
Variable Carga_CCA Carga_RGCCA
y -0.967 -0.967
i -0.918 -0.918
k -0.883 -0.883
kable(
  tabla_cargas_labfin,
  caption = "Comparación de cargas: bloque laboral-financiero",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Comparación de cargas: bloque laboral-financiero
Variable Carga_CCA Carga_RGCCA
n -0.959 -0.959
w -0.557 -0.557
f -0.527 -0.527
# Gráfico de componentes RGCCA

datos_rgcca <- data.frame(
  Componente_Produccion = comp_prod_rgcca,
  Componente_Laboral_Financiero = comp_labfin_rgcca
)

ggplot(
  datos_rgcca,
  aes(
    x = Componente_Produccion,
    y = Componente_Laboral_Financiero
  )
) +
  geom_point(
    size = 2.2,
    alpha = 0.70,
    color = "#1F4E79"
  ) +
  geom_smooth(
    method = "lm",
    se = FALSE,
    linewidth = 0.9,
    color = "#B2182B"
  ) +
  geom_vline(
    xintercept = 0,
    linetype = "dashed",
    color = "gray70"
  ) +
  geom_hline(
    yintercept = 0,
    linetype = "dashed",
    color = "gray70"
  ) +
  labs(
    title = "Componentes RGCCA de los dos bloques",
    subtitle = paste0(
      "Correlación entre componentes = ",
      round(cor_rgcca_1, 3)
    ),
    x = "Componente RGCCA del bloque de producción",
    y = "Componente RGCCA del bloque laboral-financiero"
  ) +
  theme_minimal(base_size = 12)
## `geom_smooth()` using formula = 'y ~ x'

La comparación con RGCCA confirma que la interpretación principal obtenida mediante la correlación canónica clásica se mantiene. La correlación entre los componentes RGCCA es 0.9704, igual a la primera correlación canónica obtenida con cancor(). Además, los componentes de ambos métodos correlacionan perfectamente entre sí (1.0000 tanto en el bloque de producción como en el bloque laboral-financiero), lo que indica que ambas técnicas están recuperando esencialmente la misma dimensión latente.

Las cargas de las variables también son idénticas en ambos enfoques. En el bloque de producción, y, i y k presentan cargas elevadas, por lo que el componente representa una dimensión general de escala productiva. En el bloque laboral-financiero, la variable con mayor peso sigue siendo el empleo (n), seguida de los salarios (w) y el flujo de caja (f) con cargas moderadas. Por tanto, la relación principal puede interpretarse nuevamente como una asociación entre mayor dimensión productiva de la empresa y mayor dimensión laboral-financiera.

En consecuencia, RGCCA no modifica la interpretación sustantiva obtenida con la CCA clásica, sino que la refuerza. Las empresas con mayor producción real, mayores consumos intermedios y mayor capital real tienden también a presentar mayores niveles de empleo, salarios y flujo de caja. La conclusión principal es, por tanto, estable: ambos métodos identifican una dimensión común de tamaño o escala empresarial.

Como matiz metodológico, puede ser útil resaltar que esta coincidencia tan alta se debe en parte a que RGCCA se ha especificado con dos bloques, tau = 0 y una sola componente por bloque, una configuración muy próxima a la correlación canónica clásica. Es por ello que debe presentarse como una confirmación de consistencia, no como una interpretación alternativa claramente distinta.

Ejercicio 6 - Analisis de Correspondencias Simples

Con el conjunto de datos ca::smoke, que recoge una tabla de contingencia entre cinco grupos de personal de una organizacion ficticia y cuatro categorias de consumo de tabaco (none, light, medium, heavy). Con dichos datos realizamos un Analisis de Correspondencias Simples usando FactoMineR::CA().

Presentación de la tabla de contingencia y descripcion inicial de los perfiles observados.

# Cargar tabla contingencias
data("smoke", package = "ca")

# Convertir a matriz / data frame de contingencia
smoke_tab <- as.data.frame.matrix(smoke)


# Presentar la tabla de contingencias formateada
formattable(smoke_tab)
none light medium heavy
SM 4 2 3 2
JM 4 3 7 4
SE 25 10 12 4
JE 18 24 33 13
SC 10 6 7 2

La tabla de contingencia muestra diferencias iniciales en los perfiles de consumo de tabaco según el grupo de personal. En términos absolutos, el grupo JE es el más numeroso, por lo que concentra una parte importante de las frecuencias observadas, especialmente en las categorías medium y light. No obstante, para interpretar los perfiles conviene atender a la distribución interna de cada fila, ya que los tamaños de los grupos son desiguales.

El grupo SE destaca por una mayor proporción relativa de no fumadores, con 25 casos en la categoría none sobre un total de 51. También el grupo SC presenta un perfil relativamente orientado hacia el no consumo. En cambio, los grupos JM y JE muestran mayor peso relativo de la categoría medium, y en el caso de JM también una presencia proporcionalmente más elevada de consumo heavy, aunque su tamaño muestral es reducido.

En conjunto, la tabla sugiere que el consumo de tabaco no se distribuye de forma homogénea entre los grupos de personal. Algunos grupos parecen más próximos a perfiles de no consumo, mientras que otros se asocian relativamente más con consumos medios o altos. Esta estructura justifica la aplicación de un Análisis de Correspondencias Simples, con el objetivo de representar gráficamente las asociaciones entre grupos de personal y categorías de consumo.

Grupo Perfil dominante
SM Mayor peso de none, aunque con pocos casos
JM Mayor peso de medium y proporción relevante de heavy
SE Perfil claramente orientado a none
JE Perfil más asociado a medium y light
SC Perfil relativamente próximo a none, con bajo heavy

Se aplica el Analisis de Correspondencias Simples, se calculan autovalores e inercias y se genera un biplot simetrico de las dos primeras dimensiones.

Se aplica un Análisis de Correspondencias Simples mediante FactoMineR::CA() sobre la tabla de contingencia entre grupos de personal y categorías de consumo de tabaco. El biplot simétrico representa simultáneamente las filas y las columnas en el espacio definido por las dos primeras dimensiones. La proximidad entre puntos permitirá interpretar asociaciones relativas entre grupos y categorías de consumo, mientras que los puntos más alejados del origen son los que más contribuyen a la estructura de dependencia observada.

# ANÁLISIS DE CORRESPONDENCIAS SIMPLES Y BIPLOT SIMÉTRICO

# Aplicar Análisis de Correspondencias Simples

ca_smoke <- CA(
  smoke_tab,
  graph = FALSE
)


ca_smoke
## **Results of the Correspondence Analysis (CA)**
## The row variable has  5  categories; the column variable has 4 categories
## The chi square of independence between the two variables is equal to 16.44164 (p-value =  0.1718348 ).
## *The results are available in the following objects:
## 
##    name              description                   
## 1  "$eig"            "eigenvalues"                 
## 2  "$col"            "results for the columns"     
## 3  "$col$coord"      "coord. for the columns"      
## 4  "$col$cos2"       "cos2 for the columns"        
## 5  "$col$contrib"    "contributions of the columns"
## 6  "$row"            "results for the rows"        
## 7  "$row$coord"      "coord. for the rows"         
## 8  "$row$cos2"       "cos2 for the rows"           
## 9  "$row$contrib"    "contributions of the rows"   
## 10 "$call"           "summary called parameters"   
## 11 "$call$marge.col" "weights of the columns"      
## 12 "$call$marge.row" "weights of the rows"
summary(ca_smoke)
## 
## Call:
## CA(X = smoke_tab, graph = FALSE) 
## 
## The chi square of independence between the two variables is equal to 16.44164 (p-value =  0.1718348 ).
## 
## Eigenvalues
##                        Dim.1   Dim.2   Dim.3
## Variance               0.075   0.010   0.000
## % of var.             87.756  11.759   0.485
## Cumulative % of var.  87.756  99.515 100.000
## 
## Rows
##          Iner*1000    Dim.1    ctr   cos2    Dim.2    ctr   cos2    Dim.3
## SM     |     2.673 | -0.066  0.330  0.092 |  0.194 21.356  0.800 |  0.071
## JM     |    11.881 |  0.259  8.366  0.526 |  0.243 55.115  0.465 | -0.034
## SE     |    38.314 | -0.381 51.201  0.999 |  0.011  0.300  0.001 | -0.005
## JE     |    26.269 |  0.233 33.097  0.942 | -0.058 15.177  0.058 |  0.003
## SC     |     6.053 | -0.201  7.006  0.865 | -0.079  8.052  0.133 | -0.008
##           ctr   cos2  
## SM     69.433  0.107 |
## JM     25.619  0.009 |
## SE      1.698  0.000 |
## JE      1.205  0.000 |
## SC      2.045  0.001 |
## 
## Columns
##          Iner*1000    Dim.1    ctr   cos2    Dim.2    ctr   cos2    Dim.3
## none   |    49.186 | -0.393 65.400  0.994 |  0.030  2.934  0.006 | -0.001
## light  |     7.059 |  0.099  3.085  0.327 | -0.141 46.317  0.657 |  0.022
## medium |    12.610 |  0.196 16.562  0.982 | -0.007  0.174  0.001 | -0.026
## heavy  |    16.335 |  0.294 14.954  0.684 |  0.198 50.575  0.310 |  0.026
##           ctr   cos2  
## none    0.061  0.000 |
## light  27.282  0.016 |
## medium 51.140  0.017 |
## heavy  21.517  0.005 |
# CÁLCULO INERCIA Y AUTOVALORES

# Tabla de autovalores e inercia

tabla_inercia <- data.frame(
  Dimension = paste0("Dim ", 1:nrow(ca_smoke$eig)),
  Autovalor = round(ca_smoke$eig[, 1], 4),
  Porcentaje_inercia = round(ca_smoke$eig[, 2], 2),
  Porcentaje_acumulado = round(ca_smoke$eig[, 3], 2)
)

kable(
  tabla_inercia,
  caption = "Autovalores e inercia explicada por dimensión",
  align = "c",
  row.names = FALSE
) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#1F4E79"
  )
Autovalores e inercia explicada por dimensión
Dimension Autovalor Porcentaje_inercia Porcentaje_acumulado
Dim 1 0.0748 87.76 87.76
Dim 2 0.0100 11.76 99.51
Dim 3 0.0004 0.49 100.00
# Preparar coordenadas de filas y columnas

filas <- data.frame(
  Nombre = rownames(ca_smoke$row$coord),
  Dim1 = ca_smoke$row$coord[, 1],
  Dim2 = ca_smoke$row$coord[, 2],
  Tipo = "Grupo de personal"
)

columnas <- data.frame(
  Nombre = rownames(ca_smoke$col$coord),
  Dim1 = ca_smoke$col$coord[, 1],
  Dim2 = ca_smoke$col$coord[, 2],
  Tipo = "Consumo de tabaco"
)

datos_biplot <- rbind(filas, columnas)

# Porcentaje de inercia para los ejes
inercia_dim1 <- round(ca_smoke$eig[1, 2], 2)
inercia_dim2 <- round(ca_smoke$eig[2, 2], 2)

# BIPLOT SIMÉTRICO DE LAS DOS PRIMERAS DIMENSIONES

ggplot(
  datos_biplot,
  aes(
    x = Dim1,
    y = Dim2,
    color = Tipo,
    shape = Tipo,
    label = Nombre
  )
) +
  geom_hline(
    yintercept = 0,
    linetype = "dashed",
    color = "gray70"
  ) +
  geom_vline(
    xintercept = 0,
    linetype = "dashed",
    color = "gray70"
  ) +
  geom_point(
    size = 3,
    alpha = 0.85
  ) +
  geom_text_repel(
    size = 4,
    max.overlaps = 20,
    segment.size = 0.25
  ) +
  labs(
    title = "Análisis de Correspondencias Simples",
    subtitle = "Biplot simétrico: grupos de personal y consumo de tabaco",
    x = paste0("Dimensión 1 (", inercia_dim1, "% de inercia)"),
    y = paste0("Dimensión 2 (", inercia_dim2, "% de inercia)"),
    color = "",
    shape = ""
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold", size = 15),
    plot.subtitle = element_text(size = 11, color = "gray30"),
    legend.position = "bottom",
    panel.grid.minor = element_blank()
  )

El Análisis de Correspondencias Simples muestra que las dos primeras dimensiones explican el 99.51 % de la inercia total, por lo que el plano bidimensional resume casi completamente la estructura de desviaciones observada en la tabla de contingencia. Sin embargo, el contraste Chi-cuadrado de independencia no resulta significativo al 5 % ( \(\chi^2 = 16.44; p = 0.1718\)), por lo que no puede rechazarse estadísticamente la hipótesis de independencia entre grupo de personal y categoría de consumo de tabaco. Además, la existencia de algunas frecuencias esperadas reducidas aconseja interpretar los resultados con prudencia. Por ello, el mapa factorial debe entenderse como una representación exploratoria de perfiles relativos, útil para describir tendencias de asociación, pero no como evidencia concluyente de una relación estadísticamente significativa.

A continuación, con el fin de valorar la fiabilidad de la conclusión de no significación del contraste \(\chi^2\), se revisan las frecuencias esperadas de las celdas de la tabla de contingencia. Esta comprobación es relevante porque la aproximación asintótica del contraste puede verse afectada cuando existen frecuencias esperadas reducidas. Asimismo, se calcula un p-valor mediante simulación de Monte Carlo, como comprobación adicional de robustez.

# Comprobar las frecuencias esperadas:

test_chi <- chisq.test(smoke_tab)
## Warning in chisq.test(smoke_tab): Chi-squared approximation may be incorrect
test_chi$expected
##         none     light    medium     heavy
## SM  3.476684  2.564767  3.533679  1.424870
## JM  5.689119  4.196891  5.782383  2.331606
## SE 16.119171 11.891192 16.383420  6.606218
## JE 27.813472 20.518135 28.269430 11.398964
## SC  7.901554  5.829016  8.031088  3.238342
sum(test_chi$expected < 5)
## [1] 7
# Contrastar si el p-valor cambia mucho usando simulación de Monte Carlo

chisq.test(smoke_tab, simulate.p.value = TRUE, B = 10000)
## 
##  Pearson's Chi-squared test with simulated p-value (based on 10000
##  replicates)
## 
## data:  smoke_tab
## X-squared = 16.442, df = NA, p-value = 0.1698

Como puede comprobarse, la prueba de frecuencias esperadas genera una advertencia, ya que 7 celdas presentan frecuencias esperadas inferiores a 5, lo que puede afectar a la validez de la aproximación asintótica del \(\chi^2\).

Para comprobar la robustez del resultado, se calculó también el p-valor mediante simulación de Monte Carlo, obteniéndose un valor muy similar (\(p = 0.1766\)). Esto refuerza la conclusión de que no existe evidencia estadística suficiente de asociación significativa entre ambas variables. En consecuencia, esto viene a reforzar que el Análisis de Correspondencias Simples debe interpretarse, en este caso, principalmente como una herramienta descriptiva y exploratoria, útil para representar perfiles relativos de consumo, pero no como una prueba concluyente de dependencia estadística.

Las posiciones de grupos pequeños como SM y JM, así como de la categoría heavy, deben interpretarse con especial cautela, porque están asociadas a frecuencias observadas y esperadas reducidas. Por tanto, aunque el mapa sugiere proximidades relativas —por ejemplo, SE connone, JE con medium y JM con heavy, estas asociaciones deben entenderse como patrones descriptivos, no como evidencia inferencial fuerte.

Cálculo e interpretación de la inercia total y el porcentaje de variabilidad explicado por las dos primeras dimensiones.

La inercia total del análisis es aproximadamente 0.0852, obtenida como suma de los autovalores de las tres dimensiones (0.0748 + 0.0100 + 0.0004). La primera dimensión explica el 87.76 % de la inercia total, mientras que la segunda dimensión explica el 11.76 %. En conjunto, las dos primeras dimensiones acumulan el 99.51 % de la variabilidad asociada a la tabla de contingencia, por lo que el plano bidimensional ofrece una representación prácticamente completa de la estructura de asociación observada.

La primera dimensión es claramente la más relevante y separa, sobre todo, la categoría none (ningún consumo), situada en el lado negativo del eje, de las categorías de medium (consumo medio) y heavy (consumo elevado), situadas en el lado positivo. En cuanto a la segunda dimensión, ayuda a matizar la diferencia entre light (consumo bajo) y heavy (consumo elevado), pero tiene mucho menos peso que la primera.

Identificación de qué grupos de personal aparecen mas asociados a la ausencia de consumo y cuáles se sitúan mas proximos a los consumos medios o altos.

Desde el punto de vista de los grupos de personal, el grupo SE aparece muy próximo a none (ningún consumo), por lo que puede interpretarse como el grupo más asociado a la ausencia de consumo. El grupo SC también se sitúa en el lado negativo de la primera dimensión, aunque de forma menos marcada.

En el lado positivo de la primera dimensión se sitúan los grupos JE y JM, más próximos a categorías de consumo más altas. El grupo JE aparece especialmente cercano a medium (consumo medio), lo que sugiere una asociación relativa con consumos moderados. Mientras, el grupo JM, por su posición en el cuadrante superior derecho, aparece más próximo a heavy (consumo elevado), por lo que puede vincularse relativamente con los consumos más altos. La categoría light (consumo bajo) se sitúa en una posición intermedia, aunque más vinculada a la segunda dimensión.

Se indica qué filas y columnas contribuyen más a la construccion de las dos primeras dimensiones y se resume la interpretacion del mapa.

En cuanto a las contribuciones, la primera dimensión está construida principalmente por la oposición entre SE y JE en las filas, y entre none, medium y heavy en las columnas. En concreto, SE contribuye de forma muy importante a la Dimensión 1, seguido de JE; en las columnas destaca especialmente none, que es la categoría que más contribuye a este eje.

La dimensión 2 está explicada sobre todo por JM y, en menor medida, por SM y JE en las filas, mientras que en las columnas, las mayores contribuciones corresponden a heavy y light.

Por tanto, el mapa resume una oposición principal entre ausencia de consumo y consumo medio/alto, y una segunda diferenciación más débil entre consumo ligero y consumo alto.