Data

Data loading, transformation, and registry: MOE propagation, cross-tabulation, NAICS/SOC crosswalk, sample datasets.

Data module for siege_utilities.

After ELE-2437, data/ narrowed to hold statistics primitives (data.statistics) + the RDH provider shim. Other concerns moved:

Top-level imports at this package are preserved (re-exports from the new homes) for one deprecation window so existing callers still work.

All re-exports use PEP 562 lazy loading so that importing siege_utilities.data does not pull in heavy transitive dependencies (geopandas via RDH, etc.) unless the caller actually uses them.

class siege_utilities.data.RDHClient[source]

Bases: object

Client for the Redistricting Data Hub download API.

Parameters:
  • username (str, optional) – RDH account email. Falls back to RDH_USERNAME env var.

  • password (str, optional) – RDH account password. Falls back to RDH_PASSWORD env var.

  • base_url (str, optional) – Override API endpoint (for testing).

  • cache_dir (Path or str, optional) – Directory for caching downloaded files. Defaults to ~/.cache/siege_utilities/rdh/.

__init__(username=None, password=None, base_url='https://redistrictingdatahub.org/wp-json/download/list', cache_dir=None)[source]
Parameters:
  • username (str | None)

  • password (str | None)

  • base_url (str)

  • cache_dir (str | Path | None)

close()[source]

Close the underlying HTTP session.

Return type:

None

credentials_present()[source]

Return True if a username and password are both set.

