return_solution_metrics

site.EvaluatedCombination.return_solution_metrics(full_secondary_metrics=False)

Parameters

Name Type Description Default
full_secondary_metrics bool If False (the default), each registered secondary travel matrix contributes only its core five metrics plus the float-valued equity aggregations (see point 6 below) – matching the output shape prior to this parameter’s introduction. If True, every registered secondary matrix also contributes its dict-valued equity breakdowns (weighted_by_equity_group__<label>, etc.) and description strings, exactly mirroring what the primary matrix already always returns unsuffixed. This costs nothing extra to compute – _compute_travel_metrics already produces these values for every matrix regardless – it only changes which of the already-computed keys get included here. False

Notes

INTERPRETATION GUIDE FOR SUMMARY TABLES & SORTING:

1a. ‘weighted_average’ LOWER is better. Represents travel time adjusted for specified weighting factors.

1b. Travel Costs (‘unweighted_average’, ‘90th_percentile’, ‘max’): LOWER is better. Represents travel time or distance.

1c. ‘total_cost’ LOWER is better. Total fixed cost of the selected sites (sum of the cost_col configured via add_sites()). NaN if no cost_col was configured. Only influences which solution is selected if explicitly passed as a weight (weights={“cost”: …}).

  1. Absolute Equity Gap (‘gap_absolute_weighted’): CLOSER TO 0 is better. Measures the flat minute/distance difference between the best-served and worst-served equity bands. High numbers mean severe geographical disparity.

  2. Relative Equity Gap (‘gap_relative_weighted’): CLOSER TO 1.0 is better. If it’s 1.5, the worst-served group travels 1.5x longer than the best-served group.

  3. Inter-Tertile Ratio (‘inter_tertile_ratio’): Measures macro-equity between the most- and least-disadvantaged equity-band tertiles, per add_equity_data(disadvantaged_end=...) – lower bins = more disadvantaged (e.g. IMD 1-3) when disadvantaged_end="low" or unset; the reverse when "high". SORTING CRITERIA:

    • ITR > 1.0: Inequity. The most deprived third faces longer travel times than the least deprived third (e.g., 1.25 = 25% longer travel).
    • ITR = 1.0: Perfect equality in macro travel times.
    • ITR < 1.0: Progressive equity. Travel times are shorter for the most deprived communities.
  4. Coverage Metrics (‘proportion_within_coverage_threshold’, ‘coverage_by_equity_group’): HIGHER is better (Scale: 0.0 to 1.0). Represents accessibility. Look for solutions where coverage is both globally high and uniformly distributed across groups. Both are NaN if no threshold_for_coverage was given.

    NAMING RULE: an unqualified coverage metric is weighted by the demand registered via add_demand(), so it answers “what share of people are covered”. The regions variants (‘proportion_regions_within_coverage_threshold’, ‘coverage_regions_by_equity_group’) count every region equally and answer “what share of places are covered”. The two coincide when demand is uniform, including when add_demand() was never called.

    The demand-weighted figure is the one ‘mclp’ optimises, matching the textbook Maximal Covering Location Problem.

5b. Absolute coverage headcounts (‘demand_within_coverage_threshold’, ‘regions_within_coverage_threshold’): HIGHER is better. The literal headcount/region-count behind the two coverage proportions above – e.g. “391,823 people” rather than “75.4%” – for reporting to audiences who find an absolute number more legible than a percentage. NaN/0 under the same conditions as the proportions.

5c. Unreachable headcounts (‘regions_unreachable’, ‘demand_unreachable’, ‘proportion_demand_unreachable’): LOWER is better. How many regions / how much demand had no feasible journey to any selected site at all – a missing (NaN) travel cost, only possible once a travel matrix was registered with add_travel_matrix(allow_missing=True). 0/0.0 when every region reached at least one selected site (including every problem that never opted into allow_missing). Distinct from – and independent of – threshold_for_coverage: a region can be “beyond threshold” while still reachable, or unreachable with no threshold ever configured. weighted_average/unweighted_average/90th_percentile/max above are computed over reachable regions only, rather than this headcount silently dragging them toward whatever value an unreachable pair happened to hold.

