Mapas interactivos con {mapgl} para visualizar datos geoespaciales en R

7/9/2026

mapas visualización de datos

{mapgl} es un paquete de R para visualizar datos geográficos de manera interactiva. Este paquete, desarrollado por Kyle Walker, implementa los visores de mapas interactivos Mapbox y MapLibre en R, para que puedas usarlos como lienzos para tus cartografías y datos geográficos.

En este tutorial aprenderemos a usar {mapgl}, y como ejemplo usaremos la cartografía Censal del Censo 2024 de Chile.

El primer paso es instalar {mapgl}:

install.packages("mapgl")

Datos censales

Descarga los datos cartográficos del Censo 2024 desde la página del INE, entrando a Cartografía Censal y luego descargando el archivo Cartografía País Censo 2024 (geoparquet).

Cargar cartografías censales

Los datos censales vienen en formato parquet, un formato moderno y eficiente para almacenar y consultar datos, y se cargan con la función read_parquet() del paquete {arrow}, o con open_dataset() si queremos abrir el archivo como base de datos.

Si no tenemos {arrow} instalado, lo instalamos con install.packages("arrow").

library(arrow)

manzanas <- open_dataset("Cartografia_censo2024_Pais/Cartografia_censo2024_Pais_Manzanas.parquet")

Cargamos los datos del Censo como base de datos, lo que significa que la información no está cargada aún en nuestro computador, sino que obtuvimos un esquema de los datos. Tenemos que hacer una consulta (query) a la base de datos para que Arrow obtenga los datos y los cargue al entorno de R. Filtraremos los datos con {dplyr} y luego los obtendremos con collect(). De esta manera sólo cargamos lo que necesitamos!

library(dplyr)

# desde base de datos local
manzanas <- censo |> 
  # filtrar región del país
  filter(COD_REGION == 8) |> 
  # seleccionar algunas variables interesantes
  select(SHAPE,
         CUT, COMUNA, LOCALIDAD,
         COD_MANZANA, 
         n_per, prom_edad, n_edad_60_mas, n_edad_0_5, 
         n_discapacidad, prom_escolaridad18, 
         n_desocupado, n_analfabet,
         n_transporte_bicicleta, n_jefatura_mujer,
         n_viv_hacinadas) |> 
  # copiar a la memoria
  collect()

Ahora sí, cargamos a nuestro entorno de R unos varios miles de filas de datos censales por manzana:

manzanas
# A tibble: 21,022 × 16
        SHAPE   CUT COMUNA LOCALIDAD COD_MANZANA n_per prom_edad n_edad_60_mas
   <arrw_bnr> <int> <chr>  <chr>           <int> <dbl>     <dbl>         <dbl>
 1    [2,422]  8203 CAÑETE HUILLINCO           1    31      41.1             7
 2    [7,734]  8203 CAÑETE HUILLINCO           2   107      36.5            17
 3    [3,894]  8203 CAÑETE HUILLINCO           3    63      47.5            24
 4    [2,758]  8203 CAÑETE HUILLINCO           4    10      52.9             4
 5    [1,894]  8203 CAÑETE CAYUCUPIL           1    77      40.7            17
 6    [1,510]  8203 CAÑETE CAYUCUPIL           5    55      41.7            12
 7      [246]  8203 CAÑETE CAYUCUPIL           7    28      41.3             8
 8      [822]  8203 CAÑETE CAYUCUPIL           9    18      31.4             2
 9      [294]  8203 CAÑETE CAYUCUPIL          10    16      41.9             5
10      [310]  8203 CAÑETE CAYUCUPIL          13     8      52.4             3
# ℹ 21,012 more rows
# ℹ 8 more variables: n_edad_0_5 <dbl>, n_discapacidad <dbl>,
#   prom_escolaridad18 <dbl>, n_desocupado <dbl>, n_analfabet <dbl>,
#   n_transporte_bicicleta <dbl>, n_jefatura_mujer <dbl>, n_viv_hacinadas <dbl>

El último paso es convertir la tabla de datos a una tabla de datos geográficos usando {sf}, para reconocer la columna SHAPE como la que contiene los polígonos de los datos censales:

library(sf)

manzanas <- manzanas |> 
  st_as_sf(crs = 4326)
Nota: para hacer más liviana esta página, se simplificaron los mapas

Los mapas originales del Censo son extremadamente detallados, por lo que fueron simplificados para hacer qu esta página se cargue más rápido.

