Learn how a gazetteer turns place names into coordinates while preserving ambiguous and not-found results.
On the flyer transcript, RIVERTON SKILLS OPEN HOUSE looks ready for a map until Riverton matches two places in the reference table. The coordinator needs the tie to stay visible.
A map needs coordinates, not only a place name. The team can join a mention to a small reference file, then keep the unresolved cases visible.
Note
The places, lab, and flyer here belong to an invented Riverton setting.
TipWhat you will learn
By the end of this lesson, you will be able to:
define geocoding and gazetteers in plain language;
join place mentions to latitude and longitude;
detect duplicate place-name matches;
return a miss when a place is absent; and
explain why context matters in geocoding.
Load the offline reference files
Geocoding means turning a place name in text into coordinates: a latitude and longitude a map can plot. CSV files supply the examples, dplyr and tibble handle the tables, and digest checks the gazetteer fingerprint. A gazetteer is a list of place names with location information attached. Gazetteers may be public, open-licence, or commercial reference datasets. This one is invented and local so the lesson can run offline.
The reference table has nine rows. The duplicate Riverton rows are deliberate: one sits in Marrow County, Calder, and one sits in Tidewater. The parent column names the larger place that contains a row. The coordinates are arbitrary teaching points, not the locations of real sites.
Riverton Skills Centre is typed as a building here. In the entity-linking lesson, the same words name an organisation. That is not a data-cleaning lesson to hide; the same string can denote a place and an organisation, which is the ORG-versus-place ambiguity that named-entity lessons have to handle.
Join mentions to the gazetteer
The team starts with five hand-identified place mentions. One comes from the Riverton flyer text. Four are constructed checks written in the same setting so we can see resolved, ambiguous, and not-found cases together.
place_mentions <-bind_rows( sentences |>filter(sentence_id =="s023") |>transmute(mention_id = sentence_id,source ="Riverton text", text,mention ="Riverton" ),tibble(mention_id =c("place-02", "place-03", "place-04", "place-05"),source ="constructed check",text =c("Evening classes at Riverton Skills Centre","Applicants in Riverton can apply by October 15","Weekend shifts serve Bellhaven","The route reaches West Calder" ),mention =c("Riverton Skills Centre", "Riverton", "Bellhaven", "West Calder") ))geocoded_rows <- place_mentions |>left_join( gazetteer,by =join_by(mention == place_name),relationship ="many-to-many" )knitr::kable( geocoded_rows,col.names =c("Mention ID", "Source", "Text", "Mention", "Type","Latitude", "Longitude", "Parent" ),caption ="Joining place mentions to the gazetteer",row.names =FALSE)
Joining place mentions to the gazetteer
Mention ID
Source
Text
Mention
Type
Latitude
Longitude
Parent
s023
Riverton text
RIVERTON SKILLS OPEN HOUSE
Riverton
city
41.8210
-71.4120
Marrow County
s023
Riverton text
RIVERTON SKILLS OPEN HOUSE
Riverton
city
38.4410
-75.1002
Tidewater
place-02
constructed check
Evening classes at Riverton Skills Centre
Riverton Skills Centre
building
41.8256
-71.4077
Riverton
place-03
constructed check
Applicants in Riverton can apply by October 15
Riverton
city
41.8210
-71.4120
Marrow County
place-03
constructed check
Applicants in Riverton can apply by October 15
Riverton
city
38.4410
-75.1002
Tidewater
place-04
constructed check
Weekend shifts serve Bellhaven
Bellhaven
city
41.9331
-71.2760
Marrow County
place-05
constructed check
The route reaches West Calder
West Calder
NA
NA
NA
NA
The relationship argument tells dplyr that one mention may match several rows. That fan-out is the ambiguity this lesson is about, so the code declares it. The join returns seven rows from five mentions because Riverton matches twice. West Calder receives no coordinates because it is not in the gazetteer.
Name the unresolved states
A geocoder should not pretend that every mention is solved. The next table gives each mention a status.
knitr::kable( status_counts,col.names =c("Status", "Mentions"),caption ="Resolved, ambiguous, and not-found mentions",row.names =FALSE)
Resolved, ambiguous, and not-found mentions
Status
Mentions
resolved
2
ambiguous
2
not found
1
Two mentions resolve to one row. Two are ambiguous. One is not found. These counts describe only the five mentions in this lesson.
Add context when the name is not enough
The name Riverton alone cannot choose between the two rows. No county appears in the flyer, so the context below is supplied by the analyst to show the mechanism. In a pipeline that value would need to come from a document header, a posting address, or a stated scope for the collection.
riverton_candidates <- geocoded_rows |>filter(mention_id =="s023") |>select(mention, latitude, longitude, parent)supplied_context <-tibble(mention ="Riverton",parent_context ="Marrow County")with_parent_context <- riverton_candidates |>inner_join(supplied_context, by =join_by(mention)) |>filter(parent == parent_context)knitr::kable( riverton_candidates,col.names =c("Mention", "Latitude", "Longitude", "Parent"),caption ="The name Riverton has two candidate locations",row.names =FALSE)
The context row shows a decision rule, not evidence from the flyer. Without the parent region, a prior belief, or surrounding text, the two Riverton rows remain tied.
Geocoding in production
The local exact-match join in this lesson shows the mechanism of geocoding, but production systems rely on specialized geocoding APIs (such as OpenStreetMap/Nominatim) or packages (like tidygeocoder in R or geopy in Python) rather than raw string joins.
When using a live service, several boundaries and limits apply: - Match precision: APIs can perform fuzzy matching, returning a confidence score. A low score requires uncertain-result inspection. - Service boundaries: Rate limits and Terms of Service often dictate how many requests you can make. - Caching: Storing results locally prevents redundant network calls and speeds up repeated processing. - Privacy and re-identification: Sending text containing personal locations to an external API can leak private data. Self-hosted geocoders (like a local Nominatim instance) keep sensitive text entirely on your own infrastructure.
What to remember
A gazetteer is a reference list of place names with locations attached.
Geocoding turns a place mention into coordinates.
Joining by name can return more than one coordinate pair.
A miss should stay a miss rather than becoming a guess.
Context such as a parent region can reduce the candidate list.
ambiguous and not found are first-class geocoding answers.
This teaching gazetteer can map Riverton Skills Centre and Bellhaven. The word Riverton still has two candidate locations, and West Calder has none.