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

> {mapgl} es un paquete de R increíble para visualizar datos geográficos de manera interactiva, por medio de los visores de mapas Mapbox y MapLibre, sobre los cuales puedes visualizar tus cartografías y datos geográficos, con un alto nivel de personalización y muchas características avanzadas.

- URL: https://bastianolea.rbind.io/blog/mapas_mapgl/
- Fecha: 2026-09-07
- Autor: Bastián Olea Herrera
- Etiquetas: mapas, visualización de datos
- Recursos: [mapgl](https://walker-data.com/mapgl/)

---

[`{mapgl}` es un paquete de R](https://walker-data.com/mapgl/) para visualizar datos geográficos de manera interactiva. Este paquete, desarrollado por [Kyle Walker](https://walker-data.com), implementa los visores de mapas interactivos [Mapbox](https://www.mapbox.com) y [MapLibre](https://maplibre.org) 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}`](https://walker-data.com/mapgl/):

``` r
install.packages("mapgl")
```

## Datos censales

[Descarga los datos cartográficos del Censo 2024 desde la página del INE](https://censo2024.ine.gob.cl/resultados/), entrando a *Cartografía Censal* y luego descargando el archivo **Cartografía País Censo 2024 (geoparquet)**.

[Descargar datos nivel manzana](https://storage.googleapis.com/bktdescargascenso2024/Cartografia/GEOPARQUET/Cartografia_censo2024_Pais.zip)

### 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")`.

``` r
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!

``` r
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:

``` r
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}`](https://bastianolea.rbind.io/blog/mapas_sf/), para reconocer la columna `SHAPE` como la que contiene los polígonos de los datos censales:

``` r
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](https://bastianolea.rbind.io/blog/simplificar_mapas/) para hacer que esta página se cargue más rápido.

``` r
library(rmapshaper)

manzanas <- manzanas |> 
  ms_simplify(keep = 0.3,
              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!](https://bastianolea.rbind.io/blog/simplificar_mapas/)

Ahora sí podemos visualizar los datos en mapas!

**Publicación relacionada:** [Visualiza datos del Censo 2024 en mapas a nivel de manzana con R](https://bastianolea.rbind.io/blog/mapas_censo_2024/index.md): Recientemente lanzaron los datos 2024 del Censo de Población y Vivienda de Chile, incluyendo más de 200 variables a nivel de manzana. En este tutorial veremos dos formas de visualizar datos censales a…

## Visor de mapas básico

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

``` r
library(mapgl)

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

_[Visualización interactiva: disponible en la versión web de esta publicación]_

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:

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

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

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

mapa_poligono
```

_[Visualización interactiva: disponible en la versión web de esta publicación]_

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:

``` r
# 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](https://bastianolea.rbind.io/blog/mapas_sf/#poner-un-punto-en-un-mapa) con `st_as_sf()` y la agregamos al mapa:

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

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

_[Visualización interactiva: disponible en la versión web de esta publicación]_

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.](https://bastimapache.cl/posts/paisajes/)

**Ver una alternativa para agregar un solo marcador**

La primera sería agregar una única coordenada [creando un punto](https://bastianolea.rbind.io/blog/mapas_sf/#poner-un-punto-en-un-mapa) con `sf::st_point()` a partir de los valores de longitud y latitud:

``` r
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()`:

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

_[Visualización interactiva: disponible en la versión web de esta publicación]_

## 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:

``` r
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](https://bastianolea.rbind.io/blog/2026-08-26/)), la cantidad de cortes, y la [paleta de colores.](https://bastianolea.rbind.io/blog/colores/#crear-paletas-de-colores)

``` r
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()`.

``` r
# mapa base
mapa_poblacion <- maplibre(
  center = c(-73.10, -36.74),
  zoom = 11, minZoom = 10) |> 
  # 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
```

_[Visualización interactiva: disponible en la versión web de esta publicación]_

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:

``` r
mapa_tooltip <- maplibre(
  center = c(-73.13, -36.715),
  zoom = 14, minZoom = 10) |> 
  # 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
```

_[Visualización interactiva: disponible en la versión web de esta publicación]_

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 categórico

También podemos visualizar en el mapa no son numéricos, sino categóricos, donde tenemos varios niveles discretos que queremos expresar como colores sobre los puntos o polígonos.

Primero cambiemos un poco los datos para [unir los polígonos](https://bastianolea.rbind.io/blog/mapas_sf/#unir-polígonos) de las manzanas por localidades, y así obtener 6 polígonos:

``` r
localidades <- manzanas |> 
  filter(COMUNA == "TOMÉ") |>
  # unir por localidades
  group_by(LOCALIDAD) |>
  summarize(SHAPE = st_union(SHAPE)) |> 
  # agrandar para borrar calles
  mutate(SHAPE = st_buffer(SHAPE, 15,
                           max_cells = 100000
                           ))

localidades
```

    Simple feature collection with 6 features and 1 field
    Geometry type: MULTIPOLYGON
    Dimension:     XY
    Bounding box:  xmin: -72.9804 ymin: -36.67717 xmax: -72.78573 ymax: -36.52311
    Geodetic CRS:  WGS 84
    # A tibble: 6 × 2
      LOCALIDAD                                                                SHAPE
    * <chr>                                                       <MULTIPOLYGON [°]>
    1 DICHATO        (((-72.95045 -36.54792, -72.95045 -36.54793, -72.95045 -36.547…
    2 LOS MORROS     (((-72.96276 -36.53283, -72.96276 -36.53283, -72.96276 -36.532…
    3 MENQUE         (((-72.8701 -36.55405, -72.8701 -36.55405, -72.8701 -36.55405,…
    4 PUNTA DE PARRA (((-72.94606 -36.65974, -72.94606 -36.65974, -72.94606 -36.659…
    5 RAFAEL         (((-72.79419 -36.62449, -72.79419 -36.62449, -72.79419 -36.624…
    6 TOMÉ           (((-72.94015 -36.60208, -72.94015 -36.60207, -72.94016 -36.602…

Para visualizar datos categóricos en mapas, tenemos que crear una escala de colores para cada categoría. Usaremos la variable `LOCALIDAD` para colorear los polígonos, así que podemos usar su largo con `length()` dentro de una [función para generar paletas de colores](https://bastianolea.rbind.io/blog/colores/#crear-paletas-cualitativas) para obtener 6 colores distintos:

``` r
categorias <- unique(localidades$LOCALIDAD)

library(colorspace)

# paleta categórica de arcoíris
paleta_categorica <- rainbow_hcl(
  n = length(categorias), # cantidad de colores
  c = 100, 
  start = 180,
  end = 360)
```

**Publicación relacionada:** [Colores y paletas de colores en R](https://bastianolea.rbind.io/blog/colores/index.md): El uso del color es clave para comunicar, y el ecosistema de R tiene varios trucos convenientes para ayudarnos a usar el color de mejores formas. En este post veremos consejos y trucos para trabajar…

Ahora, cuando creemos la capa de relleno con `add_fill_layer()` igual que antes, en el color de relleno usaremos `match_expr()`, función que requiere la variable a usar para aplicar el color (en este caso `LOCALIDAD`), los valores únicos de esta variable (`unique(localidades$LOCALIDAD)`) y finalmente los colores que creamos recién.

También agregamos una **leyenda** categórica con `add_categorical_legend()`, que funciona casi igual que `match_expr()` y `add_continuous_legend()`.

``` r
mapa_localidades <- maplibre(
  center = c(-72.88, -36.60),
  zoom = 10.5,
  minZoom = 8, maxZoom = 12) |> 
  # capa de figuras
  add_fill_layer(
    id = "localidades",
    source = localidades,
    # paleta categórica
    fill_color = match_expr(
      "LOCALIDAD", # variable a usar
      values = categorias, # valores únicos
      stops = paleta_categorica # colores para cada valor
    ),
    fill_opacity = 0.8
  ) |> 
  # leyenda categórica
  add_categorical_legend(
    legend_title = "Localidades",
    values = categorias, # valores únicos
    colors = paleta_categorica, # colores para cada valor
    position = "top-right",
    style = legend_style(background_opacity = .8)
  )

mapa_localidades
```

_[Visualización interactiva: disponible en la versión web de esta publicación]_

## 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](https://bastianolea.rbind.io/blog/r_introduccion/dplyr_summarize/) 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](https://bastianolea.rbind.io/blog/mapas_sf/#calcular-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.

``` r
# 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.03549 -36.81551)
     3  8101 CONCEPCIÓN  LAS ULLOAS              20    613 (-72.95516 -36.80136)
     4  8101 CONCEPCIÓN  LOS BATROS              23    448 (-72.94264 -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.

**Publicación relacionada:** [Mapas y visualización de datos geoespaciales en R con {sf}](https://bastianolea.rbind.io/blog/mapas_sf/index.md): R cuenta con un muy amplio ecosistema para trabajar con datos geoespaciales. Uno de los paquetes más importantes es {sf}, que permite manipular datos espaciales a partir del estándar simple features…

Al igual que con el [mapa de coropletas](#mapa-coroplético), primero creamos la paleta de **colores** a partir de una de las variables; en este caso `n_desocupado` para ver la desocupación:

``` r
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:

``` r
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](#visor-de-mapas-básico) 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](#agregar-tooltips) al mapa.

``` r
# agregar marcadores al mapa
maplibre(
  center = c(-73.03, -36.81),
  zoom = 10, minZoom = 7
) |>
  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,
    )
  )
```

_[Visualización interactiva: disponible en la versión web de esta publicación]_

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:

``` r
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.

``` r
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`:

``` r
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.

``` r
maplibre(
  center = c(-72.98, -36.81),
  zoom = 10, minZoom = 7
) |>
  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,
    )
  )
```

_[Visualización interactiva: disponible en la versión web de esta publicación]_

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!

> **Aviso:** Tutorial en construcción! Todavía estoy aprendiendo, pero pronto iré complementando con más información.

## 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.

``` r
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í:

``` r
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!

------------------------------------------------------------------------

## Recursos

- Este artículo contiene [muchos y variados ejemplos de uso de `mapgl` en R](https://bodhimcnally.com/resources/interactive-maps-in-r-with-mapgl/)
- [*Skill* para inteligencia artificial de `mapgl`](https://github.com/arthurgailes/r-package-skills/tree/master/skills/r-mapgl) para que tu modelo de IA sepa usar mejor el paquete. [Más información sobre las *skills* de IA aquí!](https://bastianolea.rbind.io/blog/r_ia/)

**Publicaciones sobre mapas:**

- [Plantilla de mapa interactivo de Chile con R, Shiny y MapGL](https://bastianolea.rbind.io/blog/2026-10-14/index.md)
- [Optimizando la visualización de datos geográficos complejos con {pmtiles}](https://bastianolea.rbind.io/blog/2026-08-03/index.md)
- [Probando visores de mapas interactivos en R con {mapgl}](https://bastianolea.rbind.io/blog/2026-07-29/index.md)
- [Mapas bivariados de adultos mayores en relación a viviendas propias en las comunas de Chile](https://bastianolea.rbind.io/blog/2026-07-24/index.md)
- [Crea mapas bivariados en R para visualizar la relación entre dos variables](https://bastianolea.rbind.io/blog/mapas_bivariados/index.md)
