dharits3/ncaa-college-athlete-rosters-2025-26
NCAA All Sports Rosters 2025-26 A near-census of a full NCAA athletic year — now named and enriched. 513,655 athlete roster records across all 28 championship and emerging sports, 1,087 schools, all three divisions (D1/D2/D3), men's and women's teams, one coherent year (2025-26, the first season under the House v. NCAA settlement). Every field is an institution-published roster fact from official school athletics sites, validated against the NCAA's official sponsor lists. New in… See the full description on the dataset page: https://huggingface.co/datasets/dharits3/ncaa-college-athlete-rosters-2025-26.
NCAA All Sports Rosters 2025-26
A near-census of a full NCAA athletic year — now named and enriched. 513,655 athlete roster records across all 28 championship and emerging sports, 1,087 schools, all three divisions (D1/D2/D3), men's and women's teams, one coherent year (2025-26, the first season under the House v. NCAA settlement). Every field is an institution-published roster fact from official school athletics sites, validated against the NCAA's official sponsor lists.
New in v2.1 (2026-08-14; current: v2.1.1):
- Names are public.
first_name/last_nameare columns on every row — they are school-published facts on public roster pages, and publishing them makes every record verifiable against itssource_urland linkable to other data. (last_nameis null for the ~0.5% of listings that publish a single name.) - 8 new columns: 2 name columns (
first_name/last_name, in the roster file) + 6 bio fields (major,previous_school,height_raw/height_in,weight_raw/weight_lbs) in the compact bio sidecardata/ncaa_athlete_bio_2025-26(373,817 rows — athletes with ≥1 bio field — joined onathlete_id; raw + parsed pairs ship together). As of the v2.1.1 split the roster file is 21 columns (the 19 v2.0.5 columns unchanged + names; column order locked) — 21 roster columns + 7 bio-sidecar columns, 27 distinct fields across two files. - Season-stats sidecars for 10 sports —
by_sport/<sport>/stats.parquet, 205,132 stat rows (39.9% of athletes), joinable onathlete_id. - 1,041 junk rows removed and 1,972 defective names repaired (details in RELEASE_NOTES.md).
What it's for: recruiting geography, international participation, division and gender structure, high-school → college pathways, walk-on vs contributor analysis (roster × stats), academic-major composition, transfer pathways (previous_school), anthropometrics by sport — at individual-record resolution.
Three things it shows out of the box
- Tennis (40.1%) and ice hockey (28.8%) have the highest international roster shares; golf (20.2%) and water polo (19.7%) follow. Football and softball (1.5% each) are the most domestic. (Sports with ≥1,000 athletes.)
- Division III, not D1, is the largest slice of college sports: 203,909 roster records vs 185,086 in D1 and 124,660 in D2.
- What a roster omits is a publication convention, and the data shows it: football rosters list weight for 89.3% of athletes; softball and field hockey list it for 0%. Height/weight/major gaps track school and sport publishing norms, not scraping failures.
Quick start
Load the roster file (Parquet, 513,655 × 21; bio and stats join in below — see Joining the files):
import pandas as pd
df = pd.read_parquet("hf://datasets/dharits3/ncaa-college-athlete-rosters-2025-26/data/ncaa_all_sports_rosters_2025-26_enriched.parquet")Load a single sport without downloading the rest — every sport is a config, the bio sidecar is the bio config, and each of the 10 stats sidecars is a <sport>_stats config:
from datasets import load_dataset
soccer = load_dataset("dharits3/ncaa-college-athlete-rosters-2025-26", "soccer", split="full")Just want a look? `samples/ncaa_rosters_sample_10000.csv` is a 10,000-row sample; `samples/sport_summary.csv` has per-sport counts and coverage.
Joining the files
Every file in the release joins on `athlete_id`, with two scopes:
- The bio sidecar (
data/ncaa_athlete_bio_2025-26) is one file covering every sport — it joins to the full roster file, any sport slice, or any gender/division CSV. - Stats joins are within ONE sport only. Stat columns are sport-specific (a goalie's GAA has no football equivalent), so a stats join starts from that sport's roster slice under
by_sport/, never the cross-sport file.
Every stats file also carries first_name/last_name so it reads standalone; drop them before joining to a roster slice, which already has both.
(a) Roster + bio — works across all sports:
import pandas as pd
base = "hf://datasets/dharits3/ncaa-college-athlete-rosters-2025-26"
roster = pd.read_parquet(f"{base}/data/ncaa_all_sports_rosters_2025-26_enriched.parquet") # 513,655 × 21
bio = pd.read_parquet(f"{base}/data/ncaa_athlete_bio_2025-26.parquet") # 373,817 × 7
df = roster.merge(bio, on="athlete_id", how="left") # 513,655 rows, 27 columns
# athletes with no published bio field have no sidecar row -> NaN bio columns(b) One sport's roster slice + its stats (basketball):
import pandas as pd
base = "hf://datasets/dharits3/ncaa-college-athlete-rosters-2025-26"
roster = pd.read_parquet(f"{base}/by_sport/basketball/all.parquet") # 32,614 roster rows
stats = pd.read_parquet(f"{base}/by_sport/basketball/stats.parquet") # 26,818 stat rows
# stats files carry first_name/last_name for standalone readability;
# drop them here — the roster slice already has both.
bb = roster.merge(stats.drop(columns=["first_name", "last_name"]), on="athlete_id", how="left")
# left join keeps all 32,614 athletes; no published stat line -> NaN stats(c) The full three-file pattern for one sport (roster slice + bio + stats):
import pandas as pd
base = "hf://datasets/dharits3/ncaa-college-athlete-rosters-2025-26"
roster = pd.read_parquet(f"{base}/by_sport/basketball/all.parquet") # one sport's roster slice
bio = pd.read_parquet(f"{base}/data/ncaa_athlete_bio_2025-26.parquet") # bio sidecar (all sports)
stats = pd.read_parquet(f"{base}/by_sport/basketball/stats.parquet") # THIS sport's stat columns
full = (roster
.merge(bio, on="athlete_id", how="left")
.merge(stats.drop(columns=["first_name", "last_name"]), on="athlete_id", how="left"))(d) With the `datasets` library — the sidecars are configs too:
from datasets import load_dataset
bio = load_dataset("dharits3/ncaa-college-athlete-rosters-2025-26", "bio", split="full")
bb_stats = load_dataset("dharits3/ncaa-college-athlete-rosters-2025-26", "basketball_stats", split="full")DuckDB / Polars read (and join) the same paths:
SELECT r.sport, count(*) AS athletes,
round(100.0 * avg(CASE WHEN b.height_in IS NOT NULL THEN 1 ELSE 0 END), 1) AS height_pct,
round(100.0 * avg(CASE WHEN r.origin = 'international' THEN 1 ELSE 0 END), 1) AS intl_pct
FROM read_parquet('hf://datasets/dharits3/ncaa-college-athlete-rosters-2025-26/data/ncaa_all_sports_rosters_2025-26_enriched.parquet') r
LEFT JOIN read_parquet('hf://datasets/dharits3/ncaa-college-athlete-rosters-2025-26/data/ncaa_athlete_bio_2025-26.parquet') b
USING (athlete_id)
GROUP BY 1 ORDER BY athletes DESC;import polars as pl
df = pl.read_parquet("hf://datasets/dharits3/ncaa-college-athlete-rosters-2025-26/data/ncaa_all_sports_rosters_2025-26_enriched.parquet")Left-joins keep every rostered athlete; missing bio/stats values are real roster states (redshirts, fields a school didn't publish), not join failures. As of v2.1.1 the bio columns live only in the sidecar — the roster file stays lean and the sparse fields join in when you need them. The gender × division CSVs under by_sport/<sport>/<gender>/ are row-slices of the same 21-column schema; anything you compute on them joins back the same way.
Shape (v2.1.1)
Schema & completeness
Two joinable files (v2.1.1 split). Bold = new in v2.1.x. Machine-readable version: `samples/data_dictionary.csv`; per-sport notes in each by_sport/<sport>/CODEBOOK.md.
Roster file (data/ncaa_all_sports_rosters_2025-26_enriched, 513,655 × 21, in file order):
Bio sidecar (data/ncaa_athlete_bio_2025-26, 373,817 × 7 — only athletes with ≥1 published bio field; "Known" shown within-file / of all 513,655 athletes):
Missingness is a publication convention, not a defect. Schools choose what their rosters publish, and conventions differ sharply by sport and gender: women's weight is ~0% in many sports by convention (softball 0%, field hockey 0%) while football publishes it at 89.3%; cross-country programs rarely list height. Columns are never dropped for low coverage — a blank means the school did not publish it. Compare coverage rates across sports in `samples/sport_summary.csv` before treating a gap as data loss.
Season-stats sidecars (new)
by_sport/<sport>/stats.parquet (+ .csv) for 10 sports. Columns are sport-specific by design (a baseball stat file has no reason to share a schema with soccer's); every file joins to its sport's roster slice on athlete_id. Every stats file also carries first_name/last_name so it reads standalone — drop them when joining to a roster slice (see Joining the files). 205,132 athletes (39.9%) have a stat row.
Before you count across sports
`track_indoor` and `track_outdoor` share source rows by design. Schools publish one track & field roster; indoor is materialized from the shared scrape and reconciled against indoor sponsor lists. Distinct athlete_ids (tfi_ / tfo_) for the same person-season. Never blindly dedupe across sports; when counting unique athletes, decide explicitly how to treat indoor/outdoor track.
Access: what ships and what never will
The public tier is everything in this repo: 21 roster columns + 7 bio-sidecar columns (27 distinct fields across two files) — all school-published roster facts — plus the per-sport season-stats sidecars. There is also a research tier (BISG race/ethnicity predictions, home-community income/SES, Census-tract and mobility joins) that is research-only, is in no distributed file, and never will be; it is not reconstructable from the released fields. No photos, no contact information, no birthdates, no social-media handles. The shipped build passed a PII audit.
Names are published because they are facts the schools themselves publish on public roster pages; every row carries the source_url it came from. Per-record removal is honored on request — see OPT_OUT.md.
Research using this dataset
What's in this repo
├── data/
│ ├── ncaa_all_sports_rosters_2025-26_enriched.parquet 513,655 × 21 (identity + team + hometown)
│ ├── ncaa_all_sports_rosters_2025-26_enriched.csv same data
│ ├── ncaa_athlete_bio_2025-26.parquet + .csv bio sidecar: athlete_id + major/prev_school/ht/wt, 373,817 rows (athletes with ≥1 bio field)
│ └── CODEBOOK.md full column dictionary (roster + bio sidecar + stats architecture)
├── by_sport/<sport>/
│ ├── all.parquet + all.csv per-sport slice (same 21 cols; parquet = HF config)
│ ├── <gender>/all.csv, d1/d2/d3.csv gender × division CSV slices
│ ├── stats.parquet + stats.csv season stats (10 sports; join on athlete_id)
│ └── CODEBOOK.md
├── samples/ 10k sample · data dictionary · sport & school summaries
├── metadata.json · MANIFEST.json build sidecars (row counts, sha256 per file)
├── DATASHEET.md · ETHICS_REVIEW.md · DISCLOSURE_RISK.md · LEGAL_NOTES.md · OPT_OUT.md · OFFICIAL_COMPARISON.md
├── LICENSE · CITATION.cff
└── RELEASE_NOTES.md changelog + opt-out removals ledgerRelease status
Current: v2.1.1 (2026-08-14) — the bio-sidecar split, same day as v2.1.0.
- The six sparse bio columns (
major,previous_school,height_raw,height_in,weight_raw,weight_lbs) moved out of the roster file intodata/ncaa_athlete_bio_2025-26.{parquet,csv}(373,817 rows — athletes with ≥1 published bio field), joined onathlete_id. Same values, same ids; no data changed. The roster file is now 21 columns.
v2.1.0 (2026-08-14) — the named + enriched release.
- Names public; 8 new columns; stats sidecars for 10 sports; consolidated file renamed to
data/ncaa_all_sports_rosters_2025-26_enriched.parquet. - 513,655 rows (v2.0.5: 514,696). The −1,041 are junk rows inherited from v2.0.x source data: 845 dual-render duplicate rows (
_N-suffixedathlete_ids from a platform that rendered rosters twice) and 196 header-artifact rows (e.g. aHt.column header scraped as a name). - Name repairs (all inherited defects, each change logged): 1,562
"Last," / "First"comma-swaps, 407 jersey-number-in-name fixes (recovering 80 height/weight values from mashed strings), 2 trailing-comma strips, 1 manual correction. - Verification: all 28 sports individually signed off — internal battery plus an adversarial live-page workflow (~3,000 field checks against live roster pages, zero wrong-value findings) plus external record-book cross-checks for the stats sports. See RELEASE_NOTES.md.
Prior versions (condensed; full changelog in RELEASE_NOTES.md):
Known intentional exceptions
Known limitations
- WMT-platform schools (Stanford, UCLA, Penn State) have partial bio-column coverage.
- Some Sidearm goalkeeper/pitching stat lines are rendered JS-only and are missing from the stats sidecars.
- Presto's combined "Sacks-YDS" football stat column ships unsplit.
- Track-family enrichment rejoin rate is 83.3–83.5% (the unmatched remainder in cross country / indoor / outdoor track has no row in the bio sidecar).
- Ice-hockey goalie minutes are unreliable; treat with caution.
track_indoor/track_outdoorshare source rows by design (see above).
Provenance & quality
- Source: official school athletics-site rosters (Sidearm / Presto / WMT / school roster APIs / Wayback snapshots), scraped in-season for the 2025-26 athletic year and validated against NCAA sport-sponsorship lists.
- v2.1.0 verification: every sport passed its internal integrity battery, then an adversarial live-page verification (multi-agent, ~25 stratified athletes per sport, ~3,000 field checks total — zero wrong-value findings; the only misses were under-capture, i.e. fields the pipeline left blank). Stats sports were additionally cross-checked against external record books. The per-sport verification log is maintained in the build repository.
- Raw and parsed fields ship together (
height_raw+height_in,weight_raw+weight_lbs,*_raw+ standardized) so parsing is auditable. - PII audit clean for the shipped build.
Citation
Shah, Dharit (2026). NCAA All Sports Rosters 2025-26: An Individual-Level Dataset Across All Divisions (Version 2.1.1) [Data set]. Hugging Face. https://doi.org/10.57967/hf/9512
Machine-readable: `CITATION.cff`.
Governance
- DATASHEET.md — Datasheets for Datasets
- ETHICS_REVIEW.md — ethics self-review / IRB exemption floor
- DISCLOSURE_RISK.md — identifiability analysis, updated for the named release
- LEGAL_NOTES.md — facts/CC0 rationale, scraping case law, FERPA/NIL notes
- OPT_OUT.md — per-record removal on request (dharits3@gmail.com, 14-day target); removals ledger in RELEASE_NOTES.md
