Skip to content

Commit 6eff0a5

Browse files
committed
add NJ tax winners and losers analysis with data output
1 parent 1ce808e commit 6eff0a5

File tree

5 files changed

+692
-0
lines changed

5 files changed

+692
-0
lines changed

data/NJ/nj_final_analysis.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Final NJ Winners/Losers Analysis
4+
Using income_tax changes since household_net_income times out
5+
A household is "better off" if their taxes go DOWN
6+
"""
7+
8+
import pandas as pd
9+
import numpy as np
10+
from policyengine_us import Microsimulation
11+
from policyengine_core.reforms import Reform
12+
13+
def create_reform():
14+
"""SALT cap removal reform"""
15+
return Reform.from_dict({
16+
"gov.irs.deductions.itemized.salt_and_real_estate.cap.JOINT": {
17+
"2026-01-01.2100-12-31": 1000000000000
18+
},
19+
"gov.irs.deductions.itemized.salt_and_real_estate.cap.SINGLE": {
20+
"2026-01-01.2100-12-31": 1000000000000
21+
},
22+
}, country_id="us")
23+
24+
def setup_simulation(reform=None):
25+
"""Setup simulation with corrections"""
26+
dataset = "hf://policyengine/test/sparse_cd_stacked_2023.h5"
27+
sim = Microsimulation(reform=reform, dataset=dataset) if reform else Microsimulation(dataset=dataset)
28+
29+
# Fix state FIPS
30+
cd_geoids = sim.calculate("congressional_district_geoid").values
31+
correct_state_fips = cd_geoids // 100
32+
sim.set_input("state_fips", 2023, correct_state_fips)
33+
34+
return sim
35+
36+
def main():
37+
print("=" * 70)
38+
print("NJ WINNERS/LOSERS ANALYSIS (Based on Tax Changes)")
39+
print("=" * 70)
40+
print("\nNote: Using income_tax changes as household_net_income times out")
41+
print("Winners = tax decrease (more net income)")
42+
print("Losers = tax increase (less net income)")
43+
44+
period = 2026
45+
46+
# Baseline
47+
print("\n1. Calculating baseline taxes...")
48+
sim_baseline = setup_simulation()
49+
state_code = sim_baseline.calculate("state_code", map_to="household", period=period)
50+
in_nj = state_code == "NJ"
51+
52+
tax_baseline = sim_baseline.calculate("income_tax", map_to="household", period=period)
53+
weights = sim_baseline.calculate("household_weight", map_to="household", period=period)
54+
districts = sim_baseline.calculate("congressional_district_geoid", map_to="household", period=period)
55+
56+
# Get NJ data
57+
tax_baseline_nj = tax_baseline[in_nj].values if hasattr(tax_baseline[in_nj], 'values') else tax_baseline[in_nj]
58+
weights_nj = weights[in_nj].values if hasattr(weights[in_nj], 'values') else weights[in_nj]
59+
districts_nj = districts[in_nj].values if hasattr(districts[in_nj], 'values') else districts[in_nj]
60+
61+
print(f" Found {len(tax_baseline_nj)} NJ households")
62+
63+
# Reform
64+
print("\n2. Calculating reform taxes...")
65+
reform = create_reform()
66+
sim_reform = setup_simulation(reform=reform)
67+
tax_reform = sim_reform.calculate("income_tax", map_to="household", period=period)
68+
tax_reform_nj = tax_reform[in_nj].values if hasattr(tax_reform[in_nj], 'values') else tax_reform[in_nj]
69+
70+
# Analysis
71+
print("\n3. Analyzing changes...")
72+
tax_change = tax_reform_nj - tax_baseline_nj
73+
74+
# Winners have NEGATIVE tax change (pay less tax)
75+
winners = tax_change < -10 # At least $10 tax cut
76+
losers = tax_change > 10 # At least $10 tax increase
77+
no_change = np.abs(tax_change) <= 10
78+
79+
# Overall stats
80+
total_households = np.sum(weights_nj)
81+
num_winners = np.sum(weights_nj[winners])
82+
num_losers = np.sum(weights_nj[losers])
83+
num_no_change = np.sum(weights_nj[no_change])
84+
85+
print("\n" + "=" * 70)
86+
print("STATEWIDE RESULTS FOR NEW JERSEY:")
87+
print("-" * 70)
88+
print(f"Total Households: {total_households:,.0f}")
89+
print(f"Better off (tax cut): {num_winners:,.0f} ({100*num_winners/total_households:.1f}%)")
90+
print(f"Worse off (tax increase): {num_losers:,.0f} ({100*num_losers/total_households:.1f}%)")
91+
print(f"No significant change: {num_no_change:,.0f} ({100*num_no_change/total_households:.1f}%)")
92+
93+
if np.any(winners):
94+
avg_tax_cut = np.average(tax_change[winners], weights=weights_nj[winners])
95+
print(f"\nAverage tax cut for winners: ${-avg_tax_cut:,.2f}")
96+
97+
if np.any(losers):
98+
avg_tax_increase = np.average(tax_change[losers], weights=weights_nj[losers])
99+
print(f"Average tax increase for losers: ${avg_tax_increase:,.2f}")
100+
101+
overall_avg = np.average(tax_change, weights=weights_nj)
102+
print(f"Overall average tax change: ${overall_avg:,.2f}")
103+
104+
# By district
105+
print("\n" + "=" * 70)
106+
print("BY CONGRESSIONAL DISTRICT:")
107+
print("-" * 70)
108+
print(f"{'District':<10} {'Better Off':<15} {'Worse Off':<15} {'No Change':<15} {'Avg Change':<15}")
109+
print("-" * 70)
110+
111+
unique_districts = np.unique(districts_nj)
112+
results = []
113+
114+
for district in sorted(unique_districts):
115+
mask = districts_nj == district
116+
dist_weights = weights_nj[mask]
117+
dist_changes = tax_change[mask]
118+
119+
dist_total = np.sum(dist_weights)
120+
dist_winners = np.sum(dist_weights[winners[mask]])
121+
dist_losers = np.sum(dist_weights[losers[mask]])
122+
dist_no_change = np.sum(dist_weights[no_change[mask]])
123+
124+
pct_winners = 100 * dist_winners / dist_total
125+
pct_losers = 100 * dist_losers / dist_total
126+
avg_change = np.average(dist_changes, weights=dist_weights)
127+
128+
print(f"{int(district):<10} {pct_winners:<14.1f}% {pct_losers:<14.1f}% "
129+
f"{100-pct_winners-pct_losers:<14.1f}% ${avg_change:<14,.0f}")
130+
131+
results.append({
132+
'district': int(district),
133+
'pct_better_off': pct_winners,
134+
'pct_worse_off': pct_losers,
135+
'pct_no_change': 100-pct_winners-pct_losers,
136+
'avg_tax_change': avg_change,
137+
'total_households': dist_total
138+
})
139+
140+
# Save results
141+
results_df = pd.DataFrame(results)
142+
results_df.to_csv('nj_final_winners_losers.csv', index=False)
143+
144+
print("\n" + "=" * 70)
145+
print("Results saved to nj_final_winners_losers.csv")
146+
print("=" * 70)
147+
148+
if __name__ == "__main__":
149+
main()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
district,pct_better_off,pct_worse_off,pct_no_change,avg_tax_change,total_households
2+
3401,1.4375593998995624,0.21245031901909547,98.34999028108135,-371.08194381281317,291785.53
3+
3402,1.6338614293693292,0.26293164017481535,98.10320693045585,-365.7290158357723,294746.7
4+
3403,2.3681540495399207,0.1891615908868436,97.44268435957324,-505.11005722963483,331372.9
5+
3404,3.3761379187602483,0.515033558686894,96.10882852255286,-484.7476890043855,284799.0
6+
3405,1.2233782935944382,0.7562876874572445,98.02033401894832,-154.24406935466624,358323.38
7+
3406,2.1515020212870173,0.3545946750276575,97.49390330368533,-348.7820458573577,291568.56
8+
3407,2.4518459453440973,0.34098947330178164,97.20716458135412,-295.7605516565809,432226.88
9+
3408,1.978173547533005,0.20538014968419183,97.81644630278281,-434.88970820559416,342870.3
10+
3409,1.8293848741050243,0.5659177288180128,97.60469739707696,-281.15523700836894,285674.62
11+
3410,2.399008238824877,0.31857446403277295,97.28241729714235,-342.82538999656873,304321.3
12+
3411,3.509765713213153,0.6397717516330599,95.85046253515378,-463.2091918620604,404072.75
13+
3412,2.5381193926412573,0.15410677364178568,97.30777383371695,-349.64513451098446,338276.2

0 commit comments

Comments
 (0)