Where the numbers come from, and what they are not. One real comment followed from download to its row, the rule that ties a comment to a player, the prompt word for word, and a check by hand.
Every r/NBA comment of the 2025-26 season that names a tracked player, read by a language model and tied to one player. Here is one, followed from download to its row in the data. The figures down the side are the season's totals at each step.
01DownloadArctic Shift · r/NBA7,283,478 comments
Just feels like a smarter player. Poole will probably have higher highs but Ajay feels more consistent
One line per comment, the whole regular season and playoffs. A Thunder fan comparing two guards. May 12, 2026.
The raw record
{
"id":"oldhosi",
"body":"Just feels like a smarter player. Poole will probably have higher highs but Ajay feels more consistent",
"author_flair_text":":okc-2: Thunder",
"score":1,
"link_id":"t3_1tav0sz"
}+ created_utc and the rest of what Reddit stores. The author's name never leaves the pipeline.
↳ Deleted, removed and empty bodies drop out first.
Just feels like a smarter player. Poole will probably have higher highs but Ajay feels more consistent
Every name r/NBA uses for a tracked player is matched against the body. “Ajay” is Ajay Mitchell; “Poole” is Jordan Poole. Short names only match as whole words.
↳5,172,993 comments name no tracked player and stop here. None of them are ever classified.
03Ask the classifierAnthropic Batch API100,000 per batch
The whole prompt goes out with the comment inside it, to claude-haiku-4-5-20251001, in batches of 100,000. The prompt is below, word for word.
The request
{
"custom_id":"oldhosi",
"params": {
"model":"claude-haiku-4-5-20251001",
"max_tokens":75,
"temperature":0,
"messages": [
{
"role":"user",
"content":"Classify sentiment toward NBA players.\nSlang: nasty/sick/filthy=positive, washed/brick/fraud/cooked=negative, GOAT=positive.\nA trailing \"/s\" tags the comment as sarcasm.\n\nComment: Just feels like a smarter player. Poole will probably have higher highs but Ajay feels more consistent\n\nRespond ONLY with JSON: {\"s\":\"pos|neg|neu\",\"c\":0.0-1.0,\"p\":\"Player Name\"|null}"
}
]
}
}
The share of a player's comments that were negative.
neg_count / comment_count
Positive rate
The share that were positive.
pos_count / comment_count
Net sentiment
Praise minus criticism, from −1 to +1. One figure for where r/NBA stands overall.
(pos_count - neg_count) / comment_count
Polarization
The share that took a side either way. High means strong opinions.
(pos_count + neg_count) / comment_count
Why four? One rate tells you one thing. Chet Holmgren and Naz Reid are praised at nearly the same rate, and that is the only thing their numbers share: the comments around that praise split differently, and a different metric is needed to see it.
Chet Holmgren
50.2%30.0%19.8%
50.2% negative30.0% neutral19.8% positive
Positive rate19.8%
Negative rate50.2%
Net sentiment-30.5
Polarization70.0%
Comments28,500
Naz Reid
30.2%49.7%20.1%
30.2% negative49.7% neutral20.1% positive
Positive rate20.1%
Negative rate30.2%
Net sentiment-10.2
Polarization50.3%
Comments5,719
The positive rate says how much praise a player draws and the negative rate how much criticism; neither says anything about the other. Net sentiment puts the two together, praise minus criticism, one figure for where r/NBA stands on a player overall. Polarization sets the neutral comments aside and asks how much of the conversation took a side at all.
How a comment is read
Whose comment is it
Names are found in the comment before the classifier sees it. After it answers, one rule decides who the comment counts for: one name, that player; several names, whichever tracked player the classifier names; otherwise nobody. Of 2,059,495 usable comments, 1,787,520 count for exactly one player and 271,975 for nobody.
Every case the rule can produce
Every case the attribution rule can produce
Case
Comment
Names found
Classifier's p
Counts for
No tracked name
5,172,993 downloaded comments, plus the deleted and removed ones.
none
never asked
Never classified
One name
I never would have guessed like three years ago that a Trae Young trade would net the Hawks zero draft capital.Jan 8, 2026 · ▲ 11,766 · Source ↗
One name found, and the classifier agrees.
Trae Young
"Trae Young"
Trae Young
One name, the classifier names someone else
Why are the warriors so awful at handling this stuff? Lol Steph has carried this org for too long man he deserves betterJan 21, 2026 · ▲ 4,315 · Source ↗
One name always wins. The classifier blamed the front office; the rule counts it against the one player it found.
Stephen Curry
"Golden State Warriors Organization"
Stephen Curry
Several names, the classifier names one
Just feels like a smarter player. Poole will probably have higher highs but Ajay feels more consistentMay 12, 2026 · ▲ 1 · Source ↗
Two names found; p picks one of them, as a nickname, and the name list resolves it.
Ajay MitchellJordan Poole
"Ajay"
Ajay Mitchell
Several names, no pick
Lamelo and Ant are going to be insanely entertaining Jun 25, 2026 · ▲ 9,119 · Source ↗
The classifier won’t say who it’s about, so the comment counts for no one.
LaMelo BallAnthony Edwards
null
Nobody
Several names, the pick is a tracked player not in the text
Year 23. Crazy how Coop or Wemby wasn’t even born when he got drafterNov 19, 2025 · ▲ 1,379 · Source ↗
Neither found name is the subject. “Year 23” is LeBron, and the classifier says so; he is tracked, so it counts for him.
Cooper FlaggVictor Wembanyama
"LeBron James"
LeBron James
Several names, the pick resolves to nobody
JaylenBrunson is officially a Knicks legend. 45 points to win the Championship. The most points by a Knick in Finals history. Jun 14, 2026 · ▲ 10,644 · Source ↗
The commenter blended two names; p repeats the blend, and no list resolves it.
Jaylen BrownJalen Brunson
"Jaylen Brunson"
Nobody
The prompt
The entire instruction, word for word. There is no system prompt and no examples. Every comment gets the same lines.
Classify sentiment toward NBA players.Slang: nasty/sick/filthy=positive, washed/brick/fraud/cooked=negative, GOAT=positive.A trailing "/s" tags the comment as sarcasm.Comment: {comment_body}Respond ONLY with JSON: {"s":"pos|neg|neu","c":0.0-1.0,"p":"Player Name"|null}
What comes back
Three keys, and one of them is a number the model gives about itself.
What c is
The model's own estimate of how sure it is, from 0.0 to 1.0. It isn't a measured probability, and nothing checks it.
Where it's used
Once. A positive or negative comment needs c ≥ 0.9 before it can be quoted on a player page.
Where it isn't
The rates. Every usable comment counts once, whatever c came back as.
Reads
Answers from the season, as they stand in the data.
Exhibit 01Negative.CHI flair
#THEY REALLY GAVE CHET THE MAX 🤣🤣🤣
Type of guy to shoot 2 shots in a Game 7. Fucking embarrassing.
{"s":"neg", "c":0.9, "p":"Chet Holmgren"}counts for Chet Holmgren
Mockery in capitals, and a box-score line as the insult. The name is in capitals too and still matches.
Exhibit 02Positive.CHA flair
Champagnie was Undrafted out of college and he is playing like 20 minutes a game in the WCF making league minimum money.
That’s fucking crazy.
{"s":"pos", "c":0.85, "p":"Champagnie"}counts for Julian Champagnie
Admiration for a minimum-salary player. The answer names him by surname only, and the name list resolves it.
Exhibit 03Positive.LAL flair
Incredibly common Hali W
{"s":"pos", "c":0.75, "p":"Tyrese Haliburton"}counts for Tyrese Haliburton
Four words. “W” is the whole verdict, and “Hali” is one of Haliburton’s names.
Exhibit 04Neutral.LAL flair
LeBron James will not sign with the Memphis Grizzlies.
{"s":"neu", "c":0, "p":null}counts for LeBron James
A statement of fact. Neutral, and the classifier names nobody; with one name in the comment it still counts for LeBron, as neutral. Neutral answers come back at 0.0 or 0.5 by habit.
Exhibit 05Negative.LAC flair
Giddey is going to have that hanging over his head his whole career.
{"s":"neg", "c":0.8, "p":"Josh Giddey"}counts for Josh Giddey
No insult word anywhere. The negative is the implication.
Exhibit 06Neutral.DEN flair
Shai “Quick Andy’s Coming” Gilgeous-Alexander
{"s":"neu", "c":0.5, "p":"Shai Gilgeous-Alexander"}counts for Shai Gilgeous-Alexander
A mock nickname inside his real one. The classifier read it as neutral; a fan would call it a jab. That is the kind of miss the check by hand measures.
Where it slips
Answers that a careful read disagrees with. The hand label is a read under a rubric, not the truth; here the two differ.
Where the classifier slips: the answer beside the hand label
{"s":"neu", "c":0.5, "p":"LaMelo Ball"}counts for LaMelo Ball
positive · LaMelo Ball
Quiet approval. A defense of LaMelo against a knock, with no praise word in it, read as neutral. The prompt never says what counts as positive.
Lu Dort really is an inspiration. Despite his clumsiness, he has managed to climb to the top echelon of one of the most athletic leagues in the world. Apr 25, 2026 · ▲ 123 · Source ↗
{"s":"pos", "c":0.85, "p":"Lu Dort"}counts for Lu Dort
negative · Lu Dort
Sarcasm with no tell. The thread, where Dort has just tripped Booker, gives it away; the classifier only sees the comment.
Can’t believe KAT was being compared to those other centers the spurs played in the playoffs like he’ll just be a non factor vs WembyJun 4, 2026 · ▲ 8 · Source ↗
{"s":"neg", "c":0.85, "p":"Karl-Anthony Towns"}counts for Karl-Anthony Towns
positive · Karl-Anthony Towns
The doubters quoted. “Non factor” is their word, and the comment pushes back on it; the classifier took it as the verdict and counted it against Towns.
Before a comment is quoted
A positive or negative comment with c ≥ 0.9 can be quoted on a player page, but only after a second, larger model (claude-sonnet-5) reads it and names who the opinion is aimed at. Both of these passed every rule above.
Before a comment is quoted: the verifier's answer
Comment
The answer
Counts for
The check says
They traded Luka for Marvin BagleyFeb 4, 2026 · ▲ 8,405 · Source ↗
{"s":"neg", "c":0.9, "p":"Luka Doncic"}
Luka Doncicseveral names, the classifier names one
Not a player · not quoted
The scorn is for the trade, not for Luka. The verifier answers null: aimed at a decision, not a player.
The closeup of him, grabbing Jokic after he ran up the floor, looking completely unbothered, goes hard as hell. Jaden’s having an all-time walk the talk series Apr 26, 2026 · ▲ 1,559 · Source ↗
{"s":"pos", "c":0.9, "p":"Jaden"}
Nikola Jokicone name, the classifier names someone else
Not him: Jaden McDaniels · not quoted
The praise is for Jaden McDaniels, who is not tracked; the only tracked name in the comment is Jokić, so the rule counts it for him. The verifier names McDaniels.
This r/NBA comment was labeled {sentiment_word}. Is that {sentiment_word} sentiment aimed at an NBA player, or at a team, front office, coach, referees, a decision, or a play? If at a player, name them; otherwise answer null.
The target is the player being praised or criticized - not a player who is merely mentioned, sympathized with, or the subject of someone else's decision.
Never name a player who does not appear in the comment.
Comment: {comment_body}
Respond ONLY with JSON: {"t":"Player Name"|null,"c":0.0-1.0}
Checked by hand
68.6%± 5.0agree on the sentimentof 1,000 drawn
95.4%± 2.9agree on the playerof 509 scored
66.0%± 5.0agree on bothdrawn 2026-09-24
A blind random draw of 1,000 counted comments, each read and labeled under a written rubric without the classifier's answer in view; 509 were scored. The margins are at 95%. A label is a careful read, not the truth: where the two disagree, the page says so, and never says which is right.
Per class: the classifier's share of the sample minus the hand labels' share, in points, with its margin. Negative → neutral → positive.
Why positive runs low. The prompt never says what counts as positive, so mild approval reads as neutral. The LaMelo Ball comment under Where it slips is the pattern: praise with no praise word in it.
What it cannot say. The check is across every player together. It says nothing about any one player's rate, and nothing about a single comment.
Per class, the groups and the draw
Class
Classifier said
Labels said
Precision
Recall
Negative
194
188
70.9%
78.2%
Neutral
164
142
61.3%
66.5%
Positive
151
179
75.5%
61.8%
Group
Size
Weight
Labeled
Scored
Sentiment
Player
Both
first
350
0.35
350
336
80.7%
98.5%
79.2%
ordered
629
0.65
180
173
61.8%
93.2%
59.0%
held out
21
0
20
19
47.4%
100.0%
47.4%
rejected
21 rows set aside before scoring
rubric
v1
seed
11
drawn
2026-09-24
needed context
7.1% of labels
unsure
4.5%
The rules
Minimum
Comments
Where it applies
The official minimum
5,000
A player's place on the leaderboard and a bar in the race. 67 players clear it; the slider lets you lower it, and says so.
A fanbase on a roster
200
A row in the grudges and flowers lists, a cell in the grid.
A week
30
A point on a player's season line.
The belt
300
A week's title for most hated and most loved.
A game
20
A game's verdict on a player, in his game thread.
Entry to the race
500
A player's bar appears once his comments reach it.
Receipts. The comments quoted on a player page are the top 10 by score among his positive and negative comments at c ≥ 0.9, up to 500 characters, after the quote check. The check reached 99.9% of the candidates; 77.7% of them were about the player they count for, and only those are shown.
What this is not
Sentiment is not hate. A box-score criticism and a personal attack both register as negative. The rates measure the tone of the conversation, not hostility.
Every comment weighs the same. A comment with 15,367 upvotes counts once, the same as one with 1. Viral threads carry no extra weight.
Flair is a sample. 1,244,249 of the 2,059,495 usable comments carry a team flair. The fanbase views are about those commenters only, and the thinnest fanbase (New Orleans Pelicans) is 8,065 comments.
Small samples move. Under the minimums above, one viral thread can swing a rate. That is why the minimums exist and why the slider says so.
One subreddit, one season. r/NBA is not the NBA audience, and 2025-26 is not any other year.
Use the data
Sources
r/NBAArctic Shift · comments and posts
stats.nba.comrosters · game logs
cdn.nba.complay-by-play · headshots · logos
↓download
Landingraw/ reference/ media/
Commentsone line each
7,283,478
Posts, reference, mediaheld for later stages
↓find names
Cleanfiltered/
Name a tracked player223 players
2,110,485
↓batch requests ⇄ Anthropic Batch API
Enrichmentbatches/
Read the sentimentHaiku 4.5
2,059,495
Check the targetSonnet 5 · quotes only
↓pick the player
Curatedprocessed/
The factone row per comment
1,787,520
Verdicts · posts to gameswho each quote is about · thread → game
↓aggregate, joining the reference
Presentationdashboard/
The contract15 tables · 10 recaps · manifest · schema
↓publish · MFA · pre-flighted
On AWS
Data bucketseason=2025-26/ · media/
CloudFrontcourtsentiment.com
GitHub Actionsreads /data, builds, deploys the site bucket
↓serve
Who reads it
Fansthe site, phone first
Share previewscards baked at build
AnalystsDuckDB on the public URLs
Under every stage
Config in YAML, versions stamped · Contract one schema, the site's types from it · Checked tests, both classifiers evaluated, 509 by hand · Access MFA to publish, a merge to deploy
Comments move left to right and down. The gold line is the funnel, 7,283,478 downloaded to 1,787,520 counted; reference data and media skip classification. The green dashed lines are the two publish paths. GitHub Actions is one more reader of the public data, so the site is built from what was published, never from a laptop.
Everything the site shows is read from one folder of Parquet files at a public URL. The manifest says what was published and under which rules; the schema says every table's columns, types and what may be null.
The leaderboard, from your terminal (DuckDB; the line selects whole on a click):
duckdb -c "SELECT attributed_player, neg_rate, comment_count FROM 'https://courtsentiment.com/data/season=2025-26/player_overall.parquet' WHERE comment_count >= 5000 ORDER BY neg_rate DESC LIMIT 10"
Versions
config players 4.6 · teams 2.3 · season 3.0 · recaps 1.1 · method_examples 2.0sentiment claude-haiku-4-5-20251001 · prompt v2-production+s-hinttarget claude-sonnet-5 · prompt v1snapshots games fetched at 2026-09-12 · posts processed at 2026-10-08 · download daily processed at 2026-10-11schema v13 · generated 2026-10-15T00:27:30.444250+00:00