Source code for aperta.data_processing

"""Tabular helpers that flow through aperta's (Geo)DataFrame pipeline.

Small composable column-level transforms — dedupe an index after a join,
add a straight-line origin→destination distance, take a weighted mean per
group. None of these reason about geometry as the primary concern (for
that see `geo_processing`); they operate on tables that may or may not
carry a geometry column.
"""

import numpy as np
import pandas as pd


[docs] def remove_duplicate_indices(df: pd.DataFrame) -> pd.DataFrame: """Remove duplicate indices from DataFrame or Series. Often useful after spatial join.""" df = df.loc[~df.index.duplicated(keep="first"), :] return df
[docs] def add_straight_line_dist( df: pd.DataFrame, orig_prefix: str = "orig", dest_prefix: str = "dest", out_col: str = "dist_line", ) -> pd.DataFrame: """Add euclidean origin→destination distance, in CRS units (typically metres).""" dx = df[f"{orig_prefix}_x"] - df[f"{dest_prefix}_x"] dy = df[f"{orig_prefix}_y"] - df[f"{dest_prefix}_y"] df[out_col] = np.hypot(dx, dy) return df
[docs] def weighted_group_mean( values: pd.Series, weights: pd.Series, group_id: pd.Series, ) -> pd.Series: """Weighted mean of `values` per group, indexed by group ID. NaN-aware: rows with NaN in either `values` or `weights` are dropped before aggregation. Groups with no surviving rows (or with all weights ≤ 0) yield NaN. All three inputs must share the same index (per-row alignment); the result is indexed by the unique group IDs. Typical use case: reduce per-cell values to per-zone (`group_id = cells['zone_id']`, `weights = cells['population']` or similar). Args: values: per-row numeric Series. weights: per-row non-negative weight Series (same index as `values`). group_id: per-row group-membership Series (same index as `values`). Values become the result's index. Returns: `pd.Series` indexed by the unique group IDs, with the weighted mean of `values` per group. """ valid = values.notna() & weights.notna() eff_w = weights.where(valid, 0.0) eff_v = values.where(valid, 0.0) num = (eff_v * eff_w).groupby(group_id).sum() den = eff_w.groupby(group_id).sum() return num / den.where(den > 0)