← Back to Outliers

Ratings toolMethodology recipe

Outliers

A reproducibility log: the data this ranking touches, the queries it runs, the caveats that apply, and the steps a third party would follow to re-derive any row.

Summary

Ranks every school against its peer group of ~40 demographically similar same-grade-band schools on the chosen metric and year, then sorts by peer-percentile (direction-adjusted so a higher percentile always means better outcomes). Filters by direction (top decile vs peers, bottom decile vs peers, or all), proclivity bucket, and suppression rules.

Data sources

  • Database tableschools

    One row per New York City public school (DBN, name, district, borough, school_type, grade_band, latest_enrollment, proclivity_decile, include_in_default_comparisons, comparison_group_id, closed_at).

  • Database tableschool_year_metrics

    Long-format per-school per-year metric facts. The Outliers ranking reads (school_dbn, year, metric_key, subgroup='ALL', value, suppressed, citywide_percentile, comparison_group_percentile).

  • Database tablemetric_definitions

    Each metric_key has a direction (HIGHER_BETTER / LOWER_BETTER / NEUTRAL). The peer- and citywide-percentile fields stored in school_year_metrics are already direction-adjusted, so a 95th-percentile chronic-absenteeism rate means the school has LOW absenteeism (good).

  • Database tablepeer_groups + school_peers

    K-nearest-neighbor peer groups built once and cached per school. Default k=40. Hard match on grade_band + admission_category; soft match (weighted distance) on demographic shares + topic tags + grade-served details.

  • Loader scriptlib/queries/schools.ts

    Server function backing the page. Builds a Prisma query against school_year_metrics filtered to the chosen metric/year/direction, joined to schools for the proclivity and display fields. Memoized in-process for ~1 hour.

  • Loader scriptscripts/derive/peer-groups.ts

    Builds the peer-group + school-peers tables. Re-run this to change k or the matching weights.

  • Loader scriptscripts/derive/percentiles.ts

    Computes the citywide_percentile and comparison_group_percentile columns on school_year_metrics. Inverts the percentile for LOWER_BETTER metrics so the display semantics stay 'high = good.'

Steps

  1. Pick the metric and year from listMetricDefinitions() / listYearsForMetric(metricKey). Outliers also accepts direction (top/bottom/all), a proclivity range (e.g., 8-10 to find expectation-beaters at the highest-need schools), and toggles for suppressed rows and non-default-comparison schools.

  2. Filter school_year_metrics to the chosen slice. The percentile filter implements the 'top decile' or 'bottom decile' meaning of direction. By default, suppressed=false and include_in_default_comparisons=true.

    SELECT m.*
      FROM school_year_metrics m
      JOIN schools s ON s.dbn = m.school_dbn
     WHERE m.metric_key = $metric
       AND m.year      = $year
       AND m.subgroup  = 'ALL'
       AND m.suppressed = false
       AND s.include_in_default_comparisons = true
       AND m.comparison_group_percentile IS NOT NULL
       AND m.comparison_group_percentile >= 0.9   -- direction=top decile vs peers
       -- AND m.comparison_group_percentile <= 0.1 -- (direction=bottom)
     ORDER BY m.comparison_group_percentile DESC
     LIMIT 50 OFFSET <page * 50>;
  3. Optional proclivity bucket. Most expectation-beater questions want decile 8-10 with high peer-percentile; floor-of-the-floor questions want decile 1-3 with low peer-percentile.

    AND s.proclivity_decile BETWEEN $low AND $high
  4. Sort. Defaults to comparison_group_percentile DESC for top, ASC for bottom. Column-sort header allows resorting by city percentile, raw value, proclivity decile, or school name.

  5. Direction adjustment lives in the percentile-derivation step (scripts/derive/percentiles.ts), not in the page query. For LOWER_BETTER metrics (chronic_absenteeism_rate, bullying), the percentile was computed as 1 − rank, so the rest of the system can treat 'high percentile = good outcome' uniformly.

Caveats

Peer-group composition is sensitive to k. At k=40 (default), changing to k=20 (more selective) or k=80 (more inclusive) shifts a typical school's peer percentile by ~10-15 points. The percentile is a relative measure: a school in proclivity decile 8 that 'punches above' can still score below citywide average in absolute terms. Suppressed rows (low subgroup-n) are hidden by default; toggle 'show suppressed' to include them. Schools where include_in_default_comparisons=false (alternative programs, transfer schools, District 75) are hidden by default — these schools are real but their metrics aren't comparable to standard schools.

References

Reproduce

Re-run scripts/derive/peer-groups.ts (with desired k) and scripts/derive/percentiles.ts to rebuild the lookup tables. To reproduce any specific row from the Outliers page directly in SQL: take its (metric_key, year, school_dbn) and run the SELECT above. The peer group itself for that school is in school_peers — join to see the 40 neighbors that the percentile was computed against.

The recipe lives at data/ratings/recipes.ts in the repo. Corrections welcome.