Skip to content

Commit 972eaa2

Browse files
authored
Add miscellaneous income SOI targets (#882)
1 parent e1abb5f commit 972eaa2

10 files changed

Lines changed: 82 additions & 2 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added IRS SOI aggregate targets for positive miscellaneous income in calibration.

policyengine_us_data/calibration/target_config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ include:
253253
- variable: tax_unit_count
254254
geo_level: national
255255
domain_variable: adjusted_gross_income,tax_exempt_interest_income
256+
- variable: miscellaneous_income
257+
geo_level: national
258+
domain_variable: miscellaneous_income
259+
- variable: tax_unit_count
260+
geo_level: national
261+
domain_variable: miscellaneous_income
256262
- variable: refundable_ctc
257263
geo_level: national
258264
domain_variable: refundable_ctc

policyengine_us_data/db/etl_irs_soi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ def _skip_coarse_state_agi_person_count_target(geo_type: str, agi_stub: int) ->
177177
"charitable_deduction": "charitable_contributions_deductions",
178178
"dividend_income": "ordinary_dividends",
179179
"income_tax_before_credits": "income_tax_before_credits",
180+
"miscellaneous_income": "other_income",
180181
"qualified_dividend_income": "qualified_dividends",
181182
"rental_income": "rent_and_royalty_net_income",
182183
"tax_exempt_interest_income": "exempt_interest",

policyengine_us_data/storage/calibration_targets/refresh_soi_table_targets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"income_tax_before_credits": {True: ("ER",), False: ("ES",)},
4444
"ira_distributions": {True: ("AT",), False: ("AU",)},
4545
"ordinary_dividends": {True: ("X",), False: ("Y",)},
46+
"other_income": {True: ("CN",), False: ("CO",)},
4647
"partnership_and_s_corp_income": {
4748
True: ("BP", "BT"),
4849
False: ("BQ", "BU"),

policyengine_us_data/storage/calibration_targets/soi_targets.csv

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9778,6 +9778,12 @@ Year,SOI table,XLSX column,XLSX row,Variable,Filing status,AGI lower bound,AGI u
97789778
2023,Table 1.4,V,27,exempt_interest,All,5000000.0,10000000.0,True,False,False,29763
97799779
2023,Table 1.4,W,28,exempt_interest,All,10000000.0,inf,False,False,False,6779791000
97809780
2023,Table 1.4,V,28,exempt_interest,All,10000000.0,inf,True,False,False,21005
9781+
2021,Table 1.4,CC,9,other_income,All,-inf,inf,False,False,True,62702551000
9782+
2021,Table 1.4,CB,9,other_income,All,-inf,inf,True,False,True,5930776
9783+
2022,Table 1.4,CO,9,other_income,All,-inf,inf,False,False,True,62607506000
9784+
2022,Table 1.4,CN,9,other_income,All,-inf,inf,True,False,True,7557464
9785+
2023,Table 1.4,CO,9,other_income,All,-inf,inf,False,False,True,60816508000
9786+
2023,Table 1.4,CN,9,other_income,All,-inf,inf,True,False,True,7468434
97819787
2023,Table 2.1,CF,10,idpitgst,All,-inf,inf,False,False,True,218543083000
97829788
2023,Table 2.1,CF,33,idpitgst,All,-inf,inf,False,True,False,214582294000
97839789
2023,Table 2.1,CE,10,idpitgst,All,-inf,inf,True,False,True,14464822

policyengine_us_data/utils/soi.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
# deductions, so use total interest deductions as the closest available
1818
# proxy.
1919
"mortgage_interest_deductions": "interest_deduction",
20+
"other_income": "miscellaneous_income",
2021
"total_pension_income": "pension_income",
2122
"total_social_security": "social_security",
2223
"business_net_losses": "total_self_employment_income",
@@ -77,6 +78,7 @@ def pe(variable):
7778
df["ordinary_dividends"] = pe("non_qualified_dividend_income") + pe(
7879
"qualified_dividend_income"
7980
)
81+
df["other_income"] = pe("miscellaneous_income") * (pe("miscellaneous_income") > 0)
8082
df["partnership_and_s_corp_income"] = pe("partnership_s_corp_income") * (
8183
pe("partnership_s_corp_income") > 0
8284
)
@@ -131,6 +133,7 @@ def puf_to_soi(puf, year):
131133
df["ira_distributions"] = puf.E01400
132134
df["count_of_exemptions"] = puf.XTOT
133135
df["ordinary_dividends"] = puf.E00600
136+
df["other_income"] = puf.E01200 * (puf.E01200 > 0)
134137
df["partnership_and_s_corp_income"] = puf.E26270 * (puf.E26270 > 0)
135138
df["partnership_and_s_corp_losses"] = -puf.E26270 * (puf.E26270 < 0)
136139
df["total_pension_income"] = puf.E01500

tests/unit/calibration/test_target_config.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,30 @@ def test_training_config_includes_national_charitable_deduction_targets(
284284
"domain_variable": "charitable_deduction",
285285
} in include_rules
286286

287+
def test_training_config_includes_national_miscellaneous_income_targets(
288+
self,
289+
):
290+
config = load_target_config(
291+
str(
292+
Path(__file__).resolve().parents[3]
293+
/ "policyengine_us_data"
294+
/ "calibration"
295+
/ "target_config.yaml"
296+
)
297+
)
298+
299+
include_rules = config["include"]
300+
assert {
301+
"variable": "miscellaneous_income",
302+
"geo_level": "national",
303+
"domain_variable": "miscellaneous_income",
304+
} in include_rules
305+
assert {
306+
"variable": "tax_unit_count",
307+
"geo_level": "national",
308+
"domain_variable": "miscellaneous_income",
309+
} in include_rules
310+
287311
def test_training_config_includes_medicare_part_b_target(self):
288312
config = load_target_config(
289313
str(

tests/unit/test_etl_irs_soi_overlay.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ def test_workbook_domain_targets_include_charitable_deduction():
201201
)
202202

203203

204+
def test_workbook_domain_targets_include_miscellaneous_income():
205+
assert WORKBOOK_NATIONAL_DOMAIN_TARGETS["miscellaneous_income"] == "other_income"
206+
207+
204208
def test_skip_coarse_state_agi_person_count_target_only_for_state_stub_9():
205209
assert _skip_coarse_state_agi_person_count_target("state", 9) is True
206210
assert _skip_coarse_state_agi_person_count_target("state", 8) is False

tests/unit/test_refresh_soi_table_targets.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,11 @@ def test_build_target_year_rows_reads_standard_table_cells(monkeypatch):
103103

104104
def test_build_target_year_rows_uses_semantic_table_1_4_columns(monkeypatch):
105105
module = load_module()
106-
workbook = make_workbook(cols=80)
106+
workbook = make_workbook(cols=100)
107107
row_index = 9 # Excel row 10
108108

109+
workbook.iat[row_index, module._column_index("CN")] = 9
110+
workbook.iat[row_index, module._column_index("CO")] = 11
109111
workbook.iat[row_index, module._column_index("BP")] = 10
110112
workbook.iat[row_index, module._column_index("BQ")] = 30
111113
workbook.iat[row_index, module._column_index("BT")] = 20
@@ -117,6 +119,24 @@ def test_build_target_year_rows_uses_semantic_table_1_4_columns(monkeypatch):
117119

118120
targets = pd.DataFrame(
119121
[
122+
make_target_row(
123+
**{
124+
"SOI table": "Table 1.4",
125+
"XLSX column": "CB",
126+
"XLSX row": 10,
127+
"Variable": "other_income",
128+
"Count": True,
129+
}
130+
),
131+
make_target_row(
132+
**{
133+
"SOI table": "Table 1.4",
134+
"XLSX column": "CC",
135+
"XLSX row": 10,
136+
"Variable": "other_income",
137+
"Count": False,
138+
}
139+
),
120140
make_target_row(
121141
**{
122142
"SOI table": "Table 1.4",
@@ -163,8 +183,17 @@ def test_build_target_year_rows_uses_semantic_table_1_4_columns(monkeypatch):
163183
targets, source_year=2021, target_year=2023
164184
)
165185

166-
assert refreshed["Value"].tolist() == [30.0, 70_000.0, 11.0, 15_000.0]
186+
assert refreshed["Value"].tolist() == [
187+
9.0,
188+
11_000.0,
189+
30.0,
190+
70_000.0,
191+
11.0,
192+
15_000.0,
193+
]
167194
assert refreshed["XLSX column"].tolist() == [
195+
"CN",
196+
"CO",
168197
"BP+BT",
169198
"BQ+BU",
170199
"BR+BV",

tests/unit/test_soi_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,12 @@ def test_get_soi_includes_mortgage_interest_deduction_targets():
8484
soi_module = load_soi_module()
8585
soi = soi_module.get_soi(2024)
8686
mortgage_interest = soi[soi.Variable == "mortgage_interest_deductions"]
87+
other_income = soi[soi.Variable == "other_income"]
8788

8889
assert not mortgage_interest.empty
8990
assert mortgage_interest["Value"].gt(0).all()
91+
assert not other_income.empty
92+
assert other_income["Value"].gt(0).all()
9093

9194

9295
def test_pe_to_soi_combines_sstb_and_non_sstb_schedule_c(monkeypatch):
@@ -102,6 +105,7 @@ def calculate(self, variable, map_to=None):
102105
values = {
103106
"self_employment_income": np.array([100.0, -10.0]),
104107
"sstb_self_employment_income": np.array([50.0, -25.0]),
108+
"miscellaneous_income": np.array([12.0, -5.0]),
105109
"filing_status": np.array(["SINGLE", "SINGLE"]),
106110
"tax_unit_weight": np.ones(n),
107111
"household_id": np.arange(1, n + 1),
@@ -120,6 +124,7 @@ def calculate(self, variable, map_to=None):
120124
np.testing.assert_array_equal(
121125
soi["business_net_losses"].to_numpy(), np.array([0.0, 35.0])
122126
)
127+
np.testing.assert_array_equal(soi["other_income"].to_numpy(), np.array([12.0, 0.0]))
123128

124129

125130
def test_get_soi_uses_best_available_year_per_variable(monkeypatch):

0 commit comments

Comments
 (0)