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 ("\n Note: 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 ("\n 1. 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 ("\n 2. 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 ("\n 3. 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"\n Average 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 ()
0 commit comments