--- title: "Joining data" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Joining data} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 6, out.width = "100%" ) ``` ```{r setup} library(geopt) ``` Portuguese data usually identifies places in one of three ways: by official code (the 4-digit DTMN for a municipality, the 6-digit DTMNFR for a freguesia), by name, in whatever spelling the source uses, or by NUTS code. This guide shows how to get each of them onto a map, and how to deal with the usual problems: repeated names, lost leading zeros and spelling variants. ## Join by code when available Codes are unambiguous, so use them whenever possible: ```{r by-code} df <- data.frame(dtmn = c("1106", "1312"), value = c(20, 10)) m <- get_map("municipio", data = df, by = "dtmn") ``` Excel often turns `"0115"` into `115`. geopt restores the leading zeros when it joins. To fix a column directly: ```{r pad} pt_pad_code(c(115, 1106), "municipio") ``` ## Join by name Names are matched by `pt_match()`, which ignores accents, case and punctuation, and reads the abbreviations `S.`, `Sta.` and `Sto.` as São, Santa and Santo: ```{r by-name} df <- data.frame(concelho = c("porto", "CÂMARA DE LOBOS", "S. João da Madeira"), value = c(1, 2, 3)) pt_match(df$concelho) ``` ### Repeated names Some names are shared: two municipalities are called **Lagoa** (one in Faro district, one on São Miguel), and 196 freguesia names are used by more than one freguesia. A name that fits more than one unit returns `NA` with a warning. geopt never guesses: ```{r ambiguous} pt_match("Lagoa") ``` There are two ways to say which one is meant. Add the region in brackets after the name, as INE's files often do, or pass a `context`, the unit it belongs to: ```{r disambiguate} pt_match(c("Lagoa (Faro)", "Lagoa (Açores)", "Lagoa (R.A.A.)"), quiet = TRUE) pt_match("Lagoa", context = "Faro", quiet = TRUE) ``` At freguesia level, repeated names are common, so always give the municipality as context. No municipality has two freguesias with the same name, so this always settles it: ```{r freg-context} df <- data.frame(freg = c("Pinheiro", "Pinheiro"), mun = c("Guimarães", "Castro Daire")) pt_match(df$freg, level = "freguesia", context = df$mun, quiet = TRUE) ``` ### Names from older data In 2013, about half of all freguesias were merged into *uniões de freguesias*, and in 2025 some of them were split again. Names from before 2013 match the freguesia that covers the same place today, so a file from an older census joins without being converted first: ```{r historic} pt_match(c("Sé", "Maximinos"), level = "freguesia", context = "Braga", quiet = TRUE) ``` Both are now part of `União das freguesias de Braga (Maximinos, Sé e Cividade)`. The report marks these matches as `"historic"`, which shows how much of a join relied on old names. A name that is still in use always takes priority over an old one, so this never changes what a current name matches. Pass `historic = FALSE` to accept only current names. To get the code each freguesia had before 2013 instead, pass `admin_vintage = "2011"` (the map the 2011 census used). Each old freguesia then keeps its own code, rather than sharing the code of the união it joined: ```{r historic-to} pt_match(c("Sé", "Maximinos"), level = "freguesia", context = "Braga", admin_vintage = "2011", quiet = TRUE) ``` To convert the *values* in a dataset between these versions of the map, see `pt_recode()` and `vignette("vintages")`. ### Spelling variants and typos If a source consistently spells a name its own way, pass `aliases`: ```{r aliases} pt_match("VN Gaia", aliases = c("VN Gaia" = "Vila Nova de Gaia"), quiet = TRUE) ``` For typos, turn on fuzzy matching (this needs the stringdist package). It only runs on names that didn't match exactly, only accepts a candidate that is clearly the closest, and reports every match it makes: ```{r fuzzy, eval = requireNamespace("stringdist", quietly = TRUE)} pt_match("Guimaraess", fuzzy = TRUE) ``` ### Checking what matched Every result from `pt_match()` and `pt_join()` carries a report. Check it before trusting a join: ```{r report} res <- pt_match(c("Porto", "Lagoa", "Atlantis"), quiet = TRUE) pt_match_report(res) ``` The `method` column says how each input was matched, or why it wasn't: | `method` | Meaning | |---|---| | `code` | the input was an official code | | `exact` | the name matched, once accents, case and punctuation were ignored | | `loose` | the name matched once small words like "da" or "dos" were ignored | | `historic` | the name is from an older map, such as one from before 2013 | | `alias` | the name matched through `aliases` | | `fuzzy` | a close spelling matched (only with `fuzzy = TRUE`) | | `ambiguous` | more than one unit has this name: add a context | | `context_unrecognized` | the given context isn't a unit geopt knows | | `unmatched` | no unit has this name | | `empty` | the input was empty or `NA` | ## Three ways to join **`get_map(data =, by =)`** joins straight onto the boundaries, for maps: ```{r get-map-join, eval = FALSE} m <- get_map("municipio", data = df, by = c(municipio = "concelho")) ``` **`pt_join()`** adds to the data frame the codes and the regions each unit belongs to, for work that doesn't need a map. It only adds columns that have a single value for each unit at the level joined on. Some municipalities are split between several ULS, for example, so a join by municipality gets `uls_municipio` rather than `uls` (see `vignette("health-geographies")`): ```{r pt-join} df <- data.frame(concelho = c("Porto", "Águeda"), value = c(1, 2)) out <- pt_join(df, by = c(municipio = "concelho")) names(out) ``` Like `pt_match()`, `pt_join()` takes `admin_vintage` and `nuts_vintage`. INE publishes freguesias on the 2013 map, so for its data `admin_vintage = "2013"` (or `"auto"`) keeps the 2013 codes and adds that map's names, and `nuts_vintage` picks the NUTS revision of the regions added: ```{r pt-join-vintage} ine <- data.frame(dtmnfr = "030882", value = 3) # a união split in 2025 pt_join(ine, by = "dtmnfr", admin_vintage = "2013")[, c("dtmnfr", "freguesia", "municipio")] ``` **`pt_lookup()`** returns the table of codes and regions itself, as a plain data frame, ready for any other kind of join. Health columns are only added when `health_vintage` says which version of the health map to use: ```{r codes-direct} codes <- pt_lookup(health_vintage = "2024") head(codes[, c("dtmnfr", "freguesia", "dtmn", "municipio", "nuts3_cod", "unit")]) ``` ## Cleaning names directly The name cleaning that `pt_match()` uses is available as `pt_normalize()`, so the same rules can be applied to any column before a manual join: ```{r normalize} pt_normalize(c("S. Jorge", "SÃO JORGE", "Sao-Jorge")) ```