Skip to content

Method

Everything settled, why, and what is still open. This analysis reads NIRF and nothing else.

This folder deliberately contains no PLFS, AISHE, AICTE or NAAC data. Those comparisons are real and are a later piece; mixing them in here would mean that a reader could not tell which claims depend on NIRF alone and which depend on a reconciliation between two sources with different populations, different measurement moments and different definitions.

So: every number is a NIRF submission, and every rate is one NIRF number over another. The analysis is internally consistent by construction, and its conclusions are about what the ranked sector reports, never about what is true of Indian graduates.

nirf_fact_master only, pivoted with MAX().

nirf_fact_aggregate is not used. It sums duplicate rows, including median salary, inflating 29–54 institutes per year by 2–3×. Filed upstream as external_data_sources#73.

Each of these silently multiplies or drops numbers rather than failing. All five are enforced in extract.sql.

1. One ranking year spans seven academic years, and they overlap across rankings

Section titled “1. One ranking year spans seven academic years, and they overlap across rankings”

The 2025 ranking carries academic years 2017-18 to 2023-24; the 2024 ranking carries 2016-17 to 2022-23. Academic year 2021-22 appears in five different ranking editions. Summing over ranking_year counts the same cohort up to seven times. Every query pins exactly one.

2. Intake and outcomes are reported on different academic years

Section titled “2. Intake and outcomes are reported on different academic years”

Within ranking 2025, intake is populated for 2017-18 to 2022-23 and is entirely NULL for 2023-24. Placement, salary and graduation are populated for 2021-22 to 2023-24 and are entirely NULL before. There is no academic year on which both sides exist.

A funnel read off a single row is therefore always empty on one side. Entry and exit are different rows, separated by the programme’s duration — which is why the extract carries duration_years.

3. An institute appears in several ranking categories

Section titled “3. An institute appears in several ranking categories”

An IIT is in Engineering and Overall, sometimes also University. Summing across ranking_category double- or triple-counts the same students. Disciplines are reported side by side and never added. track_kind separates the eight subject tracks from the six institution-type tracks (Overall, University, College, State Public Universities, Skill, Open), which overlap the subject tracks completely.

6,271 keys of (institute, ranking year, category, academic year, type, metric) appear more than once. Verified before relying on it: all 6,271 carry identical values, zero conflicting, so MAX(value) is a true de-duplication rather than an arbitrary choice. Had they conflicted, MAX() would have silently taken the larger.

5. The latest outcome year is the wrong anchor, and differs by discipline

Section titled “5. The latest outcome year is the wrong anchor, and differs by discipline”

Most 2025 tracks report outcomes to 2023-24. Medical UG stops at 2022-23, because the 5-year MBBS had no 2023-24 cohort in that edition. Pinning one academic year across disciplines drops MBBS entirely, with no error. The extract computes is_latest_outcome_year per (discipline, ranking year, level, grain), so a cross-discipline table selects the right row for each track rather than hard-coding a year.

But “latest” is the wrong anchor for a trend. Within an older edition institutes report different final years: ranking 2019 has 192 engineering institutes reporting 2015-16 and 25 reporting 2017-18. Anchoring on the latest year published that 25-institute slice — 13% of the edition — as the edition’s outcome, and the trend table then set it beside a 97-institute row. is_best_covered_outcome_year takes the academic year with the most institutes reporting instead, and the notes use it. Where an edition reports evenly, as 2025 does with 97 in all three years, the two agree — so the board is byte-identical and only the trend tables moved.

6. The ranked set changes size, and Engineering halves

Section titled “6. The ranked set changes size, and Engineering halves”

How deep each track was published, from nirf_fact_rankings:

Track 2019 2020 2021 2022 2023 2024 2025
Engineering 200 200 200 200 100 100 100
Pharmacy 75 75 75 101 100 100 101
Management 75 75 75 101 100 100 100
University / College / Overall 100 100 100 100 100 100 100
Medical 30 40 50 50 50 50 50
Law 15 20 30 30 30 40 40

Engineering is the only track that shrinks. Every other one is flat or growing. So “ranked engineering” means about 192 institutes up to 2022 and about 97 from 2023 — the same phrase covering half as many colleges. A trend across that boundary compares two different populations, which is why the institute count sits in the trend tables and the caption points at it.

VERIFIED AGAINST NIRF’S PUBLISHED LISTS — this is a real change, not an ingestion gap. The question was open when this note was written: nirf_rank simply stops at 100 from 2023, with no gap and no error, which is equally consistent with NIRF ceasing to publish ranks 101–200 and with our load being short for that track. Confirmed against nirfindia.org: NIRF stopped publishing the 101–200 band for Engineering after 2022.

So the discontinuity is a fact about the ranking, and the trend tables can be read across it — provided the reader is told that “ranked engineering” covers about 192 institutes up to 2022 and about 97 from 2023. That is why the institute count sits in the third column of every trend table and the caption points at it.

