Skip to contents

Objective

By the end of this tutorial you will be able to assign a geometry to each health record — linking the time series built in the previous stage to Brazil’s official geographic units (municipalities, biomes, health regions, metropolitan regions, urban areas, indigenous lands, postal codes) with sus_spatial_join(), and to integrate gridded environmental data into the health pipeline with sus_grid_join().

The spatial join is the fifth stage of the climasus4r pipeline. It turns an “ordinary” table (health × date × municipal code) into an object of class sf with explicit geometry — a requirement for mapping, neighborhood computations and — critical to our driving case — merging with socioeconomic indicators (Tutorial 6) and gridded climate data (Tutorial 8).

Scientific context

Climate–health studies are, by nature, spatial: the exposure (temperature, pollution) and the outcome (deaths, hospitalizations) occur in concrete places. Linking a health record to its geographic unit allows mapping inequalities, controlling for contextual confounders, and defining the unit of analysis. This choice is not neutral: the Modifiable Areal Unit Problem (MAUP; Openshaw, 1984) shows that statistical results can change depending on the scale (municipality vs. census tract) and the partitioning (health regions vs. metropolitan regions) adopted.

In Brazil, official geometries come from IBGE and are accessed through the geobr package (Pereira et al., 2019). climasus4r wraps this access in sus_spatial_join(), adding geometry caching and code normalization — a sensitive point, since the IBGE municipal code has 7 digits (including a check digit), while the Health Information Systems usually record 6 digits. The function aligns the two representations automatically. The coordinate reference system adopted is SIRGAS 2000 (EPSG:4674), the official Brazilian standard, reprojected to WGS84 (EPSG:4326) when centroids are computed.

Geographer’s hat. Three concerns run through this stage: (i) the CRS (always use SIRGAS 2000 / WGS84 consistently); (ii) the difference between 6- and 7-digit IBGE codes (resolved by the function); and (iii) the MAUP — the scale and the delineation chosen affect inference, so document them.

Driving case. We are investigating temperature and outcomes in older adults (60+) in the São Paulo Metropolitan Region, 2014–2019. At this stage, we link the respiratory-death series (output of Tutorial 4) to the municipalities of the São Paulo Metropolitan Region (RMSP), obtaining the spatial object that Tutorial 6 (Socioeconomic Linkage) will use.

Data used

Input Source Role in the case
caso_serie.rds Tutorial 4 (series aggregated by municipality × date) Health input
Municipal geometries IBGE via geobr::read_municipality() RMSP polygons
Climate grid (demonstration) synthetic (real climate enters in T8) Illustrates sus_grid_join()

The input is the object dados/caso_serie.rds (loaded with an file.exists() guard; in its absence, the script uses a minimal synthetic example). The output is dados/caso_espacial.rds, which feeds Tutorial 6.

Full pipeline

The blocks below reproduce what the script scripts/tutorial_05_juncao_espacial.R executes. Start by loading the package:

library(climasus4r)

Load the health series (output of Tutorial 4)

# A etapa anterior salvou a série agregada (óbitos respiratórios × município × data).
caso_serie  <- readRDS("dados/caso_serie.rds")
serie_saude <- if (is.data.frame(caso_serie)) caso_serie else caso_serie$serie

sus_spatial_join() automatically detects the municipal code column (join_col = NULL), downloads the geometries from geobr (with caching) and returns an sf object at stage = "spatial". The default level is "munic":

caso_espacial <- sus_spatial_join(
  df        = serie_saude,
  level     = "munic",     # município (padrão)
  join_col  = NULL,         # auto-detecção da coluna geográfica
  lang      = "pt",
  use_cache = TRUE,         # reaproveita geometrias já baixadas
  cache_dir = "~/.climasus4r_cache/spatial",
  verbose   = TRUE
)

The function accepts many other geographic levels — simply change level:

# Bioma (chapéu ambiental):
sf_bioma <- sus_spatial_join(serie_saude, level = "biomes", lang = "pt")

# Região metropolitana:
sf_metro <- sus_spatial_join(serie_saude, level = "metro_area", lang = "pt")

# Geocodificação por CEP (somente sistemas SIH e CNES, gera POINTs):
sf_cep   <- sus_spatial_join(df_sih, level = "cep", lang = "pt")

Integrate gridded environmental data with sus_grid_join()

sus_grid_join() is the bridge between the environmental pipeline (sus_grid_*, which produces objects at stage = "climate") and the health pipeline. It merges the two tables on the space-time key c("code_muni", "date") (real climate data is introduced in Tutorial 8; here the usage is illustrative):

# grid_clim é a saída de uma função sus_grid_*() (ERA5, CHIRPS, ...), stage = "climate"
caso_clima <- sus_grid_join(
  health_data = caso_espacial,
  grid_data   = grid_clim,
  by          = c("code_muni", "date"),  # chave espaço-temporal
  type_out    = NULL,                      # herda o type do grid
  lang        = "pt",
  verbose     = TRUE
)

sus_meta(caso_clima, "stage")  # "climate"

Temporal granularity. If health data are monthly and the grid is daily, joining on date produces many NAs. In that case, aggregate the grid first (with sus_climate_aggregate()), or, for annual grids (e.g., PRODES), join only on by = c("code_muni") — the annual value is replicated for every month.

At the end of this stage we have caso_espacial — the health series with geometry, saved for Tutorial 6.

Function characteristics

sus_spatial_join()

Links health data to Brazil’s official geometries (geobr) or geocodes postal codes (CEP). Requires a climasus_df at minimum stage "stand" and returns an sf object at stage = "spatial".

