Introduction
You have a spreadsheet column of place names — countries, U.S. states or counties, ZIP codes, or subnational divisions — but no shapefile, no lat/lon pairs, no FIPS codes. Can you still put it on a map? CreateChoroplethMapFromNames resolves free-text names to boundary polygons automatically, in the spirit of Excel's "Geography" data type: it normalizes and fuzzy-matches names against an authoritative source (the U.S. Census Bureau's TIGER database or the geoBoundaries project), attaches a match-confidence score to every row, and optionally renders an interactive Folium choropleth colored by a value column — while surfacing anything it couldn't confidently match rather than silently dropping it.
Resolving named geographies to boundaries is essential for:
- Turning open-source reporting (news articles, sanctions lists, incident logs) that references place names into mappable intelligence products
- Rapidly visualizing survey, sales, or population data that was collected by place name rather than by geographic identifier
- Cross-referencing OSINT datasets from different sources that describe the same geography inconsistently (e.g. "USA" vs "United States" vs "U.S.")
- Building situational-awareness maps during fast-moving events, where analysts are working from name-based source material, not GIS-ready data
- Quality-checking name-based geographic fields before they are joined to other spatial datasets, by surfacing unmatched or ambiguous names rather than dropping them
- Supporting country- and subnational-level trend analysis without requiring analysts to hand-source or maintain their own shapefiles
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
dataframerequired | pd.DataFrame | — | The input dataset containing a column of place names and a column of values to map. |
location_columnrequired | str | — | The name of the column in dataframe containing place names to resolve (e.g. country names, U.S. state/county names, ZIP codes, or subnational division names). |
value_columnrequired | str | — | The name of the numeric column to color the choropleth by (e.g. counts, rates, scores). Also carried through to the returned matched GeoDataFrame. |
geography_level | str | 'auto' | The type of geography represented by location_column. One of 'auto', 'country', 'us_state', 'us_county', 'zip', 'admin1' (first-level subnational, e.g. states or provinces outside the U.S.), or 'admin2' (second-level subnational, e.g. districts or counties outside the U.S.). Defaults to 'auto', which inspects the values in location_column and infers the level. If the values are too ambiguous to infer confidently (common for admin1/admin2 names), a ValueError is raised asking the caller to specify geography_level explicitly. |
country_column | str | None | The name of a column in dataframe containing the country each row belongs to. When provided, it restricts fuzzy-matching candidates to boundaries within that country, which reduces false positives on ambiguous names (e.g. a district name that exists in multiple countries). Required when boundary_source='geoboundaries' and geography_level is 'admin1' or 'admin2', since the geoBoundaries API is queried per country. Defaults to None. |
boundary_source | str | 'geoboundaries' | Where to source boundary polygons from. 'geoboundaries' (default) fetches boundaries from the geoBoundaries project's API and is licensed CC-BY, which makes it safe for commercial and redistributable use (unlike GADM, whose license restricts redistribution). 'census' fetches boundaries from the U.S. Census Bureau's TIGER database via FetchUSShapefile and only supports geography_level values 'us_state', 'us_county', and 'zip'. Defaults to 'geoboundaries'. |
api_key | str | None | An API key to send as an Authorization header on outbound requests to boundary_source's API. geoBoundaries' official public API is free and does not require a key, so this can be left as None for the default path. It exists for forward compatibility with rate-limited mirrors, proxies, or alternative keyed boundary providers. Defaults to None. |
fuzzy_match_threshold | int | 85 | The minimum rapidfuzz token-sort-ratio score (0-100) required to accept a fuzzy match when an exact match isn't found. Defaults to 85. |
normalize_diacritics | bool | True | Whether to strip accents/diacritics (e.g. 'Cordoba' vs 'Córdoba') when comparing names. The original boundary name is always returned unmodified; stripping is only used to improve matching. Defaults to True. |
census_year | int | 2021 | The census year to request when boundary_source='census'. Passed through to FetchUSShapefile. Defaults to 2021. |
map_choropleth | bool | False | Whether to generate and display an interactive Folium choropleth map of the matched geographies, colored by value_column. Defaults to False. |
Returns
A tuple of (matched_geodataframe, unmatched_names_df). matched_geodataframe is the original dataframe joined with polygon geometry, matched_boundary_name (the original, un-normalized name of the boundary that was matched), and match_confidence (100 for an exact match, otherwise the fuzzy match score). unmatched_names_df is the subset of rows whose location_column value could not be resolved at or above fuzzy_match_threshold, including a best_fuzzy_score column, so the analyst can inspect and fix them rather than have them silently dropped.
Example
from analysistoolbox.geospatial_analysis import CreateChoroplethMapFromNames
import pandas as pd
# OSINT: mapping incident counts by country (geoBoundaries, CC-BY licensed, default)
incident_data = pd.DataFrame({
'country': ['Kenya', 'Boliva', "Cote d'Ivoire", 'Not A Real Country'],
'incident_count': [12, 4, 7, 1]
})
matched, unmatched = CreateChoroplethMapFromNames(
incident_data,
location_column='country',
value_column='incident_count',
geography_level='country',
boundary_source='geoboundaries',
map_choropleth=True
)
# `matched` has one polygon per resolved country; 'Not A Real Country' lands in `unmatched`