Mapas Interativos com Leaflet e R

Neste post mostro como fazer mapas interativos usando o pacote leaflet no R. O pacote é bastante flexível na construção de mapas, permitindo muitas opções de customização. Mostro como fazer mapas simples de pontos e também como fazer mapas coropléticos.
mapas
data-visualization
tutorial-R
data-science
leaflet
demografia
Author

Vinicius Oike

Published

March 25, 2024

Mapas Interativos

Mapas interativos permitem uma visualização mais interessante e exploratória dos dados. Nos últimos tempos, este tipo de visualização se tornou mais popular por permitir que usuário encontre os dados que procura de maneira mais simples e intuitiva. Neste post vou mostrar como fazer mapas interativos usando o pacote leaflet no R.

Leaflet

Leaflet é uma biblioteca de JavaScript feita para produzir mapas interativos de qualidade. O pacote leaflet, do R, é uma interface que permite produzir estes mapas diretamente dentro do R.

As funções do leaflet somam elementos e camadas em um mapa inicial usando o operador pipe; o nome das funções segue o padrão camel case (i.e. nomeDaFuncao). A documentação das funções pode ser encontrada aqui.

De maneira geral, o leaflet é bastante flexível na construção de mapas, permitindo muitas opções de customização. Como de costume, esta flexibilidade vem com um custo: mesmo mapas relativamente simples exigem uma quantidade considerável de código. Neste sentido, pode-se considerar pacotes alternativos como tmap e mapview.

Básico

# Pacotes necessários para este tutorial
library(leaflet)
library(sf)
library(dplyr)
library(tidyr)
library(stringr)
library(sidrar)
# Pactotes que utilizamos apenas uma função
# library(janitor)
# library(mapview)
# library(sidrar)
# library(MetBrewer)

Criar um mapa iterativo no leaflet é bastante simples. O código abaixo cria um mapa de São Paulo. Note que o mapa é construindo como uma composição de funções usando o operador pipe. Assim, a construção de mapas no leaflet fica similar ao ggplot2 onde soma-se várias funções a uma mesma visualização. O pacote leaflet carrega automaticamente o pipe do magritrr, mas, naturalmente, é possível usar o pipe |> nativo do R. Para mais informações sobre o operador pipe no R vale consultar meu post sobre o assunto.

A função setView define o ponto central do mapa e o nível do zoom. Defino o ponto central como as coordenadas do Museu de Arte de São Paulo (MASP).

m <- leaflet() %>%
  addTiles() %>%
  setView(lng = -46.655837, lat = -23.561387, zoom = 12)

m

Pode-se trocar o basemap do mapa alterando o provider.

m %>%
  addProviderTiles(provider = "CartoDB")

Para adicionar “shapes” ao mapa usa-se as funções add* como addMarkers.

pontos <- tibble(
  lng = -46.655837, lat = -23.561387
)

pontos <- st_as_sf(pontos, coords = c("lng", "lat"), crs = 4326)

m %>%
  addMarkers(data = pontos) %>%
  addProviderTiles("CartoDB")

Markers

O leaflet tem algumas opções para mapear pontos. A mais simples delas é a addMarkers, vista acima. No mapa abaixo, mostro todos os Starbucks de São Paulo. Os dados provêm de um webscrape, que fiz em outro post.

sp_starbucks <- dplyr::filter(starbucks, code_muni == 3550308)
  
m %>%
  addMarkers(data = sp_starbucks, label = ~as.character(name)) %>%
  addProviderTiles("CartoDB")

Quando temos muitos pontos podemos agregá-los usando markerClusterOptions().

m %>%
  addMarkers(
    data = sp_starbucks,
    label = ~as.character(name),
    clusterOptions = markerClusterOptions()) %>%
  addProviderTiles("CartoDB")

É possível fazer outros tipos de marcadores e inclusive usar cores para representar diferentes valores ou categorias. Comparado com outras alternativas, como tmap ou mapview, contudo, o leaflet é um pouco mais trabalhoso. O código abaixo categoriza as unidades do Starbucks em 4 tipos distintos:

  1. Shopping - se a unidade estiver dentro de um shopping.
  2. Aeroporto - se a unidade estiver dentro de um aeroporto.
  3. B2B - se a unidade estiver inserida dentro de outro negócio (e.g. hospital, torre corporativa, etc.)
  4. On-street - se a unidade for uma loja de rua.

Eu mapeio uma cor distinta para cada uma das categorias. Como se vê, é necessário criar um objeto que mapeia os valores para as cores (usando colorFactor). Vale notar que esta função aceita paletas de cores do RColorBrewer. A legenda de cores não é adicionada automaticamente e é preciso configurá-la também.

sp_starbucks <- sp_starbucks %>%
  mutate(
    type = case_when(
      str_detect(name, "Shopping|shopping") ~ "Shopping",
      str_detect(name, "Aeroporto") ~ "Aeroporto",
      str_detect(name, "Hospital|Corporate") ~ "B2B",
      TRUE ~ "On-street"
    )
  )

