import ast
from typing import Callable
[docs]
def remap(plans, unitmaps, popmap=None) -> Callable:
"""
Re-maps assignments to the specified set of units.
Args:
plans (DataFrame): The Pandas DataFrame produced by ``tabularized()``.
unitmaps (dict): A dictionary whose keys are unit types appearing
in the `unitsType` column, and whose values are dictionaries
mapping unique identifiers of one set of geometries to unique
identifiers (or lists of unique identifiers) of another set of
geometries; these correspond to mappings generated by `unitmap()`
and the inverse mapping generated by `invert()`.
popmap (dict, optional): A mapping from unit unique identifiers to
population values. Only applies when we are mapping from smaller
units to larger ones.
Returns:
A function
"""
def _(row):
# Get the assignment for the row.
assignment = ast.literal_eval(row["plan"])
# Attempt to get the type of units specified by the row; if we
# can't – i.e.the user didn't specify a unit mapping corresponding
# to that unit typein `unitmaps` – we leave the assignment alone and warn the user.
try:
unitsType = row["units"]
unitmap = unitmaps[unitsType]
except BaseException:
print(f"No unit mapping provided for {row['units']}; skipping.")
return assignment
# What kind of mapping do we have? If `mapping` is from a single key
# to a single value, then we're mapping units one-to-one (e.g. block
# IDs to VTD IDs); otherwise, we're mapping units one-to-many (e.g.
# VTD IDs to blocks). If `mapping` is of the former type, then
# it's possible that some larger units may comprise smaller units
# in multiple districts. If this is the case, then we assign larger
# units to the district in which most of the larger unit's population
# resides; otherwise, we simply assign all smaller units to whichever
# district the larger unit's in.
firstvalue = next(iter(unitmap.values()))
unitmapdirection = "down"
# Mark which kind of mapping we have.
if isinstance(firstvalue, list):
unitmapdirection = "down"
else:
unitmapdirection = "up"
# Now, based on the mapping type, return the appropriate mapping.
if unitmapdirection == "down":
return _down(unitmap, assignment)
return _up(unitmap, popmap, assignment)
plans["plan"] = plans.apply(_, axis=1)
return plans
def _down(unitmap, assignment) -> dict:
"""
Maps districting assignments from larger units to smaller
units (which nest in the larger units).
Args:
unitmap (dict): Dictionary which maps larger unit identifiers to
lists of smaller unit identifiers (e.g. VTD identifiers to
block identifiers).
assignment (dict): Maps larger unit identifiers to districts.
Returns:
Maps smaller unit identifiers to districts according to `assignment`.
"""
# Create an empty mapping.
mapped = {}
# For each of the keys in the provided assignment, get the units to which
# the keys correspond, and assign them appropriately.
for bigger, district in assignment.items():
smallers = unitmap[bigger]
mapped.update({smaller: district for smaller in smallers})
return mapped
def _up(unitmap, assignment, popmap):
return assignment