1 INDICE COMPUESTO

La construcción de un indice compuesto ayuda a explicar las dinamicas territoriales, integrando distintas variables en una suma ponderada. Los pasos metodologicos implican la seleccion de indicadores, un análisis factorial para la determinacion de pesos y la integracion de los mismos en la sumatoria final. Los resultados se presentarán en un mapa interactivo para mejor visualización.

1.1 SELECCION DE VARIABLES

Se trabaja con datos continuos para volumen poblacional, Concentración de servicios, Acceso a servicios y Ubicación estratégica.

df_yungay <- read_excel("/Users/dalineportocarrero/Documents/Daline/4.PROYECTOS/PCM_PNUD/R_CUF/YUNGAY/BD_Jerarquia_310326ev.xlsx")

# Nuevo dataframe

df_red <- df_yungay[, c("COD","POB", "PIB_gs", "Acc_capital1", "A_IE", "A_IS")]

glimpse(df_red)
## Rows: 95
## Columns: 6
## $ COD          <chr> "NP - 1", "NP - 2", "NP - 3", "NP - 5", "NP - 8", "NP - 9…
## $ POB          <dbl> 11932, 2898, 2461, 1388, 941, 899, 619, 676, 1390, 871, 8…
## $ PIB_gs       <dbl> 79273.901, 82647.749, 30588.009, 26379.958, 46779.670, 97…
## $ Acc_capital1 <dbl> 1.937721, 5.203335, 23.180526, 11.881624, 22.076591, 1.05…
## $ A_IE         <dbl> 1.4559873, 4.7662559, 1.1158021, 7.1962497, 22.8238485, 0…
## $ A_IS         <dbl> 2.731443, 15.195762, 36.931645, 26.852967, 63.642009, 183…
summary(df_red)
##      COD                 POB              PIB_gs         Acc_capital1   
##  Length:95          Min.   :   51.0   Min.   :  211.1   Min.   :  0.00  
##  Class :character   1st Qu.:  131.0   1st Qu.:  644.5   1st Qu.: 24.30  
##  Mode  :character   Median :  193.0   Median : 1516.2   Median : 44.76  
##                     Mean   :  463.2   Mean   : 6210.1   Mean   : 98.62  
##                     3rd Qu.:  364.5   3rd Qu.: 4566.5   3rd Qu.:138.51  
##                     Max.   :11932.0   Max.   :82647.7   Max.   :631.30  
##       A_IE              A_IS       
##  Min.   :  0.000   Min.   :  1.23  
##  1st Qu.:  1.844   1st Qu.: 36.86  
##  Median : 10.451   Median : 92.16  
##  Mean   : 15.267   Mean   :130.15  
##  3rd Qu.: 21.680   3rd Qu.:186.36  
##  Max.   :107.482   Max.   :630.79

1.1.1 Normalización de indicadores

Para aplicar el analisis factorial es necesario que todos los indicadores esten en una misma escala. Se aplica una normalizacion bajo metodo min max de 0 a 1. Asimismo, se han invertido los valores finales de los indicadores que guardan una relación contraría con la jerarquía, en este caso los indicadores de tiempos de desplazamiento, debido a que a menor tiempo de dezplazamiento, presentan mayor oportunidad y por ende mayor jerarquía.

# Normalizacion de variables seleccionadas
vars_modelo <- c("POB", "PIB_gs", "Acc_capital1", "A_IE", "A_IS")

# Función Min-Max → [0, 1]
minmax <- function(x) (x - min(x, na.rm = TRUE)) / (max(x, na.rm = TRUE) - min(x, na.rm = TRUE))

# Aplicar y guardar en nuevas columnas
df_red <- df_red |>
  mutate(across(all_of(vars_modelo), minmax, .names = "{.col}_mm"))
df_red <- df_red |>
  mutate(across(ends_with("_mm"), ~ round(., 3)))

#invertir los indicadores de tiempo
vars_tiempo <- c("Acc_capital1_mm", "A_IE_mm", "A_IS_mm")

df_red <- df_red |>
  mutate(
    across(
      all_of(vars_tiempo),
      ~ 1 - .,
      .names = "{.col}_inv"
    )
  )

1.2 ANALISIS FACTORIAL

Se emplea el Análisis factorial (AF) con el objetivo de identificar estructuras latentes entre los indicadores y derivar ponderaciones que reflejen su contribución relativa para la construcción de un indice compuesto.

1.2.1 Matriz de correlaciones

Teniendo un conjunto de indicadores X= (x1,X2….Xn) se construye una matriz de correlaciones.

1.2.2 Modelo de análisis factorial

El modelo factorial se expresa como

X = ΛF + ε donde:

Λ es la matriz de cargas factoriales (p x m) F es el vector de factores comunes (m x l) ε es el vector de errores específicos.

Asimismo, la matriz de correlaciones puede descomponerse como:

R= ΛΛ’ + Ψ

donde Ψ es la matriz diagonal de varianzas específicas.

# Matriz de datos estandarizados
vars_fa <- c("POB_mm", "PIB_gs_mm", "Acc_capital1_mm_inv", "A_IE_mm_inv", "A_IS_mm_inv")

