# 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)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
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)
mPode-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:
- Shopping - se a unidade estiver dentro de um shopping.
- Aeroporto - se a unidade estiver dentro de um aeroporto.
- B2B - se a unidade estiver inserida dentro de outro negócio (e.g. hospital, torre corporativa, etc.)
- 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") Para adicionar labels, é preciso formatar o texto em html. Este é o texto que vai aparecer para o usuário quando o cursor do mouse estiver sobre o polígono. Além disso, para melhor formatar os números, uso as funções round e format.
labels <- sprintf(
"<strong>%s</strong><br/> Owned: %s <br/> Rented: %s <br/> Other: %s",
state_prop$name_state,
format(round(state_prop$Owned, 1), decimal.mark = ","),
format(round(state_prop$Rented, 1), decimal.mark = ","),
format(round(state_prop$Other, 1), decimal.mark = ",")
)
# Exemplifica o label
labels[1][1] "<strong>Rondônia</strong><br/> Owned: 65,1 <br/> Rented: 22,2 <br/> Other: 12,8"
# Converte para html
labels <- lapply(labels, htmltools::HTML)Além de acrescentar este “popup” de informação, também podemos dar um destaque para o estado que o usuário está analisando com highlightOptions.
leaflet(state_prop) %>%
addTiles() %>%
addPolygons(
weight = 2,
color = "white",
fillColor = ~pal(Rented),
fillOpacity = 0.9,
label = ~labels,
highlightOptions = highlightOptions(
color = "orange",
weight = 4,
bringToFront = TRUE)
) %>%
addLegend(
pal = pal,
values = ~Rented,
title = "Rented (%)",
position = "bottomright"
) %>%
addProviderTiles("CartoDB")