En 2015, los países de las Naciones Unidas se comprometieron a que, para 2030, todas las personas tengan acceso a energía asequible, segura, sostenible y moderna lo cual hace parte del Objetivo de Desarrollo Sostenible 7, parece una sola meta, pero son tres: que todos tengan electricidad y puedan cocinar sin humo (meta 7.1), que las energías renovables ganen peso (meta 7.2) y que la energía se use con más eficiencia (meta 7.3). A esto se suma el compromiso del Acuerdo de París de reducir las emisiones. El Consejo Mundial de la Energía (World Energy Council, 2021) lo resume como un trilema y es que cada país tiene que equilibrar la equidad, es decir, que la energía llegue a todos, la sostenibilidad ambiental que contamine poco y el desarrollo económico que impulse la producción.
El problema es que estas metas no siempre avanzan juntas. Nuestros propios datos lo muestran:
La lección es que mirar un solo indicador engaña. Un ranking de energía renovable pondría a países sin electricidad por delante de Alemania y uno de ingreso pondría a los grandes exportadores de petróleo como modelo de desarrollo. Parte de la explicación es la escalera energética (Hosier & Dowd, 1987; Leach, 1992) que nos dice que los hogares pobres cocinan con leña y carbón vegetal que cuentan como energía renovable y al subir el ingreso los cambian por electricidad y combustibles fósiles, y solo más adelante, y no en todos los países, llegan las renovables modernas.
Por eso en este trabajo miramos varios indicadores a la vez: 15 del Banco Mundial (World Development Indicators) para 164 países en 2021. Cubren el acceso, las renovables y la eficiencia, las consecuencias ambientales (emisiones, aire y bosques) y el contexto económico y demográfico. Usamos aprendizaje no supervisado, es decir, no predecimos nada dejamos que los datos muestren qué países se parecen. Primero resumimos los 15 indicadores en unas pocas dimensiones (Análisis de Componentes Principales, ACP) y luego agrupamos los países parecidos (método de Ward).
Pregunta de investigación ¿Qué grupos de países se pueden identificar a partir de sus indicadores energéticos, ambientales, económicos y demográficos en 2021, y cuáles son las principales diferencias entre ellos en relación con la transición energética?
El objetivo es identificar y describir perfiles de países según su situación energética, ambiental y socioeconómica en 2021, así mismo, buscamos describir cómo se comportan los 15 indicadores y cómo se relacionan con el ingreso y la región, resumir esos indicadores en pocas dimensiones e interpretar qué representa cada una, agrupar los países con esas dimensiones y caracterizar cada grupo y proponer implicaciones de política pública diferenciadas para cada grupo.
Alcance del estudio. El análisis es transversal (un solo año) y descriptivo. Permite identificar asociaciones entre indicadores y perfiles de países en 2021, pero no explicar qué factores causan la transición energética ni reconstruir la evolución de cada país en el tiempo. Los grupos obtenidos son una segmentación exploratoria, relativa a los 15 indicadores seleccionados, y no una clasificación universal o permanente de los países.
La investigación adopta un enfoque cuantitativo, descriptivo y transversal, orientado a caracterizar las diferencias entre países en materia de transición energética y sostenibilidad ambiental. La unidad de análisis es cada país y el período de referencia es el año 2021. Al tratarse de aprendizaje no supervisado, no se define una variable dependiente ni se busca predecir. El propósito es explorar la estructura de los datos, identificar las principales dimensiones de variabilidad y reconocer grupos de países con características similares.
La muestra incluye 164 países de todos los continentes. El mapa permite ver la cobertura geográfica y cambiar entre cinco indicadores clave con el control de la esquina superior derecha. Los 20 estados insulares o de territorio muy pequeño que no se distinguen a esta escala se representan con círculos.
capas_mapa <- c(
"Países incluidos" = "incluido",
"Energía renovable (%)" = "Energia_renovable",
"Acceso a electricidad (%)" = "Acceso_electricidad",
"CO2 per cápita (t)" = "Emisiones_CO2",
"PM2.5 (µg/m³)" = "Contaminacion_PM25"
)
paletas <- c(Energia_renovable = "YlGn", Acceso_electricidad = "Greens",
Emisiones_CO2 = "YlOrBr", Contaminacion_PM25 = "OrRd")
mundo$incluido <- ifelse(is.na(mundo$country), NA, 1)
pequenos$incluido <- 1
# Mapa base sin teselas externas (no dependen de claves de API): el oceano lo da el fondo del contenedor
mapa <- leaflet(mundo, options = leafletOptions(minZoom = 1, maxZoom = 6, zoomSnap = .25,
attributionControl = FALSE)) %>%
setView(lng = 12, lat = 20, zoom = 1.6)
for (nombre in names(capas_mapa)) {
v <- capas_mapa[[nombre]]
valores <- mundo[[v]]
todos <- c(valores, pequenos[[v]])
if (v == "incluido") {
fpal <- colorNumeric(pal[["natural"]], c(1, 1), na.color = "#E6E6E6")
} else if (v == "Emisiones_CO2") {
fpal <- colorQuantile(paletas[[v]], todos, n = 5, na.color = "#E6E6E6")
} else {
fpal <- colorNumeric(paletas[[v]], todos, na.color = "#E6E6E6")
}
clase_leyenda <- paste0("info legend leyenda-capa capa-", match(nombre, names(capas_mapa)) - 1)
etiqueta_valor <- function(datos) {
if (v == "incluido") return(ifelse(is.na(datos$country), "No incluido (información incompleta)", paste0(datos$pais, " · incluido")))
ifelse(is.na(datos[[v]]), "Sin datos", paste0(datos$pais, ": ", num(datos[[v]], 1)))
}
mapa <- mapa %>%
addPolygons(
fillColor = fpal(valores), fillOpacity = .85, weight = .6, color = "#FFFFFF",
group = nombre, label = etiqueta_valor(mundo),
highlightOptions = highlightOptions(weight = 2, color = pal[["profundo"]], bringToFront = TRUE)
) %>%
addCircleMarkers(
data = pequenos, radius = 4, stroke = TRUE, color = "#FFFFFF", weight = 1,
fillColor = fpal(pequenos[[v]]), fillOpacity = .95,
group = nombre, label = etiqueta_valor(pequenos)
)
if (v == "incluido") {
mapa <- mapa %>% addLegend("bottomleft", colors = c(pal[["natural"]], "#E6E6E6"),
labels = c("Incluido", "Sin información completa"),
title = "Muestra", opacity = .9, className = clase_leyenda)
} else if (v == "Emisiones_CO2") {
mapa <- mapa %>% addLegend("bottomleft", pal = fpal, values = todos[!is.na(todos)],
title = "CO2 per cápita<br>(quintiles)", opacity = .9, className = clase_leyenda,
labFormat = function(type, cuts, p) paste0(num(head(cuts, -1), 1), " – ", num(cuts[-1], 1)))
} else {
mapa <- mapa %>% addLegend("bottomleft", pal = fpal, values = todos[!is.na(todos)],
title = nombre, opacity = .9, className = clase_leyenda)
}
}
# Solo se muestra la leyenda de la capa activa
mapa %>%
addLayersControl(baseGroups = names(capas_mapa), position = "topright",
options = layersControlOptions(collapsed = FALSE)) %>%
htmlwidgets::onRender(sprintf("
function(el, x) {
var capas = %s;
var map = this;
function mostrar(nombre) {
var i = capas.indexOf(nombre);
el.querySelectorAll('.leyenda-capa').forEach(function(l) {
l.style.display = l.classList.contains('capa-' + i) ? '' : 'none';
});
}
map.on('baselayerchange', function(e) { mostrar(e.name); });
mostrar(capas[0]);
}", jsonlite::toJSON(names(capas_mapa))))Fuente: elaboración propia con datos WDI, Banco Mundial (2021). Los países en gris no tienen información completa para los 15 indicadores.
La muestra cubre casi toda América, Europa y Asia, y buena parte de África y Oceanía. Las principales ausencias son países con estadísticas incompletas para 2021, entre ellos algunos con más de 20 millones de habitantes, como Etiopía, Venezuela, Angola, Yemen, Nepal, Burkina Faso y Corea del Norte. Esto debe tenerse en cuenta al interpretar los resultados, en particular para África oriental y para países afectados por conflictos. Las capas temáticas anticipan el patrón central del estudio: el mapa de energía renovable es casi el negativo del mapa de acceso a electricidad.
Para caracterizar a los países se seleccionaron 15 variables cuantitativas de la base World Development Indicators del Banco Mundial, distribuidas en cuatro dimensiones: energía, medio ambiente, economía y demografía. La selección aborda la transición energética desde diferentes perspectivas, incorporando tanto indicadores directamente relacionados con el uso y el acceso a la energía como variables que contextualizan las condiciones ambientales, productivas y poblacionales de cada país. La tabla relaciona cada indicador con la meta del ODS 7 o con el aspecto del trilema energético que representa.
# TABLA DESCRIPCION DE LAS VARIABLES
tabla_variables <- data.frame(
Dimension = c(
rep("Energia", 7),
rep("Medio ambiente", 4),
rep("Economia", 2),
rep("Demografia", 2)
),
Variable = c(
"Consumo de energia renovable",
"Electricidad generada de fuentes renovables",
"Intensidad energetica",
"Acceso a electricidad",
"Acceso rural a electricidad",
"Acceso a tecnologias limpias para cocinar",
"Acceso rural a tecnologias limpias para cocinar",
"Emisiones de CO2 per capita",
"Emisiones totales de GEI per capita",
"Contaminacion por PM2.5",
"Superficie forestal",
"PIB per capita PPA",
"Valor agregado industrial",
"Poblacion urbana",
"Crecimiento poblacional"
),
Codigo = c(
"EG.FEC.RNEW.ZS",
"EG.ELC.RNEW.ZS",
"EG.EGY.PRIM.PP.KD",
"EG.ELC.ACCS.ZS",
"EG.ELC.ACCS.RU.ZS",
"EG.CFT.ACCS.ZS",
"EG.CFT.ACCS.RU.ZS",
"EN.GHG.CO2.PC.CE.AR5",
"EN.GHG.ALL.PC.CE.AR5",
"EN.ATM.PM25.MC.M3",
"AG.LND.FRST.ZS",
"NY.GDP.PCAP.PP.KD",
"NV.IND.TOTL.ZS",
"SP.URB.TOTL.IN.ZS",
"SP.POP.GROW"
),
Unidad = c(
"% del consumo final",
"% de la generacion electrica",
"MJ por dolar PPA del PIB de 2021",
"% de la poblacion",
"% de la poblacion rural",
"% de la poblacion",
"% de la poblacion rural",
"t CO2e por persona",
"t CO2e por persona",
"Microgramos por metro cubico",
"% de la superficie terrestre",
"Dolares internacionales constantes de 2021",
"% del PIB",
"% de la poblacion",
"% anual"
),
Relacion = c(
"ODS 7.2 · Sostenibilidad",
"ODS 7.2 · Sostenibilidad",
"ODS 7.3 · Eficiencia",
"ODS 7.1 · Equidad",
"ODS 7.1 · Equidad",
"ODS 7.1 · Equidad",
"ODS 7.1 · Equidad",
"Acuerdo de París · Sostenibilidad",
"Acuerdo de París · Sostenibilidad",
"Salud ambiental",
"Capital natural",
"Desarrollo económico",
"Estructura productiva",
"Demanda energética",
"Presión demográfica"
)
)
reactable(
tabla_variables,
groupBy = "Dimension",
defaultExpanded = TRUE,
pagination = FALSE,
striped = TRUE,
compact = TRUE,
columns = list(
Dimension = colDef(name = "Dimensión", minWidth = 120),
Variable = colDef(minWidth = 230),
Codigo = colDef(name = "Código WDI", minWidth = 150,
style = list(fontFamily = "IBM Plex Mono, monospace", fontSize = "12px")),
Unidad = colDef(minWidth = 180),
Relacion = colDef(name = "ODS 7 / trilema", minWidth = 190,
cell = function(value) span(class = "etiqueta-ods", value))
),
theme = reactableTheme(headerStyle = list(background = pal[["bosque"]], color = "white"))
)Fuente: elaboración propia a partir de World Development Indicators (WDI), Banco Mundial. Los nombres y códigos de los países se usan solo como identificadores y no son variables activas del ACP.
Año. La elección de 2021 responde a tres criterios:
Variables. Las 15 variables responden a tres preguntas: ¿De dónde proviene la energía y qué tan eficiente es su uso? (renovables e intensidad energética), ¿Quién tiene acceso a servicios energéticos modernos? (acceso total y rural a electricidad y a cocina limpia) y ¿Qué consecuencias ambientales y qué contexto socioeconómico acompañan esas decisiones? (emisiones, PM2.5, bosques, ingreso, industria, urbanización y crecimiento poblacional). La inclusión del acceso rural permite medir la brecha territorial, y la distinción entre consumo renovable total y electricidad renovable permite separar la biomasa tradicional como la leña de las renovables modernas.
La información proviene de World Development Indicators
(WDI), una base de datos administrada por el Banco Mundial que reúne
indicadores internacionales sobre desarrollo económico, social y
ambiental. Se obtuvo con el paquete WDI de R, que permite
consultar directamente los indicadores disponibles en la plataforma
oficial.
Fuente de información: https://databank.worldbank.org/source/world-development-indicators
Primero se descargaron los indicadores y se organizó la información usando el país y el año como criterios de correspondencia. Después se excluyeron los agregados regionales y económicos, con el fin de conservar solo observaciones individuales. Este procedimiento dejó 217 registros iniciales de países y economías.
A continuación se evaluó la disponibilidad de información de cada indicador y se conservaron únicamente las observaciones con valores completos en las 15 variables. Se excluyeron 53 registros incompletos y la base quedó conformada por 164 países, aproximadamente el 75,6% de las observaciones iniciales. No se aplicaron procedimientos de imputación, interpolación ni sustitución de datos faltantes.
La información se organizó en formato tabular: cada fila es un país y
cada columna uno de los indicadores. Además se conservaron tres columnas
de identificación (nombre del país, código ISO de tres letras y año de
referencia). La base depurada se almacenó en el archivo
Banco_Mundial_Energia_2021.csv.
DT::datatable(
base_final %>% select(País = pais, ISO = iso3c, all_of(variables)),
rownames = FALSE,
filter = "top",
extensions = "Buttons",
options = list(
pageLength = 8,
lengthMenu = c(8, 25, 50, 100, 164),
scrollX = TRUE,
dom = "Bfltip",
buttons = c("copy", "csv", "excel"),
language = list(
search = "Buscar:",
lengthMenu = "Mostrar _MENU_ registros",
info = "Mostrando _START_ a _END_ de _TOTAL_ países",
paginate = list(previous = "Anterior", "next" = "Siguiente")
)
)
) %>%
formatRound(columns = variables, digits = 2, mark = ".", dec.mark = ",")El análisis se desarrolla en cinco etapas encadenadas. Cada una usa como insumo el resultado de la anterior:
El análisis descriptivo se organiza en torno a cuatro preguntas que se desprenden del trilema energético. Primero se presenta un panorama general de los indicadores y luego cada pregunta se responde con una figura y un hallazgo principal. El objetivo no es solo describir promedios, sino identificar las relaciones que justifican el análisis multivariado posterior.
# ============================================================
# ESTADISTICAS DESCRIPTIVAS
# ============================================================
asimetria <- function(x) mean((x - mean(x))^3) / sd(x)^3
descriptivas <- map_dfr(variables, function(v) {
x <- base_final[[v]]
tibble(
Dimension = dimension_var[[v]],
Indicador = etiquetas[[v]],
Media = mean(x),
Mediana = median(x),
DE = sd(x),
CV = 100 * sd(x) / mean(x),
Asimetria = asimetria(x),
Minimo = paste0(num(min(x), 1), " · ", base_final$pais[which.min(x)]),
Maximo = paste0(num(max(x), 1), " · ", base_final$pais[which.max(x)])
)
})
cv_co2 <- descriptivas$CV[descriptivas$Indicador == etiquetas[["Emisiones_CO2"]]]
formato <- function(d = 2) function(value) num(value, d)
reactable(
descriptivas,
groupBy = "Dimension",
defaultExpanded = TRUE,
pagination = FALSE,
compact = TRUE,
columns = list(
Dimension = colDef(name = "Dimensión", minWidth = 110),
Indicador = colDef(minWidth = 220),
Media = colDef(cell = formato(), align = "right"),
Mediana = colDef(cell = formato(), align = "right"),
DE = colDef(name = "Desv. est.", cell = formato(), align = "right"),
CV = colDef(name = "CV (%)", minWidth = 130, align = "left",
cell = function(value) {
ancho <- paste0(min(100, value / 2), "%")
div(class = "barra-cv",
div(class = "barra-relleno", style = list(width = ancho)),
span(num(value, 0)))
}),
Asimetria = colDef(name = "Asimetría", align = "right",
cell = formato(),
style = function(value) if (abs(value) > 1) list(color = pal[["terracota"]], fontWeight = 700)),
Minimo = colDef(name = "Mínimo · país", minWidth = 190),
Maximo = colDef(name = "Máximo · país", minWidth = 190)
),
theme = reactableTheme(headerStyle = list(background = pal[["bosque"]], color = "white"))
)El coeficiente de variación (CV) mide la dispersión relativa. Las asimetrías mayores a 1 en valor absoluto se resaltan en color terracota.
En la tabla se muestran tres comportamientos distintos. El primer comportamiento corresponde a las brechas de acceso que se concentran en una minoría de países. Tenemos que el acceso a electricidad tiene media de 86,9% y mediana de 100%, donde presenta una asimetría negativa: la mayoría de los países ya está en el techo y la dispersión proviene de un grupo de países con coberturas muy pero muy bajas. La cocina limpia tiene una dispersión mucho mayor con una desviación de 36,1 puntos, lo que indica que es la meta del ODS 7 más rezagada.
El segundo comportamiento es el de las desigualdades extremas. Las emisiones de CO2 (CV de 162%), las de GEI y el PIB per cápita tienen la media muy por encima de lo que es la mediana ya que unos pocos países concentran valores extremos: Palaos y los Petro estados del Golfo en emisiones, y Luxemburgo, Singapur e Irlanda en ingreso. Vemos que en estas variables la media resulta engañosa. La media de CO2 es de 5,04 t per cápita, pero la mediana es de 2,70 t, y la media del PIB per cápita (US$ 25.120) supera en más de 50% a la mediana (US$ 16.061). Por eso, en el resto del análisis descriptivo se usan medianas, que no se ven arrastradas por los valores extremos que se presentan, Estas colas largas también nos ayudan a justificar estandarizar antes del ACP y tener precaución con los casos atípicos en la clusterización.
El tercero es el de una variable que esconde dos realidades: la energía renovable va de 0,0% a 96,3%, y su máximo está en la República Democrática del Congo que es uno de los países con menor acceso a electricidad del mundo. Con este contraste podemos anticipar la pregunta central del análisis descriptivo.
indicadores_escalera <- c("Energia_renovable", "Electricidad_renovable", "Cocina_limpia",
"Emisiones_CO2", "Contaminacion_PM25", "Intensidad_energetica")
escalera <- base_final %>%
select(quintil, all_of(indicadores_escalera)) %>%
pivot_longer(-quintil, names_to = "variable", values_to = "valor") %>%
group_by(variable, quintil) %>%
summarise(mediana = median(valor), q1 = quantile(valor, .25), q3 = quantile(valor, .75),
.groups = "drop") %>%
mutate(variable = factor(cortas[variable], levels = cortas[indicadores_escalera]))
g_escalera <- escalera %>%
ggplot(aes(quintil, mediana, group = variable,
text = paste0("<b>", variable, "</b><br>", gsub("\n", " ", quintil),
"<br>Mediana: ", num(mediana, 2),
"<br>Rango intercuartil: ", num(q1, 2), " – ", num(q3, 2)))) +
geom_ribbon(aes(ymin = q1, ymax = q3), fill = pal[["menta"]], alpha = .7) +
geom_line(colour = pal[["bosque"]], linewidth = 1.1) +
geom_point(colour = pal[["bosque"]], fill = pal[["dorado"]], shape = 21, size = 3, stroke = 1.1) +
facet_wrap(~variable, scales = "free_y", ncol = 3) +
labs(x = NULL, y = NULL) +
theme(panel.spacing = unit(1.4, "lines"), axis.text.x = element_text(size = 8))
# Animacion: los paises de cada quintil en el plano cocina limpia - energia renovable
animacion <- base_final %>%
mutate(q = gsub("\n", " ", quintil)) %>%
plot_ly(x = ~Cocina_limpia, y = ~Energia_renovable, frame = ~q,
type = "scatter", mode = "markers",
size = ~sqrt(Emisiones_CO2), sizes = c(10, 55),
color = ~log10(Emisiones_CO2), colors = c(pal[["menta"]], "#E0A230", pal[["terracota"]], pal[["profundo"]]),
marker = list(opacity = .85, line = list(color = "white", width = 1), sizemode = "diameter"),
text = ~paste0("<b>", pais, "</b><br>", q, "<br>Cocina limpia: ", num(Cocina_limpia, 0), "%",
"<br>Renovable: ", num(Energia_renovable), "%<br>CO2: ", num(Emisiones_CO2, 2), " t pc"),
hoverinfo = "text") %>%
colorbar(title = "CO2 pc<br>(log10 t)") %>%
layout(xaxis = list(title = "Población con cocina limpia (%)", range = c(-5, 108), zeroline = FALSE),
yaxis = list(title = "Energía renovable (% del consumo)", range = c(-5, 105), zeroline = FALSE),
annotations = list(
list(x = 4, y = 100, text = "Biomasa tradicional", showarrow = FALSE, xanchor = "left",
font = list(color = pal[["terracota"]], size = 13)),
list(x = 104, y = 4, text = "Modernos de base fósil", showarrow = FALSE, xanchor = "right",
font = list(color = pal[["profundo"]], size = 13)))) %>%
animation_opts(frame = 1500, transition = 800, easing = "cubic-in-out", redraw = FALSE) %>%
animation_slider(currentvalue = list(prefix = "Quintil de ingreso: ", font = list(color = pal[["bosque"]], size = 15))) %>%
animation_button(label = "▶ Subir la escalera", x = 0, xanchor = "left", y = -0.18,
bgcolor = pal[["dorado"]], font = list(color = pal[["profundo"]])) %>%
estilo_plotly()
selector_vistas(list(
"Medianas por quintil" = interactivo(g_escalera, tooltip = "text"),
"▶ La escalera en movimiento" = animacion
), "escalera", alto = 660)Primera vista: la línea une las medianas y la franja verde muestra el rango intercuartil de cada quintil. Segunda vista: pulse «Subir la escalera» para ver cómo se desplazan los países al pasar de un quintil de ingreso al siguiente; el tamaño y el color de cada burbuja indican sus emisiones de CO2.
Hallazgo
Los datos son coherentes con la escalera energética, pero la energía renovable sigue una curva en U. Al pasar del quintil más pobre al más rico, la mediana de cocina limpia sube de 8,0% a 100,0% y la mediana de PM2.5 baja de 32,3 a 10,4 µg/m³. Es decir, los países de mayor ingreso tienen, en mediana, más acceso a servicios energéticos modernos y un aire local menos contaminado. Pero esto no equivale a un mejor desempeño ambiental en todos los indicadores: la mediana de emisiones de CO2 se multiplica por más de 25 (de 0,3 a 8,3 t per cápita).
La energía renovable, en cambio, no aumenta con el ingreso, esta cae de 71,1% en el quintil más pobre a 9,7% en el quintil medio, vemos que cuando los países abandonan la leña y adoptan combustibles fósiles, y solo se recupera levemente en los quintiles altos (12,5%). La electricidad renovable dibuja exactamente la misma U. Con este patrón respaldaría la expectativa E1 y sugiere que la adopción de renovables modernas no acompaña automáticamente a un mayor ingreso. Tenemos que recordar que esto se trata de una comparación entre países distintos en un mismo año, no de la evolución de un país a lo largo del tiempo.
cuadrantes <- base_final %>%
mutate(cuadrante = case_when(
Energia_renovable >= 50 & Cocina_limpia < 50 ~ "Renovable tradicional",
Energia_renovable >= 50 & Cocina_limpia >= 50 ~ "Renovable moderna",
Energia_renovable < 50 & Cocina_limpia >= 50 ~ "Moderno de base fósil",
TRUE ~ "Doble rezago"
))
conteo_cuad <- count(cuadrantes, cuadrante)
n_cuad <- function(x) conteo_cuad$n[conteo_cuad$cuadrante == x]
g_cuad <- ggplot(highlight_key(cuadrantes, ~region), aes(Cocina_limpia, Energia_renovable)) +
annotate("rect", xmin = -2, xmax = 50, ymin = 50, ymax = 100, fill = "#F6E3DB", alpha = .8) +
annotate("rect", xmin = 50, xmax = 102, ymin = 50, ymax = 100, fill = "#DDF1E8", alpha = .9) +
annotate("rect", xmin = 50, xmax = 102, ymin = -2, ymax = 50, fill = "#ECEFF1", alpha = .9) +
annotate("rect", xmin = -2, xmax = 50, ymin = -2, ymax = 50, fill = "#FBF1D9", alpha = .9) +
annotate("text", x = 2, y = 98, hjust = 0, size = 3.6, colour = pal[["terracota"]],
label = paste0("Renovable tradicional (", n_cuad("Renovable tradicional"), ")")) +
annotate("text", x = 100, y = 98, hjust = 1, size = 3.6, colour = pal[["bosque"]],
label = paste0("Renovable moderna (", n_cuad("Renovable moderna"), ")")) +
annotate("text", x = 100, y = 46, hjust = 1, size = 3.6, colour = "#4D5B63",
label = paste0("Moderno de base fósil (", n_cuad("Moderno de base fósil"), ")")) +
annotate("text", x = 2, y = 46, hjust = 0, size = 3.6, colour = "#9A7316",
label = paste0("Doble rezago (", n_cuad("Doble rezago"), ")")) +
geom_point(aes(colour = region,
text = paste0("<b>", pais, "</b><br>", cuadrante,
"<br>Renovable: ", num(Energia_renovable), "%",
"<br>Cocina limpia: ", num(Cocina_limpia, 0), "%",
"<br>Electricidad renovable: ", num(Electricidad_renovable), "%")),
size = 2.6, alpha = .85) +
scale_colour_manual(values = col_region) +
coord_cartesian(xlim = c(0, 100), ylim = c(0, 100), expand = FALSE) +
labs(x = "Población con acceso a cocina limpia (%)", y = "Energía renovable (% del consumo final)", colour = NULL)
interactivo(g_cuad, tooltip = "text") %>%
layout(legend = list(orientation = "h", y = -0.22)) %>%
highlight(on = "plotly_hover", off = "plotly_doubleclick", opacityDim = .18, selected = attrs_selected(showlegend = FALSE))Pase el cursor sobre un país para resaltar a los demás países de su región; doble clic para restablecer.
Hallazgo
La mayoría de los países muy renovables lo son por pobreza energética. De los 39 países con más del 50% de energía renovable, 30 están en el cuadrante renovable tradicional, notamos que su población cocina mayoritariamente con la leña o el carbón vegetal y su renovable proviene de biomasa, una fuente asociada a deforestación y a contaminación del aire dentro de los hogares. Solo 9 países combinan alta participación renovable con acceso masivo a cocina limpia.
El cuadrante más poblado es el moderno de base fósil (109 países): ya superaron la pobreza energética, pero lo hicieron con petróleo, gas y carbón. Con un criterio algo más flexible (renovable ≥ 30% y cocina limpia ≥ 90%) aparecen 15 países los cuales combinan ambas condiciones en 2021: Gabón, Bután, Noruega, Suecia, Uruguay, Finlandia, Brasil, Letonia, Dinamarca, Estonia, Austria, Costa Rica, Croacia, Lituania, Portugal. Como podemos notar su mayoría son países europeos y latinoamericanos con abundante hidroenergía.
tipos_ingreso <- base_final %>%
mutate(tipo = case_when(
PIB_per_capita >= 30000 & Emisiones_CO2 < 5 ~ "Ingreso alto con bajas emisiones",
PIB_per_capita >= 30000 ~ "Ingreso alto con altas emisiones",
TRUE ~ "Resto de países"
))
ricos <- filter(tipos_ingreso, PIB_per_capita >= 30000)
ingreso_alto_baja_emision <- filter(tipos_ingreso, tipo == "Ingreso alto con bajas emisiones")
# Correlación descriptiva entre los logaritmos del ingreso y las emisiones
cor_log_ingreso_emisiones <- cor(
log(base_final$PIB_per_capita),
log(base_final$Emisiones_CO2)
)
# Gráfico de dispersión
g_emi <- ggplot(highlight_key(tipos_ingreso, ~tipo), aes(PIB_per_capita, Emisiones_CO2)) +
geom_point(aes(colour = tipo,
text = paste0("<b>", pais, "</b><br>PIB pc: US$ ", num(PIB_per_capita, 0),
"<br>CO2: ", num(Emisiones_CO2, 2), " t per cápita",
"<br>Electricidad renovable: ", num(Electricidad_renovable), "%")),
size = 2.7, alpha = .85) +
scale_x_log10(labels = scales::dollar) +
scale_y_log10(labels = function(x) num(x, 1)) +
scale_colour_manual(values = c("Ingreso alto con bajas emisiones" = "#E0A230",
"Ingreso alto con altas emisiones" = pal[["profundo"]],
"Resto de países" = "#9DB8AE")) +
labs(x = "PIB per cápita PPA (escala logarítmica)",
y = "CO2 per cápita (t, escala logarítmica)",
colour = NULL)
interactivo(g_emi, tooltip = "text") %>%
layout(legend = list(orientation = "h", y = -0.2)) %>%
highlight(on = "plotly_hover",
off = "plotly_doubleclick",
opacityDim = .18,
selected = attrs_selected(showlegend = FALSE))Pase el cursor sobre un país para resaltar a los demás de su mismo grupo; doble clic para restablecer.
Hallazgo
En el gráfico observamos una relación positiva entre el ingreso y las emisiones de CO₂ per cápita. La correlación entre los logaritmos de ambas variables es de 0,83, lo que indica una asociación positiva fuerte. Sin embargo, los países con mayores ingresos no presentan necesariamente los mismos niveles de emisiones.
De los 50 países con un PIB per cápita de al menos US$ 30.000, 10 registran emisiones inferiores a 5 toneladas de CO₂ por habitante. Por ejemplo, Malta (US$ 56.824; 3,1 t), Suecia (US$ 62.191; 3,9 t) y Suiza (US$ 83.525; 4,3 t) presentan ingresos elevados y emisiones relativamente bajas. En contraste, Catar registra un PIB per cápita de US$ 116.833 y emisiones de 47,3 toneladas por habitante.
Estos resultados muestran que el ingreso no es suficiente para describir las diferencias en las emisiones de CO₂ entre países. Por esta razón, es importante considerar también los demás indicadores energéticos, ambientales, económicos y demográficos para identificar patrones más completos mediante el ACP.
z <- base_final %>% mutate(across(all_of(variables), ~ as.numeric(scale(.x))))
perfil_region <- z %>%
group_by(region) %>%
summarise(across(all_of(variables), median), n = n(), .groups = "drop") %>%
pivot_longer(all_of(variables), names_to = "variable", values_to = "z") %>%
left_join(
base_final %>% group_by(region) %>%
summarise(across(all_of(variables), median), .groups = "drop") %>%
pivot_longer(-region, names_to = "variable", values_to = "real"),
by = c("region", "variable")
) %>%
mutate(variable = factor(cortas[variable], levels = rev(cortas)),
region = paste0(region, " (", n, ")"))
g_region <- perfil_region %>%
ggplot(aes(region, variable, fill = z,
text = paste0("<b>", region, "</b><br>", variable,
"<br>Mediana: ", num(real, 2), "<br>Puntaje z: ", num(z, 2)))) +
geom_tile(colour = "white", linewidth = .7) +
geom_text(aes(label = num(z, 1)), size = 3, colour = ifelse(abs(perfil_region$z) > 1, "white", pal[["profundo"]])) +
scale_fill_gradient2(low = pal[["terracota"]], mid = "#FFFFFF", high = pal[["bosque"]], midpoint = 0,
limits = c(-2.2, 2.2), oob = scales::squish, name = "z") +
scale_x_discrete(position = "top", labels = function(x) str_wrap(x, 16)) +
labs(x = NULL, y = NULL) +
theme(panel.grid = element_blank(), axis.text.x = element_text(size = 8.5))
interactivo(g_region, tooltip = "text")brecha <- base_final %>%
mutate(brecha = Acceso_electricidad - Electricidad_rural) %>%
slice_max(brecha, n = 12) %>%
mutate(pais = fct_reorder(pais, brecha))
g_brecha <- brecha %>%
ggplot(aes(y = pais)) +
geom_segment(aes(x = Electricidad_rural, xend = Acceso_electricidad, yend = pais),
colour = "#C9D8D1", linewidth = 2.4, lineend = "round") +
geom_point(aes(x = Electricidad_rural, text = paste0(pais, "<br>Rural: ", num(Electricidad_rural), "%")),
colour = pal[["terracota"]], size = 3.6) +
geom_point(aes(x = Acceso_electricidad, text = paste0(pais, "<br>Nacional: ", num(Acceso_electricidad), "%",
"<br>Brecha: ", num(brecha), " puntos")),
colour = pal[["bosque"]], size = 3.6) +
labs(x = "Población con acceso a electricidad (%) · terracota = rural, verde = nacional", y = NULL)
interactivo(g_brecha, tooltip = "text")Hallazgo
La pobreza energética es africana y rural; la contaminación del aire se concentra en Medio Oriente y Asia del Sur. Vemos que Africa Subsahariana es la región con mayor participación renovable con una mediana del 71,1% y al mismo tiempo la de menor acceso a electricidad con 53,4% y a cocina limpia del 10,0%. En el extremo opuesto, la región de Medio Oriente, Norte de África, Afganistán y Pakistán (agrupación vigente del Banco Mundial) tiene la menor participación renovable 6,1%, la peor calidad del aire (PM2.5 de 41,1 µg/m³), apenas 1,7% de cobertura forestal y emisiones de 4,1 t per cápita, por encima de la mediana mundial, en este caso, se puede evidenciar que Asia del Sur combina un acceso eléctrico casi universal con la segunda peor calidad del aire (32,5 µg/m³). Europa y América Latina tienen la misma mediana de electricidad renovable (38,2%) y acceso universal, pero Europa emite más del doble (5,3 frente a 2,3 t per cápita) y América Latina conserva más bosque.
Dentro de los países, la brecha es territorial. En Guinea Ecuatorial, Gabón y Botsuana la diferencia entre el acceso nacional y el rural supera los 45 puntos porcentuales: la electricidad llega a las ciudades, pero no al campo. En promedio, el acceso rural a cocina limpia está 8,3 puntos por debajo del nacional.
Como una breve conclusión tenemos que los cuatro hallazgos muestran que las variables están fuertemente interrelacionadas pero no se mueven en una sola dirección. Por ejemplo, el ingreso mejora el acceso y la calidad del aire pero aumenta las emisiones. La energía renovable alcanza su máximo en los países más pobres y su mínimo en los de ingreso medio y las regiones muestran combinaciones distintas de acceso, emisiones y renovables. Notamos que ningún indicador por sí solo permite clasificar a los países, lo que justifica usar el ACP para encontrar las dimensiones que resumen esta complejidad y la clusterización para identificar perfiles.
R <- cor(X)
orden <- hclust(as.dist(1 - R))$order
cor_df <- as.data.frame(as.table(R)) %>%
setNames(c("v1", "v2", "r")) %>%
mutate(v1 = factor(cortas[as.character(v1)], levels = cortas[orden]),
v2 = factor(cortas[as.character(v2)], levels = rev(cortas[orden])))
g_cor <- ggplot(cor_df, aes(v1, v2, fill = r, text = paste0(v1, " vs ", v2, "<br>r = ", num(r, 2)))) +
geom_tile(colour = "white", linewidth = .4) +
geom_text(aes(label = sub("^(-?)0", "\\1", formatC(r, format = "f", digits = 2))),
size = 2.6, colour = ifelse(abs(cor_df$r) > .6, "white", pal[["profundo"]])) +
scale_fill_gradient2(low = pal[["terracota"]], mid = "#FFFFFF", high = pal[["bosque"]], limits = c(-1, 1), name = "r") +
labs(x = NULL, y = NULL) +
theme(axis.text.x = element_text(angle = 40, hjust = 1, size = 9), axis.text.y = element_text(size = 9),
panel.grid = element_blank())
interactivo(g_cor, tooltip = "text")# Prueba de esfericidad de Bartlett
n <- nrow(X); p <- ncol(X)
chi_bartlett <- -(n - 1 - (2 * p + 5) / 6) * log(det(R))
gl_bartlett <- p * (p - 1) / 2
p_bartlett <- pchisq(chi_bartlett, gl_bartlett, lower.tail = FALSE)
# Observaciones por variable (tamano de muestra para el ACP)
razon_np <- n / pAl ordenar las variables por similitud aparecen tres bloques importantes: - Acceso y modernización: las variables electricidad y cocina limpia, total y rural aparecen con correlaciones entre 0,77 y 0,96 asociadas positivamente con el PIB per cápita y la urbanización. - Emisiones: CO2 y GEI prácticamente miden lo mismo r = 0,98. - Energía renovable en oposición: se correlaciona negativamente con el acceso (r = -0,75) y positivamente con el crecimiento poblacional r = 0,48, lo que expresa numéricamente la paradoja renovable. La superficie forestal y la contaminación por PM2.5 casi no se relacionan con el resto, lo que anticipa que formarán componentes propios.
La prueba de Bartlett rechaza con amplitud la hipótesis de que la matriz de correlaciones sea una identidad (χ² = 2.455, p < 0,001), es decir, las variables comparten suficiente información como para resumirlas en componentes. Además, la muestra tiene 10,9 países por variable, por encima de la proporción de 10 a 1 que Hair et al. (2019) consideran deseable. Por tanto, los datos son adecuados para el ACP.
# Valores propios de los 15 componentes (la matriz de correlaciones equivale a trabajar con datos estandarizados)
valores_propios <- eigen(cor(X), only.values = TRUE)$values
eig <- tibble(
componente = factor(paste0("CP", seq_along(valores_propios)), levels = paste0("CP", seq_along(valores_propios))),
valor_propio = valores_propios,
varianza = 100 * valores_propios / sum(valores_propios),
acumulada = cumsum(varianza)
)
# Criterio de Kaiser: se retienen los componentes con valor propio mayor que 1
k_cp <- sum(eig$valor_propio > 1)
acp <- PCA(X, scale.unit = TRUE, ncp = k_cp, graph = FALSE)g_scree <- eig %>%
ggplot(aes(componente)) +
geom_col(aes(y = varianza, fill = valor_propio > 1,
text = paste0(componente, "<br>Valor propio: ", num(valor_propio, 3),
"<br>Varianza: ", num(varianza, 2), "%",
"<br>Acumulada: ", num(acumulada, 2), "%")), width = .7) +
geom_line(aes(y = acumulada, group = 1), colour = "#E0A230", linewidth = 1.1) +
geom_point(aes(y = acumulada), colour = "#E0A230", size = 2.6) +
geom_hline(yintercept = 100 / length(variables), linetype = "dashed", colour = pal[["terracota"]]) +
annotate("text", x = 12.5, y = 100 / length(variables) + 3, hjust = .5, size = 3.3, colour = pal[["terracota"]],
label = "Criterio de Kaiser (valor propio = 1)") +
geom_vline(xintercept = k_cp + .5, linetype = "dotted", colour = pal[["profundo"]]) +
annotate("text", x = k_cp + 2.4, y = 96, hjust = .5, size = 3.3, colour = pal[["profundo"]],
label = paste0("Se retienen ", k_cp, " componentes (", num(eig$acumulada[k_cp]), "%)")) +
scale_fill_manual(values = c(`TRUE` = pal[["bosque"]], `FALSE` = pal[["menta"]]), guide = "none") +
scale_y_continuous(breaks = seq(0, 100, 20), limits = c(0, 102)) +
labs(x = NULL, y = "% de varianza (barras) y acumulada (línea)")
interactivo(g_scree, tooltip = "text") %>% layout(margin = list(b = 60))Los 4 primeros componentes tienen valor propio mayor que 1 como podemos observar en las barras verde oscuro y en conjunto explican el 74,7% de la varianza total. El gráfico tiene un codo pronunciado después del CP1 y otro quiebre después del CP4. A partir del CP5, cada componente aporta menos que el promedio de una variable (línea punteada en 6,67%). Por los tres criterios se retienen 4 componentes. El CP1 por sí solo explica el 42,0%, lo que indica que existe un eje dominante que ordena a los países.
Algo importante que resaltar es que el signo de cada componente es arbitrario, es decir, si se invirtiera la lectura sería la misma con los polos intercambiados. Lo que importa es qué variables cargan juntas y en direcciones opuestas y dónde se ubican los países respecto a esas variables. Por eso los nombres que siguen se basan en las variables con cargas mayores a 0,5 en valor absoluto y con contribuciones superiores al promedio (100/15 = 6,67%).
Eje dominante y sintético: no mide solo el acceso a electricidad, sino un conjunto de rasgos que aparecen juntos (acceso a electricidad y cocina limpia, ingreso, urbanización y menor crecimiento poblacional), frente a la dependencia de la biomasa tradicional.
Independiente del CP1. Agrupa tres fenómenos relacionados pero distintos: emisiones de gases de efecto invernadero (efecto climático global), intensidad energética (eficiencia) y PM2.5 (contaminación local del aire). Distingue a las economías intensivas en hidrocarburos e industria.
Eje territorial-ambiental: opone territorios boscosos a países áridos con aire más contaminado. La cobertura forestal es un indicador de capital natural, no una medida completa de sostenibilidad.
Recoge la combinación de electricidad renovable e ingreso alto. Una matriz eléctrica renovable no implica bajas emisiones en toda la economía (transporte e industria pueden seguir usando fósiles). Catar aparece en el polo positivo por su ingreso, no por su matriz.
La siguiente figura muestra la evidencia en que se basa esta interpretación. Con los botones se cambia entre las cargas factoriales y los círculos de correlaciones de tres planos factoriales.
# --- Cargas factoriales
cargas <- as.data.frame(acp$var$cor) %>%
rownames_to_column("variable") %>%
pivot_longer(-variable, names_to = "cp", values_to = "carga") %>%
mutate(cp = sub("Dim.", "CP", cp), variable = factor(cortas[variable], levels = rev(cortas)),
contrib = as.vector(t(acp$var$contrib)))
g_cargas <- ggplot(cargas, aes(cp, variable, fill = carga,
text = paste0(variable, "<br>", cp, "<br>Carga: ", num(carga, 3),
"<br>Contribución: ", num(contrib, 1), "%"))) +
geom_tile(colour = "white", linewidth = .6) +
geom_text(aes(label = num(carga, 2)), size = 3.1, colour = ifelse(abs(cargas$carga) > .5, "white", pal[["profundo"]])) +
scale_fill_gradient2(low = pal[["terracota"]], mid = "white", high = pal[["bosque"]], limits = c(-1, 1), name = "Carga") +
scale_x_discrete(position = "top") +
labs(x = NULL, y = NULL) +
theme(panel.grid = element_blank())
# --- Circulo de correlaciones: flechas coloreadas por dimension, grosor y opacidad segun la calidad (cos2)
col_dim <- c("Energía" = pal[["bosque"]], "Medio ambiente" = "#E0A230", "Economía" = pal[["terracota"]], "Demografía" = "#4F9DA6")
nombre_cp <- c("Modernización", "Emisiones y contaminación", "Bosque frente a PM2.5", "Electricidad renovable")
circulo <- function(a, b) {
v <- tibble(variable = variables, nombre = cortas[variables], dimension = dimension_var[variables],
x = acp$var$coord[, a], y = acp$var$coord[, b],
cos2 = acp$var$cos2[, a] + acp$var$cos2[, b],
contrib = (acp$var$contrib[, a] * eig$valor_propio[a] + acp$var$contrib[, b] * eig$valor_propio[b]) /
(eig$valor_propio[a] + eig$valor_propio[b]))
t <- seq(0, 2 * pi, length.out = 240)
flechas <- lapply(seq_len(nrow(v)), function(i) list(
x = v$x[i], y = v$y[i], ax = 0, ay = 0, xref = "x", yref = "y", axref = "x", ayref = "y",
text = "", showarrow = TRUE, arrowhead = 2, arrowsize = 1.1, arrowwidth = 1.2 + 2.6 * v$cos2[i],
arrowcolor = col_dim[[v$dimension[i]]], opacity = .35 + .65 * v$cos2[i]))
sentidos <- list(
list(x = 1, y = 0, xref = "paper", yref = "paper", xanchor = "right", yanchor = "bottom", showarrow = FALSE,
text = paste0("CP", a, ": ", nombre_cp[a], " →"), font = list(size = 11, color = "#5C7469")),
list(x = 0, y = 1, xref = "paper", yref = "paper", xanchor = "left", yanchor = "top", showarrow = FALSE,
text = paste0("↑ CP", b, ": ", nombre_cp[b]), font = list(size = 11, color = "#5C7469")))
# Las variables con flechas casi superpuestas comparten una sola etiqueta para que el texto no se encime
grupo_txt <- cutree(hclust(dist(cbind(v$x, v$y)), method = "single"), h = .13)
etiquetas_txt <- v %>%
mutate(g = grupo_txt) %>%
group_by(g) %>%
summarise(x = mean(x), y = mean(y), texto = paste(nombre, collapse = "<br>"),
color = if (n_distinct(dimension) == 1) col_dim[[first(dimension)]] else pal[["profundo"]],
.groups = "drop")
fig <- plot_ly() %>%
add_trace(x = cos(t), y = sin(t), type = "scatter", mode = "lines", fill = "toself",
fillcolor = "rgba(197,235,221,.18)", line = list(color = pal[["natural"]], width = 1.6),
hoverinfo = "skip", showlegend = FALSE) %>%
add_trace(x = .5 * cos(t), y = .5 * sin(t), type = "scatter", mode = "lines",
line = list(color = "#B9CEC3", width = 1, dash = "dot"), hoverinfo = "skip", showlegend = FALSE)
for (d in names(col_dim)) {
vd <- filter(v, dimension == d)
fig <- fig %>% add_trace(
x = vd$x, y = vd$y, type = "scatter", mode = "markers", name = d, cliponaxis = FALSE,
marker = list(size = 6 + 8 * vd$cos2, color = col_dim[[d]], line = list(color = "white", width = 1.5)),
hovertext = paste0("<b>", vd$nombre, "</b> · ", d,
"<br>Correlación con CP", a, ": ", num(vd$x, 2),
"<br>Correlación con CP", b, ": ", num(vd$y, 2),
"<br>Calidad de representación (cos²): ", num(vd$cos2, 2),
"<br>Contribución al plano: ", num(vd$contrib, 1), "%"),
hoverinfo = "text")
}
fig <- fig %>% add_trace(
x = etiquetas_txt$x, y = etiquetas_txt$y, type = "scatter", mode = "text", showlegend = FALSE,
cliponaxis = FALSE, hoverinfo = "skip", text = etiquetas_txt$texto,
textposition = ifelse(etiquetas_txt$x >= 0, "middle right", "middle left"),
textfont = list(size = 11.5, color = etiquetas_txt$color))
fig %>%
layout(
annotations = c(flechas, sentidos),
xaxis = list(title = paste0("CP", a, " · ", nombre_cp[a], " (", num(eig$varianza[a]), "%)"),
range = c(-1.6, 1.6), zeroline = TRUE, zerolinecolor = "#B9CEC3", scaleanchor = "y"),
yaxis = list(title = paste0("CP", b, " · ", nombre_cp[b], " (", num(eig$varianza[b]), "%)"),
range = c(-1.22, 1.22), zeroline = TRUE, zerolinecolor = "#B9CEC3"),
legend = list(orientation = "h", y = -0.14, title = list(text = "Dimensión: "))
) %>%
estilo_plotly()
}
# --- Paises en el espacio de los tres primeros componentes
# (se conservan estas definiciones porque la figura de los clusters usa grafico_3d)
coord_ind <- as.data.frame(acp$ind$coord) %>%
setNames(paste0("CP", 1:4)) %>%
mutate(pais = base_final$pais, region = base_final$region)
# Aporte de cada pais a la inercia del espacio CP1-CP3 (suma de sus contribuciones ponderadas por los valores propios)
contrib_3d <- 100 * rowSums(acp$ind$coord[, 1:3]^2) / (nrow(X) * sum(eig$valor_propio[1:3]))
destacados <- union(names(sort(contrib_3d, decreasing = TRUE))[1:10],
unlist(lapply(1:3, function(j) names(sort(acp$ind$contrib[, j], decreasing = TRUE))[1:2])))
# Giro automatico de la camara; se detiene cuando el usuario interactua con el grafico
js_giro <- "
function(el) {
var ang = -0.75, r = 1.75, z = 0.62, girando = true, ocupado = false;
el.style.position = 'relative';
var btn = document.createElement('button');
btn.type = 'button'; btn.className = 'btn-giro'; btn.textContent = '⏸ Pausar giro';
el.appendChild(btn);
function fijar(estado) { girando = estado; btn.textContent = estado ? '⏸ Pausar giro' : '⟳ Girar'; }
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { fijar(false); }
btn.addEventListener('click', function(e) { e.stopPropagation(); fijar(!girando); });
['mousedown', 'wheel', 'touchstart'].forEach(function(ev) {
el.addEventListener(ev, function(e) { if (e.target !== btn) fijar(false); }, { passive: true });
});
setInterval(function() {
if (!girando || el.offsetParent === null) return;
if (ocupado && Date.now() - ocupado < 500) return;
var caja = el.getBoundingClientRect();
if (caja.bottom < 0 || caja.top > window.innerHeight) return;
ang += 0.01; ocupado = Date.now();
Plotly.relayout(el, { 'scene.camera.eye': { x: r * Math.cos(ang), y: r * Math.sin(ang), z: z } })
.then(function() { ocupado = false; }, function() { ocupado = false; });
}, 60);
}"
eje_3d <- function(titulo) list(title = titulo, backgroundcolor = "#F3F8F5", showbackground = TRUE,
gridcolor = "#D9E7DF", zerolinecolor = "#8FB3A3", tickfont = list(size = 10))
grafico_3d <- function(grupo, colores, centroides = FALSE) {
d <- coord_ind %>%
mutate(grupo = as.character(.data[[grupo]]), aporte = contrib_3d,
info = paste0("<b>", pais, "</b><br>", grupo,
"<br>CP1: ", num(CP1, 2), " · CP2: ", num(CP2, 2), " · CP3: ", num(CP3, 2),
"<br>Aporte a la inercia (CP1–CP3): ", num(aporte, 2), "%"))
fig <- plot_ly()
for (g in names(colores)) {
dg <- filter(d, grupo == g)
if (nrow(dg) == 0) next
fig <- fig %>% add_trace(
x = dg$CP1, y = dg$CP2, z = dg$CP3, type = "scatter3d", mode = "markers", name = g,
marker = list(size = 3.5 + 10 * sqrt(dg$aporte / max(d$aporte)), color = colores[[g]], opacity = .88,
line = list(color = "white", width = .6)),
hovertext = dg$info, hoverinfo = "text")
}
cortos <- c("República Democrática del Congo" = "RD Congo", "República Centroafricana" = "Rep. Centroafricana")
top <- filter(d, pais %in% destacados) %>%
mutate(etiqueta = ifelse(pais %in% names(cortos), cortos[pais], pais))
fig <- fig %>%
add_trace(x = top$CP1, y = top$CP2, z = top$CP3, type = "scatter3d", mode = "text",
name = "Países que más aportan", text = top$etiqueta, textposition = "top center",
textfont = list(size = 11, color = pal[["profundo"]]), hoverinfo = "skip")
if (centroides) {
cen <- d %>% group_by(grupo) %>% summarise(across(c(CP1, CP2, CP3), mean), .groups = "drop")
fig <- fig %>% add_trace(
x = cen$CP1, y = cen$CP2, z = cen$CP3, type = "scatter3d", mode = "markers", name = "Centroides",
marker = list(size = 13, symbol = "diamond", color = unname(colores[cen$grupo]),
line = list(color = "white", width = 3)),
hovertext = paste0("<b>Centroide</b><br>", cen$grupo), hoverinfo = "text")
}
fig %>%
layout(scene = list(xaxis = eje_3d("CP1 · Modernización"), yaxis = eje_3d("CP2 · Emisiones y contaminación"),
zaxis = eje_3d("CP3 · Bosque vs. PM2.5"), aspectmode = "cube",
domain = list(x = c(0, 1), y = c(.1, 1)),
camera = list(eye = list(x = 1.28, y = -1.19, z = .62))),
legend = list(orientation = "h", y = 0, font = list(size = 11.5)),
margin = list(l = 0, r = 0, t = 10, b = 0)) %>%
estilo_plotly(rejilla = FALSE) %>%
htmlwidgets::onRender(js_giro)
}
vistas <- list(
"Cargas" = interactivo(g_cargas, tooltip = "text"),
"Círculo CP1–CP2" = circulo(1, 2),
"Círculo CP1–CP3" = circulo(1, 3),
"Círculo CP3–CP4" = circulo(3, 4)
)
selector_vistas(vistas, "evid", alto = 660)Círculos: cada flecha es una variable; su color indica la dimensión, y cuanto más larga, gruesa y opaca, mejor representada está en el plano (cos² alto). La circunferencia punteada representa una distancia de 0,5 respecto al centro del plano de correlaciones. Vista 3D: el tamaño de cada punto es proporcional a su aporte a la inercia de los tres primeros componentes; se etiquetan los países que más aportan y Colombia (rombo dorado). Arrastre para girar; el giro automático se detiene al interactuar.
Los círculos de correlaciones nos ayudan a ver qué indicadores se mueven juntos y cuáles lo hacen en sentido contrario. En el plano CP1–CP2 podemos observar que las flechas de acceso a electricidad y a cocina limpia, tanto total como rural, apuntan hacia la derecha y llegan casi al borde del círculo; el PIB per cápita y la urbanización van en la misma dirección, aunque son más cortas. En contraste, la energía renovable y el crecimiento poblacional apuntan hacia la izquierda, lo que nos dice que los países con menos acceso son los que tienen más energía renovable. Esta es la paradoja renovable que veníamos viendo, ahora representada gráficamente.
Notemos que las flechas de intensidad energética, PM2.5 e industria suben casi en vertical, perpendiculares a las de acceso. Esto nos da a entender que la contaminación y la intensidad energética son una dimensión distinta del nivel de acceso: un país puede tener electricidad para toda su población y aun así contaminar mucho. Las emisiones de CO₂ y de gases de efecto invernadero quedan en diagonal porque se relacionan con los dos ejes: están ligadas tanto al ingreso y la urbanización como a la industria y la contaminación.
En el plano CP1–CP3, la superficie forestal y el PM2.5 se separan en direcciones opuestas sobre el eje vertical, es decir, los países con más bosque tienden a tener menos contaminación del aire. Por último, en el plano CP3–CP4 la electricidad renovable se alinea con el CP4 junto con el PIB per cápita.
# Ward sobre los componentes retenidos
factores <- acp$ind$coord[, 1:k_cp]
distancias <- dist(factores, method = "euclidean")
hc <- hclust(distancias, method = "ward.D2")# Suma de cuadrados dentro de los grupos (inercia intra) y silueta media para k = 1, ..., 10
inercia_intra <- function(g) {
sum(sapply(split(as.data.frame(factores), g), function(d) sum(scale(d, scale = FALSE)^2)))
}
criterios <- tibble(k = 1:10) %>%
mutate(
wss = map_dbl(k, ~ inercia_intra(cutree(hc, .x))),
r2 = 100 * (1 - wss / wss[1]), # % de la inercia total explicada por la particion
ganancia = r2 - lag(r2), # puntos de inercia que agrega cada grupo adicional
silueta = map_dbl(k, ~ if (.x == 1) NA_real_ else mean(silhouette(cutree(hc, .x), distancias)[, 3])),
tamanos = map_chr(k, ~ paste(sort(table(cutree(hc, .x)), decreasing = TRUE), collapse = " · "))
)
k_final <- 3
crit <- function(k, col) criterios[[col]][criterios$k == k]
ganancia_despues <- max(criterios$ganancia[criterios$k > k_final])Elegir el número de grupos es una de las decisiones más delicadas del análisis de conglomerados, porque en la literatura no existe un criterio único ni universalmente óptimo. Everitt et al. (2011) señalan que las reglas para detener la agrupación sirven como guías y no como pruebas formales, y Hennig (2015) plantea que cuáles son los «verdaderos» grupos depende del propósito del análisis. Por esta razón, y siguiendo a Hair et al. (2019), la decisión combina criterios estadísticos con la utilidad práctica de la solución.
Evaluamos soluciones de 1 a 10 grupos con dos criterios estadísticos que se complementan: el método del codo, que nos dice cuánta información gana cada grupo adicional, y el coeficiente de silueta, que mide qué tan bien quedó ubicado cada país dentro de su grupo. A esto sumamos un criterio más sencillo: que los grupos se puedan interpretar a la luz del trilema energético.
# --- Metodo del codo: suma de cuadrados intra (arriba) y ganancia de inercia explicada (abajo)
destaca <- function(k, si, no) ifelse(k == k_final, si, no)
codo_sup <- plot_ly(criterios, x = ~k) %>%
add_trace(y = ~wss, type = "scatter", mode = "lines", fill = "tozeroy", fillcolor = "rgba(54,153,120,.12)",
line = list(color = pal[["natural"]], width = 3, shape = "spline"), hoverinfo = "skip", showlegend = FALSE) %>%
add_trace(y = ~wss, type = "scatter", mode = "markers", showlegend = FALSE,
marker = list(size = destaca(criterios$k, 20, 11), color = destaca(criterios$k, pal[["dorado"]], "#FFFFFF"),
line = list(color = destaca(criterios$k, pal[["profundo"]], pal[["natural"]]), width = 2.5)),
hovertext = ~paste0("<b>k = ", k, "</b><br>Suma de cuadrados intra: ", num(wss, 1),
"<br>Inercia explicada: ", num(r2, 1), "%<br>Tamaños: ", tamanos),
hoverinfo = "text")
ganancias <- filter(criterios, k > 1)
codo_inf <- plot_ly(ganancias, x = ~k, y = ~ganancia, type = "bar", showlegend = FALSE,
marker = list(color = destaca(ganancias$k, pal[["dorado"]], pal[["menta"]]),
line = list(color = destaca(ganancias$k, pal[["profundo"]], pal[["natural"]]), width = 1.5)),
text = ~paste0("+", num(ganancia, 1)), textposition = "outside", cliponaxis = FALSE,
hovertext = ~paste0("Pasar de ", k - 1, " a ", k, " grupos agrega<br>", num(ganancia, 1),
" puntos de inercia explicada"),
hoverinfo = "text")
codo <- subplot(codo_sup, codo_inf, nrows = 2, heights = c(.6, .4), shareX = TRUE, titleY = TRUE, margin = .07) %>%
layout(
xaxis = list(title = "Número de clusters (k)", dtick = 1, range = c(.5, 10.5)),
yaxis = list(title = "Suma de cuadrados intra"),
yaxis2 = list(title = "Ganancia (pts)", range = c(0, max(ganancias$ganancia) * 1.3)),
shapes = list(list(type = "rect", xref = "x", yref = "paper", x0 = k_final + .5, x1 = 10.5, y0 = 0, y1 = 1,
fillcolor = "rgba(242,205,115,.14)", line = list(width = 0), layer = "below")),
annotations = list(
list(x = k_final, y = crit(k_final, "wss"), xref = "x", yref = "y", showarrow = TRUE, arrowhead = 2,
arrowcolor = pal[["profundo"]], ax = 90, ay = -55, bgcolor = "#FFFFFF", bordercolor = pal[["dorado"]],
borderwidth = 1.5, borderpad = 6, font = list(size = 12.5),
text = paste0("<b>Codo en k = ", k_final, "</b><br>", num(crit(k_final, "r2")), "% de la inercia explicada")),
list(x = 7.5, y = 1, xref = "x", yref = "paper", yanchor = "top", showarrow = FALSE,
font = list(size = 12, color = "#9A7316"),
text = paste0("Rendimientos decrecientes: desde k = ", k_final + 1, "<br>cada grupo adicional aporta ",
num(ganancia_despues), " pts o menos")))
) %>%
estilo_plotly()
# --- Silueta media segun k, con las franjas de interpretacion de Kaufman y Rousseeuw (1990)
sil_k <- filter(criterios, k > 1)
franja <- function(y0, y1, color) list(type = "rect", xref = "paper", x0 = 0, x1 = 1, y0 = y0, y1 = y1,
fillcolor = color, line = list(width = 0), layer = "below")
rotulo <- function(y, texto, color) list(x = 12.55, y = y, xanchor = "right", showarrow = FALSE, align = "right",
text = texto, font = list(size = 11.5, color = color))
g_silk <- plot_ly(sil_k, x = ~k, y = ~silueta, type = "bar", showlegend = FALSE,
marker = list(color = destaca(sil_k$k, pal[["dorado"]], pal[["natural"]]),
line = list(color = destaca(sil_k$k, pal[["profundo"]], "#FFFFFF"), width = 2)),
text = ~num(silueta, 3), textposition = "outside", cliponaxis = FALSE,
hovertext = ~paste0("<b>k = ", k, "</b><br>Silueta media: ", num(silueta, 3), "<br>Tamaños: ", tamanos),
hoverinfo = "text") %>%
layout(
xaxis = list(title = "Número de clusters (k)", tickvals = 2:10, range = c(1.4, 12.6)),
yaxis = list(title = "Silueta media", range = c(0, .72)),
shapes = list(franja(0, .25, "rgba(181,83,47,.07)"), franja(.25, .50, "rgba(242,205,115,.18)"),
franja(.50, .70, "rgba(54,153,120,.12)")),
annotations = list(rotulo(.125, "<b>Sin estructura</b><br>sustancial (≤ 0,25)", pal[["terracota"]]),
rotulo(.375, "<b>Estructura débil</b><br>a moderada (0,26–0,50)", "#9A7316"),
rotulo(.60, "<b>Estructura</b><br>razonable (0,51–0,70)", pal[["bosque"]]),
list(x = k_final, y = crit(k_final, "silueta"), yshift = 26, showarrow = FALSE,
text = "▼ máximo", font = list(size = 12, color = pal[["profundo"]])))
) %>%
estilo_plotly()
# --- Silueta de cada pais con k = 3 (perfil clasico de Rousseeuw)
df_sil <- tibble(pais = base_final$pais, cluster = base_final$cluster, s = silueta_pais, vecino = vecino_pais) %>%
arrange(cluster, desc(s)) %>%
mutate(orden = row_number())
neg <- filter(df_sil, s < 0)
marcas_neg <- lapply(seq_len(nrow(neg)), function(i) list(
x = neg$orden[i], y = neg$s[i], text = neg$pais[i], showarrow = TRUE, arrowhead = 0, arrowcolor = "#9DB8AE",
ax = ifelse(i %% 2 == 1, -48, 48), ay = 20 + 18 * ((i - 1) %% 4),
font = list(size = 10.5, color = pal[["terracota"]])))
g_silp <- plot_ly(df_sil, x = ~orden, y = ~s, color = ~cluster, colors = col_cluster, type = "bar",
hovertext = ~paste0("<b>", pais, "</b><br>", cluster, "<br>Silueta: ", num(s, 3),
"<br>Cluster vecino más cercano: ", vecino),
hoverinfo = "text") %>%
layout(
bargap = .08,
xaxis = list(title = "Países ordenados por cluster y por silueta", showticklabels = FALSE, zeroline = FALSE),
yaxis = list(title = "Ancho de silueta", zerolinecolor = pal[["profundo"]], range = c(-.62, .78)),
shapes = list(list(type = "line", xref = "paper", x0 = 0, x1 = 1,
y0 = crit(k_final, "silueta"), y1 = crit(k_final, "silueta"),
line = list(color = pal[["terracota"]], dash = "dash", width = 1.5))),
annotations = c(list(list(x = 1, xref = "paper", xanchor = "right", y = crit(k_final, "silueta"), yanchor = "bottom",
showarrow = FALSE, text = paste0("Silueta media = ", num(crit(k_final, "silueta"), 3)),
font = list(color = pal[["terracota"]], size = 12))), marcas_neg),
legend = list(orientation = "h", y = -0.12)
) %>%
estilo_plotly()
selector_vistas(list(
"Método del codo" = codo,
"Silueta media según k" = g_silk,
"Silueta de cada país (k = 3)" = g_silp
), "k", alto = 580)Método del codo: la curva superior muestra la suma de cuadrados dentro de los grupos y las barras inferiores la ganancia de inercia explicada al agregar cada grupo; la zona sombreada marca los rendimientos decrecientes. Silueta: las franjas siguen la escala de interpretación de Kaufman y Rousseeuw (1990). Pase el cursor para ver el tamaño de los grupos en cada solución.
Método del codo: busca el punto a partir del cual agregar un grupo deja de reducir de forma importante la suma de cuadrados dentro de los grupos, que es justamente lo que el método de Ward minimiza en cada fusión. Como podemos observar en la figura, la curva cae con fuerza al pasar de uno a dos grupos +41,6 puntos de inercia explicada y de dos a tres +15,3 puntos y a partir de ahí se aplana, el cuarto grupo agrega solo 7,0 puntos, menos de la mitad que el tercero. Por eso el codo está en k = 3 donde la clasificación ya explica el 56,9% de la inercia total de los cuatro componentes.
Coeficiente de silueta: para cada país compara qué tan lejos está de los países de su propio grupo con qué tan lejos está del grupo vecino más cercano. Va de −1 a 1 y su promedio resume qué tan bien quedó toda la clasificación. La silueta media es máxima en k = 3 (0,434), un poco por encima de k = 2 (0,411) y baja con fuerza con cuatro grupos 0,342. Según la escala de Kaufman y Rousseeuw (1990), un valor entre 0,26 y 0,50 indica una estructura débil: los grupos existen, pero con fronteras difusas. Esto es esperable en un fenómeno gradual como la transición energética, en el que los países se distribuyen a lo largo de un continuo y no en bloques separados. Notemos que solo 6 de los 164 países 3,7% tienen silueta negativa, es decir, están algo más cerca del grupo vecino que del suyo: Bangladés, Belice, Botsuana, Fiyi, Pakistán y Samoa. Todos son casos de frontera entre la pobreza energética y el acceso universal.
La decisión: ambos criterios coinciden en k = 3. Además, Hair et al. (2019) recomiendan preferir la solución más simple cuando las alternativas solo agregan mejoras pequeñas, y aquí un cuarto grupo baja la calidad de la clasificación sin ganar mucha inercia. Y, como proponen Husson et al. (2017), los tres grupos se pueden leer directamente porque son tres posiciones distintas frente al trilema energético: déficit de acceso, acceso resuelto con distinta participación de energías renovables, y alta emisión con alto ingreso.
orden_dend <- as.character(unique(grupos[hc$order])) # clusters en el orden del dendrograma
colores_dend <- unname(col_cluster[nombres_cl[orden_dend]])
corte <- mean(rev(hc$height)[c(k_final - 1, k_final)]) # punto medio entre la fusion 3->2 y la 4->3
dend <- as.dendrogram(hc) %>%
dendextend::color_branches(k = k_final, col = colores_dend) %>%
dendextend::color_labels(k = k_final, col = colores_dend) %>%
dendextend::set("labels_cex", .85) %>%
dendextend::set("branches_lwd", 1.8)
par(mar = c(4, 1, 1, 10), family = "sans", col.axis = pal[["profundo"]], col.lab = pal[["bosque"]])
plot(dend, horiz = TRUE, xlab = "Distancia de agregación (criterio de Ward)", axes = TRUE)
abline(v = corte, lty = 2, lwd = 1.6, col = pal[["terracota"]])
text(corte, 1, "corte en k = 3", pos = 4, cex = .9, col = pal[["terracota"]])Desplace el panel para recorrer los 164 países. Cada color corresponde a un cluster y la línea discontinua marca el corte en tres grupos.
El dendrograma nos muestra cómo se fueron uniendo los países paso a paso. Como podemos observar, la primera gran división separa a los 55 países con déficit de acceso del resto. Los 9 países de alto ingreso y alta emisión forman una rama propia que solo se une con los de acceso universal casi al final, lo que nos da a entender que se diferencian claramente de los demás.
# Plano factorial con elipses de concentracion (90%) y centroides de cada cluster
datos_plano <- coord_ind
centroides <- datos_plano %>% group_by(cluster) %>% summarise(CP1 = mean(CP1), CP2 = mean(CP2), .groups = "drop")
g_plano_cl <- ggplot(datos_plano, aes(CP1, CP2, colour = cluster, fill = cluster)) +
stat_ellipse(geom = "polygon", alpha = .08, level = .9, linewidth = .4) +
geom_point(aes(text = paste0("<b>", pais, "</b><br>", cluster, "<br>", region)), size = 2.6, alpha = .9) +
geom_point(data = centroides, aes(text = paste0("Centroide<br>", cluster)),
shape = 23, size = 5.5, colour = "white", stroke = 1.2) +
scale_colour_manual(values = col_cluster) +
scale_fill_manual(values = col_cluster) +
labs(x = paste0("CP1 · Modernización (", num(eig$varianza[1]), "%)"),
y = paste0("CP2 · Intensidad fósil (", num(eig$varianza[2]), "%)"), colour = NULL, fill = NULL)
mundo_cl <- mundo %>% select(adm0_a3, geometry) %>%
left_join(base_final %>% select(iso3c, pais, cluster, subperfil, Energia_renovable, Emisiones_CO2, Acceso_electricidad),
by = c("adm0_a3" = "iso3c"))
pequenos_cl <- pequenos %>% select(adm0_a3, geometry) %>%
left_join(base_final %>% select(iso3c, pais, cluster), by = c("adm0_a3" = "iso3c"))
pal_cl <- colorFactor(unname(col_cluster), levels = orden_cl, na.color = "#E6E6E6")
mapa_cl <- leaflet(mundo_cl, options = leafletOptions(minZoom = 1, maxZoom = 6, zoomSnap = .25,
attributionControl = FALSE), height = 460) %>%
setView(lng = 12, lat = 20, zoom = 1.6) %>%
addPolygons(
fillColor = ~pal_cl(cluster), fillOpacity = .88, weight = .6, color = "white",
label = ~ifelse(is.na(cluster), "Sin datos", paste0(pais, " · ", cluster)),
popup = ~ifelse(is.na(cluster), "Sin datos",
paste0("<b>", pais, "</b><br><i>", cluster, "</i><br>Subperfil: ", subperfil,
"<br>Renovable: ", num(Energia_renovable), "%<br>Acceso electricidad: ",
num(Acceso_electricidad), "%<br>CO2: ", num(Emisiones_CO2, 2), " t pc")),
highlightOptions = highlightOptions(weight = 2, color = pal[["profundo"]], bringToFront = TRUE)
) %>%
addCircleMarkers(data = pequenos_cl, radius = 4, color = "white", weight = 1, fillOpacity = .95,
fillColor = ~pal_cl(cluster), label = ~paste0(pais, " · ", cluster)) %>%
addLegend("bottomleft", pal = pal_cl, values = orden_cl, title = "Cluster", opacity = .9)
selector_vistas(list(
"Espacio 3D" = grafico_3d("cluster", col_cluster, centroides = TRUE),
"Plano CP1–CP2" = interactivo(g_plano_cl, tooltip = "text") %>% layout(legend = list(orientation = "h", y = -0.2)),
"Mapa mundial" = mapa_cl
), "segm", alto = 620)
Espacio 3D: los rombos grandes son los centroides de cada cluster y se etiquetan los países que más aportan a la inercia. El giro automático se detiene al arrastrar el gráfico.
En el espacio factorial los tres grupos se ordenan, ante todo, a lo largo del CP1. El centroide de Pobreza energética con biomasa queda en el extremo negativo de la modernización (CP1 = −3,02), el de Acceso universal con matriz mixta en la zona positiva (CP1 = 1,30) y el de Alto ingreso y alta emisión todavía más a la derecha (CP1 = 3,98). Lo que separa a este último grupo del resto es el CP2: su centroide vale 4,58 en ese eje, muy por encima de los otros dos (0,33 y −0,59), por lo que en la vista 3D se ve como una nube aislada en la parte alta del eje de emisiones y contaminación. Los otros dos grupos se tocan en la zona central del CP1, lo que explica que sus fronteras sean difusas.
El mapa nos muestra que los grupos tienen una geografía reconocible, pero que no coinciden con los continentes. De los 41 países de África subsahariana 37 pertenecen al grupo de pobreza energética y los otros 4 que son Cabo Verde, Mauricio, Seychelles y Sudáfrica quedan con el acceso universal. En América Latina y el Caribe 22 de los 27 países tienen acceso universal, mientras que Belice, Guatemala, Honduras y Nicaragua comparten el perfil de pobreza energética y Trinidad y Tobago se ubica en el grupo de alto ingreso y alta emisión. Asia Oriental y Pacífico es la región más repartida con 10, 14 y 2 países en cada grupo; los dos del grupo de alto ingreso y alta emisión son Brunéi y Palaos. Por último, en Medio Oriente, Norte de África, Afganistán y Pakistán conviven los 6 países del Golfo de alto ingreso y alta emisión con 10 países de acceso universal y 2 en pobreza energética (Afganistán y Yibuti).
El nombre de cada grupo no es una etiqueta arbitraria, ya que sale de los valores test que nos dicen en qué variables el grupo se aparta significativamente del promedio mundial. Un valor test positivo significa que el grupo tiene en promedio valores más altos que el conjunto de países y uno negativo, valores más bajos. La franja gris de la figura marca la zona donde la diferencia no es significativa (|v| < 1,96). Para nombrar cada grupo tomamos la combinación de rasgos con los valores test más altos en valor absoluto y la comprobamos con los países más cercanos a su centro.
vt_graf <- vt %>%
mutate(nombre = factor(cortas[variable], levels = rev(cortas)), cluster = factor(cluster, levels = orden_cl),
fila = as.numeric(nombre))
g_vt <- ggplot(vt_graf, aes(v, fila)) +
annotate("rect", xmin = -1.96, xmax = 1.96, ymin = .4, ymax = 15.6, fill = "#E9EFEB", alpha = .9) +
annotate("text", x = 0, y = 16.1, label = "No significativo (|v| < 1,96)", size = 3.2, colour = "#5C7469") +
geom_vline(xintercept = 0, colour = "#B9CEC3") +
geom_line(aes(group = nombre), colour = "#D3E0D8", linewidth = 1.3) +
geom_point(aes(colour = cluster,
text = paste0("<b>", cluster, "</b><br>", nombre, "<br>Valor test: ", num(v, 2),
"<br>Media del cluster: ", num(media, 2), "<br>Media mundial: ", num(global, 2))),
size = 4, alpha = .95) +
scale_colour_manual(values = col_cluster) +
scale_x_continuous(breaks = seq(-12, 12, 3)) +
scale_y_continuous(breaks = seq_along(levels(vt_graf$nombre)), labels = levels(vt_graf$nombre),
expand = expansion(add = .4)) +
labs(x = "Valor test (v)", y = NULL, colour = NULL)
interactivo(g_vt, tooltip = "text") %>% layout(legend = list(orientation = "h", y = -0.12))Cada fila es una variable y cada punto un cluster. Los puntos fuera de la franja gris son diferencias significativas al 5 % respecto al promedio de los 164 países.
Las tarjetas siguientes detallan cada grupo, el por qué se llama así, su perfil completo, los países más representativos y su composición por regiones. En las barras se compara el promedio del grupo (barra de color) con el promedio mundial (marca oscura).
Las variables que más lo distinguen del promedio mundial son la cocina limpia (v = −11,2), la cocina limpia rural (v = −11,2), el acceso rural a electricidad (v = −10,2) y el acceso total (v = −9,6). Es el único grupo con un déficit masivo de servicios energéticos modernos, lo que define la pobreza energética. Su rasgo positivo más fuerte es la energía renovable (v = +9,4; 58,1% frente a 30,4% en el mundo), pero con solo 26,7% de la población cocinando con combustibles limpios, esa energía renovable proviene sobre todo de leña y carbón vegetal, es decir, de biomasa tradicional. El nombre une ambos rasgos porque son dos caras del mismo fenómeno.
En promedio, solo 61,9% de la población tiene electricidad (44,7% en zonas rurales) y apenas 13,7% de los hogares rurales cocina con combustibles limpios. Tiene el menor PIB per cápita (US$ 5.649, menos de una cuarta parte del promedio), la menor urbanización (41,6%) y el mayor crecimiento poblacional (2,06% anual; v = +7,0), lo que significa que la demanda de energía crecerá rápido. Sus emisiones son mínimas (0,73 t de CO2 per cápita), pero su intensidad energética es alta (5,77 MJ/US$; v = +3,3) por la baja eficiencia de la biomasa, y su contaminación por PM2.5 (26,6 µg/m³) supera el promedio. Conserva más bosque que el promedio (40,2%), un capital natural presionado por el uso de leña.
África subsahariana (37) · Asia Oriental y Pacífico (10) · América Latina y el Caribe (4) · Asia del Sur (2) · Medio Oriente, Norte de África, Afganistán y Pakistán (2)
Sus valores test positivos más altos corresponden a la cocina limpia (v = +9,8), la cocina limpia rural (v = +9,5), la electricidad rural (v = +9,0) y el acceso total (v = +8,5): prácticamente todos sus países tienen acceso universal (99,5% en promedio). Su rasgo negativo más fuerte es la energía renovable (v = −7,5; 17,9% frente a 30,4%), pero la electricidad renovable apenas se separa del promedio (v = −2,0) y va de 0,0 a 99,4% según el país, y las emisiones de CO2 no difieren significativamente del promedio mundial (v = +0,2). Lo que estos países tienen en común es el acceso, no la fuente de energía: en el grupo conviven matrices dominadas por el carbón y el gas con otras casi totalmente renovables. Por eso se describe como acceso universal con matriz mixta.
Es el grupo más numeroso y el más cohesionado (silueta media de 0,49). Tiene ingreso medio-alto (US$ 32.915 en promedio, con una mediana de US$ 24.915), alta urbanización (67,0%), crecimiento poblacional bajo (0,34%) y la menor intensidad energética de los tres grupos (3,85 MJ/US$; v = −5,4). Su contaminación por PM2.5 (18,7 µg/m³) es inferior al promedio, aunque sigue por encima de la guía de la OMS. Sus emisiones promedio (5,14 t per cápita) esconden una gran dispersión: van de 0,0 a 15,0 t, con Australia, Canadá y Estados Unidos en el extremo alto. Reúne a las principales economías del mundo, como China, Estados Unidos, India, Japón y los países de la Unión Europea, por lo que de su evolución depende buena parte de las emisiones globales.
Europa y Asia Central (44) · América Latina y el Caribe (22) · Asia Oriental y Pacífico (14) · Medio Oriente, Norte de África, Afganistán y Pakistán (11) · África subsahariana (4) · Asia del Sur (3) · Norteamérica (2)
Es el grupo con los valores test más extremos de toda la clasificación: emisiones de GEI (v = +10,0) y de CO2 (v = +9,6), con 30,3 t de CO2 per cápita, 6 veces el promedio de los países analizados. Le siguen la industria (v = +5,4; 46,6% del PIB), la contaminación por PM2.5 (v = +4,8) y la intensidad energética (v = +4,7). En el lado negativo, su participación renovable es prácticamente nula (0,3%; v = −3,5). Por sus elevados niveles de ingreso y emisiones, este grupo se describe como países de alto ingreso y alta emisión.
Tiene el mayor PIB per cápita (US$ 57.497), la mayor urbanización (84,1%) y acceso universal, pero el peor desempeño ambiental: la intensidad energética más alta (9,00 MJ/US$), PM2.5 de 47,8 µg/m³, en parte por el polvo del desierto, y solo 3,1% de bosques en mediana. Su crecimiento poblacional promedio es negativo (−1,09%), un valor arrastrado por Catar (−10,9%), una caída probablemente asociada a la salida de trabajadores extranjeros durante la pandemia. La excepción del grupo es Palaos, que no produce hidrocarburos pero registra las mayores emisiones per cápita de la muestra (74,7 t) por su población muy pequeña y su total dependencia de combustibles importados.
Medio Oriente, Norte de África, Afganistán y Pakistán (6) · Asia Oriental y Pacífico (2) · América Latina y el Caribe (1)
Pertenecer al mismo cluster no significa tener indicadores idénticos. La tabla siguiente muestra para cada grupo la silueta media (cohesión), la proporción de países con silueta negativa, las medianas de cuatro indicadores clave y el rango de las emisiones per cápita.
heterogeneidad <- base_final %>%
mutate(silueta = silueta_pais) %>%
group_by(Cluster = as.character(cluster)) %>%
summarise(
Países = n(),
`Silueta media` = mean(silueta),
`% con silueta negativa` = 100 * mean(silueta < 0),
`Renovable (mediana %)` = median(Energia_renovable),
`Acceso elec. (mediana %)` = median(Acceso_electricidad),
`CO2 pc (mediana t)` = median(Emisiones_CO2),
`Rango CO2 pc (t)` = paste0(num(min(Emisiones_CO2), 1), " – ", num(max(Emisiones_CO2), 1)),
.groups = "drop"
) %>%
arrange(factor(Cluster, levels = orden_cl))
reactable(
heterogeneidad, pagination = FALSE, compact = TRUE,
columns = list(
Cluster = colDef(minWidth = 210, cell = function(value) {
span(class = "chip-cluster", style = paste0("background:", col_cluster[[value]]), value)
}),
`Silueta media` = colDef(cell = function(value) {
div(class = "barra-cv",
div(class = "barra-relleno", style = list(width = paste0(100 * value / max(heterogeneidad$`Silueta media`), "%"),
background = "#E0A230")),
span(num(value, 3)))
}),
`% con silueta negativa` = colDef(cell = function(v) num(v, 1)),
`Renovable (mediana %)` = colDef(cell = function(v) num(v, 1)),
`Acceso elec. (mediana %)` = colDef(cell = function(v) num(v, 1)),
`CO2 pc (mediana t)` = colDef(cell = function(v) num(v, 2))
),
theme = reactableTheme(headerStyle = list(background = pal[["bosque"]], color = "white"))
)Como podemos observar en la Tabla 4, el grupo de Acceso universal con matriz mixta es el que presenta la silueta media más alta, con un valor de 0,491, y está conformado por 100 países. Esto significa que es el grupo que presenta una mejor separación respecto a los demás. Además, solo 2 países tienen una silueta negativa, lo que nos indica que la mayoría se encuentra mejor agrupada dentro de este cluster. Sin embargo, esto no quiere decir que todos tengan las mismas características, ya que sus emisiones de CO₂ por habitante varían entre 0,0 y 15,0 toneladas.
Por otro lado, tenemos el grupo de Pobreza energética con biomasa, conformado por 55 países, con una silueta media de 0,357. En este caso, 4 países presentan valores negativos, lo que muestra que algunos tienen características más cercanas a otro grupo. También podemos observar que su mediana de energía renovable es del 62,1 %, mientras que la mediana de acceso a electricidad es del 65,4 %. Esta diferencia es importante porque nos muestra que tener una participación elevada de energía renovable no significa necesariamente que exista un alto acceso a servicios energéticos.
Finalmente, el grupo de Países de alto ingreso y alta emisión está formado por 9 países y presenta la silueta media más baja, con un valor de 0,267. Aunque ninguno de sus países tiene una silueta negativa, existen diferencias importantes dentro del grupo. Por ejemplo, las emisiones de CO₂ per cápita van de 19,2 a 74,7 toneladas. Esto nos permite entender que los países pueden pertenecer a un mismo cluster y aun así presentar valores muy diferentes en algunos indicadores.
En general, los resultados de la tabla nos muestran que los tres clusters permiten identificar grupos de países con ciertas características similares, pero esto no significa que todos los integrantes de un mismo grupo sean iguales. Por esta razón, la clasificación es útil para reconocer patrones generales, teniendo en cuenta que también existen diferencias entre los países que conforman cada grupo.
Después de realizar el análisis de los 164 países con los 15 indicadores del Banco Mundial correspondientes al año 2021 podemos concluir que sí fue posible identificar diferentes patrones de desarrollo energético, ambiental y económico. Los resultados nos muestran que los países no presentan las mismas condiciones y que tener un mayor ingreso o una participación elevada de energía renovable no significa necesariamente contar con mejores condiciones de acceso energético o menores niveles de contaminación.
En primer lugar, mediante el Análisis de Componentes Principales (ACP) logramos resumir las 15 variables en cuatro componentes que explican conjuntamente el 74,7 % de la variabilidad total. El componente más importante fue el CP1, modernización y acceso energético con un 42,0 %, seguido del CP2, emisiones e intensidad energética con un 15,9 %. Por otro lado, el CP3 permitió identificar diferencias relacionadas con la cobertura forestal y la contaminación del aire mientras que el CP4 estuvo relacionado principalmente con la electricidad renovable y el ingreso. De acuerdo con estos resultados, podemos observar que las mayores diferencias entre los países están relacionadas principalmente con el acceso a servicios energéticos modernos, aunque los indicadores ambientales también tienen una participación importante.
Posteriormente, al aplicar el método de Ward utilizando los cuatro componentes principales, obtuvimos tres clusters. El primero, denominado Pobreza energética con biomasa que reúne a 55 países que presentan menores niveles de ingreso y acceso a electricidad y cocina limpia, pero una participación elevada de energía renovable. El segundo el Acceso universal con matriz mixta que está conformado por 100 países y se caracteriza por mejores condiciones de acceso energético, aunque existen diferencias importantes en sus emisiones y en la participación de electricidad renovable. Finalmente, el tercer grupo Países de alto ingreso y alta emisión que contiene 9 países que se destacan por sus elevados niveles de ingreso y emisiones de CO₂. Esto nos permitió identificar tres perfiles generales con características distintas, sin que todos los países de un mismo grupo sean necesariamente iguales.
Uno de los resultados que más llama la atención es que una participación alta de energía renovable no siempre representa mejores condiciones energéticas. Como pudimos observar, algunos de los países con mayor consumo de energía renovable también presentan dificultades para acceder a electricidad y tecnologías limpias para cocinar. Esto puede estar relacionado con el uso de biomasa tradicional, como la leña. En contraste, encontramos países con mayores ingresos y acceso a electricidad que presentan emisiones elevadas. Por lo tanto, consideramos que no es suficiente analizar únicamente el ingreso o las energías renovables, sino que es necesario tener en cuenta varios indicadores para comprender mejor la situación de cada país.
También identificamos diferencias entre regiones, una parte importante de los países con mayores dificultades de acceso energético se encuentra en África subsahariana, mientras que en otras regiones encontramos países con mejores condiciones de acceso pero con resultados ambientales diferentes. En el caso de América Latina podemos observar que varios países presentan una participación importante de electricidad renovable, aunque todavía existen desafíos relacionados con otros servicios energéticos. Colombia es un ejemplo, ya que cuenta con una elevada participación de electricidad renovable, pero mantiene diferencias entre las zonas urbanas y rurales en el acceso a tecnologías limpias para cocinar. Esto demuestra que las condiciones energéticas de un país no pueden evaluarse únicamente mediante un promedio nacional.
En cuanto a las posibles aplicaciones, consideramos que este tipo de análisis puede ser útil en economía y políticas públicas, porque permite identificar países con necesidades similares y establecer prioridades según las características de cada grupo. Por ejemplo, en los países con menor acceso energético sería importante fortalecer la electrificación y el acceso a tecnologías limpias para cocinar. Por otro lado, en los países con mejores condiciones de acceso, las prioridades podrían estar relacionadas con aumentar la participación de electricidad renovable, mejorar la eficiencia energética y reducir las emisiones. Para los países con ingresos y emisiones elevados, los resultados pueden servir como punto de partida para estudiar alternativas que permitan reducir su impacto ambiental.
Finalmente, debemos tener en cuenta que el análisis presenta algunas limitaciones. Trabajamos únicamente con información del año 2021 y con los 164 países que tenían datos completos, por lo que no podemos afirmar que estos resultados representen a todos los países del mundo ni cómo ha evolucionado cada uno a través del tiempo. Además, los clusters dependen de las variables seleccionadas y del método utilizado. A pesar de estas limitaciones, consideramos que el ACP y el método de Ward fueron herramientas útiles para resumir la información, identificar semejanzas entre países y comprender mejor las diferencias que existen en sus condiciones de desarrollo energético y ambiental.
Bartlett, M. S. (1950). Tests of significance in factor analysis. British Journal of Psychology, Statistical Section, 3(2), 77–85. https://doi.org/10.1111/j.2044-8317.1950.tb00285.x
Everitt, B. S., Landau, S., Leese, M., & Stahl, D. (2011). Cluster analysis (5.ª ed.). Wiley. https://doi.org/10.1002/9780470977811
Hair, J. F., Black, W. C., Babin, B. J., & Anderson, R. E. (2019). Multivariate data analysis (8.ª ed.). Cengage Learning.
Hennig, C. (2015). What are the true clusters? Pattern Recognition Letters, 64, 53–62. https://doi.org/10.1016/j.patrec.2015.04.009
Hosier, R. H., & Dowd, J. (1987). Household fuel choice in Zimbabwe: An empirical test of the energy ladder hypothesis. Resources and Energy, 9(4), 347–361. https://doi.org/10.1016/0165-0572(87)90003-X
Husson, F., Lê, S., & Pagès, J. (2017). Exploratory multivariate analysis by example using R (2.ª ed.). CRC Press. https://doi.org/10.1201/b21874
Jolliffe, I. T., & Cadima, J. (2016). Principal component analysis: A review and recent developments. Philosophical Transactions of the Royal Society A, 374(2065), 20150202. https://doi.org/10.1098/rsta.2015.0202
Kaiser, H. F. (1960). The application of electronic computers to factor analysis. Educational and Psychological Measurement, 20(1), 141–151. https://doi.org/10.1177/001316446002000116
Kaufman, L., & Rousseeuw, P. J. (1990). Finding groups in data: An introduction to cluster analysis. Wiley. https://doi.org/10.1002/9780470316801
Lê, S., Josse, J., & Husson, F. (2008). FactoMineR: An R package for multivariate analysis. Journal of Statistical Software, 25(1), 1–18. https://doi.org/10.18637/jss.v025.i01
Leach, G. (1992). The energy transition. Energy Policy, 20(2), 116–123. https://doi.org/10.1016/0301-4215(92)90105-B
Murtagh, F., & Legendre, P. (2014). Ward’s hierarchical agglomerative clustering method: Which algorithms implement Ward’s criterion? Journal of Classification, 31(3), 274–295. https://doi.org/10.1007/s00357-014-9161-z
Naciones Unidas. (2015). Transformar nuestro mundo: la Agenda 2030 para el Desarrollo Sostenible (A/RES/70/1). Asamblea General de las Naciones Unidas.
Rousseeuw, P. J. (1987). Silhouettes: A graphical aid to the interpretation and validation of cluster analysis. Journal of Computational and Applied Mathematics, 20, 53–65. https://doi.org/10.1016/0377-0427(87)90125-7
Stern, D. I. (2004). The rise and fall of the environmental Kuznets curve. World Development, 32(8), 1419–1439. https://doi.org/10.1016/j.worlddev.2004.03.004
Thorndike, R. L. (1953). Who belongs in the family? Psychometrika, 18(4), 267–276. https://doi.org/10.1007/BF02289263
Ward, J. H. (1963). Hierarchical grouping to optimize an objective function. Journal of the American Statistical Association, 58(301), 236–244. https://doi.org/10.1080/01621459.1963.10500845
World Bank. (2024). World Development Indicators [Base de datos]. https://databank.worldbank.org/source/world-development-indicators
World Energy Council. (2021). World energy trilemma index 2021. World Energy Council.
World Health Organization. (2021). WHO global air quality guidelines: Particulate matter (PM2.5 and PM10), ozone, nitrogen dioxide, sulfur dioxide and carbon monoxide. World Health Organization.