pal <- colorFactor("Set1", domain = unique(sp_starbucks$type))

m %>%
  addCircleMarkers(
    data = sp_starbucks,
    color = ~pal(type),
    stroke = FALSE,
    fillOpacity = 0.5,
    label = ~as.character(name)) %>%
  addProviderTiles(provider = "CartoDB") %>%
  addLegend(
    position = "bottomright",
    pal = pal,
    values = unique(sp_starbucks$type))

A título de exemplo, note como é possível chegar numa mapa muito similar usando somente uma linha de código.

mapview::mapview(sp_starbucks, zcol = "type")

O exemplo acima ilustra bem o trade-off do leaflet: a flexibilidade do pacote vem ao custo de maior complexidade.

Choropleths

Mapas coropléticos mostram a distribuição espacial de uma variável num mapa, usando cores para respresentar diferentes valores de uma variável. Montar mapas coropléticos no leaflet é um tanto trabalhoso. Em geral, é necessário fornecer as cores, legendas e valores manualmente. Isto ficará mais claro nos exemplos abaixo.

Domicílios por tipo de Ocupação

Dados

Para este exemplo vamos importar os dados da PNADC/A sobre condição de ocupação dos imóveis. Importo os dados usando o pacote sidrar. Para mais informações sobre como usar o pacote veja o meu post sobre o assunto.

Queremos montar um mapa que mostre o percentual de domicílio alugados em cada estado. Além disso, o mapa deve mostrar os outros tipos de ocupação (próprio, etc.)

pnad <- sidrar::get_sidra(
  6821,
  period = "2022",
  variable = 162,
  geo = "State"
)

Após importar os dados, precisamos fazer uma limpeza básica e agregar um pouco as categorias do IBGE.

tab_houses <- pnad |> 
  as_tibble() |> 
  janitor::clean_names() |> 
  select(
    code_state = unidade_da_federacao_codigo,
    prop_type = condicao_de_ocupacao_do_domicilio,
    count = valor
    )

tab_houses <- tab_houses |> 
  filter(prop_type != "Total") |> 
  mutate(
    prop_type_grouped = case_when(
      str_detect(prop_type, "Próprio") ~ "Owned",
      prop_type == "Alugado" ~ "Rented",
      TRUE ~ "Other"
    )
  ) |> 
  summarise(
    total = sum(count, na.rm = TRUE),
    .by = c("code_state", "prop_type_grouped")
    ) |> 
  mutate(share = total / sum(total) * 100, .by = "code_state")

Por fim, precisamos transformar os dados em “wide” para fazer o join com o shapefile.

houses_prop <- tab_houses |> 
  pivot_wider(
    id_cols = "code_state",
    names_from = "prop_type_grouped",
    values_from = "share"
  ) |> 
  mutate(code_state = as.numeric(code_state))

houses_prop
# A tibble: 27 × 4
   code_state Owned Rented Other
        <dbl> <dbl>  <dbl> <dbl>
 1         11  65.1   22.2 12.8 
 2         12  78.0   13.8  8.16
 3         13  78.1   14.5  7.43
 4         14  64.6   23.4 12   
 5         15  78.4   12.8  8.82
 6         16  80     12.7  7.35
 7         17  65.7   23.3 11.0 
 8         21  81.7   11.5  6.86
 9         22  81.7   10.3  8.06
10         23  71.3   19.4  9.34
# ℹ 17 more rows

Shape das UFs

Conseguir o shape dos estados é bastante simples, usando o pacote geobr. O leaflet é feito para funcionar com a projeção WGS84. Na prática, é possível usar outras (como a 4674, por exemplo), mas a função vai sempre retornar um aviso: Need '+proj=longlat +datum=WGS84'.

Após importar o shape, basta juntar os dados com o shapefile usando left_join.

# Importa o shapefile de todos os estados do Brasil
state_border <- geobr::read_state(showProgress = FALSE)
state_border <- sf::st_transform(state_border, crs = 4326)
# Junta o shapefile com os dados da tabela
state_prop <- left_join(state_border, houses_prop, by = "code_state")

O mapa

O código abaixo mostra como fazer a primeira versão do mapa. Este mapa é quase equivalente a um mapa estático: mostra a proporção domicílios alugados numa escala de cor simples. A função colorNumeric serve para mapear os valores numéricos na coluna Rented em cores. O primeiro argumento pode ser tanto um vetor com cores, como o nome de uma paleta; felizmente, as paletas do ColorBrewer são aceitas.

pal = colorNumeric("Greens", domain = c(min(state_prop$Rented), max(state_prop$Rented)))

leaflet(state_prop) %>%
  addTiles() %>%
  addPolygons(
    weight = 2,
    color = "white",
    fillColor = ~pal(Rented),
    fillOpacity = 0.9
    ) %>%
  addLegend(
    pal = pal,
    values = ~Rented,
    title = "Rented (%)",
    position = "bottomright"
  ) %>%
  addProviderTiles("CartoDB")