library(rmapshaper)

manzanas <- manzanas |> 
  ms_simplify(keep = 0.4,
              keep_shapes = TRUE)

Con esta configuración se retienen el 40% de todos los puntos del mapa, un cambio casi imperceptible. Para más información sobre este método, revisa este tutorial!

Ahora sí podemos visualizar los datos en mapas!

Visor de mapas básico

Para empezar, probemos con un mapa vacío, usando la función maplibre().

library(mapgl)

maplibre(
  center = c(-71.5, -33.0), 
  zoom = 4)

Indicamos unas coordenadas y un nivel de zoom. Prueba moviendo el mapa o acercándote! Guau! Esta función será la primera capa de nuestros mapas interactivos.

Visualizar polígonos en el mapa

Ahora usemos {mapgl} con los datos censales. Primero filtremos un poco los datos para hacer un mapa de ejemplo:

comuna <- manzanas |> 
  filter(COMUNA == "CONCEPCIÓN")

Ahora crearemos el mapa base con maplibre(), y le agregamos una capa de relleno con add_fill_layer():

mapa_poligono <- maplibre(
  center = c(-73.04, -36.82), 
  zoom = 11) |> 
  # capa con polígonos
  add_fill_layer(
    id = "comuna",
    source = comuna, # objeto sf
    fill_color = "#6E3998",
    fill_opacity = 0.6)

mapa_poligono

Obtenemos el mapa base con los polígonos encima, con un color de nuestra elección. Hazle zoom para ver el nivel de calidad!

Esto nos sirve para probar los polígonos, o visualizaciones simples.

Mapa con marcadores

Podemos agregar marcadores individuales a un mapa a partir de coordenadas (longitud y latitud). Para ello, creamos una tabla de datos con varios lugares y sus respectivas coordenadas de longitud y latitud:

# tabla de coordenadas
lugares <- tribble(
  ~lugar,       ~lon,     ~lat,   
  "Barriga",    -70.9251, -33.5304, 
  "Chada",      -70.6488, -33.9348, 
  "Mallarauco", -70.9608, -33.6180, 
  "Dormida",    -71.0096, -33.0618, 
  "Chacabuco",  -70.7065, -32.9595
) |> 
  # texto en html
  mutate(texto = paste("<b>Lugar:</b>", lugar))

También creamos una columna texto en la que agregaremos contenido HTML que aparecerá como popup cuando se aprieten los marcadores.

Convertimos esta tabla de coordenadas a una tabla de datos geográficos con st_as_sf() y la agregamos al mapa:

puntos <- lugares |> 
  st_as_sf(
    coords = c("lon", "lat"), 
    crs = 4326
  )

# agregar marcadores al mapa
maplibre(
  center = c(-70.65, -33.44),
  zoom = 7
) |>
  add_markers(
    data = puntos,
    color = "#6E3998",
    popup = "texto"
  )

Presiona los marcadores para ver su información! Puedes ver un ejemplo aplicado de un mapa {mapgl} con marcadores y popups con imágenes en mi blog personal.

Ver una alternativa para agregar un solo marcador

La primera sería agregar una única coordenada creando un punto con sf::st_point() a partir de los valores de longitud y latitud:

library(sf)

coordenadas <- c(-71.48135, -33.96850)

# convertir las coordenadas en datos geográficos
punto <- coordenadas |> 
  st_point() |> 
  st_sfc(crs = 4326)

Luego agregamos el punto como un marcador con add_markers():

# agregar al mapa
maplibre(
  center = coordenadas,
  zoom = 8,
) |>
  add_markers(
    data = punto,
    color = "#6E3998")

Mapa coroplético interactivo

Los mapas coropléticos o de coropletas rellenan cada polígono del mapa con un color de acuerdo al valor de una variable. Es quizás la forma más básica e intuitiva de visualizar datos que se corresponden con polígonos.

Filtremos un poco los datos para hacer un mapa de ejemplo:

comuna <- manzanas |> 
  filter(COMUNA == "TALCAHUANO")

Para visualizar un mapa coloreando los polígonos según una variable en {mapgl}, primero tenemos que crear una escala. Esta escala nos permitirá definir la variable que queremos visualizar, los cortes en los datos, y los colores.

Creamos la escala con interpolate_palette(), indicando la columna que nos interesa. Elegimos la variable n_per(cantidad de habitantes por manzana), junto al método para cortar la variable (en este caso, el método Jenks), la cantidad de cortes, y la paleta de colores.

