The pipeline pulls six macroeconomic series from FRED, scores the federal funds rate against four competing monetary rules, and writes both the interactive charts and the static fallbacks that appear in the essay. One command runs the analysis; one more renders the figures.

What the code does

This is the analysis pipeline behind the essay Inheriting Discretion: What Monetary Rules Prescribe for the Warsh Fed.

The project is deliberately small: roughly 800 lines split across five R scripts and one Python file. The R side, driven by run_all.R, fetches the data and computes every rule prescription. The Python side draws the charts. The four rules under test are the Taylor rule under two neutral-rate assumptions, Meltzer's k-percent money-growth rule, and Selgin's productivity norm.

The pipeline

Stage Script Does Produces
1 R/01_data.R FRED pull of six series, conversion to quarterly df_raw.rds
2 R/02_rules.R Output gap, four rule prescriptions, deviations df_rules.rds
3 R/03_export.R Flatten to CSV, write prose key numbers rules_export.csv
4 python/charts.py Chart.js widgets and matplotlib PNG fallbacks output/widgets/, output/figures/

run_all.R sources the three R scripts in order. The chart generator runs separately against the exported CSV, which keeps the heavy plotting dependencies out of the analysis environment.

Walkthrough

The analytical core is a single stage. After 01_data.R returns a clean quarterly frame, 02_rules.R writes all four rules into that frame in one pass.

`R/02_rules.R`: four monetary rules in one mutate chain
# ── Taylor Rule (both r* variants) ───────────────────────────────────────────
# r = r* + π + 0.5*(π - π*) + 0.5*y_gap

df <- df |>
  mutate(
    taylor_base = pmax(R_STAR_BASE + PCEPILFE + 0.5 * (PCEPILFE - PI_STAR) + 0.5 * output_gap, 0),
    taylor_orig = pmax(R_STAR_ORIG + PCEPILFE + 0.5 * (PCEPILFE - PI_STAR) + 0.5 * output_gap, 0),
    taylor_dev_base = taylor_base - FEDFUNDS,
    taylor_dev_orig = taylor_orig - FEDFUNDS
  )

# ── NGDP level path (4% trend from 2019 Q4) ──────────────────────────────────

gdp_base <- df |> dplyr::filter(date == NGDP_BASE_DATE) |> dplyr::pull(GDP)

df <- df |>
  mutate(
    quarters_since_base = as.numeric(difftime(date, NGDP_BASE_DATE, units = "days")) / 91.25,
    ngdp_trend          = gdp_base * (1 + NGDP_TREND_RATE / 4) ^ quarters_since_base,
    ngdp_gap            = 100 * (GDP - ngdp_trend) / ngdp_trend
  )

# ── Meltzer k-percent rule (M2 YoY growth vs. 4% benchmark) ──────────────────

df <- df |>
  arrange(date) |>
  mutate(
    m2_yoy  = 100 * (M2SL / lag(M2SL, 4) - 1),
    k_target = K_PERCENT,
    m2_dev   = m2_yoy - k_target
  )

# ── Selgin productivity norm (NGDP growth vs. potential GDP growth) ───────────

df <- df |>
  mutate(
    gdppot_growth = 100 * (GDPPOT / lag(GDPPOT, 4) - 1),
    ngdp_growth   = 100 * (GDP    / lag(GDP,    4) - 1),
    selgin_dev    = ngdp_growth - gdppot_growth
  )

Four rules with incompatible theories live in one frame, each reduced to a prescription and a deviation from the actual rate. The two Taylor lines share a formula and differ only in the neutral rate R_STAR_BASE against R_STAR_ORIG, which isolates how much of the headline gap is an assumption about r* rather than a reading of the data. The pmax(..., 0) floor enforces the zero lower bound on the level rules. The k-percent and productivity-norm rules target growth rates instead of a level, so they enter as year-over-year deviations. Holding all four to the same data is what lets the essay ask which rule the Fed could have claimed to follow, and answer that the choice of rule is itself discretionary.

The NGDP trend hides a small piece of arithmetic worth isolating.

`R/02_rules.R`: compounding a quarterly trend from a fixed base date
quarters_since_base = as.numeric(difftime(date, NGDP_BASE_DATE, units = "days")) / 91.25,
ngdp_trend          = gdp_base * (1 + NGDP_TREND_RATE / 4) ^ quarters_since_base,

The level path needs an exponent measured in quarters, but the dates arrive as calendar days. Dividing the day count by 91.25, the mean length of a quarter, converts the gap to fractional quarters, which then compounds the quarterly trend rate cleanly from the 2019 Q4 anchor. The fractional exponent keeps the path exact even when a publication date does not fall on a quarter boundary.

The last stage worth a look is cosmetic in purpose and structural in design. The Python chart generator does not invent its own styling; it mirrors the helper functions on the R side so the two languages emit the same markup.

`python/charts.py`: stat-card helper that defers theming to the page
def stat_card(label, value):
    return (
        '<div style="background:var(--bg-soft);border:1px solid var(--border);'
        'border-radius:6px;padding:8px 12px;">'
        f'<div style="font-size:0.68rem;color:var(--muted);text-transform:uppercase;'
        f'letter-spacing:.05em;margin-bottom:2px;">{label}</div>'
        f'<div style="font-size:1.05rem;font-weight:600;color:var(--text);">{value}</div>'
        '</div>'
    )

The card hardcodes no colors. Every visible value resolves to a CSS custom property: --bg-soft, --border, --muted, --text. The site owns those variables, so a light or dark theme switch repaints the figure with no regeneration and no second copy of the markup. The function is a line-for-line twin of stat_card_html in the R figure script, which is what keeps the interactive widgets and the static exports visually identical.1

Reproducing it

The pipeline needs a FRED API key and a recent R installation. Rscript run_all.R fetches the data, computes the rules, and writes the export CSV; python python/charts.py then renders the widgets and PNGs. The data vintage is locked to 2018 Q1 through 2026 Q1 so the figures in the essay remain reproducible against the numbers quoted in the text.


  1. Macroeconomic series are drawn from the Federal Reserve Economic Data (FRED) database maintained by the Federal Reserve Bank of St. Louis, with potential-output estimates from the Congressional Budget Office.