6 Visualización dinámica
Fecha de compilación: 2026-10-13
Instalación y carga de librerías
if (!require(DT)) install.packages('DT')
library(DT) # tablas interactivas (DataTables)
if (!require(pxR)) install.packages('pxR')
library(pxR) # lectura de ficheros PC-Axis
if (!require(gifski)) install.packages('gifski')
library(gifski) # renderizado de GIF (con gganimate)
if (!require(gganimate)) install.packages('gganimate')
library(gganimate) # animaciones sobre ggplot2
if (!require(highcharter)) install.packages('highcharter')
library(highcharter) # gráficos interactivos (Highcharts)
if (!require(fpp3)) install.packages('fpp3')
library(fpp3) # series temporales: tsibble, fable, feasts
if (!require(RColorBrewer)) install.packages('RColorBrewer')
library(RColorBrewer)# paletas de colores
if (!require(openxlsx)) install.packages('openxlsx')
library(openxlsx) # lectura de Excel
if (!require(leaflet)) install.packages('leaflet')
library(leaflet) # mapas interactivos
if (!require(sf)) install.packages('sf')
library(sf) # datos geoespaciales, lectura de GeoJSON
if (!require(plotly)) install.packages('plotly')
library(plotly) # gráficos interactivos (ggplotly)
if (!require(ggplot2)) install.packages('ggplot2')
library(ggplot2) # gramática de gráficos
if (!require(tidyverse)) install.packages('tidyverse')
library(tidyverse) # manipulación y visualización de datos
theme_set(theme_bw()) # tema de los gráficos, igual en todo el libro
source("../utilidades.R") # funciones auxiliares del cursoowid_country <- read.xlsx("https://ctim.es/AEDV/data/owid_country.xlsx",sheet=1) |> # países OWID
as_tibble()La visualización dinámica comprende dos modalidades principales: los gráficos interactivos, en los que el usuario puede explorar los datos pasando el cursor por encima de los elementos, filtrando, haciendo zoom o seleccionando subconjuntos; y las animaciones, en las que la representación evoluciona en el tiempo mostrando cómo cambian los datos. Ambas modalidades tienen un gran interés para presentar resultados en páginas web (ficheros HTML) o cuadros de mando, como se verá en el capítulo siguiente. Las tres librerías principales de este capítulo son las siguientes:
Librerías utilizadas en este capítulo
plotly: librería de gráficos interactivos para R, adaptada de la libreríaplotly.jsde JavaScript. Destaca especialmente por su integración conggplot2: la funciónggplotly()permite convertir cualquier gráfico estático deggplot2en un gráfico interactivo con una sola línea de código.highcharter: interfaz R para la bibliotecaHighcharts.js, una de las librerías de gráficos en JavaScript más populares en el ámbito web, conocida por la calidad visual y la riqueza de opciones de sus gráficos interactivos. Atención a la licencia: Highcharts es gratuito solo para uso personal, educativo o sin ánimo de lucro; su uso comercial requiere una licencia de pago. Quien necesite una alternativa completamente libre con una filosofía similar puede usarecharts4r(interfaz R para Apache ECharts).leaflet: interfaz R para la bibliotecaLeaflet.js, especializada en la creación de mapas interactivos y en la visualización de información geográfica. Permite crear mapas que pueden desplazarse, hacer zoom y mostrar capas de datos de forma dinámica.
Las tres librerías comparten la ventaja de estar diseñadas para integrarse de forma directa en cuadros de mando, como se verá en el capítulo dedicado a Shiny y flexdashboard.
A ellas se añaden, para las animaciones y el resto de ejemplos del capítulo:
gganimateygifski: las animaciones. La primera describe la animación como una capa más deggplot2; la segunda es el motor que genera el GIF resultante.DT: tablas interactivas, con búsqueda, ordenación y paginación.sf: datos geoespaciales vectoriales (simple features), con los que se leen los ficheros GeoJSON que dibujan los mapas.RColorBrewer: paletas de colores para las escalas de los mapas y gráficos.fpp3,openxlsx,pxR,ggplot2ytidyverse: series temporales, lectura de datos y manipulación, ya presentados en capítulos anteriores.
Cuándo usar visualización estática vs. dinámica
No siempre un gráfico interactivo es mejor que uno estático. La elección depende del contexto de uso:
Visualización estática (ggplot2): preferible para publicaciones impresas, informes PDF, artículos científicos y presentaciones donde el autor controla la narrativa. Los gráficos estáticos son reproducibles, ligeros y funcionan en cualquier formato de salida.
Visualización dinámica (plotly, highcharter, leaflet): preferible para páginas web, cuadros de mandos, documentos HTML y presentaciones interactivas donde el usuario explora los datos por sí mismo. Permite zoom, tooltips, filtros y animaciones.
Es importante tener en cuenta que los gráficos interactivos no se renderizan en formato WORD o PDF; además, generan ficheros HTML más pesados y pueden ser lentos con datasets grandes. Se puede diseñar primero el gráfico estático con ggplot2 y convertirlo a interactivo con ggplotly() cuando se necesite la interactividad; aunque hay que tener en cuenta que no todos los gráficos dinámicos de plotly se pueden generar con ggplotly().
Dos formas de construir un gráfico interactivo
A lo largo del capítulo alternaremos las dos vías que ofrece plotly, y conviene entender desde el principio en qué se diferencian:
ggplotly()recibe un gráfico deggplot2ya construido y lo convierte en interactivo. Es la forma más cómoda cuando el gráfico ya existe o cuando queremos reutilizar todo lo aprendido en el capítulo anterior: la gramática de capas, los temas, las escalas. Será nuestra opción por defecto.plot_ly()construye el gráfico directamente enplotly, capa a capa, añadiendo cada elemento con funcionesadd_*(add_lines,add_ribbons,add_pie…). Se recurre a ella cuando el gráfico no se puede expresar bien enggplot2—como los circulares o los de donut— o cuando hay que combinar elementos que proceden de tablas distintas, como una serie observada y su predicción.
En ambos casos el resultado es el mismo tipo de objeto, que se puede seguir ajustando con layout() (títulos, ejes, leyenda).
6.1 Diagramas de barras interactivos
Usando ggplotly vamos a crear un gráfico interactivo con el mismo diagrama de barras de la sección 4.4. Como vemos, el gráfico se genera igual. Lo único que cambia es que el gráfico se almacena previamente en la variable p y después se dibuja con ggplotly. Observamos que cuando pasamos el ratón por encima de las barras nos aparece información adicional
p <- owid_country |> # países OWID
filter(!is.na(aged_65_older)) |> # solo los países con dato de edad
mutate(mayores_65 = population*aged_65_older/100, # personas de 65 años o más
menores_65 = population - mayores_65) |> # el resto de la población
group_by(continent) |> # agrupamos por continente
summarise( # población de cada grupo de edad en el continente
mayores_65=sum(mayores_65),
menores_65=sum(menores_65)) |>
pivot_longer(mayores_65:menores_65, names_to = "edad", values_to = "poblacion") |> # formato largo
ggplot(aes(x=continent,y=poblacion,fill=edad)) + # color por grupo de edad
geom_bar(stat = "identity") # barras apiladas
ggplotly(p) # versión interactiva
Figure 6.1: Diagrama de barras apilado usando geom_bar
6.2 Gráficos circulares interactivos
La librería plotly no solo sirve para hacer dinámicos gráficos generados con ggplot, plotly tiene sus propias funciones de visualización como plot_ly que usaremos para hacer un gráfico circular
owid_country |> # países OWID
group_by(continent) |> # agrupamos por continente
dplyr::summarise(population=sum(population)) |> # sumamos población
plotly::plot_ly(
labels = ~continent, # sectores = continentes
values = ~population, # tamaño = población
textinfo='label+percent',
insidetextorientation='radial'
) |>
add_pie() |> # gráfico circular
hide_legend() # sin leyenda
Figure 6.2: Gráfico circular usando plot_ly
Cómo se nombran las opciones en plotly
Al pasar de ggplot2 a plot_ly() cambia la forma de declarar las opciones, y conviene entender la lógica en vez de memorizar recetas:
- Las columnas de la tabla se referencian con una tilde delante:
labels = ~continentsignifica “usa la columnacontinent”. Es el equivalente del mapeo estético deaes(). - Las opciones de aspecto se agrupan en listas anidadas que reproducen la jerarquía de la figura. Por eso el título del eje horizontal se escribe
layout(xaxis = list(title = "PIB")):layoutcontienexaxis, que contienetitle. Lo mismo conline = list(color = "red", width = 2)dentro de una capa.
Saber esto es lo que permite usar cualquier opción de la documentación de plotly —que está escrita como esa misma jerarquía de atributos— sin depender de encontrar un ejemplo idéntico al caso propio.
6.3 Gráficos de donuts interactivos
Vamos a hacer un gráfico de donuts con los mismos datos
owid_country |> # países OWID
group_by(continent) |> # agrupamos por continente
dplyr::summarise(population=sum(population)) |> # sumamos población
plotly::plot_ly(
labels = ~continent, # sectores = continentes
values = ~population, # tamaño = población
textinfo='label+percent',
insidetextorientation='radial'
) |>
add_pie(hole=0.4) |> # con agujero central
hide_legend() # sin leyenda
Figure 6.3: Gráfico de donuts usando plot_ly
6.4 Diagramas de cajas interactivos
Usando ggplotly vamos a crear un gráfico interactivo con el mismo diagrama de cajas de la sección 4.8. Observamos que cuando pasamos el ratón por encima de los puntos nos aparece información adicional y si nos situamos a la derecha de una caja nos sale información estadística de la distribución de valores.
p <- owid_country |> # países OWID
ggplot(aes(x=continent,y=gdp_per_capit,fill=continent,label=location)) + # PIB por continente
geom_boxplot(outlier.shape = NA) + # cajas sin marcar outliers aparte
geom_jitter(shape=16, position=position_jitter(0.2))+ # puntos con dispersión horizontal
theme(legend.position = "none") # sin leyenda
ggplotly(p,tooltip=c("location","gdp_per_capit")) # solo estos campos en el tooltip
Figure 6.4: Diagrama de cajas interactivo usando ggplotly
Como observamos en el código anterior, con la opción tooltip podemos seleccionar los campos que aparecen en el desplegable. Además es posible formatear el contenido del desplegable. A continuación haremos el mismo gráfico cambiando el texto del desplegable (<br> es la instrucción en HTML para hacer un cambio de línea (retorno de carro)) :
p <- owid_country |> # países OWID
ggplot(aes(x=continent,y=gdp_per_capit,fill=continent,
text = paste('País: ', location, # texto del tooltip a medida
'<br>PIB per cápita: ', gdp_per_capit))
) +
geom_boxplot(outlier.shape = NA) + # cajas sin marcar outliers aparte
geom_jitter(shape=16, position=position_jitter(0.2))+ # puntos con dispersión horizontal
theme(legend.position = "none") # sin leyenda
ggplotly(p,tooltip=c("text")) # usamos el texto formateado
Figure 6.5: Formateo desplegable usando ggplotly
6.5 Diagramas de dispersión interactivos
Usando ggplotly vamos a crear un gráfico interactivo con el mismo diagrama de dispersión de la sección 4.9. Observamos que cuando pasamos el ratón por encima de los puntos nos aparece información adicional.
p <- owid_country |> # países OWID
mutate(gdp_per_capit=round(gdp_per_capit)) |> # redondeamos para el tooltip
ggplot(aes(x=gdp_per_capit,y=life_expectancy,color=continent,label=location)) + # color por continente
geom_point() + # puntos
scale_x_continuous(transform = 'log2')+ # eje x logarítmico
theme(legend.position = "none") # sin leyenda
ggplotly(p) # versión interactiva
Figure 6.6: Diagrama de puntos interactivo usando ggplotly
6.6 Combinando gráficos interactivos con subplot
La función subplot de plotly permite combinar varios gráficos interactivos en una sola visualización, de forma similar a lo que hace patchwork::wrap_plots con gráficos estáticos. Los gráficos pueden compartir ejes y tooltips:
p1 <- owid_country |>
mutate(gdp_per_capit = round(gdp_per_capit)) |> # redondeamos para el tooltip
ggplot(aes(x = gdp_per_capit, y = life_expectancy, color = continent, label = location)) + # PIB vs. esperanza de vida
geom_point() +
scale_x_continuous(transform = 'log2') +
labs(title = "Esperanza de vida vs PIB") +
theme(legend.position = "none")
p2 <- owid_country |>
mutate(gdp_per_capit = round(gdp_per_capit)) |> # redondeamos para el tooltip
ggplot(aes(x = gdp_per_capit, y = human_development_index, color = continent, label = location)) + # PIB vs. IDH
geom_point() +
scale_x_continuous(transform = 'log2') +
labs(title = "IDH vs PIB") +
theme(legend.position = "none")
subplot(ggplotly(p1), ggplotly(p2), nrows = 1, shareY = FALSE, titleX = TRUE) # los combinamos lado a lado
Figure 6.7: Combinación de dos gráficos interactivos usando subplot
La opción shareY = TRUE haría que ambos gráficos compartan el eje vertical, lo cual es útil cuando las variables son comparables. nrows controla la distribución en filas y columnas.
6.7 Gráficos interactivos con highcharter
La librería highcharter construye los gráficos con su función principal, hchart(), que sigue una lógica parecida a la de ggplot2: recibe la tabla, el tipo de gráfico ("column" para barras verticales, "bar" para horizontales, "line", "scatter", "pie"…) y, dentro de hcaes(), la correspondencia entre las columnas de la tabla y los elementos del gráfico, igual que aes(). El resto del gráfico se ajusta añadiendo funciones hc_* con el pipe: hc_title() para el título, hc_xAxis() y hc_yAxis() para los ejes, hc_tooltip() para el cuadro emergente, etc. Veamos el diagrama de barras de la población por continente:
owid_country |> # países OWID
group_by(continent) |> # agrupamos por continente
summarise(population = sum(population)) |> # sumamos población
hchart("column", hcaes(x = continent, y = population), name = "Población") |> # barras verticales
hc_title(text = "Población por continente") |> # título
hc_xAxis(title = list(text = "")) |> # sin título en el eje x
hc_yAxis(title = list(text = "Habitantes")) # título del eje y
Figure 6.8: Diagrama de barras interactivo usando hchart
Con el argumento group de hcaes() se dibuja una serie por grupo, con su propio color y su entrada en la leyenda (un clic en ella oculta o muestra la serie). En el cuadro emergente podemos mostrar cualquier columna de la tabla escribiendo {point.columna}:
owid_country |> # países OWID
filter(!is.na(gdp_per_capit), !is.na(life_expectancy)) |> # países con los dos datos
mutate(gdp_per_capit = round(gdp_per_capit)) |> # redondeamos para el cuadro emergente
hchart("scatter", hcaes(x = gdp_per_capit, y = life_expectancy, group = continent)) |> # una serie por continente
hc_title(text = "Esperanza de vida frente al PIB por habitante") |> # título
hc_xAxis(type = "logarithmic", title = list(text = "PIB por habitante (escala logarítmica)")) |> # eje x logarítmico
hc_yAxis(title = list(text = "Esperanza de vida (años)")) |> # título del eje y
hc_tooltip(pointFormat = "{point.location}: {point.x} $, {point.y} años") # contenido del cuadro emergente
Figure 6.9: Diagrama de dispersión interactivo usando hchart
Además de tablas, hchart() sabe dibujar directamente muchos tipos de objetos de R, eligiendo por sí misma el gráfico adecuado: con un vector numérico dibuja su histograma, y con el resultado de un análisis de componentes principales (prcomp), su biplot, como veremos en el capítulo de análisis de atributos. También es la librería que usaremos en algunos de los cuadros de mando del capítulo siguiente.
6.8 Gráficos de línea interactivos
A partir de un informe del 2022 del Oak Ridge National Laboratory vamos a estudiar el consumo de combustible según el tipo de coche y cómo aumenta dicho consumo con la velocidad. El informe expresa tanto la velocidad como la eficiencia en unidades anglosajonas —millas por hora, y millas recorridas con un galón (3.785 litros)—, así que antes de dibujar nada las pasamos a las que usamos habitualmente: km/h para la velocidad y kilómetros por litro (km/L) para la eficiencia.
ConsumoVehiculos <- read.xlsx("https://ctim.es/AEDV/data/ConsumoVehiculos.xlsx",startRow = 6) |> # datos ORNL
as_tibble()
str(ConsumoVehiculos) # estructura de la tabla## tibble [4 × 8] (S3: tbl_df/tbl/data.frame)
## $ speed : num [1:4] 45 55 65 75
## $ Gasoline.Midsize.Car: num [1:4] 43 45 38 32
## $ Gasoline.Small.SUV : num [1:4] 37 36 30 26
## $ Gasoline.Large.SUV : num [1:4] 35 31 29 25
## $ Diesel.Midsize.Car : num [1:4] 57 55 45 37
## $ Diesel.Small.SUV : num [1:4] 48 45 36 30
## $ Diesel.Large.SUV : num [1:4] 48 40 35 29
## $ Hybrid.Midsize.Car : num [1:4] 55 46 38 33
# 1 milla = 1.60934 km; 1 galón (US) = 3.78541 litros
p <- ConsumoVehiculos |>
pivot_longer(Gasoline.Midsize.Car:Hybrid.Midsize.Car, names_to = "Car.Type", values_to = "Millas.Por.Galon") |> # formato largo
mutate(`speed (km/h)` = speed * 1.60934, # a km/h
`km/L` = Millas.Por.Galon * 1.60934 / 3.78541) |> # a km/L
ggplot(aes(x=`speed (km/h)`,y=`km/L`,colour=Car.Type))+ # color por tipo de coche
geom_line()+ # líneas
geom_point()
ggplotly(p)
Figure 6.10: Gráfico de línea interactivo usando ggplotly
6.9 Mapas de calor interactivo
El gráfico de línea anterior muestra la tendencia, pero para comparar los vehículos entre sí a velocidades concretas conviene una tabla. Nos quedamos con tres de las velocidades del informe —120.7, 88.5 y 72.4 km/h (75, 55 y 45 mph)— y añadimos, entre cada par de velocidades consecutivas, la variación porcentual de los km/L al pasar de la más alta a la más baja: un valor positivo indica que el coche gana eficiencia al reducir la velocidad.
consumo_resumen <- ConsumoVehiculos |>
pivot_longer(Gasoline.Midsize.Car:Hybrid.Midsize.Car,
names_to = "Car.Type", values_to = "Millas.Por.Galon") |>
mutate(`speed (km/h)` = round(speed * 1.60934, 1),
Km.Por.Litro = round(Millas.Por.Galon * 1.60934 / 3.78541, 1)) |>
filter(`speed (km/h)` %in% c(120.7, 88.5, 72.4)) |>
select(Car.Type, `speed (km/h)`, Km.Por.Litro) |>
pivot_wider(names_from = `speed (km/h)`, values_from = Km.Por.Litro,
names_prefix = "kml_") |>
transmute(
# conservamos el orden del fichero original (gasolina, diésel, híbrido)
Car.Type = factor(str_replace_all(Car.Type, "\\.", " "), levels = rev(unique(str_replace_all(Car.Type, "\\.", " ")))),
`120.7 km/h` = kml_120.7,
`88.5 km/h` = kml_88.5,
`Var. 88.5 vs 120.7 (%)` = round(100 * (kml_88.5 - kml_120.7) / kml_120.7, 1),
`72.4 km/h` = kml_72.4,
`Var. 72.4 vs 88.5 (%)` = round(100 * (kml_72.4 - kml_88.5) / kml_88.5, 1)
)
consumo_resumen## # A tibble: 7 × 6
## Car.Type `120.7 km/h` `88.5 km/h` Var. 88.5 vs 120.7 (…¹ `72.4 km/h`
## <fct> <dbl> <dbl> <dbl> <dbl>
## 1 Gasoline Midsize … 13.6 19.1 40.4 18.3
## 2 Gasoline Small SUV 11.1 15.3 37.8 15.7
## 3 Gasoline Large SUV 10.6 13.2 24.5 14.9
## 4 Diesel Midsize Car 15.7 23.4 49 24.2
## 5 Diesel Small SUV 12.8 19.1 49.2 20.4
## 6 Diesel Large SUV 12.3 17 38.2 20.4
## 7 Hybrid Midsize Car 14 19.6 40 23.4
## # ℹ abbreviated name: ¹`Var. 88.5 vs 120.7 (%)`
## # ℹ 1 more variable: `Var. 72.4 vs 88.5 (%)` <dbl>
Las cinco columnas numéricas tienen escalas muy distintas —km/L frente a porcentajes—, así que normalizamos cada columna por separado entre su mínimo y su máximo antes de colorear; el cuadro emergente conserva el valor original, que es el que interesa leer:
p <- consumo_resumen |>
pivot_longer(-Car.Type, names_to = "metrica", values_to = "valor") |>
mutate(metrica = factor(metrica, levels = c(
"120.7 km/h", "88.5 km/h", "Var. 88.5 vs 120.7 (%)",
"72.4 km/h", "Var. 72.4 vs 88.5 (%)"))) |>
group_by(metrica) |>
mutate(valor_norm = (valor - min(valor)) / (max(valor) - min(valor))) |>
ungroup() |>
# las columnas "Var." son porcentajes; el resto, km/L
mutate(unidad = if_else(str_starts(as.character(metrica), "Var"), "%", "km/L"),
etiqueta = paste(valor, unidad)) |>
ggplot(aes(metrica, Car.Type, fill = valor_norm)) +
geom_tile(color = "grey80") +
# texto blanco sobre las celdas oscuras, gris oscuro sobre las claras
geom_text(aes(label = etiqueta, color = valor_norm > 0.5), size = 3, show.legend = FALSE) +
scale_fill_gradient(low = "white", high = "steelblue") +
scale_color_manual(values = c(`TRUE` = "white", `FALSE` = "grey20")) +
labs(x = "", y = "") +
theme(axis.text.x = element_text(angle = 30, hjust = 1), legend.position = "none")
ggplotly(p, tooltip = c("metrica", "Car.Type", "valor"))Figure 6.11: Mapa de calor de la eficiencia (km/L) y su variación porcentual, por tipo de vehículo y velocidad
Conclusiones
Los coches convencionales son significativamente más eficientes que los SUV en el consumo de combustible.
En términos de consumo, los motores diésel son más eficientes que los de gasolina, aunque cuando se aumenta la velocidad su consumo tiende a aumentar más que en los de gasolina.
Reducir la velocidad, en general, aumenta mucho la distancia que recorremos con la misma cantidad de combustible, con el consecuente ahorro de energía. Al pasar de 120,7 km/h a 88,5 km/h aumentamos la distancia recorrida entre un 24,5 % y un 49,2 % (lo cual es muy significativo). Al pasar de 88,5 km/h a 72,4 km/h la variación es menor, del −4,2 % al 20 %: algún modelo incluso pierde algo de eficiencia, y los coches grandes y los híbridos son los que más ganan.
Aunque no se deduce de estos datos, que se midieron sin viento, conviene tener en cuenta que el viento puede afectar mucho al consumo, puesto que el rozamiento con el aire es lo que más contribuye a su aumento con la velocidad: lo que importa es la velocidad del coche respecto al aire, es decir, la suma de la velocidad del coche y la del viento en contra. Por ejemplo, con un viento en contra de unos 35 km/h, como el que es habitual en verano en algunas zonas de Gran Canaria, un automóvil a 85 km/h encuentra una resistencia del aire parecida a la que tendría a 120 km/h sin viento.
6.10 Series temporales interactivas y gráficos de área
Usando ggplotly vamos a crear un gráfico interactivo con series temporales de población como las de la sección 5.2, ahora para los seis continentes. Observamos que cuando pasamos el ratón por encima de las series temporales nos aparece información adicional
# Evolución de la población por continente
owid_population <- read_csv("https://ctim.es/AEDV/data/owid_population.csv") |>
as_tibble() |>
filter(Entity %in% c("North America","South America","Europe","Asia",
"Africa","Oceania")
)p <- owid_population |>
rename(Population=`Population (historical estimates)`) |> # nombre más corto
ggplot(aes(Year,Population,colour=Entity)) + # color por continente
geom_line() # líneas
ggplotly(p) |>
layout(hovermode = "x unified") # un solo tooltip con todas las series del año
Figure 6.12: Gráfico interactivo de series temporales usando ggplotly
Para ver la evolución del porcentaje de población de cada continente sobre el total vamos a usar un gráfico de área. Para ello, después de calcular el porcentaje, utilizamos geom_col con barras de anchura 1, que quedan pegadas unas a otras y dan el aspecto de un área apilada. Lo natural sería usar geom_area, pero esta función no interactúa bien con ggplotly: el cuadro emergente no muestra bien los valores de cada año. Al apilar, las barras suman el 100 % de cada año, que es precisamente el caso en que un apilado tiene sentido:
p <- owid_population |>
group_by(Year) |> # % relativo a cada año
mutate(`Population Percentage` = 100*`Population (historical estimates)`/
sum(`Population (historical estimates)`)) |>
ggplot(aes(Year,`Population Percentage`,fill=Entity)) + # color por continente
geom_col(width = 1) # barras apiladas y pegadas: aspecto de área
ggplotly(p) # versión interactiva
Figure 6.13: Gráfico interactivo de porcentajes usando ggplotly
Representación interactiva de una predicción
A continuación vamos a construir una gráfica interactiva con la predicción ARIMA de la llegada de pasajeros a los aeropuertos canarios. Primero leemos los datos:
meses_es <- c("Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio", # nombres de los meses del ISTAC,
"Agosto","Septiembre","Octubre","Noviembre","Diciembre") # para no depender del idioma del sistema
istac_aeropuertos <- read.px("https://ctim.es/AEDV/data/istac_aeropuertos.px") |> # datos del ISTAC
as_tibble() |>
filter(word(Periodos,2)!="TOTAL" & is.na(value)==FALSE & word(Periodos,1)!="2020") |> # sin totales, NA ni 2020
mutate(date = make_yearmonth(year = as.integer(word(Periodos,1)), # fecha mensual: año...
month = match(word(Periodos,2), meses_es))) |> # ...y nº de mes
select(date,pasajeros=value) |>
as_tsibble(index=date) # tsibble mensualLa representación de una predicción es un caso donde conviene combinar elementos de naturaleza distinta y procedentes de tablas diferentes —la serie observada, el ajuste del modelo, la predicción y la banda del intervalo de confianza—: exactamente el tipo de gráfico para el que antes recomendábamos plot_ly() capa a capa. Pero el curso ya lo trae resuelto: las funciones aedv_dibujar_arima_dinamico() y aedv_dibujar_stl_arima_dinamico() de utilidades.R son las versiones interactivas de aedv_dibujar_arima() y aedv_dibujar_stl_arima(), que usamos en el capítulo anterior, con los mismos argumentos y el mismo aspecto. Ajustamos el modelo con aedv_prediccion_arima() para predecir dos años:
prediccion <- aedv_prediccion_arima(istac_aeropuertos, h = "2 years")
# En el tramo observado, "prediccion" es el ajuste del modelo y la banda tiene
# anchura nula; a partir del primer mes predicho, la banda se abre
prediccion |> tail(3)## # A tsibble: 3 x 5 [1M]
## date pasajeros prediccion lower upper
## <mth> <dbl> <dbl> <dbl> <dbl>
## 1 2021 oct. NA 412101. 338056. 486146.
## 2 2021 nov. NA 349976. 274767. 425186.
## 3 2021 dic. NA 393499. 317204. 469794.
El gráfico interactivo sale entonces de una sola llamada, que además dibuja el ajuste del modelo sobre el tramo observado —justo lo que un plot_ly() escrito a mano, capa a capa, es fácil olvidar—:
Figure 6.14: Predicción interactiva ARIMA de la llegada de pasajeros a los aeropuertos canarios, con el ajuste del modelo y su intervalo de confianza al 95%
La interactividad aporta aquí algo que el gráfico estático no da: al pasar el cursor se leen en un mismo recuadro el valor observado, el ajuste y la predicción de cada mes, y se puede ampliar el tramo final arrastrando para ver con detalle cómo se abre la incertidumbre a medida que nos alejamos.
La otra función, aedv_dibujar_stl_arima_dinamico(), hace lo propio con el resultado de aedv_prediccion_stl_arima() y añade en naranja la tendencia de la descomposición STL, sólida en el tramo observado y discontinua en el extrapolado. No hay que pasarle nada: la tendencia viaja dentro del propio resultado (es lo que devuelve aedv_tendencia()) y la función la recoge sola, con el mismo criterio que la versión estática del capítulo anterior —en una serie sin estacionalidad no se dibuja, porque coincidiría con la predicción—:
prediccion_stl <- aedv_prediccion_stl_arima(istac_aeropuertos, h = 24) # 24 meses
aedv_dibujar_stl_arima_dinamico(prediccion_stl, y_lab = "pasajeros")Figure 6.15: Predicción interactiva STL + ARIMA de la llegada de pasajeros a los aeropuertos canarios, con la tendencia de la descomposición en naranja
Aquí la interactividad se nota todavía más, porque hay más curvas superpuestas que leer: al situar el cursor sobre un mes observado el recuadro muestra a la vez el valor real, la reconstrucción de la descomposición y la tendencia, de modo que se puede comprobar mes a mes cuánto de la serie explica el movimiento de fondo y cuánto el ciclo estacional. Un clic en una entrada de la leyenda oculta esa curva, lo que permite quedarse solo con las dos o tres que interesan.
Predicción interactiva con intervalos no paramétricos
La lectura con el ratón resulta especialmente útil cuando la banda no es la del modelo, sino la que se estima con validación cruzada temporal, en la sección de predicción con intervalos de confianza no paramétricos: esa banda son los cuantiles empíricos del error a cada horizonte, de modo que puede ser asimétrica, y sus dos límites se comparan mucho mejor leyéndolos como números que estimándolos a ojo sobre el sombreado. Reproducimos la predicción a 12 meses del capítulo anterior, ahora en versión interactiva. Primero, la validación cruzada y la predicción entrenada con el último pliegue:
# validación cruzada temporal: pliegues de 120 meses y horizonte de 12 meses
cv_stl <- aedv_cv_stl_arima(istac_aeropuertos, h = 12, tamano_pliegue = 120)
# la predicción se entrena solo con el último pliegue, para ser coherente con la
# validación cruzada: periodos_atras = tamaño del pliegue
stl_12 <- aedv_prediccion_stl_arima(istac_aeropuertos, h = 12, periodos_atras = 120)
# se sustituye la banda del modelo por los cuantiles empíricos del error
stl_cv <- aedv_aplicar_bandas_cv(stl_12, cv_stl$bandas)Y ahora el gráfico, con la misma llamada de antes:
Figure 6.16: Predicción interactiva STL + ARIMA de la llegada de pasajeros a 12 meses (entrenada con el último pliegue), con intervalo de confianza no paramétrico
Conviene fijarse en dos detalles que el gráfico interactivo deja comprobar de un vistazo. El primero es la asimetría de la banda: al pasar el cursor por los meses predichos se ve que los dos límites no están a la misma distancia de la predicción, algo que un intervalo paramétrico normal nunca produciría. El segundo es el efecto de periodos_atras: como el modelo solo ha visto los últimos 120 meses, el histórico anterior aparece en gris sin descomposición ni tendencia superpuestas, y su recuadro muestra únicamente el valor observado; sigue ahí como contexto, pero ningún modelo lo ha utilizado.
Rendimiento de los gráficos interactivos
Un gráfico interactivo guarda todos los datos dentro del fichero HTML, de modo que su tamaño crece con el número de elementos representados. Con muchos miles de puntos la página puede volverse pesada y lenta de abrir. En esos casos conviene:
- Reducir el número de elementos representados, agregando los datos o tomando una muestra (
slice_sample()). - Dibujar con la tarjeta gráfica:
toWebGL()sobre el gráfico ya convertido, o directamenteplot_ly(type = "scattergl"), que admite conjuntos mucho mayores que el tiposcatterestándar. La conversión conggplotly()de un gráfico con muchos elementos (varios miles de puntos, un mapa de calor con muchas celdas) es justamente el caso en que esto se nota. - Reservar la interactividad para los gráficos donde de verdad aporte, y dejar los demás como gráficos estáticos.
Los mapas son, con diferencia, lo más pesado: cada polígono guarda todas sus coordenadas, así que un mapa con varios miles de municipios puede ocupar por sí solo varios megabytes. Conviene filtrar la zona de interés en lugar de dibujar el país entero, y recordar que este libro contiene decenas de gráficos interactivos: es la razón de que su fichero HTML sea grande.
6.11 Mapas interactivos con leaflet
Con frecuencia, los datos que manejamos están vinculados a localizaciones geográficas (países, municipios, etc.) o coordenadas GPS (latitud y longitud) y en la exploración de estos datos, resulta de gran interés posicionar los datos en un gráfico, de acuerdo con su localización geográfica.
¿Cómo se representa la información geográfica?
Usaremos dos formas principales (que se pueden combinar) de representar información geográfica:
- Ficheros
geojson: para leer estos ficheros usaremos la libreríasf(simple features), el estándar actual en R para datos geoespaciales. Al leer un ficherogeojsonconsfse obtiene un objeto que es, esencialmente, untibblecon una columna especial (geometry) que contiene la geometría de cada región (polígono que delinea su contorno); por tanto se puede manipular directamente con las funciones dedplyr(filter,mutate,left_join, …) que ya conocemos. El flujo de trabajo habitual con este tipo de ficheros es el siguiente:- Previamente disponemos de los datos que queremos representar en el mapa.
- Leemos la tabla de datos geoespaciales (fichero
geojson) de las áreas geográficas asociadas a nuestros datos. - Combinamos, con un
left_join, la tabla de datos geoespaciales con nuestros datos originales. - Creamos un vector de etiquetas HTML con la información que queremos que aparezca al pasar el ratón, en el mapa, por encima de un área geográfica.
- Con toda esta información creamos el mapa.
- Mapas OpenStreetMap: son mapas tipo Google Maps que se renderizan en tiempo real (hace falta conexión a internet) donde podemos situar etiquetas con la información que queramos usando sus coordenadas GPS. En este caso no hay que leer ningún fichero de datos geoespaciales, lo que sí necesitamos son las coordenadas GPS de las posiciones geográficas donde queramos poner las etiquetas. Es decir, si los datos originales son, por ciudades, necesitamos disponer de las coordenadas GPS de las ciudades.
Como los ficheros geojson del curso están en un servidor remoto, usamos la función auxiliar aedv_leer_geojson() del fichero utilidades.R, que primero descarga el fichero a un temporal y después lo lee con st_read de sf. La descarga previa es necesaria porque, en algunos sistemas Windows, st_read no puede acceder directamente a URLs https. Además, la función normaliza las coordenadas al sistema WGS84 (longitud/latitud), que es el que espera leaflet.
A continuación veremos algunos ejemplos de manejo de ficheros geojson para diferentes niveles de áreas geográficas:
Países del mundo
geoj <- aedv_leer_geojson("https://ctim.es/AEDV/data/geo_countries.geojson") # geometría de los países
geoj |> as_tibble()## # A tibble: 255 × 3
## ADMIN ISO_A3 geometry
## <chr> <chr> <MULTIPOLYGON [°]>
## 1 Aruba ABW (((-69.99694 12.57758, -70.05809 12.53718, -69.8…
## 2 Afghanistan AFG (((71.0498 38.40866, 70.97404 38.47367, 70.76109…
## 3 Angola AGO (((23.96746 -10.87231, 23.83372 -11.02847, 23.49…
## 4 Anguilla AIA (((-63.03767 18.21296, -62.97289 18.26927, -63.0…
## 5 Albania ALB (((20.06496 42.54676, 19.8196 42.46648, 19.60504…
## 6 Aland ALA (((19.91847 60.37018, 19.74757 60.10139, 20.0063…
## 7 Andorra AND (((1.707006 42.50278, 1.721993 42.60986, 1.46681…
## 8 United Arab Emirates ARE (((56.27906 25.62745, 56.14513 25.67084, 56.1834…
## 9 Argentina ARG (((-64.34057 -54.716, -64.75866 -54.83229, -64.6…
## 10 Armenia ARM (((45.0024 41.29045, 44.61286 41.22312, 44.55152…
## # ℹ 245 more rows
Al pasar el ratón por encima de cada país vamos a mostrar su nombre y su código ISO de tres letras, construyendo para cada uno una pequeña etiqueta HTML (<b> pone el nombre en negrita y <br> hace un salto de línea):
etiquetas <- paste0("<b>", geoj$ADMIN, "</b><br>Código ISO: ", geoj$ISO_A3) |> # nombre y código ISO
lapply(htmltools::HTML) # cada etiqueta se interpreta como HTML
geoj |>
leaflet() |> # mapa base
addPolygons(label = etiquetas,weight = 0.5) # un polígono por país
Figure 6.17: Visualización interactiva de los países del mundo usando leaflet
Datos de Canarias
geoj <- aedv_leer_geojson("https://ctim.es/AEDV/data/geo_canarias_municipios.geojson") # geometría de municipios
geoj |> as_tibble()|>
head() |>
knitr::kable()| geocode | geopadre | etiqueta | notas | granularidad | gcd_provincia | gcd_isla | gcd_grancomarca | gcd_comarca | ign_sup | ign_perim | utm_x | utm_y | longitud | latitud | utm_x_capi | utm_y_capi | long_capi | lati_capi | geometry |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 35001 | ES705A22 | Agaete | Ayuntamiento de Agaete | MUNICIPIOS | ES701 | ES705 | ES705A2 | ES705A22 | 4451.16 | 41038 | 432237.3 | 3105496 | -15.68965 | 28.07314 | 431360.3 | 3108388 | -15.69874 | 28.09919 | MULTIPOLYGON (((-15.74604 2… |
| 35002 | ES705A32 | Agüimes | Ayuntamiento de Agüimes | MUNICIPIOS | ES701 | ES705 | ES705A3 | ES705A32 | 7889.72 | 55508 | 455417.3 | 3085974 | -15.45300 | 27.89789 | 456117.6 | 3086640 | -15.44591 | 27.90393 | MULTIPOLYGON (((-15.3869 27… |
| 35003 | ES704A01 | Antigua | Ayuntamiento De Antigua | MUNICIPIOS | ES701 | ES704 | ES704A0 | ES704A01 | 25024.80 | 79292 | 603336.9 | 3136733 | -13.94558 | 28.35277 | 596564.7 | 3144503 | -14.01402 | 28.42341 | MULTIPOLYGON (((-13.91951 2… |
| 35004 | ES708A01 | Arrecife | Ayuntamiento De Arrecife | MUNICIPIOS | ES701 | ES708 | ES708A0 | ES708A01 | 2427.54 | 36641 | 641157.8 | 3206510 | -13.55112 | 28.97888 | 641613.1 | 3204339 | -13.54672 | 28.95923 | MULTIPOLYGON (((-13.58359 2… |
| 35005 | ES705A23 | Artenara | Ayuntamiento de Artenara | MUNICIPIOS | ES701 | ES705 | ES705A2 | ES705A23 | 6641.90 | 50791 | 432859.4 | 3099393 | -15.68297 | 28.01807 | 436452.0 | 3099620 | -15.64644 | 28.02031 | MULTIPOLYGON (((-15.74959 2… |
| 35006 | ES705A10 | Arucas | Ayuntamiento de Arucas | MUNICIPIOS | ES701 | ES705 | ES705A1 | ES705A10 | 3283.87 | 38491 | 448820.8 | 3110505 | -15.52109 | 28.11910 | 448399.3 | 3110440 | -15.52538 | 28.11849 | MULTIPOLYGON (((-15.55943 2… |
Para cada municipio vamos a mostrar su nombre, su geocode, su latitud y su longitud:
etiquetas <-paste("<strong> Municipio: ",geoj$etiqueta ,"</strong><br>GEOCODE: ",geoj$geocode, # etiqueta HTML
"<br>longitud: ",geoj$longitud,
"<br>latitud: ",geoj$latitud) |>
lapply(htmltools::HTML)
geoj |>
leaflet() |> # mapa base
addPolygons(label = etiquetas,weight = 0.5) # un polígono por municipio
Figure 6.18: Visualización interactiva de los municipios de Canarias usando leaflet
Datos de España
Los datos geográficos de España se han obtenido a través de GADM
Los ejemplos anteriores (países del mundo, municipios de Canarias) repiten el mismo patrón: leer la geometría, crear las etiquetas con htmltools::HTML y dibujar los polígonos con leaflet + addPolygons. Para no repetir ese código en los siguientes mapas, lo encapsulamos en una pequeña función auxiliar que reutilizaremos:
# Dibuja los polígonos de un objeto sf con sus etiquetas
mapa_poligonos <- function(geoj, etiquetas, weight = 0.5) {
geoj |>
leaflet() |>
addPolygons(label = lapply(etiquetas, htmltools::HTML), weight = weight)
}Autonomías
geoj <- aedv_leer_geojson("https://ctim.es/AEDV/data/ccaa.geojson") # geometría de las autonomías
geoj |> as_tibble()|>
knitr::kable()| codauto | iso2.ccaa.code | nuts1.code | nuts2.code | ine.ccaa.name | iso2.ccaa.name.es | iso2.ccaa.name.ca | iso2.ccaa.name.gl | iso2.ccaa.name.eu | nuts2.name | cldr.ccaa.name.en | cldr.ccaa.name.es | cldr.ccaa.name.ca | cldr.ccaa.name.ga | cldr.ccaa.name.eu | ccaa.shortname.en | ccaa.shortname.es | ccaa.shortname.ca | ccaa.shortname.ga | ccaa.shortname.eu | nuts1.name | geometry |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01 | ES-AN | ES6 | ES61 | Andalucía | Andalucía | NA | NA | NA | Andalucía | Andalusia | Andalucía | Andalusia | Andalucía | Andaluzia | Andalusia | Andalucía | Andalusia | Andalucía | Andaluzia | SUR | MULTIPOLYGON (((-4.275964 3… |
| 02 | ES-AR | ES2 | ES24 | Aragón | Aragón | NA | NA | NA | Aragón | Aragon | Aragón | Aragó | Aragón | Aragoi | Aragon | Aragón | Aragó | Aragón | Aragoi | NORESTE | MULTIPOLYGON (((-0.74093 42… |
| 03 | ES-AS | ES1 | ES12 | Asturias, Principado de | Asturias, Principado de | NA | NA | NA | Principado de Asturias | Asturias | Principado de Asturias | Astúries | Principado de Asturias | Asturiesko Printzerria | Asturias | Asturias | Astúries | Asturias | Asturias | NOROESTE | MULTIPOLYGON (((-4.536122 4… |
| 04 | ES-IB | ES5 | ES53 | Balears, Illes | Islas Baleares | Illes Balears | NA | NA | Illes Balears | Balearic Islands | Islas Baleares | Illes Balears | Illas Baleares - Illes Balears | Balear Uharteak | Balearic Islands | Baleares | Illes Balears | Illas Baleares | Balear Uharteak | ESTE | MULTIPOLYGON (((4.095952 40… |
| 05 | ES-CN | ES7 | ES70 | Canarias | Canarias | NA | NA | NA | Canarias | Canary Islands | NA | NA | NA | NA | Canary Islands | Canarias | Canàries | Canarias | Kanariak | CANARIAS | MULTIPOLYGON (((-8.504825 3… |
| 06 | ES-CB | ES1 | ES13 | Cantabria | Cantabria | NA | NA | NA | Cantabria | Cantabria | Cantabria | Cantàbria | Cantabria | Kantabria | Cantabria | Cantabria | Cantàbria | Cantabria | Kantabria | NOROESTE | MULTIPOLYGON (((-3.16291 43… |
| 07 | ES-CL | ES4 | ES41 | Castilla y León | Castilla y León | NA | NA | NA | Castilla y León | Castile and León | Castilla y León | Castella i Lleó | Castela e León | Gaztela eta Leon | Castile and León | Castilla y León | Castella i Lleó | Castela e León | Gaztela eta Leon | CENTRO (ES) | MULTIPOLYGON (((-4.743011 4… |
| 08 | ES-CM | ES4 | ES42 | Castilla - La Mancha | Castilla-La Mancha | NA | NA | NA | Castilla-La Mancha | Castile-La Mancha | Castilla-La Mancha | Castella - la Manxa | Castela-A Mancha | Gaztela-Mantxa | Castile-La Mancha | Castilla-La Mancha | Castella - la Manxa | Castela-A Mancha | Gaztela-Mantxa | CENTRO (ES) | MULTIPOLYGON (((-2.050606 4… |
| 09 | ES-CT | ES5 | ES51 | Cataluña | Cataluña | Catalunya | NA | NA | Cataluña | Catalonia | Cataluña | Catalunya | Cataluña | Katalunia | Catalonia | Cataluña | Catalunya | Cataluña | Katalunia | ESTE | MULTIPOLYGON (((0.844002 42… |
| 10 | ES-VC | ES5 | ES52 | Comunitat Valenciana | Valenciana, Comunidad | Valenciana, Comunitat | NA | NA | Comunidad Valenciana | Valencian Community | Comunidad Valenciana | País Valencià | Comunidade Valenciana | Valentziako Erkidegoa | Valencian Community | Comunidad Valenciana | País Valencià | Comunidade Valenciana | Valentziako Erkidegoa | ESTE | MULTIPOLYGON (((0.689104 39… |
| 11 | ES-EX | ES4 | ES43 | Extremadura | Extremadura | NA | NA | NA | Extremadura | Extremadura | Extremadura | Extremadura | Estremadura - Extremadura | Extremadura | Extremadura | Extremadura | Extremadura | Extremadura | Extremadura | CENTRO (ES) | MULTIPOLYGON (((-5.744424 4… |
| 12 | ES-GA | ES1 | ES11 | Galicia | Galicia | NA | Galicia | NA | Galicia | Galicia | Galicia | Galícia | Galicia | Galizia | Galicia | Galicia | Galícia | Galicia | Galizia | NOROESTE | MULTIPOLYGON (((-7.699081 4… |
| 13 | ES-MD | ES3 | ES30 | Madrid, Comunidad de | Madrid, Comunidad de | NA | NA | NA | Comunidad de Madrid | Madrid Autonomous Community | Comunidad de Madrid | Comunitat de Madrid | Comunidade de Madrid | Madrilgo Erkidegoa | Madrid | Madrid | Madrid | Madrid | Madril | COMUNIDAD DE MADRID | MULTIPOLYGON (((-3.076952 4… |
| 14 | ES-MC | ES6 | ES62 | Murcia, Región de | Murcia, Región de | NA | NA | NA | Región de Murcia | Murcia Region | Región de Murcia | Regió de Múrcia | Rexión de Murcia | Murtziako Eskualdea | Murcia | Murcia | Múrcia | Murcia | Murtzia | SUR | MULTIPOLYGON (((-1.04468 38… |
| 15 | ES-NC | ES2 | ES22 | Navarra, Comunidad Foral de | Navarra, Comunidad Foral de | NA | NA | Nafarroako Foru Komunitatea | Comunidad Foral de Navarra | Navarra Chartered Community | Navarra | Navarra | Navarra | Nafarroako Foru Erkidegoa | Navarra | Navarra | Navarra | Navarra | Nafarroa | NORESTE | MULTIPOLYGON (((-0.732351 4… |
| 16 | ES-PV | ES2 | ES21 | País Vasco | País Vasco | NA | NA | Euskal Herria | País Vasco | Basque Country | País Vasco | Comunitat autònoma del País Basc | País Vasco | Euskal Autonomia Erkidegoa | Basque Country | País Vasco | País Basc | País Vasco | Euskadi | NORESTE | MULTIPOLYGON (((-2.41998 43… |
| 17 | ES-RI | ES2 | ES23 | Rioja, La | La Rioja | NA | NA | NA | La Rioja | La Rioja | La Rioja | La Rioja | A Rioxa | Errioxako Autonomia Erkidegoa | La Rioja | La Rioja | La Rioja | A Rioxa | Errioxa | NORESTE | MULTIPOLYGON (((-2.864322 4… |
| 18 | ES-CE | ES6 | ES63 | Ceuta | Ceuta | NA | NA | NA | Ciudad Autónoma de Ceuta | Ceuta | Ceuta | Ceuta | Ceuta | Ceuta | Ceuta | Ceuta | Ceuta | Ceuta | Ceuta | SUR | MULTIPOLYGON (((-5.364975 3… |
| 19 | ES-ML | ES6 | ES64 | Melilla | Melilla | NA | NA | NA | Ciudad Autónoma de Melilla | Melilla | Melilla | Melilla | Melilla | Melilla | Melilla | Melilla | Melilla | Melilla | Melilla | SUR | MULTIPOLYGON (((-2.418081 3… |
Figure 6.19: Visualización interactiva de las autonomías de España usando leaflet
Provincias
Vamos a mostrar las provincias de Galicia:
geoj <- aedv_leer_geojson("https://ctim.es/AEDV/data/esp_prov.geojson") |> # geometría de las provincias
filter(nuts2.name=="Galicia") # solo Galicia
geoj |> as_tibble()|>
knitr::kable()| codauto | cpro | iso2.prov.code | nuts.prov.code | ine.prov.name | iso2.prov.name.es | iso2.prov.name.ca | iso2.prov.name.ga | iso2.prov.name.eu | cldr.prov.name.en | cldr.prov.name.es | cldr.prov.name.ca | cldr.prov.name.ga | cldr.prov.name.eu | prov.shortname.en | prov.shortname.es | prov.shortname.ca | prov.shortname.ga | prov.shortname.eu | nuts2.code | nuts2.name | nuts1.code | nuts1.name | geometry |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12 | 15 | ES-C | ES111 | Coruña, A | La Coruña | NA | A Coruña | NA | A Coruña | Provincia de La Coruña | Província de la Corunya | Provincia da Coruña | Coruñako probintzia | A Coruña | La Coruña | La Corunya | A Coruña | Coruña | ES11 | Galicia | ES1 | NOROESTE | MULTIPOLYGON (((-7.699081 4… |
| 12 | 27 | ES-LU | ES112 | Lugo | Lugo | NA | Lugo | NA | Lugo | Provincia de Lugo | Província de Lugo | Provincia de Lugo | Lugoko probintzia | Lugo | Lugo | Lugo | Lugo | Lugo | ES11 | Galicia | ES1 | NOROESTE | MULTIPOLYGON (((-7.035698 4… |
| 12 | 32 | ES-OR | ES113 | Ourense | Orense | NA | Ourense | NA | Ourense | Provincia de Orense | Província d’Ourense | Provincia de Ourense | Ourenseko probintzia | Ourense | Orense | Ourense | Ourense | Ourense | ES11 | Galicia | ES1 | NOROESTE | MULTIPOLYGON (((-7.944449 4… |
| 12 | 36 | ES-PO | ES114 | Pontevedra | Pontevedra | NA | Pontevedra | NA | Pontevedra | Provincia de Pontevedra | Província de Pontevedra | Provincia de Pontevedra | Pontevedrako probintzia | Pontevedra | Pontevedra | Pontevedra | Pontevedra | Pontevedra | ES11 | Galicia | ES1 | NOROESTE | MULTIPOLYGON (((-8.012904 4… |
Figure 6.20: Visualización interactiva de las provincias de Galicia usando leaflet
En este caso, se ha filtrado la tabla usando la función filter de dplyr para que solo
se dibujen las provincias de Galicia. Nótese que, al ser el objeto sf una tabla, podemos usar sobre él las funciones de dplyr directamente.
Municipios
Vamos ahora a mostrar los municipios de Galicia leyendo todos los municipios de España y filtrando:
geoj <- aedv_leer_geojson("https://ctim.es/AEDV/data/esp_mun.geojson") |> # geometría de todos los municipios
filter(ine.ccaa.name=="Galicia") # solo Galicia
geoj |> as_tibble()|>
head() |>
knitr::kable()| codauto | ine.ccaa.name | cpro | ine.prov.name | cmun | name | LAU_CODE | geometry |
|---|---|---|---|---|---|---|---|
| 12 | Galicia | 15 | Coruña, A | 001 | Abegondo | 15001 | MULTIPOLYGON (((-8.28432 43… |
| 12 | Galicia | 15 | Coruña, A | 002 | Ames | 15002 | MULTIPOLYGON (((-8.69223 42… |
| 12 | Galicia | 15 | Coruña, A | 003 | Aranga | 15003 | MULTIPOLYGON (((-8.09551 43… |
| 12 | Galicia | 15 | Coruña, A | 004 | Ares | 15004 | MULTIPOLYGON (((-8.19586 43… |
| 12 | Galicia | 15 | Coruña, A | 005 | Arteixo | 15005 | MULTIPOLYGON (((-8.46901 43… |
| 12 | Galicia | 15 | Coruña, A | 006 | Arzúa | 15006 | MULTIPOLYGON (((-8.21491 42… |
Figure 6.21: Visualización interactiva de los municipios de Galicia usando leaflet
6.12 Mapas con OpenStreetMap
Leaflet utiliza, por defecto, los mapas diseñados por OpenStreetMap para crear mapas.
En primer lugar vamos a crear un mapa con las Islas Canarias donde aparezca una etiqueta en la posición donde se encuentran los municipios. Para ello vamos a leer los datos geográficos de los municipios canarios:
geoj <- aedv_leer_geojson("https://ctim.es/AEDV/data/geo_canarias_municipios.geojson") # geometría de municipios
geoj_ti <- geoj |>
as_tibble() |> # quitamos la geometría, solo nos interesan las coordenadas
rename(nombre=etiqueta) |>
select(nombre,geocode,latitud,longitud)
geoj_ti |>
print(n=3)## # A tibble: 88 × 4
## nombre geocode latitud longitud
## <chr> <chr> <dbl> <dbl>
## 1 Agaete 35001 28.1 -15.7
## 2 Agüimes 35002 27.9 -15.5
## 3 Antigua 35003 28.4 -13.9
## # ℹ 85 more rows
En el tibble geoj_ti hemos seleccionado el nombre, el código del municipio, la longitud y la latitud de los municipios que usaremos ahora para crear el mapa:
etiquetas <- paste("<strong>",geoj_ti$nombre,"</strong>") |>
lapply(htmltools::HTML)
geoj |>
leaflet() |>
addTiles() |> # mapa base OpenStreetMap
addMarkers(
geoj_ti$longitud,
geoj_ti$latitud,
label = etiquetas,
clusterOptions = markerClusterOptions() # agrupa marcadores cercanos al alejar el zoom
)Figure 6.22: Mapa interactivo con marcadores en la localización de los municipios canarios
A continuación vamos a crear un mapa con círculos cuya área sea proporcional a la superficie de los municipios canarios. Como la superficie no está en el objeto geométrico geoj, tenemos que buscarla en otra tabla y vincularla. Ambas tablas comparten el código INE del municipio —geocode en el geojson e ine_code en la tabla del ISTAC—, así que la combinación puede hacerse por código y es exacta. Lo importante es dónde guardamos el resultado: unimos los datos al propio objeto geoj, de modo que la geometría y sus datos viajen juntos en una sola tabla.
# Leemos un fichero que contiene la superficie y el código INE de los municipios
istac_municipios <- read.xlsx("https://ctim.es/AEDV/data/istac_municipios_ine_codes.xlsx") |>
as_tibble()
istac_municipios |>
print(n=3)## # A tibble: 88 × 6
## ine_code nombre nombre2 nombre3 isla superficie
## <chr> <chr> <chr> <chr> <chr> <dbl>
## 1 38001 Adeje Adeje Adeje Tenerife 106.
## 2 35001 Agaete Agaete Agaete Gran Canaria 45.5
## 3 35002 Agüimes Agüimes Agüimes Gran Canaria 79.3
## # ℹ 85 more rows
# Unimos la superficie a la GEOMETRÍA usando el código del INE, presente en las dos
# tablas. Un objeto sf es una tabla, así que left_join funciona sobre él directamente
geoj <- geoj |>
left_join(istac_municipios |> select(ine_code, nombre3, superficie),
by = c("geocode" = "ine_code"))
# Comprobamos que ningún municipio se ha quedado sin superficie
sum(is.na(geoj$superficie))## [1] 0
Ahora creamos el mapa con los círculos, usando ~nombre3 —el nombre corto del municipio— en las etiquetas. El radio del círculo se calcula con la raíz cuadrada de la superficie, porque lo que el ojo compara es el área del círculo, que crece con el cuadrado del radio: sin la raíz, un municipio con el doble de superficie se dibujaría cuatro veces más grande.
etiquetas <- paste("<strong>",geoj$nombre3,"</strong><br/>",geoj$superficie," km²") |>
lapply(htmltools::HTML)
geoj |>
leaflet() |>
addTiles() |> # mapa base
addCircles(
lng = ~longitud,
lat = ~latitud,
weight = 1,
radius = ~sqrt(superficie) * 300, # área proporcional a la superficie
label= etiquetas
)Figure 6.23: Mapa interactivo con círculos proporcionales a la superficie de los municipios canarios
Ahora creamos un mapa, centrado en Gran Canaria, donde aparecen los límites de cada municipio combinado con el mapa. Añadimos addMiniMap() para que el usuario pueda orientarse geográficamente cuando hace zoom en una zona concreta:
etiquetas <- paste("<strong>",geoj$nombre3,"</strong><br/>",geoj$superficie," km²") |>
lapply(htmltools::HTML)
geoj |>
leaflet() |>
setView(lng = -15.6, lat = 27.95, zoom = 10) |> # centrado en Gran Canaria
addTiles() |> # mapa base
addPolygons(
color = "blue", weight = 2, dashArray = "3", fillOpacity = 0., # solo el borde, sin relleno
highlightOptions = highlightOptions(
color = "black", weight = 4, dashArray = "", fillOpacity = 0., bringToFront = TRUE # resalte al pasar el ratón
),
label = etiquetas
) |>
addMiniMap(position = "bottomright", width = 120, height = 120) # mapa de referencia
Figure 6.24: Visualización interactiva del mapa con los límites dibujados de los municipios de Canarias usando leaflet
Control de capas en mapas
Cuando un mapa tiene múltiples capas de información (polígonos, marcadores, círculos), la función addLayersControl permite al usuario activar o desactivar capas interactivamente:
etiquetas_poligonos <- paste("<strong>",geoj$nombre3,"</strong>") |>
lapply(htmltools::HTML)
etiquetas_marcadores <- paste("<strong>",geoj_ti$nombre,"</strong>") |>
lapply(htmltools::HTML)
geoj |>
leaflet() |>
addTiles() |>
addPolygons( # capa de límites municipales
group = "Límites municipales",
color = "blue", weight = 1, fillOpacity = 0.1,
label = etiquetas_poligonos
) |>
addMarkers( # capa de marcadores
geoj_ti$longitud, geoj_ti$latitud,
group = "Marcadores",
label = etiquetas_marcadores,
clusterOptions = markerClusterOptions()
) |>
addLayersControl( # control para activar/desactivar cada capa
overlayGroups = c("Límites municipales", "Marcadores"),
options = layersControlOptions(collapsed = FALSE)
)
Figure 6.25: Mapa con control de capas usando addLayersControl
6.13 Mapas coropléticos
Un mapa coroplético (choropleth map) es un gráfico dividido en zonas (por ejemplo áreas geográficas) en el que cada zona se colorea de forma distinta en función del valor de una variable estadística. Para ilustrar este concepto dibujaremos un gráfico con los países del mundo, utilizando como variable estadística su PIB por habitante. En primer lugar, leemos la información geográfica:
geoj <- aedv_leer_geojson("https://ctim.es/AEDV/data/geo_countries.geojson") # geometría de los países
geoj |> as_tibble()## # A tibble: 255 × 3
## ADMIN ISO_A3 geometry
## <chr> <chr> <MULTIPOLYGON [°]>
## 1 Aruba ABW (((-69.99694 12.57758, -70.05809 12.53718, -69.8…
## 2 Afghanistan AFG (((71.0498 38.40866, 70.97404 38.47367, 70.76109…
## 3 Angola AGO (((23.96746 -10.87231, 23.83372 -11.02847, 23.49…
## 4 Anguilla AIA (((-63.03767 18.21296, -62.97289 18.26927, -63.0…
## 5 Albania ALB (((20.06496 42.54676, 19.8196 42.46648, 19.60504…
## 6 Aland ALA (((19.91847 60.37018, 19.74757 60.10139, 20.0063…
## 7 Andorra AND (((1.707006 42.50278, 1.721993 42.60986, 1.46681…
## 8 United Arab Emirates ARE (((56.27906 25.62745, 56.14513 25.67084, 56.1834…
## 9 Argentina ARG (((-64.34057 -54.716, -64.75866 -54.83229, -64.6…
## 10 Armenia ARM (((45.0024 41.29045, 44.61286 41.22312, 44.55152…
## # ℹ 245 more rows
A continuación, como el dato de PIB por habitante no está en esta tabla, lo tomaremos de owid_country y lo uniremos a la geometría con un left_join usando como campo común el código ISO del país (que tiene diferente nombre de campo en las dos tablas):
geoj <- geoj |>
left_join(owid_country |> select(iso_code, gdp_per_capit), # unimos el PIB per cápita
by = c("ISO_A3" = "iso_code"))
sum(is.na(geoj$gdp_per_capit)) # países sin dato de PIB## [1] 61
A continuación generamos el gráfico interactivo y después explicaremos los elementos que aparecen nuevos:
etiquetas <-paste("<strong> ",geoj$ADMIN ,"</strong><br>PIB/CAPITA: ",round(geoj$gdp_per_capit)) |>
lapply(htmltools::HTML)
pal <- colorQuantile("YlOrRd", geoj$gdp_per_capit, n = 9) # paleta por percentiles
geoj |>
leaflet() |>
setView(lng = 5, lat = 22, zoom = 2) |> # vista inicial del mapa
addPolygons(
fillColor = ~pal(gdp_per_capit), # color según el PIB
weight = 2, opacity = 1, color = "white", dashArray = "3", fillOpacity = 0.7,
highlightOptions = highlightOptions(
weight = 2, color = rgb(0.2,0.2,0.2), dashArray = "", fillOpacity = 0.7, bringToFront = TRUE # resalte
),
label = etiquetas
) |>
addLegend("bottomleft", pal = pal, values = ~gdp_per_capit, # leyenda de colores
title = "PIB/habit.",
labFormat = function(type, cuts, p) { # rango de valores de cada tramo
n = length(cuts)
x = format(round(cuts), big.mark = ".", trim = TRUE) # miles separados con punto
paste(x[-n], "–", x[-1])
},
opacity = 1
)## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' y 'decimal.mark' son ambos '.', lo cual puede ser confuso
Figure 6.26: Visualización interactiva del PIB por habitante en los países del mundo
La instrucción pal <- colorQuantile("YlOrRd", geoj$gdp_per_capit, n = 9) genera una tabla de colores asociando los percentiles de los valores del PIB por habitante con la tabla de colores “YlOrRd”. La instrucción fillColor = ~pal(gdp_per_capit) utiliza esa tabla de colores para asignar un color a cada país: al escribirse como fórmula, leaflet busca la columna en la propia tabla del mapa, que es lo que garantiza que cada país reciba su valor. La instrucción highlightOptions = ... produce que cuando el ratón se sitúa por encima de un país, se destaca el país para que se vea más claro el contorno del país seleccionado. La instrucción addLegend(...) posiciona una leyenda en la parte inferior izquierda del gráfico con los colores de la paleta y el rango de valores del PIB por habitante que corresponde a cada color (con labFormat escribimos el rango, en lugar del percentil que leaflet muestra por defecto en una paleta de cuantiles).
Estas piezas —escala por cuantiles, resalte, etiquetas emergentes y leyenda— son siempre las mismas, cambie el indicador que cambie. Por eso están empaquetadas en aedv_mapa_coropletico, del fichero utilidades.R, que produce el mapa anterior en una sola instrucción:
aedv_mapa_coropletico(geoj, geoj$gdp_per_capit, etiquetas, "PIB/habit.", # mismo mapa en una instrucción
lng = 5, lat = 22, zoom = 2)
Figure 6.27: El mismo mapa coroplético, construido con aedv_mapa_coropletico
Recibe el objeto geográfico, los valores que dan el color, las etiquetas y el título de la leyenda; los tres últimos argumentos fijan el encuadre inicial del mapa. Los valores se le pasan como vector, y por eso conviene tomarlos de la columna del propio objeto (geoj$gdp_per_capit): así se cumple la condición del bloque anterior sin tener que pensarlo. Es la función que usaremos en los cuadros de mando del capítulo siguiente, donde el indicador que se colorea lo elige el usuario. El atajo no exime de entender el código anterior: en cuanto haya que salirse de la escala por cuantiles —como en el mapa siguiente— hay que volver al patrón completo.
Mapa coroplético de España por comunidades autónomas
Para completar los ejemplos de mapas coropléticos, a continuación creamos uno de España coloreado por el salario medio bruto mensual obtenido del INE:
Aquí el INE nos hace un favor: antepone al nombre de la comunidad su código oficial (“01 Andalucía”), que es exactamente el que el geojson guarda en la columna codauto. Separando ese código con substr podemos unir las dos tablas de forma exacta, sin recurrir a los nombres:
# Leemos datos del INE de salario medio por CCAA. El nombre viene precedido de su
# código oficial ("01 Andalucía"): separamos el código y el nombre en dos columnas
salarios_ccaa <- read.px('https://ctim.es/AEDV/data/INE.SUELDOS.COMUNIDADES.px') |>
as_tibble() |>
filter(Tipo.de.jornada == "Total", Decil == "Total decil", Periodo == 2022) |>
mutate(codigo = substr(Comunidades.y.Ciudades.Autonómas, 1, 2),
ccaa = substr(Comunidades.y.Ciudades.Autonómas, 4, 100)) |>
select(codigo, ccaa, salario = value)
salarios_ccaa |>
print(n = 3)## # A tibble: 20 × 3
## codigo ccaa salario
## <chr> <chr> <dbl>
## 1 To al Nacional 2128.
## 2 01 Andalucía 1915.
## 3 02 Aragón 2030.
## # ℹ 17 more rows
# Leemos el geojson de las CCAA y le unimos el salario por el código oficial
geoj_ccaa <- aedv_leer_geojson("https://ctim.es/AEDV/data/ccaa.geojson") |>
left_join(salarios_ccaa |> select(codigo, salario),
by = c("codauto" = "codigo"))
# Comunidades sin salario asignado
sum(is.na(geoj_ccaa$salario))## [1] 0
etiquetas <- paste("<strong>", geoj_ccaa$ccaa.shortname.es,
"</strong><br>Salario medio: ", round(geoj_ccaa$salario), "€") |>
lapply(htmltools::HTML)
pal_ccaa <- colorNumeric("YlOrRd", geoj_ccaa$salario) # escala lineal, no por cuantiles
geoj_ccaa |>
leaflet() |>
setView(lng = -3.7, lat = 40.0, zoom = 5) |> # vista inicial centrada en España
addPolygons(
fillColor = ~pal_ccaa(salario), # color según el salario
weight = 1, color = "grey50", fillOpacity = 0.7,
highlightOptions = highlightOptions(
weight = 3, color = "black", fillOpacity = 0.85, bringToFront = TRUE # resalte
),
label = etiquetas
) |>
addLegend("bottomright", pal = pal_ccaa, values = ~salario, # leyenda de colores
title = "Salario medio (€)", opacity = 0.8)Figure 6.28: Mapa coroplético del salario medio bruto mensual por comunidades autónomas en España (2022)
Nótese que aquí la escala de color es lineal (colorNumeric) y no por cuantiles: los salarios medios de las comunidades se mueven en un rango estrecho, sin valores extremos que aplasten la escala. La elección entre una escala lineal y una por cuantiles depende, por tanto, de cómo se repartan los datos —conviene mirar antes su histograma—: el PIB por habitante del mapa anterior está tan concentrado en los valores bajos que una escala lineal dejaría casi todos los países del mismo tono.
6.14 Gráficos animados
Los gráficos animados son una herramienta muy potente de visualización que permite ver cómo evolucionan los datos en un gráfico. La librería plotly permite hacer animaciones interactivas. Veremos a continuación un diagrama de barras animado sobre la población, a lo largo de los años, por sexos y nacionalidad en las comunidades autónomas en España usando datos del INE:
INE.poblacion <- readRDS(url("http://ctim.es/AEDV/data/INE.poblacion.rds")) |> # datos del INE
as_tibble()INE.poblacion |>
filter(Sexo!="Total",Nacionalidad!="Total",Comunidad!="Total Nacional") |> # sin totales
mutate(`Nacionalidad/Sexo`=paste0(Nacionalidad,"/",Sexo)) |> # atributo combinado
mutate(Comunidad=factor(Comunidad,levels=unique(Comunidad))) |> # orden fijo
plot_ly(
x = ~value,
y = ~Comunidad,
color = ~`Nacionalidad/Sexo`,
frame = ~Periodo, # variable de animación
type = "bar",
# con 19 comunidades x 4 barras agrupadas por cada una, la altura por
# omisión (480 px) deja cada barra en unos pocos píxeles: se amplía aquí,
# en plot_ly() y no en layout(), porque solo así crece también el
# contenedor HTML del widget -si no, con layout(height=...) el eje x y
# los controles de la animación quedaban recortados por debajo de los
# 480 px del contenedor, que no había cambiado de tamaño-
height = 900
) |>
layout(barmode = "group", bargap = 0.15) # barras agrupadas, no apiladasFigure 6.29: Gráfico de barras animado con la evolución de la población en las CCAA
Hay librerías específicas como gganimate para hacer gráficos animados. Veremos cómo usarla para ver cómo evoluciona el porcentaje que representa la población extranjera sobre el total de la población en España a lo largo de los años por sexos. Esta librería genera una imagen animada en formato GIF que inserta en el documento. Tiene la ventaja de que resulta muy sencillo de usar a partir de ggplot, pero no puede hacer cosas más complejas como añadir un slider para interactuar con la animación.
p <-INE.poblacion |>
filter(Comunidad=="Total Nacional",Nacionalidad!="Total") |> # total nacional, sin fila "Total"
group_by(Periodo,Sexo) |> # % relativo a cada año y sexo
mutate(Porcentaje.Población.Extranjera =100*value/sum(value)) |>
ungroup() |>
filter(Nacionalidad=="Extranjera") |> # nos quedamos con el % de extranjeros
ggplot(aes(x=Periodo,y=Porcentaje.Población.Extranjera,colour=Sexo)) + # color por sexo
geom_line() + # línea
scale_colour_brewer(palette = "Set1") + # paleta cualitativa
labs(x="") +
transition_reveal(Periodo) # anima revelando año a año
animate(p,fps = 10,end_pause = 40)
Figure 6.30: Gráfico de líneas animado con la evolución del porcentaje de población extranjera en España por sexos
Otras transiciones de gganimate
Además de transition_reveal (que revela la serie progresivamente), gganimate ofrece otras transiciones útiles:
transition_states(variable): transiciona entre diferentes valores de una variable categórica, útil para comparar distribuciones por categoría.shadow_mark(): deja un rastro semitransparente de los estados anteriores, lo que permite ver la evolución acumulada.
Veamos un ejemplo con transition_states y shadow_mark donde comparamos la distribución del PIB per cápita por continentes:
p <- owid_country |>
filter(!is.na(gdp_per_capit)) |> # sin NA
ggplot(aes(x = gdp_per_capit, fill = continent)) + # color por continente
geom_histogram(bins = 20, alpha = 0.7, show.legend = FALSE) + # histograma
scale_x_continuous(transform = 'log2') + # eje x logarítmico
labs(title = "Continente: {closest_state}", x = "PIB per cápita (log)", y = "Nº países") + # título dinámico
transition_states(continent, transition_length = 2, state_length = 3) + # un estado por continente
shadow_mark(alpha = 0.15) # rastro de los estados anteriores
animate(p, fps = 10, end_pause = 30) # generamos el GIF
Figure 6.31: Animación con transition_states y shadow_mark mostrando la distribución del PIB per cápita por continentes
A continuación, volviendo a plotly, presentamos el uso más conocido de la animación: un diagrama de dispersión donde cada punto se desplaza a lo largo de los años. Comparamos las emisiones de CO2 por habitante debidas al carbón y al petróleo en los países más poblados del mundo, con el tamaño del círculo proporcional a su población. Lo construimos con plot_ly nativo —indicando la columna de los fotogramas en frame—, en lugar de convertir un ggplot: frame no forma parte de la gramática de ggplot2, así que pasarlo dentro de aes() obliga a convivir con un warning de estética desconocida.
# Orden FIJO de los países (el mismo del filtro de abajo, por población): si
# "country" se deja como texto, plot_ly asigna el color por orden alfabético, que
# no es más arbitrario pero tampoco aporta nada. Fijarlo como factor deja claro
# de dónde sale el orden y asegura que cada país conserva su color aunque cambie
# el filtro.
paises_poblados <- c("China", "India", "United States", "Indonesia", "Pakistan",
"Nigeria", "Brazil", "Bangladesh", "Russia")
# Paleta de 9 colores muy separados entre sí en tono, luminosidad y saturación,
# elegida a propósito frente a la paleta cualitativa por defecto de plotly (o
# ColorBrewer "Set1"), que con 9 niveles deja colores casi idénticos. Con 9
# países en un diagrama de DISPERSIÓN -sin el orden fijo de una barra o una
# línea, cualquier punto puede acabar junto a cualquier otro en la pantalla- no
# existe ninguna combinación de 9 colores que sea distinguible para una persona
# con daltonismo en los 36 pares posibles: es un límite geométrico del círculo
# de tonos, no un fallo de esta paleta en concreto. Por eso el país de cada
# punto se apoya también en la leyenda y en el cuadro emergente (`text`), que lo
# identifican por su nombre y no solo por el color.
colores_paises <- c("#ff0081", "#bb0000", "#e57600", "#316000", "#00c560",
"#006d97", "#0097ff", "#5200e4", "#e42ef6")
read.xlsx("https://ctim.es/AEDV/data/owid-co2-data-resumen.xlsx") |> # emisiones de CO2
as_tibble() |>
filter(iso_code %in% c("CHN","IND","USA","IDN","PAK","NGA","BRA","BGD","RUS"), # países más poblados
year >= 1950) |>
# las emisiones vienen en millones de toneladas: las pasamos a toneladas por habitante
mutate(coal_co2_per_capita = coal_co2 * 1e6 / population,
oil_co2_per_capita = oil_co2 * 1e6 / population,
country = factor(country, levels = paises_poblados)) |> # color fijo por país
select(country,year,population,coal_co2_per_capita,oil_co2_per_capita) |>
arrange(year, country) |> # orden temporal para la animación
plot_ly(
x = ~coal_co2_per_capita,
y = ~oil_co2_per_capita,
color = ~country,
colors = colores_paises,
size = ~population, # tamaño = población
sizes = c(20, 800),
frame = ~year, # variable de animación
text = ~country,
hoverinfo = "text",
type = "scatter",
mode = "markers"
) |>
layout(xaxis = list(title = "CO2 por habitante debido al carbón (t)"), # títulos de los ejes
yaxis = list(title = "CO2 por habitante debido al petróleo (t)")) |>
animation_opts(frame = 200) # velocidad de la animaciónFigure 6.32: Gráfico de dispersión con las emisiones de CO2 per cápita debidas al carbón y al petróleo en los países más poblados del mundo
La animación cuenta una historia que una tabla no dejaría ver igual de rápido. En 1950 solo dos países se separan del origen, Estados Unidos y Rusia; Estados Unidos sube después en petróleo hasta los años setenta y a partir de ahí retrocede en carbón; China permanece pegada al origen durante décadas y desde el año 2000 asciende casi en vertical hasta adelantarlo en emisiones de carbón por habitante.
6.15 Visualización dinámica de tablas
Usando, por ejemplo, la librería DT, es posible visualizar tablas (que no sean demasiado grandes) de forma dinámica con múltiples opciones de configuración. A continuación presentamos un ejemplo con un cuadro de búsqueda, sliders para movernos por la tabla, podemos ordenar por cada columna y dejamos la primera columna fija como referencia.
owid_country |>
relocate(location) |> # location en primer lugar
datatable(
extensions = 'FixedColumns', # columnas fijas al hacer scroll
options = list(
scrollX = TRUE,
scrollY = "400px",
paging = FALSE,
fixedColumns = list(leftColumns = 2) # fijamos las 2 primeras columnas
)
)Formato condicional en tablas
La función formatStyle de DT permite colorear celdas en función de su valor, lo cual es muy útil en cuadros de mandos para resaltar indicadores que superan un umbral:
owid_country |>
select(location, continent, life_expectancy, gdp_per_capit, human_development_index) |> # columnas de interés
filter(!is.na(human_development_index)) |> # sin NA
datatable(options = list(pageLength = 10, scrollX = TRUE)) |>
formatStyle( # color de fondo por rango
'life_expectancy',
backgroundColor = styleInterval(c(65, 75), c('#FF6B6B', '#FFE66D', '#4ECDC4'))
) |>
formatStyle( # barra de progreso proporcional al valor
'human_development_index',
background = styleColorBar(range(owid_country$human_development_index, na.rm = TRUE), '#6C5CE7'),
backgroundSize = '95% 80%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center'
) |>
formatRound(columns = c('life_expectancy', 'gdp_per_capit', 'human_development_index'), digits = 2) # redondeoEn este ejemplo, la columna life_expectancy se colorea en rojo (<65), amarillo (65-75) o verde (>75), y la columna human_development_index muestra una barra de progreso proporcional al valor.
Referencias
[Ca] Cartography Vectors. Spain with regions (geometría de las comunidades autónomas; en octubre de 2026 el sitio no respondía).
[Hi21] Asha Hill. 9 Useful R Data Visualization Packages for Data Visualization, 2021.
[Ka20] Rob Kabacoff. Data Visualization with R, 2020.
[Leaflet] Leaflet for R.
[Open] OpenDataSoft
[Rc] R CODER. R CHARTS.
[Sthda] STHDA Statistical tools for high-throughput data analysis.
[Ho] Yan Holtz. The R Graph Gallery.
[DataNovia] Highchart Lessons. Datanovia.