escala <- interpolate_palette(
  data = comuna,
  column = "n_per",
  method = "jenks", 
  n = 5, 
  colors = c("#EAD1FA", "#9069C0", "#6E3998")
)

Una vez que tenemos la escala de colores, la usamos en el argumento fill_color como escala$expression para indicarle al mapa los cortes en los datos y los colores que les corresponden. Luego agregamos la leyenda correspondiente con add_continuous_legend().

# mapa base
mapa_poblacion <- maplibre(
  center = c(-73.10, -36.74),
  zoom = 11) |> 
  # capa de relleno
  add_fill_layer(
    id = "comuna",
    source = comuna, # objeto sf con geometrías
    fill_color = escala$expression, # desde escala
    fill_opacity = 0.8
  ) |> 
  # leyenda
  add_continuous_legend(
    legend_title = "Personas por manzana",
    values = escala$breaks,
    colors = escala$colors,
    position = "top-right",
    style = legend_style(
      border_radius = 6,
      padding = 8,
      background_opacity = 0.8,
      text_size = 10
    )
  )

mapa_poblacion

Como vemos, la leyenda adquiere sus características desde la escala que creamos antes, y además tiene varias opciones de personalización.

Agregar tooltips

Los tooltips permiten que el/la usuario/a pase el cursor sobre los polígonos o elementos del mapa para ver información relevante. Son clave para poder entregar información más detallada en mapas interactivos.

Podemos agregar tooltips a una capa de relleno de polígonos, como la que agregamos en el paso anterior, con el argumento tooltip de la función add_fill_layer(). Este argumento recibe el nombre de una variable, o bien, se pueden construir textos con la función concat(), y usando dentro HTML y la función get_column() para referirnos al valor del polígono en alguna variable.

En este ejemplo agregamos un tooltip que hace referencia a tres variables:

mapa_tooltip <- maplibre(
  center = c(-73.13, -36.715),
  zoom = 14) |> 
  # capa de relleno
  add_fill_layer(
    id = "comuna",
    source = comuna, 
    fill_color = escala$expression,
    fill_opacity = 0.8,
    # agregar tooltip
    tooltip = concat(
      "<div style='font-size: 120%;'>Personas por manzana</div>",
      "<b>Población: </b>", get_column("n_per"), " personas",
      "<br>",
      "<b>Edad promedio: </b>", get_column("prom_edad"), " años",
      "<br>",
      "<b>Escolaridad: </b>", get_column("prom_escolaridad18")
    ),
    # personalizar estilo de tooltip
    tooltip_style = tooltip_style(
      max_width = 200,
      padding = 8,
      background_color = "#FFFFFF",
      background_opacity = .9,
    )
  )

mapa_tooltip

Pasa el cursor sobre el mapa para ver el tooltip!

En el argumento tooltip_style podemos personalizar la ventanita, pero si necesitamos algo más personalizado siempre podemos usar CSS.

Mapa interactivo con puntos o círculos

Si tus datos geográficos representan coordenadas o lugares, pero además necesitas expresar o visualizar otras variables asociadas a esos lugares, las coordenadas pueden expresarse como puntos, donde su tamaño y color puede variar de acuerdo a los datos.

Transformemos los datos censales para expresar la desocupación por medio de puntos. Recordemos que los datos del censo vienen por manzana, así que agregaremos la información a un nivel geográfico superior: las localidades. Así serán menos puntos, y cada uno representará un territorio más extenso: cada punto representará una suma de varias manzanas.

Agruparemos los datos por comuna y localidad con group_by(), luego haremos un resumen de datos con summarize() donde sumaremos las variables y uniremos las manzanas con st_union(), de manera que todas las manzanas que estaban dentro de una misma localidad quedarán unidas. Luego, calcularemos el centroide de los polígonos con st_centroid() para obtener los puntos, de manera que un punto resuma los datos de todas las manzanas de la misma localidad.

# agrupar y unir manzanas
localidades <- manzanas |> 
  group_by(CUT, COMUNA, LOCALIDAD) |> 
  summarize(n_desocupado = sum(n_desocupado),
            n_per = sum(n_per),
            SHAPE = st_union(SHAPE))

# calcular porcentaje de desocupación
localidades_b <- localidades |> 
  mutate(p_desocupado = n_desocupado / n_per) |> 
  mutate(p_desocupado = round(p_desocupado * 100, 1)) |> 
  filter(p_desocupado > 1)

# convertir a puntos
puntos <- localidades_b |> 
  mutate(SHAPE = st_centroid(SHAPE)) # calcular centroide

puntos
Simple feature collection with 94 features and 6 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -73.65098 ymin: -38.33939 xmax: -71.6376 ymax: -36.52825
Geodetic CRS:  WGS 84
# A tibble: 94 × 7
# Groups:   CUT, COMUNA [33]
     CUT COMUNA      LOCALIDAD     n_desocupado  n_per                 SHAPE
 * <int> <chr>       <chr>                <dbl>  <dbl>           <POINT [°]>
 1  8101 CONCEPCIÓN  CHAIMÁVIDA              20    612 (-72.87485 -36.85856)
 2  8101 CONCEPCIÓN  GRAN CONCEPC…         8960 223525  (-73.0355 -36.81551)
 3  8101 CONCEPCIÓN  LAS ULLOAS              20    613 (-72.95516 -36.80136)
 4  8101 CONCEPCIÓN  LOS BATROS              23    448 (-72.94263 -36.81627)
 5  8101 CONCEPCIÓN  PUENTE CINCO            17    398 (-72.85963 -36.81696)
 6  8102 CORONEL     CORONEL               7197 119270   (-73.1554 -36.9911)
 7  8103 CHIGUAYANTE GRAN CONCEPC…         3708  85668 (-73.02556 -36.91317)
 8  8104 FLORIDA     COPIULEMU               51    697 (-72.80623 -36.89688)
 9  8104 FLORIDA     FLORIDA                233   4719  (-72.6574 -36.82237)
10  8105 HUALQUI     AGUA DEL OBI…           15    418  (-72.9568 -36.96096)
# ℹ 84 more rows
# ℹ 1 more variable: p_desocupado <dbl>

El resultado es una tabla con menos observaciones, porque ahora cada fila representa a una localidad, y en vez de contener polígonos, el dato espacial se presenta como puntos.

Al igual que con el mapa de coropletas, primero creamos la paleta de colores a partir de una de las variables; en este caso n_desocupado para ver la desocupación:

escala <- interpolate_palette(
  data = puntos,
  column = "n_desocupado",
  colors = c("#EAD1FA", "#9069C0", "#6E3998")
)

Algo común al visualizar puntos o círculos es hacer que el radio o tamaño de cada círculo dependa de una variable numérica. Usamos la función interpolate() para hacer que los valores de una variable, en este caso n_desocupado, se expresen en distintos tamaños de los círculos:

tamaños <- interpolate(
  column = "n_desocupado",
  values = range(puntos$n_desocupado), # rango de la variable
  stops = c(10, 26) # tamaños de los círculos
)

Luego creamos el mapa base centrado en una ubicación, con la tabla de datos geográficos, usando la paleta de colores (escala) en circle_color, y aplicamos la escala de tamaños (tamaños) en el argmento circle_radius. Además aprovechamos de agregar tooltips al mapa.

# agregar marcadores al mapa
maplibre(
  center = c(-73.03, -36.81),
  zoom = 10
) |>
  add_circle_layer(
    id = "puntos",
    source = puntos,
    # escala de colores
    circle_color = escala$expression,
    circle_opacity = 0.8,
    # escala de tamaños
    circle_radius = tamaños,
    circle_stroke_color = "#FFFFFF",
    circle_stroke_width = 1,
    # contenido del tooltip
    tooltip = concat(
      "<b>Comuna: ", get_column("COMUNA"), "</b>",
      "<br>",
      "<b>Localidad: ", get_column("LOCALIDAD"), "</b>",
      "<br>",
      get_column("p_desocupado"), "% de desocupación"
    ),
    # personalizar estilo de tooltip
    tooltip_style = tooltip_style(
      max_width = 200,
      padding = 8,
      background_color = "#FFFFFF",
      background_opacity = .9,
    )
  )

Creamos un mapa interactivo donde cada unidad de información se representa mediante un círculo, y su color y tamaños dependen de una variable. Pasa el cursor sobre los círculos para ver la información!

Mapa con puntos agrupados

¿Qué pasa si queremos visualizar cientos o miles de puntos o coordenadas? Cuando tenemos demasiados puntos, la visualización puede complicarse. En este caso puede usarse el argumento cluster_options de la función add_circle_layer() para configurar que los puntos se agrupen cuando están demasiado cerca. El resultado será un mapa que presenta un resumen de la información y va revelando más puntos a medida que nos acercamos al mapa.