mat_af <- df_red |> 
  select(all_of(vars_fa)) |> 
  as.matrix()

mat_cor <- df_red |>
  select(all_of(vars_fa)) |>
  cor(use = "complete.obs")

round(mat_cor, 2)
##                     POB_mm PIB_gs_mm Acc_capital1_mm_inv A_IE_mm_inv
## POB_mm                1.00      0.75                0.14        0.15
## PIB_gs_mm             0.75      1.00                0.17        0.16
## Acc_capital1_mm_inv   0.14      0.17                1.00        0.21
## A_IE_mm_inv           0.15      0.16                0.21        1.00
## A_IS_mm_inv           0.16      0.19                0.87        0.11
##                     A_IS_mm_inv
## POB_mm                     0.16
## PIB_gs_mm                  0.19
## Acc_capital1_mm_inv        0.87
## A_IE_mm_inv                0.11
## A_IS_mm_inv                1.00
library(corrplot)
## corrplot 0.95 loaded
corrplot(mat_cor, 
         method = "color",
         type = "upper",
         tl.col = "black",
         tl.srt = 45)

# Validación
KMO(mat_af)
## Kaiser-Meyer-Olkin factor adequacy
## Call: KMO(r = mat_af)
## Overall MSA =  0.52
## MSA for each item = 
##              POB_mm           PIB_gs_mm Acc_capital1_mm_inv         A_IE_mm_inv 
##                0.53                0.54                0.51                0.53 
##         A_IS_mm_inv 
##                0.51
cortest.bartlett(mat_af)
## R was not square, finding R from data
## $chisq
## [1] 220.9732
## 
## $p.value
## [1] 6.686125e-42
## 
## $df
## [1] 10

1.2.3 Determinación del número de factores

Para el AF se necesita determinar el número de factores “m”, esto puede realizarse mediante: - Análisis paralelo - Valores propios mayores a 1 - Porcentaje acumulado de varianza explicada >= 60%

Se observa que los factores M1 y M2 presentan cargas mayores a 1 (1.814 y 1.524 respectivamente) y cuentan con una varianza acumulada de 67%

1.2.4 Rotación factorial

Una vez estimados los factores, se aplica una rotación ortogonal tipo varimax, con el objetivo de obtener una estructura simple que facilite la interpretación, maximizando las cargas altas y minimizando las bajas dentro de cada factor.

1.2.5 Varianza explicada por los factores

Para cada factor j, se obtiene su contribución a la varianza total:

donde

λij es la carga de la variable i en el factor j vj representa la proporción de varianza explicada por el factor j

1.2.6 Cálculo de pesos de los indicadores

El peso de cada indicador se construye en dos etapas:

  • Contribución de la variable dentro de cada factor

  • Ponderación por la importancia del factor

Finalmente los pesos se reescalan para asegurar que sumen la unidad.

# Determinar número de factores
fa.parallel(mat_af, fm = "ml", fa = "fa")

## Parallel analysis suggests that the number of factors =  2  and the number of components =  NA
# Modelo con n factores 
fa_rot <- fa(
  r        = cor(mat_af),
  nfactors = 2,
  rotate   = "varimax",
  fm       = "ml"
)

# Cargas factoriales rotadas, comunalidades y varianza por factor
print(fa_rot$loadings, cutoff = 0.3)
## 
## Loadings:
##                     ML1   ML2  
## POB_mm                    0.874
## PIB_gs_mm                 0.851
## Acc_capital1_mm_inv 0.996      
## A_IE_mm_inv                    
## A_IS_mm_inv         0.872      
## 
##                  ML1   ML2
## SS loadings    1.814 1.524
## Proportion Var 0.363 0.305
## Cumulative Var 0.363 0.668
comunalidades <- fa_rot$communality          # h² por variable
var_por_factor <- fa_rot$Vaccounted          # matriz: SS loadings, prop var, etc.
prop_var       <- var_por_factor["Proportion Var", ]   # proporción de varianza explicada


# Cargas factoriales rotadas (variables × factores)
cargas <- fa_rot$loadings[]  # matriz completa sin truncar


# Peso de cada variable = suma ponderada de cargas² × proporción de varianza del factor
pesos_fa <- rowSums((cargas^2) * prop_var)
pesos_fa  <- pesos_fa / sum(pesos_fa)          # normalizar a suma = 1

print(round(pesos_fa, 5))
##              POB_mm           PIB_gs_mm Acc_capital1_mm_inv         A_IE_mm_inv 
##             0.20257             0.22966             0.31025             0.01778 
##         A_IS_mm_inv 
##             0.23974
# Revisión total del modelo factorial
#print(fa_rot)

1.3 INDICE DE JERARQUIA

Con los pesos obtenidos se procede a construir el indice compuesto de jerarquia.

# Combinación lineal simple con los pesos derivados
indice_fa <- as.vector(mat_af %*% pesos_fa)

# Añadir al dataframe
df_red <- df_red |>
  mutate(
    indice_fa = round (indice_fa, 5)
  )

1.3.1 Cluster exploratorio

El resultado se puede visualizar con un cluster exploratorio bajo el algoritmo K-means

