"""m2-and-inflation.py — how M2 growth and CPI inflation move together, by period and by lag. Input : datasets/m2-and-inflation.csv (FRED M2SL billions USD s.a. monthly, CPIAUCSL index), retrieved 2026-09-05 via the public fredgraph CSV. Re-download with --download. Method: m2_yoy and cpi_yoy are 12-month percent changes. Pearson correlation of cpi_yoy with m2_yoy lagged k months, k = 0..36, over the whole sample and three sub-periods. Also the peak of each series in the 2020s. Correlation is not causation and this script does not test causation. Run : python code/m2-and-inflation.py [--download] Needs : Python 3.13, pandas 3.0.2. """ import io import sys import urllib.request import pandas as pd CSV = "datasets/m2-and-inflation.csv" def download(): frames = [] for sid in ("M2SL", "CPIAUCSL"): req = urllib.request.Request(f"https://fred.stlouisfed.org/graph/fredgraph.csv?id={sid}", headers={"User-Agent": "prism-data-lab/1.0"}) txt = urllib.request.urlopen(req, timeout=60).read().decode() frames.append(pd.read_csv(io.StringIO(txt), na_values=".", index_col=0).iloc[:, 0].rename(sid)) df = pd.concat(frames, axis=1) df.index.name = "date" df[df.index >= "1959-01-01"].to_csv(CSV) def main(): if "--download" in sys.argv: download() df = pd.read_csv(CSV, parse_dates=["date"], index_col="date") df["m2_yoy"] = 100 * (df["M2SL"] / df["M2SL"].shift(12) - 1) df["cpi_yoy"] = 100 * (df["CPIAUCSL"] / df["CPIAUCSL"].shift(12) - 1) d = df.dropna() periods = {"whole sample": d, "1960-1989": d.loc["1960":"1989"], "1990-2019": d.loc["1990":"2019"], "2020-2026": d.loc["2020":]} print("| period | months | corr at lag 0 | best lag (months) | corr at best lag | corr at lag 24 |") print("|---|---|---|---|---|---|") for name, p in periods.items(): cors = {} for k in range(0, 37): cors[k] = p["cpi_yoy"].corr(d["m2_yoy"].shift(k).reindex(p.index)) best = max(cors, key=cors.get) print(f"| {name} | {len(p)} | {cors[0]:.2f} | {best} | {cors[best]:.2f} | {cors[24]:.2f} |") print("\n| series | peak y/y in 2020s | month | trough y/y in 2020s | month |") print("|---|---|---|---|---|") for col, lab in (("m2_yoy", "M2 growth"), ("cpi_yoy", "CPI inflation")): s = d.loc["2020":, col] print(f"| {lab} | {s.max():.1f}% | {s.idxmax():%Y-%m} | {s.min():.1f}% | {s.idxmin():%Y-%m} |") s = d["m2_yoy"] negs = s[s < 0] print(f"\nM2 y/y below zero: {len(negs)} months; first {negs.index.min():%Y-%m}; last {negs.index.max():%Y-%m}; " f"sample {d.index[0]:%Y-%m} to {d.index[-1]:%Y-%m}") last = d.iloc[-1] print(f"latest {d.index[-1]:%Y-%m}: M2 {last['M2SL']:.1f} bn, M2 y/y {last['m2_yoy']:.2f}%, CPI y/y {last['cpi_yoy']:.2f}%") hi = s[s > 10] print(f"months with M2 y/y above 10%: {len(hi)}; years: {sorted(set(hi.index.year))}") for y in (1970, 1980, 1990, 2000, 2010, 2020): p = d.loc[str(y):str(y + 9)] if len(p): print(f" {y}s: mean M2 y/y {p['m2_yoy'].mean():.1f}%, mean CPI y/y {p['cpi_yoy'].mean():.1f}%") if __name__ == "__main__": main()