Statistics
Per-tile quality (uncertainty) API
Three layers that separate support (vote counts) from scatter (marker disagreement). See the Per-Tile Quality tutorial for the rationale.
add_uncertainty_columns
def add_uncertainty_columns(
df, # A fan or blotch catalog frame carrying the per-marking cluster standard
# deviations (``x_std``, ``y_std``, ``angle_std`` and, by kind,
# ``distance_std``/``spread_std`` for fans or ``radius1_std``/``radius2_std``
# for blotches) plus ``n_votes``.
min_votes:int=5, # A std estimated from fewer votes is treated as too noisy to use for
# scatter (drives the ``scatter_ok`` gate).
circular_ratio_limit:float=0.8, # Blotches with ``radius_2 / radius_1`` above this are near-circular, so
# their orientation is physically ill-defined and ``angle_usable`` is False.
): # Copy of ``df`` with added columns ``pos_std``, ``size_cv``,
``angle_usable`` and ``scatter_ok``.Add derived per-marking uncertainty columns; returns a copy.
The catalog kind (fan vs blotch) is inferred from the columns present.
tile_quality
def tile_quality(
kind:str='both', # Which catalog(s) to include.
version:str | None=None, # Catalog version; defaults to the p4tools session version (v3.1).
min_votes:int=5, # Vote gate for the scatter group (passed to `add_uncertainty_columns`).
agg:str='median', # Tile-level aggregator for the scatter metrics. "median" is robust.
ranks:bool=True, # Append cap-wide percentile-rank columns ``support_rank``/``scatter_rank``
# (0-100; 50 = median tile). ``scatter_rank`` is the median of the three
# scatter metrics' individual ranks so incommensurate units (deg, px, --)
# combine; ``support_rank`` is the same construction on the vote axis.
): # Indexed by ``tile_id`` with support columns (``n_markings``, ``n_fans``,
``n_blotches``, ``votes_median``, ``votes_min``, ``votes_total``), scatter
columns (``angle_scatter``, ``pos_scatter``, ``size_scatter``,
``n_scatter_markings``) and, when ``ranks``, ``support_rank`` /
``scatter_rank``. Scatter values are NaN for tiles with no gated marking.One row per tile_id with separate support and scatter column groups.
The central design rule (see the Per-Tile Quality tutorial) is that support — how many votes back a tile — and scatter — how much the citizen markers disagreed — are kept on separate axes and never mixed. Scatter is computed only from markings that pass the vote gate (n_votes >= min_votes), so weak support cannot masquerade as disagreement. Vote counts never divide the scatter numbers (no SEM).
classify_tile_quality
def classify_tile_quality(
tq, # Output of `tile_quality` with rank columns.
support_thresh:float=50.0, scatter_thresh:float=50.0
): # Copy of ``tq`` with an added ``quality_class`` column.Add a quality_class label from the two rank axes.
Requires support_rank and scatter_rank (i.e. tile_quality(..., ranks=True)). A tile is “high support” when support_rank >= support_thresh and “high scatter” when scatter_rank >= scatter_thresh.
============== ======= ======= ========================================== quality_class support scatter reading ============== ======= ======= ========================================== consistent high low reliable – best QC tier contested high high many votes, genuine disagreement sparse low low few votes, but they agree noisy low high least reliable ============== ======= ======= ==========================================
Tiles whose scatter_rank (or support_rank) is NaN – e.g. no marking passed the vote gate, so no scatter estimate exists – get quality_class = <NA> rather than a guess.