The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Health geographies

library(geopt)

Besides the administrative levels, geopt includes three health geographies. They’re built from the same freguesia boundaries as everything else, so their borders line up exactly with municipalities and NUTS regions.

They work wherever a level is accepted:

plot_pt(level = "uls")

Joining health data

Join by unit name. Names are matched the same way as everywhere else, ignoring case and accents:

df <- data.frame(uls = c("ULS Coimbra", "uls são joão"), casos = c(340, 120))
m <- get_map("uls", data = df, by = "uls")
#> get_map: 53 boundaries have no data row (NA fill).
plot_pt(m, value = "casos")

Some ULS split municipalities

Lisboa is divided between three ULS: São José, Santa Maria and Lisboa Ocidental. Loures, Porto and Funchal are split too, and so is Gondomar, by a single freguesia (see below). The uls map draws these correctly, because it’s built from freguesias.

This matters when joining data. If it’s by municipality, there’s no way to tell which of Lisboa’s three ULS a value for Lisboa belongs to. For data by municipality, use uls_municipio instead. It’s the same as uls, except that ULS sharing a municipality are combined into one unit:

h <- pt_lookup(health_vintage = "2024")
unique(h$unit_municipio[h$unit != h$unit_municipio])
#> [1] "ULS Lisboa Ocidental + Loures-Odivelas + Santa Maria + São José"
#> [2] "ULS Santo António + São João"                                   
#> [3] "CS Funchal Zona I + Funchal Zona II"

ULS Loures-Odivelas is in the first unit, although no part of Lisboa belongs to it. Combining works in chains: ULS São José also covers four freguesias of Loures, and the rest of Loures belongs to ULS Loures-Odivelas, so a value for Loures can’t be divided between those two either. Counting the freguesias of each municipality by ULS shows the chain:

u <- unique(h$unit_municipio[h$municipio == "Lisboa"])
lx <- h[h$unit_municipio == u, ]
table(lx$municipio, sub("ULS ", "", lx$unit))
#>           
#>            Lisboa Ocidental Loures-Odivelas Santa Maria São José
#>   Cascais                 4               0           0        0
#>   Lisboa                  3               0           8       13
#>   Loures                  0               6           0        4
#>   Mafra                   0               0          11        0
#>   Odivelas                0               4           0        0
#>   Oeiras                  5               0           0        0

Every municipality in the chain gets the combined unit, including those that lie entirely in one ULS, such as Odivelas and Cascais. Data for Odivelas on its own would fit ULS Loures-Odivelas, but data for Loures wouldn’t, so no total for ULS Loures-Odivelas can be built from data by municipality.

In pt_lookup() these columns are called unit and unit_municipio, because with health_vintage = "2022" they hold ACES instead of ULS (see below).

pt_join() picks the right column automatically. Joining by freguesia adds uls; joining by municipality adds only uls_municipio:

df <- data.frame(concelho = "Gondomar", v = 1)
out <- pt_join(df, by = c(municipio = "concelho"))
#> pt_join: 1/1 row(s) matched at municipio level.
out$uls_municipio
#> [1] "ULS Santo António + São João"
"uls" %in% names(out)
#> [1] FALSE

To map data by municipality, add up the values by uls_municipio and draw them at level = "uls_municipio", where each combined unit is a single area:

df <- data.frame(concelho = c("Lisboa", "Odivelas", "Porto", "Maia", "Coimbra"),
                 casos = c(10, 5, 7, 3, 4))
df <- pt_join(df, by = c(municipio = "concelho"))
#> pt_join: 5/5 row(s) matched at municipio level.
casos <- aggregate(casos ~ uls_municipio, data = df, FUN = sum)
casos
#>                                                     uls_municipio casos
#> 1                                                     ULS Coimbra     4
#> 2 ULS Lisboa Ocidental + Loures-Odivelas + Santa Maria + São José    15
#> 3                                    ULS Santo António + São João    10
plot_pt(casos, value = "casos", level = "uls_municipio", by = "uls_municipio")
#> get_map: 47 boundaries have no data row (NA fill).

Lisboa and Odivelas add up to one value, for the combined unit, and so do Porto and Maia.

One freguesia is handled specially. Lomba, in Gondomar, belongs to a different ULS and ACES from the rest of Gondomar. Taken as it is, it would combine ULS Vila Nova de Gaia/Espinho with Porto’s ULS into one unit, and ACES Gondomar with ACES Espinho/Gaia. Following established practice, uls_municipio and aces_municipio count Lomba with the rest of Gondomar, which keeps those units separate, and their maps draw it with Gondomar too. The freguesia-level uls and aces still place Lomba correctly.

Before and after the 2024 reform

Before 2024, primary care was organized into ACES, under regional health administrations (ARS). The 2024 reform replaced them with the ULS, and moved some municipalities to a different health region. geopt has both versions; choose one with health_vintage:

health_vintage unit holds reg_saude holds Law
"2024" (current) a ULS, or its island equivalent the health regions since 2024 Decreto-Lei 102/2023, which created the ULS
"2022" an ACES (71 of them) the health regions before 2024 Decreto-Lei 28/2008, which created the ACES

Four municipalities changed health region in 2024:

h24 <- pt_lookup(health_vintage = "2024")
h22 <- pt_lookup(health_vintage = "2022")
d <- data.frame(municipio = h24$municipio,
                reg_saude_2022 = h22$reg_saude[match(h24$dtmnfr, h22$dtmnfr)],
                reg_saude = h24$reg_saude)
d <- d[d$reg_saude != d$reg_saude_2022, ]
unique(d[, c("municipio", "reg_saude_2022", "reg_saude")])
#>      municipio reg_saude_2022 reg_saude
#> 139       Ovar         Centro     Norte
#> 1541  Alcobaça            LVT    Centro
#> 1613    Nazaré            LVT    Centro
#> 2279     Ourém            LVT    Centro

ACES names match and join like any other level, so data from before the reform can go on a map:

df <- data.frame(aces = c("ACES Baixo Vouga", "aces braga"), casos = c(10, 20))
head(pt_join(df, by = "aces"))
#> pt_join: 2/2 row(s) matched at aces level.
#>               aces casos nuts1_cod      nuts1 reg_saude
#> 1 ACES Baixo Vouga    10       PT1 Continente    Centro
#> 2       aces braga    20       PT1 Continente     Norte
plot_pt(level = "aces")

Some ACES also split municipalities, so for data by municipality there’s an aces_municipio column, and an "aces_municipio" map, that work the same way as uls_municipio:

h <- pt_lookup(health_vintage = "2022")
grep(" [+] ", unique(h$unit_municipio), value = TRUE)
#> [1] "ACES Espinho/Gaia + Gaia"                                    
#> [2] "ACES Lisboa Central + Lisboa Norte + Lisboa Ocidental/Oeiras"
#> [3] "ACES Porto Ocidental + Porto Oriental"                       
#> [4] "CS Funchal Zona I + Funchal Zona II"

Notes and limits

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.