5d. Ranking-only travel cost (‘weighted_average_for_ranking’, ‘unweighted_average_for_ranking’, ‘max_for_ranking’): LOWER is better – but NOT a reporting metric. Identical to ‘weighted_average’/‘unweighted_average’/‘max’ unless solve(unreachable_cost=...) was used, in which case every unreachable row is substituted with unreachable_cost instead of excluded, matching what solve() actually ranked/pruned combinations on. Kept alongside the honest figures rather than replacing them so the two are always visibly distinguishable: the reachable-only numbers describe what really happened, these describe what the search optimised. Present regardless of whether unreachable_cost was used – identical to their honest counterparts when it wasn’t, so this column always exists and needs no conditional handling.

  1. Secondary travel matrices (columns suffixed __<label>, e.g. ’weighted_average__public_transport’):

    Registered via add_secondary_travel_matrix(label=...). Same metrics and sort direction as their unsuffixed counterparts above (1a/1b/5/5c), computed against that matrix’s own travel costs instead of the primary matrix. By default, only the core scalar metrics (both coverage proportions and the unreachable headcounts included) plus the float-valued equity aggregations (gap_absolute_weighted, gap_relative_weighted, avg_*_third_bins, inter_tertile_ratio) are included per matrix, to keep this table from growing unboundedly with each registered matrix – pass full_secondary_metrics=True to also include the dict-valued equity breakdowns and description strings, matching what the primary matrix already returns. The underlying per-region problem_df always carries min_cost__<label> / selected_site__<label> / within_threshold__<label> regardless of this setting.

  2. Population impact vs baseline (demand_improved, demand_worsened, demand_unchanged, proportion_demand_ improved, proportion_demand_worsened, regions_improved, regions_worsened, regions_unchanged, mean_reduction_among_improved, mean_increase_among_worsened, max_reduction, max_increase):

    Only present when a baseline was supplied (solve(baseline=...), or a baseline was passed to evaluate_single_solution_single_ objective() directly) – absent otherwise, not NaN, so solution_df’s schema is unchanged for callers that never ask for a baseline. demand_*/mean_* are HIGHER-is-better for the _improved/reduction names (more people better off, or a bigger improvement) and LOWER-is-better for the _worsened/ increase names; *_unchanged is directionless. See SolutionComparator.population_impact_summary() for the equivalent baseline-vs-candidate comparison without solving via solve(baseline=...). Secondary travel matrices and secondary demand scenarios get only the demand-weighted subset (demand_improved__<label>, demand_worsened__<label>, demand_unchanged__<label>, mean_reduction_among_improved__ <label>, mean_increase_among_worsened__<label>) by default; full_secondary_metrics=True also adds the region counts and max change (regions_improved__<label>, etc.) for secondary travel matrices. Demand scenarios never get the region-count/max variants (region membership doesn’t vary with demand), matching point 6’s “only what varies with demand” rule.

    When equity data is also registered, demand_improved_by_equity_ group / demand_worsened_by_equity_group (dict per band) are also added – e.g. “are the most deprived areas improving at a higher rate than the least deprived?”. Primary matrix only; not yet broken down for secondary travel matrices/demand scenarios.

7b. Which sites actually changed (sites_closed_vs_baseline, sites_added_vs_baseline):

`sites_closed_vs_baseline` is the baseline's own site names
absent from this solution's `site_names`; `sites_added_vs_
baseline` is `site_names` absent from the baseline's. Answers
"which sites changed?" directly, rather than requiring a caller
to compute the set difference themselves -- useful when several
solutions share nearly all the same sites and only differ in
one or two, which a bare `site_names` column doesn't make
obvious at a glance. Present under the same condition as the
rest of this point (a baseline was supplied); absent, not empty
lists, otherwise.
  1. “Left behind” headcounts (demand_beyond_threshold_<t>, regions_beyond_threshold_<t>, one pair per threshold t in beyond_thresholds, plus a _by_equity_group dict variant of each when equity data is registered):

    LOWER is better – the opposite direction from the coverage metrics in point 5, since these count people/regions a threshold crossing leaves outside rather than within. A distinct concept from threshold_for_coverage/‘coverage metrics’ even though both compare a travel cost against a cutoff: “covered” (good, one threshold) vs “beyond” (bad, any number of thresholds at once). Thresholds are independent, not cumulative – demand_beyond_threshold_45 is not a subset of demand_beyond_threshold_30’s complement; each is a fresh count against its own cutoff. Only present when beyond_thresholds was supplied – absent, not NaN, otherwise. Secondary travel matrices get the demand-weighted headcount by default (demand_beyond_threshold_<t>__<label>); full_secondary_metrics=True also adds region counts and the equity breakdowns, matching point 6/7’s pattern. Secondary demand scenarios get only the demand-weighted headcount, never region counts or equity breakdowns, matching point 6’s “only what varies with demand” rule.

  2. Additional sites chosen beyond required (additional_site_names):

    site_names with the required sites (flagged via add_sites(required_sites_col=...)) removed – e.g. for a “we have 4 sites and are opening 1 more” problem, this is just the new site, rather than all 5 names, which otherwise look identical across every solution that all correctly keep the same 4 required sites. Only present when at least one required site is configured; absent, not an empty list, otherwise.

  3. Sites not selected (unselected_site_names):

    Every registered candidate site absent from site_names, in canonical site-index order – the complement of site_names among ALL candidates, not just the required ones point 9 removes. Always present (unlike point 9), since it doesn’t depend on any optional registered data.

Back to top