🇬🇧 English · 🇪🇸 Español
Quickstart Guide
Start with the local-projection example below, then choose a model or estimator. The numerical core uses NumPy, SciPy, pandas and Matplotlib, with no compiled extension of its own. A local workstation installation is the supported target:
Browser execution under Pyodide is best effort. The runtime guide explains file-format, network and compute limits; the notebook learning paths provide longer worked examples.
1. Local Projections (LP) with 1-Line Visualization
Estimate impulse response functions directly via Jordà (2005) predictive regressions:
import numpy as np
import pandas as pd
from puremacro.lp import lp_hac
# 1. Generate synthetic data: response of output to a policy shock
rng = np.random.default_rng(42)
T = 200
shock = rng.standard_normal(T)
gdp = np.cumsum(0.7 * shock + 0.3 * rng.standard_normal(T))
df = pd.DataFrame({"gdp": gdp, "shock": shock})
# 2. Fit Local Projection up to horizon 12 with 4 lags of controls
res = lp_hac(df, y="gdp", x="shock", horizon=12, lags=4, ci=0.90)
# 3. View summary & plot IRF with confidence bands
print(res.summary())
res.plot(title="Response of GDP to Structural Shock")
# 4. Export table directly to LaTeX or Typst for your paper
print(res.to_latex())
print(res.to_typst())
2. Structural VAR (SVAR) with Bootstrap Bands
Estimate a VAR and identify structural shocks via Cholesky, Sign Restrictions, or Proxy Instruments:
from puremacro.var.identify import cholesky_svar
# (T, 3) macro system: [Output, Inflation, Interest Rate]
Y = rng.standard_normal((200, 3)).cumsum(axis=0)
# Estimate VAR(2) and compute impulse responses with 90% bootstrap bands
res_svar = cholesky_svar(Y, p=2, horizon=16, n_boot=500, ci=0.90, seed=42)
# Inspect summary & plot impulse response of variable 0 to shock 0
print(res_svar.summary())
res_svar.plot(target_idx=0, shock_idx=0, title="Output Response to Monetary Shock")
# Export to tidy DataFrame
df_irf = res_svar.to_frame(target_idx=0, shock_idx=0)
3. Modern Staggered Difference-in-Differences
Estimate heterogeneity-robust treatment effects under staggered adoption without negative weighting issues:
import numpy as np
import pandas as pd
from puremacro.did import callaway_santanna
# Staggered adoption: 60 counties observed 2000-2011, cohorts first treated in
# 2005 and 2008 plus a never-treated group (replace with your own panel)
rng = np.random.default_rng(0)
rows = []
for county in range(60):
g = {0: 2005, 1: 2008, 2: np.nan}[county % 3]
for year in range(2000, 2012):
effect = 3.0 if (not np.isnan(g) and year >= g) else 0.0
rows.append({"county_id": county, "year": year, "first_treated_year": g,
"employment": 100 + 0.5 * (year - 2000) + effect + rng.standard_normal()})
panel_df = pd.DataFrame(rows)
# Callaway & Sant'Anna (2021) group-time ATTs
res_did = callaway_santanna(
panel_df,
unit="county_id",
time="year",
outcome="employment",
treat_time="first_treated_year",
control="never_treated",
n_boot=200,
ci=0.95,
)
print(res_did.summary())
# Dynamic event-study profile relative to adoption timing
print(res_did.att_event_study.head())
print(res_did.to_markdown())
4. Sequence-Space HANK Model
Solve an incomplete-markets New Keynesian model with uninsurable idiosyncratic income risk using sequence Jacobians (Auclert et al. 2021):
from puremacro.models import solve_hank_sequence_space
# Solve GE equilibrium transition path to a 25 bps rate hike
res_hank = solve_hank_sequence_space(T=40, beta=0.985, phi_pi=1.5, kappa=0.1)
print(res_hank.summary())
# Inspect output and inflation impulse responses
print("Peak output drop:", res_hank.irf_output.min())
print("Bottom-decile MPC:", res_hank.mpc_distribution["Decile 1"])
5. Mixed-Frequency GDP Nowcasting
Track quarterly GDP growth in real time from monthly indicators with ragged edges and missing releases:
import numpy as np
import pandas as pd
from puremacro.nowcast import nowcast_gdp
# Ten years of six monthly indicators driven by one common factor, and the
# quarterly GDP history; the last month of the current quarter is only
# partly published (a ragged edge)
rng = np.random.default_rng(1)
months = pd.date_range("2016-01-31", periods=120, freq="ME")
factor = np.cumsum(rng.standard_normal(120)) * 0.3
monthly_indicators_df = pd.DataFrame(
np.outer(factor, rng.uniform(0.5, 1.5, 6)) + 0.5 * rng.standard_normal((120, 6)),
index=months, columns=["ip", "retail", "orders", "hours", "pmi", "exports"],
)
monthly_indicators_df.iloc[-1, 3:] = np.nan # not yet released
quarters = pd.period_range("2016Q1", periods=39, freq="Q")
historical_gdp_series = pd.Series(
factor.reshape(-1, 3).mean(axis=1)[:39] + 0.2 * rng.standard_normal(39), index=quarters, name="gdp",
)
res_nowcast = nowcast_gdp(monthly_indicators_df, historical_gdp_series, n_factors=2)
print(res_nowcast.summary())
print(res_nowcast.to_frame().tail())
6. Factor-Augmented VAR (FAVAR)
Extract latent factors from high-dimensional informational panels and project policy responses back to all individual series (Bernanke, Boivin & Eliasz 2005):
import numpy as np
import pandas as pd
from puremacro.var import favar
# A (T x N) informational panel and the policy rate, simulated for the example
rng = np.random.default_rng(2)
T = 240
f = np.cumsum(rng.standard_normal(T)) * 0.1
names = ["Industrial_Production", "CPI", "Employment"] + [f"x{i}" for i in range(9)]
panel_macro_df = pd.DataFrame(
np.outer(f, rng.uniform(0.5, 1.5, len(names))) + 0.3 * rng.standard_normal((T, len(names))),
columns=names,
)
policy_rate_series = pd.Series(0.5 * f + 0.2 * rng.standard_normal(T), name="policy_rate")
favar_res = favar(
panel_macro_df,
policy_rate_series,
n_factors=3,
p=2,
horizon=20,
ci=0.90,
n_boot=50,
)
print(favar_res.summary())
# Plot responses for specific macroeconomic variables
favar_res.plot(variables=["Industrial_Production", "CPI", "Employment"])
7. DSGE Higher-Order Approximation & Dynare Parity
Solve nonlinear DSGE models to 1st or 2nd order with Kim et al. (2008) pruning, cross-derivatives, and Dynare oo_.dr compatibility:
from puremacro.dsge import load_mod
# 1. A Dynare .mod file: a path, or (as here) the source text itself
rbc_mod = """
var c k a;
varexo eps;
parameters alpha beta delta gamma rho;
alpha = 0.30;
beta = 0.99;
delta = 0.025;
gamma = 1.0;
rho = 0.80;
model;
c^(-gamma) = beta * c(+1)^(-gamma) * (alpha * exp(a(+1)) * k^(alpha - 1.0) + 1.0 - delta);
k = exp(a) * k(-1)^alpha - c + (1.0 - delta) * k(-1);
a = rho * a(-1) + eps;
end;
initval;
k = 38.0;
a = 0.0;
c = 2.0;
end;
shocks;
var eps; stderr 0.01;
end;
"""
model = load_mod(rbc_mod) # first-order LinearModel (load_mod(rbc_mod, order=2) goes straight to 2nd order)
# 2. Solve the 2nd-order pruned perturbation
sol = model.solve(order=2)
# 3. Access Dynare-style decision rules (oo_.dr)
print(sol.oo_dr["ghx"]) # first-order state transition
print(sol.oo_dr["ghxx"]) # second-order state curvature
print(sol.oo_dr.summary())
# 4. Analytical theoretical moments & variance decomposition
mom = sol.theoretical_moments()
print(mom.summary())
print(mom.to_latex())
8. iPad / Juno / Pyodide to Google Colab Offloader
When working on a tablet or client-side Pyodide session that hits memory or runtime constraints, generate an executable Google Colab notebook with automatic Google Drive result syncing:
from puremacro.runtime.colab import (
generate_colab_notebook,
show_colab_offload_dialog,
load_colab_result,
)
# 1. Package the heavy task as a self-contained notebook (auth and Drive-mount cells
# included). The task's `result` variable is exported as a .pmz cartridge.
nb = generate_colab_notebook(
"""
import puremacro as pm
result = pm.dsge.estimate_sw07(n_draws=10000, n_chains=4)
""",
mount_drive=True,
save_path="sw07_offload.ipynb",
output_filename="sw07_posterior.pmz",
)
# 2. Show the upload instructions (HTML card in Juno / Jupyter, plain text in a terminal)
show_colab_offload_dialog("sw07_offload.ipynb")
# 3. When the cartridge comes back from Google Drive, load it into the local session:
# posterior = load_colab_result("sw07_posterior.pmz")