The Blatsheet

Numbers, narratives, and unsolicited opinion about basketball statistics and visualization.

Fragile Starters

What happens to an NBA team, and a fantasy team, when a player is available just a bit more?

13 starters played under 55% of their team’s games over the last three seasons, 2023-24 to 2025-26

No rookies or sophomores: in the league all three seasons, played last season, on a roster now. Starters only, 24+ minutes a night, where having him back adds to the team.

Joel Embiid
39% Joel Embiid 96 of 246 games· PHI with him 63–33 · without 53–97 23-24 3924-25 1925-26 38
Robert Williams
35% Robert Williams 85 of 246 games· POR with him 39–46 · without 60–101 23-24 624-25 2025-26 59
Anthony Davis
60% Anthony Davis 147 of 246 games· DAL with him 85–62 · without 25–73 23-24 7624-25 5125-26 20
Zach LaVine
56% Zach LaVine 138 of 246 games· SAC with him 51–87 · without 47–63 23-24 2524-25 7425-26 39

What if Joel Embiid was healthy just a little bit more

Philadelphia wins 66% with him and 35% without. Below: 10%, 20% and 30% more of him, for the 76ers and for whoever holds him. No one goes past 70 games.

Real NBA · Philadelphia

Wins per 82 games

  1. As it was 32 of 82 games 38.7–43.3
  2. +10% 40 of 82 games 41.2–40.8
  3. +20% 48 of 82 games 43.6–38.4
  4. +30% 57 of 82 games 46.1–35.9

With him the 76ers won 66% of games over 2023-24 to 2025-26; without him 35%. Each added game is won at the with-him rate.

Load Management · 9-cat H2H

His line in a fantasy week

CatPer gameAs it was1.4 gp+10%1.7 gp+20%2.1 gp+30%2.4 gp
FG%48.9samesamesamesame
FT%85.4samesamesamesame
3PTM1.41.92.42.93.4
PTS26.936.746.155.564.9
REB7.710.513.215.918.6
AST3.95.46.88.29.5
ST0.60.80.91.11.3
BLK1.21.72.12.52.9
TO2.93.94.95.96.9

2025-26 per-game line times the games he plays in a 3.5-game week. Percentages do not move; the weight behind them does.

On our rosters

Every Load Management team holding a fragile starter. Slide a player up and watch his team’s head-to-head season move.

The Predictions page’s projection over the 19 regular-season weeks left: today’s rosters, each week’s matchup, the nine categories, 2025-26 per-game lines. Each fragile starter plays his share of his games; slide him up, to no more than 70 games.

Jason
16–3 projected head-to-head 5.5 categories a week
FG%0½FT%153PM18½PTS18REB9½AST18STL16BLK8½TO0½
Jan
17–2 projected head-to-head 6.2 categories a week
FG%4FT%183PM17PTS17REB11½AST12½STL16BLK17TO5½
Tristan
3–16 projected head-to-head 3.2 categories a week
FG%12FT%43PM9PTS11REB2½AST12½STL4BLK0TO5½
Damien
7–12 projected head-to-head 3.5 categories a week
FG%9½FT%83PM5PTS6REB3AST8STL7½BLK7½TO12
Pete
15–3–1 projected head-to-head 5.2 categories a week
FG%6FT%53PM4½PTS11REB17AST18STL15BLK16TO5½
Martin
5–14 projected head-to-head 4.1 categories a week
FG%14½FT%183PM12PTS5REB0AST2STL1BLK5½TO19
Koops
10–8–1 projected head-to-head 4.6 categories a week
FG%19FT%03PM0PTS3REB19AST6STL13½BLK19TO7

The starters who are not there

Below are the least available players on a roster today who still average 24 minutes when they do play, with at least 30 games in the window. These are the names a fantasy manager drafts on talent and then watches from the injured list.

Ja Morant
32% Ja Morant 79 of 246· MEM
De'Anthony Melton
38% De'Anthony Melton 93 of 246· GSW
Bradley Beal
46% Bradley Beal 112 of 246· LAC
Kevin Porter Jr.
46% Kevin Porter Jr. 113 of 246· MIL
Marcus Smart
47% Marcus Smart 116 of 246· LAL
Dejounte Murray
50% Dejounte Murray 123 of 246· NOP
Mark Williams
50% Mark Williams 123 of 246· PHX
Walker Kessler
52% Walker Kessler 127 of 246· UTA

What it costs a team

Take every player who averaged 20 minutes across the window and group them by how often they played. The teams of the players under 55% won 44% of the games those players appeared in. The teams of the players at 75% or better won 54%. Read that second column with care: it is the win rate of the games the player was on the roster for, weighted by his games, so a star who missed a tanking season’s second half still carries that season’s record.

Rotation players grouped by share of games played, with the win rate of their teams
Games playedPlayersAverageTeam win rateFull-season win rate
Under 55%4537.8%44.3%43.2%
55% to 75%8566.6%48.9%48.2%
75% and up12084.9%54.0%53.7%

The team view is sharper. For each of the 88 team-seasons in the window we took the players who averaged 20 minutes and asked what share of the 82 games they played, then put that beside the team’s record. The correlation is 0.65. Teams whose rotation was available more than the average (75%) won 58% of their games. Teams below it won 39%. Causation runs both ways, since losing teams shut players down in March, but the Embiid arithmetic above does not need the correlation to make its point.

Least available rotations
SeasonTeamPlayedRecord
2025-26MEM34%25–57
2023-24MEM42%27–55
2024-25PHI42%24–58
2024-25NOP46%21–61
2025-26UTA47%22–60
Most available rotations
SeasonTeamPlayedRecord
2023-24OKC95%57–25
2023-24DEN93%57–25
2023-24MIN93%56–26
2023-24SAC92%46–36
2025-26MIN91%49–33

Say nice things

Every October an r/NBA thread asks which player you most want to see get through one season in one piece. Its one rule: say nice things. So. Joel, the league is better when you are in it, and Philadelphia is a different team on the nights you walk out. Rob, that 2022 version of you is still the best rim protector most of us have seen in a Celtics jersey. Anthony, Zach, you are owed one of those years where the body cooperates all the way to April. We would like to see it. The spreadsheet would too.

Every player in the window is in the availability table on the league site, with the Load Management manager holding him.

Games played and per-game lines from Basketball Reference, team records from ESPN, with and without records from Basketball Reference game logs, seasons 2023-24 to 2025-26. A traded player’s team win rate is weighted by the games he played for each team. Headshots from NBA.com.

Preparing for BlatBlat’s first off-season

Our Yahoo Fantasy API access stopped working during the off-season. Getting it back meant reapplying for access and completing a DocuSign form. We had built around an API we could use, then found ourselves having to explain the project and ask to use it again.

Yahoo access now comes with paperwork

That extra step was frustrating while we were trying to build. We also think it is necessary. AI slop is cheap to produce, and rapid prototyping makes it easy to put together something that calls an API before anyone has thought through how often it should call, what it should keep, or who will maintain it. We would rather spend time explaining our use of the data than assume access should be automatic.

That is our reading of the trade-off, not a claim about why Yahoo changed its process. For us, the reapplication and DocuSign form became part of getting the integration working again. If you are building against the same API, start with Yahoo’s developer access page and allow time for the application, not just the code.

The site still expected a game every night

Access was only one problem. BlatBlat was still asking for game data through the summer, then showing “No games for Thu, Aug 27”. Correct, and useless. Naming the date made it look as though we expected a game that night.

We now treat the off-season as a state the site can recognise. A decided NBA Finals triggers the change, so we do not have to guess which day the season will end. We slow the crawl, keep news and transactions moving, and label completed standings and leaders accordingly.

Check for the next schedule without hammering the source

The return date is harder. When the Finals finish, the next season’s schedule may not be published. We check weekly and show “schedule pending” until we have a date. A weekly check can be up to seven days behind publication; that is an acceptable delay for a season still months away.

We try the league’s static schedule first, then a second source if that fails. Once we have an opening date, a failed refresh cannot erase it. We keep the last known date until a successful check gives us something newer.

The off-season exposed two mistakes

Our season identifier rolled over in July, before the new season had any statistics. The leaders panel asked for numbers that did not exist and came back blank. It now uses the completed season while we wait for games to resume.

We also found transactions labelled as debuts. Joining a roster does not mean a player has taken the floor, and re-signing with the same club is not a debut. The correction checks transaction history and the schedule after parsing. It can remove a mistaken badge; missing history cannot create one.

A schedule you can read in two dimensions, then raise

Looking at the coming season led us to LayerChart, a Svelte chart library with an orthographic view. The schedule’s Charts tab starts flat, with one cell per team and week. Choose Graphic and those cells rise into columns: more games, more height. Hover for the count, or select a cell and open that week’s schedule.

Height helps us spot busy and light weeks across the season. The flat view and the original table are better for reading exact numbers, so both remain available. Fantasy counts use today’s rosters and the games that fit the starting lineup; they are not predictions of who will be healthy months from now.

You can try it on the 2026–27 schedule. The two undated NBA Cup games per team are still missing from week 7’s counts.

Filtering news

A player's name appears in an article. The crawler sees it, matches it, stores it. Now that article shows up on the player's news feed. Simple. Except the article isn't about them.

"Lakers finalise trade package centred around Anthony Davis" mentions LeBron James in paragraph four. A passing reference. Context, not subject. But the crawler doesn't know the difference between being the story and being mentioned in one.

The mention problem

Name-matching is binary. The name is either in the text or it isn't. What we actually need is relevance — is this article meaningfully about this player, or does it just reference them in passing?

A headline mention is strong signal. A first-paragraph mention is decent. A mention buried in paragraph six alongside fifteen other names is noise. But automating that distinction requires understanding article structure, not just scanning for strings.

What we do now

The current system uses headline fingerprinting and Jaccard similarity to deduplicate stories across sources. If ESPN and Yahoo both cover the same trade, only the highest-priority source version survives. That part works.

What doesn't work well is the relevance filter. A player mentioned once in a 2,000-word roundup gets the same treatment as a player who is the sole subject of a profile piece. Both get stored. Both show up. The feed fills with tangential mentions that dilute the signal.

Where this goes

The honest answer is that filtering context — distinguishing subject from mention — remains an ongoing challenge. The options are positional weighting (headline > lead > body), mention density (one name in 200 words vs. one name in 2,000), or co-occurrence patterns (is this player mentioned alongside their team's activity, or just namedropped?).

None of these are clean. All of them are better than what we have, which is: if the name appears, the article counts. Working on it.

The player who never misses vs. the player who never plays

Why TCR and PGR tell different stories about the same season

Two numbers sit side by side on every player page. TCR. PGR. One measures what you did all season. The other measures what you did when you showed up. In most cases they track together. When they diverge, they reveal something the box score hides.

What they are

PGR is Per Game Rank. It takes a player's per-game averages across nine categories—points, rebounds, assists, steals, blocks, threes, turnovers, FG impact, FT impact—converts each to a z-score against the league, and ranks the composite. It answers: how good is this player on any given night?

TCR is Total Contribution Rank. Same nine categories, but using season totals instead of averages. Benchmarked against the top 170 players by minutes. It answers: how much has this player actually contributed to your fantasy team this year?

The gap between them is where the story lives.

The case for showing up

Jay Huff
Jay Huff
IND · C · 28 years old
Games played75
Minutes/game20.8
Points9.3
Rebounds3.8
Assists1.3
Blocks1.8
FG%47.3%
FT%81.9%
TCR 56
PGR 133
Gap +77

Jay Huff is not a star. 9.3 points, 3.8 boards, 1.8 blocks in 21 minutes off the bench. His per-game rank says he is the 133rd best fantasy player in the league. That's barely rosterable.

His season rank says 56th. That puts him ahead of players you actually drafted. Players you traded for. Players you talk about. The reason is the number in bold above: 75 games played. Huff shows up. Game after game after game. Those 1.8 blocks per night compound into a season-long blocks total that outpaces players with better per-game numbers who sat out 20 games.

In a fantasy season that runs six months, availability is not just the best ability. It is the entire argument.

The case for not showing up

Kristaps Porzingis
Kristaps Porziņģis
GSW · F-C · 30 years old
Games played28
Minutes/game23.9
Points17.3
Rebounds4.9
Assists2.6
Blocks1.3
FG%45.4%
FT%83.1%
PGR 48
TCR 254
Gap −206

Kristaps Porziņģis has played 28 games this season. When he plays, he is the 48th best fantasy player in basketball. But he has missed more games than he has played. His season total contribution ranks him 254th. That is not a roster spot. That is a memory.

This is the trap that PGR sets for you. Porziņģis looks like a top-50 player. He plays like a top-50 player. But he plays less than half the season, and fantasy does not give credit for games you could have played.

The outliers

The most dramatic gaps reveal the season's truths. Joel Embiid: PGR 14, TCR 136. When he plays he is elite. He has played 36 games. Jimmy Butler: PGR 16, TCR 122. Anthony Davis: PGR 31, TCR 278. Paul George: PGR 37, TCR 222. Talent does not expire. But it does depreciate when it sits on the bench in a suit.

PlayerGPPGRTCRGap
Joel Embiid3614136−122
Jimmy Butler III3816122−106
Anthony Davis2031278−247
Paul George3037222−185
Jay Huff7513356+77
Nickeil Alexander-Walker732413+11
Mikal Bridges753515+20
Desmond Bane742817+11

The bottom half of that table is the longevity argument. Mikal Bridges at PGR 35 is not exciting. At TCR 15 he is one of the most valuable fantasy assets in basketball, because he played 75 games and his contributions compounded quietly over six months.

What to do with this

If you play weekly head-to-head, PGR matters more on any given matchup. But if you are building a roster for the long season—or trading mid-year—TCR is the number that tells the truth. A player who gives you 80% of the value for 95% of the games will outscore a player who gives you 100% of the value for 40% of the games.

You can explore both ranks on the Players page, sorted and filtered however you like. The gap between the two columns is the season's real story.

The rule of thumb: A high TCR with a mediocre PGR is a player your league undervalues. A high PGR with a low TCR is a player your league overvalues.

Removing the minimums

The NBA uses qualification thresholds for percentage stats. 300 field goals made for FG%. 82 threes for 3P%. 125 free throws for FT%. These numbers exist so a player who goes 2-for-2 from three does not lead the league at 100%.

Fair enough for a full season. 82 games. Months of data. The thresholds filter noise.

Shrink the window to January and those numbers collapse. Nobody hits 300 field goals in a month. The thresholds that protected signal become the thing that kills it. Every player fails qualification. The leaderboard empties.

What we changed

When a custom date range is active, qualification minimums are removed entirely. All players with games in the window are eligible. FG%, 3P%, FT% leaders reflect the actual sample. Small sample, yes. Misleading, possibly. But visible. The user chose the window. The data should respect it.

A quiet line appears below the stats table: Qualification minimums removed for custom date range. No modal. No tooltip. Just a fact.

Why it matters

Thresholds are not universal constants. They are calibrated to a specific scope. Change the scope, recalibrate the threshold. In our case, recalibrating to zero was the honest answer.