It also fits what the coverage work found independently: the 166 colleges NIRF dropped place at essentially the same rate as the 97 that remain (+0.33 points). They did not fall out because their outcomes deteriorated — the ranking stopped publishing them.

Two grains, marked by grain. level gives one row per discipline × UG/PG with a DISTINCT institute count — the grain to read totals and rates from. level_duration splits by programme length, needed for cohort linking; institute counts overlap between durations, so they must never be summed across it.

Placement rate is placed / graduated, both the institute’s own submissions. Internally consistent, needs nothing external.

“Placed or continuing” is reported alongside it because a low placement rate is usually a high higher-study rate, and either alone misleads. Pharmacy UG reads 43.97% placed but 82.82% placed or continuing.

Salary is the median of institute medians. NIRF publishes one median per institute, so a sector median cannot be recovered. The median of those medians weights a 60-graduate college the same as a 1,200-graduate one. Quartiles are reported to show the spread. This is a useful ordering of disciplines and a bad estimate of graduate earnings; every note says so.

Medians use APPROX_QUANTILES(..., 100), not 2. At low bucket counts the approximation is coarse enough to return different answers on identical reruns.

State Public University (2024) and State Public Universities (2025) are one track under two spellings; normalised to the plural.

The checker verifies claims, not just cells

Section titled “The checker verifies claims, not just cells”

check_notes.py compares every table cell against the CSV — the ordinary transcription guard — and then separately asserts every comparative claim in the prose.

That second part exists because of a real failure. The first draft of the eight discipline readings contained five false superlatives:

Written Actually
Pharmacy has the lowest UG placement rate Agriculture, at 36.44%
Dental has the widest UG-to-PG gap Pharmacy, at 38.97 points
Pharmacy is the next biggest UG track after Engineering Agriculture, at more than twice Pharmacy’s size
Management has the highest placement rate anywhere Dental PG, at 89.74%
Engineering has the second-highest UG median salary It has the highest

Every one was plausible. None was catchable by comparing cells, because each cell was correct — the error was in the comparison between them. So each superlative is now registered as an assertion computed from the CSV, and a claim that cannot be written as one does not belong in the prose.

Reviewed before merge by rebuilding and interrogating the extract rather than re-reading the notes. Four defects, one of them published.

1. A 200% placement rate was published. Architecture’s trend table showed ranking 2021 at 200.00% — 3 institutes, 33 graduated, 66 placed. NIRF’s own numbers are not always internally consistent: in thin early editions the metrics are reported per programme by different institutes, so placed need not be drawn from the same cohort as graduated. 30 rows in the extract are affected, 14 of them otherwise publishable. Fix: a rate_is_coherent flag in the extract, trend tables exclude flagged rows and say which ranking years were excluded and why, and the checker now asserts that every published rate is coherent. Not silently dropped — the flag is in the CSV.

2. n_reporting_* counted programme rows, not institutes. At grain='level' an institute running both a 4-year and a 5-year programme contributes two rows, so Pharmacy PG read 95 institutes and 153 “reporting”. The name invited exactly the misreading it got in review. Fix: renamed n_prog_rows_*, with genuine COUNT(DISTINCT institute_id) columns added beside them.

3. The checker’s discipline matcher was dead code. It derived a discipline from the filename with a prefix slice — "engineering"[:6] is "engine" — so it never matched, and every cell fell through to a fallback keyed on the numbers themselves. Coverage was correct by luck. Had two disciplines shared an institute count and graduate count, cells would have been skipped silently. Fix: an explicit filename-to-discipline map, and a missing row is now an error rather than a continue.

4. A markdown heading ran straight into its body text, because the trend section overwrote the blank line after its own heading.

The first three are the same shape as the defects this repo keeps producing: not wrong arithmetic, but a number that means something other than its label says, or a check that passes without checking.

  • No cohort funnel. Trap 2 makes intake-to-outcome linking a real piece of work: for each institute, intake year Y links to the outcome year Y + duration. duration_years is in the extract for exactly this, and coverage will be well under half — intake 2019-20 is reported by 129 engineering institutes against 254 reporting outcomes for 2023-24. Worth doing, with the coverage loss stated.
  • The institution-type tracks are extracted but not written up. Overall, University, College, State Public Universities, Skill and Open University are in the CSV with track_kind='institution' and have no notes. They are a different question — how institution types differ — and mixing them into the discipline board would double-count.
  • Lateral entry is extracted and unused. It matters for engineering, where diploma holders enter in year two and so are not in the first-year intake but are in the graduating cohort.
  • Nothing is audited. Placement counts, salary medians and graduation counts are institute self-reports. This analysis takes them at face value and says so; testing them against anything external is the later combined piece, by design.