Primero filtremos una comuna del conjunto de datos:

comuna <- manzanas |> 
  filter(COMUNA == "CONCEPCIÓN")

Ahora calculemos una variable para visualizar; en este caso, calcularemos el porcentaje de personas en situación de discapacidad (n_discapacidad) por manzana, y dejaremos aquellas donde haya más de un 20% de la población. Finalmente, convertimos los polígonos a puntos con st_centroid(), como en el paso anterior.

puntos <- comuna |> 
  mutate(p_discapacidad = n_discapacidad/n_per) |> 
  filter(p_discapacidad > 0.2) |> 
  mutate(p_discapacidad = round(p_discapacidad * 100, 1)) |> 
  # convertir a puntos
  mutate(SHAPE = st_centroid(SHAPE))

Ahora creamos la escala de colores con interpolate_palette(), para que los tres colores de la paleta se correspondan con los valores de la variable n_discapacidad:

escala <- interpolate_palette(
  data = puntos,
  column = "n_discapacidad",
  colors = c("#E6C7FA", "#9069C0", "#6E3998")
)

Ahora creamos el mapa con maplibre() y add_circle_layer(), donde especificamos los datos (puntos), la escala de colores en el argumento circle_color, y ahora configuramos el argumento cluster_options con cluster_options(), donde tenemos amplio margen para experimentar definiendo los puntos de corte de los conteos de datos, los tamaños de los puntos de agrupación, los colores, y el tamaño del radio de agrupación de los puntos (cluster_radius). También, el argumento max_zoom nos permite poner un punto de corte para desagrupar todos los puntos a cierto nivel de zoom en el mapa.

maplibre(
  center = c(-72.98, -36.81),
  zoom = 10
) |>
  add_circle_layer(
    id = "puntos",
    source = puntos,
    # escala de colores
    circle_color = escala$expression,
    circle_opacity = 0.9,
    circle_radius = 6,
    circle_stroke_color = "#FFFFFF",
    circle_stroke_width = 1,
    # opciones de agrupación de puntos
    cluster_options = cluster_options(
      circle_opacity = 0.9,
      count_stops = c(10, 20, 30, 100),
      radius_stops = c(12, 22, 30, 40),
      color_stops = c("#BB98DD", "#9069C0", "#7F51AC", "#6E3998"),
      cluster_radius = 60,
      max_zoom = 12,
      text_color = "#FFFFFF",
      circle_stroke_color = "#FFFFFF",
      circle_stroke_width = 1.2,
    ),
    # contenido del tooltip
    tooltip = concat(
      get_column("n_per"), " personas en total",
      "<br>",
      get_column("n_discapacidad"), " personas en situación de discapacidad, ",
      get_column("p_discapacidad"), "% del total"
    ),
    # estilo de tooltip
    tooltip_style = tooltip_style(
      max_width = 200,
      padding = 8,
      background_color = "#FFFFFF",
      background_opacity = .8,
    )
  )

Acércate al mapa o presiona sobre cada punto para ir acercándote. El mapa irá revelando más detalle a medida que te acercas!

Personalización

Aquí dejaré más alternativas que ofrece el paquete {maplibre}.

Mapa en modo oscuro

Los mapas maplibre() tienen tres temas posibles: "voyager", "positron" y "dark-matter", este último siendo un mapa en modo oscuro.

maplibre(
style = carto_style("dark-matter")
)

Desactivar atribución

Para ocultar el panel blanco inferior que indica las atribuciones a MapLibre y OpenStreetMap, agrega el argumento attributionControl = FALSE a la capa base del mapa, así:

maplibre(
attributionControl = FALSE
) 

Esto da como resultado un mapa más limpio, pero recuerda que es importante dar créditos a quienes están facilitándote estas herramientas gratuitas, así que considera dejar la atirbución activada!

Fecha de publicación:
7/9/2026
Extensión:
14 minute read, 2855 words
Categorías:
Tutoriales
Tags:
mapas visualización de datos
Ver también:
Optimizando la visualización de datos geográficos complejos con `{pmtiles}`
Probando visores de mapas interactivos en R con `{mapgl}`
Mapas bivariados de adultos mayores en relación a viviendas propias en las comunas de Chile