This is a presence check only – it does NOT contact RDH or verify that the credentials authenticate. A live check is impractical here: the RDH catalog endpoint is WAF-gated to allowlisted ingest hosts (#1115), so from other hosts an authenticated-style request returns the website HTML rather than the JSON API. Callers needing a true liveness check should call list_datasets() and handle the ValueError it now raises on an API error envelope.

Return type:

bool

validate_credentials()[source]

Backward-compatible alias for credentials_present().

Retained for existing callers. The name is a misnomer kept for compatibility: it checks credential presence, not live authentication (see credentials_present()). (#1115)

Return type:

bool

list_datasets(states=None, format=None, year=None, dataset_type=None, geography=None, official=None)[source]

List available datasets with optional filters.

Parameters:
  • states (list of str, optional) – State abbreviations to filter (e.g. ["VA", "MD"]). Due to API limits, queries more than 4 states sequentially.

  • format (str, optional) – "csv" or "shp".

  • year (str, optional) – Four-digit year.

  • dataset_type (str, optional) – Filter keyword in title.

  • geography (str, optional) – Geographic level filter.

  • official (bool, optional) – If True, only return official/enacted datasets.

Return type:

list of RDHDataset

download_dataset(dataset, output_dir=None, use_cache=True)[source]

Download a dataset file.

Parameters:
  • dataset (RDHDataset or str) – Dataset object or direct download URL.

  • output_dir (Path, optional) – Where to save. Defaults to cache_dir.

  • use_cache (bool) – If True, skip download if file already exists.

Return type:

Path to the downloaded file (or extracted directory for ZIPs).

load_csv(dataset, **kwargs)[source]

Download (if needed) and load a CSV dataset into a DataFrame.

Parameters:
  • dataset (RDHDataset, str, or Path) – Dataset to load.

  • **kwargs – Passed to pd.read_csv().

Return type:

pd.DataFrame

load_shapefile(dataset, **kwargs)[source]

Download (if needed) and load a shapefile into a GeoDataFrame.

Parameters:
  • dataset (RDHDataset, str, or Path) – Dataset to load.

  • **kwargs – Passed to gpd.read_file().

Return type:

gpd.GeoDataFrame

get_enacted_plans(state, chamber='congress', year=None, format='shp')[source]

Find enacted redistricting plan datasets for a state.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • chamber (str) – "congress", "state_senate", or "state_house".

  • year (str, optional) – Filter by year.

  • format (str, default "shp") – Dataset format filter. Only "shp" is loadable via load_shapefile(); other formats are returned as metadata only.

Return type:

List[RDHDataset]

get_precinct_data(state, year=None, format='shp')[source]

Find precinct-level datasets for a state.

Parameters:
Return type:

List[RDHDataset]

get_cvap_data(state, year=None)[source]

Find CVAP (Citizen Voting Age Population) datasets.

Parameters:
  • state (str)

  • year (str | None)

Return type:

List[RDHDataset]

get_pl94171_data(state, year=None)[source]

Find PL 94-171 redistricting data datasets.

Parameters:
  • state (str)

  • year (str | None)

Return type:

List[RDHDataset]

static to_crosstab_input(df, geography_col='GEOID', variable_cols=None)[source]

Reshape an RDH DataFrame into long format for cross-tabulation.

Returns a DataFrame with columns [geography, variable, value] suitable for use with siege_utilities.data.cross_tabulation.contingency_table().

Parameters:
  • df (DataFrame) – Wide-format data (one row per geography, numeric columns as variables).

  • geography_col (str) – Column identifying the geographic unit.

  • variable_cols (list of str, optional) – Columns to melt. If None, all numeric columns except the geography column are used.

Return type:

DataFrame with columns geography, variable, value.

class siege_utilities.data.RDHDataset[source]

Bases: object

Metadata for a single RDH dataset entry.

title: str
url: str
state: str = ''
format: str = ''
year: str = ''
geography: str = ''
dataset_type: str = ''
official: bool = False
file_size: str = ''
raw: Dict[str, Any]
property filename: str

Extract filename from URL.

property is_shapefile: bool
property is_csv: bool
__init__(title, url, state='', format='', year='', geography='', dataset_type='', official=False, file_size='', raw=<factory>)
Parameters:
Return type:

None

class siege_utilities.data.RDHDataFormat[source]

Bases: str, Enum

File format for RDH downloads.

CSV = 'csv'
SHAPEFILE = 'shp'
__new__(value)
class siege_utilities.data.RDHDatasetType[source]

Bases: str, Enum

Categories of RDH datasets.

PL94171 = 'pl94171'
CVAP = 'cvap'
ACS5 = 'acs5'
ELECTION = 'election'
LEGISLATIVE = 'legislative'
PRECINCT = 'precinct'
VOTER_FILE = 'voter_file'
PROJECTION = 'projection'
__new__(value)
siege_utilities.data.polsby_popper(geometry, source_crs=None)[source]

Compute Polsby-Popper compactness score for a geometry.

Score = (4 * pi * area) / perimeter^2 Range: 0 (least compact) to 1 (circle).

Parameters:
  • geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.

  • source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area before calculation. Pass the CRS (e.g., “EPSG:4326”) when calling with unprojected data.

Return type:

float

siege_utilities.data.reock(geometry, source_crs=None)[source]

Compute Reock compactness score for a geometry.

Score = area / area_of_minimum_bounding_circle Range: 0 (least compact) to 1 (circle).

Parameters:
  • geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.

  • source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area.

Return type:

float

siege_utilities.data.convex_hull_ratio(geometry, source_crs=None)[source]

Compute convex hull ratio for a geometry.

Score = area / convex_hull_area Range: 0 to 1.

Parameters:
  • geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.

  • source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area.

Return type:

float

siege_utilities.data.schwartzberg(geometry, source_crs=None)[source]

Compute Schwartzberg compactness score.

Score = 1 / (perimeter / circumference_of_equal_area_circle) Range: 0 to 1.

Parameters:
  • geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.

  • source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area.

Return type:

float

siege_utilities.data.compute_compactness(gdf, district_id_col='GEOID', geometry_col='geometry')[source]

Compute all compactness metrics for a GeoDataFrame of districts.

Automatically reprojects to an equal-area CRS (Mollweide) before calculating area-dependent metrics.

Parameters:
  • gdf (GeoDataFrame) – Districts with geometry.

  • district_id_col (str) – Column name for district identifiers.

  • geometry_col (str) – Column name for geometry.

Returns:

  • DataFrame with columns (district_id, polsby_popper, reock,)

  • convex_hull_ratio, schwartzberg.

Return type:

pd.DataFrame

siege_utilities.data.compare_plans(plan_a, plan_b, population_col='TOTPOP', district_id_col='GEOID')[source]

Compare two redistricting plans on key metrics.

Parameters:
  • plan_a (GeoDataFrame) – District plans with population and geometry.

  • plan_b (GeoDataFrame) – District plans with population and geometry.

  • population_col (str) – Column with total population.

  • district_id_col (str) – Column with district identifier.

Return type:

dict with comparison metrics.

siege_utilities.data.demographic_profile(plan_gdf, census_gdf, district_id_col='GEOID', population_cols=None)[source]

Overlay Census ACS data onto a redistricting plan.

Performs a spatial join between district geometries and Census block/tract geometries, then aggregates demographics per district.

Parameters:
  • plan_gdf (GeoDataFrame) – District plan with geometries.

  • census_gdf (GeoDataFrame) – Census data with geometries and demographic columns.

  • district_id_col (str) – Column in plan_gdf identifying districts.

  • population_cols (list of str, optional) – Demographic columns to aggregate. Defaults to common ACS fields.

Return type:

DataFrame with one row per district and summed demographic columns.

siege_utilities.data.fetch_enacted_plan(state, chamber='congress', year=None, client=None)[source]

Fetch enacted district plan as GeoDataFrame with boundaries + attributes.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • chamber (str) – "congress", "state_senate", or "state_house".

  • year (str, optional) – Filter by year.

  • client (RDHClient, optional) – Existing client instance. Created from env vars if omitted.

Return type:

GeoDataFrame with district boundaries and attributes.

Raises:

FileNotFoundError – If no matching enacted plan dataset is found.

siege_utilities.data.fetch_precinct_results(state, year=None, client=None)[source]

Fetch precinct boundaries with election results as GeoDataFrame.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • year (str, optional) – Election year filter.

  • client (RDHClient, optional) – Existing client instance.

Return type:

GeoDataFrame with precinct boundaries and vote columns.

siege_utilities.data.fetch_cvap(state, year=None, geography='tract', client=None)[source]

Fetch CVAP (Citizen Voting Age Population) data as DataFrame.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • year (str, optional) – Data year filter.

  • geography (str) – Geographic level hint for filtering (e.g. "tract", "block_group").

  • client (RDHClient, optional) – Existing client instance.

Return type:

DataFrame with CVAP estimates.

siege_utilities.data.fetch_pl94171(state, year=None, geography='block', client=None)[source]

Fetch PL 94-171 redistricting population data as DataFrame.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • year (str, optional) – Decennial year filter.

  • geography (str) – Geographic level hint (e.g. "block", "tract").

  • client (RDHClient, optional) – Existing client instance.

Return type:

DataFrame with PL 94-171 population data.

siege_utilities.data.fetch_demographic_summary(state, year=None, client=None)[source]

Fetch ACS 5-year demographic summary by district.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • year (str, optional) – ACS year filter.

  • client (RDHClient, optional) – Existing client instance.

Return type:

DataFrame with ACS demographic columns.

class siege_utilities.data.CompactnessScores[source]

Bases: object

Compactness metrics for a district geometry.

district_id: str
polsby_popper: float = 0.0
reock: float = 0.0
convex_hull_ratio: float = 0.0
schwartzberg: float = 0.0
__init__(district_id, polsby_popper=0.0, reock=0.0, convex_hull_ratio=0.0, schwartzberg=0.0)
Parameters:
Return type:

None

class siege_utilities.data.Engine[source]

Bases: Enum

Supported DataFrame engine back-ends.

PANDAS = 'pandas'
DUCKDB = 'duckdb'
SPARK = 'spark'
POSTGIS = 'postgis'
class siege_utilities.data.DataFrameEngine[source]

Bases: ABC

Abstract base class for engine-agnostic DataFrame operations.

abstract property name: str

Return the canonical engine name.

abstractmethod read_csv(path, **kwargs)[source]

Read a CSV file and return a DataFrame-like object.

Parameters:
Return type:

Any

abstractmethod read_parquet(path, **kwargs)[source]

Read a Parquet file and return a DataFrame-like object.

Parameters:
Return type:

Any

abstractmethod query(sql, **kwargs)[source]

Execute sql and return a DataFrame-like object.

Back-end specific keyword arguments (e.g. table for DuckDB, connection for PostGIS) are passed through via kwargs.

Parameters:
Return type:

Any

abstractmethod to_pandas(df)[source]

Convert df to a pandas DataFrame.

Parameters:

df (Any)

Return type:

Any

abstractmethod groupby_agg(df, group_cols, agg_dict)[source]

Group df by group_cols and aggregate according to agg_dict.

agg_dict maps column names to aggregation function names ("sum", "mean", "count", "min", "max").

Parameters:
Return type:

Any

abstractmethod filter(df, condition)[source]

Return rows of df that satisfy condition.

The type of condition is engine-specific (boolean Series for pandas, SQL expression string for DuckDB/Spark, etc.).

Parameters:
Return type:

Any

abstractmethod join(left, right, on, how='inner')[source]

Join left and right on column(s) on using join type how.

Parameters:
Return type:

Any

abstractmethod read_spatial(path, *, crs=None, **kwargs)[source]

Read a spatial file (GeoJSON, Shapefile, GeoParquet, GPKG).

Returns an engine-native spatial DataFrame. crs defaults to EPSG:4326 (via geo.crs.get_default_crs).

Parameters:
Return type:

Any

abstractmethod spatial_join(left, right, how='inner', predicate='intersects', *, left_geom='geometry', right_geom='geometry')[source]

Spatial join using a geometric predicate.

predicate is one of: intersects, contains, within, touches, crosses, overlaps.

Parameters:
Return type:

Any

abstractmethod buffer(df, distance, geometry_col='geometry', *, crs=None)[source]

Buffer geometries by distance (in CRS units).

Returns a new DataFrame with the geometry column replaced by buffered geometries.

Parameters:
Return type:

Any

abstractmethod distance(df, other, geometry_col='geometry', other_geom='geometry')[source]

Compute distances between geometries.

other may be a DataFrame (row-aligned pairwise) or a single geometry (shapely object / WKT string).

Parameters:
Return type:

Any

abstractmethod to_geodataframe(df, geometry_col='geometry', *, crs=None)[source]

Convert df to a GeoPandas GeoDataFrame.

Always returns a geopandas.GeoDataFrame regardless of engine.

Parameters:
  • df (Any)

  • geometry_col (str)

  • crs (str | None)

Return type:

Any

from_geodataframe(gdf, geometry_col='geometry')[source]

Convert a GeoPandas GeoDataFrame to this engine’s native format.

The inverse of to_geodataframe(). Geometry is serialized as WKB-hex (DuckDB) or WKT (Spark/PostGIS) for engine-native spatial operations.

Subclasses should override this for engine-specific conversion. The default implementation returns the GeoDataFrame unchanged (suitable for PandasEngine).

Parameters:
  • gdf (Any)

  • geometry_col (str)

Return type:

Any

index_points(df, lat_col, lon_col, *, grid=None, resolution=None, level=None, as_token=True)[source]

Compute a grid (H3 or S2) cell ID for each row.

Default: round-trip through pandas. Engines that benefit from a native path (Spark via pandas UDF, DuckDB via SQL when the spatial extension is loaded) should override.

See siege_utilities.geo.grids.index_points() for the kwarg inference rules – pass resolution= for H3, level= for S2, or set grid= explicitly.

Returns the same engine-native frame type as df would have been augmented with – for the default, that’s a column appended to a pandas-converted copy. Engines that override return their native shape.

Parameters:
  • df (Any)

  • lat_col (str)

  • lon_col (str)

  • grid (str | None)

  • resolution (int | None)

  • level (int | None)

  • as_token (bool)

Return type:

Any

index_polygon(geometry, *, grid=None, resolution=None, level=None, max_cells=None, min_level=None, max_level=None, refine=True)[source]

Cover a polygon with grid cells.

Engine-agnostic by construction – the input is a single geometry, not a frame, so no engine-specific dispatch is required at this level. Provided here for API symmetry with index_points() and so consumer code can accept engine as a config knob and call engine.index_polygon(...) uniformly.

Parameters:
  • geometry (Any)

  • grid (str | None)

  • resolution (int | None)

  • level (int | None)

  • max_cells (int | None)

  • min_level (int | None)

  • max_level (int | None)

  • refine (bool)

Return type:

Any

point_in_polygon(points_df, polygons_df, *, points_geom='geometry', polygons_geom='geometry')[source]

Find which polygon each point falls within.

Delegates to spatial_join() with predicate='within'.

Parameters:
  • points_df (Any)

  • polygons_df (Any)

  • points_geom (str)

  • polygons_geom (str)

Return type:

Any

dissolve(df, by, geometry_col='geometry', **agg_kwargs)[source]

Dissolve/merge geometries grouped by by columns.

Default converts to GeoDataFrame and uses gdf.dissolve(). Engines may override for native performance.

Parameters:
Return type:

Any

load_polygons(path, *, geometry_col='geometry', format='auto', crs=None)[source]

Load polygon geometries from any supported spatial format.

Handles GeoJSON, Shapefile, GeoParquet, GPKG, WKT CSV. Returns engine-native DataFrame with a geometry column.

Parameters:
  • path (str) – Path to the spatial file.

  • geometry_col (str) – Name for the geometry column in the returned DataFrame. If the source data uses a different column name it will be renamed.

  • format (str) – Spatial format hint. "auto" (default) infers from the file extension. Explicit values: "geojson", "shapefile", "gpkg", "geopackage", "parquet", "geoparquet".

  • crs (str or None) – Target CRS. Defaults to EPSG:4326 via get_default_crs().

Return type:

Any

load_points(df, lat_col='lat', lon_col='lon', geometry_col='geometry')[source]

Create point geometries from latitude/longitude columns.

Adds a geometry column with WKT POINT strings (or native geometry objects, depending on engine). Engines may override for native point construction.

Parameters:
Return type:

Any

load_lines(path, *, geometry_col='geometry', format='auto', crs=None)[source]

Load line/multiline geometries from any supported spatial format.

Same interface as load_polygons() – works for roads, rivers, transit routes, or any linear feature.

Parameters:
  • path (str) – Path to the spatial file.

  • geometry_col (str) – Name for the geometry column in the returned DataFrame. If the source data uses a different column name it will be renamed.

  • format (str) – Spatial format hint. "auto" (default) infers from the file extension. Explicit values: "geojson", "shapefile", "gpkg", "geopackage", "parquet", "geoparquet".

  • crs (str or None) – Target CRS. Defaults to EPSG:4326 via get_default_crs().

Return type:

Any

assign_boundaries(points, polygons, *, point_geom='geometry', polygon_geom='geometry', how='left')[source]

Assign each point to its containing polygon(s).

Core DSTK replacement: given addresses (points) and boundaries (polygons), determine which boundary contains each address.

Default delegates to spatial_join() with predicate=’contains’ (reversed: polygon contains point). Engines may override for broadcast optimization or native spatial indexing.

Parameters:
  • points (Any)

  • polygons (Any)

  • point_geom (str)

  • polygon_geom (str)

  • how (str)

Return type:

Any

intersect_boundaries(poly_a, poly_b, *, a_geom='geometry', b_geom='geometry')[source]

Find overlapping regions between two polygon boundary sets.

Returns all pairs of polygons that intersect, with both sets of attributes. Useful for county-to-district apportionment or boundary change analysis.

Default delegates to spatial_join() with predicate=’intersects’. Engines may override to compute intersection geometry and area.

Parameters:
Return type:

Any

apportion(source_polys, target_polys, weight_col, *, source_geom='geometry', target_geom='geometry')[source]

Apportion a value from source polygons to target polygons by overlap.

Distributes weight_col from source to target proportional to the fraction of source area that overlaps each target. Example: apportion tract population to congressional districts.

Default: intersect, compute area ratios via GeoPandas, aggregate. Engines may override for native area computation (Sedona ST_Area, PostGIS ST_Area, etc.).

Parameters:
  • source_polys (Any)

  • target_polys (Any)

  • weight_col (str)

  • source_geom (str)

  • target_geom (str)

Return type:

Any

nearest(points, targets, *, k=1, max_distance=None, point_geom='geometry', target_geom='geometry')[source]

Find the k nearest targets for each point.

Default: brute-force via GeoPandas sjoin_nearest (supports k=1 only). Engines should override for native spatial indexing (Sedona KNN, PostGIS ST_DWithin + ORDER BY distance, DuckDB spatial) and k>1.

Raises:

NotImplementedError – If k > 1 (base implementation only supports single-nearest).

Parameters:
Return type:

Any

multi_assign(points, boundary_layers, *, point_geom='geometry')[source]

Assign points to multiple boundary layers simultaneously.

Calls assign_boundaries() for each layer and renames every polygon-side column to be {layer_name}_{column} so layers do not collide on shared schemas (TIGER layers share geoid / name columns – without prefixing, the second join silently overwrites the first layer’s columns).

Parameters:
  • points (DataFrame) – Point DataFrame.

  • boundary_layers (dict) – {layer_name: polygon_DataFrame}. Each polygon DataFrame must have a geometry column and identifying columns.

  • point_geom (str)

Returns:

Points enriched with {layer_name}_* columns for every polygon-side column from each boundary layer. Bare polygon column names never appear in the output.

Return type:

DataFrame

class siege_utilities.data.PandasEngine[source]

Bases: DataFrameEngine

Engine backed by pandas (and optionally GeoPandas).

property name: str

Return the canonical engine name.

read_csv(path, **kwargs)[source]

Read a CSV file.

**kwargs: Forwarded to pandas.read_csv().

Parameters:
Return type:

Any

read_parquet(path, **kwargs)[source]

Read a Parquet file.

**kwargs: Forwarded to pandas.read_parquet().

Parameters:
Return type:

Any

query(sql, **kwargs)[source]

Execute SQL via pandas.read_sql.

Requires a connection keyword argument (a SQLAlchemy engine or DBAPI2 connection).

Parameters:
Return type:

Any

to_pandas(df)[source]

Convert df to a pandas DataFrame.

Parameters:

df (Any)

Return type:

Any

groupby_agg(df, group_cols, agg_dict)[source]

Group df by group_cols and aggregate according to agg_dict.

agg_dict maps column names to aggregation function names ("sum", "mean", "count", "min", "max").

Parameters:
Return type:

Any

filter(df, condition)[source]

Return rows of df that satisfy condition.

The type of condition is engine-specific (boolean Series for pandas, SQL expression string for DuckDB/Spark, etc.).

Parameters:
Return type:

Any

join(left, right, on, how='inner')[source]

Join left and right on column(s) on using join type how.

Parameters:
Return type:

Any

read_spatial(path, *, crs=None, **kwargs)[source]

Read a spatial file.

**kwargs: Forwarded to geopandas.read_file().

Parameters:
Return type:

Any

spatial_join(left, right, how='inner', predicate='intersects', *, left_geom='geometry', right_geom='geometry')[source]

Spatial join using a geometric predicate.

predicate is one of: intersects, contains, within, touches, crosses, overlaps.

buffer(df, distance, geometry_col='geometry', *, crs=None)[source]

Buffer geometries by distance (in CRS units).

Returns a new DataFrame with the geometry column replaced by buffered geometries.

distance(df, other, geometry_col='geometry', other_geom='geometry')[source]

Compute distances between geometries.

other may be a DataFrame (row-aligned pairwise) or a single geometry (shapely object / WKT string).

to_geodataframe(df, geometry_col='geometry', *, crs=None)[source]

Convert df to a GeoPandas GeoDataFrame.

Always returns a geopandas.GeoDataFrame regardless of engine.

class siege_utilities.data.DuckDBEngine[source]

Bases: DataFrameEngine

Engine backed by DuckDB.

__init__(**kwargs)[source]
Parameters:

kwargs (Any)

Return type:

None

property name: str

Return the canonical engine name.

read_csv(path, **kwargs)[source]

Read a CSV via DuckDB’s read_csv_auto.

Engine-specific kwargs (DuckDB’s read_csv_auto options like header, delim, columns) are not yet forwarded; pass the equivalent options inside a custom SQL via query() if you need to override auto-detection.

Parameters:
Return type:

Any

read_parquet(path, **kwargs)[source]

Read a Parquet file via DuckDB’s read_parquet.

Engine-specific kwargs are not yet forwarded; use query() for DuckDB-specific options.

Parameters:
Return type:

Any

query(sql, **kwargs)[source]

Execute sql against the DuckDB connection.

If a table keyword is supplied together with a pandas DataFrame value, that DataFrame is first registered as a virtual table so it can be referenced in sql.

Parameters:
Return type:

Any

to_pandas(df)[source]

Convert df to a pandas DataFrame.

Parameters:

df (Any)

Return type:

Any

groupby_agg(df, group_cols, agg_dict)[source]

Group df by group_cols and aggregate according to agg_dict.

agg_dict maps column names to aggregation function names ("sum", "mean", "count", "min", "max").

Parameters:
Return type:

Any

filter(df, condition)[source]

Return rows of df that satisfy condition.

The type of condition is engine-specific (boolean Series for pandas, SQL expression string for DuckDB/Spark, etc.).

Parameters:
Return type:

Any

join(left, right, on, how='inner')[source]

Join left and right on column(s) on using join type how.

Parameters:
Return type:

Any

read_spatial(path, *, crs=None, **kwargs)[source]

Read a spatial file via DuckDB’s ST_Read.

**kwargs: Forwarded to geopandas.read_file() (unused in the current DuckDB path but accepted for interface compatibility).

Parameters:
Return type:

Any

spatial_join(left, right, how='inner', predicate='intersects', *, left_geom='geometry', right_geom='geometry')[source]

Spatial join using a geometric predicate.

predicate is one of: intersects, contains, within, touches, crosses, overlaps.

buffer(df, distance, geometry_col='geometry', *, crs=None)[source]

Buffer geometries by distance (in CRS units).

Returns a new DataFrame with the geometry column replaced by buffered geometries.

distance(df, other, geometry_col='geometry', other_geom='geometry')[source]

Compute distances between geometries.

other may be a DataFrame (row-aligned pairwise) or a single geometry (shapely object / WKT string).

to_geodataframe(df, geometry_col='geometry', *, crs=None)[source]

Convert df to a GeoPandas GeoDataFrame.

Always returns a geopandas.GeoDataFrame regardless of engine.

from_geodataframe(gdf, geometry_col='geometry')[source]

Convert GeoDataFrame to a DuckDB pandas DataFrame with WKB-hex geometry.

class siege_utilities.data.SparkEngine[source]

Bases: DataFrameEngine

Engine backed by PySpark.

Parameters:

spark (optional) – An existing SparkSession. When None the engine creates or retrieves the active session on first use.

__init__(spark=None, *, enable_sedona=False)[source]
Parameters:
Return type:

None

property name: str

Return the canonical engine name.

read_csv(path, **kwargs)[source]

Read a CSV file via Spark.

**kwargs: Forwarded to Spark’s DataFrameReader.csv().

Parameters:
Return type:

Any

read_parquet(path, **kwargs)[source]

Read a Parquet file via Spark.

**kwargs: Forwarded to Spark’s DataFrameReader.parquet().

Parameters:
Return type:

Any

query(sql, **kwargs)[source]

Execute sql via Spark SQL.

SparkEngine.query accepts no engine-specific kwargs; self._session.sql() takes no options beyond the SQL text. The **kwargs parameter is present for interface symmetry with DuckDBEngine (which accepts table / df) and PostGISEngine (which accepts geom_col). A non-empty kwargs is a caller error and is raised to surface the mistake rather than silently ignored.

Parameters:
Return type:

Any

to_pandas(df)[source]

Convert df to a pandas DataFrame.

Parameters:

df (Any)

Return type:

Any

groupby_agg(df, group_cols, agg_dict)[source]

Group df by group_cols and aggregate according to agg_dict.

agg_dict maps column names to aggregation function names ("sum", "mean", "count", "min", "max").

Parameters:
Return type:

Any

filter(df, condition)[source]

Return rows of df that satisfy condition.

The type of condition is engine-specific (boolean Series for pandas, SQL expression string for DuckDB/Spark, etc.).

Parameters:
Return type:

Any

join(left, right, on, how='inner')[source]

Join left and right on column(s) on using join type how.

Parameters:
Return type:

Any

read_spatial(path, *, crs=None, **kwargs)[source]

Read a spatial file via GeoPandas and convert to a Spark DataFrame.

**kwargs: Forwarded to geopandas.read_file().

Parameters:
Return type:

Any

spatial_join(left, right, how='inner', predicate='intersects', *, left_geom='geometry', right_geom='geometry')[source]

Spatial join using a geometric predicate.

predicate is one of: intersects, contains, within, touches, crosses, overlaps.

buffer(df, distance, geometry_col='geometry', *, crs=None)[source]

Buffer geometries by distance (in CRS units).

Returns a new DataFrame with the geometry column replaced by buffered geometries.

distance(df, other, geometry_col='geometry', other_geom='geometry')[source]

Compute distances between geometries.

other may be a DataFrame (row-aligned pairwise) or a single geometry (shapely object / WKT string).

to_geodataframe(df, geometry_col='geometry', *, crs=None)[source]

Convert df to a GeoPandas GeoDataFrame.

Always returns a geopandas.GeoDataFrame regardless of engine.

load_polygons(path, *, geometry_col='geometry', format='auto', crs=None)[source]

Load polygons – Spark-native parquet/json reader.

load_points(df, lat_col='lat', lon_col='lon', geometry_col='geometry')[source]

Create point geometries – Spark-native WKT string construction.

assign_boundaries(points, polygons, *, point_geom='geometry', polygon_geom='geometry', how='left')[source]

Point-in-polygon – Sedona ST_Contains with optional broadcast.

nearest(points, targets, *, k=1, max_distance=None, point_geom='geometry', target_geom='geometry')[source]

K-nearest – Sedona ST_Distance with window function.

class siege_utilities.data.PostGISEngine[source]

Bases: DataFrameEngine

Engine backed by PostGIS via SQLAlchemy + GeoPandas.

Parameters:

connection_string (str) – A SQLAlchemy connection string, e.g. "postgresql://user:pass@host:5432/dbname".

__init__(connection_string)[source]
Parameters:

connection_string (str)

Return type:

None

property name: str

Return the canonical engine name.

index_points(df, lat_col, lon_col, *, grid=None, resolution=None, level=None, as_token=True)[source]

PostGIS does not natively compute H3 / S2 cell IDs.

For S2-keyed warehouse joins, the recommended pattern is:

  1. Compute cell IDs ONCE during ingest (e.g. via the PandasEngine or in your ETL job using s2_index_points).

  2. Store as a BIGINT column on the row.

  3. For region queries, generate (range_min, range_max) pairs via s2_cells_to_ranges() and filter with WHERE s2_cell BETWEEN range_min AND range_max.

Computing cell IDs at query time over PostGIS would defeat the whole point – the database can’t index a value it has to compute per-row. Hence this engine raises rather than silently degrade.

Parameters:
  • df (Any)

  • lat_col (str)

  • lon_col (str)

  • grid (str | None)

  • resolution (int | None)

  • level (int | None)

  • as_token (bool)

Return type:

Any

read_csv(path, **kwargs)[source]

Read a CSV file.

**kwargs: Forwarded to pandas.read_csv().

Parameters:
Return type:

Any

read_parquet(path, **kwargs)[source]

Read a parquet file. Returns a GeoDataFrame when the file carries GeoParquet metadata, a plain DataFrame otherwise.

The decision is by file inspection (pyarrow schema metadata), not by exception-fallback. An earlier implementation tried gpd.read_parquet first and silently fell back to pd.read_parquet on any exception – that pattern hid real gpd errors (a corrupt geoparquet file, an out-of-date geopandas) as “this must not be a geoparquet file.” The current path checks the parquet metadata directly so the decision is explicit.

Parameters:
Return type:

Any

query(sql, **kwargs)[source]

Execute sql against the PostGIS database.

Pass geom_col="<column>" to indicate a geometry column in the result; the query then returns a GeoDataFrame with that column parsed as geometry. Omit geom_col (or pass an empty string) for queries that return no geometry; the result is a plain DataFrame.

The Geo-vs-plain decision is made by the caller (via the presence of geom_col), not by exception-fallback. An earlier implementation tried gpd.read_postgis first and silently fell back to pd.read_sql on any exception – that pattern hid real PostGIS errors (a malformed SQL, a missing column) as “this must not have a geometry column.”

Parameters:
Return type:

Any

to_pandas(df)[source]

Convert df to a pandas DataFrame.

Parameters:

df (Any)

Return type:

Any

groupby_agg(df, group_cols, agg_dict)[source]

Group df by group_cols and aggregate according to agg_dict.

agg_dict maps column names to aggregation function names ("sum", "mean", "count", "min", "max").

Parameters:
Return type:

Any

filter(df, condition)[source]

Return rows of df that satisfy condition.

The type of condition is engine-specific (boolean Series for pandas, SQL expression string for DuckDB/Spark, etc.).

Parameters:
Return type:

Any

join(left, right, on, how='inner')[source]

Join left and right on column(s) on using join type how.

Parameters:
Return type:

Any

read_spatial(path, *, crs=None, **kwargs)[source]

Read a spatial file or execute a spatial SQL query.

**kwargs: Forwarded to geopandas.read_file().

Parameters:
Return type:

Any

spatial_join(left, right, how='inner', predicate='intersects', *, left_geom='geometry', right_geom='geometry')[source]

Spatial join using a geometric predicate.

predicate is one of: intersects, contains, within, touches, crosses, overlaps.

buffer(df, distance, geometry_col='geometry', *, crs=None)[source]

Buffer geometries by distance (in CRS units).

Returns a new DataFrame with the geometry column replaced by buffered geometries.

distance(df, other, geometry_col='geometry', other_geom='geometry')[source]

Compute distances between geometries.

other may be a DataFrame (row-aligned pairwise) or a single geometry (shapely object / WKT string).

to_geodataframe(df, geometry_col='geometry', *, crs=None)[source]

Convert df to a GeoPandas GeoDataFrame.

Always returns a geopandas.GeoDataFrame regardless of engine.

siege_utilities.data.get_engine(name, **kwargs)[source]

Return an engine instance for the requested back-end.

Parameters:
  • name (str or Engine) – One of "pandas", "duckdb", "spark", "postgis" (or the corresponding Engine enum member).

  • **kwargs – Passed to the engine constructor (e.g. connection_string for PostGIS, spark for Spark).

Return type:

DataFrameEngine

Submodules

Deprecation shim — re-exports from siege_utilities.data.statistics.cross_tabulation.

Moved during ELE-2437 (statistics primitives grouped under data/statistics/). Will be removed in v4.0.0.

class siege_utilities.data.cross_tabulation.ChiSquareResult[source]

Bases: object

Result of a chi-square test of independence.

statistic

Chi-square test statistic.

Type:

float

p_value

p-value under the null hypothesis of independence.

Type:

float

dof

Degrees of freedom.

Type:

int

expected_freq

Expected frequency table under independence.

Type:

pandas.DataFrame

statistic: float
p_value: float
dof: int
expected_freq: pandas.DataFrame
__init__(statistic, p_value, dof, expected_freq)
Parameters:
Return type:

None

class siege_utilities.data.cross_tabulation.CrossTabSpec[source]

Bases: object

Specification for a cross-tabulation operation.

row_var

Column name for rows.

Type:

str

col_var

Column name for columns.

Type:

str

value_var

Column to aggregate (default None → counts).

Type:

str | None

weight_var

Optional weight column.

Type:

str | None

aggfunc

Aggregation function name ('sum', 'mean', 'count').

Type:

str

geography

Optional geography column for stratified cross-tabs.

Type:

str | None

row_var: str
col_var: str
value_var: str | None = None
weight_var: str | None = None
aggfunc: str = 'sum'
geography: str | None = None
__init__(row_var, col_var, value_var=None, weight_var=None, aggfunc='sum', geography=None)
Parameters:
  • row_var (str)

  • col_var (str)

  • value_var (str | None)

  • weight_var (str | None)

  • aggfunc (str)

  • geography (str | None)

Return type:

None

siege_utilities.data.cross_tabulation.chi_square_test(ct)[source]

Run a chi-square test of independence on a contingency table.

Parameters:

ct (DataFrame) – Contingency table (without margins). If margins are present (row/column named 'Total'), they are stripped automatically.

Return type:

ChiSquareResult

siege_utilities.data.cross_tabulation.contingency_table(df, row_var, col_var, value_var=None, weight_var=None, aggfunc='sum', margins=True)[source]

Build a contingency table (cross-tabulation) from df.

Parameters:
  • df (DataFrame) – Input data.

  • row_var (str) – Column for rows.

  • col_var (str) – Column for columns.

  • value_var (str, optional) – Column to aggregate. If None, counts occurrences.

  • weight_var (str, optional) – Column of weights (multiplied into value_var before aggregation).

  • aggfunc (str) – Aggregation function: 'sum', 'mean', or 'count'.

  • margins (bool) – If True, add row/column totals.

Return type:

DataFrame with row_var as index, col_var as columns, and aggregated values.

siege_utilities.data.cross_tabulation.moe_cross_tab(estimates_df, moes_df, row_var, col_var, value_var, moe_var)[source]

Propagate ACS margins of error through a cross-tabulation.

Computes the MOE of each cell sum using the Census Bureau’s root-sum-of- squares rule. Returns a DataFrame shaped like the contingency table with the propagated MOE in each cell.

Parameters:
  • estimates_df (DataFrame) – Data with estimate values.

  • moes_df (DataFrame) – Data with corresponding MOE values (same structure as estimates_df).

  • row_var (str) – Row and column variables.

  • col_var (str) – Row and column variables.

  • value_var (str) – Name of the estimate column in estimates_df.

  • moe_var (str) – Name of the MOE column in moes_df.

Return type:

DataFrame of propagated MOEs with shape matching the contingency table.

siege_utilities.data.cross_tabulation.rate_table(ct, normalize='all')[source]

Normalize a contingency table to proportions.

Parameters:
  • ct (DataFrame) – Contingency table (output of contingency_table()).

  • normalize (str) – 'all' (divide by grand total), 'index' (row proportions), 'columns' (column proportions).

Return type:

DataFrame of proportions summing to 1.0 along the requested axis.

Raises:
  • ZeroDivisionError – When the grand total, any row sum, or any column sum is zero (normalization is mathematically undefined).

  • ValueError – When normalize is not one of the accepted values.

Deprecation shim — re-exports from siege_utilities.data.statistics.moe_propagation.

Moved during ELE-2437. Will be removed in v4.0.0.

class siege_utilities.data.moe_propagation.Estimate[source]

Bases: object

An ACS estimate with its margin of error at 90% confidence.

value: float
moe: float
property se: float

Standard error (MOE / 1.645).

property cv: float

Coefficient of variation (SE / |estimate|), as a proportion.

property cv_pct: float

Coefficient of variation as a percentage.

is_reliable(cv_threshold=0.4)[source]

Return True if CV is below cv_threshold (default 40%).

Parameters:

cv_threshold (float)

Return type:

bool

confidence_interval(confidence=0.9)[source]

Return (lower, upper) bounds at the given confidence level.

Parameters:

confidence (float) – Confidence level. Only 0.90 (default, uses stored MOE directly) and 0.95 are supported.

Return type:

tuple[float, float]

__init__(value, moe)
Parameters:
Return type:

None

siege_utilities.data.moe_propagation.flag_unreliable(estimates, cv_threshold=0.4)[source]

Return indices and Estimates that exceed cv_threshold.

Parameters:
  • estimates (sequence of Estimate)

  • cv_threshold (float) – Maximum acceptable CV (default 0.40 = 40%).

Return type:

list of (index, Estimate)

siege_utilities.data.moe_propagation.moe_difference(a, b)[source]

Propagate MOE through the difference a - b.

Uses the same root-sum-of-squares formula as moe_sum().

Parameters:
Return type:

Estimate

siege_utilities.data.moe_propagation.moe_product(a, b)[source]

Propagate MOE through a product a * b (independent estimates).

Uses the delta-method approximation:

SE_ab = sqrt(a^2 * SE_b^2 + b^2 * SE_a^2)

Parameters:
Return type:

Estimate

siege_utilities.data.moe_propagation.moe_proportion(numerator, denominator)[source]

Propagate MOE for a proportion (numerator / denominator).

Uses the Census Bureau’s recommended formula for proportions where the numerator is a subset of the denominator:

SE_p = sqrt(SE_num^2 - p^2 * SE_den^2) / denominator

If the radicand is negative (which can happen with small samples), falls back to the conservative approximation:

SE_p = sqrt(SE_num^2 + p^2 * SE_den^2) / denominator

Parameters:
  • numerator (Estimate) – Must be a subset of denominator.

  • denominator (Estimate) – The total.

Returns:

Proportion with propagated MOE, bounded to [0, 1].

Return type:

Estimate

Raises:

ZeroDivisionError – If the denominator estimate is zero.

siege_utilities.data.moe_propagation.moe_ratio(numerator, denominator)[source]

Propagate MOE for a ratio of non-nested estimates.

Uses the delta method:

SE_r = sqrt(SE_num^2 + r^2 * SE_den^2) / denominator

Parameters:
  • numerator (Estimate) – Independent (non-nested) estimates.

  • denominator (Estimate) – Independent (non-nested) estimates.

Return type:

Estimate

Raises:

ZeroDivisionError – If the denominator estimate is zero.

siege_utilities.data.moe_propagation.moe_sum(estimates)[source]

Propagate MOE through a sum of independent ACS estimates.

MOE_sum = sqrt(sum(MOE_i^2))

Parameters:

estimates (sequence of Estimate) – The addends.

Returns:

Combined estimate with propagated MOE.

Return type:

Estimate

Deprecation shim — re-exports from siege_utilities.reference.naics_soc_crosswalk.

Moved during ELE-2437 (reference data grouped under reference/). Will be removed in v4.0.0.

class siege_utilities.data.naics_soc_crosswalk.NAICSCode[source]

Bases: object

A NAICS industry code with hierarchy metadata.

code: str
title: str
level: int
parent_code: str | None = None
property sector: str

2-digit sector code.

__init__(code, title, level, parent_code=None)
Parameters:
  • code (str)

  • title (str)

  • level (int)

  • parent_code (str | None)

Return type:

None

siege_utilities.data.naics_soc_crosswalk.parse_naics(code)[source]

Parse a NAICS code string into a NAICSCode.

Parameters:

code (str) – 2-6 digit NAICS code.

Return type:

NAICSCode

Raises:

ValueError – If the code is not 2-6 digits.

siege_utilities.data.naics_soc_crosswalk.naics_ancestors(code)[source]

Return ancestor codes from sector down to the given code.

>>> naics_ancestors("541511")
['54', '541', '5415', '54151', '541511']
Parameters:

code (str)

Return type:

list[str]

siege_utilities.data.naics_soc_crosswalk.naics_to_sector(code)[source]

Return (sector_code, sector_title) for any NAICS code.

Parameters:

code (str)

Return type:

tuple[str, str]

siege_utilities.data.naics_soc_crosswalk.crosswalk_naics(code, from_year=2017, to_year=2022)[source]

Map a NAICS code from one revision to another.

Parameters:
  • code (str) – Source NAICS code.

  • from_year (int) – Source revision year (2012 or 2017).

  • to_year (int) – Target revision year (2017 or 2022).

Returns:

Target code(s). May be >1 if the source was split. Returns [code] if no mapping is found (assumed unchanged).

Return type:

list of str

class siege_utilities.data.naics_soc_crosswalk.SOCCode[source]

Bases: object

A Standard Occupational Classification code.

code: str
title: str
level: str
property major_group: str

2-digit major group (e.g., “11” from “11-1011”).

__init__(code, title, level)
Parameters:
Return type:

None

siege_utilities.data.naics_soc_crosswalk.parse_soc(code)[source]

Parse an SOC code string.

Parameters:

code (str) – SOC code in "XX-XXXX" format or just the major group "XX".

Return type:

SOCCode

siege_utilities.data.naics_soc_crosswalk.soc_to_major_group(code)[source]

Return (major_code, title) for any SOC code.

Parameters:

code (str)

Return type:

tuple[str, str]

siege_utilities.data.naics_soc_crosswalk.fuzzy_match_naics(text, candidates=None, threshold=0.5)[source]

Simple token-overlap fuzzy match of text against NAICS sector titles.

Parameters:
  • text (str) – Free-text industry description (e.g., from NLRB filings).

  • candidates (dict, optional) – {code: title} mapping. Defaults to NAICS_SECTORS.

  • threshold (float) – Minimum similarity score (0-1) to include.

Returns:

Sorted by score descending.

Return type:

list of (code, title, score)

siege_utilities.data.naics_soc_crosswalk.get_naics_lookup()[source]

Return a combined NAICS lookup table (sectors + subsectors).

Returns:

Dict mapping NAICS codes to titles at all available levels.

Return type:

dict[str, str]

siege_utilities.data.naics_soc_crosswalk.get_soc_lookup()[source]

Return a combined SOC lookup table (major groups + minor groups).

Returns:

Dict mapping SOC codes to titles at all available levels.

Return type:

dict[str, str]

siege_utilities.data.naics_soc_crosswalk.naics_title(code)[source]

Look up the title for a NAICS code at any level.

Checks subsector-level first, then falls back to sector. Returns ‘Unknown’ if not found.

Parameters:

code (str)

Return type:

str

siege_utilities.data.naics_soc_crosswalk.soc_title(code)[source]

Look up the title for an SOC code at any level.

Checks minor-group level first, then falls back to major group. Returns ‘Unknown’ if not found.

Parameters:

code (str)

Return type:

str

siege_utilities.data.naics_soc_crosswalk.filter_by_naics(records, naics_prefix)[source]

Filter NLRB case records by NAICS code prefix.

Works with any iterable of objects that have a naics_code attribute (NLRBCaseRecord, NLRBCase model instances, or dicts with ‘naics_code’).

Parameters:
  • records – Iterable of records to filter.

  • naics_prefix (str) – NAICS prefix to match (e.g., ‘622’ for hospitals, ‘62’ for all health care).

Returns:

List of matching records.

Return type:

list

Example:

# Show all elections in NAICS 622 (hospitals)
hospital_cases = filter_by_naics(result.cases, "622")
siege_utilities.data.naics_soc_crosswalk.filter_by_naics_sector(records, sector_code)[source]

Filter records by 2-digit NAICS sector code.

Parameters:

sector_code (str)

Return type:

list

siege_utilities.data.naics_soc_crosswalk.group_by_naics_sector(records)[source]

Group records by their 2-digit NAICS sector code.

Returns:

Dict mapping sector codes to lists of records. Records without a NAICS code are grouped under ‘’.

Return type:

dict[str, list]

Deprecation shim — re-exports from siege_utilities.geo.providers.redistricting_data_hub.

RDH is an external spatial data source; moved to geo/providers/ under ELE-2438 (D3) so all spatial sources live in one place. Will be removed in v4.0.0.

class siege_utilities.data.redistricting_data_hub.RDHClient[source]

Bases: object

Client for the Redistricting Data Hub download API.

Parameters:
  • username (str, optional) – RDH account email. Falls back to RDH_USERNAME env var.

  • password (str, optional) – RDH account password. Falls back to RDH_PASSWORD env var.

  • base_url (str, optional) – Override API endpoint (for testing).

  • cache_dir (Path or str, optional) – Directory for caching downloaded files. Defaults to ~/.cache/siege_utilities/rdh/.

__init__(username=None, password=None, base_url='https://redistrictingdatahub.org/wp-json/download/list', cache_dir=None)[source]
Parameters:
  • username (str | None)

  • password (str | None)

  • base_url (str)

  • cache_dir (str | Path | None)

close()[source]

Close the underlying HTTP session.

Return type:

None

credentials_present()[source]

Return True if a username and password are both set.

This is a presence check only – it does NOT contact RDH or verify that the credentials authenticate. A live check is impractical here: the RDH catalog endpoint is WAF-gated to allowlisted ingest hosts (#1115), so from other hosts an authenticated-style request returns the website HTML rather than the JSON API. Callers needing a true liveness check should call list_datasets() and handle the ValueError it now raises on an API error envelope.

Return type:

bool

validate_credentials()[source]

Backward-compatible alias for credentials_present().

Retained for existing callers. The name is a misnomer kept for compatibility: it checks credential presence, not live authentication (see credentials_present()). (#1115)

Return type:

bool

list_datasets(states=None, format=None, year=None, dataset_type=None, geography=None, official=None)[source]

List available datasets with optional filters.

Parameters:
  • states (list of str, optional) – State abbreviations to filter (e.g. ["VA", "MD"]). Due to API limits, queries more than 4 states sequentially.

  • format (str, optional) – "csv" or "shp".

  • year (str, optional) – Four-digit year.

  • dataset_type (str, optional) – Filter keyword in title.

  • geography (str, optional) – Geographic level filter.

  • official (bool, optional) – If True, only return official/enacted datasets.

Return type:

list of RDHDataset

download_dataset(dataset, output_dir=None, use_cache=True)[source]

Download a dataset file.

Parameters:
  • dataset (RDHDataset or str) – Dataset object or direct download URL.

  • output_dir (Path, optional) – Where to save. Defaults to cache_dir.

  • use_cache (bool) – If True, skip download if file already exists.

Return type:

Path to the downloaded file (or extracted directory for ZIPs).

load_csv(dataset, **kwargs)[source]

Download (if needed) and load a CSV dataset into a DataFrame.

Parameters:
  • dataset (RDHDataset, str, or Path) – Dataset to load.

  • **kwargs – Passed to pd.read_csv().

Return type:

pd.DataFrame

load_shapefile(dataset, **kwargs)[source]

Download (if needed) and load a shapefile into a GeoDataFrame.

Parameters:
  • dataset (RDHDataset, str, or Path) – Dataset to load.

  • **kwargs – Passed to gpd.read_file().

Return type:

gpd.GeoDataFrame

get_enacted_plans(state, chamber='congress', year=None, format='shp')[source]

Find enacted redistricting plan datasets for a state.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • chamber (str) – "congress", "state_senate", or "state_house".

  • year (str, optional) – Filter by year.

  • format (str, default "shp") – Dataset format filter. Only "shp" is loadable via load_shapefile(); other formats are returned as metadata only.

Return type:

List[RDHDataset]

get_precinct_data(state, year=None, format='shp')[source]

Find precinct-level datasets for a state.

Parameters:
Return type:

List[RDHDataset]

get_cvap_data(state, year=None)[source]

Find CVAP (Citizen Voting Age Population) datasets.

Parameters:
  • state (str)

  • year (str | None)

Return type:

List[RDHDataset]

get_pl94171_data(state, year=None)[source]

Find PL 94-171 redistricting data datasets.

Parameters:
  • state (str)

  • year (str | None)

Return type:

List[RDHDataset]

static to_crosstab_input(df, geography_col='GEOID', variable_cols=None)[source]

Reshape an RDH DataFrame into long format for cross-tabulation.

Returns a DataFrame with columns [geography, variable, value] suitable for use with siege_utilities.data.cross_tabulation.contingency_table().

Parameters:
  • df (DataFrame) – Wide-format data (one row per geography, numeric columns as variables).

  • geography_col (str) – Column identifying the geographic unit.

  • variable_cols (list of str, optional) – Columns to melt. If None, all numeric columns except the geography column are used.

Return type:

DataFrame with columns geography, variable, value.

class siege_utilities.data.redistricting_data_hub.RDHDataset[source]

Bases: object

Metadata for a single RDH dataset entry.

title: str
url: str
state: str = ''
format: str = ''
year: str = ''
geography: str = ''
dataset_type: str = ''
official: bool = False
file_size: str = ''
raw: Dict[str, Any]
property filename: str

Extract filename from URL.

property is_shapefile: bool
property is_csv: bool
__init__(title, url, state='', format='', year='', geography='', dataset_type='', official=False, file_size='', raw=<factory>)
Parameters:
Return type:

None

class siege_utilities.data.redistricting_data_hub.RDHDataFormat[source]

Bases: str, Enum

File format for RDH downloads.

CSV = 'csv'
SHAPEFILE = 'shp'
__new__(value)
class siege_utilities.data.redistricting_data_hub.RDHDatasetType[source]

Bases: str, Enum

Categories of RDH datasets.

PL94171 = 'pl94171'
CVAP = 'cvap'
ACS5 = 'acs5'
ELECTION = 'election'
LEGISLATIVE = 'legislative'
PRECINCT = 'precinct'
VOTER_FILE = 'voter_file'
PROJECTION = 'projection'
__new__(value)
siege_utilities.data.redistricting_data_hub.polsby_popper(geometry, source_crs=None)[source]

Compute Polsby-Popper compactness score for a geometry.

Score = (4 * pi * area) / perimeter^2 Range: 0 (least compact) to 1 (circle).

Parameters:
  • geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.

  • source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area before calculation. Pass the CRS (e.g., “EPSG:4326”) when calling with unprojected data.

Return type:

float

siege_utilities.data.redistricting_data_hub.reock(geometry, source_crs=None)[source]

Compute Reock compactness score for a geometry.

Score = area / area_of_minimum_bounding_circle Range: 0 (least compact) to 1 (circle).

Parameters:
  • geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.

  • source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area.

Return type:

float

siege_utilities.data.redistricting_data_hub.convex_hull_ratio(geometry, source_crs=None)[source]

Compute convex hull ratio for a geometry.

Score = area / convex_hull_area Range: 0 to 1.

Parameters:
  • geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.

  • source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area.

Return type:

float

siege_utilities.data.redistricting_data_hub.schwartzberg(geometry, source_crs=None)[source]

Compute Schwartzberg compactness score.

Score = 1 / (perimeter / circumference_of_equal_area_circle) Range: 0 to 1.

Parameters:
  • geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.

  • source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area.

Return type:

float

siege_utilities.data.redistricting_data_hub.compute_compactness(gdf, district_id_col='GEOID', geometry_col='geometry')[source]

Compute all compactness metrics for a GeoDataFrame of districts.

Automatically reprojects to an equal-area CRS (Mollweide) before calculating area-dependent metrics.

Parameters:
  • gdf (GeoDataFrame) – Districts with geometry.

  • district_id_col (str) – Column name for district identifiers.

  • geometry_col (str) – Column name for geometry.

Returns:

  • DataFrame with columns (district_id, polsby_popper, reock,)

  • convex_hull_ratio, schwartzberg.

Return type:

pd.DataFrame

class siege_utilities.data.redistricting_data_hub.CompactnessScores[source]

Bases: object

Compactness metrics for a district geometry.

district_id: str
polsby_popper: float = 0.0
reock: float = 0.0
convex_hull_ratio: float = 0.0
schwartzberg: float = 0.0
__init__(district_id, polsby_popper=0.0, reock=0.0, convex_hull_ratio=0.0, schwartzberg=0.0)
Parameters:
Return type:

None

siege_utilities.data.redistricting_data_hub.compare_plans(plan_a, plan_b, population_col='TOTPOP', district_id_col='GEOID')[source]

Compare two redistricting plans on key metrics.

Parameters:
  • plan_a (GeoDataFrame) – District plans with population and geometry.

  • plan_b (GeoDataFrame) – District plans with population and geometry.

  • population_col (str) – Column with total population.

  • district_id_col (str) – Column with district identifier.

Return type:

dict with comparison metrics.

siege_utilities.data.redistricting_data_hub.demographic_profile(plan_gdf, census_gdf, district_id_col='GEOID', population_cols=None)[source]

Overlay Census ACS data onto a redistricting plan.

Performs a spatial join between district geometries and Census block/tract geometries, then aggregates demographics per district.

Parameters:
  • plan_gdf (GeoDataFrame) – District plan with geometries.

  • census_gdf (GeoDataFrame) – Census data with geometries and demographic columns.

  • district_id_col (str) – Column in plan_gdf identifying districts.

  • population_cols (list of str, optional) – Demographic columns to aggregate. Defaults to common ACS fields.

Return type:

DataFrame with one row per district and summed demographic columns.

siege_utilities.data.redistricting_data_hub.fetch_enacted_plan(state, chamber='congress', year=None, client=None)[source]

Fetch enacted district plan as GeoDataFrame with boundaries + attributes.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • chamber (str) – "congress", "state_senate", or "state_house".

  • year (str, optional) – Filter by year.

  • client (RDHClient, optional) – Existing client instance. Created from env vars if omitted.

Return type:

GeoDataFrame with district boundaries and attributes.

Raises:

FileNotFoundError – If no matching enacted plan dataset is found.

siege_utilities.data.redistricting_data_hub.fetch_precinct_results(state, year=None, client=None)[source]

Fetch precinct boundaries with election results as GeoDataFrame.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • year (str, optional) – Election year filter.

  • client (RDHClient, optional) – Existing client instance.

Return type:

GeoDataFrame with precinct boundaries and vote columns.

siege_utilities.data.redistricting_data_hub.fetch_cvap(state, year=None, geography='tract', client=None)[source]

Fetch CVAP (Citizen Voting Age Population) data as DataFrame.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • year (str, optional) – Data year filter.

  • geography (str) – Geographic level hint for filtering (e.g. "tract", "block_group").

  • client (RDHClient, optional) – Existing client instance.

Return type:

DataFrame with CVAP estimates.

siege_utilities.data.redistricting_data_hub.fetch_pl94171(state, year=None, geography='block', client=None)[source]

Fetch PL 94-171 redistricting population data as DataFrame.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • year (str, optional) – Decennial year filter.

  • geography (str) – Geographic level hint (e.g. "block", "tract").

  • client (RDHClient, optional) – Existing client instance.

Return type:

DataFrame with PL 94-171 population data.

siege_utilities.data.redistricting_data_hub.fetch_demographic_summary(state, year=None, client=None)[source]

Fetch ACS 5-year demographic summary by district.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • year (str, optional) – ACS year filter.

  • client (RDHClient, optional) – Existing client instance.

Return type:

DataFrame with ACS demographic columns.

Deprecation shim — re-exports from siege_utilities.reference.sample_data.

Moved during ELE-2437. Will be removed in v4.0.0.

siege_utilities.data.sample_data.list_available_datasets()[source]

List all available sample datasets with descriptions and metadata.

Returns:

Dictionary of available datasets with metadata

Return type:

Dict[str, Dict[str, Any]]

siege_utilities.data.sample_data.get_dataset_info(dataset_name)[source]

Get detailed information about a specific dataset.

Parameters:

dataset_name (str) – Name of the dataset

Returns:

Dataset information dictionary or None if not found

Return type:

Dict[str, Any] | None

siege_utilities.data.sample_data.load_sample_data(dataset_name, **kwargs)[source]

Load a sample dataset by name.

Parameters:
  • dataset_name (str) – Name of the dataset to load

  • **kwargs – Additional arguments for dataset generation

Returns:

DataFrame or GeoDataFrame with sample data

Raises:
  • ValueError – If dataset name is not recognized

  • ImportError – If required dependencies are not available

Return type:

pandas.DataFrame | geopandas.GeoDataFrame

siege_utilities.data.sample_data.get_census_boundaries(year=2020, geographic_level='tract', state_fips=None, county_fips=None)[source]

Download Census geographic boundaries.

Parameters:
  • year (int) – Census year (default: 2020)

  • geographic_level (str) – Geographic level (state, county, tract, etc.)

  • state_fips (str | None) – State FIPS code for filtering

  • county_fips (str | None) – County FIPS code for filtering (if applicable)

Returns:

GeoDataFrame with boundaries.

Raises:
  • ImportError – If geo extras are not installed.

  • RuntimeError – If no boundaries are returned from Census source.

Return type:

geopandas.GeoDataFrame

siege_utilities.data.sample_data.get_census_data(year=2020, data_type='demographics', geographic_level='tract', state_fips=None, county_fips=None)[source]

Get Census demographic/attribute data.

Parameters:
  • year (int) – Census year (default: 2020)

  • data_type (str) – Type of data (demographics, housing, income, etc.)

  • geographic_level (str) – Geographic level for data

  • state_fips (str | None) – State FIPS code for filtering

  • county_fips (str | None) – County FIPS code for filtering

Returns:

DataFrame with Census data or None if failed

Return type:

pandas.DataFrame | None

siege_utilities.data.sample_data.join_boundaries_and_data(boundaries, data, boundary_id_col='geoid', data_id_col='geoid')[source]

Join geographic boundaries with attribute data.

Parameters:
  • boundaries (geopandas.GeoDataFrame) – GeoDataFrame with geographic boundaries

  • data (pandas.DataFrame) – DataFrame with attribute data

  • boundary_id_col (str) – Column name for boundary identifiers

  • data_id_col (str) – Column name for data identifiers

Returns:

GeoDataFrame with joined data.

Raises:
  • KeyError – If the specified ID columns do not exist.

  • ValueError – If the join produces zero records.

Return type:

geopandas.GeoDataFrame

siege_utilities.data.sample_data.create_sample_dataset(year=2020, geographic_level='tract', state_fips='06', county_fips='037', include_geometry=True)[source]

Create a real-world sample dataset by combining boundaries and data.

Parameters:
  • year (int) – Census year

  • geographic_level (str) – Geographic level

  • state_fips (str) – State FIPS code

  • county_fips (str) – County FIPS code

  • include_geometry (bool) – Whether to include geographic boundaries

Returns:

Combined dataset.

Raises:
Return type:

pandas.DataFrame | geopandas.GeoDataFrame

siege_utilities.data.sample_data.get_census_county_sample(state_fips='06', county_fips='037', tract_count=5)[source]

Generate a sample county dataset with multiple tracts and synthetic data.

Parameters:
  • state_fips (str) – State FIPS code (default: CA)

  • county_fips (str) – County FIPS code (default: Los Angeles)

  • tract_count (int) – Number of tracts to include

Returns:

DataFrame or GeoDataFrame with county data

Return type:

pandas.DataFrame | geopandas.GeoDataFrame

siege_utilities.data.sample_data.get_metropolitan_sample(cbsa_code='31080', county_count=3)[source]

Generate a metropolitan area sample with multiple counties.

Parameters:
  • cbsa_code (str) – CBSA code (default: Los Angeles metro)

  • county_count (int) – Number of counties to include

Returns:

DataFrame or GeoDataFrame with metro data

Return type:

pandas.DataFrame | geopandas.GeoDataFrame

siege_utilities.data.sample_data.generate_synthetic_population(demographics=None, size=1000, geography_level='tract', tract_info=None, include_names=True, include_addresses=True, include_income=True, include_education=True)[source]

Generate synthetic population data matching real demographic patterns.

Parameters:
  • demographics (Dict | None) – Dictionary of demographic percentages

  • size (int) – Number of people to generate

  • geography_level (str) – Geographic level (tract, county, etc.)

  • tract_info (Dict | None) – Additional tract information

  • include_names (bool) – Whether to include synthetic names

  • include_addresses (bool) – Whether to include synthetic addresses

  • include_income (bool) – Whether to include synthetic income

  • include_education (bool) – Whether to include synthetic education

Returns:

DataFrame with synthetic population data

Return type:

pandas.DataFrame

siege_utilities.data.sample_data.generate_synthetic_businesses(business_count=500, industry_distribution=None, include_locations=True)[source]

Generate synthetic business data with realistic industry patterns.

Parameters:
  • business_count (int) – Number of businesses to generate

  • industry_distribution (Dict | None) – Dictionary of industry percentages

  • include_locations (bool) – Whether to include synthetic addresses

Returns:

DataFrame with synthetic business data

Return type:

pandas.DataFrame

siege_utilities.data.sample_data.generate_synthetic_housing(housing_count=300, property_types=None, include_coordinates=True, locale='us', lat_range=None, lon_range=None, area_unit=None, area_range=None, value_range=None)[source]

Generate synthetic housing data with realistic property patterns.

Parameters:
  • housing_count (int) – Number of housing units to generate

  • property_types (Dict | None) – Dictionary of property type percentages. If None, uses locale-appropriate defaults.

  • include_coordinates (bool) – Whether to include synthetic coordinates

  • locale (str) – Country/region preset (‘us’, ‘uk’, ‘de’, ‘fr’, ‘au’). Controls address format, coordinate bounds, units, and value ranges.

  • lat_range (Tuple[float, float] | None) – Override latitude bounds (min, max)

  • lon_range (Tuple[float, float] | None) – Override longitude bounds (min, max)

  • area_unit (str | None) – Override area column name (e.g. ‘square_feet’, ‘square_metres’)

  • area_range (Tuple[int, int] | None) – Override area range (min, max)

  • value_range (Tuple[int, int] | None) – Override property value range (min, max)

Returns:

DataFrame with synthetic housing data

Return type:

pandas.DataFrame

Statistics

Statistical primitives for DataFrame analysis.

  • cross_tabulation — contingency tables, chi-square, proportion math

  • moe_propagation — ACS margin-of-error propagation through derived estimates

Submodules load on first attribute access via PEP 562 __getattr__.

class siege_utilities.data.statistics.ChiSquareResult[source]

Bases: object

Result of a chi-square test of independence.

statistic

Chi-square test statistic.

Type:

float

p_value

p-value under the null hypothesis of independence.

Type:

float

dof

Degrees of freedom.

Type:

int

expected_freq

Expected frequency table under independence.

Type:

pandas.DataFrame

statistic: float
p_value: float
dof: int
expected_freq: pandas.DataFrame
__init__(statistic, p_value, dof, expected_freq)
Parameters:
Return type:

None

class siege_utilities.data.statistics.CrossTabSpec[source]

Bases: object

Specification for a cross-tabulation operation.

row_var

Column name for rows.

Type:

str

col_var

Column name for columns.

Type:

str

value_var

Column to aggregate (default None → counts).

Type:

str | None

weight_var

Optional weight column.

Type:

str | None

aggfunc

Aggregation function name ('sum', 'mean', 'count').

Type:

str

geography

Optional geography column for stratified cross-tabs.

Type:

str | None

row_var: str
col_var: str
value_var: str | None = None
weight_var: str | None = None
aggfunc: str = 'sum'
geography: str | None = None
__init__(row_var, col_var, value_var=None, weight_var=None, aggfunc='sum', geography=None)
Parameters:
  • row_var (str)

  • col_var (str)

  • value_var (str | None)

  • weight_var (str | None)

  • aggfunc (str)

  • geography (str | None)

Return type:

None

siege_utilities.data.statistics.chi_square_test(ct)[source]

Run a chi-square test of independence on a contingency table.

Parameters:

ct (DataFrame) – Contingency table (without margins). If margins are present (row/column named 'Total'), they are stripped automatically.

Return type:

ChiSquareResult

siege_utilities.data.statistics.contingency_table(df, row_var, col_var, value_var=None, weight_var=None, aggfunc='sum', margins=True)[source]

Build a contingency table (cross-tabulation) from df.

Parameters:
  • df (DataFrame) – Input data.

  • row_var (str) – Column for rows.

  • col_var (str) – Column for columns.

  • value_var (str, optional) – Column to aggregate. If None, counts occurrences.

  • weight_var (str, optional) – Column of weights (multiplied into value_var before aggregation).

  • aggfunc (str) – Aggregation function: 'sum', 'mean', or 'count'.

  • margins (bool) – If True, add row/column totals.

Return type:

DataFrame with row_var as index, col_var as columns, and aggregated values.

siege_utilities.data.statistics.moe_cross_tab(estimates_df, moes_df, row_var, col_var, value_var, moe_var)[source]

Propagate ACS margins of error through a cross-tabulation.

Computes the MOE of each cell sum using the Census Bureau’s root-sum-of- squares rule. Returns a DataFrame shaped like the contingency table with the propagated MOE in each cell.

Parameters:
  • estimates_df (DataFrame) – Data with estimate values.

  • moes_df (DataFrame) – Data with corresponding MOE values (same structure as estimates_df).

  • row_var (str) – Row and column variables.

  • col_var (str) – Row and column variables.

  • value_var (str) – Name of the estimate column in estimates_df.

  • moe_var (str) – Name of the MOE column in moes_df.

Return type:

DataFrame of propagated MOEs with shape matching the contingency table.

siege_utilities.data.statistics.rate_table(ct, normalize='all')[source]

Normalize a contingency table to proportions.

Parameters:
  • ct (DataFrame) – Contingency table (output of contingency_table()).

  • normalize (str) – 'all' (divide by grand total), 'index' (row proportions), 'columns' (column proportions).

Return type:

DataFrame of proportions summing to 1.0 along the requested axis.

Raises:
  • ZeroDivisionError – When the grand total, any row sum, or any column sum is zero (normalization is mathematically undefined).

  • ValueError – When normalize is not one of the accepted values.

class siege_utilities.data.statistics.Estimate[source]

Bases: object

An ACS estimate with its margin of error at 90% confidence.

value: float
moe: float
property se: float

Standard error (MOE / 1.645).

property cv: float

Coefficient of variation (SE / |estimate|), as a proportion.

property cv_pct: float

Coefficient of variation as a percentage.

is_reliable(cv_threshold=0.4)[source]

Return True if CV is below cv_threshold (default 40%).

Parameters:

cv_threshold (float)

Return type:

bool

confidence_interval(confidence=0.9)[source]

Return (lower, upper) bounds at the given confidence level.

Parameters:

confidence (float) – Confidence level. Only 0.90 (default, uses stored MOE directly) and 0.95 are supported.

Return type:

tuple[float, float]

__init__(value, moe)
Parameters:
Return type:

None

siege_utilities.data.statistics.flag_unreliable(estimates, cv_threshold=0.4)[source]

Return indices and Estimates that exceed cv_threshold.

Parameters:
  • estimates (sequence of Estimate)

  • cv_threshold (float) – Maximum acceptable CV (default 0.40 = 40%).

Return type:

list of (index, Estimate)

siege_utilities.data.statistics.moe_difference(a, b)[source]

Propagate MOE through the difference a - b.

Uses the same root-sum-of-squares formula as moe_sum().

Parameters:
Return type:

Estimate

siege_utilities.data.statistics.moe_product(a, b)[source]

Propagate MOE through a product a * b (independent estimates).

Uses the delta-method approximation:

SE_ab = sqrt(a^2 * SE_b^2 + b^2 * SE_a^2)

Parameters:
Return type:

Estimate

siege_utilities.data.statistics.moe_proportion(numerator, denominator)[source]

Propagate MOE for a proportion (numerator / denominator).

Uses the Census Bureau’s recommended formula for proportions where the numerator is a subset of the denominator:

SE_p = sqrt(SE_num^2 - p^2 * SE_den^2) / denominator

If the radicand is negative (which can happen with small samples), falls back to the conservative approximation:

SE_p = sqrt(SE_num^2 + p^2 * SE_den^2) / denominator

Parameters:
  • numerator (Estimate) – Must be a subset of denominator.

  • denominator (Estimate) – The total.

Returns:

Proportion with propagated MOE, bounded to [0, 1].

Return type:

Estimate

Raises:

ZeroDivisionError – If the denominator estimate is zero.

siege_utilities.data.statistics.moe_ratio(numerator, denominator)[source]

Propagate MOE for a ratio of non-nested estimates.

Uses the delta method:

SE_r = sqrt(SE_num^2 + r^2 * SE_den^2) / denominator

Parameters:
  • numerator (Estimate) – Independent (non-nested) estimates.

  • denominator (Estimate) – Independent (non-nested) estimates.

Return type:

Estimate

Raises:

ZeroDivisionError – If the denominator estimate is zero.

siege_utilities.data.statistics.moe_sum(estimates)[source]

Propagate MOE through a sum of independent ACS estimates.

MOE_sum = sqrt(sum(MOE_i^2))

Parameters:

estimates (sequence of Estimate) – The addends.

Returns:

Combined estimate with propagated MOE.

Return type:

Estimate

Cross-tabulation engine for geographic longitudinal analysis.

Provides functions to build contingency tables from joined datasets, compute rates and proportions, run chi-square independence tests, and propagate ACS margins of error through cross-tabulation operations.

Designed to consume output from: - siege_utilities.data.redistricting_data_hub.RDHClient.to_crosstab_input() - siege_utilities.geo.timeseries.longitudinal_data.LongitudinalAligner

Usage:

from siege_utilities.data.statistics.cross_tabulation import contingency_table, chi_square_test

ct = contingency_table(df, row_var="race", col_var="county")
result = chi_square_test(ct)
print(result.p_value)
class siege_utilities.data.statistics.cross_tabulation.ChiSquareResult[source]

Bases: object

Result of a chi-square test of independence.

statistic

Chi-square test statistic.

Type:

float

p_value

p-value under the null hypothesis of independence.

Type:

float

dof

Degrees of freedom.

Type:

int

expected_freq

Expected frequency table under independence.

Type:

pandas.DataFrame

statistic: float
p_value: float
dof: int
expected_freq: pandas.DataFrame
__init__(statistic, p_value, dof, expected_freq)
Parameters:
Return type:

None

class siege_utilities.data.statistics.cross_tabulation.CrossTabSpec[source]

Bases: object

Specification for a cross-tabulation operation.

row_var

Column name for rows.

Type:

str

col_var

Column name for columns.

Type:

str

value_var

Column to aggregate (default None → counts).

Type:

str | None

weight_var

Optional weight column.

Type:

str | None

aggfunc

Aggregation function name ('sum', 'mean', 'count').

Type:

str

geography

Optional geography column for stratified cross-tabs.

Type:

str | None

row_var: str
col_var: str
value_var: str | None = None
weight_var: str | None = None
aggfunc: str = 'sum'
geography: str | None = None
__init__(row_var, col_var, value_var=None, weight_var=None, aggfunc='sum', geography=None)
Parameters:
  • row_var (str)

  • col_var (str)

  • value_var (str | None)

  • weight_var (str | None)

  • aggfunc (str)

  • geography (str | None)

Return type:

None

siege_utilities.data.statistics.cross_tabulation.chi_square_test(ct)[source]

Run a chi-square test of independence on a contingency table.

Parameters:

ct (DataFrame) – Contingency table (without margins). If margins are present (row/column named 'Total'), they are stripped automatically.

Return type:

ChiSquareResult

siege_utilities.data.statistics.cross_tabulation.contingency_table(df, row_var, col_var, value_var=None, weight_var=None, aggfunc='sum', margins=True)[source]

Build a contingency table (cross-tabulation) from df.

Parameters:
  • df (DataFrame) – Input data.

  • row_var (str) – Column for rows.

  • col_var (str) – Column for columns.

  • value_var (str, optional) – Column to aggregate. If None, counts occurrences.

  • weight_var (str, optional) – Column of weights (multiplied into value_var before aggregation).

  • aggfunc (str) – Aggregation function: 'sum', 'mean', or 'count'.

  • margins (bool) – If True, add row/column totals.

Return type:

DataFrame with row_var as index, col_var as columns, and aggregated values.

siege_utilities.data.statistics.cross_tabulation.moe_cross_tab(estimates_df, moes_df, row_var, col_var, value_var, moe_var)[source]

Propagate ACS margins of error through a cross-tabulation.

Computes the MOE of each cell sum using the Census Bureau’s root-sum-of- squares rule. Returns a DataFrame shaped like the contingency table with the propagated MOE in each cell.

Parameters:
  • estimates_df (DataFrame) – Data with estimate values.

  • moes_df (DataFrame) – Data with corresponding MOE values (same structure as estimates_df).

  • row_var (str) – Row and column variables.

  • col_var (str) – Row and column variables.

  • value_var (str) – Name of the estimate column in estimates_df.

  • moe_var (str) – Name of the MOE column in moes_df.

Return type:

DataFrame of propagated MOEs with shape matching the contingency table.

siege_utilities.data.statistics.cross_tabulation.rate_table(ct, normalize='all')[source]

Normalize a contingency table to proportions.

Parameters:
  • ct (DataFrame) – Contingency table (output of contingency_table()).

  • normalize (str) – 'all' (divide by grand total), 'index' (row proportions), 'columns' (column proportions).

Return type:

DataFrame of proportions summing to 1.0 along the requested axis.

Raises:
  • ZeroDivisionError – When the grand total, any row sum, or any column sum is zero (normalization is mathematically undefined).

  • ValueError – When normalize is not one of the accepted values.

ACS Margin of Error propagation through derived estimates.

Implements the Census Bureau’s recommended methods for propagating margins of error through arithmetic operations on ACS estimates:

  • Sum/difference: root-sum-of-squares of component MOEs

  • Proportion: delta method with bounded output

  • Ratio: delta method for ratios of non-nested estimates

  • CV threshold: flag unreliable estimates by coefficient of variation

Reference:

U.S. Census Bureau, “Understanding and Using ACS Data”, Appendix A — Calculating Margins of Error.

class siege_utilities.data.statistics.moe_propagation.Estimate[source]

Bases: object

An ACS estimate with its margin of error at 90% confidence.

value: float
moe: float
property se: float

Standard error (MOE / 1.645).

property cv: float

Coefficient of variation (SE / |estimate|), as a proportion.

property cv_pct: float

Coefficient of variation as a percentage.

is_reliable(cv_threshold=0.4)[source]

Return True if CV is below cv_threshold (default 40%).

Parameters:

cv_threshold (float)

Return type:

bool

confidence_interval(confidence=0.9)[source]

Return (lower, upper) bounds at the given confidence level.

Parameters:

confidence (float) – Confidence level. Only 0.90 (default, uses stored MOE directly) and 0.95 are supported.

Return type:

tuple[float, float]

__init__(value, moe)
Parameters:
Return type:

None

siege_utilities.data.statistics.moe_propagation.flag_unreliable(estimates, cv_threshold=0.4)[source]

Return indices and Estimates that exceed cv_threshold.

Parameters:
  • estimates (sequence of Estimate)

  • cv_threshold (float) – Maximum acceptable CV (default 0.40 = 40%).

Return type:

list of (index, Estimate)

siege_utilities.data.statistics.moe_propagation.moe_difference(a, b)[source]

Propagate MOE through the difference a - b.

Uses the same root-sum-of-squares formula as moe_sum().

Parameters:
Return type:

Estimate

siege_utilities.data.statistics.moe_propagation.moe_product(a, b)[source]

Propagate MOE through a product a * b (independent estimates).

Uses the delta-method approximation:

SE_ab = sqrt(a^2 * SE_b^2 + b^2 * SE_a^2)

Parameters:
Return type:

Estimate

siege_utilities.data.statistics.moe_propagation.moe_proportion(numerator, denominator)[source]

Propagate MOE for a proportion (numerator / denominator).

Uses the Census Bureau’s recommended formula for proportions where the numerator is a subset of the denominator:

SE_p = sqrt(SE_num^2 - p^2 * SE_den^2) / denominator

If the radicand is negative (which can happen with small samples), falls back to the conservative approximation:

SE_p = sqrt(SE_num^2 + p^2 * SE_den^2) / denominator

Parameters:
  • numerator (Estimate) – Must be a subset of denominator.

  • denominator (Estimate) – The total.

Returns:

Proportion with propagated MOE, bounded to [0, 1].

Return type:

Estimate

Raises:

ZeroDivisionError – If the denominator estimate is zero.

siege_utilities.data.statistics.moe_propagation.moe_ratio(numerator, denominator)[source]

Propagate MOE for a ratio of non-nested estimates.

Uses the delta method:

SE_r = sqrt(SE_num^2 + r^2 * SE_den^2) / denominator

Parameters:
  • numerator (Estimate) – Independent (non-nested) estimates.

  • denominator (Estimate) – Independent (non-nested) estimates.

Return type:

Estimate

Raises:

ZeroDivisionError – If the denominator estimate is zero.

siege_utilities.data.statistics.moe_propagation.moe_sum(estimates)[source]

Propagate MOE through a sum of independent ACS estimates.

MOE_sum = sqrt(sum(MOE_i^2))

Parameters:

estimates (sequence of Estimate) – The addends.

Returns:

Combined estimate with propagated MOE.

Return type:

Estimate