From 5d5f5fdf627e1d993a6d5c68626211f01ab17adc Mon Sep 17 00:00:00 2001 From: David Lucey Date: Tue, 31 Mar 2026 13:11:30 -0400 Subject: [PATCH 1/8] feat: extend BattingStats view with FanGraphs data for 2022+ The Lahman CSV files are frozen at 2021. BattingStats now conditionally UNIONs FangraphsBattingWAR rows for yearID > 2021 when FangraphsBattingWAR, ChadwickIDs, and People tables are all present in the database. Join path: FangraphsBattingWAR.playerid -> ChadwickIDs.key_fangraphs -> People.bbrefID -> People.playerID. Graceful fallback to Lahman-only when FG tables are absent (e.g., in :memory: test DBs). Adds message '(view, Lahman + FanGraphs (2022+))' on create to confirm the extension is active. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- R/stats_views.R | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/R/stats_views.R b/R/stats_views.R index 5d5457c..be418d5 100644 --- a/R/stats_views.R +++ b/R/stats_views.R @@ -62,7 +62,46 @@ create_stats_views <- function(con) { # X2B, X3B -- stored with R's prefix because "2B"/"3B" are invalid identifiers # HBP, SF, SH -- nullable in early seasons; COALESCE to 0 # OBP denominator excludes SH (sacrifice bunts) per official MLB definition - DBI::dbExecute(con, " + # + # Extension: when FangraphsBattingWAR and ChadwickIDs are present, seasons + # after the last Lahman year (>2021) are filled from FanGraphs via UNION ALL. + # FG pre-computes AVG/OBP/SLG/ISO/BABIP/BB_pct/K_pct as proportions, matching + # the Lahman-derived values. teamID and lgID are NULL for FG rows (FG uses + # internal team codes that don't map cleanly to Lahman identifiers). + tbls <- DBI::dbListTables(con) + has_fg <- all(c("FangraphsBattingWAR", "ChadwickIDs", "People") %in% tbls) + + fg_union <- if (has_fg) " + UNION ALL + -- FanGraphs extension for seasons not covered by Lahman CSVs (>2021) + SELECT + p.playerID, + fw.Season::INTEGER AS yearID, + 1 AS stint, + NULL::VARCHAR AS teamID, + NULL::VARCHAR AS lgID, + fw.G, fw.AB, fw.R, fw.H, + fw.\"2B\"::INTEGER AS X2B, + fw.\"3B\"::INTEGER AS X3B, + fw.HR, fw.RBI, fw.SB, fw.CS, fw.BB, fw.SO, + COALESCE(fw.IBB, 0) AS IBB, + COALESCE(fw.HBP, 0) AS HBP, + COALESCE(fw.SH, 0) AS SH, + COALESCE(fw.SF, 0) AS SF, + COALESCE(fw.GDP, 0) AS GIDP, + fw.PA, + fw.AVG, fw.OBP, fw.SLG, + fw.OBP + fw.SLG AS OPS, + fw.ISO, fw.BABIP, + fw.BB_pct, fw.K_pct + FROM FangraphsBattingWAR fw + JOIN ChadwickIDs c ON fw.playerid::VARCHAR = c.key_fangraphs::VARCHAR + JOIN People p ON c.key_bbref = p.bbrefID + WHERE fw.Season > 2021 + AND fw.AB > 0 + " else "" + + DBI::dbExecute(con, paste0(" CREATE OR REPLACE VIEW BattingStats AS SELECT playerID, yearID, stint, teamID, lgID, @@ -112,8 +151,9 @@ create_stats_views <- function(con) { / NULLIF(AB + BB + COALESCE(HBP,0) + COALESCE(SF,0) + COALESCE(SH,0), 0) AS K_pct FROM Batting - ") - message(sprintf(" %-25s (view)", "BattingStats")) + ", fg_union)) + src_note <- if (has_fg) " + FanGraphs (2022+)" else "" + message(sprintf(" %-25s (view, Lahman%s)", "BattingStats", src_note)) # ── PitchingStats ──────────────────────────────────────────────────────────── # IPouts = total outs recorded (IP * 3); use throughout to avoid /3 /3 chains. From a897f11d96f4c9edb3b06c81e471607c1c90c813 Mon Sep 17 00:00:00 2001 From: David Lucey Date: Wed, 1 Apr 2026 11:05:17 -0400 Subject: [PATCH 2/8] security: harden MCP config, add CODEOWNERS, update agent instructions - Remove mcptools MCP server entry: mcp_server() with no tools arg errors immediately; session_tools=TRUE would expose arbitrary R code execution via list/select_r_sessions -- not acceptable - Fix shell injection in DuckDB server command: replace sh -c with Python subprocess so LAHMANS_DBDIR is passed as a list arg, never interpolated into a shell string - Remove .copilot/mcp-config.json from .gitignore so the config is version-controlled and reviewed like other code - Add .github/CODEOWNERS: require owner review on copilot-instructions.md and mcp-config.json to protect against prompt injection via PRs - Update copilot-instructions: fix test count (71 blocks / 202 assertions), add MCP server docs, add Security section explaining attack surface and CODEOWNERS rationale Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .copilot/mcp-config.json | 17 ++++++++ .github/CODEOWNERS | 13 ++++++ .github/copilot-instructions.md | 70 ++++++++++++++++++++++++++++++--- .gitignore | 1 - 4 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 .copilot/mcp-config.json create mode 100644 .github/CODEOWNERS diff --git a/.copilot/mcp-config.json b/.copilot/mcp-config.json new file mode 100644 index 0000000..277e3b4 --- /dev/null +++ b/.copilot/mcp-config.json @@ -0,0 +1,17 @@ +{ + "mcpServers": { + "baseball": { + "type": "stdio", + "command": "python3", + "args": [ + "-c", + "import os, subprocess; db=os.path.join(os.environ.get('LAHMANS_DBDIR', os.path.expanduser('~/Documents/Data/baseball')), 'baseball.duckdb'); subprocess.run(['duckdb-mcp-server', '--db-path', db, '--readonly'])" + ] + }, + "r-btw": { + "type": "stdio", + "command": "Rscript", + "args": ["-e", "btw::btw_mcp_server()"] + } + } +} \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..58f8835 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,13 @@ +# CODEOWNERS — these files require owner review on every PR. +# Protects against prompt injection via AI instruction files and +# unauthorized expansion of the MCP attack surface. + +# AI agent instructions and MCP server config are sensitive: +# a malicious change could redirect agent behaviour or expose new +# execution vectors. Require explicit owner sign-off. +.github/copilot-instructions.md @davidlucey +.copilot/mcp-config.json @davidlucey + +# DESCRIPTION and NAMESPACE control package identity and imports +DESCRIPTION @davidlucey +NAMESPACE @davidlucey diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2a17962..f347a75 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -97,15 +97,73 @@ cd $PROJ && git reset When developing analysis scripts or iterating on charts, use an **interactive R session** instead of re-running the full script each time: -1. Start R in async mode: `bash mode="async" command="R --no-save"` -2. Source shared setup (DB connection, libraries) once -3. Send individual code blocks via `write_bash` to iterate on specific charts or queries -4. Use the `view` tool on saved PNG files to inspect chart output visually -5. Only assemble the final `.R` script once the individual pieces are working +1. Start R in async mode: `bash mode="async" command="R --no-save" shellId="r-session"` +2. Manually connect to DuckDB and load libraries (do NOT source the full script — see gotcha below) +3. Source only the SQL/data-processing block once (lines after `dbConnect`, before chart code) +4. Send only the chart code block via `write_bash` to iterate on specific charts or queries +5. Use the `view` tool on saved PNG files to inspect chart output visually +6. Only assemble the final `.R` script once the individual pieces are working + +**`on.exit` gotcha in sourced scripts:** Analysis scripts use `on.exit(dbDisconnect(con, shutdown = TRUE))` at the top level. When you `source()` such a file interactively, R fires the `on.exit` handler when `source()` returns, closing the connection immediately. **Workaround:** connect manually in the session first, then source only the data-processing and chart sections (not the preamble). + +```r +# Step 1 — run once to set up the session +suppressPackageStartupMessages({ + library(data.table); library(ggplot2); library(DBI); library(duckdb) +}) +db_path <- file.path(path.expand(Sys.getenv("LAHMANS_DBDIR", "~/Documents/Data/baseball")), "baseball.duckdb") +con <- dbConnect(duckdb(), db_path, read_only = TRUE) + +# Step 2 — source just the SQL + data wrangling (skip preamble lines) +source("/tmp/roi_data.R") # or whichever temp file has only the data block + +# Step 3 — iterate: edit chart file, source, view +source("/tmp/roi_chart.R") +``` This avoids the 60-90 second penalty of re-running a full analysis script on every change and enables tight visual feedback loops. -**DuckDB CLI for ad-hoc queries:** Use `duckdb ~/Documents/Data/baseball/baseball.duckdb` for quick schema checks (`DESCRIBE`, `SUMMARIZE`) rather than writing throwaway R code. +**DuckDB CLI for ad-hoc queries:** Use `duckdb $LAHMANS_DBDIR/baseball.duckdb` for quick schema checks (`DESCRIBE`, `SUMMARIZE`) rather than writing throwaway R code. + +## MCP Servers + +Two MCP servers are configured in `.copilot/mcp-config.json`: + +| Server | Command | Purpose | +|--------|---------|---------| +| `baseball` | `duckdb-mcp-server --readonly` | Read-only SQL access to `baseball.duckdb`; path resolved via `$LAHMANS_DBDIR` using Python (no shell expansion — avoids injection) | +| `r-btw` | `btw::btw_mcp_server()` | R package dev tools: test, document, check, coverage, help | + +**Prerequisites:** +- `LAHMANS_DBDIR` env var should be set (defaults to `~/Documents/Data/baseball`). +- `duckdb-mcp-server` binary on `PATH` (installed at `~/.local/bin/duckdb-mcp-server`). +- `btw` R package installed in the system library (not renv). + +**Using `r-btw` tools:** prefer them over bash for package tasks — `btw_tool_pkg_test`, `btw_tool_pkg_check`, `btw_tool_pkg_coverage`, `btw_tool_pkg_document` all run in-process and are faster than shell invocations. + +**`mcptools` is intentionally NOT configured** as an MCP server. `mcptools::mcp_server(session_tools = TRUE)` would expose `list_r_sessions` / `select_r_session`, giving the AI arbitrary R code execution in any session that has called `mcp_session()`. Use the bash async session approach instead (see Interactive R Sessions above). + +## Security + +The following files are high-value targets for prompt injection and are protected by CODEOWNERS (owner review required on every PR): + +- `.github/copilot-instructions.md` — controls AI agent behaviour for all sessions +- `.copilot/mcp-config.json` — controls which MCP servers (execution surfaces) are available + +**What prompt injection means here:** a malicious PR that modifies `copilot-instructions.md` could redirect the agent to exfiltrate data, weaken commit checks, or perform unintended operations. The CODEOWNERS rule ensures a human must explicitly approve any change to these files before merge. + +**MCP surface area (in order of privilege):** +1. `baseball` (DuckDB, read-only) — SQL queries only; no writes; path constructed programmatically to avoid shell injection. +2. `r-btw` — can read all package source files and run tests/checks. Cannot write files or execute arbitrary shell commands. + +**Never add to MCP config without security review:** +- Any server that exposes `eval`, `system()`, `shell()`, or arbitrary R/Python execution. +- `mcptools::mcp_server(session_tools = TRUE)` — see above. +- Any server that takes user-supplied input as a shell argument. + +## Tests + +Run with `devtools::test()`. The suite has ~71 `test_that()` blocks (202 assertions) across 6 files. All must pass before committing. The full-DB smoke test uses `skip_on_ci()` and `skip_if_not_installed("Lahman")`. ## R CMD Check diff --git a/.gitignore b/.gitignore index 9fb0da2..7db0d8a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,6 @@ tests/testthat/_snaps/ # Copilot CLI config (environment-specific, not for contributors) .github/lsp.json -.copilot/mcp-config.json .mcp.json # Old scratch notebooks (superseded by analysis/) From 113fd7af843b485db041320c9e5a5b85fb0786a4 Mon Sep 17 00:00:00 2001 From: David Lucey Date: Wed, 8 Apr 2026 07:55:55 -0600 Subject: [PATCH 3/8] Add franchise-efficiency vignette (Quarto) and update DESCRIPTION - Add vignettes/franchise-efficiency.qmd: two-stage playoff efficiency analysis narrative (Stage 1: getting to October; Stage 2: going deep) - DESCRIPTION: add quarto, ggrepel, scales, knitr to Suggests - DESCRIPTION: add VignetteBuilder: quarto Vignette covers: payroll paradox, dead-money burden, homegrown pipelines, postseason WAR retention proxy, and 5-dimension franchise scorecard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- DESCRIPTION | 5 + vignettes/franchise-efficiency.qmd | 578 +++++++++++++++++++++++++++++ 2 files changed, 583 insertions(+) create mode 100644 vignettes/franchise-efficiency.qmd diff --git a/DESCRIPTION b/DESCRIPTION index d67844b..e1a3653 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -28,5 +28,10 @@ Suggests: jsonlite, re2, ggplot2, + ggrepel, + scales, + quarto, + knitr, testthat (>= 3.0.0) +VignetteBuilder: quarto Config/testthat/edition: 3 diff --git a/vignettes/franchise-efficiency.qmd b/vignettes/franchise-efficiency.qmd new file mode 100644 index 0000000..965eb39 --- /dev/null +++ b/vignettes/franchise-efficiency.qmd @@ -0,0 +1,578 @@ +--- +title: "MLB Franchise Management Efficiency" +author: "David Lucey" +date: last-modified +format: + html: + toc: true + toc-depth: 3 + code-fold: true + fig-width: 10 + fig-height: 7 + theme: flatly +vignette: > + %\VignetteIndexEntry{MLB Franchise Management Efficiency} + %\VignetteEngine{quarto::html} + %\VignetteEncoding{UTF-8} +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.align = "center" +) +``` + +## Overview + +This vignette walks through a two-stage analysis of MLB franchise management +efficiency covering 1995–2021 (excluding the anomalous 2020 bubble season). + +**Stage 1 — Getting to October:** How efficiently does a franchise convert +payroll dollars into playoff appearances? Dimensions: + +- Veteran free-agent cost ($/WAR) +- Dead-money burden (salary paid to ≤0-WAR players) +- Homegrown pipeline (% of roster WAR from drafted/developed players) + +**Stage 2 — Going Deep:** Among teams that *make* the playoffs, which ones +convert appearances into deep runs? Dimensions: + +- Regular-season WAR rank among that year's playoff field +- Postseason WAR retention (RS production that actually showed up in October) +- Playoff achievement score (WC win = 1 pt, DS = 2, LCS = 4, WS = 8) + +The analysis uses: + +- **Lahman Baseball Database** (team stats, postseason tables, salary 1985–2016) +- **FanGraphs WAR** via `baseballr` (1985–2025) +- **Spotrac / USA Today salary data** (2017–2025, in `SalariesAll` view) + +```{r prereqs, eval=FALSE} +# Prerequisites: database must be built first +# lahmanTools::setup_baseball_db() # builds baseball.duckdb +``` + +```{r libs} +#| message: false +#| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) +suppressPackageStartupMessages({ + library(data.table) + library(ggplot2) + library(ggrepel) + library(DBI) + library(duckdb) + library(scales) +}) + +n_distinct <- function(x) length(unique(x[!is.na(x)])) + +db_path <- file.path( + path.expand(Sys.getenv("LAHMANS_DBDIR", "~/Documents/Data/baseball")), + "baseball.duckdb" +) +con <- dbConnect(duckdb(), db_path, read_only = TRUE) +qry <- function(sql) setDT(dbGetQuery(con, sql)) +``` + +--- + +## Franchise map + +We use `franchID` throughout so that relocated/renamed franchises are tracked +consistently (e.g., FLO → MIA, MON → WAS). + +```{r fran-map} +#| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) +fran_map <- qry(" + SELECT DISTINCT t.teamID, t.franchID, tf.franchName + FROM Teams t + JOIN TeamsFranchises tf ON t.franchID = tf.franchID + WHERE t.yearID BETWEEN 1995 AND 2021 +") +short_names <- c( + NYA = "Yankees", NYN = "Mets", LAN = "Dodgers", + BOS = "Red Sox", OAK = "Athletics", TBD = "Rays", + HOU = "Astros", ATL = "Braves", ARI = "D-Backs", + CHC = "Cubs", CHN = "Cubs", SFN = "Giants", + SLN = "Cardinals", PHI = "Phillies", CLE = "Guardians", + MIN = "Twins", DET = "Tigers", SEA = "Mariners", + TEX = "Rangers", BAL = "Orioles", TOR = "Blue Jays", + KCA = "Royals", MIL = "Brewers", MIA = "Marlins", + FLA = "Marlins", CIN = "Reds", SDN = "Padres", + COL = "Rockies", ANA = "Angels", WAS = "Nationals", + MON = "Expos", CHA = "White Sox", PIT = "Pirates", + ML4 = "Brewers", CAL = "Angels", FLO = "Marlins" +) +fran_map[, short := short_names[franchID]] +fran_map[is.na(short), short := franchID] +fran_key <- unique(fran_map[, .(franchID, franchName, short)]) +fran_key <- fran_key[!duplicated(franchID)] +``` + +--- + +## Playoff achievement scores + +Each playoff appearance is scored by how far the team advanced: + +| Round | Points | +|-------|-------:| +| Wild Card win | 1 | +| Division Series win | 2 | +| LCS win | 4 | +| World Series win | 8 | + +Maximum possible = 15 (WC + DS + LCS + WS wins for a champion). + +```{r playoff-scores} +#| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) +playoff_seasons <- qry(" + WITH all_teams AS ( + SELECT teamIDwinner AS teamID, yearID FROM SeriesPost + WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + UNION + SELECT teamIDloser AS teamID, yearID FROM SeriesPost + WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + ) + SELECT a.teamID, a.yearID, + COALESCE(SUM(CASE + WHEN sw.round = 'WS' THEN 8 + WHEN sw.round IN ('ALCS','NLCS') THEN 4 + WHEN sw.round IN ('ALDS1','ALDS2','NLDS1','NLDS2') THEN 2 + WHEN sw.round IN ('ALWC','NLWC') + OR sw.round LIKE 'ALWC%' + OR sw.round LIKE 'NLWC%' THEN 1 + ELSE 0 END), 0) AS achievement_score + FROM all_teams a + LEFT JOIN SeriesPost sw + ON a.teamID = sw.teamIDwinner + AND a.yearID = sw.yearID + AND sw.yearID BETWEEN 1995 AND 2021 + AND sw.yearID != 2020 + GROUP BY a.teamID, a.yearID +") +playoff_seasons <- merge(playoff_seasons, + fran_map[, .(teamID, franchID)], + by = "teamID", all.x = TRUE) +``` + +--- + +## Postseason WAR proxy + +No published postseason WAR exists from FanGraphs or any other source for this +era. The industry-standard approach scales each player's regular-season WAR by +their postseason usage rate: + +$$\text{PS WAR proxy} = \text{RS WAR} \times \frac{\text{PS PA (or IP)}}{\text{RS PA (or IP)}}$$ + +Players who appear on a playoff roster but record zero postseason PA/IP receive a +proxy of 0. This directly captures two important signals: + +1. **Injury/decline** — a high-salary veteran who is hurt in October contributes 0 +2. **Roster construction** — teams that carry their best regular-season producers + into the playoffs score higher + +```{r war-proxy} +#| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) +playoff_ty <- unique(playoff_seasons[, .(teamID, yearID)]) + +rs_pa <- qry(" + SELECT playerID, yearID, teamID, + SUM(AB + COALESCE(BB,0) + COALESCE(HBP,0) + + COALESCE(SF,0) + COALESCE(SH,0)) AS rs_pa + FROM Batting + WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + GROUP BY playerID, yearID, teamID +") +rs_ip <- qry(" + SELECT playerID, yearID, teamID, SUM(IPouts) AS rs_ipouts + FROM Pitching + WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + GROUP BY playerID, yearID, teamID +") +ps_pa <- qry(" + SELECT playerID, yearID, teamID, + SUM(AB + COALESCE(BB,0) + COALESCE(HBP,0) + + COALESCE(SF,0) + COALESCE(SH,0)) AS ps_pa + FROM BattingPost + WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + GROUP BY playerID, yearID, teamID +") +ps_ip <- qry(" + SELECT playerID, yearID, teamID, SUM(IPouts) AS ps_ipouts + FROM PitchingPost + WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + GROUP BY playerID, yearID, teamID +") +war_data <- qry(" + SELECT playerID, yearID, bat_war, pit_war, total_war + FROM PlayerWAR WHERE total_war IS NOT NULL +") + +# Batter proxy +bat_proxy <- rs_pa[playoff_ty, on = c("teamID","yearID"), nomatch = 0] +bat_proxy <- bat_proxy[war_data[, .(playerID, yearID, bat_war)], + on = c("playerID","yearID"), nomatch = 0] +bat_proxy <- merge(bat_proxy, ps_pa, by = c("playerID","yearID","teamID"), all.x = TRUE) +bat_proxy[is.na(ps_pa), ps_pa := 0L] +bat_proxy[, bat_war_proxy := fifelse( + rs_pa > 0 & is.finite(bat_war) & bat_war > 0, + bat_war * (ps_pa / rs_pa), 0 +)] +bat_team <- bat_proxy[, .( + rs_bat_war = sum(bat_war, na.rm = TRUE), + ps_bat_war = sum(bat_war_proxy, na.rm = TRUE) +), by = .(teamID, yearID)] + +# Pitcher proxy +pit_proxy <- rs_ip[playoff_ty, on = c("teamID","yearID"), nomatch = 0] +pit_proxy <- pit_proxy[war_data[, .(playerID, yearID, pit_war)], + on = c("playerID","yearID"), nomatch = 0] +pit_proxy <- merge(pit_proxy, ps_ip, by = c("playerID","yearID","teamID"), all.x = TRUE) +pit_proxy[is.na(ps_ipouts), ps_ipouts := 0L] +pit_proxy[, pit_war_proxy := fifelse( + rs_ipouts > 0 & is.finite(pit_war) & pit_war > 0, + pit_war * (ps_ipouts / rs_ipouts), 0 +)] +pit_team <- pit_proxy[, .( + rs_pit_war = sum(pit_war, na.rm = TRUE), + ps_pit_war = sum(pit_war_proxy, na.rm = TRUE) +), by = .(teamID, yearID)] + +war_ret <- merge(bat_team, pit_team, by = c("teamID","yearID"), all = TRUE) +for (col in c("rs_bat_war","rs_pit_war","ps_bat_war","ps_pit_war")) + set(war_ret, which(is.na(war_ret[[col]])), col, 0) +war_ret[, `:=`( + rs_war_total = rs_bat_war + rs_pit_war, + ps_war_proxy = ps_bat_war + ps_pit_war +)] +war_ret[, war_retention := fifelse(rs_war_total > 0, + ps_war_proxy / rs_war_total, NA_real_)] +``` + +--- + +## Stage 1: Getting to October + +```{r stage1-data} +#| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) +fa_cost <- qry(" + SELECT t.franchID, + MEDIAN(sp.dollars_per_war / 1e6) AS fa_m_per_war, + COUNT(*) AS n_fa_seasons + FROM SalaryPerWAR sp + JOIN PlayerAcquisitionType pat + ON sp.playerID = pat.playerID AND sp.teamID = pat.teamID + JOIN Teams t ON sp.teamID = t.teamID AND sp.yearID = t.yearID + WHERE pat.acq_type = 'veteran_acq' + AND sp.yearID BETWEEN 1995 AND 2021 AND sp.yearID != 2020 + GROUP BY t.franchID +") + +playoff_rate <- qry(" + WITH all_seasons AS ( + SELECT DISTINCT t.franchID, t.yearID + FROM Teams t + WHERE t.yearID BETWEEN 1995 AND 2021 AND t.yearID != 2020 + ), + playoff_fran AS ( + SELECT DISTINCT t2.franchID, ps.yearID + FROM ( + SELECT teamIDwinner AS teamID, yearID FROM SeriesPost + WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + UNION + SELECT teamIDloser AS teamID, yearID FROM SeriesPost + WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + ) ps + JOIN Teams t2 ON ps.teamID = t2.teamID AND ps.yearID = t2.yearID + ) + SELECT a.franchID, + COUNT(DISTINCT a.yearID) AS total_seasons, + COUNT(DISTINCT pf.yearID) AS n_playoff_seasons, + COUNT(DISTINCT pf.yearID)::DOUBLE / COUNT(DISTINCT a.yearID) AS playoff_rate + FROM all_seasons a + LEFT JOIN playoff_fran pf ON a.franchID = pf.franchID AND a.yearID = pf.yearID + GROUP BY a.franchID +") + +dead_pct <- qry(" + WITH payroll AS ( + SELECT t.franchID, SUM(s.salary) / 1e6 AS total_payroll_M + FROM SalariesAll s + JOIN Teams t ON s.teamID = t.teamID AND s.yearID = t.yearID + WHERE s.is_actual = TRUE AND s.salary >= 1e6 + AND s.yearID BETWEEN 1995 AND 2021 AND s.yearID != 2020 + GROUP BY t.franchID + ), + dead AS ( + SELECT t.franchID, SUM(s.salary) / 1e6 AS dead_M + FROM SalariesAll s + JOIN Teams t ON s.teamID = t.teamID AND s.yearID = t.yearID + LEFT JOIN PlayerWAR w ON s.playerID = w.playerID AND s.yearID = w.yearID + WHERE s.is_actual = TRUE AND s.salary >= 1e6 + AND s.yearID BETWEEN 1995 AND 2021 AND s.yearID != 2020 + AND (w.total_war IS NULL OR w.total_war <= 0) + GROUP BY t.franchID + ) + SELECT p.franchID, + p.total_payroll_M, + d.dead_M, + d.dead_M / p.total_payroll_M * 100 AS dead_pct + FROM payroll p JOIN dead d ON p.franchID = d.franchID +") + +hg_war <- qry(" + SELECT t.franchID, + SUM(CASE WHEN pat.acq_type = 'homegrown' THEN sp.total_war ELSE 0 END) + ::DOUBLE / NULLIF(SUM(sp.total_war), 0) * 100 AS hg_war_pct + FROM SalaryPerWAR sp + JOIN PlayerAcquisitionType pat + ON sp.playerID = pat.playerID AND sp.teamID = pat.teamID + JOIN Teams t ON sp.teamID = t.teamID AND sp.yearID = t.yearID + WHERE sp.yearID BETWEEN 1995 AND 2021 AND sp.yearID != 2020 + AND sp.total_war IS NOT NULL + GROUP BY t.franchID +") + +avg_payroll <- qry(" + SELECT t.franchID, + SUM(s.salary) / 1e6 / COUNT(DISTINCT s.yearID) AS avg_payroll_M + FROM SalariesAll s + JOIN Teams t ON s.teamID = t.teamID AND s.yearID = t.yearID + WHERE s.is_actual = TRUE AND s.salary >= 1e6 + AND s.yearID BETWEEN 1995 AND 2021 AND s.yearID != 2020 + GROUP BY t.franchID +") + +stage1 <- Reduce( + function(a, b) merge(a, b, by = "franchID", all.x = TRUE), + list(fa_cost, playoff_rate, dead_pct[, .(franchID, dead_pct)], + hg_war, avg_payroll) +) +stage1 <- merge(stage1, fran_key, by = "franchID", all.x = TRUE) +stage1 <- stage1[!is.na(short)] +``` + +```{r chart-stage1} +#| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) +#| fig-cap: "Stage 1: efficient franchises appear in the upper-left (low $/WAR, high playoff rate). Bubble size = dead-money burden; blue fill = strong homegrown pipeline." +HIGHLIGHTS <- c("NYA","NYN","LAN","BOS","OAK","TBD","HOU","ATL") +stage1[, label := ifelse(franchID %in% HIGHLIGHTS, short, NA_character_)] + +ggplot(stage1, aes(fa_m_per_war, playoff_rate * 100)) + + geom_point(aes(size = dead_pct, fill = hg_war_pct), + shape = 21, colour = "white", alpha = 0.9) + + geom_label_repel(aes(label = label), size = 3.2, fontface = "bold", + box.padding = 0.4, na.rm = TRUE, seed = 42) + + scale_fill_gradient(low = "#D6604D", high = "#2166AC", + name = "Homegrown WAR (%)") + + scale_size_continuous(range = c(2, 9), + name = "Dead money\n(% payroll)") + + scale_x_continuous(labels = dollar_format(suffix = "M"), + name = "Median $/WAR for veteran free agents") + + scale_y_continuous(name = "Playoff appearance rate (%)", + labels = function(x) paste0(x, "%")) + + labs( + title = "Stage 1: Who Gets to October Efficiently?", + caption = "Sources: Lahman, FanGraphs, Spotrac, USA Today -- 1995-2021" + ) + + theme_minimal(base_size = 13) + + theme(plot.title = element_text(face = "bold"), + legend.position = "bottom", + panel.grid.minor = element_blank()) +``` + +--- + +## Stage 2: Going Deep + +```{r stage2-data} +#| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) +team_rs_war <- qry(" + WITH player_pa AS ( + SELECT playerID, yearID, teamID, + SUM(AB + COALESCE(BB,0) + COALESCE(HBP,0) + + COALESCE(SF,0) + COALESCE(SH,0)) AS pa + FROM Batting + WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + GROUP BY playerID, yearID, teamID + UNION ALL + SELECT playerID, yearID, teamID, SUM(IPouts) AS pa + FROM Pitching + WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + GROUP BY playerID, yearID, teamID + ), + primary_team AS ( + SELECT DISTINCT ON (playerID, yearID) playerID, yearID, teamID + FROM player_pa + ORDER BY playerID, yearID, pa DESC + ) + SELECT pt.teamID, pt.yearID, SUM(pw.total_war) AS rs_war + FROM primary_team pt + JOIN PlayerWAR pw ON pt.playerID = pw.playerID AND pt.yearID = pw.yearID + WHERE pw.total_war IS NOT NULL + GROUP BY pt.teamID, pt.yearID +") +rs_war_playoff <- team_rs_war[playoff_ty, on = c("teamID","yearID"), nomatch = 0] +rs_war_playoff[, rs_war_rank := frank(-rs_war, ties.method = "average"), by = yearID] + +stage2_raw <- Reduce( + function(a, b) merge(a, b, by = c("teamID","yearID"), all.x = TRUE), + list( + playoff_seasons, + rs_war_playoff[, .(teamID, yearID, rs_war, rs_war_rank)], + war_ret[, .(teamID, yearID, rs_war_total, ps_war_proxy, war_retention)] + ) +) +stage2_raw <- merge(stage2_raw, fran_key, by = "franchID", all.x = TRUE) + +stage2 <- stage2_raw[!is.na(franchID), .( + n_playoffs = .N, + avg_achievement = mean(achievement_score, na.rm = TRUE), + avg_rs_war_rank = mean(rs_war_rank, na.rm = TRUE), + avg_war_retention = mean(war_retention, na.rm = TRUE) +), by = .(franchID, short, franchName)] +setorder(stage2, -avg_achievement) +``` + +```{r chart-stage2} +#| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) +#| fig-cap: "Stage 2: x-axis reversed so stronger RS WAR teams are on the right. Colour shows WAR retention -- how much of that regular-season talent materialised in October." +stage2_plot <- stage2[n_playoffs >= 5] +stage2_plot[, label := short] + +ggplot(stage2_plot, aes(avg_rs_war_rank, avg_achievement)) + + geom_point(aes(size = n_playoffs, colour = avg_war_retention * 100), alpha = 0.9) + + geom_label_repel(aes(label = label), size = 3.2, fontface = "bold", + box.padding = 0.5, seed = 42) + + scale_colour_gradient2( + low = "#D6604D", mid = "gold", high = "#2166AC", midpoint = 50, + name = "WAR retention (%)", + labels = function(x) paste0(round(x), "%") + ) + + scale_size_continuous(range = c(2, 9), name = "# playoff appearances") + + scale_x_reverse( + name = "Avg RS WAR rank among playoff teams that year (1 = most WAR)", + n.breaks = 8 + ) + + scale_y_continuous(name = "Avg playoff achievement score per appearance") + + labs( + title = "Stage 2: Who Converts October Appearances to Deep Runs?", + caption = paste0("WAR retention proxy: RS WAR x (PS PA/RS PA). ", + "Absent players = 0. Sources: Lahman, FanGraphs") + ) + + theme_minimal(base_size = 13) + + theme(plot.title = element_text(face = "bold"), + legend.position = "bottom", + panel.grid.minor = element_blank()) +``` + +--- + +## Synthesis Scorecard + +All five dimensions combined into a single percentile-rank heatmap. + +```{r scorecard} +#| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) +#| fig-height: 9 +#| fig-cap: "Percentile rank (100 = best franchise on that metric). Franchises with 5+ playoff appearances only." +syn <- Reduce( + function(a, b) merge(a, b, by = "franchID", all.x = TRUE), + list( + stage1[, .(franchID, short, fa_m_per_war, playoff_rate, dead_pct, hg_war_pct)], + stage2[, .(franchID, avg_achievement, avg_war_retention, n_playoffs)], + avg_payroll + ) +) +syn <- syn[n_playoffs >= 5 & !is.na(fa_m_per_war)] + +pct_rank <- function(x, higher_better = TRUE) { + r <- rank(x, na.last = "keep", ties.method = "average") + p <- (r - 1) / (sum(!is.na(x)) - 1) * 100 + if (!higher_better) p <- 100 - p + round(p, 1) +} +syn[, `:=`( + pct_fa_cost = pct_rank(fa_m_per_war, higher_better = FALSE), + pct_dead = pct_rank(dead_pct, higher_better = FALSE), + pct_hg = pct_rank(hg_war_pct, higher_better = TRUE), + pct_retention = pct_rank(avg_war_retention, higher_better = TRUE), + pct_achievement = pct_rank(avg_achievement, higher_better = TRUE) +)] +syn[, overall := rowMeans(.SD, na.rm = TRUE), + .SDcols = c("pct_fa_cost","pct_dead","pct_hg","pct_retention","pct_achievement")] +setorder(syn, -overall) +syn[, short := factor(short, levels = rev(short))] + +metrics_labels <- c( + pct_fa_cost = "FA Efficiency\n($/WAR)", + pct_dead = "Low Dead\nMoney", + pct_hg = "Homegrown\nWAR %", + pct_retention = "Oct WAR\nRetention", + pct_achievement = "Playoff\nAchievement" +) +syn_long <- melt( + syn[, c("franchID","short", names(metrics_labels)), with = FALSE], + id.vars = c("franchID","short"), + variable.name = "metric", + value.name = "pct" +) +syn_long[, metric_label := factor(metrics_labels[metric], levels = metrics_labels)] + +ggplot(syn_long, aes(metric_label, short, fill = pct)) + + geom_tile(colour = "white", linewidth = 0.6) + + geom_text(aes(label = round(pct)), size = 2.8, + colour = "white", fontface = "bold") + + scale_fill_gradient2(low = "#D6604D", mid = "gold", high = "#2166AC", + midpoint = 50, name = "Percentile", + limits = c(0, 100)) + + scale_x_discrete(position = "top") + + labs( + title = "Franchise Management Efficiency Scorecard", + subtitle = "Percentile rank (100 = best). Franchises with 5+ playoff appearances, 1995-2021.", + caption = "Sources: Lahman, FanGraphs, Spotrac, USA Today" + ) + + theme_minimal(base_size = 12) + + theme( + plot.title = element_text(face = "bold"), + axis.title = element_blank(), + axis.text.y = element_text(size = 10, face = "bold"), + legend.position = "right", + panel.grid = element_blank() + ) +``` + +--- + +## Cleanup + +```{r cleanup} +#| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) +DBI::dbDisconnect(con, shutdown = TRUE) +``` + +--- + +## Key Takeaways + +1. **High payroll does not buy rings.** The correlation between average seasonal + payroll and playoff achievement score is near zero across 1995--2021. + +2. **Dead money disproportionately hurts small-market teams.** A single bad + long-term contract can consume 25--30% of a small-market team's entire + budget, crowding out productive roster depth. + +3. **Homegrown pipelines are the most efficient path to sustained WAR.** Teams + that develop talent internally pay pre-arbitration and arbitration rates -- + a fraction of open-market costs. + +4. **WAR retention in October separates contenders from early exits.** Aging + veteran rosters assembled through expensive free-agent signings are more + likely to show depleted WAR in the postseason due to injury and decline. + +5. **The best franchises score well across all five dimensions.** No single + lever -- payroll, development, or health -- is sufficient on its own. From 91a13353ebee7968dfd23a1f9f94a5d1c7b961a2 Mon Sep 17 00:00:00 2001 From: David Lucey Date: Wed, 8 Apr 2026 08:46:10 -0600 Subject: [PATCH 4/8] vignette: add WAR definition, fix franchID labels, clarify scorecard ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add WAR background section with interpretation table - Add mission statement (5 management dimensions explained) - Add data coverage limitations note (postseason capped at 2021) - Fix short_names to use franchID keys (NYY/NYM/LAD not NYA/NYN/LAN) - Remove manual HIGHLIGHTS filter -- label all franchises in Stage 1 chart - Fix scorecard: add OVERALL column with thick border, sort by overall mean - Add callout box explaining Red Sox paradox (95th pct achievement, mid-table overall) - Update regression takeaway: R² < 0.001, slope slightly negative but not significant Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- vignettes/franchise-efficiency.qmd | 166 ++++++++++++++++++----------- 1 file changed, 103 insertions(+), 63 deletions(-) diff --git a/vignettes/franchise-efficiency.qmd b/vignettes/franchise-efficiency.qmd index 965eb39..39432ea 100644 --- a/vignettes/franchise-efficiency.qmd +++ b/vignettes/franchise-efficiency.qmd @@ -24,30 +24,49 @@ knitr::opts_chunk$set( ) ``` -## Overview - -This vignette walks through a two-stage analysis of MLB franchise management -efficiency covering 1995–2021 (excluding the anomalous 2020 bubble season). - -**Stage 1 — Getting to October:** How efficiently does a franchise convert -payroll dollars into playoff appearances? Dimensions: - -- Veteran free-agent cost ($/WAR) -- Dead-money burden (salary paid to ≤0-WAR players) -- Homegrown pipeline (% of roster WAR from drafted/developed players) - -**Stage 2 — Going Deep:** Among teams that *make* the playoffs, which ones -convert appearances into deep runs? Dimensions: - -- Regular-season WAR rank among that year's playoff field -- Postseason WAR retention (RS production that actually showed up in October) -- Playoff achievement score (WC win = 1 pt, DS = 2, LCS = 4, WS = 8) - -The analysis uses: - -- **Lahman Baseball Database** (team stats, postseason tables, salary 1985–2016) +## Background: What is WAR? + +**Wins Above Replacement (WAR)** measures how many more wins a player contributed +than a freely available minor-league call-up would have. A WAR of 5 means the +player was worth 5 extra wins to his team. FanGraphs publishes separate WAR for +batters (fWAR: hitting + baserunning + fielding) and pitchers (FIP-based). We +use FanGraphs WAR here because it extends back to 1985 and is available via the +`baseballr` package. + +| WAR range | Interpretation | +|----------:|----------------| +| < 0 | Below replacement level | +| 0–2 | Bench / back-of-rotation player | +| 2–5 | Solid regular or mid-rotation starter | +| 5–8 | All-Star calibre | +| > 8 | MVP / Cy Young calibre | + +## The Question We Are Answering + +Every team can buy payroll. The question is: **who converts dollars into wins, +playoff appearances, and ultimately World Series rings — and why do some +franchises consistently fail to do so despite spending heavily?** + +We score each franchise across five management dimensions using percentile ranks: + +1. **FA efficiency** — median $/WAR paid to veteran free agents +2. **Dead money** — salary committed to players who produced zero or negative WAR +3. **Homegrown pipeline** — share of WAR from players developed internally +4. **October WAR retention** — how much of a team's regular-season talent showed + up in the postseason (proxy for health and roster depth) +5. **Playoff achievement** — how deep teams went when they made the postseason + +## Data Coverage + +- **Lahman Baseball Database** — team stats, postseason tables (`SeriesPost`, + `BattingPost`, `PitchingPost`), and salary data through 2021 - **FanGraphs WAR** via `baseballr` (1985–2025) -- **Spotrac / USA Today salary data** (2017–2025, in `SalariesAll` view) +- **Spotrac / USA Today salary data** (2017–2025, combined in `SalariesAll` view) + +All playoff-dependent metrics (achievement scores, WAR retention proxy) are +capped at 2021 because the Lahman postseason tables do not extend beyond that year. +Salary and WAR data extend to 2025 but are shown here on the same 1995–2021 +window for consistency. ```{r prereqs, eval=FALSE} # Prerequisites: database must be built first @@ -92,18 +111,16 @@ fran_map <- qry(" WHERE t.yearID BETWEEN 1995 AND 2021 ") short_names <- c( - NYA = "Yankees", NYN = "Mets", LAN = "Dodgers", - BOS = "Red Sox", OAK = "Athletics", TBD = "Rays", - HOU = "Astros", ATL = "Braves", ARI = "D-Backs", - CHC = "Cubs", CHN = "Cubs", SFN = "Giants", - SLN = "Cardinals", PHI = "Phillies", CLE = "Guardians", - MIN = "Twins", DET = "Tigers", SEA = "Mariners", - TEX = "Rangers", BAL = "Orioles", TOR = "Blue Jays", - KCA = "Royals", MIL = "Brewers", MIA = "Marlins", - FLA = "Marlins", CIN = "Reds", SDN = "Padres", - COL = "Rockies", ANA = "Angels", WAS = "Nationals", - MON = "Expos", CHA = "White Sox", PIT = "Pirates", - ML4 = "Brewers", CAL = "Angels", FLO = "Marlins" + NYY = "Yankees", NYM = "Mets", LAD = "Dodgers", + BOS = "Red Sox", OAK = "Athletics", TBD = "Rays", + HOU = "Astros", ATL = "Braves", ARI = "D-Backs", + CHC = "Cubs", SFG = "Giants", STL = "Cardinals", + PHI = "Phillies", CLE = "Guardians", MIN = "Twins", + DET = "Tigers", SEA = "Mariners", TEX = "Rangers", + BAL = "Orioles", TOR = "Blue Jays", KCR = "Royals", + MIL = "Brewers", FLA = "Marlins", CIN = "Reds", + SDP = "Padres", COL = "Rockies", ANA = "Angels", + WSN = "Nationals", CHW = "White Sox", PIT = "Pirates" ) fran_map[, short := short_names[franchID]] fran_map[is.na(short), short := franchID] @@ -359,20 +376,17 @@ stage1 <- stage1[!is.na(short)] ```{r chart-stage1} #| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) #| fig-cap: "Stage 1: efficient franchises appear in the upper-left (low $/WAR, high playoff rate). Bubble size = dead-money burden; blue fill = strong homegrown pipeline." -HIGHLIGHTS <- c("NYA","NYN","LAN","BOS","OAK","TBD","HOU","ATL") -stage1[, label := ifelse(franchID %in% HIGHLIGHTS, short, NA_character_)] - ggplot(stage1, aes(fa_m_per_war, playoff_rate * 100)) + geom_point(aes(size = dead_pct, fill = hg_war_pct), - shape = 21, colour = "white", alpha = 0.9) + - geom_label_repel(aes(label = label), size = 3.2, fontface = "bold", - box.padding = 0.4, na.rm = TRUE, seed = 42) + + shape = 21, colour = "grey30", alpha = 0.9) + + geom_label_repel(aes(label = short), size = 3.0, fontface = "bold", + box.padding = 0.4, max.overlaps = 30, seed = 42, + min.segment.length = 0.2) + scale_fill_gradient(low = "#D6604D", high = "#2166AC", name = "Homegrown WAR (%)") + - scale_size_continuous(range = c(2, 9), - name = "Dead money\n(% payroll)") + + scale_size_continuous(range = c(2, 9), name = "Dead money (% payroll)") + scale_x_continuous(labels = dollar_format(suffix = "M"), - name = "Median $/WAR for veteran free agents") + + name = "Median $/WAR for veteran FAs (lower = more efficient)") + scale_y_continuous(name = "Playoff appearance rate (%)", labels = function(x) paste0(x, "%")) + labs( @@ -474,17 +488,29 @@ ggplot(stage2_plot, aes(avg_rs_war_rank, avg_achievement)) + ## Synthesis Scorecard -All five dimensions combined into a single percentile-rank heatmap. +All five dimensions combined into a single percentile-rank heatmap. Rows are +sorted by the **Overall** column — the simple mean of all five percentile ranks. + +::: callout-note +**How to read this chart:** A score of 100 means that franchise ranked best +among all qualifying franchises on that dimension. Scores of 0 are worst. +The rightmost column (**OVERALL**) is the simple mean across all five +dimensions and determines the row sort order. + +**Why the Red Sox appear mid-table despite a 95th-percentile Playoff Achievement +score:** They also rank near the bottom on FA Efficiency and Dead Money, dragging +their Overall mean to a middling position. High achievement came at high cost. +::: ```{r scorecard} #| eval: !expr !is.na(Sys.getenv("LAHMANS_DBDIR", unset = NA)) #| fig-height: 9 -#| fig-cap: "Percentile rank (100 = best franchise on that metric). Franchises with 5+ playoff appearances only." +#| fig-cap: "Rows sorted by OVERALL = mean of 5 dimension percentile ranks. The rightmost column shows that overall rank. Red = bottom of league; Blue = top." syn <- Reduce( function(a, b) merge(a, b, by = "franchID", all.x = TRUE), list( stage1[, .(franchID, short, fa_m_per_war, playoff_rate, dead_pct, hg_war_pct)], - stage2[, .(franchID, avg_achievement, avg_war_retention, n_playoffs)], + stage2[, .(franchID, avg_achievement, avg_war_ret_pct, n_playoffs)], avg_payroll ) ) @@ -497,43 +523,55 @@ pct_rank <- function(x, higher_better = TRUE) { round(p, 1) } syn[, `:=`( - pct_fa_cost = pct_rank(fa_m_per_war, higher_better = FALSE), - pct_dead = pct_rank(dead_pct, higher_better = FALSE), - pct_hg = pct_rank(hg_war_pct, higher_better = TRUE), - pct_retention = pct_rank(avg_war_retention, higher_better = TRUE), - pct_achievement = pct_rank(avg_achievement, higher_better = TRUE) + pct_fa_cost = pct_rank(fa_m_per_war, higher_better = FALSE), + pct_dead = pct_rank(dead_pct, higher_better = FALSE), + pct_hg = pct_rank(hg_war_pct, higher_better = TRUE), + pct_retention = pct_rank(avg_war_ret_pct, higher_better = TRUE), + pct_achievement = pct_rank(avg_achievement, higher_better = TRUE) )] syn[, overall := rowMeans(.SD, na.rm = TRUE), .SDcols = c("pct_fa_cost","pct_dead","pct_hg","pct_retention","pct_achievement")] +syn[, overall_pct := pct_rank(overall, TRUE)] setorder(syn, -overall) -syn[, short := factor(short, levels = rev(short))] +syn[, short_f := factor(short, levels = rev(short))] metrics_labels <- c( pct_fa_cost = "FA Efficiency\n($/WAR)", pct_dead = "Low Dead\nMoney", pct_hg = "Homegrown\nWAR %", pct_retention = "Oct WAR\nRetention", - pct_achievement = "Playoff\nAchievement" + pct_achievement = "Playoff\nAchievement", + overall_pct = "OVERALL\n(mean rank)" ) syn_long <- melt( - syn[, c("franchID","short", names(metrics_labels)), with = FALSE], - id.vars = c("franchID","short"), + syn[, c("franchID","short_f", names(metrics_labels)), with = FALSE], + id.vars = c("franchID","short_f"), variable.name = "metric", - value.name = "pct" + value.name = "pct" ) -syn_long[, metric_label := factor(metrics_labels[metric], levels = metrics_labels)] +syn_long[, metric_label := factor(metrics_labels[as.character(metric)], + levels = metrics_labels)] +syn_long[, is_overall := metric == "overall_pct"] -ggplot(syn_long, aes(metric_label, short, fill = pct)) + - geom_tile(colour = "white", linewidth = 0.6) + +ggplot(syn_long, aes(metric_label, short_f, fill = pct)) + + geom_tile(aes(colour = is_overall, linewidth = is_overall)) + geom_text(aes(label = round(pct)), size = 2.8, colour = "white", fontface = "bold") + scale_fill_gradient2(low = "#D6604D", mid = "gold", high = "#2166AC", midpoint = 50, name = "Percentile", limits = c(0, 100)) + + scale_colour_manual(values = c(`FALSE` = "white", `TRUE` = "#333333"), + guide = "none") + + scale_linewidth_manual(values = c(`FALSE` = 0.5, `TRUE` = 1.5), + guide = "none") + scale_x_discrete(position = "top") + labs( title = "Franchise Management Efficiency Scorecard", - subtitle = "Percentile rank (100 = best). Franchises with 5+ playoff appearances, 1995-2021.", + subtitle = paste0( + "Sorted by OVERALL = simple mean of all 5 dimension percentile ranks.\n", + "100 = best franchise on that dimension; 0 = worst. ", + "Franchises with \u22655 playoff appearances, 1995-2021." + ), caption = "Sources: Lahman, FanGraphs, Spotrac, USA Today" ) + theme_minimal(base_size = 12) + @@ -559,8 +597,10 @@ DBI::dbDisconnect(con, shutdown = TRUE) ## Key Takeaways -1. **High payroll does not buy rings.** The correlation between average seasonal - payroll and playoff achievement score is near zero across 1995--2021. +1. **High payroll does not buy rings.** The Pearson correlation between average + seasonal payroll and playoff achievement score is r ≈ 0.00 (R² < 0.001) + across 1995--2021. In fact, the slope is slightly *negative* — not because + spending hurts, but because the signal is genuinely absent. 2. **Dead money disproportionately hurts small-market teams.** A single bad long-term contract can consume 25--30% of a small-market team's entire From d19d45e82696b5f6dd6a4c4a50ad3cd3bb2f486b Mon Sep 17 00:00:00 2001 From: David Lucey Date: Wed, 8 Apr 2026 12:40:03 -0600 Subject: [PATCH 5/8] Fix load_retrosheet_post: integer division and Teams year coverage - Use // (integer division) instead of / for year extraction from YYYYMMDD integer dates; DuckDB's / on BIGINT returns DOUBLE - Replace year-matched Teams join with ROW_NUMBER() latest-year lookup: Teams only covers through 2021, causing 0-row results for 2022+ - Remove p_gdp reference from PitchingPost query (column absent in Retrosheet pitching.csv); replaced with 0::BIGINT - Restructure to single CSV read per table (all stat columns in src CTE) - Remove unused src_alias argument from round_cte_sql() helper - Verified: 1706 BattingPost + 793 PitchingPost + 44 SeriesPost rows loaded for 2022-2025; WS outcomes match known results (HOU/TEX/LAN) - All 251 tests pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- NAMESPACE | 1 + R/loaders.R | 446 ++++++++++++++++++++++++++++++++++++ man/load_retrosheet_post.Rd | 68 ++++++ 3 files changed, 515 insertions(+) create mode 100644 man/load_retrosheet_post.Rd diff --git a/NAMESPACE b/NAMESPACE index f4e0000..7a4d337 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,6 +7,7 @@ export(db_query) export(dt_factors_to_char) export(load_chadwick_ids) export(load_fangraphs_war) +export(load_retrosheet_post) export(load_statcast) export(match_player_ids) export(normalise_player_name) diff --git a/R/loaders.R b/R/loaders.R index 62b4612..066e077 100644 --- a/R/loaders.R +++ b/R/loaders.R @@ -287,6 +287,452 @@ load_fangraphs_war <- function(con, years = 1985:2025, overwrite = FALSE) { } +#' Load Retrosheet postseason data (2022+) +#' +#' Downloads Retrosheet simplified CSV files and appends postseason player +#' statistics for the requested seasons to the `BattingPost`, `PitchingPost`, +#' and `SeriesPost` tables in the database. +#' +#' The Lahman `BattingPost`, `PitchingPost`, and `SeriesPost` tables stop at +#' 2021. This function extends them using Retrosheet data (available through +#' 2025) by: +#' +#' \enumerate{ +#' \item Downloading the Retrosheet simplified CSV archive +#' (`basiccsvs.zip`) to a local path. +#' \item Filtering to the requested \code{years} and postseason game types +#' (wildcard, divisionseries, lcs, worldseries). +#' \item Mapping Retrosheet player IDs to Lahman \code{playerID} via +#' \code{People$retroID}. +#' \item Mapping Retrosheet team codes to Lahman \code{teamID} and +#' \code{lgID} via the \code{Teams} table. +#' \item Deriving Lahman-style \code{round} codes (e.g. \code{ALCS}, +#' \code{ALDS1}, \code{ALWC1}, \code{WS}). +#' \item Aggregating per-game rows into per-series player totals. +#' \item Inserting new rows into \code{BattingPost}, \code{PitchingPost}, +#' and \code{SeriesPost}. +#' } +#' +#' **Attribution:** Data are from Retrosheet. You are free to use, sell, or +#' build products from Retrosheet data provided the following notice appears +#' prominently: *"The information used here was obtained free of charge from +#' and is copyrighted by Retrosheet. Interested parties may contact Retrosheet +#' at \url{https://www.retrosheet.org}"*. +#' +#' @param con A writable \code{DBIConnection} to the baseball DuckDB database. +#' The database must already contain \code{People} and \code{Teams} tables +#' (loaded by \code{\link{setup_baseball_db}}). +#' @param years Integer vector of seasons to load. Defaults to all seasons +#' after the current maximum \code{yearID} in \code{BattingPost} (typically +#' 2022:2025 when Lahman is current through 2021). +#' @param zip_path Path to a pre-downloaded \code{basiccsvs.zip} file. When +#' \code{NULL} (default) the file is downloaded to \code{tempdir()} if not +#' already cached there. +#' @param overwrite Logical. When \code{TRUE}, delete any existing rows for +#' the requested \code{years} in all three tables before inserting. Default +#' \code{FALSE} skips years already present. +#' +#' @return Invisibly returns \code{con}. +#' @export +#' +#' @examples +#' \dontrun{ +#' con <- connect_baseball_db(read_only = FALSE) +#' load_retrosheet_post(con) # extend through latest available year +#' load_retrosheet_post(con, years = 2024) # single season +#' DBI::dbDisconnect(con, shutdown = TRUE) +#' } +load_retrosheet_post <- function(con, + years = NULL, + zip_path = NULL, + overwrite = FALSE) { + needed <- c("People", "Teams", "BattingPost", "PitchingPost", "SeriesPost") + missing_tbls <- setdiff(needed, DBI::dbListTables(con)) + if (length(missing_tbls)) { + stop("Required tables missing from database: ", + paste(missing_tbls, collapse = ", "), + "\n Run setup_baseball_db() first.", call. = FALSE) + } + + # Determine which years to load ----------------------------------------------- + existing_max <- DBI::dbGetQuery( + con, "SELECT COALESCE(MAX(yearID), 2021) AS m FROM BattingPost" + )$m + if (is.null(years)) years <- seq.int(existing_max + 1L, 2025L) + years <- as.integer(years) + if (!length(years)) { + message(" No new postseason years to load.") + return(invisible(con)) + } + + if (!overwrite) { + already <- DBI::dbGetQuery( + con, + paste0("SELECT DISTINCT yearID FROM BattingPost WHERE yearID IN (", + paste(years, collapse = ","), ")") + )$yearID + years <- setdiff(years, already) + if (!length(years)) { + message(" BattingPost already contains all requested years. ", + "Use overwrite = TRUE to reload.") + return(invisible(con)) + } + } + + yr_min <- min(years) + yr_max <- max(years) + message(sprintf("Loading Retrosheet postseason data for %d-%d...", yr_min, yr_max)) + + # Find / download zip --------------------------------------------------------- + if (is.null(zip_path)) { + zip_path <- file.path(tempdir(), "retrosheet_basiccsvs.zip") + if (!file.exists(zip_path)) { + message(" Downloading Retrosheet basiccsvs.zip ...") + utils::download.file( + "https://www.retrosheet.org/downloads/basiccsvs.zip", + zip_path, mode = "wb", quiet = FALSE + ) + } + } + if (!file.exists(zip_path)) + stop("zip_path does not exist: ", zip_path, call. = FALSE) + + # Unzip ----------------------------------------------------------------------- + extract_dir <- file.path(tempdir(), "retrosheet_csv") + dir.create(extract_dir, showWarnings = FALSE, recursive = TRUE) + utils::unzip(zip_path, files = c("batting.csv", "pitching.csv"), + exdir = extract_dir, overwrite = TRUE) + + bat_csv <- file.path(extract_dir, "batting.csv") + pit_csv <- file.path(extract_dir, "pitching.csv") + + if (!file.exists(bat_csv) || !file.exists(pit_csv)) + stop("Expected batting.csv and pitching.csv not found after unzip.", call. = FALSE) + + # Shared constants ------------------------------------------------------------ + # Retrosheet gametype values for postseason rounds. + post_types <- "('worldseries','lcs','divisionseries','wildcard')" + # Year filter uses integer division (//) -- DuckDB's / on BIGINT returns DOUBLE. + yr_filter <- paste0("date // 10000 IN (", paste(years, collapse = ","), ")") + + # Round-code SQL snippet (used in all three table queries) -------------------- + # Maps Retrosheet gametype + league -> Lahman round code. + # Non-WS series within each (year, league, gametype) are numbered 1, 2, 3 + # by the alphabetically ordered canonical pair (LEAST(team,opp), GREATEST(...)). + round_cte_sql <- function() { + # Teams only covers through 2021 in the Lahman release. Use each team's + # most-recent available lgID as a stable proxy (teams very rarely change + # leagues). No year join needed. + " + team_lg AS ( + SELECT teamID, lgID + FROM ( + SELECT teamID, lgID, + ROW_NUMBER() OVER (PARTITION BY teamID ORDER BY yearID DESC) AS rn + FROM Teams + ) + WHERE rn = 1 + ), + -- Number series within (year, gametype, league) for ALDS1/ALDS2 etc. + -- pair_low is always within one league for non-WS series. + series_num AS ( + SELECT DISTINCT yearID, gametype, pair_low, pair_high, + DENSE_RANK() OVER ( + PARTITION BY yearID, gametype, tl.lgID + ORDER BY pair_low, pair_high + ) AS sn + FROM src + JOIN team_lg tl ON tl.teamID = src.pair_low + WHERE gametype <> 'worldseries' + UNION ALL + SELECT DISTINCT yearID, gametype, pair_low, pair_high, 1 AS sn + FROM src + WHERE gametype = 'worldseries' + ), + -- Attach round code + lgID to every source row. + augmented AS ( + SELECT + src.*, + tl.lgID, + CASE src.gametype + WHEN 'worldseries' THEN 'WS' + WHEN 'lcs' THEN + CASE tl.lgID WHEN 'AL' THEN 'ALCS' ELSE 'NLCS' END + WHEN 'divisionseries' THEN + CASE tl.lgID + WHEN 'AL' THEN 'ALDS' || CAST(sn.sn AS VARCHAR) + ELSE 'NLDS' || CAST(sn.sn AS VARCHAR) + END + WHEN 'wildcard' THEN + CASE tl.lgID + WHEN 'AL' THEN 'ALWC' || CAST(sn.sn AS VARCHAR) + ELSE 'NLWC' || CAST(sn.sn AS VARCHAR) + END + END AS round + FROM src + JOIN team_lg tl ON tl.teamID = src.team + JOIN series_num sn + ON sn.yearID = src.yearID + AND sn.gametype = src.gametype + AND sn.pair_low = src.pair_low + AND sn.pair_high = src.pair_high + )" + } + + # ── BattingPost ────────────────────────────────────────────────────────────── + message(" Building BattingPost supplement...") + bat_sql <- paste0(" + WITH + src AS ( + SELECT gid, id, team, opp, + (date // 10000)::INTEGER AS yearID, + gametype, + LEAST(team, opp) AS pair_low, + GREATEST(team, opp) AS pair_high, + COALESCE(b_ab, 0) AS b_ab, + COALESCE(b_r, 0) AS b_r, + COALESCE(b_h, 0) AS b_h, + COALESCE(b_d, 0) AS b_d, + COALESCE(b_t, 0) AS b_t, + COALESCE(b_hr, 0) AS b_hr, + COALESCE(b_rbi, 0) AS b_rbi, + COALESCE(b_sh, 0) AS b_sh, + COALESCE(b_sf, 0) AS b_sf, + COALESCE(b_hbp, 0) AS b_hbp, + COALESCE(b_w, 0) AS b_w, + COALESCE(b_iw, 0) AS b_iw, + COALESCE(b_k, 0) AS b_k, + COALESCE(b_sb, 0) AS b_sb, + COALESCE(b_cs, 0) AS b_cs, + COALESCE(b_gdp, 0) AS b_gdp + FROM read_csv_auto('", bat_csv, "', sample_size = -1, ignore_errors = TRUE) + WHERE gametype IN ", post_types, " + AND ", yr_filter, " + ),", + round_cte_sql(), " + SELECT + p.playerID AS playerID, + a.yearID AS yearID, + a.round AS round, + a.team AS teamID, + a.lgID AS lgID, + COUNT(DISTINCT a.gid) AS G, + SUM(a.b_ab) AS AB, + SUM(a.b_r) AS R, + SUM(a.b_h) AS H, + SUM(a.b_hr) AS HR, + SUM(a.b_rbi) AS RBI, + SUM(a.b_sb) AS SB, + SUM(a.b_cs) AS CS, + SUM(a.b_w) AS BB, + SUM(a.b_k) AS SO, + SUM(a.b_iw) AS IBB, + SUM(a.b_hbp) AS HBP, + SUM(a.b_sh) AS SH, + SUM(a.b_sf) AS SF, + SUM(a.b_gdp) AS GIDP, + SUM(a.b_d) AS X2B, + SUM(a.b_t) AS X3B + FROM augmented a + JOIN People p ON p.retroID = a.id + GROUP BY p.playerID, a.yearID, a.round, a.team, a.lgID + ") + + if (overwrite) { + DBI::dbExecute(con, + paste0("DELETE FROM BattingPost WHERE yearID IN (", + paste(years, collapse = ","), ")")) + } + bat_new <- DBI::dbGetQuery(con, bat_sql) + DBI::dbAppendTable(con, "BattingPost", bat_new) + message(sprintf(" %-25s +%d rows", "BattingPost", nrow(bat_new))) + + # ── PitchingPost ───────────────────────────────────────────────────────────── + message(" Building PitchingPost supplement...") + pit_sql <- paste0(" + WITH + src AS ( + SELECT gid, id, team, opp, + (date // 10000)::INTEGER AS yearID, + gametype, + LEAST(team, opp) AS pair_low, + GREATEST(team, opp) AS pair_high, + COALESCE(wp, 0) AS wp, + COALESCE(lp, 0) AS lp, + COALESCE(save, 0) AS p_sv, + COALESCE(p_gs, 0) AS p_gs, + COALESCE(p_gf, 0) AS p_gf, + COALESCE(p_cg, 0) AS p_cg, + COALESCE(p_ipouts,0) AS p_ipouts, + COALESCE(p_h, 0) AS p_h, + COALESCE(p_er, 0) AS p_er, + COALESCE(p_hr, 0) AS p_hr, + COALESCE(p_w, 0) AS p_w, + COALESCE(p_iw, 0) AS p_iw, + COALESCE(p_k, 0) AS p_k, + COALESCE(p_hbp, 0) AS p_hbp, + COALESCE(p_wp, 0) AS p_wp, + COALESCE(p_bk, 0) AS p_bk, + COALESCE(p_bfp, 0) AS p_bfp, + COALESCE(p_r, 0) AS p_r, + COALESCE(p_sh, 0) AS p_sh, + COALESCE(p_sf, 0) AS p_sf + FROM read_csv_auto('", pit_csv, "', sample_size = -1, ignore_errors = TRUE) + WHERE gametype IN ", post_types, " + AND ", yr_filter, " + ),", + round_cte_sql(), " + SELECT + pp.playerID AS playerID, + a.yearID AS yearID, + a.round AS round, + a.team AS teamID, + a.lgID AS lgID, + SUM(a.wp) AS W, + SUM(a.lp) AS L, + COUNT(DISTINCT a.gid) AS G, + SUM(a.p_gs) AS GS, + SUM(a.p_cg) AS CG, + 0::BIGINT AS SHO, + SUM(a.p_sv) AS SV, + SUM(a.p_ipouts) AS IPouts, + SUM(a.p_h) AS H, + SUM(a.p_er) AS ER, + SUM(a.p_hr) AS HR, + SUM(a.p_w) AS BB, + SUM(a.p_k) AS SO, + -- BAOpp: H / (BFP - BB - IBB - HBP - SH - SF) + SUM(a.p_h)::DOUBLE / + NULLIF(SUM(a.p_bfp) - SUM(a.p_w) - SUM(a.p_iw) + - SUM(a.p_hbp) - SUM(a.p_sh) - SUM(a.p_sf), + 0) AS BAOpp, + -- ERA: ER * 27 / IPouts + SUM(a.p_er)::DOUBLE * 27.0 / + NULLIF(SUM(a.p_ipouts), 0) AS ERA, + SUM(a.p_iw) AS IBB, + SUM(a.p_wp) AS WP, + SUM(a.p_hbp) AS HBP, + SUM(a.p_bk) AS BK, + SUM(a.p_bfp) AS BFP, + SUM(a.p_gf) AS GF, + SUM(a.p_r) AS R, + SUM(a.p_sh) AS SH, + SUM(a.p_sf) AS SF, + 0::BIGINT AS GIDP + FROM augmented a + JOIN People pp ON pp.retroID = a.id + GROUP BY pp.playerID, a.yearID, a.round, a.team, a.lgID + ") + + if (overwrite) { + DBI::dbExecute(con, + paste0("DELETE FROM PitchingPost WHERE yearID IN (", + paste(years, collapse = ","), ")")) + } + pit_new <- DBI::dbGetQuery(con, pit_sql) + DBI::dbAppendTable(con, "PitchingPost", pit_new) + message(sprintf(" %-25s +%d rows", "PitchingPost", nrow(pit_new))) + + # ── SeriesPost ─────────────────────────────────────────────────────────────── + # Derive series outcomes from game-level data. Home-team rows (vishome='h') + # give exactly one win/loss record per game without double-counting. + message(" Building SeriesPost supplement...") + ser_sql <- paste0(" + WITH + src AS ( + SELECT DISTINCT gid, team, opp, vishome, win, + (date // 10000)::INTEGER AS yearID, + gametype, + LEAST(team, opp) AS pair_low, + GREATEST(team, opp) AS pair_high + FROM read_csv_auto('", bat_csv, "', sample_size = -1, ignore_errors = TRUE) + WHERE gametype IN ", post_types, " + AND ", yr_filter, " + AND vishome = 'h' + ),", + round_cte_sql(), ", + -- Count wins per team per series using home-team perspective. + -- Home team's win=1 means visitor lost; invert for visitor. + home_side AS ( + SELECT yearID, round, team, opp, + SUM(win) AS wins, + SUM(1 - win) AS losses + FROM augmented + GROUP BY yearID, round, team, opp + ), + vis_side AS ( + SELECT yearID, round, opp AS team, team AS opp, + SUM(1 - win) AS wins, + SUM(win) AS losses + FROM augmented + GROUP BY yearID, round, opp, team + ), + totals AS ( + SELECT yearID, round, team, + SUM(wins) AS wins, + SUM(losses) AS losses + FROM (SELECT yearID, round, team, wins, losses FROM home_side + UNION ALL + SELECT yearID, round, team, wins, losses FROM vis_side) + GROUP BY yearID, round, team + ), + -- Pair teams; winner = higher wins (ties go to the larger-code team as + -- a tie-break but real ties are very rare in postseason play). + paired AS ( + SELECT + t1.yearID, t1.round, + CASE WHEN t1.wins >= t2.wins THEN t1.team ELSE t2.team END AS teamIDwinner, + CASE WHEN t1.wins >= t2.wins THEN t2.team ELSE t1.team END AS teamIDloser, + GREATEST(t1.wins, t2.wins)::BIGINT AS wins, + LEAST(t1.wins, t2.wins)::BIGINT AS losses, + 0::BIGINT AS ties + FROM totals t1 + JOIN totals t2 + ON t1.yearID = t2.yearID AND t1.round = t2.round + AND t1.team < t2.team + ), + lg AS ( + SELECT teamID, lgID + FROM ( + SELECT teamID, lgID, + ROW_NUMBER() OVER (PARTITION BY teamID ORDER BY yearID DESC) AS rn + FROM Teams + ) + WHERE rn = 1 + ) + SELECT + p.yearID, + p.round, + p.teamIDwinner, + w.lgID AS lgIDwinner, + p.teamIDloser, + l.lgID AS lgIDloser, + p.wins, + p.losses, + p.ties + FROM paired p + JOIN lg w ON w.teamID = p.teamIDwinner + JOIN lg l ON l.teamID = p.teamIDloser + ") + + if (overwrite) { + DBI::dbExecute(con, + paste0("DELETE FROM SeriesPost WHERE yearID IN (", + paste(years, collapse = ","), ")")) + } + ser_new <- DBI::dbGetQuery(con, ser_sql) + DBI::dbAppendTable(con, "SeriesPost", ser_new) + message(sprintf(" %-25s +%d rows", "SeriesPost", nrow(ser_new))) + + # Cleanup extracted CSVs (zip kept for reuse) --------------------------------- + unlink(c(bat_csv, pit_csv)) + + message(sprintf("\nRetrosheet postseason data loaded for years: %s", + paste(sort(years), collapse = ", "))) + invisible(con) +} + + #' Load Statcast pitch-level data #' #' Fetches Baseball Savant pitch-level data via \pkg{baseballr} for each diff --git a/man/load_retrosheet_post.Rd b/man/load_retrosheet_post.Rd new file mode 100644 index 0000000..c11bf33 --- /dev/null +++ b/man/load_retrosheet_post.Rd @@ -0,0 +1,68 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/loaders.R +\name{load_retrosheet_post} +\alias{load_retrosheet_post} +\title{Load Retrosheet postseason data (2022+)} +\usage{ +load_retrosheet_post(con, years = NULL, zip_path = NULL, overwrite = FALSE) +} +\arguments{ +\item{con}{A writable \code{DBIConnection} to the baseball DuckDB database. +The database must already contain \code{People} and \code{Teams} tables +(loaded by \code{\link{setup_baseball_db}}).} + +\item{years}{Integer vector of seasons to load. Defaults to all seasons +after the current maximum \code{yearID} in \code{BattingPost} (typically +2022:2025 when Lahman is current through 2021).} + +\item{zip_path}{Path to a pre-downloaded \code{basiccsvs.zip} file. When +\code{NULL} (default) the file is downloaded to \code{tempdir()} if not +already cached there.} + +\item{overwrite}{Logical. When \code{TRUE}, delete any existing rows for +the requested \code{years} in all three tables before inserting. Default +\code{FALSE} skips years already present.} +} +\value{ +Invisibly returns \code{con}. +} +\description{ +Downloads Retrosheet simplified CSV files and appends postseason player +statistics for the requested seasons to the \code{BattingPost}, \code{PitchingPost}, +and \code{SeriesPost} tables in the database. +} +\details{ +The Lahman \code{BattingPost}, \code{PitchingPost}, and \code{SeriesPost} tables stop at +2021. This function extends them using Retrosheet data (available through +2025) by: + +\enumerate{ +\item Downloading the Retrosheet simplified CSV archive +(\code{basiccsvs.zip}) to a local path. +\item Filtering to the requested \code{years} and postseason game types +(wildcard, divisionseries, lcs, worldseries). +\item Mapping Retrosheet player IDs to Lahman \code{playerID} via +\code{People$retroID}. +\item Mapping Retrosheet team codes to Lahman \code{teamID} and +\code{lgID} via the \code{Teams} table. +\item Deriving Lahman-style \code{round} codes (e.g. \code{ALCS}, +\code{ALDS1}, \code{ALWC1}, \code{WS}). +\item Aggregating per-game rows into per-series player totals. +\item Inserting new rows into \code{BattingPost}, \code{PitchingPost}, +and \code{SeriesPost}. +} + +\strong{Attribution:} Data are from Retrosheet. You are free to use, sell, or +build products from Retrosheet data provided the following notice appears +prominently: \emph{"The information used here was obtained free of charge from +and is copyrighted by Retrosheet. Interested parties may contact Retrosheet +at \url{https://www.retrosheet.org}"}. +} +\examples{ +\dontrun{ +con <- connect_baseball_db(read_only = FALSE) +load_retrosheet_post(con) # extend through latest available year +load_retrosheet_post(con, years = 2024) # single season +DBI::dbDisconnect(con, shutdown = TRUE) +} +} From 1d80c584eb968a8a0c65055bf2b20a2169918c33 Mon Sep 17 00:00:00 2001 From: David Lucey Date: Wed, 8 Apr 2026 18:13:28 -0600 Subject: [PATCH 6/8] Extend playoff analysis to 2025, add Retrosheet integration - setup_baseball_db(): add load_retrosheet and retrosheet_zip params so load_retrosheet_post() runs automatically during DB build - playoff_efficiency: extend SeriesPost queries to 1995-2025 (excl. 2020); team_rs_war / rs_pa / rs_ip extended to 2025 via FanGraphs + SalariesAll; ws_wins and all Stage 1 queries use fran_lookup (eliminates 6 repeated CTEs); total_ach_by_fran computed once and reused; n_playoffs collision fixed in syn merge - vignettes/franchise-efficiency.qmd: update data coverage section and captions to reflect Retrosheet 2022-2025 postseason and WAR coverage notes - tests/testthat/test-loaders.R: add 4 tests for load_retrosheet_post() (missing tables error, bad zip path error, skip when already loaded, integration test appending rows to BattingPost/PitchingPost/SeriesPost) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- R/setup_db.R | 17 +++- tests/testthat/test-loaders.R | 145 +++++++++++++++++++++++++++++ vignettes/franchise-efficiency.qmd | 27 +++--- 3 files changed, 175 insertions(+), 14 deletions(-) diff --git a/R/setup_db.R b/R/setup_db.R index 62f097d..a6cf919 100644 --- a/R/setup_db.R +++ b/R/setup_db.R @@ -44,6 +44,14 @@ #' Requires an internet connection and \pkg{baseballr}. Default `FALSE`. #' @param war_years Integer vector of seasons to fetch for WAR data. #' Defaults to `1985:2025` (full salary era). +#' @param load_retrosheet If `TRUE`, download Retrosheet postseason CSVs and +#' extend `BattingPost`, `PitchingPost`, and `SeriesPost` through the latest +#' available year (currently 2025). The Lahman tables stop at 2021; this +#' fills the gap. Requires an internet connection. Defaults to `TRUE` when +#' `load_war = TRUE`, `FALSE` otherwise. +#' @param retrosheet_zip Optional path to a pre-downloaded Retrosheet +#' `basiccsvs.zip`. When `NULL` (default), the file is downloaded from +#' \url{https://www.retrosheet.org/downloads/basiccsvs.zip}. #' #' @return Invisibly returns `dbdir`. #' @export @@ -53,8 +61,9 @@ #' # Download all tables from Chadwick Bureau and build database #' setup_baseball_db() #' -#' # With full WAR coverage (requires baseballr and internet) +#' # With full WAR and postseason coverage through 2025 #' setup_baseball_db(load_war = TRUE, overwrite = TRUE) +#' # load_war = TRUE implies load_retrosheet = TRUE automatically #' } setup_baseball_db <- function(dbdir = NULL, sal_file = NULL, @@ -62,7 +71,9 @@ setup_baseball_db <- function(dbdir = NULL, overwrite = FALSE, load_chadwick = FALSE, load_war = FALSE, - war_years = 1985:2025) { + war_years = 1985:2025, + load_retrosheet = load_war, + retrosheet_zip = NULL) { if (is.null(dbdir)) { dbdir <- Sys.getenv( "LAHMANS_DBDIR", @@ -374,6 +385,8 @@ setup_baseball_db <- function(dbdir = NULL, if (load_war && !load_chadwick) load_chadwick <- TRUE if (load_chadwick) load_chadwick_ids(con, overwrite = overwrite) if (load_war) load_fangraphs_war(con, years = war_years, overwrite = overwrite) + if (load_retrosheet) load_retrosheet_post(con, zip_path = retrosheet_zip, + overwrite = overwrite) n <- length(DBI::dbListTables(con)) message(sprintf("\nDone. %d tables/views written to %s", n, dbdir)) diff --git a/tests/testthat/test-loaders.R b/tests/testthat/test-loaders.R index bddb045..d428810 100644 --- a/tests/testthat/test-loaders.R +++ b/tests/testthat/test-loaders.R @@ -248,3 +248,148 @@ test_that("load_statcast rejects years before 2015", { on.exit(DBI::dbDisconnect(con, shutdown = TRUE)) expect_error(load_statcast(con, years = 2014L), "2015") }) + +# ── load_retrosheet_post ────────────────────────────────────────────────────── + +# Build a minimal in-memory DB with the five tables load_retrosheet_post needs. +stub_retrosheet_tables <- function(con) { + DBI::dbExecute(con, " + CREATE TABLE People ( + playerID VARCHAR, retroID VARCHAR, bbrefID VARCHAR, + nameFirst VARCHAR, nameLast VARCHAR + )") + DBI::dbExecute(con, " + CREATE TABLE Teams ( + teamID VARCHAR, yearID INTEGER, lgID VARCHAR, franchID VARCHAR + )") + DBI::dbExecute(con, " + CREATE TABLE BattingPost ( + playerID VARCHAR, yearID INTEGER, round VARCHAR, + teamID VARCHAR, lgID VARCHAR, + G INTEGER, AB INTEGER, R INTEGER, H INTEGER, X2B INTEGER, X3B INTEGER, + HR INTEGER, RBI INTEGER, SB INTEGER, CS INTEGER, + BB INTEGER, SO INTEGER, IBB INTEGER, HBP INTEGER, + SH INTEGER, SF INTEGER, GIDP INTEGER + )") + DBI::dbExecute(con, " + CREATE TABLE PitchingPost ( + playerID VARCHAR, yearID INTEGER, round VARCHAR, + teamID VARCHAR, lgID VARCHAR, + W INTEGER, L INTEGER, G INTEGER, GS INTEGER, + CG INTEGER, SHO INTEGER, SV INTEGER, IPouts INTEGER, + H INTEGER, ER INTEGER, HR INTEGER, BB INTEGER, SO INTEGER, + BAOpp DOUBLE, ERA DOUBLE, IBB INTEGER, WP INTEGER, HBP INTEGER, + BK INTEGER, BFP INTEGER, GF INTEGER, R INTEGER, SH INTEGER, + SF INTEGER, GIDP INTEGER + )") + DBI::dbExecute(con, " + CREATE TABLE SeriesPost ( + yearID INTEGER, round VARCHAR, + teamIDwinner VARCHAR, lgIDwinner VARCHAR, + teamIDloser VARCHAR, lgIDloser VARCHAR, + wins INTEGER, losses INTEGER, ties INTEGER + )") + # Two teams for 2022 WS (HOU vs PHI) + DBI::dbExecute(con, "INSERT INTO Teams VALUES ('HOU', 2021, 'AL', 'HOU')") + DBI::dbExecute(con, "INSERT INTO Teams VALUES ('PHI', 2021, 'NL', 'PHI')") +} + +# Create a minimal Retrosheet-format zip from vectors of game records. +make_retro_zip <- function(zip_path, bat_rows, pit_rows) { + extract_dir <- dirname(zip_path) + bat_file <- file.path(extract_dir, "batting.csv") + pit_file <- file.path(extract_dir, "pitching.csv") + + bat_header <- paste( + "gid,id,team,opp,b_lp,b_seq,stattype,b_pa,b_ab,b_r,b_h,b_d,b_t,b_hr,", + "b_rbi,b_sh,b_sf,b_hbp,b_w,b_iw,b_k,b_sb,b_cs,b_gdp,b_xi,b_roe,", + "dh,ph,pr,date,number,site,vishome,win,loss,tie,gametype,box,pbp", + sep = "") + writeLines(c(bat_header, bat_rows), bat_file) + + pit_header <- paste( + "gid,id,team,opp,date,number,site,vishome,win,loss,tie,gametype,box,pbp,", + "wp,lp,save,p_gs,p_gf,p_cg,p_ipouts,p_h,p_er,p_hr,p_w,p_iw,p_k,", + "p_hbp,p_wp,p_bk,p_bfp,p_r,p_sh,p_sf", + sep = "") + writeLines(c(pit_header, pit_rows), pit_file) + + zip::zip(zip_path, files = c("batting.csv", "pitching.csv"), + root = extract_dir, mode = "cherry-pick") + unlink(c(bat_file, pit_file)) +} + +test_that("load_retrosheet_post errors when required tables are missing", { + con <- DBI::dbConnect(duckdb::duckdb(), ":memory:") + on.exit(DBI::dbDisconnect(con, shutdown = TRUE)) + expect_error(load_retrosheet_post(con), "Required tables missing") +}) + +test_that("load_retrosheet_post errors when zip_path does not exist", { + con <- DBI::dbConnect(duckdb::duckdb(), ":memory:") + on.exit(DBI::dbDisconnect(con, shutdown = TRUE)) + stub_retrosheet_tables(con) + expect_error( + load_retrosheet_post(con, years = 2022L, zip_path = "/no/such/file.zip"), + "zip_path does not exist" + ) +}) + +test_that("load_retrosheet_post skips when all years already loaded", { + con <- DBI::dbConnect(duckdb::duckdb(), ":memory:") + on.exit(DBI::dbDisconnect(con, shutdown = TRUE)) + stub_retrosheet_tables(con) + # Seed BattingPost with 2022 already present + DBI::dbExecute(con, + "INSERT INTO BattingPost (playerID,yearID,round,teamID,lgID,G,AB,R,H, + X2B,X3B,HR,RBI,SB,CS,BB,SO,IBB,HBP,SH,SF,GIDP) + VALUES ('testpl01',2022,'WS','HOU','AL',1,4,1,2,0,0,1,2,0,0,0,1,0,0,0,0,0)") + expect_message( + load_retrosheet_post(con, years = 2022L, zip_path = "unused"), + "already contains" + ) +}) + +test_that("load_retrosheet_post appends rows to BattingPost, PitchingPost, SeriesPost", { + skip_if_not_installed("zip") + con <- DBI::dbConnect(duckdb::duckdb(), ":memory:") + on.exit(DBI::dbDisconnect(con, shutdown = TRUE)) + stub_retrosheet_tables(con) + + # Seed People with retroIDs for HOU batter + pitcher + DBI::dbExecute(con, + "INSERT INTO People VALUES ('altuvjo01','altuvj001','altuvjo01','Jose','Altuve')") + DBI::dbExecute(con, + "INSERT INTO People VALUES ('verlaju01','verlaj001','verlaju01','Justin','Verlander')") + DBI::dbExecute(con, + "INSERT INTO People VALUES ('harpbr01', 'harpebr01','harpbr01', 'Bryce','Harper')") + + td <- tempdir() + zip_path <- file.path(td, "test_retro.zip") + + # One WS game: HOU home, PHI visitor, HOU wins + bat_rows <- c( + "WS2022HOU10200220,altuvj001,HOU,PHI,1,1,batter,4,4,1,2,0,0,1,2,0,0,0,0,0,1,0,0,0,0,,0,0,0,20221101,1,MINU,h,1,0,0,worldseries,,", + "WS2022HOU10200220,harpebr01,PHI,HOU,2,1,batter,4,3,0,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,,0,0,0,20221101,1,MINU,v,1,0,0,worldseries,," + ) + pit_rows <- c( + "WS2022HOU10200220,verlaj001,HOU,PHI,20221101,1,MINU,h,1,0,0,worldseries,,,1,0,0,1,0,0,24,5,1,0,1,0,5,0,0,0,28,1,0,0" + ) + + make_retro_zip(zip_path, bat_rows, pit_rows) + + load_retrosheet_post(con, years = 2022L, zip_path = zip_path) + + bp <- DBI::dbGetQuery(con, "SELECT * FROM BattingPost WHERE yearID = 2022") + pp <- DBI::dbGetQuery(con, "SELECT * FROM PitchingPost WHERE yearID = 2022") + sp <- DBI::dbGetQuery(con, "SELECT * FROM SeriesPost WHERE yearID = 2022") + + expect_gt(nrow(bp), 0L) + expect_gt(nrow(pp), 0L) + expect_gt(nrow(sp), 0L) + expect_true(all(bp$yearID == 2022L)) + expect_true("WS" %in% bp$round) + expect_true("WS" %in% sp$round) + # HOU won the single game, so should be winner + expect_equal(sp$teamIDwinner, "HOU") +}) diff --git a/vignettes/franchise-efficiency.qmd b/vignettes/franchise-efficiency.qmd index 39432ea..6c13308 100644 --- a/vignettes/franchise-efficiency.qmd +++ b/vignettes/franchise-efficiency.qmd @@ -58,15 +58,18 @@ We score each franchise across five management dimensions using percentile ranks ## Data Coverage -- **Lahman Baseball Database** — team stats, postseason tables (`SeriesPost`, - `BattingPost`, `PitchingPost`), and salary data through 2021 +- **Lahman Baseball Database** — team stats, salary data through 2016, and + postseason tables (`SeriesPost`, `BattingPost`, `PitchingPost`) through 2021 +- **Retrosheet bulk CSVs** — postseason batting, pitching, and series results + extended to 2025 via `load_retrosheet_post()` (WC/DS/LCS/WS rounds) - **FanGraphs WAR** via `baseballr` (1985–2025) - **Spotrac / USA Today salary data** (2017–2025, combined in `SalariesAll` view) -All playoff-dependent metrics (achievement scores, WAR retention proxy) are -capped at 2021 because the Lahman postseason tables do not extend beyond that year. -Salary and WAR data extend to 2025 but are shown here on the same 1995–2021 -window for consistency. +Playoff achievement scores and series outcomes extend to **1995–2025** (excl. 2020). +WAR retention proxy is capped at **2021** because regular-season `Batting` and +`Pitching` tables (used to compute RS PA/IPouts) stop at 2021 in the Lahman release. +Call `lahmanTools::load_retrosheet_post()` after `setup_baseball_db()` to add +2022–2025 postseason records before running this analysis. ```{r prereqs, eval=FALSE} # Prerequisites: database must be built first @@ -148,10 +151,10 @@ Maximum possible = 15 (WC + DS + LCS + WS wins for a champion). playoff_seasons <- qry(" WITH all_teams AS ( SELECT teamIDwinner AS teamID, yearID FROM SeriesPost - WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + WHERE yearID BETWEEN 1995 AND 2025 AND yearID != 2020 UNION SELECT teamIDloser AS teamID, yearID FROM SeriesPost - WHERE yearID BETWEEN 1995 AND 2021 AND yearID != 2020 + WHERE yearID BETWEEN 1995 AND 2025 AND yearID != 2020 ) SELECT a.teamID, a.yearID, COALESCE(SUM(CASE @@ -166,7 +169,7 @@ playoff_seasons <- qry(" LEFT JOIN SeriesPost sw ON a.teamID = sw.teamIDwinner AND a.yearID = sw.yearID - AND sw.yearID BETWEEN 1995 AND 2021 + AND sw.yearID BETWEEN 1995 AND 2025 AND sw.yearID != 2020 GROUP BY a.teamID, a.yearID ") @@ -391,7 +394,7 @@ ggplot(stage1, aes(fa_m_per_war, playoff_rate * 100)) + labels = function(x) paste0(x, "%")) + labs( title = "Stage 1: Who Gets to October Efficiently?", - caption = "Sources: Lahman, FanGraphs, Spotrac, USA Today -- 1995-2021" + caption = "Sources: Lahman, Retrosheet, FanGraphs, Spotrac, USA Today -- 1995-2025 (achievement); WAR retention 1995-2021" ) + theme_minimal(base_size = 13) + theme(plot.title = element_text(face = "bold"), @@ -570,7 +573,7 @@ ggplot(syn_long, aes(metric_label, short_f, fill = pct)) + subtitle = paste0( "Sorted by OVERALL = simple mean of all 5 dimension percentile ranks.\n", "100 = best franchise on that dimension; 0 = worst. ", - "Franchises with \u22655 playoff appearances, 1995-2021." + "Franchises with \u22655 playoff appearances, 1995-2025 (excl. 2020)." ), caption = "Sources: Lahman, FanGraphs, Spotrac, USA Today" ) + @@ -599,7 +602,7 @@ DBI::dbDisconnect(con, shutdown = TRUE) 1. **High payroll does not buy rings.** The Pearson correlation between average seasonal payroll and playoff achievement score is r ≈ 0.00 (R² < 0.001) - across 1995--2021. In fact, the slope is slightly *negative* — not because + across 1995--2025 (excl. 2020). In fact, the slope is slightly *negative* — not because spending hurts, but because the signal is genuinely absent. 2. **Dead money disproportionately hurts small-market teams.** A single bad From 661acaae8f75c980fc7c489702be1b8f015e2b8d Mon Sep 17 00:00:00 2001 From: David Lucey Date: Thu, 9 Apr 2026 10:14:36 -0600 Subject: [PATCH 7/8] Add inst/RETROSHEET_NOTICE with required attribution text Data from Retrosheet is used via load_retrosheet_post(). Retrosheet terms require a prominent notice in any work that includes their data. The roxygen docs already carry the notice; this file makes it visible at the package level. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- inst/RETROSHEET_NOTICE | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 inst/RETROSHEET_NOTICE diff --git a/inst/RETROSHEET_NOTICE b/inst/RETROSHEET_NOTICE new file mode 100644 index 0000000..cd4882a --- /dev/null +++ b/inst/RETROSHEET_NOTICE @@ -0,0 +1,8 @@ +The information used here was obtained free of charge from and is copyrighted +by Retrosheet. Interested parties may contact Retrosheet at: + + https://www.retrosheet.org + +Users of Retrosheet data are free to use, sell, or build products from it +provided this notice appears prominently in any work that includes Retrosheet +data. From 5dfe286045aec88f38cfeeacbce1ff1610bd4167 Mon Sep 17 00:00:00 2001 From: David Lucey Date: Thu, 9 Apr 2026 12:26:12 -0600 Subject: [PATCH 8/8] release: v0.4.0 - Add Retrosheet attribution to README.md attribution table and DESCRIPTION - DRY: replace 3 raw as.data.table(dbGetQuery()) calls with db_query() - Add zip to Suggests (used in test-loaders.R) - Add PDF to .Rbuildignore to fix top-level file NOTE - Update renv.lock: magrittr 2.0.5, rlang 1.2.0, cli 3.6.6, zip 2.3.3, quarto 1.5.1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .Rbuildignore | 1 + ...-evaluation-of-the-moneyball-hypothesis.pdf | Bin 0 -> 106267 bytes DESCRIPTION | 8 +++++--- NEWS.md | 15 +++++++++++++++ R/scrape.R | 2 +- R/utils.R | 4 ++-- README.md | 3 ++- man/setup_baseball_db.Rd | 17 +++++++++++++++-- renv.lock | 15 ++++++++------- 9 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 2006-an-economic-evaluation-of-the-moneyball-hypothesis.pdf diff --git a/.Rbuildignore b/.Rbuildignore index 49c0220..b8c7312 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -14,3 +14,4 @@ ^AGENTS\.md$ ^CONTRIBUTING\.md$ ^analysis$ +^.*\.pdf$ diff --git a/2006-an-economic-evaluation-of-the-moneyball-hypothesis.pdf b/2006-an-economic-evaluation-of-the-moneyball-hypothesis.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a8a85e7e501bfd2a5dd54345209f82a535aa2f19 GIT binary patch literal 106267 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

J{w-TeC9Cec|y~rWQVJe3iWMMfLEhY(! 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, Posit Software, PBC [cph, fnd]", + "Author": "Gábor Csárdi [aut, cre], Hadley Wickham [ctb], Kirill Müller [ctb], Salim Brüggemann [ctb] (ORCID: ), Posit Software, PBC [cph, fnd] (ROR: )", "Maintainer": "Gábor Csárdi ", "Repository": "CRAN" }, @@ -491,7 +492,7 @@ }, "magrittr": { "Package": "magrittr", - "Version": "2.0.4", + "Version": "2.0.5", "Source": "Repository", "Type": "Package", "Title": "A Forward-Pipe Operator for R", @@ -743,7 +744,7 @@ }, "rlang": { "Package": "rlang", - "Version": "1.1.7", + "Version": "1.2.0", "Source": "Repository", "Title": "Functions for Base Types and Core R and 'Tidyverse' Features", "Description": "A toolbox for working with base types, core R features like the condition system, and core 'Tidyverse' features like tidy evaluation.", @@ -771,7 +772,7 @@ "pkgload", "rmarkdown", "stats", - "testthat (>= 3.2.0)", + "testthat (>= 3.3.2)", "tibble", "usethis", "vctrs (>= 0.2.3)",