| Title: | Audit-Ready Geocoding and Local Geography for Messy Location Data |
|---|---|
| Description: | Cleans, geocodes, validates, reviews, and exports messy location address data supplied by the user. The package sits on top of 'tidygeocoder': it calls geocoding services, rejects implausible coordinates with configurable region guards, applies fallback name/address matching, joins points to optional local geography with 'sf', and records an audit trail showing how each coordinate was produced. Outputs are designed for manual review, dashboards, and reusable location crosswalks. |
| Authors: | George Arthur [aut, cre] (ORCID: <https://orcid.org/0000-0002-1975-1459>) |
| Maintainer: | George Arthur <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-07-24 08:11:35 UTC |
| Source: | https://github.com/prigasg/locatr |
Enriches geocoded records with one or more Census TIGER/Line geography levels
in a single call, by point-in-polygon assignment. Where add_county_muni()
answers "which county and municipality", this answers "which tract, block
group, ZCTA, congressional district, state legislative district, or school
district" - the analysis and policy geographies that dashboards and grant
reporting often need. Each requested level adds two columns:
<level>_geoid (the Census GEOID) and <level>_name (the layer's name field,
NA where the layer has none, e.g. ZCTAs).
add_census_geographies( data, state, levels = "tract", county = NULL, year = NULL, cb = TRUE, ... )add_census_geographies( data, state, levels = "tract", county = NULL, year = NULL, cb = TRUE, ... )
data |
A geocoded data frame with |
state |
Two-letter state abbreviation or FIPS code (passed to
tigris). ZCTAs are national, so |
levels |
Character vector of geography levels to attach. Any of
|
county |
Optional county filter (name or FIPS) for the levels that
accept one ( |
year |
Vintage year for the boundary files. |
cb |
If |
... |
Passed through to the underlying tigris download functions. |
The geography step only needs latitude/longitude; it does not touch the
address columns. Downloads use tigris and therefore need network access.
data with, for each requested level, a <level>_geoid and
<level>_name column. Rows without usable coordinates get NA.
add_county_muni() for county/municipality, build_local_geography()
for a single reusable boundary layer.
if (interactive()) { enriched <- add_census_geographies( geocoded, state = "NJ", levels = c("tract", "congressional_district", "school_district") ) }if (interactive()) { enriched <- add_census_geographies( geocoded, state = "NJ", levels = c("tract", "congressional_district", "school_district") ) }
Convenience wrapper for the common workflow where users want county and
municipality/locality fields but do not already have a boundary file. It
builds a Census TIGER/Line geography layer with build_local_geography() and
then applies add_muni_from_shapes().
add_county_muni( data, state, geography = c("county_subdivision", "place", "county", "tract"), county = NULL, year = NULL, cb = TRUE, ... )add_county_muni( data, state, geography = c("county_subdivision", "place", "county", "tract"), county = NULL, year = NULL, cb = TRUE, ... )
data |
A validated data frame with |
state |
Two-letter state abbreviation or FIPS code. |
geography |
Which Census layer should become |
county |
Optional county filter for supported Census layers. |
year |
Vintage year for the boundary files. |
cb |
If |
... |
Passed through to |
For reporting where municipality has a state-specific legal definition, use
add_muni_from_shapes() with an official state/local GIS layer.
data with County, Municipality, Muni Key, stable code/join
columns when Census provides them, and muni_match_status, plus the
generic location_* geography audit fields.
Spatially joins geocoded points to a local polygon layer and returns selected geography attributes for dashboards. County and locality column names are auto-detected from common boundary schemas, or can be set explicitly.
add_local_geography( data, geography_shapes = NULL, county_col = NULL, locality_col = NULL )add_local_geography( data, geography_shapes = NULL, county_col = NULL, locality_col = NULL )
data |
A validated data frame with |
geography_shapes |
An |
county_col, locality_col
|
Optional explicit column names in |
If geography_shapes is NULL, the function looks for a packaged
local_geography dataset (for production this is the NJGIN/NJOGIS municipal
boundary layer built by data-raw/local_geography.R, whose attributes are
already named location_county/location_locality). Pass an sf polygon
layer to adapt this join to another state, county, or service area.
For NJ production maps, location_locality is taken from an authoritative
municipal boundary polygon - not from the geocoder response or Census
reverse-geocoding, whose "county subdivision" names only look municipal - so
every locality is traceable to a named boundary source.
data with location_county, location_locality, and
geography_match_status. Rows without usable coordinates are kept (audit-safe)
with NA geography.
Collapses locatr's several trust signals into one calibrated
match_confidence on a 0-1 scale plus a short confidence_reason string, so
a reviewer can sort or threshold on a single column instead of reading
match_status, validation_status, nm_status, and review_status
together. Higher is more trustworthy.
add_match_confidence(data)add_match_confidence(data)
data |
A data frame from the batch pipeline or from |
The right scoring model is chosen from the columns present:
Pipeline output (from geocode_records() / the crosswalk): scored
from the tier that placed the row (geocode_pass), the match and
validation status, and the name-tier confidence. Rejected or unplaced
rows score near zero; reference-verified and manual rows score highest.
Candidate output (from geocode_address()): scored from the ArcGIS
match score, discounted by how coarse the address type is, and capped
when the point falls outside a supplied bbox.
The score is a transparent, rule-based prior - deliberately explainable
rather than a black-box model - so every value can be traced to its
confidence_reason.
data with two added columns: match_confidence (0-1, rounded to
three decimals) and confidence_reason.
add_match_confidence(data.frame( geocode_pass = c("pass_1_census_structured", "pass_4_name_lookup"), match_status = c("matched", "matched_low_confidence"), validation_status = c("coordinate_ok", "coordinate_ok"), latitude = c(40.2, 40.3), longitude = c(-74.7, -74.8) ))add_match_confidence(data.frame( geocode_pass = c("pass_1_census_structured", "pass_4_name_lookup"), match_status = c("matched", "matched_low_confidence"), validation_status = c("coordinate_ok", "coordinate_ok"), latitude = c(40.2, 40.3), longitude = c(-74.7, -74.8) ))
The non-spatial counterpart to add_muni_from_shapes(). Instead of a
point-in-polygon join, it merges geography attributes from a boundary layer
onto geocoded records by a shared key column (for example a ZIP, FIPS, GEOID,
or local region code that both tables carry). Use it when a spatial join is
not the right criterion - the records may lack coordinates, or the
authoritative geography is keyed by a code rather than a polygon footprint.
add_muni_from_key( data, muni_shapes, data_key, shp_key, county_col = NULL, muni_col = NULL, key_col = NULL )add_muni_from_key( data, muni_shapes, data_key, shp_key, county_col = NULL, muni_col = NULL, key_col = NULL )
data |
A data frame of geocoded records carrying |
muni_shapes |
An |
data_key |
Name (string) of the join-key column in |
shp_key |
Name (string) of the join-key column in |
county_col, muni_col
|
Optional explicit county / locality column names
in |
key_col |
Optional municipal key column in |
Output columns match add_muni_from_shapes() exactly, so the two join paths
are interchangeable downstream: County, Municipality, Muni Key,
muni_join_key, the stable code/identifier fields, location_county,
location_locality, geography_match_status, and muni_match_status.
Stable code fields (county_code, municipality_code, municipality_geoid,
etc.) are auto-detected from common boundary schemas; county_fips is
synthesised from a state FIPS plus county code when not supplied directly,
and Muni Key falls back to County::Municipality when no official
identifier exists.
data with County, Municipality, Muni Key, muni_join_key,
county_code, county_fips, municipality_code, municipality_geoid,
municipality_name_standard, municipality_type, muni_match_status, and
the generic location_county / location_locality /
geography_match_status audit fields.
add_muni_from_shapes() for the spatial (point-in-polygon) join.
A crosswalk-oriented wrapper around add_local_geography() for workflows
where the final output should carry explicit county/municipality columns and
stable join identifiers. It spatially joins geocoded points to
municipal/local boundary polygons and adds County, Municipality,
Muni Key, muni_join_key, code fields, and muni_match_status.
add_muni_from_shapes( data, muni_shapes, county_col = NULL, muni_col = NULL, key_col = NULL )add_muni_from_shapes( data, muni_shapes, county_col = NULL, muni_col = NULL, key_col = NULL )
data |
A validated data frame with |
muni_shapes |
An |
county_col, muni_col
|
Optional explicit column names in |
key_col |
Optional municipal key column in |
muni_join_key is copied from key_col when supplied, or auto-detected from
common stable identifier columns such as GEOID and MUNI_KEY. Muni Key
is retained as a readable key and falls back to County::Municipality when
no official identifier exists.
data with County, Municipality, Muni Key, muni_join_key,
county_code, county_fips, municipality_code,
municipality_geoid, municipality_name_standard, municipality_type,
and muni_match_status when available. The generic location_county,
location_locality, and geography_match_status columns are retained.
Joins a reviewer-completed override file (same layout
write_geocode_review() produced) and coalesces verified coordinates and
geography over the automated values. Overrides are themselves bbox-checked so
a typo can't drop a point in the ocean.
apply_manual_overrides(data, override_file, bbox = region_bbox("NJ"))apply_manual_overrides(data, override_file, bbox = region_bbox("NJ"))
data |
A data frame with |
override_file |
Path to the completed override CSV. |
bbox |
Bounding box for validating manual coordinates; see
|
data with overrides applied and manual_override_used set.
The authoritative first tier of the cascade. Joins coordinates from a curated
key -> coordinates table - an institutional-memory table of records whose
location was resolved and checked at some point - over the automated
geocoders, so a record that has already been verified never has to be
re-geocoded. This is what turns one analyst's manual review into a permanent
asset: feed last cycle's completed overrides (or any trusted coordinate list)
back in as reference, and those rows are placed instantly and exactly.
backfill_from_reference( data, reference, by = "record_id", lat_col = "latitude", lon_col = "longitude", county_col = NULL, locality_col = NULL, bbox = region_bbox("NJ") )backfill_from_reference( data, reference, by = "record_id", lat_col = "latitude", lon_col = "longitude", county_col = NULL, locality_col = NULL, bbox = region_bbox("NJ") )
data |
A data frame from |
reference |
A data frame of verified records: the key column plus
coordinate columns. May also carry county/locality columns. |
by |
Name of the key column shared by |
lat_col, lon_col
|
Coordinate column names in |
county_col, locality_col
|
Optional geography column names in |
bbox |
Bounding box used to reject out-of-region reference coordinates;
see |
Reference coordinates are still bbox-validated, so a stale or fat-fingered
entry cannot drop a point outside the region. Because the reference is
authoritative, a matched row is marked review_status == "reference_backfilled"
and carries valid coordinates, so geocode_census() and every later tier skip
it automatically - even if its raw address was previously flagged (e.g. a
PO box whose true coordinates were verified once).
Run this before geocode_census() (the cascade does so when you pass
reference = to geocode_records()).
data with reference audit columns ref_latitude, ref_longitude,
ref_status, and, for rows the reference filled, updated
latitude/longitude/geocode_method/geocode_pass ("pass_0_reference")/
match_status/review_status.
records <- tibble::tibble( record_id = c("a", "b"), review_status = c("ready_for_geocoding", "needs_manual_review") ) verified <- tibble::tibble(record_id = "b", latitude = 40.22, longitude = -74.76) backfill_from_reference(records, verified)records <- tibble::tibble( record_id = c("a", "b"), review_status = c("ready_for_geocoding", "needs_manual_review") ) verified <- tibble::tibble(record_id = "b", latitude = 40.22, longitude = -74.76) backfill_from_reference(records, verified)
Converts any point, line, or polygon sf layer to WGS84 and returns a named
latitude/longitude bounding box suitable for geocode_records(),
geocode_arcgis(), geocode_by_name(), and validate_geocodes(). This is
the safest way to keep multi-state geocoding and local geography joins aligned:
build or load the geography layer first, then derive the bbox from it.
bbox_from_sf(geography_shapes, buffer = 0.05)bbox_from_sf(geography_shapes, buffer = 0.05)
geography_shapes |
An |
buffer |
Numeric buffer in decimal degrees added to each side of the
bounding box. Defaults to |
A named numeric vector with lat_min, lat_max, lon_min, and
lon_max.
if (interactive()) { areas <- build_local_geography("PA") bbox <- bbox_from_sf(areas) }if (interactive()) { areas <- build_local_geography("PA") bbox <- bbox_from_sf(areas) }
Downloads an authoritative Census boundary layer for a state with the
tigris package and standardises it into the two-column schema
add_local_geography() expects: location_county (always from counties) and
location_locality (from the requested geography). It also carries stable
Census identifiers such as county_fips, municipality_geoid, and
muni_join_key when those fields exist. This makes "locality" a configurable
concept, because what counts as a municipality is not consistent across
states.
build_local_geography( state, geography = c("county_subdivision", "place", "county", "tract"), county = NULL, year = NULL, cb = TRUE, ... )build_local_geography( state, geography = c("county_subdivision", "place", "county", "tract"), county = NULL, year = NULL, cb = TRUE, ... )
state |
Two-letter state abbreviation or FIPS code (passed to tigris). |
geography |
Which Census layer becomes |
county |
Optional county filter (name or FIPS) for
|
year |
Vintage year for the boundary files. |
cb |
If |
... |
Passed through to the underlying tigris download function. |
Choosing geography:
"county_subdivision" (default) maps well to townships/municipalities in
states like NJ, PA, NY and New England. Best general default for "locality".
"place" maps to incorporated places and CDPs, but misses townships and
many unincorporated areas. Places can also straddle counties, so each place
is assigned the county it overlaps most.
"county" sets locality to the county itself.
"tract" uses the census tract identifier as the locality (analysis, not
administrative naming).
Census TIGER/Line is the best scalable baseline nationwide. For high-stakes,
state-specific reporting where "municipality" has legal meaning, prefer an
official state GIS layer and pass it straight to add_local_geography() -
that path works regardless, since the join accepts any polygon layer.
An sf polygon layer in WGS84 (EPSG:4326) with
location_county, location_locality, stable join-code columns when
available, and geometry, ready for
add_local_geography(geography_shapes = ...).
if (interactive()) { areas <- build_local_geography(state = "PA", geography = "county_subdivision") final <- add_local_geography(geocoded, geography_shapes = areas) }if (interactive()) { areas <- build_local_geography(state = "PA", geography = "county_subdivision") final <- add_local_geography(geocoded, geography_shapes = areas) }
Turns a set of per-record review decisions (accept / reject / relocate) into
a completed override table with the same layout write_geocode_review()
produces, so it can be written to CSV and fed straight to
apply_manual_overrides(). This is the testable core behind the map-based
review app (run_locatr_review_app()); it holds no reactive or UI code, so
it can also be used headless.
build_review_overrides(data, decisions)build_review_overrides(data, decisions)
data |
A geocoded data frame with at least |
decisions |
A data frame with |
Decision semantics:
accept - confirm the automated coordinate: manual_latitude /
manual_longitude are set to the record's current coordinate.
relocate - use the reviewer's coordinate: manual_latitude /
manual_longitude come from the decisions table.
reject - drop the coordinate: manual_* are left NA (so
apply_manual_overrides() does not place it) and the note records the
rejection.
A tibble with record_id, record_name, full_address_clean,
the current latitude/longitude, the reviewer's manual_latitude /
manual_longitude, and manual_note. Compatible with
apply_manual_overrides().
run_locatr_review_app(), apply_manual_overrides(),
write_geocode_review()
geocoded <- data.frame( record_id = c("a", "b", "c"), record_name = c("A", "B", "C"), full_address_clean = c("1 A St", "2 B St", "3 C St"), latitude = c(40.1, 40.2, NA), longitude = c(-74.1, -74.2, NA) ) decisions <- data.frame( record_id = c("a", "b", "c"), decision = c("accept", "relocate", "reject"), manual_latitude = c(NA, 40.25, NA), manual_longitude = c(NA, -74.25, NA) ) build_review_overrides(geocoded, decisions)geocoded <- data.frame( record_id = c("a", "b", "c"), record_name = c("A", "B", "C"), full_address_clean = c("1 A St", "2 B St", "3 C St"), latitude = c(40.1, 40.2, NA), longitude = c(-74.1, -74.2, NA) ) decisions <- data.frame( record_id = c("a", "b", "c"), decision = c("accept", "relocate", "reject"), manual_latitude = c(NA, 40.25, NA), manual_longitude = c(NA, -74.25, NA) ) build_review_overrides(geocoded, decisions)
Empties the in-memory table. For a persistent cache this also deletes the
file, so it is guarded: a persistent cache requires confirm = TRUE.
cache_clear(cache, confirm = FALSE)cache_clear(cache, confirm = FALSE)
cache |
A |
confirm |
Must be |
The cleared cache, invisibly.
Summarise a locatr cache
cache_info(cache)cache_info(cache)
cache |
A |
A one-row tibble: rows, distinct keys, distinct methods,
oldest/newest cached_at, whether it is persistent, its path, and
format.
Normalises raw address, city and ZIP text into geocoder-friendly columns and
builds a single-line full_address_clean. Column mappings are supplied with
tidy-eval (bare column names). Original address pieces are preserved in
*_raw columns. If the input already contains a full_address_clean column
in any case style (for example Full_Address_Clean), locatr preserves that
user-supplied value as full_address_raw so there is only one canonical
full_address_clean column after cleaning.
clean_addresses( data, id = NULL, address, city, zip = NULL, name = NULL, state = "NJ" )clean_addresses( data, id = NULL, address, city, zip = NULL, name = NULL, state = "NJ" )
data |
A data frame of records with addresses. |
id |
Optional bare column name holding a unique record identifier. When
omitted, |
address, city
|
Bare column names for the raw address and city. Required. |
zip |
Optional bare column name for the raw ZIP/postal code. When
omitted, |
name |
Optional bare column name for the record name (kept as
|
state |
Two-letter state used for all rows. Defaults to |
Only address and city are required. When id is omitted, a surrogate
record_id is generated from the row position. When zip is omitted (or
empty), zip_clean is NA and full_address_clean is built without a
trailing ZIP, so an address + city + state row is still geocodable. Supplying
a ZIP improves Census structured-match precision but is no longer mandatory.
data with added columns: record_id, record_name,
address_raw, city_raw, zip_raw, optional full_address_raw,
address_clean, city_clean, state_clean, zip_clean,
full_address_clean.
df <- tibble::tibble( LocationID = "NJ306100", Name = "Hackensack-UMC Mountainside", Address = "ONE BAY AVE", City = "Montclair", Zip = "7042" ) clean_addresses(df, id = LocationID, address = Address, city = City, zip = Zip, name = Name) # address + city only (surrogate id, no ZIP) clean_addresses(tibble::tibble(Address = "100 Main St", City = "Trenton"), address = Address, city = City)df <- tibble::tibble( LocationID = "NJ306100", Name = "Hackensack-UMC Mountainside", Address = "ONE BAY AVE", City = "Montclair", Zip = "7042" ) clean_addresses(df, id = LocationID, address = Address, city = City, zip = Zip, name = Name) # address + city only (surrogate id, no ZIP) clean_addresses(tibble::tibble(Address = "100 Main St", City = "Trenton"), address = Address, city = City)
Finds records whose coordinates, review status, geocode pass, or geography assignment changed between two runs. This is useful after changing thresholds, adding a reference file, or swapping geography sources.
compare_geocode_runs( old, new, by = "record_id", coordinate_tolerance = 1e-06, changed_only = TRUE )compare_geocode_runs( old, new, by = "record_id", coordinate_tolerance = 1e-06, changed_only = TRUE )
old |
Previous locatr output. |
new |
New locatr output. |
by |
Key column used to match rows. Defaults to |
coordinate_tolerance |
Numeric tolerance for latitude/longitude changes. |
changed_only |
If |
A tibble with old/new values and change flags.
old <- tibble::tibble(record_id = "a", latitude = 40, longitude = -75) new <- tibble::tibble(record_id = "a", latitude = 41, longitude = -75) compare_geocode_runs(old, new)old <- tibble::tibble(record_id = "a", latitude = 40, longitude = -75) new <- tibble::tibble(record_id = "a", latitude = 41, longitude = -75) compare_geocode_runs(old, new)
Turns the main audit columns into short, reviewer-friendly sentences. This is useful when checking a few records by hand or when adding plain-English notes to a review export.
explain_geocode_result(data, row = NULL)explain_geocode_result(data, row = NULL)
data |
A locatr output data frame. |
row |
Optional row selector. Use |
A character vector of explanations.
x <- tibble::tibble( record_id = "a", geocode_pass = "pass_4_name_lookup", match_status = "matched_low_confidence", validation_status = "coordinate_ok", review_status = "needs_manual_review", nm_score = 87, nm_addr_type = "POI" ) explain_geocode_result(x)x <- tibble::tibble( record_id = "a", geocode_pass = "pass_4_name_lookup", match_status = "matched_low_confidence", validation_status = "coordinate_ok", review_status = "needs_manual_review", nm_score = 87, nm_addr_type = "POI" ) explain_geocode_result(x)
Selects the final, stable set of columns for dashboards, GIS joins, and reusable reference tables, and optionally writes them to CSV. Audit columns are retained so a reviewer can always see how each coordinate was produced, including score/type/status fields from the name lookup tier when available.
export_location_crosswalk(data, path = NULL)export_location_crosswalk(data, path = NULL)
data |
A fully processed data frame. |
path |
Optional output CSV path. When |
The crosswalk tibble (also written to path when supplied).
Identifies PO boxes, placeholders, and missing fields so they go straight to
review instead of wasting geocoder calls (or producing confident-but-wrong
matches). Sets bad_address_flag and an initial review_status.
flag_bad_addresses(data)flag_bad_addresses(data)
data |
A data frame from |
A missing ZIP is recorded as bad_address_flag == "missing_zip" for audit,
but it does not block geocoding: as long as the address and city are
present, the row stays ready_for_geocoding (Census matches on
street/city/state and ArcGIS on the single-line address). Only genuinely
unusable rows - missing address or city, PO boxes, placeholders, test
records - are routed to needs_manual_review.
data with added columns bad_address_flag and review_status.
Rows fit for geocoding get review_status == "ready_for_geocoding".
df <- tibble::tibble( record_id = c("a", "b"), address_clean = c("100 MAIN STREET", "PO BOX 42"), city_clean = c("TRENTON", "TRENTON"), zip_clean = c("08608", "08608"), record_name = c("Real Site", "Mailbox Co") ) flag_bad_addresses(df)df <- tibble::tibble( record_id = c("a", "b"), address_clean = c("100 MAIN STREET", "PO BOX 42"), city_clean = c("TRENTON", "TRENTON"), zip_clean = c("08608", "08608"), record_name = c("Real Site", "Mailbox Co") ) flag_bad_addresses(df)
Catches a class of data-entry errors the geocoder itself will silently accept: a ZIP that cannot belong to the stated state, and a stated county that disagrees with the county the coordinate actually fell in. It adds audit columns rather than changing any coordinate, so a reviewer can decide what to do.
flag_field_conflicts( data, zip = "zip_clean", state = "state_clean", stated_county = NULL, geocoded_county = "location_county" )flag_field_conflicts( data, zip = "zip_clean", state = "state_clean", stated_county = NULL, geocoded_county = "location_county" )
data |
A data frame of cleaned/geocoded records. |
zip |
Name of the ZIP column (default |
state |
Name of the state column (default |
stated_county |
Optional name of a county column supplied in the input. The county check runs only when this is given. |
geocoded_county |
Name of the geocoded county column to compare against
(default |
The ZIP check is deliberately conservative. It compares the ZIP's leading
digit against the USPS regional assignment for the stated state, so it only
flags a ZIP that is definitively in the wrong region (for example a
"8xxxx" ZIP recorded in New Jersey). It never flags a same-region
near-miss, and it stays silent when the state is unknown or the ZIP is
missing, so it does not produce false positives.
The county check compares a stated county column against a geocoded county
column (for example location_county from add_county_muni()), after
normalising case and stripping the trailing "County"/"Parish"/"Borough".
data with three added columns: zip_state_conflict (logical, NA
when indeterminate), county_conflict (logical, NA when either county is
missing), and field_conflict (a "; "-joined summary such as
"zip_state", "county", or "zip_state; county"; NA when clean).
df <- data.frame( zip_clean = c("07030", "85001"), # 07 is NJ; 85 is AZ state_clean = c("NJ", "NJ") ) flag_field_conflicts(df)df <- data.frame( zip_clean = c("07030", "85001"), # 07 is NJ; 85 is AZ state_clean = c("NJ", "NJ") ) flag_field_conflicts(df)
An interactive, one-shot companion to the batch pipeline: pass a literal address (no data frame) and get back a tibble of candidate matches ranked by geocoder confidence, highest first. Each candidate carries its coordinates, match score, and ArcGIS address type, and - unless turned off - the county and municipality the point falls in. Handy for spot-checking one address, or for letting a reviewer eyeball the plausible locations the cascade would choose from.
geocode_address( address, city = NULL, state = NULL, zip = NULL, id = NULL, min_score = 0, max_candidates = 5L, geography = TRUE, geography_shapes = NULL, bbox = NULL, quiet = TRUE, show_progress = interactive(), cache = NULL, refresh = FALSE )geocode_address( address, city = NULL, state = NULL, zip = NULL, id = NULL, min_score = 0, max_candidates = 5L, geography = TRUE, geography_shapes = NULL, bbox = NULL, quiet = TRUE, show_progress = interactive(), cache = NULL, refresh = FALSE )
address |
Single-line street address as a length-1 character string. |
city |
Optional locality for the address (length-1 character). |
state |
Optional two-letter state abbreviation. When |
zip |
Optional ZIP/postal code. Improves match precision when supplied. |
id |
Optional label echoed back in the |
min_score |
Minimum ArcGIS match score (0-100) a returned candidate must
reach to be kept. Defaults to |
max_candidates |
Maximum number of candidates to return. Defaults to |
geography |
If |
geography_shapes |
Optional |
bbox |
Optional region bounding box (see |
quiet |
If |
show_progress |
If |
cache |
Optional |
refresh |
If |
The address text is normalised with the same abbreviation/secondary-unit
cleaning used by clean_addresses(), then sent to the free ArcGIS
findAddressCandidates service. city, state, and zip are optional for
this one-off helper: use them when you want to narrow the search, or pass only
address to inspect broad candidate matches. If city is supplied and
state is omitted, state defaults to "NJ" for compatibility with the
package's first workflow.
A tibble of candidates ordered by descending match_score, with
query_id, rank, match_score, match_addr_type, matched_address,
latitude, longitude, the cleaned input_address, an optional in_bbox
flag, and (when geography = TRUE) County/Municipality and related
fields. Zero rows if nothing matched at or above min_score.
geocode_records() for the batch cascade over a data frame.
if (interactive()) { # ranked candidates for one address geocode_address("1600 Pennsylvania Ave NW") # only high-confidence matches, coordinates only geocode_address("1 City Hall Sq", city = "Boston", state = "MA", min_score = 90, geography = FALSE) }if (interactive()) { # ranked candidates for one address geocode_address("1600 Pennsylvania Ave NW") # only high-confidence matches, coordinates only geocode_address("1 City Hall Sq", city = "Boston", state = "MA", min_score = 90, geography = FALSE) }
For rows the Census pass could not place inside the configured region,
re-geocodes with a composite geocoder (ArcGIS by default: free, no API key,
fuzzy matching close to Google) using the single-line full_address_clean.
ArcGIS requests are constrained to the region bbox when possible, and results
are still guarded against the bounding box so out-of-region false matches are
discarded before coordinates are coalesced back into latitude/longitude.
geocode_arcgis( data, method = "arcgis", bbox = region_bbox("NJ"), ..., cache = NULL, refresh = FALSE )geocode_arcgis( data, method = "arcgis", bbox = region_bbox("NJ"), ..., cache = NULL, refresh = FALSE )
data |
A data frame from |
method |
tidygeocoder method for this pass (default |
bbox |
Bounding box used to reject out-of-region matches; see
|
... |
Passed through to |
cache |
Optional |
refresh |
If |
Formerly geocode_fallback(); renamed because this tier is specifically the
ArcGIS (composite) address pass.
data with fallback columns fb_latitude, fb_longitude,
fb_status, and updated latitude, longitude, geocode_method,
geocode_pass, match_status for rows this pass filled.
For rows still unplaced after the address-based passes, geocodes by record name plus city and state rather than the street line. This can resolve campus/landmark addresses (e.g. a unit inside a hospital) that street-range interpolation cannot, because a composite geocoder recognises the named place.
geocode_by_name( data, method = "arcgis", bbox = region_bbox("NJ"), min_score = 90, accept_types = c("PointAddress", "Subaddress", "StreetAddress"), ..., cache = NULL, refresh = FALSE )geocode_by_name( data, method = "arcgis", bbox = region_bbox("NJ"), min_score = 90, accept_types = c("PointAddress", "Subaddress", "StreetAddress"), ..., cache = NULL, refresh = FALSE )
data |
A data frame carrying |
method |
tidygeocoder method that accepts free-text queries
(default |
bbox |
Bounding box used to reject out-of-region matches; see
|
min_score |
Minimum match score (0-100) for a name hit to pass without
review. Hits below this stay reviewable. Default |
accept_types |
Address types precise enough to pass without review
(matched case-insensitively against the geocoder's |
... |
Passed through to |
cache |
Optional |
refresh |
If |
Because name lookups are looser than address matching, each hit is scored
using the geocoder's match score and address type (when available - ArcGIS
returns both via full_results, which this pass requests automatically). A
hit passes cleanly only when it resolves to a precise point address at or
above min_score; fuzzier hits (a POI, a locality centroid, or a low score)
still have their coordinates filled in for context but are marked
match_status == "matched_low_confidence" and routed to
needs_manual_review so a person can confirm them. When the geocoder returns
no score/type information (e.g. method = "osm"), the pass falls back to the
previous rule: any in-region match is accepted.
Filled rows are tagged geocode_pass == "pass_4_name_lookup". The bounding box
still rejects out-of-region hits, but cannot catch a wrong same-state match,
which is exactly what the score gate is for.
data with name-lookup audit columns nm_latitude, nm_longitude,
nm_score, nm_addr_type, nm_status, and updated
latitude/longitude/geocode_method/geocode_pass/match_status for
rows the name pass filled. When there is nothing for the tier to geocode,
nm_status is set to "not_run" for audit clarity. Low-confidence fills
also set review_status == "needs_manual_review".
Geocodes only the rows marked ready_for_geocoding, using the structured
Census engine (street / city / state / ZIP) rather than a single-line string,
which matches reliably more often. Rows not ready are returned untouched with
empty coordinate columns so the frame stays rectangular.
geocode_census(data, ..., cache = NULL, refresh = FALSE)geocode_census(data, ..., cache = NULL, refresh = FALSE)
data |
A data frame from |
... |
Passed through to |
cache |
Optional |
refresh |
If |
Volatile Census full-result columns (tiger_line_id, id) are coerced to
character to avoid the bind_rows() integer/character type clash that the
Census service triggers intermittently between batches.
data with latitude, longitude, geocode_method,
geocode_pass, match_status, plus Census full-result columns when
full_results = TRUE (full-result columns are not stored in the cache, so
cache-filled rows omit them).
geocode_records() attaches a run manifest as an attribute of its output.
This returns it: a run id and UTC timestamp, the locatr / tidygeocoder /
cache-schema versions, the tiers run, whether a reference table was used, the
cache path, per-review_status counts, and cache activity
(cache_hits / cache_misses / cache_writes). Read it directly on the
geocode_records() result, since later data-frame operations may drop the
attribute.
geocode_provenance(data) ## S3 method for class 'locatr_provenance' print(x, ...)geocode_provenance(data) ## S3 method for class 'locatr_provenance' print(x, ...)
data |
Output of |
x |
A |
... |
Ignored. |
A locatr_provenance object (a named list) describing the run.
geocode_records(), locatr_cache()
Orchestrates the tiered strategy on an already-cleaned, already-flagged frame
(see clean_addresses() and flag_bad_addresses()): Census structured
match, then ArcGIS address fallback, then name lookup, validating against the
configured region after each tier so a later tier only retries what is still
unplaced.
geocode_records( data, tiers = c("census", "arcgis", "name"), reference = NULL, boundary = NULL, bbox = region_bbox("NJ"), name_min_score = 90, name_accept_types = c("PointAddress", "Subaddress", "StreetAddress"), verbose = TRUE, cache = NULL, refresh = FALSE )geocode_records( data, tiers = c("census", "arcgis", "name"), reference = NULL, boundary = NULL, bbox = region_bbox("NJ"), name_min_score = 90, name_accept_types = c("PointAddress", "Subaddress", "StreetAddress"), verbose = TRUE, cache = NULL, refresh = FALSE )
data |
A data frame from |
tiers |
Which tiers to run, in order. Any subset of
|
reference |
Optional trusted key -> coordinates table for Tier 0; see
|
boundary |
Optional |
bbox |
Bounding box for region guards; see |
name_min_score |
Minimum ArcGIS score for a name lookup to pass without
review. Passed to |
name_accept_types |
ArcGIS address types precise enough for a name lookup
to pass without review. Passed to |
verbose |
Whether to print a per-tier match tally. |
cache |
Optional |
refresh |
If |
Each tier records how it placed a row in geocode_pass, so the final frame is
self-documenting: pass_0_reference, pass_1_census_structured,
pass_2_fallback, or pass_4_name_lookup. After the cascade, valid matched
rows are marked review_status == "auto_accepted"; anything still unmatched
lands in needs_manual_review, while invalid coordinates are rejected.
Supplying reference runs an authoritative Tier 0 first
(backfill_from_reference()): rows whose verified coordinates are already
known are placed exactly and skipped by every later tier. Feed prior cycles'
completed overrides back in here so manual review accrues over time.
data with coordinates and the full audit trail populated.
Turns a finished geocode_records() frame into an audit report: counts by
review status, by placing tier, and by cache status; a match_confidence
summary; and an auto-generated plain-language methods paragraph suitable for
a report or a paper. When the run manifest is present (attached by
geocode_records()), the methods paragraph names the package versions, run
date, region guard, and cache activity; without it, the report is built from
the audit columns alone.
geocode_report(data, file = NULL, low_confidence_below = 0.5) ## S3 method for class 'locatr_report' print(x, ...)geocode_report(data, file = NULL, low_confidence_below = 0.5) ## S3 method for class 'locatr_report' print(x, ...)
data |
A finished data frame from |
file |
Optional path. When given, a Markdown version of the report is written there and the report object is returned invisibly. |
low_confidence_below |
Confidence threshold (0-1) used to count
low-confidence rows in the summary. Defaults to |
x |
A |
... |
Ignored. |
A locatr_report object (a named list) with the counts, confidence
summary, and methods paragraph. Printing it shows a formatted summary.
geocode_records(), geocode_provenance(), add_match_confidence()
df <- data.frame( record_id = c("a", "b", "c"), geocode_pass = c("pass_1_census_structured", "pass_2_fallback", "pass_4_name_lookup"), review_status = c("auto_accepted", "auto_accepted", "needs_manual_review"), match_confidence = c(0.9, 0.72, 0.35) ) geocode_report(df)df <- data.frame( record_id = c("a", "b", "c"), geocode_pass = c("pass_1_census_structured", "pass_2_fallback", "pass_4_name_lookup"), review_status = c("auto_accepted", "auto_accepted", "needs_manual_review"), match_confidence = c(0.9, 0.72, 0.35) ) geocode_report(df)
Is a coordinate inside a bounding box?
in_bbox(lat, lon, bbox)in_bbox(lat, lon, bbox)
lat |
Numeric vector of latitudes. |
lon |
Numeric vector of longitudes. |
bbox |
A named bounding box as returned by |
A logical vector the same length as lat/lon. NA coordinates
return FALSE.
in_bbox(40.2, -74.5, region_bbox("NJ")) in_bbox(40.5, -104.9, region_bbox("NJ")) # a Colorado false-matchin_bbox(40.2, -74.5, region_bbox("NJ")) in_bbox(40.5, -104.9, region_bbox("NJ")) # a Colorado false-match
A cache of parsed geocoder results that makes runs reproducible: repeated
queries are served locally instead of re-hitting the service, and - because
the parsed coordinates are stored - a cached result can be replayed offline,
even without the httr/jsonlite packages that the live call needs. Pass the
returned object to geocode_address() via its cache argument.
locatr_cache(path = NULL, format = c("rds", "parquet"), store_raw = FALSE) ## S3 method for class 'locatr_cache' print(x, ...)locatr_cache(path = NULL, format = c("rds", "parquet"), store_raw = FALSE) ## S3 method for class 'locatr_cache' print(x, ...)
path |
Optional file path for a persistent cache. |
format |
On-disk format when |
store_raw |
Reserved for storing raw service responses. |
x |
A |
... |
Ignored. |
The cache table is long: one row per candidate result (so
geocode_address()'s ranked set round-trips exactly), plus a single
sentinel row (result_rank = 0, status = "no_match") for a query that
matched nothing, so misses are replayable and never silently re-queried.
The lookup key is an implementation detail (a hash). The visible
method, endpoint, query, and params columns are the audit contract -
keys can always be rebuilt from them if a future rlang changes its hash.
A locatr_cache object (an environment) to pass to geocoding
functions.
cache_info(), cache_clear(), geocode_address()
cache <- locatr_cache() cache_info(cache)cache <- locatr_cache() cache_info(cache)
Creates a small interactive leaflet map, colored by an audit column. The helper is intentionally lightweight: it is for quick review, not for producing a full dashboard.
plot_geocode_review_map( data, color_by = c("review_status", "geocode_pass", "match_status") )plot_geocode_review_map( data, color_by = c("review_status", "geocode_pass", "match_status") )
data |
A locatr output data frame with |
color_by |
Column used to color points. Defaults to |
A leaflet map.
if (interactive()) { plot_geocode_review_map(geocoded) }if (interactive()) { plot_geocode_review_map(geocoded) }
Returns an approximate latitude/longitude bounding box for a US state (or
"DC"), used as a fast sanity guard on geocoded coordinates. Presets are
deliberately a little generous so legitimate edge locations are not rejected;
they are coarse guard boxes, not precise boundaries. For a tighter or
non-state region, pass your own named vector, or derive one from an sf
layer with bbox_from_sf().
region_bbox(region = "NJ")region_bbox(region = "NJ")
region |
Two-letter US state abbreviation (or |
A named numeric vector with elements lat_min, lat_max,
lon_min, lon_max.
region_bbox("NJ") region_bbox("CA")region_bbox("NJ") region_bbox("CA")
Runs the bundled web app (the same one published as a Hugging Face Space): upload a CSV/Excel/Parquet file, geocode it with the locatr cascade and a session cache, download the geocoded records directly, or optionally attach local geography from Census TIGER/Line/an uploaded shapefile before downloading a crosswalk. The app can add Census policy geographies, flag ZIP/state and county conflicts, remove selected columns before export, and download a provenance/reporting bundle for audit records. The app is for demonstration and light interactive use; production pipelines should call the package functions directly.
run_locatr_app(...)run_locatr_app(...)
... |
Passed to |
The app depends on packages that are only listed under Suggests, so they
are not installed automatically. If any are missing, this function stops with
the install command you need.
Called for its side effect of starting the app; does not return.
if (interactive()) { run_locatr_app() }if (interactive()) { run_locatr_app() }
A standalone Shiny app that takes records from upload through geocoding to
map-based review. Upload a file, map the ID and address columns, then either
geocode the addresses (clean, flag, cascade, and attach county/municipality)
or use existing coordinates. The flagged records appear on a map to accept,
reject, or relocate; the app builds a completed override table with
build_review_overrides() that you download as CSV and feed to
apply_manual_overrides(). Geocoding calls external services and needs
network access.
run_locatr_review_app(...)run_locatr_review_app(...)
... |
Passed to |
The app depends on packages listed only under Suggests, so they are not
installed automatically. If any are missing, this function stops with the
install command you need.
Called for its side effect of starting the app; does not return.
build_review_overrides(), apply_manual_overrides()
## Not run: run_locatr_review_app() ## End(Not run)## Not run: run_locatr_review_app() ## End(Not run)
Gives a practical starting point for build_local_geography() and
add_county_muni(). It is a recommendation, not a legal definition of local
government. For production municipal joins, an official state/local boundary
layer is still the strongest source.
suggest_geography_level(state)suggest_geography_level(state)
state |
Two-letter state abbreviation. |
A one-row tibble with the recommended Census geography and note.
suggest_geography_level("NJ") suggest_geography_level("CA")suggest_geography_level("NJ") suggest_geography_level("CA")
Counts the main outcomes in a locatr result so you can quickly judge whether a run is ready for review, export, or threshold tuning.
summarise_geocoding(data)summarise_geocoding(data)
data |
A locatr output data frame. |
A one-row tibble with counts and rates.
x <- tibble::tibble( latitude = c(40, NA), longitude = c(-75, NA), match_status = c("matched", "unmatched"), review_status = c("auto_accepted", "needs_manual_review"), geocode_pass = c("pass_1_census_structured", NA_character_) ) summarise_geocoding(x)x <- tibble::tibble( latitude = c(40, NA), longitude = c(-75, NA), match_status = c("matched", "unmatched"), review_status = c("auto_accepted", "needs_manual_review"), geocode_pass = c("pass_1_census_structured", NA_character_) ) summarise_geocoding(x)
Rejects suspicious coordinates before they reach a dashboard. By default this
is a fast bounding-box check; supply boundary (an sf polygon of the
service area) for a precise point-in-polygon test.
validate_geocodes(data, boundary = NULL, bbox = region_bbox("NJ"))validate_geocodes(data, boundary = NULL, bbox = region_bbox("NJ"))
data |
A geocoded data frame with |
boundary |
Optional |
bbox |
Bounding box for the fast path; see |
data with validation_status and an updated review_status
(anything failing validation or unmatched becomes needs_manual_review).
Writes a tidy review CSV of rows whose review_status is
"needs_manual_review", with blank manual_* columns for a reviewer to fill
in. Feed the completed file back through apply_manual_overrides().
write_geocode_review(data, path)write_geocode_review(data, path)
data |
A data frame carrying the audit columns. |
path |
Output CSV path. |
Invisibly, the review tibble that was written.