Common analysis patterns for PolicyEngine research repositories (CRFB, newsletters, dashboards, impact studies). For population-level estimates (cost, poverty, distributional impacts), use the policyengine-microsimulation skill instead.
From essentialnpx claudepluginhub policyengine/policyengine-claude --plugin data-scienceThis skill uses the workspace's default tool permissions.
MICROSIMULATION_REFORM_GUIDE.mdexamples/reform_template.pyGuides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Optimizes cloud costs on AWS, Azure, GCP via rightsizing, tagging strategies, reserved instances, spot usage, and spending analysis. Use for expense reduction and governance.
Patterns for creating policy impact analyses, dashboards, and research using PolicyEngine.
For population-level estimates (budgetary cost, poverty impact, distributional analysis), use the policyengine-microsimulation skill instead. This skill covers analysis repo patterns, visualization, and household-level calculations.
See MICROSIMULATION_REFORM_GUIDE.md for UK-specific microsimulation patterns.
Analysis repositories produce the research you see on PolicyEngine:
Blog posts:
Dashboards:
Research reports:
Key sections in typical analysis:
The proposal:
Household impacts:
Statewide/national impacts:
See policyengine-writing-skill for writing conventions.
crfb-tob-impacts - Policy impact analysesnewsletters - Data-driven newsletters2024-election-dashboard - Policy comparison dashboardsmarginal-child - Specialized policy analysesgivecalc - Charitable giving calculatorStandard analysis repository structure:
analysis-repo/
├── analysis.ipynb # Main Jupyter notebook
├── requirements.txt # Python dependencies
├── README.md # Documentation
├── data/ # Data files (if needed)
└── outputs/ # Generated charts, tables
import pandas as pd
import numpy as np
from policyengine_us import Simulation
# Define reform
reform = {
"gov.irs.credits.ctc.amount.base[0].amount": {
"2026-01-01.2100-12-31": 5000
}
}
# Analyze across income distribution
incomes = np.linspace(0, 200000, 101)
results = []
for income in incomes:
# Baseline
situation = create_situation(income=income)
sim_baseline = Simulation(situation=situation)
tax_baseline = sim_baseline.calculate("income_tax", 2026)[0]
# Reform
sim_reform = Simulation(situation=situation, reform=reform)
tax_reform = sim_reform.calculate("income_tax", 2026)[0]
results.append({
"income": income,
"tax_baseline": tax_baseline,
"tax_reform": tax_reform,
"tax_change": tax_reform - tax_baseline
})
df = pd.DataFrame(results)
# Define representative households
households = {
"Single, No Children": {
"income": 40000,
"num_children": 0,
"married": False
},
"Single Parent, 2 Children": {
"income": 50000,
"num_children": 2,
"married": False
},
"Married, 2 Children": {
"income": 100000,
"num_children": 2,
"married": True
}
}
# Calculate impacts for each
case_studies = {}
for name, params in households.items():
situation = create_family(**params)
sim_baseline = Simulation(situation=situation)
sim_reform = Simulation(situation=situation, reform=reform)
case_studies[name] = {
"baseline_tax": sim_baseline.calculate("income_tax", 2026)[0],
"reform_tax": sim_reform.calculate("income_tax", 2026)[0],
"ctc_baseline": sim_baseline.calculate("ctc", 2026)[0],
"ctc_reform": sim_reform.calculate("ctc", 2026)[0]
}
case_df = pd.DataFrame(case_studies).T
states = ["CA", "NY", "TX", "FL", "PA", "OH", "IL", "MI"]
state_results = []
for state in states:
situation = create_situation(income=75000, state=state)
sim_baseline = Simulation(situation=situation)
sim_reform = Simulation(situation=situation, reform=reform)
state_results.append({
"state": state,
"baseline_net_income": sim_baseline.calculate("household_net_income", 2026)[0],
"reform_net_income": sim_reform.calculate("household_net_income", 2026)[0],
"change": (sim_reform.calculate("household_net_income", 2026)[0] -
sim_baseline.calculate("household_net_income", 2026)[0])
})
state_df = pd.DataFrame(state_results)
import plotly.graph_objects as go
# Calculate across income range
situation_with_axes = {
# ... setup ...
"axes": [[{
"name": "employment_income",
"count": 1001,
"min": 0,
"max": 200000,
"period": 2026
}]]
}
sim_baseline = Simulation(situation=situation_with_axes)
sim_reform = Simulation(situation=situation_with_axes, reform=reform)
incomes = sim_baseline.calculate("employment_income", 2026)
baseline_net = sim_baseline.calculate("household_net_income", 2026)
reform_net = sim_reform.calculate("household_net_income", 2026)
gains = reform_net - baseline_net
# Identify winners and losers
winners = gains > 0
losers = gains < 0
neutral = gains == 0
print(f"Winners: {winners.sum() / len(gains) * 100:.1f}%")
print(f"Losers: {losers.sum() / len(gains) * 100:.1f}%")
print(f"Neutral: {neutral.sum() / len(gains) * 100:.1f}%")
import plotly.graph_objects as go
# PolicyEngine brand colors — see policyengine-design-skill for canonical values.
# Python charts can't use CSS vars, so reference the design token hex values:
TEAL = "#319795" # --pe-color-primary-500
BLUE = "#026AA2" # --pe-color-blue-700
DARK_GRAY = "#5A5A5A" # --pe-color-text-secondary
def create_pe_layout(title, xaxis_title, yaxis_title):
"""Create standard PolicyEngine chart layout."""
return go.Layout(
title=title,
xaxis_title=xaxis_title,
yaxis_title=yaxis_title,
font=dict(family="Inter", size=14),
plot_bgcolor="white",
hovermode="x unified",
xaxis=dict(
showgrid=True,
gridcolor="lightgray",
zeroline=True
),
yaxis=dict(
showgrid=True,
gridcolor="lightgray",
zeroline=True
)
)
# Use in charts
fig = go.Figure(layout=create_pe_layout(
"Tax Impact by Income",
"Income",
"Tax Change"
))
fig.add_trace(go.Scatter(x=incomes, y=tax_change, line=dict(color=TEAL)))
1. Line Chart (Impact by Income)
fig = go.Figure()
fig.add_trace(go.Scatter(
x=df.income,
y=df.tax_change,
mode='lines',
name='Tax Change',
line=dict(color=TEAL, width=3)
))
fig.update_layout(
title="Tax Impact by Income Level",
xaxis_title="Income",
yaxis_title="Tax Change ($)",
xaxis_tickformat="$,.0f",
yaxis_tickformat="$,.0f"
)
2. Bar Chart (State Comparison)
fig = go.Figure()
fig.add_trace(go.Bar(
x=state_df.state,
y=state_df.change,
marker_color=TEAL
))
fig.update_layout(
title="Net Income Change by State",
xaxis_title="State",
yaxis_title="Change ($)",
yaxis_tickformat="$,.0f"
)
3. Waterfall Chart (Budget Impact)
fig = go.Figure(go.Waterfall(
x=["Baseline", "Tax Credit", "Phase-out", "Reform"],
y=[baseline_revenue, credit_cost, phaseout_revenue, 0],
measure=["absolute", "relative", "relative", "total"],
connector={"line": {"color": "gray"}}
))
# Cell 1: Title and Description
"""
# Policy Analysis: [Policy Name]
**Date:** [Date]
**Author:** [Your Name]
## Summary
Brief description of the analysis and key findings.
"""
# Cell 2: Imports
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from policyengine_us import Simulation
# Cell 3: Configuration
YEAR = 2026
STATES = ["CA", "NY", "TX", "FL"]
# Cell 4+: Analysis sections with markdown headers
# Save DataFrame
df.to_csv("outputs/impact_analysis.csv", index=False)
# Save Plotly chart
fig.write_html("outputs/chart.html")
fig.write_image("outputs/chart.png", width=1200, height=600)
# Save summary statistics
summary = {
"total_winners": winners.sum(),
"total_losers": losers.sum(),
"avg_gain": gains[winners].mean(),
"avg_loss": gains[losers].mean()
}
pd.DataFrame([summary]).to_csv("outputs/summary.csv", index=False)
This skill includes example templates in the examples/ directory:
impact_analysis_template.ipynb - Standard impact analysisstate_comparison.py - State-by-state analysiscase_studies.py - Household case studiesreform_definitions.py - Common reform patternsProblem: Mixing years across calculations
Solution: Define year constant at top:
CURRENT_YEAR = 2026
# Use everywhere
simulation.calculate("income_tax", CURRENT_YEAR)
Problem: Creating new simulation for each income level
Solution: Use axes for efficiency:
# SLOW
for income in incomes:
situation = create_situation(income=income)
sim = Simulation(situation=situation)
results.append(sim.calculate("income_tax", 2026)[0])
# FAST
situation_with_axes = create_situation_with_axes(incomes)
sim = Simulation(situation=situation_with_axes)
results = sim.calculate("income_tax", 2026) # Array of all results
Problem: Only showing reform results
Solution: Always show both:
results = {
"baseline": sim_baseline.calculate("income_tax", 2026),
"reform": sim_reform.calculate("income_tax", 2026),
"change": reform - baseline
}
For larger-scale analyses, use the PolicyEngine API:
import requests
def calculate_via_api(situation, reform=None):
"""Calculate using PolicyEngine API."""
url = "https://api.policyengine.org/us/calculate"
payload = {
"household": situation,
"policy_id": reform_id if reform else baseline_policy_id
}
response = requests.post(url, json=payload)
return response.json()
import pytest
def test_reform_increases_ctc():
"""Test that reform increases CTC as expected."""
situation = create_family(income=50000, num_children=2)
sim_baseline = Simulation(situation=situation)
sim_reform = Simulation(situation=situation, reform=reform)
ctc_baseline = sim_baseline.calculate("ctc", 2026)[0]
ctc_reform = sim_reform.calculate("ctc", 2026)[0]
assert ctc_reform > ctc_baseline, "Reform should increase CTC"
assert ctc_reform == 5000 * 2, "CTC should be $5000 per child"
# [Analysis Name]
## Overview
Brief description of the analysis.
## Key Findings
- Finding 1
- Finding 2
- Finding 3
## Methodology
Explanation of approach and data sources.
## How to Run
\```bash
uv pip install -r requirements.txt
python app.py # or jupyter notebook analysis.ipynb
\```
## Outputs
- `outputs/chart1.png` - Description
- `outputs/results.csv` - Description
## Contact
PolicyEngine Team - hello@policyengine.org