lgtm
This commit is contained in:
parent
982e0cc89c
commit
cfaf58dfba
44 changed files with 793 additions and 201 deletions
|
|
@ -5,7 +5,7 @@ are set per admission authority, only a handful of councils publish polygons,
|
|||
and the pupil-residence data behind commercial "heatmap" catchments lives in
|
||||
the restricted National Pupil Database. This module therefore COMPILES one
|
||||
from open data, estimating each school's admission cutoff distance ("last
|
||||
distance offered") — the radius within which an applicant would plausibly be
|
||||
distance offered"): the radius within which an applicant would plausibly be
|
||||
offered a place.
|
||||
|
||||
Model: English state admissions are run as deferred acceptance with distance
|
||||
|
|
@ -13,30 +13,30 @@ tie-breaks, which in a continuum economy is equivalent to finding
|
|||
market-clearing cutoff distances (Azevedo & Leshno 2016). Per phase
|
||||
(primary/secondary):
|
||||
|
||||
1. Demand — Census 2021 children per LSOA (TS007A age bands, prorated to the
|
||||
1. Demand: Census 2021 children per LSOA (TS007A age bands, prorated to the
|
||||
phase's cohort ages) split evenly across the LSOA's live postcodes.
|
||||
2. Supply — every open, non-selective state-funded school (GIAS), with a fill
|
||||
2. Supply: every open, non-selective state-funded school (GIAS), with a fill
|
||||
target of max(capacity, headcount) prorated to the phase's cohorts
|
||||
(sixth-form and nursery years carry reduced weight, since their class
|
||||
sizes differ and they are not allocated by the same admissions round).
|
||||
3. Preferences — children prefer nearby schools, trading distance against
|
||||
3. Preferences: children prefer nearby schools, trading distance against
|
||||
Ofsted grade: a school's effective distance is its real distance minus a
|
||||
grade bonus (Outstanding > Good > ungraded > below-Good). Because real
|
||||
first preferences are heterogeneous, each postcode's children split
|
||||
across nearby feasible schools with logit weights over effective
|
||||
distance rather than all picking the same one.
|
||||
4. Equilibrium — cutoffs start unbounded and tighten monotonically: each
|
||||
4. Equilibrium: cutoffs start unbounded and tighten monotonically. Each
|
||||
round, children apply to their preferred feasible school(s), and
|
||||
oversubscribed schools tighten their cutoff to the distance of their
|
||||
marginal admitted child. Converges to the deferred-acceptance outcome.
|
||||
5. Schools that never fill have no binding cutoff — anyone who applies gets
|
||||
in — so their feasibility radius is the distance within which the local
|
||||
5. Schools that never fill have no binding cutoff (anyone who applies gets
|
||||
in), so their feasibility radius is the distance within which the local
|
||||
child population would cover their fill target, capped.
|
||||
|
||||
The free parameters (preference bonuses, demand scale, choice temperature,
|
||||
residual calibration factors) are CALIBRATED against published "last
|
||||
distance offered" figures scraped from nine local authorities' allocation
|
||||
reports — see check_school_cutoffs.py and the constants below.
|
||||
reports. See check_school_cutoffs.py and the constants below.
|
||||
|
||||
A postcode is "inside the catchment" of every school whose cutoff radius
|
||||
covers it. The output counts those schools per postcode for the four
|
||||
|
|
@ -71,7 +71,7 @@ SCHOOL_GROUPS = {
|
|||
# PRIMARY-age children if its statutory lowest age is <= 10, and SECONDARY-age
|
||||
# children if its statutory highest age is >= 12. All-through (e.g. 3-18) and
|
||||
# middle-deemed-secondary (e.g. 9-13) schools satisfy BOTH and so are counted in
|
||||
# both the primary and the secondary metrics — Ofsted's coarse "Ofsted phase"
|
||||
# both the primary and the secondary metrics. Ofsted's coarse "Ofsted phase"
|
||||
# labels such schools as just "Secondary", which previously hid them from every
|
||||
# postcode's primary-school count.
|
||||
PRIMARY_MAX_AGE = 10
|
||||
|
|
@ -133,7 +133,7 @@ CHOICE_TEMPERATURE_KM = 0.3
|
|||
|
||||
# Residual calibration from the same ground truth: after the equilibrium
|
||||
# solve, modelled cutoffs still ran systematically tight (median log2 bias
|
||||
# -0.53 primary / -0.36 secondary at the settings above — published "last
|
||||
# -0.53 primary / -0.36 secondary at the settings above; published "last
|
||||
# distance offered" reflects offer-day frictions, waiting-list churn and
|
||||
# furthest-applicant noise that no clean equilibrium reproduces). Radii are
|
||||
# multiplied by 2^-bias so the modelled median matches the published median;
|
||||
|
|
@ -166,7 +166,7 @@ def classify_good_plus_schools(
|
|||
Framework). A large and growing share of schools were last inspected under an
|
||||
UNGRADED (Section 8) inspection or the post-2024 report-card framework, so
|
||||
that column is null/"Not judged" for them even when they are demonstrably
|
||||
good — their status lives in "Ungraded inspection overall outcome" ("School
|
||||
good. Their status lives in "Ungraded inspection overall outcome" ("School
|
||||
remains Good"/"School remains Outstanding"). Filtering on the graded column
|
||||
alone dropped ~7,000 genuinely good/outstanding schools. We fall back to the
|
||||
ungraded outcome, but ONLY when there is no usable graded result
|
||||
|
|
@ -296,15 +296,15 @@ def phase_intakes(gias: pl.DataFrame) -> pl.DataFrame:
|
|||
|
||||
Returns one row per open, non-selective state-funded school with valid
|
||||
coordinates: ``(urn, lat, lng, primary_intake, secondary_intake)``. The
|
||||
fill target — max(capacity, headcount), so over-full schools keep their
|
||||
demonstrated size and under-full schools can admit up to capacity — is
|
||||
fill target (max(capacity, headcount), so over-full schools keep their
|
||||
demonstrated size and under-full schools can admit up to capacity) is
|
||||
spread over the cohort ages the school teaches (parsed from ``age_range``,
|
||||
e.g. "3–11" = ages 3..10) with nursery and sixth-form ages down-weighted,
|
||||
and each phase receives the share of cohort weight in its age band.
|
||||
"""
|
||||
# gias._format_age_range emits three shapes: "{low}–{high}", "up to {high}"
|
||||
# (StatutoryLowAge missing) and "{low}+" (StatutoryHighAge missing). Parse
|
||||
# all three — the one-sided shapes previously fell through the two-number
|
||||
# all three. The one-sided shapes previously fell through the two-number
|
||||
# parse and silently dropped the school from the catchment supply.
|
||||
age = pl.col("age_range")
|
||||
leading = age.str.extract(r"^\s*(\d+)", 1).cast(pl.Int64, strict=False)
|
||||
|
|
@ -426,10 +426,10 @@ def equilibrium_cutoffs(
|
|||
|
||||
Deferred acceptance with distance priority, solved as cutoff dynamics
|
||||
(Azevedo & Leshno): cutoffs start unbounded; each round every child unit
|
||||
applies to its preferred feasible school(s) — a logit split over
|
||||
applies to its preferred feasible school(s): a logit split over
|
||||
effective distance (distance - school bonus) among schools whose cutoff
|
||||
covers it, collapsing to the single best school when ``tau_km`` is 0 —
|
||||
and each oversubscribed school tightens its cutoff to its marginal
|
||||
covers it, collapsing to the single best school when ``tau_km`` is 0.
|
||||
Each oversubscribed school then tightens its cutoff to its marginal
|
||||
admitted child's distance. Cutoffs only ever tighten, so the iteration
|
||||
converges.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue