<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>maydaystats</title>
<link>https://maydaystats.com/</link>
<atom:link href="https://maydaystats.com/index.xml" rel="self" type="application/rss+xml"/>
<description>A public sports analytics project covering baseball, hockey, and volleyball. Most posts are backed by a real, automated data pipeline - pulling live data into published analysis, with the code and methodology shown alongside the writing. Any post that isn&#39;t pipeline-backed says so up front.</description>
<image>
<url>https://maydaystats.com/images/og-default.png</url>
<title>maydaystats</title>
<link>https://maydaystats.com/</link>
<height>76</height>
<width>144</width>
</image>
<generator>quarto-1.6.39</generator>
<lastBuildDate>Tue, 28 Jul 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>What the Data Actually Says About Nashville’s Offseason</title>
  <link>https://maydaystats.com/posts/hockey/2026-07-28-predators-offseason-additions/</link>
  <description><![CDATA[ 




<p>The Predators are my favorite team, so this one’s personal, but the goal here is the same as every other post on this site: let the data drive the conversation, not a hunch. Nashville had a rough 2024-25 (more on that below), which is the backdrop for a genuinely busy offseason: trades for Mavrik Bourque, Ilya Lyubushkin, Jack Drury, Ross Colton, Nils Hoglander, and Adam Edstrom, plus free agent signings Alexander Kerfoot, Jack Ahcan, and Hunter Skinner.</p>
<p>Until recently, our <a href="../../../posts/hockey/2026-07-08-year-in-review/index.html">hockey pipeline</a> only held a single season of league-wide data, enough to look back at last year but not enough to tell a real trend from a one-year blip. We backfilled three additional seasons (2022-23 through 2024-25) specifically to check that before writing anything about these additions. What follows is what each of them has actually done over the last four years, not a projection of how Nashville’s 2026-27 season goes; that’s a different question, and one this post isn’t trying to answer.</p>
<section id="where-the-roster-turnover-came-from" class="level2">
<h2 class="anchored" data-anchor-id="where-the-roster-turnover-came-from">Where the roster turnover came from</h2>
<div id="cell-fetch-nsh-col-record" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.cloud <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> bigquery</span>
<span id="cb1-2"></span>
<span id="cb1-3">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bigquery.Client()</span>
<span id="cb1-4"></span>
<span id="cb1-5">record_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WITH tagged AS (</span></span>
<span id="cb1-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT game_id, game_date, team, decision, position_group,</span></span>
<span id="cb1-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CASE</span></span>
<span id="cb1-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      WHEN game_date BETWEEN '2022-10-01' AND '2023-06-30' THEN '2022-23'</span></span>
<span id="cb1-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      WHEN game_date BETWEEN '2023-10-01' AND '2024-06-30' THEN '2023-24'</span></span>
<span id="cb1-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      WHEN game_date BETWEEN '2024-10-01' AND '2025-06-30' THEN '2024-25'</span></span>
<span id="cb1-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      WHEN game_date BETWEEN '2025-10-01' AND '2026-06-30' THEN '2025-26'</span></span>
<span id="cb1-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    END AS season,</span></span>
<span id="cb1-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CAST(SUBSTR(CAST(game_id AS STRING), 5, 2) AS INT64) AS game_type</span></span>
<span id="cb1-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM `maydaystats.nhl_stats.boxscores`</span></span>
<span id="cb1-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE team IN ('NSH', 'COL')</span></span>
<span id="cb1-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT team, season,</span></span>
<span id="cb1-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNTIF(decision = 'W') AS wins,</span></span>
<span id="cb1-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNTIF(decision = 'L') AS losses</span></span>
<span id="cb1-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM tagged</span></span>
<span id="cb1-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE season IS NOT NULL AND game_type = 2 AND position_group = 'goalie' AND decision IS NOT NULL</span></span>
<span id="cb1-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY team, season</span></span>
<span id="cb1-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY team, season</span></span>
<span id="cb1-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-26"></span>
<span id="cb1-27">nsh_col_record <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(record_query).to_dataframe()</span>
<span id="cb1-28">nsh_col_record_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nsh_col_record.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-29">nsh_col_record_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-30">nsh_col_record_tbl</span></code></pre></div>
<div id="fetch-nsh-col-record" class="cell-output cell-output-display" data-execution_count="1">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">season</th>
<th data-quarto-table-cell-role="th">wins</th>
<th data-quarto-table-cell-role="th">losses</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>COL</td>
<td>2022-23</td>
<td>51</td>
<td>24</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>COL</td>
<td>2023-24</td>
<td>50</td>
<td>25</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>COL</td>
<td>2024-25</td>
<td>49</td>
<td>29</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>COL</td>
<td>2025-26</td>
<td>58</td>
<td>16</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>NSH</td>
<td>2022-23</td>
<td>42</td>
<td>32</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>NSH</td>
<td>2023-24</td>
<td>47</td>
<td>30</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>NSH</td>
<td>2024-25</td>
<td>30</td>
<td>44</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>NSH</td>
<td>2025-26</td>
<td>40</td>
<td>34</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Nashville went 42-32 and 47-30 the two seasons before last, then fell to 30-44 in 2024-25, before a partial recovery to 40-34 last season, still outside a playoff spot. That’s the record Chris MacFarland inherited when he was hired as President of Hockey Operations and General Manager on June 2, 2026, replacing Barry Trotz.</p>
<p>Every trade and signing covered in this post is MacFarland’s, made within his first six weeks in the job. That doesn’t mean he’s an unknown quantity, though: he spent the previous four seasons as general manager of the Colorado Avalanche, the same four seasons in our data above, and the table shows exactly why he was a sought-after hire. Colorado won 51, 50, and 49 games the first three of those seasons, then 58 last year on the way to the Presidents’ Trophy. Three of Nashville’s new additions, Jack Drury, Ross Colton, and Jack Ahcan, played for Colorado under MacFarland before he brought them with him, which changes how to read their sections below: this isn’t a GM betting on strangers, it’s a GM going back to players whose performance under his own front office he already has years of data on.</p>
</section>
<section id="the-additions-by-the-numbers" class="level2">
<h2 class="anchored" data-anchor-id="the-additions-by-the-numbers">The additions, by the numbers</h2>
<div id="fetch-additions" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb2-2"></span>
<span id="cb2-3">additions_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WITH tagged AS (</span></span>
<span id="cb2-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT *,</span></span>
<span id="cb2-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CASE</span></span>
<span id="cb2-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      WHEN game_date BETWEEN '2022-10-01' AND '2023-06-30' THEN '2022-23'</span></span>
<span id="cb2-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      WHEN game_date BETWEEN '2023-10-01' AND '2024-06-30' THEN '2023-24'</span></span>
<span id="cb2-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      WHEN game_date BETWEEN '2024-10-01' AND '2025-06-30' THEN '2024-25'</span></span>
<span id="cb2-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      WHEN game_date BETWEEN '2025-10-01' AND '2026-06-30' THEN '2025-26'</span></span>
<span id="cb2-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    END AS season,</span></span>
<span id="cb2-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CAST(SUBSTR(CAST(game_id AS STRING), 5, 2) AS INT64) AS game_type,</span></span>
<span id="cb2-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CAST(SPLIT(toi, ':')[OFFSET(0)] AS FLOAT64) + CAST(SPLIT(toi, ':')[OFFSET(1)] AS FLOAT64)/60 AS toi_min</span></span>
<span id="cb2-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM `maydaystats.nhl_stats.boxscores`</span></span>
<span id="cb2-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE player_name IN (</span></span>
<span id="cb2-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    'M. Bourque', 'I. Lyubushkin', 'J. Drury', 'R. Colton', 'N. Hoglander',</span></span>
<span id="cb2-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    'A. Edstrom', 'A. Kerfoot', 'J. Ahcan', 'H. Skinner'</span></span>
<span id="cb2-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  )</span></span>
<span id="cb2-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND position_group != 'goalie'</span></span>
<span id="cb2-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, season,</span></span>
<span id="cb2-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  STRING_AGG(DISTINCT team, '/') AS team,</span></span>
<span id="cb2-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNT(DISTINCT game_id) AS games,</span></span>
<span id="cb2-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(goals) AS goals, SUM(assists) AS assists, SUM(points) AS points,</span></span>
<span id="cb2-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SAFE_DIVIDE(SUM(points), COUNT(DISTINCT game_id)), 2) AS pts_per_gp,</span></span>
<span id="cb2-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(shots_on_goal) AS shots, SUM(hits) AS hits, SUM(blocked_shots) AS blocked,</span></span>
<span id="cb2-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(AVG(toi_min), 2) AS avg_toi</span></span>
<span id="cb2-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM tagged</span></span>
<span id="cb2-29"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE season IS NOT NULL AND game_type IN (2, 3)</span></span>
<span id="cb2-30"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, season</span></span>
<span id="cb2-31"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY player_name, season</span></span>
<span id="cb2-32"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-33"></span>
<span id="cb2-34">additions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(additions_query).to_dataframe()</span>
<span id="cb2-35"></span>
<span id="cb2-36">TEAM_NAMES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb2-37">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DAL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Dallas Stars"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"COL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Colorado Avalanche"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"VAN"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Vancouver Canucks"</span>,</span>
<span id="cb2-38">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BUF"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Buffalo Sabres"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TOR"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Toronto Maple Leafs"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ANA"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Anaheim Ducks"</span>,</span>
<span id="cb2-39">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ARI"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Arizona Coyotes"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UTA"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Utah Hockey Club/Mammoth"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CAR"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Carolina Hurricanes"</span>,</span>
<span id="cb2-40">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TBL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Tampa Bay Lightning"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"STL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"St. Louis Blues"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NYR"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"New York Rangers"</span>,</span>
<span id="cb2-41">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NSH"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Nashville Predators"</span>,</span>
<span id="cb2-42">}</span></code></pre></div>
</div>
<p>Every table in the sections below is pulled from this same four-season dataset, filtered to that one player. All games played column figures are regular season plus playoffs combined; “avg_toi” is average time on ice per game, in minutes.</p>
<section id="mavrik-bourque-a-two-year-breakout-not-a-one-year-spike" class="level3">
<h3 class="anchored" data-anchor-id="mavrik-bourque-a-two-year-breakout-not-a-one-year-spike">Mavrik Bourque: a two-year breakout, not a one-year spike</h3>
<div class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">bourque <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> additions[additions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"M. Bourque"</span>]</span>
<span id="cb3-2">bourque_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bourque[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"season"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"games"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"goals"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"assists"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pts_per_gp"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg_toi"</span>]].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-3">bourque_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-4">bourque_tbl</span></code></pre></div>
<div id="tbl-bourque" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="3">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-bourque-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;1: Mavrik Bourque, 2022-23 through 2025-26
</figcaption>
<div aria-describedby="tbl-bourque-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="3">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">season</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">games</th>
<th data-quarto-table-cell-role="th">goals</th>
<th data-quarto-table-cell-role="th">assists</th>
<th data-quarto-table-cell-role="th">points</th>
<th data-quarto-table-cell-role="th">pts_per_gp</th>
<th data-quarto-table-cell-role="th">avg_toi</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2023-24</td>
<td>DAL</td>
<td>2</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.00</td>
<td>9.43</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2024-25</td>
<td>DAL</td>
<td>76</td>
<td>11.0</td>
<td>14.0</td>
<td>25.0</td>
<td>0.33</td>
<td>12.60</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2025-26</td>
<td>DAL</td>
<td>88</td>
<td>21.0</td>
<td>21.0</td>
<td>42.0</td>
<td>0.48</td>
<td>15.48</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Bourque barely played in his first two seasons with Dallas, then put together two real, progressively better years: 25 points in 76 games (.33 points per game) in 2024-25, then 42 points in 88 games (.48 points per game) last season, a career high. His average ice time climbed right alongside it, from 12.6 to 15.5 minutes a night. That’s usage and production rising together across two separate seasons, not one good stretch. It’s also why Dallas moved to re-sign him the same day it traded him away, and why Nashville followed with a six-year, $33 million extension almost immediately after.</p>
</section>
<section id="nils-hoglander-the-clearest-risk-in-the-group" class="level3">
<h3 class="anchored" data-anchor-id="nils-hoglander-the-clearest-risk-in-the-group">Nils Hoglander: the clearest risk in the group</h3>
<div class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">hoglander <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> additions[additions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"N. Hoglander"</span>]</span>
<span id="cb4-2">hoglander_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hoglander[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"season"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"games"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"goals"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"assists"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pts_per_gp"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg_toi"</span>]].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb4-3">hoglander_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb4-4">hoglander_tbl</span></code></pre></div>
<div id="tbl-hoglander" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="4">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-hoglander-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;2: Nils Hoglander, 2022-23 through 2025-26
</figcaption>
<div aria-describedby="tbl-hoglander-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="4">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">season</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">games</th>
<th data-quarto-table-cell-role="th">goals</th>
<th data-quarto-table-cell-role="th">assists</th>
<th data-quarto-table-cell-role="th">points</th>
<th data-quarto-table-cell-role="th">pts_per_gp</th>
<th data-quarto-table-cell-role="th">avg_toi</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2022-23</td>
<td>VAN</td>
<td>25</td>
<td>3.0</td>
<td>6.0</td>
<td>9.0</td>
<td>0.36</td>
<td>12.06</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2023-24</td>
<td>VAN</td>
<td>91</td>
<td>25.0</td>
<td>13.0</td>
<td>38.0</td>
<td>0.42</td>
<td>11.85</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2024-25</td>
<td>VAN</td>
<td>72</td>
<td>8.0</td>
<td>17.0</td>
<td>25.0</td>
<td>0.35</td>
<td>12.13</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>2025-26</td>
<td>VAN</td>
<td>38</td>
<td>2.0</td>
<td>3.0</td>
<td>5.0</td>
<td>0.13</td>
<td>11.52</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Hoglander is Bourque’s mirror image. He broke out with Vancouver in 2023-24 (38 points in 91 games, .42 per game), then both his production rate and his games played have fallen every season since: .35 points per game in 2024-25, then just .13 points per game across only 38 games last season, his worst rate anywhere in this four-year window, including a shortened 2022-23. Nothing in this dataset explains why; injury and role changes both look plausible, and we don’t have the data to say which. But the direction itself, happening right in his walk year before a trade, is worth flagging plainly rather than assuming last year was a fluke.</p>
</section>
<section id="two-opposite-trajectories-side-by-side" class="level3">
<h3 class="anchored" data-anchor-id="two-opposite-trajectories-side-by-side">Two opposite trajectories, side by side</h3>
<p>Put the two players’ four-year lines on the same chart and the contrast is hard to miss: one arrow pointing up, one pointing down, right as both of them join the same team.</p>
<div id="cell-fig-bourque-hoglander-trend" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb5-2"></span>
<span id="cb5-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Bourque has no regular-season or playoff games in 2022-23 (just a few</span></span>
<span id="cb5-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># preseason appearances, filtered out above), so his line is missing that</span></span>
<span id="cb5-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># season while Hoglander's isn't. Reindexing both to the same full season</span></span>
<span id="cb5-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># list, rather than plotting each dataframe's own "season" column directly,</span></span>
<span id="cb5-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># keeps the x-axis in chronological order instead of matplotlib appending</span></span>
<span id="cb5-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># whichever season it encounters first as a new category, which is what</span></span>
<span id="cb5-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># was pushing 2022-23 to the wrong end of the chart.</span></span>
<span id="cb5-10">ALL_SEASONS <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2022-23"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2023-24"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2024-25"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2025-26"</span>]</span>
<span id="cb5-11">bourque_by_season <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bourque.set_index(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"season"</span>)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pts_per_gp"</span>].reindex(ALL_SEASONS)</span>
<span id="cb5-12">hoglander_by_season <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hoglander.set_index(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"season"</span>)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pts_per_gp"</span>].reindex(ALL_SEASONS)</span>
<span id="cb5-13"></span>
<span id="cb5-14">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb5-15">ax.plot(ALL_SEASONS, bourque_by_season, marker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o"</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.5</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bourque"</span>)</span>
<span id="cb5-16">ax.plot(ALL_SEASONS, hoglander_by_season, marker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"o"</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.5</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#c0392b"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hoglander"</span>)</span>
<span id="cb5-17">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Points per game"</span>)</span>
<span id="cb5-18">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Two Opposite Trajectories"</span>)</span>
<span id="cb5-19">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb5-20">ax.legend(loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper center"</span>, bbox_to_anchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.12</span>), ncol<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb5-21">plt.tight_layout()</span>
<span id="cb5-22">plt.show()</span></code></pre></div>
<div class="cell-output cell-output-display">
<div id="fig-bourque-hoglander-trend" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-bourque-hoglander-trend-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://maydaystats.com/posts/hockey/2026-07-28-predators-offseason-additions/index_files/figure-html/fig-bourque-hoglander-trend-output-1.png" width="760" height="468" class="figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-bourque-hoglander-trend-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Points per game by season: Bourque’s rise vs.&nbsp;Hoglander’s decline
</figcaption>
</figure>
</div>
</div>
</div>
<p>Neither line guarantees what happens next in Nashville, but heading into the same season, one of these two is trending toward more opportunity and one is trending toward less.</p>
</section>
<section id="jack-drury-a-trade-adjustment-hes-already-lived-through-once" class="level3">
<h3 class="anchored" data-anchor-id="jack-drury-a-trade-adjustment-hes-already-lived-through-once">Jack Drury: a trade adjustment he’s already lived through once</h3>
<div class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">drury <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> additions[additions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"J. Drury"</span>]</span>
<span id="cb6-2">drury_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> drury[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"season"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"games"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"goals"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"assists"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pts_per_gp"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg_toi"</span>]].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb6-3">drury_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-4">drury_tbl</span></code></pre></div>
<div id="tbl-drury" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="6">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-drury-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;3: Jack Drury, 2022-23 through 2025-26
</figcaption>
<div aria-describedby="tbl-drury-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="6">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">season</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">games</th>
<th data-quarto-table-cell-role="th">goals</th>
<th data-quarto-table-cell-role="th">assists</th>
<th data-quarto-table-cell-role="th">points</th>
<th data-quarto-table-cell-role="th">pts_per_gp</th>
<th data-quarto-table-cell-role="th">avg_toi</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2022-23</td>
<td>CAR</td>
<td>51</td>
<td>2.0</td>
<td>9.0</td>
<td>11.0</td>
<td>0.22</td>
<td>11.58</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2023-24</td>
<td>CAR</td>
<td>85</td>
<td>9.0</td>
<td>23.0</td>
<td>32.0</td>
<td>0.38</td>
<td>12.38</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2024-25</td>
<td>COL/CAR</td>
<td>79</td>
<td>9.0</td>
<td>11.0</td>
<td>20.0</td>
<td>0.25</td>
<td>12.92</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>2025-26</td>
<td>COL</td>
<td>95</td>
<td>13.0</td>
<td>20.0</td>
<td>33.0</td>
<td>0.35</td>
<td>13.94</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Drury already has a data point for exactly the situation he’s about to go through again. His rate production dipped the season he was traded from Carolina to Colorado, 2024-25 (.25 points per game, down from .38 the year before), then recovered to a new career high in ice time (13.9 minutes a night) once he settled in with Colorado last season, under MacFarland. A mid-career trade denting his numbers for a season before a rebound isn’t a guarantee it happens the same way twice, but it’s a real, observed precedent for what an adjustment period might look like for him specifically, and it’s worth noting the GM who watched that rebound happen up close is the same one bringing him to Nashville.</p>
</section>
<section id="ross-colton-the-steadiest-piece-of-the-return" class="level3">
<h3 class="anchored" data-anchor-id="ross-colton-the-steadiest-piece-of-the-return">Ross Colton: the steadiest piece of the return</h3>
<div class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">colton <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> additions[additions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"R. Colton"</span>]</span>
<span id="cb7-2">colton_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> colton[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"season"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"games"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pts_per_gp"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hits"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"blocked"</span>]].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb7-3">colton_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb7-4">colton_tbl</span></code></pre></div>
<div id="tbl-colton" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="7">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-colton-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;4: Ross Colton, 2022-23 through 2025-26
</figcaption>
<div aria-describedby="tbl-colton-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="7">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">season</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">games</th>
<th data-quarto-table-cell-role="th">points</th>
<th data-quarto-table-cell-role="th">pts_per_gp</th>
<th data-quarto-table-cell-role="th">hits</th>
<th data-quarto-table-cell-role="th">blocked</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2022-23</td>
<td>TBL</td>
<td>87</td>
<td>36.0</td>
<td>0.41</td>
<td>207.0</td>
<td>29.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2023-24</td>
<td>COL</td>
<td>91</td>
<td>44.0</td>
<td>0.48</td>
<td>184.0</td>
<td>57.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2024-25</td>
<td>COL</td>
<td>62</td>
<td>29.0</td>
<td>0.47</td>
<td>148.0</td>
<td>42.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>2025-26</td>
<td>COL</td>
<td>84</td>
<td>30.0</td>
<td>0.36</td>
<td>205.0</td>
<td>31.0</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Colton is the lowest-variance player in this group: between 29 and 44 points every season across two different rosters (Tampa Bay, then Colorado, where he’s played for MacFarland since the 2023-24 trade), and consistently among the heaviest hitters in the entire sample, 148 to 207 hits a year. Whatever role he’s handed in Nashville, the four-year track record, three of those years under the GM who just signed him, says both the scoring floor and the physical identity travel with him.</p>
</section>
<section id="ilya-lyubushkin-minutes-and-physicality-not-scoring" class="level3">
<h3 class="anchored" data-anchor-id="ilya-lyubushkin-minutes-and-physicality-not-scoring">Ilya Lyubushkin: minutes and physicality, not scoring</h3>
<div class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">lyubushkin <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> additions[additions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"I. Lyubushkin"</span>]</span>
<span id="cb8-2">lyubushkin_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lyubushkin[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"season"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"games"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hits"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"blocked"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg_toi"</span>]].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb8-3">lyubushkin_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-4">lyubushkin_tbl</span></code></pre></div>
<div id="tbl-lyubushkin" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="8">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-lyubushkin-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;5: Ilya Lyubushkin, 2022-23 through 2025-26
</figcaption>
<div aria-describedby="tbl-lyubushkin-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="8">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">season</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">games</th>
<th data-quarto-table-cell-role="th">points</th>
<th data-quarto-table-cell-role="th">hits</th>
<th data-quarto-table-cell-role="th">blocked</th>
<th data-quarto-table-cell-role="th">avg_toi</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2022-23</td>
<td>BUF</td>
<td>68</td>
<td>14.0</td>
<td>99.0</td>
<td>104.0</td>
<td>15.01</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2023-24</td>
<td>TOR/ANA</td>
<td>81</td>
<td>11.0</td>
<td>205.0</td>
<td>175.0</td>
<td>17.07</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2024-25</td>
<td>DAL</td>
<td>94</td>
<td>17.0</td>
<td>102.0</td>
<td>151.0</td>
<td>17.34</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>2025-26</td>
<td>DAL</td>
<td>55</td>
<td>11.0</td>
<td>74.0</td>
<td>94.0</td>
<td>15.56</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Lyubushkin’s point totals, 11 to 17 a season, are close to noise for a full-time NHL defenseman. What isn’t noise is his usage: 15 to 17-plus minutes a night in every one of the last four seasons, with some of the heaviest hits-plus-blocks totals of anyone in this group, including a career-high 205 hits in 2023-24, the same season he was traded mid-year from Anaheim to Toronto. He was brought in to defend, not to add offense, and the numbers back that up every single year.</p>
</section>
<section id="alexander-kerfoot-reliable-across-three-team-identities" class="level3">
<h3 class="anchored" data-anchor-id="alexander-kerfoot-reliable-across-three-team-identities">Alexander Kerfoot: reliable across three team identities</h3>
<div class="cell" data-execution_count="9">
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">kerfoot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> additions[additions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A. Kerfoot"</span>]</span>
<span id="cb9-2">kerfoot_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kerfoot[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"season"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"games"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pts_per_gp"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg_toi"</span>]].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb9-3">kerfoot_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb9-4">kerfoot_tbl</span></code></pre></div>
<div id="tbl-kerfoot" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="9">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-kerfoot-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;6: Alexander Kerfoot, 2022-23 through 2025-26
</figcaption>
<div aria-describedby="tbl-kerfoot-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="9">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">season</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">games</th>
<th data-quarto-table-cell-role="th">points</th>
<th data-quarto-table-cell-role="th">pts_per_gp</th>
<th data-quarto-table-cell-role="th">avg_toi</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2022-23</td>
<td>TOR</td>
<td>93</td>
<td>34.0</td>
<td>0.37</td>
<td>14.57</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2023-24</td>
<td>ARI</td>
<td>82</td>
<td>45.0</td>
<td>0.55</td>
<td>17.43</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2024-25</td>
<td>UTA</td>
<td>81</td>
<td>28.0</td>
<td>0.35</td>
<td>15.25</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>2025-26</td>
<td>UTA</td>
<td>40</td>
<td>14.0</td>
<td>0.35</td>
<td>13.42</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Kerfoot has posted between .35 and .55 points per game in every one of the last four seasons, across two different organizations, one of which changed its own name twice in that span (Arizona Coyotes became the Utah Hockey Club in 2024-25, then the Utah Mammoth last season). Middle-six production that holds up regardless of the roster around it is exactly what a two-year, $7 million free agent deal is supposed to buy.</p>
</section>
<section id="adam-edstrom-energy-line-depth-not-a-scoring-addition" class="level3">
<h3 class="anchored" data-anchor-id="adam-edstrom-energy-line-depth-not-a-scoring-addition">Adam Edstrom: energy-line depth, not a scoring addition</h3>
<div class="cell" data-execution_count="10">
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">edstrom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> additions[additions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A. Edstrom"</span>]</span>
<span id="cb10-2">edstrom_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> edstrom[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"season"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"games"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pts_per_gp"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg_toi"</span>]].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb10-3">edstrom_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb10-4">edstrom_tbl</span></code></pre></div>
<div id="tbl-edstrom" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="10">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-edstrom-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;7: Adam Edstrom, 2023-24 through 2025-26
</figcaption>
<div aria-describedby="tbl-edstrom-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="10">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">season</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">games</th>
<th data-quarto-table-cell-role="th">points</th>
<th data-quarto-table-cell-role="th">pts_per_gp</th>
<th data-quarto-table-cell-role="th">avg_toi</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2023-24</td>
<td>NYR</td>
<td>11</td>
<td>2.0</td>
<td>0.18</td>
<td>8.41</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2024-25</td>
<td>NYR</td>
<td>51</td>
<td>9.0</td>
<td>0.18</td>
<td>9.27</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2025-26</td>
<td>NYR</td>
<td>35</td>
<td>7.0</td>
<td>0.20</td>
<td>9.55</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Edstrom’s ice time, 8 to 9.5 minutes a night every season he’s played, is the lowest of any forward in this group, and his points total tracks with it. That profile, heavy on shifts, light on minutes, is a bottom-six energy role, not a scoring addition, and nothing here suggests Nashville is expecting otherwise.</p>
</section>
<section id="jack-ahcan-and-hunter-skinner-too-little-nhl-sample-to-say-anything" class="level3">
<h3 class="anchored" data-anchor-id="jack-ahcan-and-hunter-skinner-too-little-nhl-sample-to-say-anything">Jack Ahcan and Hunter Skinner: too little NHL sample to say anything</h3>
<div class="cell" data-execution_count="11">
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">depth_signings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> additions[additions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>].isin([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"J. Ahcan"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"H. Skinner"</span>])]</span>
<span id="cb11-2">depth_signings_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> depth_signings[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"season"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"games"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>]].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb11-3">depth_signings_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb11-4">depth_signings_tbl</span></code></pre></div>
<div id="tbl-ahcan-skinner" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="11">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-ahcan-skinner-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;8: Jack Ahcan and Hunter Skinner, every NHL game either has played, 2022-23 through 2025-26
</figcaption>
<div aria-describedby="tbl-ahcan-skinner-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="11">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">season</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">games</th>
<th data-quarto-table-cell-role="th">points</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>H. Skinner</td>
<td>2025-26</td>
<td>STL</td>
<td>1</td>
<td>0.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>J. Ahcan</td>
<td>2024-25</td>
<td>COL</td>
<td>2</td>
<td>0.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>J. Ahcan</td>
<td>2025-26</td>
<td>COL</td>
<td>14</td>
<td>2.0</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Both of these signings genuinely don’t have enough played games to analyze the way the seven players above do. Ahcan has appeared in 16 total NHL games across all four seasons in our data, all of them with Colorado; Skinner has appeared in one. That’s the entire table above, not a partial view of it. That’s not a data gap on our end, it’s an accurate reflection of two-way contracts for players who’ve spent most of the last four years in the minors. Even Ahcan’s tie to MacFarland’s Colorado roster doesn’t change that; 16 games is still 16 games. Grouping them here instead of forcing a trend out of a handful of games is the honest version of covering them.</p>
</section>
</section>
<section id="what-this-actually-adds-up-to" class="level2">
<h2 class="anchored" data-anchor-id="what-this-actually-adds-up-to">What this actually adds up to</h2>
<p>Four years of real data points to a roster that added one legitimate growth story (Bourque), one clear risk worth watching (Hoglander), one player with a specific, already-lived precedent for a trade adjustment (Drury), two proven physical, low-variance role players (Colton, Lyubushkin), one reliable if unspectacular scorer (Kerfoot), one honest depth piece (Edstrom), and two players who are still too much of an unknown to say anything about. Three of those names, Drury, Colton, and Ahcan, aren’t strangers to the new front office either; their track records are also a track record of playing for MacFarland himself. None of that adds up to a predicted record or a playoff projection for 2026-27; that would take more than four years of individual track records, it would take assumptions about health, chemistry, and a full season that hasn’t been played yet. What the data can say, honestly, is what each of these players, and the GM who brought several of them along with him, has actually done before this summer in Nashville.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>This post uses Quarto’s frozen execution (<code>freeze: auto</code>): the numbers above reflect the <code>nhl_stats.boxscores</code> data as of whenever this was last rendered locally, not a live query on every page load. The 2022-23 through 2024-25 seasons were backfilled specifically for this post; the 2025-26 season was already in place from the <a href="../../../posts/hockey/2026-07-08-year-in-review/index.html">hockey year-in-review post</a>.</p>
</div>
</div>


</section>

 ]]></description>
  <category>Hockey</category>
  <category>Featured</category>
  <category>Offseason</category>
  <guid>https://maydaystats.com/posts/hockey/2026-07-28-predators-offseason-additions/</guid>
  <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>First Half in Review: Hitting and Pitching Trends, Team Momentum, and the Reds</title>
  <link>https://maydaystats.com/posts/baseball/2026-07-13-first-half-review/</link>
  <description><![CDATA[ 




<p>Every team has now played most of its schedule, and with our <a href="../../../posts/baseball/2026-07-08-first-pitches/index.html">Statcast pipeline</a> holding a full first half of pitch-level data (March 25 through July 12), and having just covered the <a href="../../../posts/baseball/2026-07-10-all-star-reality-check/index.html">All-Star rosters themselves</a>, it’s a good moment to ask three broader questions: how has hitting and pitching around the league actually changed since Opening Day, which teams are genuinely trending up or down heading into the second half, and how do our own team’s hitting and pitching numbers, the Cincinnati Reds, actually add up.</p>
<section id="how-the-league-has-hit-and-pitched-so-far" class="level2">
<h2 class="anchored" data-anchor-id="how-the-league-has-hit-and-pitched-so-far">How the league has hit and pitched so far</h2>
<div id="cell-fetch-monthly-trends" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.cloud <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> bigquery</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb1-3"></span>
<span id="cb1-4">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bigquery.Client()</span>
<span id="cb1-5"></span>
<span id="cb1-6">monthly_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb1-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FORMAT_DATE('%Y-%m', CAST(game_date AS DATE)) AS month,</span></span>
<span id="cb1-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SUM(woba_value)/SUM(woba_denom), 3) AS lg_woba,</span></span>
<span id="cb1-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SAFE_DIVIDE(COUNTIF(events = 'strikeout'), COUNTIF(events IS NOT NULL)), 3) AS k_rate,</span></span>
<span id="cb1-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SAFE_DIVIDE(COUNTIF(events = 'home_run'), COUNTIF(events IS NOT NULL)), 4) AS hr_rate,</span></span>
<span id="cb1-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(AVG(IF(pitch_type IN ('FF', 'SI'), release_speed, NULL)), 1) AS avg_fastball_velo,</span></span>
<span id="cb1-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SAFE_DIVIDE(COUNTIF(description IN ('swinging_strike', 'swinging_strike_blocked')), COUNTIF(description IS NOT NULL)), 3) AS whiff_rate</span></span>
<span id="cb1-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlb_statcast.pitches`</span></span>
<span id="cb1-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE game_type = 'R'</span></span>
<span id="cb1-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY month</span></span>
<span id="cb1-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY month</span></span>
<span id="cb1-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-19"></span>
<span id="cb1-20">monthly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(monthly_query).to_dataframe()</span>
<span id="cb1-21">monthly_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> monthly.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-22">monthly_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-23">monthly_tbl</span></code></pre></div>
<div id="fetch-monthly-trends" class="cell-output cell-output-display" data-execution_count="1">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">month</th>
<th data-quarto-table-cell-role="th">lg_woba</th>
<th data-quarto-table-cell-role="th">k_rate</th>
<th data-quarto-table-cell-role="th">hr_rate</th>
<th data-quarto-table-cell-role="th">avg_fastball_velo</th>
<th data-quarto-table-cell-role="th">whiff_rate</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2026-03</td>
<td>0.317</td>
<td>0.244</td>
<td>0.0279</td>
<td>94.5</td>
<td>0.119</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2026-04</td>
<td>0.328</td>
<td>0.216</td>
<td>0.0281</td>
<td>94.3</td>
<td>0.106</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2026-05</td>
<td>0.320</td>
<td>0.218</td>
<td>0.0284</td>
<td>94.5</td>
<td>0.107</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>2026-06</td>
<td>0.334</td>
<td>0.222</td>
<td>0.0342</td>
<td>94.7</td>
<td>0.109</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>2026-07</td>
<td>0.332</td>
<td>0.221</td>
<td>0.0353</td>
<td>94.7</td>
<td>0.108</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<div id="cell-fig-hr-rate-by-month" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb2-2"></span>
<span id="cb2-3">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb2-4">ax.bar(monthly[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"month"</span>], monthly[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hr_rate"</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span>)</span>
<span id="cb2-5">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Month"</span>)</span>
<span id="cb2-6">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Home runs per plate appearance"</span>)</span>
<span id="cb2-7">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"League HR Rate by Month, 2026 First Half"</span>)</span>
<span id="cb2-8">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb2-9">plt.tight_layout()</span>
<span id="cb2-10">plt.show()</span></code></pre></div>
<div class="cell-output cell-output-display">
<div id="fig-hr-rate-by-month" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-hr-rate-by-month-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://maydaystats.com/posts/baseball/2026-07-13-first-half-review/index_files/figure-html/fig-hr-rate-by-month-output-1.png" width="760" height="470" class="figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-hr-rate-by-month-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: League-wide home run rate per plate appearance, by month
</figcaption>
</figure>
</div>
</div>
</div>
<p>A couple of these columns are worth defining before going further. wOBA (weighted on-base average) is the rate stat used throughout this post instead of batting average or OPS, because it assigns each outcome, a walk, a single, a double, a home run, and so on, its actual measured run value rather than treating every hit the same way or adding two differently-scaled numbers together. A .330 wOBA is roughly league average; anything north of .370 is excellent. Whiff rate is simply the share of pitches a hitter swings at and misses entirely, a proxy for how often pitchers are missing bats regardless of the final result.</p>
<p>With those defined, the league-wide numbers tell a fairly consistent story as the season has warmed up: home run rate has climbed from 2.8% of plate appearances in March to 3.5% in July, a jump of roughly a quarter, and wOBA has followed the same direction, up from .317 to .332. Strikeout rate has eased at the same time, down from 24.4% to 22.1%. The pitching side of the ledger hasn’t moved much to explain that: average fastball velocity has held steady around 94.5 mph all season, and whiff rate has barely budged (11.9% in March, 10.8% in July). Pitchers aren’t throwing softer or missing fewer bats; hitters are simply doing more damage on the contact they were already making. Two things typically get credited for that, and this data can’t cleanly separate them: the ball carries better in warmer summer air, and hitters are also just more locked in by June and July, with a few extra months of live at-bats to groove their timing against pitching they’ve now often seen once already. None of this is unique to this season, but it’s a useful reminder that stats collected in April and stats collected in July aren’t quite the same measurement, which matters for the next question: which individual hitters and pitchers are actually trending, separate from that league-wide drift.</p>
<div id="fetch-batter-movers" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">movers_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WITH pa AS (</span></span>
<span id="cb3-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb3-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    batter,</span></span>
<span id="cb3-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CAST(game_date AS DATE) AS game_date,</span></span>
<span id="cb3-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    woba_value, woba_denom</span></span>
<span id="cb3-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM `maydaystats.mlb_statcast.pitches`</span></span>
<span id="cb3-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE game_type = 'R' AND woba_denom IS NOT NULL</span></span>
<span id="cb3-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb3-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">max_date AS (SELECT MAX(game_date) AS d FROM pa),</span></span>
<span id="cb3-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">splits AS (</span></span>
<span id="cb3-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb3-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    batter,</span></span>
<span id="cb3-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_value, 0)) AS early_val,</span></span>
<span id="cb3-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_denom, 0)) AS early_pa,</span></span>
<span id="cb3-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_value, 0)) AS recent_val,</span></span>
<span id="cb3-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_denom, 0)) AS recent_pa</span></span>
<span id="cb3-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM pa</span></span>
<span id="cb3-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  GROUP BY batter</span></span>
<span id="cb3-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb3-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb3-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  b.player_name, b.team_name,</span></span>
<span id="cb3-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  s.early_pa, ROUND(s.early_val / s.early_pa, 3) AS early_woba,</span></span>
<span id="cb3-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  s.recent_pa, ROUND(s.recent_val / s.recent_pa, 3) AS recent_woba,</span></span>
<span id="cb3-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(s.recent_val / s.recent_pa - s.early_val / s.early_pa, 3) AS woba_delta</span></span>
<span id="cb3-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM splits s</span></span>
<span id="cb3-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">JOIN `maydaystats.mlb_season_stats.batting_latest` b ON b.player_id = s.batter</span></span>
<span id="cb3-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE s.early_pa &gt;= 100 AND s.recent_pa &gt;= 100</span></span>
<span id="cb3-29"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY woba_delta DESC</span></span>
<span id="cb3-30"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-31"></span>
<span id="cb3-32">movers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(movers_query).to_dataframe()</span>
<span id="cb3-33">risers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> movers.head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>).reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-34">risers.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-35">fallers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> movers.tail(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>).sort_values(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"woba_delta"</span>).reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-36">fallers.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span></code></pre></div>
</div>
<p>Splitting each qualified hitter’s plate appearances into “before the last 45 days” and “the last 45 days” isolates real in-season swings from that seasonal drift. The clearest risers:</p>
<div class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">risers[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"early_woba"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"recent_woba"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"woba_delta"</span>]]</span></code></pre></div>
<div id="tbl-batter-risers" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="4">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-batter-risers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;1: Biggest wOBA gainers, early season vs.&nbsp;last 45 days (min. 100 PA in each window)
</figcaption>
<div aria-describedby="tbl-batter-risers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="4">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team_name</th>
<th data-quarto-table-cell-role="th">early_woba</th>
<th data-quarto-table-cell-role="th">recent_woba</th>
<th data-quarto-table-cell-role="th">woba_delta</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Pete Crow-Armstrong</td>
<td>Chicago Cubs</td>
<td>0.307</td>
<td>0.507</td>
<td>0.200</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Kyle Karros</td>
<td>Colorado Rockies</td>
<td>0.289</td>
<td>0.460</td>
<td>0.170</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Caleb Durbin</td>
<td>Boston Red Sox</td>
<td>0.236</td>
<td>0.385</td>
<td>0.149</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Victor Caratini</td>
<td>Minnesota Twins</td>
<td>0.256</td>
<td>0.404</td>
<td>0.148</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Tyler Soderstrom</td>
<td>Athletics</td>
<td>0.314</td>
<td>0.461</td>
<td>0.147</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>And the clearest decliners:</p>
<div class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">fallers[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"early_woba"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"recent_woba"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"woba_delta"</span>]]</span></code></pre></div>
<div id="tbl-batter-fallers" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="5">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-batter-fallers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;2: Biggest wOBA decliners, early season vs.&nbsp;last 45 days (min. 100 PA in each window)
</figcaption>
<div aria-describedby="tbl-batter-fallers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="5">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team_name</th>
<th data-quarto-table-cell-role="th">early_woba</th>
<th data-quarto-table-cell-role="th">recent_woba</th>
<th data-quarto-table-cell-role="th">woba_delta</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Drake Baldwin</td>
<td>Atlanta Braves</td>
<td>0.409</td>
<td>0.225</td>
<td>-0.184</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Luke Raley</td>
<td>Seattle Mariners</td>
<td>0.377</td>
<td>0.254</td>
<td>-0.123</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Ildemaro Vargas</td>
<td>Arizona Diamondbacks</td>
<td>0.363</td>
<td>0.243</td>
<td>-0.120</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Oswald Peraza</td>
<td>Los Angeles Angels</td>
<td>0.363</td>
<td>0.244</td>
<td>-0.119</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Travis Bazzana</td>
<td>Cleveland Guardians</td>
<td>0.393</td>
<td>0.276</td>
<td>-0.117</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>The Cubs’ Pete Crow-Armstrong tops the risers, and two names on the decliner list are worth pausing on: Atlanta’s Drake Baldwin, the NL’s elected starting catcher, is the single biggest decliner in baseball over this stretch, and Cleveland’s Travis Bazzana, an AL player-elected pick, isn’t far off that pace either, both All-Stars in our <a href="../../../posts/baseball/2026-07-10-all-star-reality-check/index.html">earlier piece on the fan vote</a>. That’s not an argument against either selection; the vote reflects the season as a whole, and both still have real first halves behind them. It’s just a sign of where each of them stands heading into the second half.</p>
<p>The same split works just as well from the pitcher’s side of the ball, using opponent wOBA (what a pitcher’s batters have actually done against him) instead of a pitcher’s own wOBA. Starters and relievers are shown separately below, since the two roles face lineups differently (starters work through a batting order multiple times, relievers usually face it once) and mixing them together makes both lists harder to read.</p>
<div id="fetch-pitcher-movers" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">pitcher_movers_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb6-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WITH pa AS (</span></span>
<span id="cb6-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb6-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    pitcher,</span></span>
<span id="cb6-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CAST(game_date AS DATE) AS game_date,</span></span>
<span id="cb6-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    woba_value, woba_denom</span></span>
<span id="cb6-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM `maydaystats.mlb_statcast.pitches`</span></span>
<span id="cb6-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE game_type = 'R' AND woba_denom IS NOT NULL</span></span>
<span id="cb6-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb6-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">max_date AS (SELECT MAX(game_date) AS d FROM pa),</span></span>
<span id="cb6-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">splits AS (</span></span>
<span id="cb6-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb6-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    pitcher,</span></span>
<span id="cb6-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_value, 0)) AS early_val,</span></span>
<span id="cb6-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_denom, 0)) AS early_bf,</span></span>
<span id="cb6-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_value, 0)) AS recent_val,</span></span>
<span id="cb6-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_denom, 0)) AS recent_bf</span></span>
<span id="cb6-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM pa</span></span>
<span id="cb6-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  GROUP BY pitcher</span></span>
<span id="cb6-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb6-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb6-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  p.player_name, p.team_name,</span></span>
<span id="cb6-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  IF(SAFE_DIVIDE(p.gamesStarted, p.gamesPitched) &gt;= 0.5, 'Starter', 'Reliever') AS role,</span></span>
<span id="cb6-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  s.early_bf, ROUND(s.early_val / s.early_bf, 3) AS early_woba_against,</span></span>
<span id="cb6-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  s.recent_bf, ROUND(s.recent_val / s.recent_bf, 3) AS recent_woba_against,</span></span>
<span id="cb6-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(s.recent_val / s.recent_bf - s.early_val / s.early_bf, 3) AS woba_against_delta</span></span>
<span id="cb6-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM splits s</span></span>
<span id="cb6-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">JOIN `maydaystats.mlb_season_stats.pitching_latest` p ON p.player_id = s.pitcher</span></span>
<span id="cb6-29"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE s.early_bf &gt;= 60 AND s.recent_bf &gt;= 60</span></span>
<span id="cb6-30"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY woba_against_delta ASC</span></span>
<span id="cb6-31"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb6-32"></span>
<span id="cb6-33">pitcher_movers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(pitcher_movers_query).to_dataframe()</span>
<span id="cb6-34">cols <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"early_woba_against"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"recent_woba_against"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"woba_against_delta"</span>]</span>
<span id="cb6-35"></span>
<span id="cb6-36">starters <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pitcher_movers[pitcher_movers[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Starter"</span>].sort_values(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"woba_against_delta"</span>)</span>
<span id="cb6-37">starter_movers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.concat([starters.head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>), starters.tail(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)])[cols].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb6-38">starter_movers.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-39"></span>
<span id="cb6-40">relievers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pitcher_movers[pitcher_movers[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Reliever"</span>].sort_values(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"woba_against_delta"</span>)</span>
<span id="cb6-41">reliever_movers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.concat([relievers.head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>), relievers.tail(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)])[cols].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb6-42">reliever_movers.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span></code></pre></div>
</div>
<p>Starters, biggest opponent-wOBA improvers and decliners:</p>
<div class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">starter_movers</span></code></pre></div>
<div id="tbl-starter-movers" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="7">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-starter-movers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;3: Starters: biggest opponent-wOBA swings, early season vs.&nbsp;last 45 days (min. 60 batters faced in each window)
</figcaption>
<div aria-describedby="tbl-starter-movers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="7">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team_name</th>
<th data-quarto-table-cell-role="th">early_woba_against</th>
<th data-quarto-table-cell-role="th">recent_woba_against</th>
<th data-quarto-table-cell-role="th">woba_against_delta</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Jack Flaherty</td>
<td>Detroit Tigers</td>
<td>0.384</td>
<td>0.267</td>
<td>-0.117</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Brayan Bello</td>
<td>Boston Red Sox</td>
<td>0.396</td>
<td>0.280</td>
<td>-0.116</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Trevor Rogers</td>
<td>Baltimore Orioles</td>
<td>0.382</td>
<td>0.269</td>
<td>-0.113</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Eury Pérez</td>
<td>Miami Marlins</td>
<td>0.338</td>
<td>0.228</td>
<td>-0.110</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Stephen Kolek</td>
<td>Kansas City Royals</td>
<td>0.249</td>
<td>0.400</td>
<td>0.151</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Mitch Keller</td>
<td>Pittsburgh Pirates</td>
<td>0.270</td>
<td>0.426</td>
<td>0.155</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Andrew Painter</td>
<td>Philadelphia Phillies</td>
<td>0.350</td>
<td>0.507</td>
<td>0.157</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Randy Vásquez</td>
<td>San Diego Padres</td>
<td>0.324</td>
<td>0.489</td>
<td>0.164</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Relievers, the same split:</p>
<div class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">reliever_movers</span></code></pre></div>
<div id="tbl-reliever-movers" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="8">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-reliever-movers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;4: Relievers: biggest opponent-wOBA swings, early season vs.&nbsp;last 45 days (min. 60 batters faced in each window)
</figcaption>
<div aria-describedby="tbl-reliever-movers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="8">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team_name</th>
<th data-quarto-table-cell-role="th">early_woba_against</th>
<th data-quarto-table-cell-role="th">recent_woba_against</th>
<th data-quarto-table-cell-role="th">woba_against_delta</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Trevor Megill</td>
<td>Milwaukee Brewers</td>
<td>0.289</td>
<td>0.179</td>
<td>-0.110</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Greg Weissert</td>
<td>Boston Red Sox</td>
<td>0.346</td>
<td>0.239</td>
<td>-0.107</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Paul Blackburn</td>
<td>New York Yankees</td>
<td>0.316</td>
<td>0.216</td>
<td>-0.099</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Yohan Ramírez</td>
<td>Pittsburgh Pirates</td>
<td>0.366</td>
<td>0.268</td>
<td>-0.098</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Gregory Soto</td>
<td>Pittsburgh Pirates</td>
<td>0.211</td>
<td>0.399</td>
<td>0.188</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Tobias Myers</td>
<td>New York Mets</td>
<td>0.294</td>
<td>0.498</td>
<td>0.203</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Joel Kuhnel</td>
<td>Milwaukee Brewers</td>
<td>0.276</td>
<td>0.486</td>
<td>0.210</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Antonio Senzatela</td>
<td>Colorado Rockies</td>
<td>0.217</td>
<td>0.434</td>
<td>0.217</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Two names on the starter improvers list are worth flagging now, because they show up again in the next section: Detroit’s Jack Flaherty and Miami’s Eury Pérez have both cut real ground off their opponent wOBA, and both of their teams are about to turn up as the two clearest hot streaks in baseball.</p>
</section>
<section id="which-teams-are-trending-and-is-any-of-it-real" class="level2">
<h2 class="anchored" data-anchor-id="which-teams-are-trending-and-is-any-of-it-real">Which teams are trending, and is any of it real</h2>
<p>Records can move for two different reasons: a team is genuinely playing better or worse baseball, or the same underlying performance is just running into better or worse luck. Splitting each team’s early season from its last 45 days, the same way as the hitters above, and pairing the win rate with the underlying offensive and pitching quality (wOBA for and against), separates the two.</p>
<div id="fetch-team-trend" class="cell" data-execution_count="9">
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">team_trend_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb9-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WITH games AS (</span></span>
<span id="cb9-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb9-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    game_pk,</span></span>
<span id="cb9-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CAST(ANY_VALUE(game_date) AS DATE) AS game_date,</span></span>
<span id="cb9-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ANY_VALUE(home_team) AS home_team,</span></span>
<span id="cb9-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ANY_VALUE(away_team) AS away_team,</span></span>
<span id="cb9-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    MAX(post_home_score) AS home_final,</span></span>
<span id="cb9-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    MAX(post_away_score) AS away_final</span></span>
<span id="cb9-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM `maydaystats.mlb_statcast.pitches`</span></span>
<span id="cb9-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE game_type = 'R'</span></span>
<span id="cb9-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  GROUP BY game_pk</span></span>
<span id="cb9-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">team_games AS (</span></span>
<span id="cb9-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT game_pk, game_date, home_team AS team,</span></span>
<span id="cb9-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    IF(home_final &gt; away_final, 1, 0) AS win</span></span>
<span id="cb9-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM games</span></span>
<span id="cb9-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  UNION ALL</span></span>
<span id="cb9-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT game_pk, game_date, away_team AS team,</span></span>
<span id="cb9-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    IF(away_final &gt; home_final, 1, 0) AS win</span></span>
<span id="cb9-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM games</span></span>
<span id="cb9-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">max_date AS (SELECT MAX(game_date) AS d FROM team_games),</span></span>
<span id="cb9-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">records AS (</span></span>
<span id="cb9-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb9-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    team,</span></span>
<span id="cb9-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ROUND(SAFE_DIVIDE(SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), win, 0)),</span></span>
<span id="cb9-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), 1, 0))), 3) AS early_wpct,</span></span>
<span id="cb9-29"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ROUND(SAFE_DIVIDE(SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), win, 0)),</span></span>
<span id="cb9-30"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), 1, 0))), 3) AS recent_wpct</span></span>
<span id="cb9-31"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM team_games</span></span>
<span id="cb9-32"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  GROUP BY team</span></span>
<span id="cb9-33"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-34"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">pa AS (</span></span>
<span id="cb9-35"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb9-36"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CAST(game_date AS DATE) AS game_date,</span></span>
<span id="cb9-37"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    IF(inning_topbot = 'Top', away_team, home_team) AS bat_team,</span></span>
<span id="cb9-38"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    IF(inning_topbot = 'Top', home_team, away_team) AS pit_team,</span></span>
<span id="cb9-39"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    woba_value, woba_denom</span></span>
<span id="cb9-40"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM `maydaystats.mlb_statcast.pitches`</span></span>
<span id="cb9-41"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE game_type = 'R' AND woba_denom IS NOT NULL</span></span>
<span id="cb9-42"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-43"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">max_pa_date AS (SELECT MAX(game_date) AS d FROM pa),</span></span>
<span id="cb9-44"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">off AS (</span></span>
<span id="cb9-45"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT bat_team AS team,</span></span>
<span id="cb9-46"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ROUND(SAFE_DIVIDE(SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_pa_date), INTERVAL 45 DAY), woba_value, 0)),</span></span>
<span id="cb9-47"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_pa_date), INTERVAL 45 DAY), woba_denom, 0))), 3) AS off_early,</span></span>
<span id="cb9-48"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ROUND(SAFE_DIVIDE(SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_pa_date), INTERVAL 45 DAY), woba_value, 0)),</span></span>
<span id="cb9-49"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_pa_date), INTERVAL 45 DAY), woba_denom, 0))), 3) AS off_recent</span></span>
<span id="cb9-50"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM pa GROUP BY bat_team</span></span>
<span id="cb9-51"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-52"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">def AS (</span></span>
<span id="cb9-53"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT pit_team AS team,</span></span>
<span id="cb9-54"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ROUND(SAFE_DIVIDE(SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_pa_date), INTERVAL 45 DAY), woba_value, 0)),</span></span>
<span id="cb9-55"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_pa_date), INTERVAL 45 DAY), woba_denom, 0))), 3) AS def_early,</span></span>
<span id="cb9-56"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ROUND(SAFE_DIVIDE(SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_pa_date), INTERVAL 45 DAY), woba_value, 0)),</span></span>
<span id="cb9-57"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_pa_date), INTERVAL 45 DAY), woba_denom, 0))), 3) AS def_recent</span></span>
<span id="cb9-58"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM pa GROUP BY pit_team</span></span>
<span id="cb9-59"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb9-60"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb9-61"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  r.team, r.early_wpct, r.recent_wpct,</span></span>
<span id="cb9-62"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(r.recent_wpct - r.early_wpct, 3) AS wpct_delta,</span></span>
<span id="cb9-63"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  o.off_early, o.off_recent, ROUND(o.off_recent - o.off_early, 3) AS off_delta,</span></span>
<span id="cb9-64"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  d.def_early, d.def_recent, ROUND(d.def_recent - d.def_early, 3) AS def_delta</span></span>
<span id="cb9-65"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM records r</span></span>
<span id="cb9-66"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">JOIN off o ON o.team = r.team</span></span>
<span id="cb9-67"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">JOIN def d ON d.team = r.team</span></span>
<span id="cb9-68"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY wpct_delta DESC</span></span>
<span id="cb9-69"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb9-70"></span>
<span id="cb9-71">team_trend <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(team_trend_query).to_dataframe()</span>
<span id="cb9-72"></span>
<span id="cb9-73">TEAM_NAMES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb9-74">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ATH"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Athletics"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ATL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Atlanta Braves"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AZ"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Arizona Diamondbacks"</span>,</span>
<span id="cb9-75">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BAL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Baltimore Orioles"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BOS"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Boston Red Sox"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CHC"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Chicago Cubs"</span>,</span>
<span id="cb9-76">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CIN"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Cincinnati Reds"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CLE"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Cleveland Guardians"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"COL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Colorado Rockies"</span>,</span>
<span id="cb9-77">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CWS"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Chicago White Sox"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DET"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Detroit Tigers"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"HOU"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Houston Astros"</span>,</span>
<span id="cb9-78">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"KC"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Kansas City Royals"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LAA"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Los Angeles Angels"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LAD"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Los Angeles Dodgers"</span>,</span>
<span id="cb9-79">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MIA"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Miami Marlins"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MIL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Milwaukee Brewers"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MIN"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Minnesota Twins"</span>,</span>
<span id="cb9-80">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NYM"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"New York Mets"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NYY"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"New York Yankees"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PHI"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Philadelphia Phillies"</span>,</span>
<span id="cb9-81">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PIT"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Pittsburgh Pirates"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SD"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"San Diego Padres"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SEA"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Seattle Mariners"</span>,</span>
<span id="cb9-82">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SF"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"San Francisco Giants"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"STL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"St. Louis Cardinals"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TB"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Tampa Bay Rays"</span>,</span>
<span id="cb9-83">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TEX"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Texas Rangers"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TOR"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Toronto Blue Jays"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"WSH"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Washington Nationals"</span>,</span>
<span id="cb9-84">}</span>
<span id="cb9-85">team_trend[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> team_trend[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">map</span>(TEAM_NAMES)</span>
<span id="cb9-86"></span>
<span id="cb9-87">movers_teams <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.concat([team_trend.head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>), team_trend.tail(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)])</span></code></pre></div>
</div>
<div id="cell-fig-team-wpct-swing" class="cell" data-execution_count="10">
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>))</span>
<span id="cb10-2">plot_teams <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> movers_teams.iloc[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb10-3">colors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#c0392b"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> v <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> plot_teams[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wpct_delta"</span>]]</span>
<span id="cb10-4">ax.barh(plot_teams[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team_name"</span>], plot_teams[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wpct_delta"</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>colors)</span>
<span id="cb10-5">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Win rate change (recent 45 days minus early season)"</span>)</span>
<span id="cb10-6">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Biggest Team Win-Rate Swings"</span>)</span>
<span id="cb10-7">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb10-8">ax.axvline(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#888888"</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb10-9">plt.tight_layout()</span>
<span id="cb10-10">plt.show()</span></code></pre></div>
<div class="cell-output cell-output-display">
<div id="fig-team-wpct-swing" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-team-wpct-swing-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://maydaystats.com/posts/baseball/2026-07-13-first-half-review/index_files/figure-html/fig-team-wpct-swing-output-1.png" width="765" height="566" class="figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-team-wpct-swing-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Biggest win-rate swings, early season vs.&nbsp;last 45 days
</figcaption>
</figure>
</div>
</div>
</div>
<div class="cell" data-execution_count="11">
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">trend_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> movers_teams[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wpct_delta"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off_delta"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"def_delta"</span>]].rename(</span>
<span id="cb11-2">    columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off_delta"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"batting_woba_delta"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"def_delta"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pitching_woba_allowed_delta"</span>}</span>
<span id="cb11-3">).reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb11-4">trend_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb11-5">trend_tbl</span></code></pre></div>
<div id="tbl-team-trend" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="11">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-team-trend-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;5: Win rate, offensive wOBA, and opponent wOBA allowed (pitching) trend for the biggest movers
</figcaption>
<div aria-describedby="tbl-team-trend-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="11">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">team_name</th>
<th data-quarto-table-cell-role="th">wpct_delta</th>
<th data-quarto-table-cell-role="th">batting_woba_delta</th>
<th data-quarto-table-cell-role="th">pitching_woba_allowed_delta</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Miami Marlins</td>
<td>0.194</td>
<td>0.037</td>
<td>-0.007</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Detroit Tigers</td>
<td>0.157</td>
<td>0.019</td>
<td>-0.019</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Boston Red Sox</td>
<td>0.149</td>
<td>0.007</td>
<td>-0.018</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Texas Rangers</td>
<td>0.130</td>
<td>0.026</td>
<td>0.025</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Colorado Rockies</td>
<td>0.112</td>
<td>0.054</td>
<td>0.021</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Arizona Diamondbacks</td>
<td>-0.125</td>
<td>-0.019</td>
<td>0.027</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Athletics</td>
<td>-0.132</td>
<td>0.002</td>
<td>0.035</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>San Diego Padres</td>
<td>-0.149</td>
<td>0.021</td>
<td>0.047</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Cincinnati Reds</td>
<td>-0.177</td>
<td>-0.011</td>
<td>-0.001</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Atlanta Braves</td>
<td>-0.199</td>
<td>-0.026</td>
<td>0.035</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Miami and Detroit are the two clearest hot streaks backed by real substance, not just a stretch of good luck. Miami’s win rate has jumped from .456 to .650 over the last 45 days, driven mostly by a real gain in team batting wOBA, with Eury Pérez’s improvement from the last section doing its part on the mound too. Detroit’s climb from .393 to .550 is just as real, and backed on both sides of the ball: batting wOBA up, and less wOBA allowed by the pitching staff, with Jack Flaherty leading that charge. Atlanta sits at the other end: a win rate that fell from .661 down to .462, matched by real decline in both their hitting and their pitching.</p>
<p>Cincinnati and San Diego both show declines where the win rate moved further than the underlying numbers would suggest, which is a sign that some of the drop is bad sequencing rather than the whole roster suddenly playing worse. For the Reds, that’s worth a full section of its own; for San Diego, the driver looks specifically like the pitching staff, whose opponent wOBA allowed climbed sharply even as the offense actually improved.</p>
</section>
<section id="a-closer-look-at-the-reds" class="level2">
<h2 class="anchored" data-anchor-id="a-closer-look-at-the-reds">A closer look at the Reds</h2>
<div id="cell-fetch-reds-standings" class="cell" data-execution_count="12">
<div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1">standings_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb12-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WITH games AS (</span></span>
<span id="cb12-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb12-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    game_pk,</span></span>
<span id="cb12-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ANY_VALUE(home_team) AS home_team,</span></span>
<span id="cb12-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ANY_VALUE(away_team) AS away_team,</span></span>
<span id="cb12-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    MAX(post_home_score) AS home_final,</span></span>
<span id="cb12-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    MAX(post_away_score) AS away_final</span></span>
<span id="cb12-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM `maydaystats.mlb_statcast.pitches`</span></span>
<span id="cb12-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE game_type = 'R'</span></span>
<span id="cb12-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  GROUP BY game_pk</span></span>
<span id="cb12-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb12-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">team_games AS (</span></span>
<span id="cb12-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT home_team AS team, IF(home_final &gt; away_final, 1, 0) AS win FROM games</span></span>
<span id="cb12-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  UNION ALL</span></span>
<span id="cb12-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT away_team AS team, IF(away_final &gt; home_final, 1, 0) AS win FROM games</span></span>
<span id="cb12-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb12-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT team, COUNT(*) AS games, SUM(win) AS wins, COUNT(*) - SUM(win) AS losses,</span></span>
<span id="cb12-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SAFE_DIVIDE(SUM(win), COUNT(*)), 3) AS win_pct</span></span>
<span id="cb12-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM team_games</span></span>
<span id="cb12-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE team IN ('MIL', 'CHC', 'STL', 'PIT', 'CIN')</span></span>
<span id="cb12-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY team</span></span>
<span id="cb12-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY win_pct DESC</span></span>
<span id="cb12-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb12-25"></span>
<span id="cb12-26">standings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(standings_query).to_dataframe()</span>
<span id="cb12-27">standings[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> standings[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">map</span>(TEAM_NAMES)</span>
<span id="cb12-28">standings_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> standings.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb12-29">standings_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb12-30">standings_tbl</span></code></pre></div>
<div id="fetch-reds-standings" class="cell-output cell-output-display" data-execution_count="12">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">games</th>
<th data-quarto-table-cell-role="th">wins</th>
<th data-quarto-table-cell-role="th">losses</th>
<th data-quarto-table-cell-role="th">win_pct</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Milwaukee Brewers</td>
<td>96</td>
<td>59</td>
<td>37</td>
<td>0.615</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Chicago Cubs</td>
<td>96</td>
<td>54</td>
<td>42</td>
<td>0.563</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>St. Louis Cardinals</td>
<td>95</td>
<td>50</td>
<td>45</td>
<td>0.526</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Pittsburgh Pirates</td>
<td>97</td>
<td>50</td>
<td>47</td>
<td>0.515</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Cincinnati Reds</td>
<td>95</td>
<td>43</td>
<td>52</td>
<td>0.453</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>The Reds go into the break in last place in the NL Central, nine games under .500. Before getting into why, it’s worth checking the most basic question directly: how many runs has this team actually been scoring and allowing.</p>
<div id="cell-fetch-reds-runs" class="cell" data-execution_count="13">
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1">runs_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb13-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WITH games AS (</span></span>
<span id="cb13-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb13-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    game_pk,</span></span>
<span id="cb13-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CAST(ANY_VALUE(game_date) AS DATE) AS game_date,</span></span>
<span id="cb13-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ANY_VALUE(home_team) AS home_team,</span></span>
<span id="cb13-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ANY_VALUE(away_team) AS away_team,</span></span>
<span id="cb13-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    MAX(post_home_score) AS home_final,</span></span>
<span id="cb13-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    MAX(post_away_score) AS away_final</span></span>
<span id="cb13-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM `maydaystats.mlb_statcast.pitches`</span></span>
<span id="cb13-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE game_type = 'R'</span></span>
<span id="cb13-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  GROUP BY game_pk</span></span>
<span id="cb13-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb13-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">team_games AS (</span></span>
<span id="cb13-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT game_pk, game_date, home_team AS team, home_final AS runs_scored, away_final AS runs_allowed FROM games</span></span>
<span id="cb13-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  UNION ALL</span></span>
<span id="cb13-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT game_pk, game_date, away_team AS team, away_final AS runs_scored, home_final AS runs_allowed FROM games</span></span>
<span id="cb13-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb13-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">max_date AS (SELECT MAX(game_date) AS d FROM team_games)</span></span>
<span id="cb13-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb13-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  team,</span></span>
<span id="cb13-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SUM(runs_scored) / COUNT(*), 2) AS rpg_season,</span></span>
<span id="cb13-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SUM(runs_allowed) / COUNT(*), 2) AS runs_allowed_pg_season,</span></span>
<span id="cb13-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SAFE_DIVIDE(SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), runs_scored, 0)),</span></span>
<span id="cb13-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), 1, 0))), 2) AS rpg_recent,</span></span>
<span id="cb13-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SAFE_DIVIDE(SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), runs_allowed, 0)),</span></span>
<span id="cb13-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), 1, 0))), 2) AS runs_allowed_pg_recent</span></span>
<span id="cb13-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM team_games</span></span>
<span id="cb13-29"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY team</span></span>
<span id="cb13-30"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb13-31">runs_all <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(runs_query).to_dataframe()</span>
<span id="cb13-32">lg_avg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> runs_all[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rpg_season"</span>].mean()</span>
<span id="cb13-33">reds_runs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> runs_all[runs_all[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CIN"</span>].copy()</span>
<span id="cb13-34">reds_runs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Cincinnati Reds"</span></span>
<span id="cb13-35">reds_runs_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> reds_runs.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb13-36">reds_runs_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb13-37">reds_runs_tbl</span></code></pre></div>
<div id="fetch-reds-runs" class="cell-output cell-output-display" data-execution_count="13">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">rpg_season</th>
<th data-quarto-table-cell-role="th">runs_allowed_pg_season</th>
<th data-quarto-table-cell-role="th">rpg_recent</th>
<th data-quarto-table-cell-role="th">runs_allowed_pg_recent</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Cincinnati Reds</td>
<td>4.16</td>
<td>4.79</td>
<td>3.75</td>
<td>4.6</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>The Reds are averaging 4.16 runs a game for the season, against a league average of 4.52, which puts them in the bottom third of baseball for scoring. That’s not a recent development; it’s been true most of the year. What has changed is the size of the gap: scoring dropped further over the last 45 days, down to 3.75 runs a game, while runs allowed actually improved slightly, from 4.79 a game for the season to 4.60 recently. In plain terms: this has been a below-average offense all year, not a good offense that collapsed, and pitching has quietly held up better than the win-loss record suggests.</p>
<div id="cell-fetch-reds-hitters" class="cell" data-execution_count="14">
<div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1">reds_hitters_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb14-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, position, gamesPlayed, homeRuns, rbi,</span></span>
<span id="cb14-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  CAST(avg AS FLOAT64) AS avg, CAST(obp AS FLOAT64) AS obp,</span></span>
<span id="cb14-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  CAST(slg AS FLOAT64) AS slg, CAST(ops AS FLOAT64) AS ops, plateAppearances</span></span>
<span id="cb14-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlb_season_stats.batting_latest`</span></span>
<span id="cb14-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE team_name = 'Cincinnati Reds' AND plateAppearances &gt;= 50</span></span>
<span id="cb14-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY ops DESC</span></span>
<span id="cb14-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb14-9">reds_hitters <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(reds_hitters_query).to_dataframe()</span>
<span id="cb14-10">reds_hitters_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> reds_hitters.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb14-11">reds_hitters_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb14-12">reds_hitters_tbl</span></code></pre></div>
<div id="fetch-reds-hitters" class="cell-output cell-output-display" data-execution_count="14">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">position</th>
<th data-quarto-table-cell-role="th">gamesPlayed</th>
<th data-quarto-table-cell-role="th">homeRuns</th>
<th data-quarto-table-cell-role="th">rbi</th>
<th data-quarto-table-cell-role="th">avg</th>
<th data-quarto-table-cell-role="th">obp</th>
<th data-quarto-table-cell-role="th">slg</th>
<th data-quarto-table-cell-role="th">ops</th>
<th data-quarto-table-cell-role="th">plateAppearances</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>JJ Bleday</td>
<td>LF</td>
<td>67</td>
<td>16</td>
<td>43</td>
<td>0.242</td>
<td>0.350</td>
<td>0.496</td>
<td>0.846</td>
<td>284</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Elly De La Cruz</td>
<td>SS</td>
<td>76</td>
<td>15</td>
<td>44</td>
<td>0.274</td>
<td>0.347</td>
<td>0.492</td>
<td>0.839</td>
<td>340</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Sal Stewart</td>
<td>1B</td>
<td>95</td>
<td>19</td>
<td>65</td>
<td>0.256</td>
<td>0.338</td>
<td>0.474</td>
<td>0.812</td>
<td>414</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Nathaniel Lowe</td>
<td>DH</td>
<td>70</td>
<td>10</td>
<td>26</td>
<td>0.247</td>
<td>0.335</td>
<td>0.465</td>
<td>0.800</td>
<td>224</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Spencer Steer</td>
<td>1B</td>
<td>91</td>
<td>14</td>
<td>37</td>
<td>0.247</td>
<td>0.328</td>
<td>0.421</td>
<td>0.749</td>
<td>358</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Dane Myers</td>
<td>CF</td>
<td>63</td>
<td>3</td>
<td>14</td>
<td>0.256</td>
<td>0.358</td>
<td>0.385</td>
<td>0.743</td>
<td>137</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Blake Dunn</td>
<td>CF</td>
<td>38</td>
<td>2</td>
<td>7</td>
<td>0.282</td>
<td>0.335</td>
<td>0.387</td>
<td>0.722</td>
<td>155</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Tyler Stephenson</td>
<td>C</td>
<td>73</td>
<td>6</td>
<td>24</td>
<td>0.238</td>
<td>0.319</td>
<td>0.361</td>
<td>0.680</td>
<td>257</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Eugenio Suárez</td>
<td>DH</td>
<td>67</td>
<td>11</td>
<td>35</td>
<td>0.208</td>
<td>0.285</td>
<td>0.388</td>
<td>0.673</td>
<td>267</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Will Benson</td>
<td>RF</td>
<td>51</td>
<td>3</td>
<td>6</td>
<td>0.188</td>
<td>0.310</td>
<td>0.333</td>
<td>0.643</td>
<td>114</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">11</td>
<td>Noelvi Marte</td>
<td>RF</td>
<td>41</td>
<td>6</td>
<td>13</td>
<td>0.195</td>
<td>0.248</td>
<td>0.375</td>
<td>0.623</td>
<td>137</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">12</td>
<td>Matt McLain</td>
<td>2B</td>
<td>83</td>
<td>8</td>
<td>25</td>
<td>0.190</td>
<td>0.293</td>
<td>0.328</td>
<td>0.621</td>
<td>309</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">13</td>
<td>Jose Trevino</td>
<td>C</td>
<td>25</td>
<td>1</td>
<td>8</td>
<td>0.239</td>
<td>0.250</td>
<td>0.328</td>
<td>0.578</td>
<td>70</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">14</td>
<td>Edwin Arroyo</td>
<td>2B</td>
<td>33</td>
<td>0</td>
<td>4</td>
<td>0.240</td>
<td>0.280</td>
<td>0.290</td>
<td>0.570</td>
<td>108</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">15</td>
<td>TJ Friedl</td>
<td>CF</td>
<td>59</td>
<td>3</td>
<td>11</td>
<td>0.181</td>
<td>0.258</td>
<td>0.271</td>
<td>0.529</td>
<td>214</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">16</td>
<td>Ke'Bryan Hayes</td>
<td>3B</td>
<td>46</td>
<td>2</td>
<td>5</td>
<td>0.143</td>
<td>0.200</td>
<td>0.222</td>
<td>0.422</td>
<td>135</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>JJ Bleday narrowly leads the everyday lineup in OPS (.846), just ahead of Elly De La Cruz (.839). The underlying contact data points to De La Cruz as the more dangerous hitter of the two, though: he produces the hardest, fastest contact on the entire roster, well clear of second place.</p>
<div id="cell-fetch-reds-statcast" class="cell" data-execution_count="15">
<div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1">reds_contact_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb15-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT b.player_name,</span></span>
<span id="cb15-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(AVG(p.launch_speed), 1) AS avg_exit_velo,</span></span>
<span id="cb15-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SAFE_DIVIDE(COUNTIF(p.launch_speed &gt;= 95), COUNTIF(p.launch_speed IS NOT NULL)), 3) AS hard_hit_rate,</span></span>
<span id="cb15-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(AVG(p.bat_speed), 1) AS avg_bat_speed,</span></span>
<span id="cb15-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNT(p.launch_speed) AS batted_balls</span></span>
<span id="cb15-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlb_season_stats.batting_latest` b</span></span>
<span id="cb15-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">JOIN `maydaystats.mlb_statcast.pitches` p ON p.batter = b.player_id</span></span>
<span id="cb15-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE b.team_name = 'Cincinnati Reds' AND p.launch_speed IS NOT NULL AND p.game_type = 'R'</span></span>
<span id="cb15-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY b.player_name</span></span>
<span id="cb15-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">HAVING batted_balls &gt;= 100</span></span>
<span id="cb15-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY avg_exit_velo DESC</span></span>
<span id="cb15-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb15-14">reds_contact <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(reds_contact_query).to_dataframe()</span>
<span id="cb15-15">reds_contact_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> reds_contact.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb15-16">reds_contact_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb15-17">reds_contact_tbl</span></code></pre></div>
<div id="fetch-reds-statcast" class="cell-output cell-output-display" data-execution_count="15">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">avg_exit_velo</th>
<th data-quarto-table-cell-role="th">hard_hit_rate</th>
<th data-quarto-table-cell-role="th">avg_bat_speed</th>
<th data-quarto-table-cell-role="th">batted_balls</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Elly De La Cruz</td>
<td>86.3</td>
<td>0.336</td>
<td>73.9</td>
<td>345</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Ke'Bryan Hayes</td>
<td>85.4</td>
<td>0.300</td>
<td>69.9</td>
<td>140</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Sal Stewart</td>
<td>84.8</td>
<td>0.257</td>
<td>71.7</td>
<td>486</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Tyler Stephenson</td>
<td>84.7</td>
<td>0.265</td>
<td>70.4</td>
<td>298</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Spencer Steer</td>
<td>84.5</td>
<td>0.263</td>
<td>70.3</td>
<td>407</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Eugenio Suárez</td>
<td>83.0</td>
<td>0.201</td>
<td>70.2</td>
<td>288</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>JJ Bleday</td>
<td>82.7</td>
<td>0.281</td>
<td>73.1</td>
<td>352</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Dane Myers</td>
<td>82.6</td>
<td>0.196</td>
<td>70.0</td>
<td>153</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Will Benson</td>
<td>82.6</td>
<td>0.220</td>
<td>72.5</td>
<td>109</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Noelvi Marte</td>
<td>82.4</td>
<td>0.185</td>
<td>71.6</td>
<td>162</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">11</td>
<td>Edwin Arroyo</td>
<td>81.9</td>
<td>0.188</td>
<td>68.7</td>
<td>144</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">12</td>
<td>Matt McLain</td>
<td>81.9</td>
<td>0.218</td>
<td>70.7</td>
<td>344</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">13</td>
<td>Nathaniel Lowe</td>
<td>81.4</td>
<td>0.206</td>
<td>73.3</td>
<td>301</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">14</td>
<td>TJ Friedl</td>
<td>80.1</td>
<td>0.197</td>
<td>67.2</td>
<td>229</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">15</td>
<td>Blake Dunn</td>
<td>79.9</td>
<td>0.201</td>
<td>69.2</td>
<td>169</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>That contact-quality table also surfaces the more useful story on this roster: a gap between how well some of these hitters are actually squaring up the ball and what their batting average says.</p>
<div id="cell-fetch-reds-luck" class="cell" data-execution_count="16">
<div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1">reds_luck_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb16-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT b.player_name,</span></span>
<span id="cb16-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(AVG(p.estimated_ba_using_speedangle), 3) AS xba,</span></span>
<span id="cb16-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  CAST(b.avg AS FLOAT64) AS actual_avg,</span></span>
<span id="cb16-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(CAST(b.avg AS FLOAT64) - AVG(p.estimated_ba_using_speedangle), 3) AS avg_minus_xba,</span></span>
<span id="cb16-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNT(*) AS batted_balls</span></span>
<span id="cb16-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlb_season_stats.batting_latest` b</span></span>
<span id="cb16-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">JOIN `maydaystats.mlb_statcast.pitches` p ON p.batter = b.player_id</span></span>
<span id="cb16-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE b.team_name = 'Cincinnati Reds'</span></span>
<span id="cb16-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND p.estimated_ba_using_speedangle IS NOT NULL AND p.game_type = 'R'</span></span>
<span id="cb16-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY b.player_name, b.avg</span></span>
<span id="cb16-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">HAVING batted_balls &gt;= 90</span></span>
<span id="cb16-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY avg_minus_xba ASC</span></span>
<span id="cb16-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb16-15">reds_luck <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(reds_luck_query).to_dataframe()</span>
<span id="cb16-16">reds_luck_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> reds_luck.head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"xba"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"actual_avg"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg_minus_xba"</span>]].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb16-17">reds_luck_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb16-18">reds_luck_tbl</span></code></pre></div>
<div id="fetch-reds-luck" class="cell-output cell-output-display" data-execution_count="16">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">xba</th>
<th data-quarto-table-cell-role="th">actual_avg</th>
<th data-quarto-table-cell-role="th">avg_minus_xba</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Ke'Bryan Hayes</td>
<td>0.313</td>
<td>0.143</td>
<td>-0.170</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Matt McLain</td>
<td>0.329</td>
<td>0.190</td>
<td>-0.139</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Nathaniel Lowe</td>
<td>0.382</td>
<td>0.247</td>
<td>-0.135</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Elly De La Cruz</td>
<td>0.397</td>
<td>0.274</td>
<td>-0.123</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Eugenio Suárez</td>
<td>0.321</td>
<td>0.208</td>
<td>-0.113</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Ke’Bryan Hayes is the extreme case: Statcast’s contact-quality model expects a .313 batting average from the way he’s hitting the ball, and he’s actually running a .143, the largest gap on the roster among players with a meaningful sample of batted balls. His exit velocity and hard-hit rate are both solidly average-or-better for this team; the results just haven’t shown up yet. Elly De La Cruz carries a smaller version of the same gap (an expected .397 against an actual .274), which matters more here because his actual season is already excellent; if that gap closes at all, his results have real room to get even better. None of this guarantees a second-half turnaround, but it lines up with the team-wide finding from the last section: results that have fallen further than the process backing them up.</p>
<div id="cell-fetch-reds-pitching" class="cell" data-execution_count="17">
<div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1">reds_pitching_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb17-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name,</span></span>
<span id="cb17-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  IF(SAFE_DIVIDE(gamesStarted, gamesPitched) &gt;= 0.5, 'Starter', 'Reliever') AS role,</span></span>
<span id="cb17-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  wins, losses, CAST(era AS FLOAT64) AS era, strikeOuts,</span></span>
<span id="cb17-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  CAST(whip AS FLOAT64) AS whip, CAST(inningsPitched AS FLOAT64) AS ip</span></span>
<span id="cb17-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlb_season_stats.pitching_latest`</span></span>
<span id="cb17-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE team_name = 'Cincinnati Reds' AND CAST(inningsPitched AS FLOAT64) &gt;= 20</span></span>
<span id="cb17-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY role, era ASC</span></span>
<span id="cb17-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb17-10">reds_pitching <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(reds_pitching_query).to_dataframe()</span>
<span id="cb17-11">reds_pitching_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> reds_pitching.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb17-12">reds_pitching_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb17-13">reds_pitching_tbl</span></code></pre></div>
<div id="fetch-reds-pitching" class="cell-output cell-output-display" data-execution_count="17">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">role</th>
<th data-quarto-table-cell-role="th">wins</th>
<th data-quarto-table-cell-role="th">losses</th>
<th data-quarto-table-cell-role="th">era</th>
<th data-quarto-table-cell-role="th">strikeOuts</th>
<th data-quarto-table-cell-role="th">whip</th>
<th data-quarto-table-cell-role="th">ip</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Tejay Antone</td>
<td>Reliever</td>
<td>1</td>
<td>0</td>
<td>2.25</td>
<td>25</td>
<td>0.89</td>
<td>28.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Brock Burke</td>
<td>Reliever</td>
<td>3</td>
<td>4</td>
<td>3.02</td>
<td>39</td>
<td>1.39</td>
<td>44.2</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Graham Ashcraft</td>
<td>Reliever</td>
<td>1</td>
<td>1</td>
<td>3.33</td>
<td>32</td>
<td>1.22</td>
<td>27.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Sam Moll</td>
<td>Reliever</td>
<td>1</td>
<td>6</td>
<td>4.19</td>
<td>43</td>
<td>1.32</td>
<td>38.2</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Pierce Johnson</td>
<td>Reliever</td>
<td>2</td>
<td>1</td>
<td>4.23</td>
<td>25</td>
<td>1.30</td>
<td>27.2</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Chase Petty</td>
<td>Reliever</td>
<td>1</td>
<td>2</td>
<td>4.38</td>
<td>11</td>
<td>1.09</td>
<td>24.2</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Tony Santillan</td>
<td>Reliever</td>
<td>1</td>
<td>4</td>
<td>5.23</td>
<td>28</td>
<td>1.39</td>
<td>31.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Connor Phillips</td>
<td>Reliever</td>
<td>1</td>
<td>0</td>
<td>5.53</td>
<td>28</td>
<td>1.77</td>
<td>27.2</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Chase Burns</td>
<td>Starter</td>
<td>11</td>
<td>1</td>
<td>2.54</td>
<td>118</td>
<td>1.11</td>
<td>102.2</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Andrew Abbott</td>
<td>Starter</td>
<td>5</td>
<td>5</td>
<td>4.11</td>
<td>84</td>
<td>1.42</td>
<td>105.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">11</td>
<td>Nick Lodolo</td>
<td>Starter</td>
<td>3</td>
<td>2</td>
<td>4.60</td>
<td>50</td>
<td>1.47</td>
<td>62.2</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">12</td>
<td>Brady Singer</td>
<td>Starter</td>
<td>3</td>
<td>9</td>
<td>4.72</td>
<td>76</td>
<td>1.47</td>
<td>89.2</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">13</td>
<td>Rhett Lowder</td>
<td>Starter</td>
<td>3</td>
<td>6</td>
<td>4.91</td>
<td>59</td>
<td>1.54</td>
<td>69.2</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">14</td>
<td>Brandon Williamson</td>
<td>Starter</td>
<td>2</td>
<td>3</td>
<td>6.11</td>
<td>19</td>
<td>1.64</td>
<td>28.0</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Splitting by role clarifies who is actually holding the staff together. Among starters, Chase Burns is the headline: an 11-1 record and a 2.54 ERA over 102.2 innings, averaging just under 95 mph with plus spin on his fastball, the profile of a legitimate front-of-rotation arm rather than a first-half surprise. Among relievers, Tejay Antone’s 2.25 ERA is the clear standout. Beyond those two, both groups thin out quickly.</p>
<p>The same early-vs-recent split used earlier in this post, applied to the Reds staff specifically, shows why that thinness matters more than any single number in the season table above.</p>
<div id="cell-fetch-reds-pitching-trend" class="cell" data-execution_count="18">
<div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1">reds_pitch_trend_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb18-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WITH pa AS (</span></span>
<span id="cb18-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb18-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    pitcher,</span></span>
<span id="cb18-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CAST(game_date AS DATE) AS game_date,</span></span>
<span id="cb18-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    woba_value, woba_denom</span></span>
<span id="cb18-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM `maydaystats.mlb_statcast.pitches`</span></span>
<span id="cb18-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE game_type = 'R' AND woba_denom IS NOT NULL</span></span>
<span id="cb18-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb18-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">max_date AS (SELECT MAX(game_date) AS d FROM pa),</span></span>
<span id="cb18-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">splits AS (</span></span>
<span id="cb18-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT</span></span>
<span id="cb18-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    pitcher,</span></span>
<span id="cb18-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_value, 0)) AS early_val,</span></span>
<span id="cb18-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &lt; DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_denom, 0)) AS early_bf,</span></span>
<span id="cb18-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_value, 0)) AS recent_val,</span></span>
<span id="cb18-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(IF(game_date &gt;= DATE_SUB((SELECT d FROM max_date), INTERVAL 45 DAY), woba_denom, 0)) AS recent_bf</span></span>
<span id="cb18-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM pa</span></span>
<span id="cb18-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  GROUP BY pitcher</span></span>
<span id="cb18-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb18-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb18-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  p.player_name,</span></span>
<span id="cb18-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  IF(SAFE_DIVIDE(p.gamesStarted, p.gamesPitched) &gt;= 0.5, 'Starter', 'Reliever') AS role,</span></span>
<span id="cb18-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SAFE_DIVIDE(s.early_val, s.early_bf), 3) AS early_woba_against,</span></span>
<span id="cb18-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ROUND(SAFE_DIVIDE(s.recent_val, s.recent_bf), 3) AS recent_woba_against</span></span>
<span id="cb18-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM splits s</span></span>
<span id="cb18-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">JOIN `maydaystats.mlb_season_stats.pitching_latest` p ON p.player_id = s.pitcher</span></span>
<span id="cb18-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE p.team_name = 'Cincinnati Reds' AND s.early_bf &gt;= 20 AND s.recent_bf &gt;= 20</span></span>
<span id="cb18-29"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY role, recent_woba_against ASC</span></span>
<span id="cb18-30"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb18-31">reds_pitch_trend <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(reds_pitch_trend_query).to_dataframe()</span>
<span id="cb18-32">reds_pitch_trend_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> reds_pitch_trend.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb18-33">reds_pitch_trend_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb18-34">reds_pitch_trend_tbl</span></code></pre></div>
<div id="fetch-reds-pitching-trend" class="cell-output cell-output-display" data-execution_count="18">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">role</th>
<th data-quarto-table-cell-role="th">early_woba_against</th>
<th data-quarto-table-cell-role="th">recent_woba_against</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Tejay Antone</td>
<td>Reliever</td>
<td>0.328</td>
<td>0.279</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Tony Santillan</td>
<td>Reliever</td>
<td>0.397</td>
<td>0.300</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Chase Petty</td>
<td>Reliever</td>
<td>0.334</td>
<td>0.313</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Brock Burke</td>
<td>Reliever</td>
<td>0.352</td>
<td>0.318</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Pierce Johnson</td>
<td>Reliever</td>
<td>0.322</td>
<td>0.408</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Sam Moll</td>
<td>Reliever</td>
<td>0.293</td>
<td>0.432</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Chase Burns</td>
<td>Starter</td>
<td>0.267</td>
<td>0.330</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Brady Singer</td>
<td>Starter</td>
<td>0.428</td>
<td>0.331</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Andrew Abbott</td>
<td>Starter</td>
<td>0.331</td>
<td>0.333</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Nick Lodolo</td>
<td>Starter</td>
<td>0.404</td>
<td>0.346</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">11</td>
<td>Rhett Lowder</td>
<td>Starter</td>
<td>0.321</td>
<td>0.383</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>It’s a mixed picture, not a uniform one. Tejay Antone and Brady Singer have both gotten better against opposing hitters recently, and Andrew Abbott has held steady. Chase Burns has actually seen his opponent wOBA rise somewhat over the last 45 days, still solid, just not quite as dominant as his season line suggests. The clearer problem is further down the staff: Rhett Lowder and Sam Moll have both gotten meaningfully worse against opposing hitters recently, and neither was especially strong to begin with. A rotation and bullpen this dependent on two pitchers performing at their best has less margin for error than a staff with the same season-long ERA spread more evenly.</p>
<p>Put together, the Reds’ first half looks less like a team that fell apart and more like a team that was already below average on offense, got worse there over the last 45 days, and has real pitching depth questions once you get past Burns and Antone. The individual talent is real (De La Cruz, Burns, Antone), and a couple of hitters, Hayes especially, look likely to see their results catch up to their contact quality. Whether that’s enough to climb out of last place in the NL Central is a second-half question, but it will take more than better luck; the offense needs to actually produce more runs than it has all year, not just more than its recent stretch.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>This post uses Quarto’s frozen execution (<code>freeze: auto</code>): the numbers above reflect the Statcast and <code>mlb_season_stats</code> data as of whenever this was last rendered locally, not a live query on every page load. “Last 45 days” is calculated relative to the most recent game date in the pipeline at render time, not a fixed calendar date.</p>
</div>
</div>


</section>

 ]]></description>
  <category>Baseball</category>
  <category>Featured</category>
  <category>Season Recap</category>
  <guid>https://maydaystats.com/posts/baseball/2026-07-13-first-half-review/</guid>
  <pubDate>Mon, 13 Jul 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Fan Vote vs. the Numbers: Grading the 2026 All-Star Rosters</title>
  <link>https://maydaystats.com/posts/baseball/2026-07-10-all-star-reality-check/</link>
  <description><![CDATA[ 




<p>The 2026 All-Star Game is July 14 in Philadelphia, and the rosters are set. Starters are elected by fans in a multi-phase vote, most reserves are added by a mix of player ballot and each league’s coaching/front office staff, and the Commissioner gets one “Legend Pick” per league on top of all that. That process is built around name recognition and storylines as much as this season’s stats, and there’s nothing wrong with that: the All-Star Game is partly a popularity contest by design, and always has been.</p>
<p>But it also means “elected starter” and “best player at the position this year” aren’t the same claim. With our own season stats pipeline now tracking every hitter and pitcher’s numbers daily (see <a href="../../../posts/baseball/2026-07-08-first-pitches/index.html">the pipeline behind this analysis</a> for the batting/pitching side of things), it’s worth actually checking: who’s on the roster because the numbers say so, and who’s there for other reasons?</p>
<section id="who-actually-leads-the-league" class="level2">
<h2 class="anchored" data-anchor-id="who-actually-leads-the-league">Who actually leads the league</h2>
<div id="fetch-leaders" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.cloud <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> bigquery</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb1-3"></span>
<span id="cb1-4">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bigquery.Client()</span>
<span id="cb1-5"></span>
<span id="cb1-6">batting_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb1-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  league_name, player_name, team_name,</span></span>
<span id="cb1-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  homeRuns, CAST(ops AS FLOAT64) AS ops, CAST(avg AS FLOAT64) AS avg,</span></span>
<span id="cb1-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  plateAppearances,</span></span>
<span id="cb1-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  RANK() OVER (PARTITION BY league_name ORDER BY CAST(ops AS FLOAT64) DESC) AS ops_rank</span></span>
<span id="cb1-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlb_season_stats.batting_latest`</span></span>
<span id="cb1-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE plateAppearances &gt;= 150</span></span>
<span id="cb1-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY league_name, ops_rank</span></span>
<span id="cb1-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Every qualified hitter, ranked; used below for specific-player lookups</span></span>
<span id="cb1-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (some storyline players, like Guerrero, rank well outside the top 15).</span></span>
<span id="cb1-18">batting_all <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(batting_query).to_dataframe()</span>
<span id="cb1-19">batting <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting_all[batting_all[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ops_rank"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>].copy()</span>
<span id="cb1-20"></span>
<span id="cb1-21">pitching_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb1-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  league_name, player_name, team_name,</span></span>
<span id="cb1-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  CAST(era AS FLOAT64) AS era, strikeOuts, CAST(whip AS FLOAT64) AS whip,</span></span>
<span id="cb1-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  wins, CAST(inningsPitched AS FLOAT64) AS ip</span></span>
<span id="cb1-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlb_season_stats.pitching_latest`</span></span>
<span id="cb1-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE CAST(inningsPitched AS FLOAT64) &gt;= 60</span></span>
<span id="cb1-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">QUALIFY RANK() OVER (PARTITION BY league_name ORDER BY era ASC) &lt;= 15</span></span>
<span id="cb1-29"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY league_name, era ASC</span></span>
<span id="cb1-30"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-31">pitching <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(pitching_query).to_dataframe()</span>
<span id="cb1-32"></span>
<span id="cb1-33"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The full announced rosters (starters, pitchers, and reserves), plus how</span></span>
<span id="cb1-34"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># each player got there. Source: MLB.com's official roster announcement</span></span>
<span id="cb1-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># and the subsequent replacement announcements for Vladimir Guerrero Jr.</span></span>
<span id="cb1-36">all_stars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb1-37">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># American League</span></span>
<span id="cb1-38">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Shea Langeliers"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Vladimir Guerrero Jr."</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter (withdrew, injury)"</span>,</span>
<span id="cb1-39">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Ernie Clement"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Junior Caminero"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>,</span>
<span id="cb1-40">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bobby Witt Jr."</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mike Trout"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>,</span>
<span id="cb1-41">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Byron Buxton"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Aaron Judge"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>,</span>
<span id="cb1-42">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Yordan Alvarez"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>,</span>
<span id="cb1-43">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bryan Baker"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Dylan Cease"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-44">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Aroldis Chapman"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Jacob Latz"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>,</span>
<span id="cb1-45">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Parker Messick"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Drew Rasmussen"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Reserve"</span>,</span>
<span id="cb1-46">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Joe Ryan"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Cam Schlittler"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-47">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Cade Smith"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Ranger Suarez"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>,</span>
<span id="cb1-48">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Louis Varland"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Michael Wacha"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>,</span>
<span id="cb1-49">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Dillon Dingler"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Adley Rutschman"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>,</span>
<span id="cb1-50">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Travis Bazzana"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Nick Kurtz"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Reserve -&gt; now starting (Guerrero replacement)"</span>,</span>
<span id="cb1-51">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Kevin McGonigle"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Ben Rice"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>,</span>
<span id="cb1-52">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Miguel Vargas"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Randy Arozarena"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-53">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Cody Bellinger"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Riley Greene"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-54">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Yandy Díaz"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Justin Verlander"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Legend Pick"</span>,</span>
<span id="cb1-55">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Willson Contreras"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Roster replacement (for Guerrero)"</span>,</span>
<span id="cb1-56">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># National League</span></span>
<span id="cb1-57">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Drake Baldwin"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Freddie Freeman"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>,</span>
<span id="cb1-58">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Ozzie Albies"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Max Muncy"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>,</span>
<span id="cb1-59">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CJ Abrams"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Brandon Marsh"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>,</span>
<span id="cb1-60">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Juan Soto"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Andy Pages"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>,</span>
<span id="cb1-61">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Shohei Ohtani"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>,</span>
<span id="cb1-62">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Chase Burns"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Jhoan Duran"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-63">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Raisel Iglesias"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Max Meyer"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>,</span>
<span id="cb1-64">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mason Miller"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Jacob Misiorowski"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-65">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Eduardo Rodriguez"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Chris Sale"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-66">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Cristopher Sánchez"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Paul Skenes"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-67">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Logan Webb"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Yoshinobu Yamamoto"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>,</span>
<span id="cb1-68">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"William Contreras"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hunter Goodman"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>,</span>
<span id="cb1-69">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Luis Arraez"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bryce Harper"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Legend Pick"</span>,</span>
<span id="cb1-70">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Otto Lopez"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Matt Olson"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-71">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Sal Stewart"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Corbin Carroll"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-72">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Pete Crow-Armstrong"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Jordan Walker"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MLB's choice"</span>,</span>
<span id="cb1-73">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"James Wood"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Kyle Schwarber"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Player-elected"</span>,</span>
<span id="cb1-74">}</span>
<span id="cb1-75"></span>
<span id="cb1-76">batting_all[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"selection"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting_all[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">map</span>(all_stars).fillna(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Not selected"</span>)</span>
<span id="cb1-77">batting[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"selection"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">map</span>(all_stars).fillna(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Not selected"</span>)</span>
<span id="cb1-78">pitching[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"selection"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pitching[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">map</span>(all_stars).fillna(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Not selected"</span>)</span></code></pre></div>
</div>
<p>Two lists: OPS leaders (minimum 150 plate appearances) and ERA leaders (minimum 60 innings), split by league, with each name tagged by whether (and how) they made an All-Star roster.</p>
<div id="cell-fig-al-ops-leaders" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> matplotlib.patches <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Patch</span>
<span id="cb2-3"></span>
<span id="cb2-4">al_bat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting[batting[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"league_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AL"</span>].head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>).iloc[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb2-5">colors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Not selected"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#c0392b"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> al_bat[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"selection"</span>]]</span>
<span id="cb2-6">legend_handles <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb2-7">    Patch(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"On an All-Star roster"</span>),</span>
<span id="cb2-8">    Patch(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#c0392b"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Not selected"</span>),</span>
<span id="cb2-9">]</span>
<span id="cb2-10"></span>
<span id="cb2-11">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.5</span>))</span>
<span id="cb2-12">ax.barh(al_bat[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>], al_bat[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ops"</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>colors)</span>
<span id="cb2-13">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"OPS"</span>)</span>
<span id="cb2-14">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AL OPS Leaders (min. 150 PA)"</span>)</span>
<span id="cb2-15">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb2-16">ax.legend(</span>
<span id="cb2-17">    handles<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>legend_handles, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper center"</span>, bbox_to_anchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>),</span>
<span id="cb2-18">    ncol<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>,</span>
<span id="cb2-19">)</span>
<span id="cb2-20">plt.tight_layout()</span>
<span id="cb2-21">plt.show()</span></code></pre></div>
<div class="cell-output cell-output-display">
<div id="fig-al-ops-leaders" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-al-ops-leaders-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://maydaystats.com/posts/baseball/2026-07-10-all-star-reality-check/index_files/figure-html/fig-al-ops-leaders-output-1.png" width="765" height="615" class="figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-al-ops-leaders-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: AL OPS leaders and All-Star status
</figcaption>
</figure>
</div>
</div>
</div>
<div id="cell-fig-nl-ops-leaders" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">nl_bat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting[batting[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"league_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NL"</span>].head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>).iloc[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb3-2">colors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Not selected"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#c0392b"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> nl_bat[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"selection"</span>]]</span>
<span id="cb3-3"></span>
<span id="cb3-4">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.5</span>))</span>
<span id="cb3-5">ax.barh(nl_bat[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>], nl_bat[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ops"</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>colors)</span>
<span id="cb3-6">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"OPS"</span>)</span>
<span id="cb3-7">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NL OPS Leaders (min. 150 PA)"</span>)</span>
<span id="cb3-8">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb3-9">ax.legend(</span>
<span id="cb3-10">    handles<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>legend_handles, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper center"</span>, bbox_to_anchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>),</span>
<span id="cb3-11">    ncol<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>,</span>
<span id="cb3-12">)</span>
<span id="cb3-13">plt.tight_layout()</span>
<span id="cb3-14">plt.show()</span></code></pre></div>
<div class="cell-output cell-output-display">
<div id="fig-nl-ops-leaders" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-nl-ops-leaders-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://maydaystats.com/posts/baseball/2026-07-10-all-star-reality-check/index_files/figure-html/fig-nl-ops-leaders-output-1.png" width="765" height="615" class="figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-nl-ops-leaders-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: NL OPS leaders and All-Star status
</figcaption>
</figure>
</div>
</div>
</div>
<p>Red bars are the players leading their league in OPS who aren’t on either All-Star roster in any capacity, elected or otherwise. Most of the list is dark blue, which is the actual headline: the system mostly works. But “mostly” is doing some work in that sentence, and it’s worth looking at where it didn’t.</p>
</section>
<section id="the-starters-who-back-it-up" class="level2">
<h2 class="anchored" data-anchor-id="the-starters-who-back-it-up">The starters who back it up</h2>
<div id="elected-starters-check" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">elected <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting[batting[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"selection"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Elected starter"</span>].sort_values(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ops"</span>, ascending<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb4-2">top_elected <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> elected.head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span></code></pre></div>
</div>
<p>Start with the good news. 9 of the 18 elected position-player starters show up in the top 15 of their league in OPS, a solid hit rate for a system built on votes rather than end-of-July stat lines. The clearest examples that fans got exactly right:</p>
<div class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">top_starters_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> top_elected[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"league_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"homeRuns"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ops"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg"</span>]].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-2">top_starters_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-3">top_starters_tbl</span></code></pre></div>
<div id="tbl-top-starters" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="5">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-top-starters-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;1: Elected starters ranking near the top of their league in OPS
</figcaption>
<div aria-describedby="tbl-top-starters-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="5">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team_name</th>
<th data-quarto-table-cell-role="th">league_name</th>
<th data-quarto-table-cell-role="th">homeRuns</th>
<th data-quarto-table-cell-role="th">ops</th>
<th data-quarto-table-cell-role="th">avg</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Yordan Alvarez</td>
<td>Houston Astros</td>
<td>AL</td>
<td>29</td>
<td>1.030</td>
<td>0.310</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Juan Soto</td>
<td>New York Mets</td>
<td>NL</td>
<td>21</td>
<td>0.993</td>
<td>0.297</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Shohei Ohtani</td>
<td>Los Angeles Dodgers</td>
<td>NL</td>
<td>20</td>
<td>0.939</td>
<td>0.290</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Junior Caminero</td>
<td>Tampa Bay Rays</td>
<td>AL</td>
<td>27</td>
<td>0.913</td>
<td>0.277</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Aaron Judge</td>
<td>New York Yankees</td>
<td>AL</td>
<td>17</td>
<td>0.908</td>
<td>0.248</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>Yordan Alvarez leads the entire AL in OPS, and Juan Soto leads the entire NL, both elected starters, both correct. Shohei Ohtani, elected as the NL’s starting DH, is right behind Soto. This is what the selection process is supposed to produce, and for most of both rosters, it does.</p>
</section>
<section id="where-fan-vote-and-merit-actually-diverged" class="level2">
<h2 class="anchored" data-anchor-id="where-fan-vote-and-merit-actually-diverged">Where fan vote and merit actually diverged</h2>
<div id="guerrero-check" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">guerrero <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting_all[batting_all[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Vladimir Guerrero Jr."</span>].iloc[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb6-2">kurtz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting_all[batting_all[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Nick Kurtz"</span>].iloc[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb6-3">contreras <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting_all[batting_all[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Willson Contreras"</span>].iloc[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span></code></pre></div>
</div>
<p>The clearest case this year is Toronto’s Vladimir Guerrero Jr., elected as the AL’s starting first baseman on name recognition and a strong track record, but this season he’s hitting 0.262 with 5 home runs and a 0.694 OPS, ranking 87th in the AL, nowhere near the top 15. He withdrew from the game with a lower back injury, and what happened next made the point better than any stat table could: the AL didn’t just plug in whoever was next alphabetically. Nick Kurtz, already on the roster as a reserve, was elevated to start at first base after receiving the most votes among AL first basemen on the players’ ballot, backed by a 0.902 OPS and 20 home runs of his own. And Willson Contreras, who had a 0.921 OPS and 20 home runs largely outside the national conversation, was added to fill the vacated roster spot.</p>
<p>Guerrero’s original selection wasn’t wrong, exactly: he’s a six-time All-Star with a real track record, and fan voting is allowed to weigh that. But when an injury forced the question, the mechanisms built to handle exactly this (player ballots, front-office and player-elected reserve picks) pulled in two players with genuinely stronger seasons. That’s the system working as a whole, even when the first step (the fan vote) didn’t land on the best hitter at the position this year.</p>
</section>
<section id="the-two-legend-picks-and-why-the-exception-exists" class="level2">
<h2 class="anchored" data-anchor-id="the-two-legend-picks-and-why-the-exception-exists">The two Legend Picks, and why the exception exists</h2>
<div id="harper-check" class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">harper <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting_all[batting_all[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bryce Harper"</span>].iloc[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span></code></pre></div>
</div>
<p>Commissioner Rob Manfred’s “Legend Pick” is a one-per-league honor for a notable veteran, granted independent of this year’s stats rather than as a reward for them. The two picks this year show why that exception is worth having, for two very different reasons.</p>
<p>Philadelphia’s Bryce Harper got the NL nod, and it’s worth noting he barely needed it: at 0.870 OPS with 20 home runs, he ranks 16th in the NL among hitters with at least 150 plate appearances, one spot outside the group that gets in purely on merit, and squarely in the same conversation as the players who did. The exception mechanism and the stat line landed in almost the same place this time.</p>
<p>Detroit’s Justin Verlander, the AL’s Legend Pick, is the opposite case, and the clearer illustration of what the exception is actually for: one start all season, a 0-1 record, and a 12.27 ERA over 3.2 innings, having not taken the mound since March 30 because of hip and hamstring injuries. Nobody’s arguing he earned a roster spot on this year’s numbers, and the selection isn’t pretending otherwise. Verlander <a href="https://www.espn.com/mlb/story/_/id/49307720/tigers-justin-verlander-retire-2026-season">announced this week</a> that 2026 will be his final season, and this is his tenth career All-Star selection: a stats-independent sendoff for a three-time Cy Young winner, exactly the case the rule was written for.</p>
</section>
<section id="who-the-numbers-left-off-entirely" class="level2">
<h2 class="anchored" data-anchor-id="who-the-numbers-left-off-entirely">Who the numbers left off entirely</h2>
<div class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">batting_snubs_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batting[batting[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"selection"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Not selected"</span>][</span>
<span id="cb8-2">    [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"league_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"homeRuns"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ops"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg"</span>]</span>
<span id="cb8-3">].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb8-4">batting_snubs_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-5">batting_snubs_tbl</span></code></pre></div>
<div id="tbl-batting-snubs" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="8">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-batting-snubs-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;2: League OPS leaders not on any All-Star roster
</figcaption>
<div aria-describedby="tbl-batting-snubs-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="8">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team_name</th>
<th data-quarto-table-cell-role="th">league_name</th>
<th data-quarto-table-cell-role="th">homeRuns</th>
<th data-quarto-table-cell-role="th">ops</th>
<th data-quarto-table-cell-role="th">avg</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Munetaka Murakami</td>
<td>Chicago White Sox</td>
<td>AL</td>
<td>20</td>
<td>0.938</td>
<td>0.240</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Dominic Canzone</td>
<td>Seattle Mariners</td>
<td>AL</td>
<td>15</td>
<td>0.884</td>
<td>0.269</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Jonathan Aranda</td>
<td>Tampa Bay Rays</td>
<td>AL</td>
<td>13</td>
<td>0.835</td>
<td>0.287</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Mickey Moniak</td>
<td>Colorado Rockies</td>
<td>NL</td>
<td>15</td>
<td>0.924</td>
<td>0.279</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Luis García Jr.</td>
<td>Washington Nationals</td>
<td>NL</td>
<td>20</td>
<td>0.894</td>
<td>0.291</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Andrew Vaughn</td>
<td>Milwaukee Brewers</td>
<td>NL</td>
<td>2</td>
<td>0.893</td>
<td>0.319</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Jake Bauers</td>
<td>Milwaukee Brewers</td>
<td>NL</td>
<td>17</td>
<td>0.873</td>
<td>0.268</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Bryan Reynolds</td>
<td>Pittsburgh Pirates</td>
<td>NL</td>
<td>14</td>
<td>0.872</td>
<td>0.281</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<div class="cell" data-execution_count="9">
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">pitching_snubs_tbl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pitching[pitching[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"selection"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Not selected"</span>][</span>
<span id="cb9-2">    [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"league_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"era"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"strikeOuts"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"whip"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wins"</span>]</span>
<span id="cb9-3">].reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb9-4">pitching_snubs_tbl.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb9-5">pitching_snubs_tbl</span></code></pre></div>
<div id="tbl-pitching-snubs" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="9">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-pitching-snubs-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;3: League ERA leaders not on any All-Star roster
</figcaption>
<div aria-describedby="tbl-pitching-snubs-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output cell-output-display" data-execution_count="9">
<div>


<table class="dataframe do-not-create-environment cell caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team_name</th>
<th data-quarto-table-cell-role="th">league_name</th>
<th data-quarto-table-cell-role="th">era</th>
<th data-quarto-table-cell-role="th">strikeOuts</th>
<th data-quarto-table-cell-role="th">whip</th>
<th data-quarto-table-cell-role="th">wins</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Sonny Gray</td>
<td>Boston Red Sox</td>
<td>AL</td>
<td>2.61</td>
<td>82</td>
<td>1.10</td>
<td>10</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Nick Martinez</td>
<td>Tampa Bay Rays</td>
<td>AL</td>
<td>2.61</td>
<td>61</td>
<td>1.13</td>
<td>7</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Casey Mize</td>
<td>Detroit Tigers</td>
<td>AL</td>
<td>2.64</td>
<td>72</td>
<td>0.98</td>
<td>4</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Shane McClanahan</td>
<td>Tampa Bay Rays</td>
<td>AL</td>
<td>2.83</td>
<td>82</td>
<td>1.13</td>
<td>8</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Walbert Ureña</td>
<td>Los Angeles Angels</td>
<td>AL</td>
<td>2.88</td>
<td>78</td>
<td>1.32</td>
<td>5</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Tarik Skubal</td>
<td>Detroit Tigers</td>
<td>AL</td>
<td>3.06</td>
<td>84</td>
<td>0.95</td>
<td>5</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>J.T. Ginn</td>
<td>Athletics</td>
<td>AL</td>
<td>3.10</td>
<td>86</td>
<td>1.22</td>
<td>7</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Payton Tolle</td>
<td>Boston Red Sox</td>
<td>AL</td>
<td>3.14</td>
<td>80</td>
<td>1.07</td>
<td>5</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Keider Montero</td>
<td>Detroit Tigers</td>
<td>AL</td>
<td>3.15</td>
<td>60</td>
<td>0.99</td>
<td>5</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Logan Gilbert</td>
<td>Seattle Mariners</td>
<td>AL</td>
<td>3.19</td>
<td>114</td>
<td>0.95</td>
<td>7</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">11</td>
<td>Ben Brown</td>
<td>Chicago Cubs</td>
<td>NL</td>
<td>1.85</td>
<td>65</td>
<td>0.94</td>
<td>4</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">12</td>
<td>Zack Wheeler</td>
<td>Philadelphia Phillies</td>
<td>NL</td>
<td>2.28</td>
<td>98</td>
<td>0.91</td>
<td>9</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">13</td>
<td>Justin Wrobleski</td>
<td>Los Angeles Dodgers</td>
<td>NL</td>
<td>2.69</td>
<td>73</td>
<td>1.02</td>
<td>10</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">14</td>
<td>Foster Griffin</td>
<td>Washington Nationals</td>
<td>NL</td>
<td>2.77</td>
<td>109</td>
<td>1.02</td>
<td>10</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">15</td>
<td>Shane Drohan</td>
<td>Milwaukee Brewers</td>
<td>NL</td>
<td>2.97</td>
<td>61</td>
<td>1.24</td>
<td>4</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">16</td>
<td>Kyle Harrison</td>
<td>Milwaukee Brewers</td>
<td>NL</td>
<td>3.01</td>
<td>101</td>
<td>1.08</td>
<td>8</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">17</td>
<td>Michael McGreevy</td>
<td>St. Louis Cardinals</td>
<td>NL</td>
<td>3.01</td>
<td>66</td>
<td>1.10</td>
<td>4</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
</figure>
</div>
</div>
<p>A caveat before reading anything into these tables: our pipeline tracks performance, not health. It has no way to know a player was hurt, and several of the names above were, which changes what their absence actually means. Checked individually:</p>
<p>Chicago’s Munetaka Murakami ranks third in the entire AL in OPS, but he’s been out since May 29 with a grade 2 hamstring strain and only returned to the active roster today, with his first game back not until tomorrow, so he simply wasn’t available when the roster was set. The NL’s Ben Brown, third in ERA, is a cleaner version of the same story: he’s been out with a neck stress reaction since late June and isn’t expected back before late July at the earliest.</p>
<p>Two others are more of an in-between case: healthy right now, but with a big enough gap in their season to explain why the roster process looked past them. Colorado’s Mickey Moniak missed a month on the IL with an ankle injury (late May to late June) and has been excellent since returning, but that’s exactly the stretch when All-Star cases get made, and he simply wasn’t on the field for most of it. Detroit’s Tarik Skubal is similar: an elbow procedure cost him six weeks, and even though he’s been pitching like an ace since his early-June return, that missed time is a real chunk of a season’s worth of starts he doesn’t get back. Their current form is real, but so is the hole in their season that made them easy to set aside.</p>
<p>None of these four are evidence the process missed anything: their absences track health, not stats.</p>
<p>The players who were genuinely healthy and still left off make a cleaner case. Philadelphia’s Zack Wheeler, who <a href="https://www.foxsports.com/stories/mlb/phillies-zack-wheeler-2026-mlb-all-star-game">publicly called it “kind of BS”</a>, is fully healthy. His exclusion comes down to a scheduling rule, unrelated to how he’s pitched. He’s scheduled to start Philadelphia’s last game before the break, and by rule, a starter who pitches that game isn’t eligible to also pitch in the All-Star Game, regardless of how good his season has been. Boston’s Sonny Gray is the more straightforward case: healthy, 10-1, a 2.61 ERA, and left off anyway on a crowded pitching staff, though a Red Sox teammate’s injury (Ranger Suarez, to the IL) has already put him in the conversation for a roster spot before the game is played. Tampa Bay’s Jonathan Aranda, similarly, appears to be simply squeezed out on a Rays roster that already sent Junior Caminero and Yandy Díaz.</p>
<p>None of this is a scandal. Roster spots are limited, some of the best staffs in baseball only get to send two or three of their own, injuries take real players out of contention regardless of how they’re hitting, and a system built partly on fan enthusiasm and storylines was never going to perfectly match a system built purely on this year’s stat line. The interesting part is being able to point at exactly where, why, and by how much the two systems disagree.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>This post uses Quarto’s frozen execution (<code>freeze: auto</code>): the numbers above reflect <code>mlb_season_stats</code>’s snapshot as of whenever this was last rendered locally, not a live query on every page load. The underlying tables append a new snapshot daily rather than overwriting, so the same query run again next week will reflect that day’s standings instead.</p>
</div>
</div>


</section>

 ]]></description>
  <category>Baseball</category>
  <category>Featured</category>
  <category>All-Star Game</category>
  <guid>https://maydaystats.com/posts/baseball/2026-07-10-all-star-reality-check/</guid>
  <pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Year in Review: The 2025-26 MLV Season</title>
  <link>https://maydaystats.com/posts/volleyball/2026-07-09-pro-year-in-review/</link>
  <description><![CDATA[ 




<p>The MLV pipeline is the odd one out on this site: every other pipeline here reads a first-party JSON API directly, but MLV (provolleyball.com’s own Major League Volleyball) only publishes per-player box scores as PDF scoresheets attached to its schedule API. This pipeline downloads each match’s PDF, extracts the text, and parses it back into structured rows, working around two different scoresheet templates the league used across the season (see the pipeline’s <code>DEPLOY.md</code> for the full story). This post is the first analysis built on the result: every completed match from January through the May championship, backfilled into BigQuery.</p>
<p>One exclusion worth flagging up front: schedule_event 636 (Team Launiere vs.&nbsp;Team Meske, March 28) is an All-Star exhibition with draft-style team names, not a real matchup between league teams. It’s filtered out of every query below, the same way the <a href="../../../posts/hockey/2026-07-08-year-in-review/index.html">hockey year in review</a> filters out Olympic break games.</p>
<section id="querying-the-season" class="level2">
<h2 class="anchored" data-anchor-id="querying-the-season">Querying the season</h2>
<div id="cell-fetch-points-leaders" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.cloud <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> bigquery</span>
<span id="cb1-2"></span>
<span id="cb1-3">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bigquery.Client()</span>
<span id="cb1-4"></span>
<span id="cb1-5">query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb1-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  player_name,</span></span>
<span id="cb1-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  team,</span></span>
<span id="cb1-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(points) AS points,</span></span>
<span id="cb1-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(kills) AS kills,</span></span>
<span id="cb1-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(attack_attempts) AS attempts,</span></span>
<span id="cb1-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SAFE_DIVIDE(SUM(kills), SUM(attack_attempts)) AS kill_pct,</span></span>
<span id="cb1-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNT(DISTINCT schedule_event_id) AS matches</span></span>
<span id="cb1-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlv_volleyball.boxscores`</span></span>
<span id="cb1-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE team NOT IN ('Team Launiere', 'Team Meske')</span></span>
<span id="cb1-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb1-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY points DESC</span></span>
<span id="cb1-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 10</span></span>
<span id="cb1-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-20"></span>
<span id="cb1-21">leaders <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(query).to_dataframe()</span>
<span id="cb1-22">leaders <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> leaders.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-23">leaders.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-24">leaders</span></code></pre></div>
<div id="fetch-points-leaders" class="cell-output cell-output-display" data-execution_count="1">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">points</th>
<th data-quarto-table-cell-role="th">kills</th>
<th data-quarto-table-cell-role="th">attempts</th>
<th data-quarto-table-cell-role="th">kill_pct</th>
<th data-quarto-table-cell-role="th">matches</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Mimi Colyer</td>
<td>Dallas Pulse</td>
<td>516.0</td>
<td>455.0</td>
<td>1180.0</td>
<td>0.385593</td>
<td>28</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Diaz Maldonado</td>
<td>Dallas Pulse</td>
<td>509.0</td>
<td>445.0</td>
<td>1113.0</td>
<td>0.399820</td>
<td>28</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Raina Terry</td>
<td>Columbus Fury</td>
<td>398.0</td>
<td>344.0</td>
<td>1048.0</td>
<td>0.328244</td>
<td>24</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Leah Edmond</td>
<td>Atlanta Vibe</td>
<td>387.0</td>
<td>338.0</td>
<td>984.0</td>
<td>0.343496</td>
<td>24</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Grace Loberg</td>
<td>San Diego Mojo</td>
<td>361.0</td>
<td>301.0</td>
<td>998.0</td>
<td>0.301603</td>
<td>26</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Paige Briggs-Romine</td>
<td>Grand Rapids Rise</td>
<td>351.0</td>
<td>315.0</td>
<td>931.0</td>
<td>0.338346</td>
<td>25</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Carli Snyder</td>
<td>Grand Rapids Rise</td>
<td>334.0</td>
<td>293.0</td>
<td>956.0</td>
<td>0.306485</td>
<td>26</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Brooke Nuneviller</td>
<td>Omaha Supernovas</td>
<td>325.0</td>
<td>306.0</td>
<td>950.0</td>
<td>0.322105</td>
<td>28</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Azhani Tealer</td>
<td>Indy Ignite</td>
<td>319.0</td>
<td>287.0</td>
<td>634.0</td>
<td>0.452681</td>
<td>26</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Aiko Jones</td>
<td>Atlanta Vibe</td>
<td>316.0</td>
<td>273.0</td>
<td>789.0</td>
<td>0.346008</td>
<td>24</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Mimi Colyer of Dallas Pulse led the league with 516 points on 455 kills across 28 matches. Her teammate Diaz Maldonado finished second, meaning the league’s two most productive hitters played for the same team all season, a preview of who’d end up cutting down the net in May.</p>
</section>
<section id="the-top-ten-by-points" class="level2">
<h2 class="anchored" data-anchor-id="the-top-ten-by-points">The top ten, by points</h2>
<div id="cell-fig-top-points" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb2-2"></span>
<span id="cb2-3">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb2-4">labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> leaders[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" ("</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> leaders[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">")"</span></span>
<span id="cb2-5">ax.barh(labels[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], leaders[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>][::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span>)</span>
<span id="cb2-6">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Points"</span>)</span>
<span id="cb2-7">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Top 10 Scorers, 2025-26 MLV Season"</span>)</span>
<span id="cb2-8">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb2-9">plt.tight_layout()</span>
<span id="cb2-10">plt.show()</span></code></pre></div>
<div class="cell-output cell-output-display">
<div id="fig-top-points" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-top-points-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://maydaystats.com/posts/volleyball/2026-07-09-pro-year-in-review/index_files/figure-html/fig-top-points-output-1.png" width="868" height="471" class="figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-top-points-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Top 10 point scorers, 2025-26 MLV regular season and playoffs combined
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="digs-aces-and-assists" class="level2">
<h2 class="anchored" data-anchor-id="digs-aces-and-assists">Digs, aces, and assists</h2>
<div id="fetch-other-leaders" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">digs_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, SUM(digs) AS total</span></span>
<span id="cb3-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlv_volleyball.boxscores`</span></span>
<span id="cb3-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE team NOT IN ('Team Launiere', 'Team Meske')</span></span>
<span id="cb3-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb3-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY total DESC</span></span>
<span id="cb3-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 1</span></span>
<span id="cb3-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-9">digs_leader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(digs_query).to_dataframe()</span>
<span id="cb3-10"></span>
<span id="cb3-11">assists_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, SUM(assists) AS total</span></span>
<span id="cb3-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlv_volleyball.boxscores`</span></span>
<span id="cb3-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE team NOT IN ('Team Launiere', 'Team Meske')</span></span>
<span id="cb3-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb3-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY total DESC</span></span>
<span id="cb3-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 1</span></span>
<span id="cb3-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-19">assists_leader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(assists_query).to_dataframe()</span>
<span id="cb3-20"></span>
<span id="cb3-21">aces_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, SUM(service_aces) AS total</span></span>
<span id="cb3-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlv_volleyball.boxscores`</span></span>
<span id="cb3-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE team NOT IN ('Team Launiere', 'Team Meske')</span></span>
<span id="cb3-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb3-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY total DESC</span></span>
<span id="cb3-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 1</span></span>
<span id="cb3-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-29">aces_leader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(aces_query).to_dataframe()</span></code></pre></div>
</div>
<p>Shara Venegas (San Diego Mojo) led all players in digs with 320. Marlie Monserez (San Diego Mojo) ran the offense from the back row, topping the league with 1067 assists. And Natalie Foster (Orlando Valkyries) led in aces.</p>
</section>
<section id="the-best-record-didnt-win-it-either" class="level2">
<h2 class="anchored" data-anchor-id="the-best-record-didnt-win-it-either">The best record didn’t win it either</h2>
<div id="cell-fetch-standings" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">standings_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb4-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WITH regular_season AS (</span></span>
<span id="cb4-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT * FROM `maydaystats.mlv_volleyball.matches`</span></span>
<span id="cb4-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE schedule_event_id NOT IN (637, 638, 639)</span></span>
<span id="cb4-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb4-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">team_results AS (</span></span>
<span id="cb4-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT first_team_name AS team,</span></span>
<span id="cb4-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    IF(first_team_score &gt; second_team_score, 1, 0) AS win</span></span>
<span id="cb4-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM regular_season</span></span>
<span id="cb4-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  UNION ALL</span></span>
<span id="cb4-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT second_team_name AS team,</span></span>
<span id="cb4-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    IF(second_team_score &gt; first_team_score, 1, 0) AS win</span></span>
<span id="cb4-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM regular_season</span></span>
<span id="cb4-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb4-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT team, SUM(win) AS wins, COUNT(*) - SUM(win) AS losses</span></span>
<span id="cb4-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM team_results</span></span>
<span id="cb4-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY team</span></span>
<span id="cb4-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY wins DESC</span></span>
<span id="cb4-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 5</span></span>
<span id="cb4-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb4-21"></span>
<span id="cb4-22">standings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(standings_query).to_dataframe()</span>
<span id="cb4-23">standings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> standings.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb4-24">standings.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb4-25">standings</span></code></pre></div>
<div id="fetch-standings" class="cell-output cell-output-display" data-execution_count="4">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">wins</th>
<th data-quarto-table-cell-role="th">losses</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Indy Ignite</td>
<td>20</td>
<td>5</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Dallas Pulse</td>
<td>18</td>
<td>8</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>San Diego Mojo</td>
<td>13</td>
<td>12</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Omaha Supernovas</td>
<td>12</td>
<td>14</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Grand Rapids Rise</td>
<td>11</td>
<td>15</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Indy Ignite had the best regular-season record in the league at 20-5, good enough for the No.&nbsp;1 seed in a four-team playoff. It didn’t matter: the No.&nbsp;4 seed knocked them out in the semifinal, the same shape as this site’s <a href="../../../posts/hockey/2026-07-08-year-in-review/index.html">NHL year in review</a>, where Colorado had the league’s best regular season and still lost in the Western Conference Final. The best regular season and the tournament that actually decides the title are two different contests.</p>
</section>
<section id="the-playoffs" class="level2">
<h2 class="anchored" data-anchor-id="the-playoffs">The playoffs</h2>
<div id="cell-fetch-playoffs" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> re</span>
<span id="cb5-2"></span>
<span id="cb5-3">playoff_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb5-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT schedule_event_id, game_date, first_team_name, first_team_score,</span></span>
<span id="cb5-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  second_team_name, second_team_score</span></span>
<span id="cb5-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlv_volleyball.matches`</span></span>
<span id="cb5-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE schedule_event_id IN (637, 638, 639)</span></span>
<span id="cb5-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY game_date</span></span>
<span id="cb5-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb5-10">playoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(playoff_query).to_dataframe()</span>
<span id="cb5-11"></span>
<span id="cb5-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Playoff schedule-events carry a seed prefix in the raw team name</span></span>
<span id="cb5-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ("No. 4 Omaha Supernovas") that the regular-season rows don't - strip</span></span>
<span id="cb5-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># it for display, same cleanup the pipeline itself does before matching</span></span>
<span id="cb5-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># team names against the PDF text (see fetch.py's _strip_seed).</span></span>
<span id="cb5-16">seed_re <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> re.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"^No\.\s*\d+\s+"</span>)</span>
<span id="cb5-17"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> col <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"first_team_name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"second_team_name"</span>):</span>
<span id="cb5-18">    playoffs[col] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> playoffs[col].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>.replace(seed_re, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>, regex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-19"></span>
<span id="cb5-20">playoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> playoffs.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-21">playoffs.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-22">playoffs</span></code></pre></div>
<div id="fetch-playoffs" class="cell-output cell-output-display" data-execution_count="5">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">schedule_event_id</th>
<th data-quarto-table-cell-role="th">game_date</th>
<th data-quarto-table-cell-role="th">first_team_name</th>
<th data-quarto-table-cell-role="th">first_team_score</th>
<th data-quarto-table-cell-role="th">second_team_name</th>
<th data-quarto-table-cell-role="th">second_team_score</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>637</td>
<td>2026-05-07T23:00:00.000000Z</td>
<td>Indy Ignite</td>
<td>2.0</td>
<td>Omaha Supernovas</td>
<td>3.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>638</td>
<td>2026-05-08T00:00:00.000000Z</td>
<td>Dallas Pulse</td>
<td>3.0</td>
<td>San Diego Mojo</td>
<td>1.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>639</td>
<td>2026-05-09T19:00:00.000000Z</td>
<td>Dallas Pulse</td>
<td>3.0</td>
<td>Omaha Supernovas</td>
<td>2.0</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Four teams made the playoffs, seeded 1 through 4 on regular-season record, in a single-elimination bracket: 1-seed vs.&nbsp;4-seed, 2-seed vs. 3-seed, winners meet in the final. The No.&nbsp;4 seed Omaha Supernovas beat the No.&nbsp;1 seed Indy Ignite 3-2 in the semifinal, while the No.&nbsp;2 seed Dallas Pulse handled the No.&nbsp;3 seed San Diego Mojo in four sets to reach the final.</p>
<section id="the-semifinal-upset" class="level3">
<h3 class="anchored" data-anchor-id="the-semifinal-upset">The semifinal upset</h3>
<div id="cell-fetch-semifinal-box" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">semifinal_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb6-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT team, SUM(kills) AS kills, SUM(attack_attempts) AS attempts,</span></span>
<span id="cb6-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(service_errors) AS service_errors,</span></span>
<span id="cb6-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(reception_errors) AS reception_errors, SUM(total_blocks) AS blocks</span></span>
<span id="cb6-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlv_volleyball.boxscores`</span></span>
<span id="cb6-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE schedule_event_id = 637</span></span>
<span id="cb6-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY team</span></span>
<span id="cb6-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb6-9">semifinal_team_totals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(semifinal_query).to_dataframe()</span>
<span id="cb6-10">semifinal_team_totals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> semifinal_team_totals.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb6-11">semifinal_team_totals.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-12">semifinal_team_totals</span></code></pre></div>
<div id="fetch-semifinal-box" class="cell-output cell-output-display" data-execution_count="6">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">kills</th>
<th data-quarto-table-cell-role="th">attempts</th>
<th data-quarto-table-cell-role="th">service_errors</th>
<th data-quarto-table-cell-role="th">reception_errors</th>
<th data-quarto-table-cell-role="th">blocks</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Indy Ignite</td>
<td>70.0</td>
<td>187.0</td>
<td>17.0</td>
<td>4.0</td>
<td>12.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Omaha Supernovas</td>
<td>63.0</td>
<td>178.0</td>
<td>13.0</td>
<td>1.0</td>
<td>13.0</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>The team totals are the surprise: Indy Ignite actually out-hit Omaha in this match, 70 kills on 187 attempts to Omaha’s 63 kills on 178 attempts, and blocking was close, 12 to 13. Indy lost the match anyway, and the reason shows up in two other columns rather than hitting: Indy committed 17 service errors and 4 reception errors, against Omaha’s 13 and 1. Four extra free points from bad passing on top of four extra serves into the net or out of bounds accounts for most of the margin in a match that went the full five sets.</p>
<div id="cell-fetch-semifinal-players" class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">semifinal_players_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb7-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, kills, attack_attempts, total_blocks</span></span>
<span id="cb7-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlv_volleyball.boxscores`</span></span>
<span id="cb7-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE schedule_event_id = 637</span></span>
<span id="cb7-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND player_name IN ('Leketor Member-Meneh', 'Janice Leao')</span></span>
<span id="cb7-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb7-7">semifinal_players <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(semifinal_players_query).to_dataframe()</span>
<span id="cb7-8">semifinal_players <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> semifinal_players.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb7-9">semifinal_players.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb7-10">semifinal_players</span></code></pre></div>
<div id="fetch-semifinal-players" class="cell-output cell-output-display" data-execution_count="7">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">kills</th>
<th data-quarto-table-cell-role="th">attack_attempts</th>
<th data-quarto-table-cell-role="th">total_blocks</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Janice Leao</td>
<td>Omaha Supernovas</td>
<td>10.0</td>
<td>17.0</td>
<td>4.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Leketor Member-Meneh</td>
<td>Indy Ignite</td>
<td>6.0</td>
<td>22.0</td>
<td>NaN</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>The individual swing goes the other way. Leketor Member-Meneh came in averaging 9.7 kills a match on a .367 hitting percentage all season, Indy’s second-most productive hitter behind Azhani Tealer. Against Omaha she went 6-for-22, a .273 clip on well below her usual number of swings. Omaha, meanwhile, got a night nobody could have projected from its bench: Janice Leao entered the match averaging 1.6 kills and 1.3 blocks per match on a .344 hitting percentage for the season, and went 10-for-17 (.588) with 4 blocks, by a wide margin her best match of the year, in the one match her team needed it most.</p>
<div id="cell-fetch-final-box" class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">final_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb8-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, kills, points, digs, total_blocks</span></span>
<span id="cb8-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlv_volleyball.boxscores`</span></span>
<span id="cb8-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE schedule_event_id = 639</span></span>
<span id="cb8-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY points DESC</span></span>
<span id="cb8-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 5</span></span>
<span id="cb8-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb8-8">final_box <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(final_query).to_dataframe()</span>
<span id="cb8-9">final_box <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> final_box.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb8-10">final_box.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-11">final_box</span></code></pre></div>
<div id="fetch-final-box" class="cell-output cell-output-display" data-execution_count="8">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">kills</th>
<th data-quarto-table-cell-role="th">points</th>
<th data-quarto-table-cell-role="th">digs</th>
<th data-quarto-table-cell-role="th">total_blocks</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Diaz Maldonado</td>
<td>Dallas Pulse</td>
<td>26.0</td>
<td>27.0</td>
<td>11.0</td>
<td>1.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Merritt Beason</td>
<td>Omaha Supernovas</td>
<td>14.0</td>
<td>20.0</td>
<td>6.0</td>
<td>5.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Brooke Nuneviller</td>
<td>Omaha Supernovas</td>
<td>15.0</td>
<td>16.0</td>
<td>22.0</td>
<td>1.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Parsons Wilhite</td>
<td>Omaha Supernovas</td>
<td>12.0</td>
<td>16.0</td>
<td>19.0</td>
<td>1.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Kaylee Cox</td>
<td>Dallas Pulse</td>
<td>9.0</td>
<td>13.0</td>
<td>11.0</td>
<td>4.0</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Dallas Pulse beat Omaha Supernovas 3-2 in the final to win the title, powered by Diaz Maldonado’s 26 kills and 27 points, a clean encore of the two-hitter attack that carried the team all season. The runner-up’s semifinal upset ran out of steam one match short of the trophy.</p>
</section>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">What’s next</h2>
<p>This covers the season at a high level: the league leaderboards, the regular-season standings, and the playoff bracket itself. The same table supports much narrower questions too, like a single hitter’s efficiency swings across the two scoresheet eras, or how a team’s block numbers held up on the road. Those are posts for another day, now that a full season of validated data is sitting in BigQuery.</p>
<p>The pipeline code behind this post <a href="https://github.com/maydaytek/maydaystats-pipelines">lives here</a>.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Every match row in this dataset carries a <code>checksum_ok</code> flag: the pipeline sums each parsed player’s stats and cross-checks the total against the scoresheet’s own printed team total, and flags anything that doesn’t match exactly rather than trusting it silently. 88 of the 99 matches in the dataset check out clean; the other 11 are flagged for a small, isolated discrepancy in one column (attack attempts) that traces back to the scoresheet’s own printed total rather than a parsing error - see the pipeline’s <code>DEPLOY.md</code> for the full investigation. Nothing in this post depends on that column, but a query that does should filter <code>WHERE checksum_ok</code>.</p>
<p>Like the other posts on this site, this one uses Quarto’s frozen execution (<code>freeze: auto</code>): the queries above ran once, locally, against BigQuery, and the deployed site reuses that committed output rather than re-querying on every build.</p>
</div>
</div>


</section>

 ]]></description>
  <category>Volleyball</category>
  <category>Pro</category>
  <category>Featured</category>
  <category>Season Recap</category>
  <guid>https://maydaystats.com/posts/volleyball/2026-07-09-pro-year-in-review/</guid>
  <pubDate>Thu, 09 Jul 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>First Pitches: Standing Up the Statcast Pipeline</title>
  <link>https://maydaystats.com/posts/baseball/2026-07-08-first-pitches/</link>
  <description><![CDATA[ 




<p>This is the first post through the full pipeline: a Cloud Run Job pulls pitch-level Statcast data daily and appends it to BigQuery, and this page reads straight from that table and renders a chart. Nothing here is a deep analytical claim yet - the point of this post is narrower and more important than that: confirming that data really does flow from the source all the way to a published page, with no manual step in between.</p>
<section id="querying-the-pipelines-output" class="level2">
<h2 class="anchored" data-anchor-id="querying-the-pipelines-output">Querying the pipeline’s output</h2>
<div id="cell-fetch-data" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.cloud <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> bigquery</span>
<span id="cb1-2"></span>
<span id="cb1-3">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bigquery.Client()</span>
<span id="cb1-4"></span>
<span id="cb1-5">query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb1-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  pitch_type,</span></span>
<span id="cb1-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNT(*) AS n_pitches,</span></span>
<span id="cb1-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AVG(release_speed) AS avg_release_speed,</span></span>
<span id="cb1-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AVG(release_spin_rate) AS avg_spin_rate</span></span>
<span id="cb1-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.mlb_statcast.pitches`</span></span>
<span id="cb1-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE pitch_type IS NOT NULL</span></span>
<span id="cb1-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY pitch_type</span></span>
<span id="cb1-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY n_pitches DESC</span></span>
<span id="cb1-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-16"></span>
<span id="cb1-17">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(query).to_dataframe()</span>
<span id="cb1-18">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-19">df.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-20">df</span></code></pre></div>
<div id="fetch-data" class="cell-output cell-output-display" data-execution_count="1">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">pitch_type</th>
<th data-quarto-table-cell-role="th">n_pitches</th>
<th data-quarto-table-cell-role="th">avg_release_speed</th>
<th data-quarto-table-cell-role="th">avg_spin_rate</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>FF</td>
<td>125401</td>
<td>94.746337</td>
<td>2314.720487</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>SI</td>
<td>68556</td>
<td>94.063671</td>
<td>2196.530368</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>SL</td>
<td>55258</td>
<td>86.272038</td>
<td>2437.071170</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>CH</td>
<td>45763</td>
<td>86.132050</td>
<td>1746.540161</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>ST</td>
<td>34270</td>
<td>82.925728</td>
<td>2598.479823</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>FC</td>
<td>32238</td>
<td>89.681215</td>
<td>2387.076976</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>CU</td>
<td>26640</td>
<td>80.123938</td>
<td>2594.701944</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>FS</td>
<td>12899</td>
<td>86.709140</td>
<td>1387.171075</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>KC</td>
<td>6780</td>
<td>82.505575</td>
<td>2519.061243</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>SV</td>
<td>1881</td>
<td>82.271239</td>
<td>2559.736170</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">11</td>
<td>EP</td>
<td>535</td>
<td>46.276449</td>
<td>1171.022599</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">12</td>
<td>FA</td>
<td>430</td>
<td>66.118605</td>
<td>1617.135198</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">13</td>
<td>FO</td>
<td>315</td>
<td>84.044762</td>
<td>898.873016</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">14</td>
<td>KN</td>
<td>137</td>
<td>75.678832</td>
<td>280.136752</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">15</td>
<td>CS</td>
<td>92</td>
<td>70.750000</td>
<td>2430.467391</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">16</td>
<td>PO</td>
<td>24</td>
<td>91.245833</td>
<td>2232.666667</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">17</td>
<td>UN</td>
<td>8</td>
<td>59.112500</td>
<td>1600.625000</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>As of this post, the table holds 411227 pitches across 17 distinct pitch types.</p>
</section>
<section id="average-release-speed-by-pitch-type" class="level2">
<h2 class="anchored" data-anchor-id="average-release-speed-by-pitch-type">Average release speed by pitch type</h2>
<div id="cell-fig-velocity-by-pitch-type" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb2-2"></span>
<span id="cb2-3">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb2-4">ax.bar(df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pitch_type"</span>], df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg_release_speed"</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span>)</span>
<span id="cb2-5">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Pitch type"</span>)</span>
<span id="cb2-6">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Average release speed (mph)"</span>)</span>
<span id="cb2-7">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Average Release Speed by Pitch Type"</span>)</span>
<span id="cb2-8">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb2-9">plt.tight_layout()</span>
<span id="cb2-10">plt.show()</span></code></pre></div>
<div class="cell-output cell-output-display">
<div id="fig-velocity-by-pitch-type" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-velocity-by-pitch-type-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://maydaystats.com/posts/baseball/2026-07-08-first-pitches/index_files/figure-html/fig-velocity-by-pitch-type-output-1.png" width="759" height="470" class="figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-velocity-by-pitch-type-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Average release speed by pitch type, all games loaded so far
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="what-this-confirms-and-whats-next" class="level2">
<h2 class="anchored" data-anchor-id="what-this-confirms-and-whats-next">What this confirms, and what’s next</h2>
<p>Every number and every bar above came from a live query against <code>mlb_statcast.pitches</code>, which itself only exists because a Cloud Scheduler job triggers a Cloud Run Job every morning that pulls the previous day’s Statcast data. As more days accumulate, this same query pattern (and others like it) becomes the basis for real analysis rather than pipeline validation.</p>
<p>The pipeline code behind this post <a href="https://github.com/maydaytek/maydaystats-pipelines">lives here</a>.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>This post uses Quarto’s frozen execution (<code>freeze: auto</code>): the code above only re-runs when I re-render locally with fresh BigQuery access. The deployed site reuses that committed output rather than re-querying BigQuery on every build, which is what lets Cloudflare Pages build this site without needing my GCP credentials.</p>
</div>
</div>


</section>

 ]]></description>
  <category>Baseball</category>
  <category>Pipeline</category>
  <guid>https://maydaystats.com/posts/baseball/2026-07-08-first-pitches/</guid>
  <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Year in Review: The 2025-26 NHL Season</title>
  <link>https://maydaystats.com/posts/hockey/2026-07-08-year-in-review/</link>
  <description><![CDATA[ 




<p>The hockey pipeline has now pulled a complete season: every regular-season and playoff game from the 2025-26 NHL season, backfilled game by game into BigQuery. This post is the first real analysis built on top of it, a look back at the season through the same data the daily pipeline keeps adding to.</p>
<p>One wrinkle worth flagging up front: the NHL’s schedule API also listed games from the Milano Cortina Winter Olympics during the league’s February break, tagged with a different game type than regular-season or playoff games. Those are country-vs-country games, not NHL team games, so they’re filtered out of everything below.</p>
<section id="querying-the-season" class="level2">
<h2 class="anchored" data-anchor-id="querying-the-season">Querying the season</h2>
<div id="cell-fetch-scorers" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.cloud <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> bigquery</span>
<span id="cb1-2"></span>
<span id="cb1-3">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bigquery.Client()</span>
<span id="cb1-4"></span>
<span id="cb1-5">query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb1-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  player_name,</span></span>
<span id="cb1-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  team,</span></span>
<span id="cb1-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(goals) AS goals,</span></span>
<span id="cb1-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(assists) AS assists,</span></span>
<span id="cb1-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(points) AS points,</span></span>
<span id="cb1-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNT(DISTINCT game_id) AS games_played</span></span>
<span id="cb1-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.nhl_stats.boxscores`</span></span>
<span id="cb1-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE CAST(SUBSTR(CAST(game_id AS STRING), 5, 2) AS INT64) IN (2, 3)</span></span>
<span id="cb1-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND position_group != 'goalie'</span></span>
<span id="cb1-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb1-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY points DESC</span></span>
<span id="cb1-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 10</span></span>
<span id="cb1-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-20"></span>
<span id="cb1-21">scorers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(query).to_dataframe()</span>
<span id="cb1-22">scorers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> scorers.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-23">scorers.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-24">scorers</span></code></pre></div>
<div id="fetch-scorers" class="cell-output cell-output-display" data-execution_count="1">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">goals</th>
<th data-quarto-table-cell-role="th">assists</th>
<th data-quarto-table-cell-role="th">points</th>
<th data-quarto-table-cell-role="th">games_played</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>N. MacKinnon</td>
<td>COL</td>
<td>64.0</td>
<td>86.0</td>
<td>150.0</td>
<td>93</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>C. McDavid</td>
<td>EDM</td>
<td>49.0</td>
<td>98.0</td>
<td>147.0</td>
<td>88</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>N. Kucherov</td>
<td>TBL</td>
<td>47.0</td>
<td>91.0</td>
<td>138.0</td>
<td>84</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>N. Suzuki</td>
<td>MTL</td>
<td>33.0</td>
<td>89.0</td>
<td>122.0</td>
<td>101</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>M. Necas</td>
<td>COL</td>
<td>42.0</td>
<td>79.0</td>
<td>121.0</td>
<td>91</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>J. Eichel</td>
<td>VGK</td>
<td>31.0</td>
<td>88.0</td>
<td>119.0</td>
<td>96</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>M. Celebrini</td>
<td>SJS</td>
<td>45.0</td>
<td>73.0</td>
<td>118.0</td>
<td>82</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>D. Pastrnak</td>
<td>BOS</td>
<td>33.0</td>
<td>79.0</td>
<td>112.0</td>
<td>83</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>M. Marner</td>
<td>VGK</td>
<td>34.0</td>
<td>77.0</td>
<td>111.0</td>
<td>103</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>K. Kaprizov</td>
<td>MIN</td>
<td>52.0</td>
<td>59.0</td>
<td>111.0</td>
<td>89</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>N. MacKinnon led the league with 150 points (64 goals, 86 assists) across 93 games, including the playoffs. Connor McDavid finished a close second, and Colorado and Vegas each placed two players in the top ten scorers, more than any other team.</p>
</section>
<section id="the-top-ten-by-points" class="level2">
<h2 class="anchored" data-anchor-id="the-top-ten-by-points">The top ten, by points</h2>
<div id="cell-fig-top-scorers" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb2-2"></span>
<span id="cb2-3">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb2-4">labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> scorers[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" ("</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> scorers[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">")"</span></span>
<span id="cb2-5">ax.barh(labels[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], scorers[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>][::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span>)</span>
<span id="cb2-6">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Points"</span>)</span>
<span id="cb2-7">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Top 10 Scorers, 2025-26 Season"</span>)</span>
<span id="cb2-8">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb2-9">plt.tight_layout()</span>
<span id="cb2-10">plt.show()</span></code></pre></div>
<div class="cell-output cell-output-display">
<div id="fig-top-scorers" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-top-scorers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://maydaystats.com/posts/hockey/2026-07-08-year-in-review/index_files/figure-html/fig-top-scorers-output-1.png" width="862" height="471" class="figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-top-scorers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Top 10 point scorers, 2025-26 regular season and playoffs combined
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="the-best-record-didnt-win-the-cup" class="level2">
<h2 class="anchored" data-anchor-id="the-best-record-didnt-win-the-cup">The best record didn’t win the Cup</h2>
<div id="cell-fetch-standings" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">standings_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb3-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  team,</span></span>
<span id="cb3-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNTIF(decision = 'W') AS wins,</span></span>
<span id="cb3-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNTIF(decision = 'L') AS losses</span></span>
<span id="cb3-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.nhl_stats.boxscores`</span></span>
<span id="cb3-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE CAST(SUBSTR(CAST(game_id AS STRING), 5, 2) AS INT64) = 2</span></span>
<span id="cb3-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND position_group = 'goalie'</span></span>
<span id="cb3-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND decision IS NOT NULL</span></span>
<span id="cb3-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY team</span></span>
<span id="cb3-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY wins DESC</span></span>
<span id="cb3-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 5</span></span>
<span id="cb3-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-14"></span>
<span id="cb3-15">standings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(standings_query).to_dataframe()</span>
<span id="cb3-16">standings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> standings.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-17">standings.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-18">standings</span></code></pre></div>
<div id="fetch-standings" class="cell-output cell-output-display" data-execution_count="3">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">wins</th>
<th data-quarto-table-cell-role="th">losses</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>COL</td>
<td>58</td>
<td>16</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>CAR</td>
<td>55</td>
<td>22</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>DAL</td>
<td>52</td>
<td>20</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>TBL</td>
<td>51</td>
<td>28</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>MTL</td>
<td>50</td>
<td>25</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Colorado had the best regular-season record in the league at 58-16, built in part on Nathan MacKinnon’s scoring, and Carolina wasn’t far behind. But the regular season and the playoffs are different tournaments. Colorado swept the Kings in the first round and beat Minnesota 4 games to 1 in the second, then ran into Vegas in the Western Conference Final and lost in a sweep of their own. Carolina kept going all the way to the Stanley Cup Final, where they met that same Vegas team.</p>
</section>
<section id="what-actually-happened-to-colorado" class="level2">
<h2 class="anchored" data-anchor-id="what-actually-happened-to-colorado">What actually happened to Colorado</h2>
<div id="cell-fetch-col-series" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">col_series_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb4-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT game_id, team, SUM(goals) AS goals</span></span>
<span id="cb4-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.nhl_stats.boxscores`</span></span>
<span id="cb4-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE game_id IN (2025030321, 2025030322, 2025030323, 2025030324)</span></span>
<span id="cb4-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY game_id, team</span></span>
<span id="cb4-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY game_id, team</span></span>
<span id="cb4-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb4-8">col_series <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(col_series_query).to_dataframe()</span>
<span id="cb4-9">col_series <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> col_series.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb4-10">col_series.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb4-11">col_series</span></code></pre></div>
<div id="fetch-col-series" class="cell-output cell-output-display" data-execution_count="4">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">game_id</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">goals</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2025030321</td>
<td>COL</td>
<td>2.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2025030321</td>
<td>VGK</td>
<td>4.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2025030322</td>
<td>COL</td>
<td>1.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>2025030322</td>
<td>VGK</td>
<td>3.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>2025030323</td>
<td>COL</td>
<td>3.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>2025030323</td>
<td>VGK</td>
<td>5.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>2025030324</td>
<td>COL</td>
<td>1.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>2025030324</td>
<td>VGK</td>
<td>2.0</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Four games, four Vegas wins: 4-2, 3-1, 5-3, 2-1. It wasn’t a case of Colorado getting run off the ice, three of the four games were decided by a single goal or two, but a sweep is a sweep, and the box scores point at two specific reasons rather than a generic cold streak.</p>
<div id="cell-fetch-col-goalies" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">goalie_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb5-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT game_id, team, player_name, save_pctg</span></span>
<span id="cb5-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.nhl_stats.boxscores`</span></span>
<span id="cb5-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE game_id IN (2025030321, 2025030322, 2025030323, 2025030324)</span></span>
<span id="cb5-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND position_group = 'goalie'</span></span>
<span id="cb5-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND save_pctg IS NOT NULL</span></span>
<span id="cb5-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY game_id, team</span></span>
<span id="cb5-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb5-9">goalies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(goalie_query).to_dataframe()</span>
<span id="cb5-10">goalies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> goalies.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-11">goalies.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-12">goalies</span></code></pre></div>
<div id="fetch-col-goalies" class="cell-output cell-output-display" data-execution_count="5">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">game_id</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">save_pctg</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2025030321</td>
<td>COL</td>
<td>S. Wedgewood</td>
<td>0.888889</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2025030321</td>
<td>VGK</td>
<td>C. Hart</td>
<td>0.947368</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2025030322</td>
<td>COL</td>
<td>S. Wedgewood</td>
<td>0.916667</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>2025030322</td>
<td>VGK</td>
<td>C. Hart</td>
<td>0.967742</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>2025030323</td>
<td>COL</td>
<td>S. Wedgewood</td>
<td>0.818182</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>2025030323</td>
<td>VGK</td>
<td>C. Hart</td>
<td>0.914286</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>2025030324</td>
<td>COL</td>
<td>M. Blackwood</td>
<td>0.923077</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>2025030324</td>
<td>VGK</td>
<td>C. Hart</td>
<td>0.952381</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Vegas starter Cam Hart never had a bad night: a .947 save percentage in Game 1, .968 in Game 2, .914 in Game 3, .952 in Game 4, four consecutive starts above his own regular-season form. Colorado’s own goaltending was more uneven than collapsed: Scott Wedgewood started the first three games and posted a .908 save percentage across the regular season, but ranged from .889 to .917 to .818 in this series, a rough Game 3 more than a full collapse. Mackenzie Blackwood took over for Game 4 and actually outplayed his own .894 season average, stopping .923. Colorado’s goaltending wasn’t the story on its own. Vegas’s goalie having the series of his season was.</p>
<div id="cell-fetch-mackinnon-series" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">mackinnon_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb6-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT COUNT(DISTINCT game_id) AS games, SUM(goals) AS goals,</span></span>
<span id="cb6-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(assists) AS assists, SUM(points) AS points</span></span>
<span id="cb6-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.nhl_stats.boxscores`</span></span>
<span id="cb6-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE player_name = 'N. MacKinnon' AND team = 'COL'</span></span>
<span id="cb6-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND game_id IN (2025030321, 2025030322, 2025030323, 2025030324)</span></span>
<span id="cb6-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb6-8">mackinnon_series <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(mackinnon_query).to_dataframe()</span>
<span id="cb6-9">mackinnon_series <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mackinnon_series.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb6-10">mackinnon_series.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-11">mackinnon_series</span></code></pre></div>
<div id="fetch-mackinnon-series" class="cell-output cell-output-display" data-execution_count="6">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">games</th>
<th data-quarto-table-cell-role="th">goals</th>
<th data-quarto-table-cell-role="th">assists</th>
<th data-quarto-table-cell-role="th">points</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>4</td>
<td>0.0</td>
<td>2.0</td>
<td>2.0</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>The bigger story is Nathan MacKinnon, the same player whose scoring this post already credited with carrying Colorado’s regular season. Across 80 regular-season games he scored 57 goals, a .71-goal-per-game pace. Across the four-game sweep against Vegas he scored 0. Zero. Two assists, two total points, in the series that ended his team’s season. Gabriel Landeskog led the team in goals with 3, and tied Nazem Kadri and Devon Toews for the team lead in points at 3 apiece, but a team built around its best player suddenly not scoring, against a goalie who was outplaying his own season averages every single night, is a specific, findable reason a 58-win team went home in May rather than a vague “playoffs are different” shrug.</p>
<div id="cell-fetch-final" class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">final_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb7-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WITH final_games AS (</span></span>
<span id="cb7-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SELECT game_id, game_date, team, SUM(goals) AS team_goals</span></span>
<span id="cb7-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  FROM `maydaystats.nhl_stats.boxscores`</span></span>
<span id="cb7-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  WHERE CAST(SUBSTR(CAST(game_id AS STRING), 5, 2) AS INT64) = 3</span></span>
<span id="cb7-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    AND team IN ('CAR', 'VGK')</span></span>
<span id="cb7-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  GROUP BY game_id, game_date, team</span></span>
<span id="cb7-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb7-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb7-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  game_date,</span></span>
<span id="cb7-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  MAX(IF(team = 'CAR', team_goals, NULL)) AS car_goals,</span></span>
<span id="cb7-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  MAX(IF(team = 'VGK', team_goals, NULL)) AS vgk_goals</span></span>
<span id="cb7-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM final_games</span></span>
<span id="cb7-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY game_date</span></span>
<span id="cb7-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">HAVING car_goals IS NOT NULL AND vgk_goals IS NOT NULL</span></span>
<span id="cb7-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY game_date</span></span>
<span id="cb7-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb7-18"></span>
<span id="cb7-19">final <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(final_query).to_dataframe()</span>
<span id="cb7-20">final <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> final.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb7-21">final.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb7-22">final</span></code></pre></div>
<div id="fetch-final" class="cell-output cell-output-display" data-execution_count="7">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">game_date</th>
<th data-quarto-table-cell-role="th">car_goals</th>
<th data-quarto-table-cell-role="th">vgk_goals</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>2026-05-04</td>
<td>3.0</td>
<td>3.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>2026-06-02</td>
<td>4.0</td>
<td>5.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>2026-06-04</td>
<td>4.0</td>
<td>3.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>2026-06-06</td>
<td>4.0</td>
<td>5.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>2026-06-09</td>
<td>5.0</td>
<td>3.0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>2026-06-11</td>
<td>4.0</td>
<td>2.0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>2026-06-14</td>
<td>3.0</td>
<td>0.0</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>The Final went six games, and three of them (Games 1 through 3) were decided by a single goal. Carolina closed it out on June 14 with a 3-0 shutout, taking the series 4 games to 2. Vegas rode Cam Hart the entire playoffs, all 22 games across all four rounds, all the way through the Final loss. Carolina’s path in goal was less steady: Frederik Andersen started most of the postseason, including the first two games of the Final, but Carolina turned to Brandon Bussi for Game 3, which Bussi lost, before he settled in and won Games 4, 5, and 6 to close out the series. Neither goalie led the league in wins during the regular season; that distinction was an exact tie between Tampa Bay’s Andrei Vasilevskiy and Utah’s Karel Vejmelka, both at 39.</p>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">What’s next</h2>
<p>This post covers the season at a high level: scoring, standings, and the playoff run. The same table supports much narrower questions too, like how a specific team’s goaltending held up across back-to-backs, or how a rookie’s production changed after the Olympic break. Those are posts for another day, now that a full season of clean data is sitting in BigQuery.</p>
<p>The pipeline code behind this post <a href="https://github.com/maydaytek/maydaystats-pipelines">lives here</a>.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Like the <a href="../../../baseball.html">baseball</a> posts, this one uses Quarto’s frozen execution (<code>freeze: auto</code>): the queries above ran once, locally, against BigQuery, and the deployed site reuses that committed output rather than re-querying on every build.</p>
</div>
</div>


</section>

 ]]></description>
  <category>Hockey</category>
  <category>Featured</category>
  <category>Season Recap</category>
  <guid>https://maydaystats.com/posts/hockey/2026-07-08-year-in-review/</guid>
  <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Year in Review: The 2025-26 NCAA Men’s Volleyball Season</title>
  <link>https://maydaystats.com/posts/volleyball/2026-07-08-mens-year-in-review/</link>
  <description><![CDATA[ 




<p>The men’s volleyball pipeline has now pulled a complete season: every Division I match from January through the national championship in early May, backfilled game by game into BigQuery via the self-hosted NCAA API proxy. This post is the first real analysis built on top of it.</p>
<p>One note on the data: about 6% of rows carry a blank player name despite having real stat values, a gap on NCAA’s own side for a small number of games rather than a pipeline bug (see the pipeline’s <code>DEPLOY.md</code> for the full explanation). Everything below filters those rows out.</p>
<section id="querying-the-season" class="level2">
<h2 class="anchored" data-anchor-id="querying-the-season">Querying the season</h2>
<div id="cell-fetch-kills-leaders" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.cloud <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> bigquery</span>
<span id="cb1-2"></span>
<span id="cb1-3">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bigquery.Client()</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># NCAA's API returns team as its own 6-character code (e.g. "PEPPER"), not</span></span>
<span id="cb1-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a school name. Tables and charts below keep the raw code as-is (matches</span></span>
<span id="cb1-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># how the hockey post's chart also uses raw team codes); this mapping is</span></span>
<span id="cb1-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># only used to spell out real school names in the prose. A code not in</span></span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># this dict falls back to showing the raw abbreviation rather than</span></span>
<span id="cb1-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># failing, in case a future re-render surfaces a school not covered yet.</span></span>
<span id="cb1-11">TEAM_NAMES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb1-12">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PEPPER"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Pepperdine"</span>,</span>
<span id="cb1-13">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UC IRV"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UC Irvine"</span>,</span>
<span id="cb1-14">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BALLST"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Ball State"</span>,</span>
<span id="cb1-15">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CATAWB"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Catawba"</span>,</span>
<span id="cb1-16">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PENNST"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Penn State"</span>,</span>
<span id="cb1-17">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MCKEND"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"McKendree"</span>,</span>
<span id="cb1-18">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EMMAN"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Emmanuel (Ga.)"</span>,</span>
<span id="cb1-19">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LEWIS"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Lewis"</span>,</span>
<span id="cb1-20">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"HAWAII"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hawaii"</span>,</span>
<span id="cb1-21">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UCLA"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UCLA"</span>,</span>
<span id="cb1-22">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"USC"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"USC"</span>,</span>
<span id="cb1-23">}</span>
<span id="cb1-24"></span>
<span id="cb1-25"></span>
<span id="cb1-26"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> team_name(code):</span>
<span id="cb1-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TEAM_NAMES.get(code, code)</span>
<span id="cb1-28"></span>
<span id="cb1-29"></span>
<span id="cb1-30">query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-31"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb1-32"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  player_name,</span></span>
<span id="cb1-33"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  team,</span></span>
<span id="cb1-34"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(CAST(kills AS INT64)) AS kills,</span></span>
<span id="cb1-35"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(CAST(attack_attempts AS INT64)) AS attempts,</span></span>
<span id="cb1-36"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SAFE_DIVIDE(</span></span>
<span id="cb1-37"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(CAST(kills AS INT64)) - SUM(CAST(attack_errors AS INT64)),</span></span>
<span id="cb1-38"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(CAST(attack_attempts AS INT64))</span></span>
<span id="cb1-39"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ) AS hitting_pct,</span></span>
<span id="cb1-40"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNT(DISTINCT game_id) AS games</span></span>
<span id="cb1-41"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.ncaa_volleyball_men.boxscores`</span></span>
<span id="cb1-42"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE player_name != ''</span></span>
<span id="cb1-43"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb1-44"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">HAVING attempts &gt;= 200</span></span>
<span id="cb1-45"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY kills DESC</span></span>
<span id="cb1-46"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 10</span></span>
<span id="cb1-47"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-48"></span>
<span id="cb1-49">leaders <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(query).to_dataframe()</span>
<span id="cb1-50">leaders <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> leaders.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-51">leaders.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-52">leaders</span></code></pre></div>
<div id="fetch-kills-leaders" class="cell-output cell-output-display" data-execution_count="1">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">kills</th>
<th data-quarto-table-cell-role="th">attempts</th>
<th data-quarto-table-cell-role="th">hitting_pct</th>
<th data-quarto-table-cell-role="th">games</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Cole Hartke</td>
<td>PEPPER</td>
<td>452</td>
<td>937</td>
<td>0.352188</td>
<td>29</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Andrej Jokanovic</td>
<td>UC IRV</td>
<td>419</td>
<td>998</td>
<td>0.278557</td>
<td>30</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Patrick Rogers</td>
<td>BALLST</td>
<td>414</td>
<td>786</td>
<td>0.399491</td>
<td>31</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Justin Arrowood</td>
<td>CATAWB</td>
<td>397</td>
<td>938</td>
<td>0.210021</td>
<td>27</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Sean Kelly</td>
<td>UCLA</td>
<td>396</td>
<td>753</td>
<td>0.398406</td>
<td>31</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Sean Harvey</td>
<td>PENNST</td>
<td>388</td>
<td>806</td>
<td>0.328784</td>
<td>29</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Zach Rama</td>
<td>UCLA</td>
<td>386</td>
<td>803</td>
<td>0.332503</td>
<td>30</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Dillon Klein</td>
<td>USC</td>
<td>385</td>
<td>770</td>
<td>0.340260</td>
<td>27</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Bryce Wetjen</td>
<td>MCKEND</td>
<td>375</td>
<td>813</td>
<td>0.258303</td>
<td>29</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Aidan Feeney</td>
<td>EMMAN</td>
<td>374</td>
<td>880</td>
<td>0.244318</td>
<td>26</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Cole Hartke of Pepperdine led the country with 452 kills, on a 0.352 hitting percentage across 29 matches. UC Irvine’s Andrej Jokanovic finished a close second in raw kills, but needed about 60 more swings to get there, a volume-over-efficiency trade that shows up again later in his team’s championship match.</p>
</section>
<section id="the-top-ten-by-kills" class="level2">
<h2 class="anchored" data-anchor-id="the-top-ten-by-kills">The top ten, by kills</h2>
<div id="cell-fig-top-kills" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb2-2"></span>
<span id="cb2-3">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb2-4">labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> leaders[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" ("</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> leaders[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">")"</span></span>
<span id="cb2-5">ax.barh(labels[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], leaders[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kills"</span>][::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span>)</span>
<span id="cb2-6">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Kills"</span>)</span>
<span id="cb2-7">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Top 10 Kills Leaders, 2025-26 Men's Season"</span>)</span>
<span id="cb2-8">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb2-9">plt.tight_layout()</span>
<span id="cb2-10">plt.show()</span></code></pre></div>
<div class="cell-output cell-output-display">
<div id="fig-top-kills" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-top-kills-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://maydaystats.com/posts/volleyball/2026-07-08-mens-year-in-review/index_files/figure-html/fig-top-kills-output-1.png" width="863" height="470" class="figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-top-kills-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Top 10 kills leaders, 2025-26 NCAA D1 men’s season (min. 200 attempts)
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="digs-blocks-and-aces" class="level2">
<h2 class="anchored" data-anchor-id="digs-blocks-and-aces">Digs, blocks, and aces</h2>
<div id="fetch-other-leaders" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">digs_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, SUM(CAST(digs AS INT64)) AS total</span></span>
<span id="cb3-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.ncaa_volleyball_men.boxscores`</span></span>
<span id="cb3-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE player_name != ''</span></span>
<span id="cb3-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb3-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY total DESC</span></span>
<span id="cb3-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 1</span></span>
<span id="cb3-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-9">digs_leader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(digs_query).to_dataframe()</span>
<span id="cb3-10"></span>
<span id="cb3-11">blocks_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, SUM(CAST(total_blocks AS FLOAT64)) AS total</span></span>
<span id="cb3-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.ncaa_volleyball_men.boxscores`</span></span>
<span id="cb3-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE player_name != ''</span></span>
<span id="cb3-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb3-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY total DESC</span></span>
<span id="cb3-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 1</span></span>
<span id="cb3-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-19">blocks_leader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(blocks_query).to_dataframe()</span>
<span id="cb3-20"></span>
<span id="cb3-21">aces_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, SUM(CAST(service_aces AS INT64)) AS total</span></span>
<span id="cb3-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.ncaa_volleyball_men.boxscores`</span></span>
<span id="cb3-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE player_name != ''</span></span>
<span id="cb3-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb3-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY total DESC</span></span>
<span id="cb3-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 1</span></span>
<span id="cb3-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-29">aces_leader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(aces_query).to_dataframe()</span></code></pre></div>
</div>
<p>Nico Paula (Lewis) led all players in digs with 264. Owen Rose (Penn State) topped the block chart with 156, well clear of the field. And Andrew Rowan (UCLA) led in aces with 57.</p>
</section>
<section id="the-championship-hawaii-over-uc-irvine" class="level2">
<h2 class="anchored" data-anchor-id="the-championship-hawaii-over-uc-irvine">The championship: Hawaii over UC Irvine</h2>
<div id="cell-fetch-final" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">final_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb4-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, kills, attack_attempts, hitting_percentage, total_blocks</span></span>
<span id="cb4-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.ncaa_volleyball_men.boxscores`</span></span>
<span id="cb4-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE game_id = '6598557'</span></span>
<span id="cb4-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND CAST(kills AS INT64) &gt; 0</span></span>
<span id="cb4-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY team, CAST(kills AS INT64) DESC</span></span>
<span id="cb4-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb4-8">final_box <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(final_query).to_dataframe()</span>
<span id="cb4-9">final_box <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> final_box.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb4-10">final_box.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb4-11">final_box</span></code></pre></div>
<div id="fetch-final" class="cell-output cell-output-display" data-execution_count="4">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">kills</th>
<th data-quarto-table-cell-role="th">attack_attempts</th>
<th data-quarto-table-cell-role="th">hitting_percentage</th>
<th data-quarto-table-cell-role="th">total_blocks</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Kristian Titriyski</td>
<td>HAWAII</td>
<td>16</td>
<td>31</td>
<td>0.387</td>
<td>1</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Adrien Roure</td>
<td>HAWAII</td>
<td>15</td>
<td>31</td>
<td>0.323</td>
<td>0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Louis Sakanoko</td>
<td>HAWAII</td>
<td>12</td>
<td>20</td>
<td>0.500</td>
<td>3</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Justin Todd</td>
<td>HAWAII</td>
<td>7</td>
<td>10</td>
<td>0.500</td>
<td>1</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Trevell Jordan</td>
<td>HAWAII</td>
<td>5</td>
<td>7</td>
<td>0.714</td>
<td>3</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Tread Rosenthal</td>
<td>HAWAII</td>
<td>3</td>
<td>6</td>
<td>0.167</td>
<td>3</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Andrej Jokanovic</td>
<td>UC IRV</td>
<td>13</td>
<td>35</td>
<td>0.229</td>
<td>3</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>William D'arcy</td>
<td>UC IRV</td>
<td>9</td>
<td>24</td>
<td>0.292</td>
<td>0</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Andreas Brinck</td>
<td>UC IRV</td>
<td>6</td>
<td>18</td>
<td>0.111</td>
<td>1</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Trevor Clark</td>
<td>UC IRV</td>
<td>6</td>
<td>12</td>
<td>0.333</td>
<td>6</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">11</td>
<td>Micah Goss</td>
<td>UC IRV</td>
<td>3</td>
<td>4</td>
<td>0.750</td>
<td>6</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">12</td>
<td>Cameron Kosty</td>
<td>UC IRV</td>
<td>1</td>
<td>2</td>
<td>.000</td>
<td>5</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Hawaii beat UC Irvine 3-1 on May 11, 2026 to win the national championship. Kristian Titriyski led the way with 16 kills on a .387 hitting percentage, with Adrien Roure adding 15 more. Louis Sakanoko went 12-for-20 (.500) with 3 blocks off the bench, the best hitting percentage of anyone with real volume in the match (two Hawaii players posted a higher percentage on much smaller sample sizes, 5-for-7 and 7-for-10).</p>
<p>UC Irvine’s offense leaned almost entirely on Andrej Jokanovic, the same volume hitter who finished second nationally in kills for the season. He had a team-high 13 kills in the final, but needed 35 attempts to get there, a .229 hitting percentage that never let UC Irvine pull away the way Hawaii’s more balanced, more efficient attack did.</p>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">What’s next</h2>
<p>This covers the season at a high level: the national leaderboards and the championship match itself. The same table supports much narrower questions too, like a single team’s hitting efficiency by conference opponent, or how a freshman’s role grew over the season. Those are posts for another day, now that a full season of clean data is sitting in BigQuery.</p>
<p>The pipeline code behind this post <a href="https://github.com/maydaytek/maydaystats-pipelines">lives here</a>.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Like the <a href="../../../baseball.html">baseball</a> and <a href="../../../hockey.html">hockey</a> posts, this one uses Quarto’s frozen execution (<code>freeze: auto</code>): the queries above ran once, locally, against BigQuery, and the deployed site reuses that committed output rather than re-querying on every build.</p>
</div>
</div>


</section>

 ]]></description>
  <category>Volleyball</category>
  <category>Men&#39;s</category>
  <category>Featured</category>
  <category>Season Recap</category>
  <guid>https://maydaystats.com/posts/volleyball/2026-07-08-mens-year-in-review/</guid>
  <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Year in Review: The 2025 NCAA Women’s Volleyball Season</title>
  <link>https://maydaystats.com/posts/volleyball/2026-07-08-womens-year-in-review/</link>
  <description><![CDATA[ 




<p>The women’s volleyball pipeline has now pulled a complete season: every Division I match from late August through the national championship in mid-December, backfilled game by game into BigQuery via the same self-hosted NCAA API proxy behind the men’s pipeline. This post is the first real analysis built on top of it.</p>
<p>One note on the data: a much smaller share of rows here (well under 1%) carry a blank player name, the same kind of NCAA-side gap described in the <a href="../../../posts/volleyball/2026-07-08-mens-year-in-review/index.html">men’s year in review</a>, not a pipeline bug.</p>
<section id="querying-the-season" class="level2">
<h2 class="anchored" data-anchor-id="querying-the-season">Querying the season</h2>
<div id="cell-fetch-kills-leaders" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.cloud <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> bigquery</span>
<span id="cb1-2"></span>
<span id="cb1-3">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bigquery.Client()</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># NCAA's API returns team as its own code (e.g. "STFRPA"), not a school</span></span>
<span id="cb1-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># name. Tables and charts below keep the raw code as-is (matches how the</span></span>
<span id="cb1-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># hockey post's chart also uses raw team codes); this mapping is only</span></span>
<span id="cb1-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># used to spell out real school names in the prose. A code not in this</span></span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># dict falls back to showing the raw abbreviation rather than failing,</span></span>
<span id="cb1-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># in case a future re-render surfaces a school not covered yet.</span></span>
<span id="cb1-11">TEAM_NAMES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb1-12">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MIAMI"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Miami"</span>,</span>
<span id="cb1-13">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PITT"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Pittsburgh"</span>,</span>
<span id="cb1-14">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PENNST"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Penn State"</span>,</span>
<span id="cb1-15">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"JMU"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"James Madison"</span>,</span>
<span id="cb1-16">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"WISC"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Wisconsin"</span>,</span>
<span id="cb1-17">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"STFRPA"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Saint Francis (PA)"</span>,</span>
<span id="cb1-18">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PURDUE"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Purdue"</span>,</span>
<span id="cb1-19">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SAC ST"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Sacramento State"</span>,</span>
<span id="cb1-20">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UK"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Kentucky"</span>,</span>
<span id="cb1-21">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TCU"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TCU"</span>,</span>
<span id="cb1-22">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UALR"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Little Rock"</span>,</span>
<span id="cb1-23">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UT VAL"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Utah Valley"</span>,</span>
<span id="cb1-24">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TX A&amp;M"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Texas A&amp;M"</span>,</span>
<span id="cb1-25">}</span>
<span id="cb1-26"></span>
<span id="cb1-27"></span>
<span id="cb1-28"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> team_name(code):</span>
<span id="cb1-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TEAM_NAMES.get(code, code)</span>
<span id="cb1-30"></span>
<span id="cb1-31"></span>
<span id="cb1-32">query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-33"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT</span></span>
<span id="cb1-34"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  player_name,</span></span>
<span id="cb1-35"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  team,</span></span>
<span id="cb1-36"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(CAST(kills AS INT64)) AS kills,</span></span>
<span id="cb1-37"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SUM(CAST(attack_attempts AS INT64)) AS attempts,</span></span>
<span id="cb1-38"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  SAFE_DIVIDE(</span></span>
<span id="cb1-39"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(CAST(kills AS INT64)) - SUM(CAST(attack_errors AS INT64)),</span></span>
<span id="cb1-40"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SUM(CAST(attack_attempts AS INT64))</span></span>
<span id="cb1-41"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  ) AS hitting_pct,</span></span>
<span id="cb1-42"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  COUNT(DISTINCT game_id) AS games</span></span>
<span id="cb1-43"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.ncaa_volleyball_women.boxscores`</span></span>
<span id="cb1-44"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE player_name != ''</span></span>
<span id="cb1-45"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb1-46"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">HAVING attempts &gt;= 200</span></span>
<span id="cb1-47"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY kills DESC</span></span>
<span id="cb1-48"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 10</span></span>
<span id="cb1-49"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-50"></span>
<span id="cb1-51">leaders <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(query).to_dataframe()</span>
<span id="cb1-52">leaders <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> leaders.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-53">leaders.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-54">leaders</span></code></pre></div>
<div id="fetch-kills-leaders" class="cell-output cell-output-display" data-execution_count="1">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">kills</th>
<th data-quarto-table-cell-role="th">attempts</th>
<th data-quarto-table-cell-role="th">hitting_pct</th>
<th data-quarto-table-cell-role="th">games</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Flormarie Heredia Colon</td>
<td>MIAMI</td>
<td>687</td>
<td>1447</td>
<td>0.289565</td>
<td>28</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Olivia Babcock</td>
<td>PITT</td>
<td>646</td>
<td>1383</td>
<td>0.334056</td>
<td>35</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Kennedy Martin</td>
<td>PENNST</td>
<td>634</td>
<td>1423</td>
<td>0.319044</td>
<td>31</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Kennedy Louisell</td>
<td>JMU</td>
<td>605</td>
<td>1436</td>
<td>0.300836</td>
<td>31</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Mimi Colyer</td>
<td>WISC</td>
<td>598</td>
<td>1313</td>
<td>0.340442</td>
<td>33</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Korrin Burns</td>
<td>STFRPA</td>
<td>588</td>
<td>1335</td>
<td>0.268165</td>
<td>29</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Kenna Wollard</td>
<td>PURDUE</td>
<td>577</td>
<td>1383</td>
<td>0.261750</td>
<td>34</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Victoria Marthaler</td>
<td>SAC ST</td>
<td>553</td>
<td>1440</td>
<td>0.213194</td>
<td>30</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Eva Hudson</td>
<td>UK</td>
<td>546</td>
<td>1376</td>
<td>0.319041</td>
<td>33</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Evan Hendrix</td>
<td>TCU</td>
<td>543</td>
<td>1440</td>
<td>0.241667</td>
<td>32</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Flormarie Heredia Colon of Miami led the country with 687 kills across 28 matches. Miami shows up twice in the national leaderboards this season: the same team that produced the kills leader also produced the national leader in aces, a rare two-way offensive season for one program.</p>
</section>
<section id="the-top-ten-by-kills" class="level2">
<h2 class="anchored" data-anchor-id="the-top-ten-by-kills">The top ten, by kills</h2>
<div id="cell-fig-top-kills" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb2-2"></span>
<span id="cb2-3">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb2-4">labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> leaders[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"player_name"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" ("</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> leaders[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">")"</span></span>
<span id="cb2-5">ax.barh(labels[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], leaders[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kills"</span>][::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2c3e50"</span>)</span>
<span id="cb2-6">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Kills"</span>)</span>
<span id="cb2-7">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Top 10 Kills Leaders, 2025 Women's Season"</span>)</span>
<span id="cb2-8">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb2-9">plt.tight_layout()</span>
<span id="cb2-10">plt.show()</span></code></pre></div>
<div class="cell-output cell-output-display">
<div id="fig-top-kills" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-top-kills-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://maydaystats.com/posts/volleyball/2026-07-08-womens-year-in-review/index_files/figure-html/fig-top-kills-output-1.png" width="866" height="470" class="figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-top-kills-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Top 10 kills leaders, 2025 NCAA D1 women’s season (min. 200 attempts)
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="digs-blocks-and-aces" class="level2">
<h2 class="anchored" data-anchor-id="digs-blocks-and-aces">Digs, blocks, and aces</h2>
<div id="fetch-other-leaders" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">digs_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, SUM(CAST(digs AS INT64)) AS total</span></span>
<span id="cb3-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.ncaa_volleyball_women.boxscores`</span></span>
<span id="cb3-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE player_name != ''</span></span>
<span id="cb3-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb3-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY total DESC</span></span>
<span id="cb3-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 1</span></span>
<span id="cb3-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-9">digs_leader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(digs_query).to_dataframe()</span>
<span id="cb3-10"></span>
<span id="cb3-11">blocks_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, SUM(CAST(total_blocks AS FLOAT64)) AS total</span></span>
<span id="cb3-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.ncaa_volleyball_women.boxscores`</span></span>
<span id="cb3-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE player_name != ''</span></span>
<span id="cb3-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb3-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY total DESC</span></span>
<span id="cb3-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 1</span></span>
<span id="cb3-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-19">blocks_leader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(blocks_query).to_dataframe()</span>
<span id="cb3-20"></span>
<span id="cb3-21">aces_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, SUM(CAST(service_aces AS INT64)) AS total</span></span>
<span id="cb3-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.ncaa_volleyball_women.boxscores`</span></span>
<span id="cb3-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE player_name != ''</span></span>
<span id="cb3-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">GROUP BY player_name, team</span></span>
<span id="cb3-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY total DESC</span></span>
<span id="cb3-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">LIMIT 1</span></span>
<span id="cb3-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-29">aces_leader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(aces_query).to_dataframe()</span></code></pre></div>
</div>
<p>Andrea Roman (Little Rock) led all players in digs with 632. Bella Wooden of Utah Valley led the country in individual blocks with 188. And Ariana Rodriguez (Miami) topped the ace leaderboard with 77, the same program that produced the season’s kills leader.</p>
</section>
<section id="the-championship-texas-am-sweeps-kentucky" class="level2">
<h2 class="anchored" data-anchor-id="the-championship-texas-am-sweeps-kentucky">The championship: Texas A&amp;M sweeps Kentucky</h2>
<div id="cell-fetch-final" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">final_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb4-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">SELECT player_name, team, kills, attack_attempts, hitting_percentage, digs</span></span>
<span id="cb4-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">FROM `maydaystats.ncaa_volleyball_women.boxscores`</span></span>
<span id="cb4-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">WHERE game_id = '6500718'</span></span>
<span id="cb4-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  AND CAST(kills AS INT64) &gt; 0</span></span>
<span id="cb4-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">ORDER BY team, CAST(kills AS INT64) DESC</span></span>
<span id="cb4-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb4-8">final_box <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.query(final_query).to_dataframe()</span>
<span id="cb4-9">final_box <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> final_box.reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb4-10">final_box.index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb4-11">final_box</span></code></pre></div>
<div id="fetch-final" class="cell-output cell-output-display" data-execution_count="4">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">player_name</th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">kills</th>
<th data-quarto-table-cell-role="th">attack_attempts</th>
<th data-quarto-table-cell-role="th">hitting_percentage</th>
<th data-quarto-table-cell-role="th">digs</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">1</td>
<td>Logan Lednicky</td>
<td>TX A&amp;M</td>
<td>11</td>
<td>32</td>
<td>0.250</td>
<td>7</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">2</td>
<td>Kyndal Stowers</td>
<td>TX A&amp;M</td>
<td>10</td>
<td>23</td>
<td>0.304</td>
<td>6</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">3</td>
<td>Ifenna Cos-Okpalla</td>
<td>TX A&amp;M</td>
<td>8</td>
<td>17</td>
<td>0.235</td>
<td>0</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">4</td>
<td>Emily Hellmuth</td>
<td>TX A&amp;M</td>
<td>6</td>
<td>18</td>
<td>0.167</td>
<td>3</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">5</td>
<td>Morgan Perkins</td>
<td>TX A&amp;M</td>
<td>3</td>
<td>9</td>
<td>0.333</td>
<td>1</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">6</td>
<td>Maddie Waak</td>
<td>TX A&amp;M</td>
<td>1</td>
<td>2</td>
<td>0.500</td>
<td>5</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">7</td>
<td>Eva Hudson</td>
<td>UK</td>
<td>13</td>
<td>45</td>
<td>0.200</td>
<td>4</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">8</td>
<td>Brooklyn DeLeye</td>
<td>UK</td>
<td>9</td>
<td>28</td>
<td>0.036</td>
<td>8</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">9</td>
<td>Asia Thigpen</td>
<td>UK</td>
<td>7</td>
<td>15</td>
<td>0.333</td>
<td>2</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">10</td>
<td>Lizzie Carr</td>
<td>UK</td>
<td>7</td>
<td>15</td>
<td>0.200</td>
<td>1</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">11</td>
<td>Kennedy Washington</td>
<td>UK</td>
<td>2</td>
<td>7</td>
<td>-0.286</td>
<td>1</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">12</td>
<td>Kassie O'Brien</td>
<td>UK</td>
<td>2</td>
<td>4</td>
<td>0.500</td>
<td>6</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Texas A&amp;M swept Kentucky 3-0 on December 21, 2025 to win the national championship. Logan Lednicky led the Aggies with 11 kills on a .250 hitting percentage, with Kyndal Stowers adding 10 more on .304 hitting, a balanced attack across multiple hitters rather than one player carrying the offense.</p>
<p>Kentucky’s Eva Hudson, who finished among the national top ten in kills for the season, led all players in the match with 13 kills, but needed 45 attempts to get there, a .200 hitting percentage. By far the highest attempt count on the floor, it meant Kentucky’s offense ran through one hitter without the efficiency to match the volume, the same volume-over- efficiency pattern that showed up in the <a href="../../../posts/volleyball/2026-07-08-mens-year-in-review/index.html#the-championship-hawaii-over-uc-irvine">men’s championship match</a>.</p>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">What’s next</h2>
<p>This covers the season at a high level: the national leaderboards and the championship match itself. The same table supports much narrower questions too, like how a team’s blocking efficiency changed after a lineup shift, or a transfer’s production before and after switching programs. Those are posts for another day, now that a full season of clean data is sitting in BigQuery.</p>
<p>The pipeline code behind this post <a href="https://github.com/maydaytek/maydaystats-pipelines">lives here</a>.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Like the <a href="../../../baseball.html">baseball</a>, <a href="../../../hockey.html">hockey</a>, and <a href="../../../posts/volleyball/2026-07-08-mens-year-in-review/index.html">men’s volleyball</a> posts, this one uses Quarto’s frozen execution (<code>freeze: auto</code>): the queries above ran once, locally, against BigQuery, and the deployed site reuses that committed output rather than re-querying on every build.</p>
</div>
</div>


</section>

 ]]></description>
  <category>Volleyball</category>
  <category>Women&#39;s</category>
  <category>Featured</category>
  <category>Season Recap</category>
  <guid>https://maydaystats.com/posts/volleyball/2026-07-08-womens-year-in-review/</guid>
  <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Coursework Deep Dive: What Actually Predicts a Whiff</title>
  <link>https://maydaystats.com/posts/baseball/2026-06-16-purdue-grad505-pitch-analytics/</link>
  <description><![CDATA[ 




<p>Everything else on this site runs through a live pipeline: a Cloud Run Job pulls fresh data every day, loads it into BigQuery, and the posts query that table directly. This one is different, and worth flagging as different. It’s a project from GRAD 505 (Foundations in Data Science) in Purdue’s MS in Data Science program, built on a static Kaggle dataset rather than the site’s own Statcast pipeline: pitch-level data for every MLB regular-season game from 2015 through 2018, about 2.87 million pitches. No live queries here, just the analysis, with the full code, report, and slide deck linked at the bottom.</p>
<p>It’s included in Featured Research because the bar for that section is rigor, not whether the data happens to be live. This project ran three formal hypothesis tests, a linear regression, and a one-way ANOVA with post-hoc comparisons, and landed on a finding worth stating plainly: almost everything came back statistically significant, and almost none of it was large.</p>
<section id="three-research-questions" class="level2">
<h2 class="anchored" data-anchor-id="three-research-questions">Three research questions</h2>
<p>The first deliverable checked the data for major quality problems before any modeling started, a missing-values audit across all five joined tables, shown below by column. Nothing in that audit compromised any of the three analyses that follow.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig1_missing_data.png" class="img-fluid figure-img"></p>
<figcaption>Missing data by column, across all five joined tables</figcaption>
</figure>
</div>
<p><strong>Does a pitcher’s pitch mix change the third time through the order?</strong> A chi-square test of independence on pitch type versus times through the order came back significant (chi-square = 41,249.35, df = 10, p &lt; 0.001), but Cramer’s V was 0.0895, a small effect by any standard cutoff. Pitchers do shift their mix as a game goes on, just not by much.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig2_q1_pitch_type_tto.png" class="img-fluid figure-img"></p>
<figcaption>Pitch-type distribution by times through the order</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig3_q1_table.png" class="img-fluid figure-img"></p>
<figcaption>Pitch-type share summary table</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig7_q1_hypothesis_test.png" class="img-fluid figure-img"></p>
<figcaption>Chi-square test of independence: pitch type vs.&nbsp;times through the order</figcaption>
</figure>
</div>
<p><strong>Does spin rate on a four-seam fastball predict a whiff?</strong> A one-sided Mann-Whitney U test found whiffs came on pitches with a higher median spin rate (2,209.1 RPM) than pitches put in play (2,179.7 RPM), and the difference was significant (p = 8.82e-105). The rank-biserial effect size was 0.0462, again small. A follow-up OLS regression of whiff rate on average four-seam spin rate (n = 928 pitchers with at least 100 four-seamers) found a significant positive slope (0.000016 per RPM, p = 1.82e-06), but the model explained just 2.4% of the variance in whiff rate (R-squared = 0.0243). Spin rate matters, but it’s one input among many.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig4_q2_spinrate_boxplot.png" class="img-fluid figure-img"></p>
<figcaption>Four-seam spin rate distribution, whiffs vs.&nbsp;balls in play</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig5_q2_spinrate_whiff.png" class="img-fluid figure-img"></p>
<figcaption>Whiff rate by four-seam spin rate bin</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig8_q2_hypothesis_test.png" class="img-fluid figure-img"></p>
<figcaption>Mann-Whitney U test: four-seam spin rate, whiffs vs.&nbsp;balls in play</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig11_regression_spin_whiff.png" class="img-fluid figure-img"></p>
<figcaption>OLS regression: whiff rate vs.&nbsp;average four-seam spin rate</figcaption>
</figure>
</div>
<p><strong>Does average fastball velocity predict strike-zone rate in high-leverage innings (7th-9th)?</strong> This is the one question where the data didn’t clear the bar. Pearson correlation between average four-seam velocity and zone percentage came back essentially flat (r = 0.0379, p = 0.276, n = 830 pitchers with at least 50 late-inning fastballs), so this test failed to reject the null. A secondary Spearman rank correlation was technically significant (rho = 0.0727, p = 0.036), but at that size it’s not a finding to build anything on.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig6_q3_velo_zone.png" class="img-fluid figure-img"></p>
<figcaption>Average four-seam velocity vs.&nbsp;zone percentage, high-leverage innings</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig9_q3_hypothesis_test.png" class="img-fluid figure-img"></p>
<figcaption>Pearson correlation: velocity vs.&nbsp;zone percentage, with 95% CI</figcaption>
</figure>
</div>
</section>
<section id="the-velocity-creep-that-is-real-and-tiny" class="level2">
<h2 class="anchored" data-anchor-id="the-velocity-creep-that-is-real-and-tiny">The velocity creep that is real, and tiny</h2>
<p>The final analysis ran a one-way ANOVA on four-seam fastball velocity across all four seasons in the dataset:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Season</th>
<th>n</th>
<th>Mean (mph)</th>
<th>Std</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>2015</td>
<td>249,755</td>
<td>92.48</td>
<td>2.80</td>
</tr>
<tr class="even">
<td>2016</td>
<td>257,649</td>
<td>92.58</td>
<td>2.75</td>
</tr>
<tr class="odd">
<td>2017</td>
<td>253,387</td>
<td>93.22</td>
<td>2.79</td>
</tr>
<tr class="even">
<td>2018</td>
<td>254,086</td>
<td>93.17</td>
<td>2.75</td>
</tr>
</tbody>
</table>
<p>The ANOVA rejected the null outright (F = 4,942.80, p &lt; 0.001), and every pairwise comparison came back significant under a Bonferroni correction, including 2017 versus 2018, a 0.057 mph difference. League-average fastball velocity really did climb across this window, about three quarters of a mile per hour from 2015 to 2017 before leveling off. It’s a real trend. It’s also a small one, and the effect size confirms it: eta squared came out to 0.0144, meaning season explains under 2% of the variation in any single pitch’s velocity.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig12_anova_velocity_by_season.png" class="img-fluid figure-img"></p>
<figcaption>Four-seam fastball velocity by season, with post-hoc pairwise significance</figcaption>
</figure>
</div>
</section>
<section id="whiff-rate-by-pitch-type" class="level2">
<h2 class="anchored" data-anchor-id="whiff-rate-by-pitch-type">Whiff rate by pitch type</h2>
<p>One more result worth surfacing on its own, a 95% Wilson confidence interval on whiff rate for each pitch type across the full 2.87 million pitches:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Pitch</th>
<th>Whiff rate</th>
<th>95% CI</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Changeup (CH)</td>
<td>14.2%</td>
<td>14.1% - 14.4%</td>
</tr>
<tr class="even">
<td>Slider (SL)</td>
<td>14.2%</td>
<td>14.1% - 14.3%</td>
</tr>
<tr class="odd">
<td>Cutter (FC)</td>
<td>10.7%</td>
<td>10.6% - 10.9%</td>
</tr>
<tr class="even">
<td>Knuckle-curve (KC)</td>
<td>10.0%</td>
<td>9.8% - 10.3%</td>
</tr>
<tr class="odd">
<td>Curveball (CU)</td>
<td>9.4%</td>
<td>9.2% - 9.5%</td>
</tr>
<tr class="even">
<td>Four-seam (FF)</td>
<td>7.9%</td>
<td>7.8% - 7.9%</td>
</tr>
<tr class="odd">
<td>Sinker (SI)</td>
<td>5.8%</td>
<td>5.7% - 5.9%</td>
</tr>
<tr class="even">
<td>Two-seam (FT)</td>
<td>5.5%</td>
<td>5.4% - 5.6%</td>
</tr>
</tbody>
</table>
<p>At this sample size the confidence intervals are tight enough that every pitch type is distinguishable from every other, changeups and sliders generate whiffs at roughly the same rate and both clear fastballs by nearly double.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://assets.maydaystats.com/fig10_ci_whiff_by_pitch_type.png" class="img-fluid figure-img"></p>
<figcaption>Whiff rate by pitch type, with 95% Wilson confidence intervals</figcaption>
</figure>
</div>
</section>
<section id="why-this-belongs-next-to-the-pipeline-posts" class="level2">
<h2 class="anchored" data-anchor-id="why-this-belongs-next-to-the-pipeline-posts">Why this belongs next to the pipeline posts</h2>
<p>The throughline across all five analyses is the same: with millions of pitches, almost any real difference clears the p &lt; 0.05 bar, so the question that actually matters is how big the effect is, not whether it exists. That’s a distinction worth being explicit about on a site built around real data, and it’s the main thing this project set out to demonstrate.</p>
<p>Full write-up, slide deck, and all analysis code:</p>
<ul>
<li><a href="https://github.com/maydaytek/GRAD_505_PROJECT">GRAD_505_PROJECT on GitHub</a></li>
<li><a href="https://github.com/maydaytek/GRAD_505_PROJECT/blob/main/BMAY_FINAL_PROJECT_REPORT.pdf">Written report (PDF)</a></li>
<li><a href="https://github.com/maydaytek/GRAD_505_PROJECT/blob/main/BMAY_FINAL_PROJECT_PRESENTATION.pdf">Presentation slides (PDF)</a></li>
</ul>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Unlike the pipeline-driven posts on this site, this one isn’t frozen against a live BigQuery table, there’s no BigQuery table behind it. The numbers above are transcribed from the project’s own output files (<code>OUTPUTS/hypothesis_results.txt</code> and <code>OUTPUTS/final_project_results.txt</code> in the repo linked above), generated from a static Kaggle dataset rather than this site’s own pipeline.</p>
</div>
</div>


</section>

 ]]></description>
  <category>Baseball</category>
  <category>Featured</category>
  <category>Academic Project</category>
  <guid>https://maydaystats.com/posts/baseball/2026-06-16-purdue-grad505-pitch-analytics/</guid>
  <pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>
