two_step_floating_catchment
site.SiteProblem.two_step_floating_catchment(
supply_col,
catchment_size=None,
distance_decay=None,
site_names=None,
site_indices=None,
matrix=None,
demand=None,
per_capita=1,
return_site_ratios=False,
)Two-step floating catchment area (2SFCA) accessibility score.
Unlike binary threshold coverage, 2SFCA accounts for competition: a demand region near a site that is also the closest option for many other regions gets less credit for that site than a region near an uncontested one. Scores an arbitrary set of sites directly – no solve() is required, so it can describe any subset of candidate_sites as a baseline, e.g. only the sites that are already open, via site_names/site_indices. With neither argument, every registered candidate site is scored – the right default when the candidate pool is the current network, but not when it also includes not-yet-built proposals, in which case pass the currently-open subset explicitly.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| supply_col | str | Column in candidate_sites holding each site’s supply quantity (e.g. number of GPs, beds, weekly appointment slots). Named at call time rather than registered via add_sites(), so the same problem can be scored under different supply definitions without re-adding sites. |
required |
| catchment_size | float | Classic 2SFCA’s hard catchment threshold d0, in the travel matrix’s registered units. A site is “in catchment” for a demand region if the travel cost between them is <= catchment_size, with weight 1; beyond it, weight 0. Mutually exclusive with distance_decay – exactly one of the two must be given. |
None |
| distance_decay | list of (float, float) or dict | A softer catchment than a single hard cutoff. Two forms: - A list of (upper_bound, weight) pairs – Enhanced 2SFCA (E2SFCA, Luo & Qi 2009) step-decay bands, e.g. [(10, 1.0), (20, 0.68), (30, 0.22)] – this is not just an illustrative example: it is Luo & Qi’s own published “weight set 1” for 0-10/10-20/20-30 minute zones (their sharper “weight set 2” is [(10, 1.0), (20, 0.42), (30, 0.09)]). Need not be pre-sorted; a cost beyond the largest upper_bound gets weight 0. catchment_size=<upper_bound> with a single band of weight 1 is exactly equivalent to classic 2SFCA. - A dict describing a continuous decay kernel. Two forms: {"method": "gaussian", "catchment_size": d0, "bandwidth": sigma} – Dai (2010)’s truncated Gaussian: weight 1 at distance 0, weight 0 at catchment_size (the truncation radius), decaying continuously in between; or {"method": "power", "catchment_size": d0, "scale": s, "alpha": a, "min_dist": m=0} – the classic gravity-model decay, weight(d) = (max(d, min_dist)/scale)**alpha, truncated to 0 beyond catchment_size (alpha is not implicitly negative; pass a negative value for decay). Parameterisation matches pysal/access’s weights.gravity(scale, alpha, min_dist). |
None |
| site_names | list of str | None |
|
| site_indices | list of int | The site set to score. At most one may be given. If neither is given, every candidate site is scored. | None |
| matrix | str | Label of a secondary travel matrix registered via add_secondary_travel_matrix(). Scores accessibility under that matrix’s travel costs instead of the primary matrix’s. |
None |
| demand | str | Label of a secondary demand scenario registered via add_secondary_demand(). Scores accessibility under that scenario’s demand instead of the primary demand data (both catchment_demand and demand in the returned frames reflect the chosen scenario). Combines freely with matrix=. |
None |
| per_capita | float | Multiplier applied to the accessibility column, e.g. 1_000 to express supply per 1,000 head instead of raw supply units per head. Does not affect site_frame. Must be a positive number. |
1 |
| return_site_ratios | bool | If True, also return the step-1 per-site table – useful for finding which site is driving an implausible regional score. | False |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | Per demand region, indexed by demand location ID: accessibility (supply units per head, x per_capita), n_sites_in_catchment, demand. |
|
| pandas.DataFrame | Only if return_site_ratios=True. Per site, indexed by site name: supply, catchment_demand, n_regions_in_catchment, ratio. |
Raises
| Name | Type | Description |
|---|---|---|
| ValueError | If both site_names and site_indices are given, if per_capita is not a positive number, if neither or both of catchment_size/distance_decay are given, if no demand data is registered, if supply_col is missing or contains a null/negative value for a scored site, if matrix is not a registered secondary travel matrix label, if demand is not a registered secondary demand scenario label, or if distance_decay fails its own validation (see above). |
|
| TypeError | If supply_col is not numeric. |
|
| IndexError | If site_indices are out of range. |
|
| KeyError | If site_names are not found in the travel matrix or candidate_sites. |
Notes
A site with no demand within its catchment has an undefined (NaN) supply-to-demand ratio and is excluded from every region’s accessibility score, with a warning naming it. A demand region with no site in its catchment correctly scores accessibility == 0 (a real “no supply available” result, not a missing value) – these two zero-like cases are deliberately kept distinguishable. n_regions_in_catchment/n_sites_in_catchment count non-zero-weight membership, so this holds under distance_decay too.
accessibility obeys sum(demand * accessibility) == sum(supply) (before per_capita scaling) whenever every scored site has at least one region in its catchment, since the sum of demand-weighted ratios is exactly the supply that produced them – true regardless of whether catchment_size or distance_decay was used.