#K-means

data_k <- df_red |> select("indice_fa")

# Elbow method
fviz_nbclust(data_k, FUNcluster = kmeans, method = "wss", k.max = 10) +
  labs(title = "Elbow method — k-means")

# Silhouette
fviz_nbclust(data_k, FUNcluster = kmeans, method = "silhouette", k.max = 10) +
  labs(title = "Silhouette — validación k óptimo")

# K-means (ajusta k según los gráficos anteriores)
set.seed(123)
km_yungay <- kmeans(data_k, centers = 6, nstart = 25)

# Asignar cluster al dataframe ← nombre corregido
df_red$cluster_km <- as.factor(km_yungay$cluster)

df_red |>
  group_by(cluster_km) |>
  summarise(
    n      = n(),
    pob_max = max(POB),
    pob_med = median(POB),
    pob_min = min(POB)
  ) |>
  arrange(desc(pob_max))
## # A tibble: 6 × 5
##   cluster_km     n pob_max pob_med pob_min
##   <fct>      <int>   <dbl>   <dbl>   <dbl>
## 1 2              2   11932   7415     2898
## 2 4              5    2461    941      220
## 3 1             36    1390    181       51
## 4 3             26    1029    180       63
## 5 6             20     491    202       53
## 6 5              6     314    180.      72
df_red$cluster_km <- as.factor(km_yungay$cluster)

1.3.2 Visualizacion

# Cargar y reproyectar a WGS84 geográfico
cp_puntos <- st_read("/Users/dalineportocarrero/Documents/Daline/4.PROYECTOS/PCM_PNUD/R_CUF/YUNGAY/BD_CCFF/BD_CCFF.shp") |> st_transform(4326)
## Reading layer `BD_CCFF' from data source 
##   `/Users/dalineportocarrero/Documents/Daline/4.PROYECTOS/PCM_PNUD/R_CUF/YUNGAY/BD_CCFF/BD_CCFF.shp' 
##   using driver `ESRI Shapefile'
## Simple feature collection with 95 features and 44 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: 163835.1 ymin: 8957433 xmax: 239084.5 ymax: 9004316
## Projected CRS: WGS 84 / UTM zone 18S
# Verificar columnas del shp
names(cp_puntos)
##  [1] "ID_CP"      "COD"        "UBIGEO"     "CODCP"      "PROVINCIA" 
##  [6] "DISTRITO"   "NOMBRE"     "Y"          "X"          "CAPITAL"   
## [11] "RANGO_POB"  "T_AP"       "ID_NP"      "NOM_AP"     "POB_CP"    
## [16] "T_CP"       "T_IE"       "T_IS"       "POB"        "NIV_IE"    
## [21] "NIV_IS"     "PIB_gs"     "PIB_cub"    "PIB_cubn"   "A_IE"      
## [26] "A_IS"       "UE"         "P_POB"      "P_IE"       "P_IS"      
## [31] "P_SERV_gs"  "P_SERV_cub" "P_SERV_cn"  "P_AIE"      "P_AIS"     
## [36] "P_UE"       "SUMsinPIB"  "SUM_J_gs"   "SUM_J_cub"  "SUM_J_n"   
## [41] "nombdist"   "TIPOLOGIA"  "POBMIN"     "VER_CF"     "geometry"
# Ver si los códigos coinciden
head(cp_puntos$COD)
## [1] "CP - 1" "CP - 2" "CP - 3" "CP - 4" "CP - 5" "CP - 6"
head(df_red$COD)
## [1] "NP - 1" "NP - 2" "NP - 3" "NP - 5" "NP - 8" "NP - 9"
# Unir clusters al shapefile de puntos
cp_mapa <- cp_puntos |>
  left_join(df_red |> 
              select(COD, cluster_km),
            by = "COD")

# Paleta por cluster
pal <- colorFactor(palette = brewer.pal(5, "Set2"),
                   domain = cp_mapa$indice_fa)

# Mapa
leaflet() |>
  addProviderTiles("CartoDB.Positron") |>
  
  # Puntos centros poblados
   addCircleMarkers(data = cp_mapa,
                   radius = ~case_when(
                    cluster_km == 2 ~ 16,
                    cluster_km == 4 ~ 10,
                    cluster_km == 1  ~ 6,
                    cluster_km == 3 ~ 4,
                    cluster_km == 6 ~ 3,
                    cluster_km == 5 ~ 2,
                  ),
                   fillColor = "#039ccf",
                   fillOpacity = 0.5,
                   color = "white",
                   weight = 0.2,
                   label = ~paste0("Cluster: ", cluster_km,
                                   " | Pob: ", round(POB, 0)),
                   popup = ~paste0("<b>COD: ", COD, "</b><br>",
                                   "Cluster: <b>", cluster_km, "</b><br>",
                                   "Población: ", round(POB, 0))) |>
  
  addLegend(
    position = "bottomright",
    title = "Jerarquía Kmeans",
    colors = rep("#039ccf", 6),
    labels = c("2 - Mayor", "4", "1","3","6", "5 - Menor"),
    opacity = 0.35
  )

```