Resolves a character vector of Portuguese administrative unit names to
their official codes using the pt_registry table. Matching is exact on
normalized names (see pt_normalize()), optionally preceded by a
user-supplied alias lookup and followed by an opt-in fuzzy pass.
Usage
pt_match(
x,
level = "municipio",
to = NULL,
context = NULL,
admin_vintage = NULL,
nuts_vintage = NULL,
health_vintage = NULL,
fuzzy = FALSE,
max_dist = 2,
aliases = NULL,
historic = TRUE,
quiet = FALSE
)Arguments
- x
Character vector of names to match. Codes are automatically padded to the correct length, if needed.
- level
Which units
xrefers to:"freguesia","municipio"(default),"distrito","nuts1","nuts2","nuts3","uls","reg_saude","aces","uls_municipio","aces_municipio"."uls"/"reg_saude"are the 2024 health reform;"aces"is the organization it superseded. The*_municipiolevels merge the units that share a municipality, and are matched by the merged name ("ULS Santo António + São João").- to
Column of
pt_lookup()to return, at the vintages requested below. Defaults to the level's own code column (dtmnfr,dtmn,dt,nuts*_cod, ...). A column defined only at a finer level —"dtmnfr"for a municipio — yieldsNA.- context
Optional character vector (length 1 or
length(x)) naming a parent unit to scope the match. Any vintage of any parent scheme is accepted, so INE's own parent column works whichever nomenclature that indicator was published under.- admin_vintage
Which administrative vintage to match against and return codes for:
"2025","2013","2011","2001", or"auto"to infer it fromx. Leave it out for the newest ("2025"). See Vintages.- nuts_vintage
Which NUTS revision
tois expressed in:"2024","2013"or"2002". Also the vintage matched atlevel = "nuts1"/"nuts2"/"nuts3". Leave it out for the newest ("2024").- health_vintage
Which health geography
tois expressed in:"2024"or"2022". Leave it out for the newest ("2024"). Ignored atlevel = "uls"and"aces"(and their*_municipioforms), which each exist in one health geography only; a value that isn't a health vintage at all is still an error.- fuzzy
Logical. Enable the fuzzy fallback pass? Default
FALSE.- max_dist
Maximum string distance for a fuzzy match. Default
2.- aliases
Optional named character vector mapping variant spellings to either canonical names or codes, e.g.
c("VN Gaia" = "Vila Nova de Gaia"). Checked before exact matching; names are compared in normalized form.- historic
Logical. Also match names from the level's other vintages? Default
TRUE. SetFALSEto accept only names in use atadmin_vintage— useful when validating that a file really is that vintage.- quiet
Logical. Suppress warnings/messages about ambiguous, unmatched, and fuzzy-matched entries? Default
FALSE.
Value
Character vector of codes (same length as x), with a "report"
attribute retrievable via pt_match_report().
Ambiguity and context
Many names are not unique (~200 freguesia names are reused; the municipality
"Lagoa" exists in Faro and in Sao Miguel). Ambiguous names return NA with
a warning listing the candidates. To resolve them, either:
pass
context: a parent unit name per element (municipio for freguesias; distrito/ilha, NUTS region, or"Acores"/"Madeira"for municipios), orembed a qualifier in the name itself:
"Lagoa (Acores)","Lagoa (Faro)", INE-style"Calheta (R.A.M.)","Lagoa [R.A. Acores]"and dash-separated forms like"Calheta - RAA"or"Calheta-R.A.A"are all parsed automatically. A dash is only read as a separator when what follows it is a recognized region — ordinary names containing dashes ("Albergaria-a-Velha") are never split.
A qualifier is only read as one when it is not already part of an official
name. Many freguesias end in a parenthetical of their own —
"Bouro (Santa Maria)", "Uniao das freguesias de Braga (Maximinos, Se e Cividade)" — and those are matched whole, before any split is attempted.
Spelling tolerance
Matching ignores case, accents, and punctuation, and expands S./Sta./
Sto.. If that still does not match, a second pass ignores Portuguese
particles, so official designations and their working short forms both
resolve: "ULS da Guarda" → "ULS Guarda",
"ULS Trás-os-Montes Alto Douro" → "ULS Trás-os-Montes e Alto Douro".
Exact spellings always win; the report's method column shows "loose"
when this pass was needed.
A few units accept a second official spelling, so that data labelled either
way resolves: "Vila da Praia da Vitoria" for Praia da Vitória, and
"Calheta" for the Azorean Calheta (which pt_registry stores as
"Calheta de Sao Jorge"). Because Madeira also has a Calheta, a bare
"Calheta" is therefore ambiguous and returns NA with a warning —
qualify it as "Calheta (R.A.A.)" or "Calheta (Madeira)".
Historic names
Administrative reforms rename and dissolve units, so names in older census and
administrative files often have no current counterpart. With
historic = TRUE (the default) names from every vintage of the level are
matched, returning the code of the unit covering that territory in the
requested vintage; the report's method column shows "historic". A name
found in the 2001 map is only used when no later vintage has it: most 2001
names were still in use in 2011, and for the few freguesias that lost
territory in between, the 2011 sense is the one that matches a single unit
today. This is
one mechanism, not a freguesia special case — it is also what makes
"Minho-Lima" resolve at level = "nuts3", where the current name is
"Alto Minho".
Live names win outright. A name still in use resolves to the unit bearing it
now, whatever it also meant in the past. Where a predecessor was later split
between several current units the name is genuinely ambiguous and returns
NA — pass a context or use codes. Historic names are matched exactly and
particle-loosely, never fuzzily.
A freguesia renamed while its map was in force keeps its earlier names as
names of that vintage, not as historic ones. The 2013 freguesias are named as
they were last, in CAOP 2024 and in INE's tables, and 25 of them were renamed
by law before that. "Buarcos", the name of 060520 until 2015, therefore
matches it with admin_vintage = "2013" even when historic = FALSE.
Vintages
The three axes are independent and each has its own argument. admin_vintage
chooses which units exist and which vintage's codes come back;
nuts_vintage and health_vintage choose the nomenclature that NUTS and
health columns are expressed in. See pt_lookup().
admin_vintage defaults to the current set, which is what you want for
joining old data onto today's map. To keep the data on its own vintage
instead — to map it against boundaries of the era — name that vintage:
pt_match(c("Sé", "Maximinos"), level = "freguesia", context = "Braga")
#> [1] "030364" "030364" # the union covering both today
pt_match(c("Sé", "Maximinos"), level = "freguesia", context = "Braga",
admin_vintage = "2011")
#> [1] "030352" "030324" # the units they wereCodes are vintage-local. None of the 302 freguesias restored in 2025
reused its pre-2013 code, even if it has the same name and same boundaries,
so a code is only accepted against the vintage it belongs to.
pt_match("030882", level = "freguesia") is NA, because that is
a 2013 code; with admin_vintage = "2013" it resolves.
admin_vintage = "auto" infers the vintage from the input and says which it
chose. This is the setting for INE data, which is published on the 2013
freguesia set even under the 2024 NUTS revision — so 134 units that were
disaggregated in 2025 are ambiguous against the current registry and exact
against their own.
INE prefixes a city's pre-2013 parishes with the city ("Braga (Sé)") but
names the same kind of unit plainly elsewhere ("Sé" in Lisboa). All the
spellings resolve — "Braga (Sé)", "Sé (Braga)", and "Sé" with
context = "Braga" — because a bare name plus a context is recombined into
the parenthesised forms and retried, once the name as given has failed. A
trailing parenthetical never displaces a context you passed: where it is
part of the official name rather than a qualifier, the context still applies.
Fuzzy matching
With fuzzy = TRUE (requires the stringdist package), elements that
fail exact matching are compared against candidate names by optimal string
alignment distance. A fuzzy match is accepted only when the best candidate
is within max_dist and strictly better than the second-best candidate;
every accepted fuzzy match is reported via message. Fuzzy matching is
most reliable with a context, which shrinks the candidate pool.
Examples
pt_match(c("Porto", "lisboa", "S. Joao da Madeira"))
#> [1] "1312" "1106" "0116"
#> attr(,"report")
#> input matched_name code method dist
#> 1 Porto Porto 1312 exact NA
#> 2 lisboa Lisboa 1106 exact NA
#> 3 S. Joao da Madeira São João da Madeira 0116 exact NA
pt_match(c("Lagoa (Faro)", "Lagoa (Acores)"))
#> [1] "0806" "4201"
#> attr(,"report")
#> input matched_name code method dist
#> 1 Lagoa (Faro) Lagoa 0806 exact NA
#> 2 Lagoa (Acores) Lagoa 4201 exact NA
pt_match("Pinheiro", level = "freguesia", context = "Guimarães")
#> [1] "030836"
#> attr(,"report")
#> input matched_name code method dist
#> 1 Pinheiro Pinheiro 030836 exact NA
