get_2010_2020_bound_changes(res: str = 'tract', geoids: list[str] | None = None, *, state_fips: str = '51') -> pd.DataFrame
Load 2010→2020 relationship data and classify boundary changes.
Parameters:
| Name |
Type |
Description |
Default |
res
|
str
|
Resolution: "tract" or "block group".
|
'tract'
|
geoids
|
list[str] or None
|
Optional list of 2010 GEOIDs to filter.
|
None
|
state_fips
|
str
|
State FIPS for the block-group relationship file (default Virginia, "51").
|
'51'
|
Returns:
| Type |
Description |
DataFrame
|
Crosswalk with columns geoid20, geoid10, area20, area10,
area_part, type_change. type_change is one of:
"same" — one-to-one mapping, identical area
"split" — one 2010 GEOID divided into multiple 2020 GEOIDs with no
boundary movement
"moved" — partial overlap; boundary shifted
|
Source code in packages/sdc-census10to20/src/sdc_census10to20/crosswalk.py
| def get_2010_2020_bound_changes(
res: str = "tract",
geoids: list[str] | None = None,
*,
state_fips: str = "51",
) -> pd.DataFrame:
"""Load 2010→2020 relationship data and classify boundary changes.
Parameters
----------
res : str
Resolution: ``"tract"`` or ``"block group"``.
geoids : list[str] or None
Optional list of 2010 GEOIDs to filter.
state_fips : str
State FIPS for the block-group relationship file (default Virginia, "51").
Returns
-------
pd.DataFrame
Crosswalk with columns ``geoid20``, ``geoid10``, ``area20``, ``area10``,
``area_part``, ``type_change``. ``type_change`` is one of:
- ``"same"`` — one-to-one mapping, identical area
- ``"split"`` — one 2010 GEOID divided into multiple 2020 GEOIDs with no
boundary movement
- ``"moved"`` — partial overlap; boundary shifted
"""
crosswalk = _load_relationship(res, state_fips)
if geoids is not None:
crosswalk = crosswalk[crosswalk["geoid10"].isin(geoids)]
crosswalk["count_20"] = crosswalk.groupby("geoid20")["geoid20"].transform("size")
crosswalk["count_10"] = crosswalk.groupby("geoid10")["geoid10"].transform("size")
geoid_10_20 = (
crosswalk[["geoid10", "area20"]]
.groupby("geoid10", as_index=False)
.sum()
.rename(columns={"area20": "match_area"})
)
crosswalk = crosswalk.merge(geoid_10_20, on="geoid10", how="left")
# Match R's case_when first-match-wins semantics: "same" is checked
# before "split", so apply lower-priority masks first and let higher-
# priority masks overwrite.
crosswalk["type_change"] = "moved"
split_mask = crosswalk["area10"] == crosswalk["match_area"]
crosswalk.loc[split_mask, "type_change"] = "split"
same_mask = (crosswalk["count_10"] == 1) & (crosswalk["count_20"] == 1)
crosswalk.loc[same_mask, "type_change"] = "same"
crosswalk = crosswalk.drop(columns=["count_10", "count_20", "match_area"])
return crosswalk
|