Argument Essential? Description
df yes climasus_df (at least standardized) with a geographic identifier column
level yes Geographic unit. "munic" (default); "biomes", "health_region", "amazon", "semiarid", "conservation_units", "disaster_risk_area", "indigenous_land", "urban_area", "metro_area", "urban_concentrations", "pop_arrangements", "schools", "health_facilities", "neighborhood", "cep"
join_col optional Column with the geographic code; NULL (default) auto-detects
use_cache / cache_dir recommended Geometry cache at ~/.climasus4r_cache/spatial
lang / verbose optional Language ("pt", "en", "es") and progress messages

System restrictions: "cep" and "neighborhood" only for SIH/CNES; "health_facilities" only for CNES.

Return: an sf object (climasus_df, stage = "spatial", type = level) with the original columns plus geometry — POLYGON/MULTIPOLYGON for area levels, POINT for "cep"/"health_facilities".

Code normalization. Accepts municipal codes with 6 or 7 digits (the check digit is handled automatically); CEP is left-padded with zeros (8 characters); all keys are coerced to text. The area join is an inner_join — codes with no matching geometry are dropped (see Figure 3).

sus_grid_join()

Merges the output of any sus_grid_*() (at stage = "climate") with a health climasus_df (at stage "spatial", "aggregate", or "census"), producing a climasus_df at stage = "climate".

Argument Essential? Description
health_data yes Health climasus_df with code_muni and date
grid_data yes climasus_df at stage = "climate" (output of sus_grid_*)
by optional Join key; default c("code_muni", "date"); use c("code_muni") for an annual grid
type_out optional Overrides the resulting type; NULL inherits it from the grid
lang / verbose optional Language and messages

Return: climasus_df (stage = "climate") with the health columns plus the environmental variables; uses left_join, so health rows with no matching grid receive NA. The sf geometry is dropped before the join (the join is tabular, on the key). The function warns about column-name collisions and about temporal-granularity mismatches.

Formalization

The area join can be seen as a mapping from the municipal key kk (IBGE code) to the geometry G(k)G(k) of the set of geometries 𝒢\mathcal{G}:

join(r)={(r,G(kr))se kr∈𝒢(inner: descarta o resto)⌀caso contrário \text{join}(r) = \begin{cases} (r,\, G(k_r)) & \text{se } k_r \in \mathcal{G} \quad (\text{inner: descarta o resto}) \\[4pt] \varnothing & \text{caso contrário} \end{cases}

where rr is a health record and krk_r its municipal code. The match rate (quality control in Figure 3) is

cobertura=|{k:k∈𝒦entrada∩𝒦geobr}||𝒦entrada|, \text{cobertura} = \frac{\bigl|\{\,k : k \in \mathcal{K}_{\text{entrada}} \cap \mathcal{K}_{\text{geobr}}\,\}\bigr|} {\bigl|\mathcal{K}_{\text{entrada}}\bigr|},

the fraction of input codes 𝒦entrada\mathcal{K}_{\text{entrada}} that find a matching geometry in 𝒦geobr\mathcal{K}_{\text{geobr}}. For sus_grid_join(), the fraction of rows with missing climate is 1n∑i=1n𝟏{xi=NA}\frac{1}{n}\sum_{i=1}^{n} \mathbf{1}\{x_i = \text{NA}\}, directly reported in the completion message.

Illustrated results

Figure 1. Choropleth map of respiratory deaths by municipality after the spatial join (RMSP, 2014–2019). Each polygon now carries the health outcome — the basis for mapping inequalities and cross-referencing with contextual indicators.

Figure 2. Monthly series preserved after the join. The spatial join adds geometry without altering the temporal structure: the time dimension remains intact for modeling (Tutorial 9).

Figure 3. Coverage diagnostic of the join. Because sus_spatial_join() uses inner_join, municipal codes with no matching geometry in geobr are dropped. Checking this rate is an essential quality control (it avoids silent loss of records).

Tabela 1. Resumo da junção espacial (nível, CRS, linhas antes/depois, geometria).
Item Valor
Funcao sus_spatial_join()
Nivel (level) munic (municipio)
Coluna de juncao code_muni (auto-detectada)
Sistema de referencia (CRS) definido
Linhas antes da juncao 359
Linhas depois da juncao 359
Municipios distintos 5
Tipo de geometria MULTIPOLYGON (municipio)
Tabela 2. Amostra das colunas-chave do objeto sf resultante (geometria omitida).
code_muni name_muni abbrev_state date
350950 Campinas SP 2014-01-01
350950 Campinas SP 2014-02-01
350950 Campinas SP 2014-03-01
350950 Campinas SP 2014-04-01
350950 Campinas SP 2014-05-01
350950 Campinas SP 2014-06-01
350950 Campinas SP 2014-07-01
350950 Campinas SP 2014-08-01
350950 Campinas SP 2014-09-01
350950 Campinas SP 2014-10-01

References

  • Openshaw, S. (1984). The Modifiable Areal Unit Problem. CATMOG 38. Norwich: Geo Books.
  • Pereira, R. H. M., Gonçalves, C. N., et al. (2019). geobr: Loads Shapefiles of Official Spatial Data Sets of Brazil. GitHub: https://github.com/ipeaGIT/geobr.
  • Pereira, R. H. M., & Barbosa, R. J. (2023). censobr: Download Data from Brazil’s Population Census. R package v0.4.0. DOI: 10.32614/CRAN.package.censobr.
  • IBGE — Instituto Brasileiro de Geografia e Estatística. Geocentric reference system SIRGAS 2000.