diff --git a/us/states/mt/ctc_analysis.ipynb b/us/states/mt/ctc_analysis.ipynb new file mode 100644 index 0000000..19d32e4 --- /dev/null +++ b/us/states/mt/ctc_analysis.ipynb @@ -0,0 +1,897 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Montana Child Tax Credit Reform Analysis (2027)\n", + "\n", + "This notebook analyzes the impact of a Montana Child Tax Credit reform.\n", + "\n", + "## Reform Details\n", + "- **Credit Amount**: $1,800 per child under age 6, $1,500 per child ages 6-17\n", + "- **Eligibility**: Children under 18 who are dependents with SSN\n", + "- **Earned Income Requirement**: At least $1 of earned income required (enabled by default)\n", + "- **Phase-out thresholds**:\n", + " - Joint/Surviving Spouse: $120,000 AGI\n", + " - Single/Head of Household/Separate: $60,000 AGI\n", + "- **Phase-out**: $50 reduction per $1,000 of income above threshold\n", + "- **Refundable**: Yes\n", + "\n", + "## Analysis Goals\n", + "1. Budgetary impact\n", + "2. Winners and losers\n", + "3. Income decile analysis\n", + "4. Poverty impacts (overall, child, deep)\n", + "5. Income bracket breakdown" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from policyengine_us import Microsimulation\n", + "from policyengine_core.reforms import Reform\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "MT_DATASET = \"hf://policyengine/policyengine-us-data/states/MT.h5\"\n", + "PERIOD = 2027\n", + "\n", + "# Intra-decile bounds and labels (matches API v2)\n", + "INTRA_BOUNDS = [-np.inf, -0.05, -1e-3, 1e-3, 0.05, np.inf]\n", + "INTRA_LABELS = [\n", + " \"Lose more than 5%\",\n", + " \"Lose less than 5%\",\n", + " \"No change\",\n", + " \"Gain less than 5%\",\n", + " \"Gain more than 5%\",\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define Reform" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reform function defined!\n" + ] + } + ], + "source": [ + "def create_mt_ctc_reform():\n", + " \"\"\"Enable Montana Child Tax Credit reform for 2027.\"\"\"\n", + " reform = Reform.from_dict(\n", + " {\n", + " \"gov.contrib.states.mt.ctc.in_effect\": {\n", + " \"2027-01-01.2100-12-31\": True\n", + " }\n", + " },\n", + " country_id=\"us\",\n", + " )\n", + " return reform\n", + "\n", + "print(\"Reform function defined!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Simulations" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading baseline (current law - no MT CTC)...\n", + "Baseline loaded\n", + "\n", + "Loading reform (MT CTC enabled)...\n", + "Reform loaded\n", + "\n", + "============================================================\n", + "All simulations ready!\n", + "============================================================\n" + ] + } + ], + "source": [ + "print(\"Loading baseline (current law - no MT CTC)...\")\n", + "baseline = Microsimulation(dataset=MT_DATASET)\n", + "print(\"Baseline loaded\")\n", + "\n", + "print(\"\\nLoading reform (MT CTC enabled)...\")\n", + "reform = create_mt_ctc_reform()\n", + "reformed = Microsimulation(dataset=MT_DATASET, reform=reform)\n", + "print(\"Reform loaded\")\n", + "\n", + "print(\"\\n\" + \"=\"*60)\n", + "print(\"All simulations ready!\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Helper Functions" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def poverty_metrics(baseline_rate, reform_rate):\n", + " \"\"\"Return rate change and percent change for a poverty metric.\"\"\"\n", + " rate_change = reform_rate - baseline_rate\n", + " percent_change = (\n", + " rate_change / baseline_rate * 100\n", + " if baseline_rate > 0\n", + " else 0.0\n", + " )\n", + " return rate_change, percent_change" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Budgetary Impact" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "BUDGETARY IMPACT (2027)\n", + "============================================================\n", + "Total cost: $257.54 million\n", + "Total households: 410,732\n", + "\n", + "Direct credit amount: $257.58 million\n", + "Tax units receiving credit: 94,504\n", + "Average credit per tax unit: $2,726\n", + "\n", + "Validation ratio: 1.00x\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Calculate household-level income change\n", + "baseline_net_income = baseline.calc(\"household_net_income\", period=PERIOD, map_to=\"household\")\n", + "reformed_net_income = reformed.calc(\"household_net_income\", period=PERIOD, map_to=\"household\")\n", + "income_change = reformed_net_income - baseline_net_income\n", + "\n", + "# Total cost\n", + "total_cost = float(income_change.sum())\n", + "\n", + "# Total households\n", + "total_households = float((income_change * 0 + 1).sum())\n", + "\n", + "# Direct credit calculation for validation\n", + "mt_ctc_total = float(reformed.calc(\"mt_ctc\", period=PERIOD).sum())\n", + "tax_units_receiving = float((reformed.calc(\"mt_ctc\", period=PERIOD) > 0).sum())\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"BUDGETARY IMPACT ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"Total cost: ${total_cost/1e6:,.2f} million\")\n", + "print(f\"Total households: {total_households:,.0f}\")\n", + "print(f\"\\nDirect credit amount: ${mt_ctc_total/1e6:,.2f} million\")\n", + "print(f\"Tax units receiving credit: {tax_units_receiving:,.0f}\")\n", + "print(f\"Average credit per tax unit: ${mt_ctc_total / tax_units_receiving:,.0f}\")\n", + "print(f\"\\nValidation ratio: {total_cost / mt_ctc_total:.2f}x\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Winners and Losers" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "WINNERS AND LOSERS (2027)\n", + "============================================================\n", + "Winners (gain > $1): 415,596 (35.9%)\n", + "Losers (lose > $1): 0 (0.0%)\n", + "Beneficiaries (gain > $0): 415,596\n", + "\n", + "Average benefit (affected): $2,809\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Person-level winners/losers (consistent with intra-decile)\n", + "# Use same weighting approach as intra-decile: people_per_hh * household_weight\n", + "household_weight = reformed.calc(\"household_weight\", period=PERIOD)\n", + "people_per_hh = baseline.calc(\"household_count_people\", period=PERIOD, map_to=\"household\")\n", + "\n", + "weight_arr = np.array(household_weight)\n", + "change_arr = np.array(income_change)\n", + "people_arr = np.array(people_per_hh)\n", + "\n", + "# Person-weighted totals\n", + "people_weighted = people_arr * weight_arr\n", + "total_people = float(people_weighted.sum())\n", + "\n", + "# Winners/losers using person-weighted counts\n", + "winners_mask = change_arr > 1\n", + "losers_mask = change_arr < -1\n", + "beneficiaries_mask = change_arr > 0\n", + "\n", + "winners = float(people_weighted[winners_mask].sum())\n", + "losers = float(people_weighted[losers_mask].sum())\n", + "beneficiaries = float(people_weighted[beneficiaries_mask].sum())\n", + "\n", + "winners_rate = winners / total_people * 100\n", + "losers_rate = losers / total_people * 100\n", + "\n", + "# Average benefit for affected households\n", + "affected_mask = np.abs(change_arr) > 1\n", + "affected_count = float(weight_arr[affected_mask].sum())\n", + "avg_benefit = (\n", + " float(np.average(\n", + " change_arr[affected_mask],\n", + " weights=weight_arr[affected_mask],\n", + " ))\n", + " if affected_count > 0\n", + " else 0.0\n", + ")\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"WINNERS AND LOSERS ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"Winners (gain > $1): {winners:,.0f} ({winners_rate:.1f}%)\")\n", + "print(f\"Losers (lose > $1): {losers:,.0f} ({losers_rate:.1f}%)\")\n", + "print(f\"Beneficiaries (gain > $0): {beneficiaries:,.0f}\")\n", + "print(f\"\\nAverage benefit (affected): ${avg_benefit:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Households and Children" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "HOUSEHOLDS AND CHILDREN (2027)\n", + "============================================================\n", + "\n", + "HOUSEHOLDS WITH CHILDREN:\n", + " Total households with children: 144,771\n", + " Households with children benefiting: 91,687\n", + " Percentage benefiting: 63.3%\n", + "\n", + "CHILDREN (under 18):\n", + " Total children: 241,202\n", + " Children benefiting: 187,908\n", + " Percentage benefiting: 77.9%\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Calculate households and children metrics\n", + "age = baseline.calc(\"age\", period=PERIOD)\n", + "is_child_person = np.array(age) < 18\n", + "\n", + "# Household-level: count children per household\n", + "children_per_hh = baseline.calc(\"tax_unit_count_dependents\", period=PERIOD, map_to=\"household\")\n", + "children_per_hh_arr = np.array(children_per_hh)\n", + "has_children = children_per_hh_arr > 0\n", + "\n", + "# Total households with children\n", + "hh_with_children = float(weight_arr[has_children].sum())\n", + "\n", + "# Households with children that benefit\n", + "hh_with_children_benefit = float(weight_arr[has_children & (change_arr > 1)].sum())\n", + "hh_with_children_benefit_pct = hh_with_children_benefit / hh_with_children * 100\n", + "\n", + "# Total children (using person weights)\n", + "person_weight = baseline.calc(\"person_weight\", period=PERIOD)\n", + "pw_arr = np.array(person_weight)\n", + "total_children = float((pw_arr * is_child_person).sum())\n", + "\n", + "# Children benefiting (map income change to person level)\n", + "income_change_person = reformed.calc(\"household_net_income\", period=PERIOD, map_to=\"person\") - \\\n", + " baseline.calc(\"household_net_income\", period=PERIOD, map_to=\"person\")\n", + "income_change_person_arr = np.array(income_change_person)\n", + "children_benefit_mask = is_child_person & (income_change_person_arr > 1)\n", + "children_benefiting = float((pw_arr * children_benefit_mask).sum())\n", + "children_benefit_pct = children_benefiting / total_children * 100\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"HOUSEHOLDS AND CHILDREN ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"\\nHOUSEHOLDS WITH CHILDREN:\")\n", + "print(f\" Total households with children: {hh_with_children:,.0f}\")\n", + "print(f\" Households with children benefiting: {hh_with_children_benefit:,.0f}\")\n", + "print(f\" Percentage benefiting: {hh_with_children_benefit_pct:.1f}%\")\n", + "print(f\"\\nCHILDREN (under 18):\")\n", + "print(f\" Total children: {total_children:,.0f}\")\n", + "print(f\" Children benefiting: {children_benefiting:,.0f}\")\n", + "print(f\" Percentage benefiting: {children_benefit_pct:.1f}%\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Income Decile Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "INCOME DECILE ANALYSIS (2027)\n", + "============================================================\n", + "Decile Avg Change Relative Change\n", + "---------------------------------------------\n", + "1 $ 121 0.63%\n", + "2 $ 214 0.54%\n", + "3 $ 486 0.87%\n", + "4 $ 927 1.35%\n", + "5 $ 710 0.87%\n", + "6 $ 1,217 1.26%\n", + "7 $ 1,331 1.16%\n", + "8 $ 921 0.67%\n", + "9 $ 799 0.44%\n", + "10 $ 264 0.03%\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Get decile assignment\n", + "decile = baseline.calc(\"household_income_decile\", period=PERIOD, map_to=\"household\")\n", + "\n", + "decile_average = {}\n", + "decile_relative = {}\n", + "\n", + "for d in range(1, 11):\n", + " dmask = decile == d\n", + " d_count = float(dmask.sum())\n", + " if d_count > 0:\n", + " d_change_sum = float(income_change[dmask].sum())\n", + " decile_average[str(d)] = d_change_sum / d_count\n", + " d_baseline_sum = float(baseline_net_income[dmask].sum())\n", + " decile_relative[str(d)] = (\n", + " d_change_sum / d_baseline_sum * 100\n", + " if d_baseline_sum != 0\n", + " else 0.0\n", + " )\n", + " else:\n", + " decile_average[str(d)] = 0.0\n", + " decile_relative[str(d)] = 0.0\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"INCOME DECILE ANALYSIS ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"{'Decile':<10} {'Avg Change':>15} {'Relative Change':>18}\")\n", + "print(\"-\"*45)\n", + "for d in range(1, 11):\n", + " print(f\"{d:<10} ${decile_average[str(d)]:>13,.0f} {decile_relative[str(d)]:>17.2f}%\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Intra-Decile Distribution" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "INTRA-DECILE DISTRIBUTION (2027)\n", + "============================================================\n", + "Overall distribution:\n", + " Lose more than 5% 0.0%\n", + " Lose less than 5% 0.0%\n", + " No change 64.1%\n", + " Gain less than 5% 27.6%\n", + " Gain more than 5% 8.3%\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Intra-decile requires person-weighted proportions\n", + "people_per_hh = baseline.calc(\"household_count_people\", period=PERIOD, map_to=\"household\")\n", + "capped_baseline = np.maximum(np.array(baseline_net_income), 1)\n", + "rel_change_arr = np.array(income_change) / capped_baseline\n", + "\n", + "decile_arr = np.array(decile)\n", + "people_weighted = np.array(people_per_hh) * weight_arr\n", + "\n", + "intra_decile_deciles = {label: [] for label in INTRA_LABELS}\n", + "for d in range(1, 11):\n", + " dmask = decile_arr == d\n", + " d_people = people_weighted[dmask]\n", + " d_total_people = d_people.sum()\n", + " d_rel = rel_change_arr[dmask]\n", + "\n", + " for lower, upper, label in zip(\n", + " INTRA_BOUNDS[:-1], INTRA_BOUNDS[1:], INTRA_LABELS\n", + " ):\n", + " in_group = (d_rel > lower) & (d_rel <= upper)\n", + " proportion = (\n", + " float(d_people[in_group].sum() / d_total_people)\n", + " if d_total_people > 0\n", + " else 0.0\n", + " )\n", + " intra_decile_deciles[label].append(proportion)\n", + "\n", + "intra_decile_all = {\n", + " label: sum(intra_decile_deciles[label]) / 10\n", + " for label in INTRA_LABELS\n", + "}\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"INTRA-DECILE DISTRIBUTION ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(\"Overall distribution:\")\n", + "for label in INTRA_LABELS:\n", + " print(f\" {label:<20} {intra_decile_all[label]*100:>6.1f}%\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Poverty Impact" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "POVERTY IMPACT (2027)\n", + "============================================================\n", + "\n", + "OVERALL POVERTY:\n", + " Baseline rate: 14.19%\n", + " Reform rate: 13.57%\n", + " Rate change: -0.61 pp\n", + " Percent change: -4.3%\n", + "\n", + "DEEP POVERTY:\n", + " Baseline rate: 6.10%\n", + " Reform rate: 6.01%\n", + " Rate change: -0.09 pp\n", + " Percent change: -1.4%\n", + "\n", + "CHILD POVERTY (under 18):\n", + " Baseline rate: 12.84%\n", + " Reform rate: 11.35%\n", + " Rate change: -1.49 pp\n", + " Percent change: -11.6%\n", + "\n", + "DEEP CHILD POVERTY (under 18):\n", + " Baseline rate: 5.49%\n", + " Reform rate: 5.26%\n", + " Rate change: -0.23 pp\n", + " Percent change: -4.3%\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Overall poverty\n", + "pov_bl = baseline.calc(\"in_poverty\", period=PERIOD, map_to=\"person\")\n", + "pov_rf = reformed.calc(\"in_poverty\", period=PERIOD, map_to=\"person\")\n", + "poverty_baseline_rate = float(pov_bl.mean() * 100)\n", + "poverty_reform_rate = float(pov_rf.mean() * 100)\n", + "poverty_rate_change, poverty_percent_change = poverty_metrics(\n", + " poverty_baseline_rate, poverty_reform_rate\n", + ")\n", + "\n", + "# Deep poverty\n", + "deep_bl = baseline.calc(\"in_deep_poverty\", period=PERIOD, map_to=\"person\")\n", + "deep_rf = reformed.calc(\"in_deep_poverty\", period=PERIOD, map_to=\"person\")\n", + "deep_poverty_baseline_rate = float(deep_bl.mean() * 100)\n", + "deep_poverty_reform_rate = float(deep_rf.mean() * 100)\n", + "deep_poverty_rate_change, deep_poverty_percent_change = poverty_metrics(\n", + " deep_poverty_baseline_rate, deep_poverty_reform_rate\n", + ")\n", + "\n", + "# Child poverty requires age filtering\n", + "age_arr = np.array(baseline.calc(\"age\", period=PERIOD))\n", + "is_child = age_arr < 18\n", + "pw_arr = np.array(baseline.calc(\"person_weight\", period=PERIOD))\n", + "child_w = pw_arr[is_child]\n", + "total_child_w = child_w.sum()\n", + "\n", + "pov_bl_arr = np.array(pov_bl).astype(bool)\n", + "pov_rf_arr = np.array(pov_rf).astype(bool)\n", + "deep_bl_arr = np.array(deep_bl).astype(bool)\n", + "deep_rf_arr = np.array(deep_rf).astype(bool)\n", + "\n", + "def child_rate(arr):\n", + " return float(\n", + " (arr[is_child] * child_w).sum() / total_child_w * 100\n", + " ) if total_child_w > 0 else 0.0\n", + "\n", + "child_poverty_baseline_rate = child_rate(pov_bl_arr)\n", + "child_poverty_reform_rate = child_rate(pov_rf_arr)\n", + "child_poverty_rate_change, child_poverty_percent_change = poverty_metrics(\n", + " child_poverty_baseline_rate, child_poverty_reform_rate\n", + ")\n", + "\n", + "deep_child_poverty_baseline_rate = child_rate(deep_bl_arr)\n", + "deep_child_poverty_reform_rate = child_rate(deep_rf_arr)\n", + "deep_child_poverty_rate_change, deep_child_poverty_percent_change = poverty_metrics(\n", + " deep_child_poverty_baseline_rate, deep_child_poverty_reform_rate\n", + ")\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"POVERTY IMPACT ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(\"\\nOVERALL POVERTY:\")\n", + "print(f\" Baseline rate: {poverty_baseline_rate:.2f}%\")\n", + "print(f\" Reform rate: {poverty_reform_rate:.2f}%\")\n", + "print(f\" Rate change: {poverty_rate_change:+.2f} pp\")\n", + "print(f\" Percent change: {poverty_percent_change:+.1f}%\")\n", + "\n", + "print(\"\\nDEEP POVERTY:\")\n", + "print(f\" Baseline rate: {deep_poverty_baseline_rate:.2f}%\")\n", + "print(f\" Reform rate: {deep_poverty_reform_rate:.2f}%\")\n", + "print(f\" Rate change: {deep_poverty_rate_change:+.2f} pp\")\n", + "print(f\" Percent change: {deep_poverty_percent_change:+.1f}%\")\n", + "\n", + "print(\"\\nCHILD POVERTY (under 18):\")\n", + "print(f\" Baseline rate: {child_poverty_baseline_rate:.2f}%\")\n", + "print(f\" Reform rate: {child_poverty_reform_rate:.2f}%\")\n", + "print(f\" Rate change: {child_poverty_rate_change:+.2f} pp\")\n", + "print(f\" Percent change: {child_poverty_percent_change:+.1f}%\")\n", + "\n", + "print(\"\\nDEEP CHILD POVERTY (under 18):\")\n", + "print(f\" Baseline rate: {deep_child_poverty_baseline_rate:.2f}%\")\n", + "print(f\" Reform rate: {deep_child_poverty_reform_rate:.2f}%\")\n", + "print(f\" Rate change: {deep_child_poverty_rate_change:+.2f} pp\")\n", + "print(f\" Percent change: {deep_child_poverty_percent_change:+.1f}%\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Income Bracket Breakdown" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "INCOME BRACKET BREAKDOWN (2027)\n", + "============================================================\n", + "Bracket Beneficiaries Total Cost Avg Benefit\n", + "------------------------------------------------------------\n", + "Under $25k 8,736 $ 24.16M $ 2,765\n", + "$25k-$50k 12,669 $ 31.20M $ 2,463\n", + "$50k-$75k 16,468 $ 45.74M $ 2,777\n", + "$75k-$100k 18,437 $ 55.78M $ 3,025\n", + "$100k-$150k 22,656 $ 69.10M $ 3,050\n", + "Over $150k 12,217 $ 30.17M $ 2,469\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Get AGI at household level\n", + "agi = reformed.calc(\"adjusted_gross_income\", period=PERIOD, map_to=\"household\")\n", + "agi_arr = np.array(agi)\n", + "affected_mask = np.abs(change_arr) > 1\n", + "\n", + "income_brackets = [\n", + " (0, 25_000, \"Under $25k\"),\n", + " (25_000, 50_000, \"$25k-$50k\"),\n", + " (50_000, 75_000, \"$50k-$75k\"),\n", + " (75_000, 100_000, \"$75k-$100k\"),\n", + " (100_000, 150_000, \"$100k-$150k\"),\n", + " (150_000, float(\"inf\"), \"Over $150k\"),\n", + "]\n", + "\n", + "by_income_bracket = []\n", + "for min_inc, max_inc, label in income_brackets:\n", + " mask = (\n", + " (agi_arr >= min_inc)\n", + " & (agi_arr < max_inc)\n", + " & affected_mask\n", + " )\n", + " bracket_affected = float(weight_arr[mask].sum())\n", + " if bracket_affected > 0:\n", + " bracket_cost = float(\n", + " (change_arr[mask] * weight_arr[mask]).sum()\n", + " )\n", + " bracket_avg = float(\n", + " np.average(change_arr[mask], weights=weight_arr[mask])\n", + " )\n", + " else:\n", + " bracket_cost = 0.0\n", + " bracket_avg = 0.0\n", + " by_income_bracket.append({\n", + " \"bracket\": label,\n", + " \"beneficiaries\": bracket_affected,\n", + " \"total_cost\": bracket_cost,\n", + " \"avg_benefit\": bracket_avg,\n", + " })\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"INCOME BRACKET BREAKDOWN ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"{'Bracket':<15} {'Beneficiaries':>15} {'Total Cost':>15} {'Avg Benefit':>12}\")\n", + "print(\"-\"*60)\n", + "for row in by_income_bracket:\n", + " print(f\"{row['bracket']:<15} {row['beneficiaries']:>15,.0f} ${row['total_cost']/1e6:>13,.2f}M ${row['avg_benefit']:>10,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "======================================================================\n", + "MONTANA CHILD TAX CREDIT REFORM SUMMARY\n", + "======================================================================\n", + "\n", + "Year: 2027\n", + "\n", + "Reform: Refundable credit - $1,800 per child under 6, $1,500 per child 6-17\n", + " Phase-out starts at $60k (single) / $120k (joint) AGI\n", + " Phase-out rate: $50 per $1,000 above threshold\n", + " Earned income requirement: at least $1\n", + "\n", + "----------------------------------------------------------------------\n", + "KEY FINDINGS:\n", + "----------------------------------------------------------------------\n", + "\n", + " BUDGET:\n", + " Total cost: $257.54 million\n", + " Total households: 410,732\n", + " Total people: 1,156,438\n", + "\n", + " DISTRIBUTION (person-level):\n", + " Winners: 415,596 (35.9%)\n", + " Losers: 0 (0.0%)\n", + " Average benefit (per HH): $2,809\n", + "\n", + " POVERTY REDUCTION:\n", + " Overall poverty: -0.61 pp (-4.3%)\n", + " Deep poverty: -0.09 pp (-1.4%)\n", + " Child poverty: -1.49 pp (-11.6%)\n", + " Deep child poverty: -0.23 pp (-4.3%)\n", + "======================================================================\n" + ] + } + ], + "source": [ + "print(\"\\n\" + \"=\"*70)\n", + "print(\"MONTANA CHILD TAX CREDIT REFORM SUMMARY\")\n", + "print(\"=\"*70)\n", + "print(f\"\\nYear: {PERIOD}\")\n", + "print(f\"\\nReform: Refundable credit - $1,800 per child under 6, $1,500 per child 6-17\")\n", + "print(f\" Phase-out starts at $60k (single) / $120k (joint) AGI\")\n", + "print(f\" Phase-out rate: $50 per $1,000 above threshold\")\n", + "print(f\" Earned income requirement: at least $1\")\n", + "print(\"\\n\" + \"-\"*70)\n", + "print(\"KEY FINDINGS:\")\n", + "print(\"-\"*70)\n", + "print(f\"\\n BUDGET:\")\n", + "print(f\" Total cost: ${total_cost/1e6:,.2f} million\")\n", + "print(f\" Total households: {total_households:,.0f}\")\n", + "print(f\" Total people: {total_people:,.0f}\")\n", + "print(f\"\\n DISTRIBUTION (person-level):\")\n", + "print(f\" Winners: {winners:,.0f} ({winners_rate:.1f}%)\")\n", + "print(f\" Losers: {losers:,.0f} ({losers_rate:.1f}%)\")\n", + "print(f\" Average benefit (per HH): ${avg_benefit:,.0f}\")\n", + "print(f\"\\n POVERTY REDUCTION:\")\n", + "print(f\" Overall poverty: {poverty_rate_change:+.2f} pp ({poverty_percent_change:+.1f}%)\")\n", + "print(f\" Deep poverty: {deep_poverty_rate_change:+.2f} pp ({deep_poverty_percent_change:+.1f}%)\")\n", + "print(f\" Child poverty: {child_poverty_rate_change:+.2f} pp ({child_poverty_percent_change:+.1f}%)\")\n", + "print(f\" Deep child poverty: {deep_child_poverty_rate_change:+.2f} pp ({deep_child_poverty_percent_change:+.1f}%)\")\n", + "print(\"=\"*70)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Export Results" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Metric Value\n", + " Total cost $257.54M\n", + " Total households 410,732\n", + " Total people 1,156,438\n", + " Winners (people) 415,596 (35.9%)\n", + " Losers (people) 0 (0.0%)\n", + " Average benefit (per HH) $2,809\n", + " Overall poverty change -0.61 pp (-4.3%)\n", + " Child poverty change -1.49 pp (-11.6%)\n", + " Deep poverty change -0.09 pp (-1.4%)\n", + "Deep child poverty change -0.23 pp (-4.3%)\n", + "\n", + "Exported to: mt_ctc_results.csv\n" + ] + } + ], + "source": [ + "# Compile all results into a dictionary\n", + "results = {\n", + " \"budget\": {\n", + " \"total_cost\": total_cost,\n", + " \"households\": total_households,\n", + " \"people\": total_people,\n", + " },\n", + " \"decile\": {\n", + " \"average\": decile_average,\n", + " \"relative\": decile_relative,\n", + " },\n", + " \"intra_decile\": {\n", + " \"all\": intra_decile_all,\n", + " \"deciles\": intra_decile_deciles,\n", + " },\n", + " \"winners\": winners,\n", + " \"losers\": losers,\n", + " \"winners_rate\": winners_rate,\n", + " \"losers_rate\": losers_rate,\n", + " \"beneficiaries\": beneficiaries,\n", + " \"avg_benefit\": avg_benefit,\n", + " \"poverty_baseline_rate\": poverty_baseline_rate,\n", + " \"poverty_reform_rate\": poverty_reform_rate,\n", + " \"poverty_rate_change\": poverty_rate_change,\n", + " \"poverty_percent_change\": poverty_percent_change,\n", + " \"child_poverty_baseline_rate\": child_poverty_baseline_rate,\n", + " \"child_poverty_reform_rate\": child_poverty_reform_rate,\n", + " \"child_poverty_rate_change\": child_poverty_rate_change,\n", + " \"child_poverty_percent_change\": child_poverty_percent_change,\n", + " \"deep_poverty_baseline_rate\": deep_poverty_baseline_rate,\n", + " \"deep_poverty_reform_rate\": deep_poverty_reform_rate,\n", + " \"deep_poverty_rate_change\": deep_poverty_rate_change,\n", + " \"deep_poverty_percent_change\": deep_poverty_percent_change,\n", + " \"deep_child_poverty_baseline_rate\": deep_child_poverty_baseline_rate,\n", + " \"deep_child_poverty_reform_rate\": deep_child_poverty_reform_rate,\n", + " \"deep_child_poverty_rate_change\": deep_child_poverty_rate_change,\n", + " \"deep_child_poverty_percent_change\": deep_child_poverty_percent_change,\n", + " \"by_income_bracket\": by_income_bracket,\n", + "}\n", + "\n", + "# Export summary to CSV\n", + "summary_df = pd.DataFrame([\n", + " {\"Metric\": \"Total cost\", \"Value\": f\"${total_cost/1e6:,.2f}M\"},\n", + " {\"Metric\": \"Total households\", \"Value\": f\"{total_households:,.0f}\"},\n", + " {\"Metric\": \"Total people\", \"Value\": f\"{total_people:,.0f}\"},\n", + " {\"Metric\": \"Winners (people)\", \"Value\": f\"{winners:,.0f} ({winners_rate:.1f}%)\"},\n", + " {\"Metric\": \"Losers (people)\", \"Value\": f\"{losers:,.0f} ({losers_rate:.1f}%)\"},\n", + " {\"Metric\": \"Average benefit (per HH)\", \"Value\": f\"${avg_benefit:,.0f}\"},\n", + " {\"Metric\": \"Overall poverty change\", \"Value\": f\"{poverty_rate_change:+.2f} pp ({poverty_percent_change:+.1f}%)\"},\n", + " {\"Metric\": \"Child poverty change\", \"Value\": f\"{child_poverty_rate_change:+.2f} pp ({child_poverty_percent_change:+.1f}%)\"},\n", + " {\"Metric\": \"Deep poverty change\", \"Value\": f\"{deep_poverty_rate_change:+.2f} pp ({deep_poverty_percent_change:+.1f}%)\"},\n", + " {\"Metric\": \"Deep child poverty change\", \"Value\": f\"{deep_child_poverty_rate_change:+.2f} pp ({deep_child_poverty_percent_change:+.1f}%)\"},\n", + "])\n", + "print(summary_df.to_string(index=False))\n", + "\n", + "summary_df.to_csv(\"mt_ctc_results.csv\", index=False)\n", + "print(\"\\nExported to: mt_ctc_results.csv\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/us/states/mt/mt_ctc_results.csv b/us/states/mt/mt_ctc_results.csv new file mode 100644 index 0000000..e4692c1 --- /dev/null +++ b/us/states/mt/mt_ctc_results.csv @@ -0,0 +1,11 @@ +Metric,Value +Total cost,$257.54M +Total households,"410,732" +Total people,"1,156,438" +Winners (people),"415,596 (35.9%)" +Losers (people),0 (0.0%) +Average benefit (per HH),"$2,809" +Overall poverty change,-0.61 pp (-4.3%) +Child poverty change,-1.49 pp (-11.6%) +Deep poverty change,-0.09 pp (-1.4%) +Deep child poverty change,-0.23 pp (-4.3%) diff --git a/us/states/mt/mt_newborn_credit_results.csv b/us/states/mt/mt_newborn_credit_results.csv new file mode 100644 index 0000000..dabb99d --- /dev/null +++ b/us/states/mt/mt_newborn_credit_results.csv @@ -0,0 +1,10 @@ +Metric,Value +Children under age 1,"10,614" +Children benefiting,"7,296" +Share benefiting,68.7% +Total cost,$6.85M +Average benefit per child,$939 +People gaining income,"34,515" +Share of population gaining,0.0% +Overall poverty reduction,87 people +Child poverty reduction,43 children diff --git a/us/states/mt/mt_young_child_credit_results.csv b/us/states/mt/mt_young_child_credit_results.csv new file mode 100644 index 0000000..c5d0618 --- /dev/null +++ b/us/states/mt/mt_young_child_credit_results.csv @@ -0,0 +1,10 @@ +Metric,Value +Children under age 6,"65,366" +Children benefiting,"31,606" +Share benefiting,48.4% +Total cost,$25.50M +Average benefit per child,$807 +People gaining income,"100,388" +Share of population gaining,0.1% +Overall poverty reduction,"2,367 people" +Child poverty reduction,"1,185 children" diff --git a/us/states/mt/newborn_credit_analysis.ipynb b/us/states/mt/newborn_credit_analysis.ipynb new file mode 100644 index 0000000..784fbee --- /dev/null +++ b/us/states/mt/newborn_credit_analysis.ipynb @@ -0,0 +1,649 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "header", + "metadata": {}, + "source": [ + "# Montana Newborn Credit Reform Analysis (2027)\n", + "\n", + "This notebook analyzes the impact of Montana's proposed newborn credit reform.\n", + "\n", + "## Reform Details\n", + "- **Credit Amount**: $1,000 per qualifying child under age 1\n", + "- **Eligibility**: Children under 1 year old who are dependents with SSN\n", + "- **Phase-out thresholds**:\n", + " - Joint/Surviving Spouse: $120,000 AGI\n", + " - Single/Head of Household/Separate: $60,000 AGI\n", + "- **Phase-out**: $50 reduction per $1,000 of income above threshold\n", + "- **Refundable**: Yes\n", + "\n", + "## Analysis Goals\n", + "1. Count children under age 1 in Montana\n", + "2. Count children benefiting from the reform\n", + "3. Calculate budgetary impact\n", + "4. Measure poverty impacts" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from policyengine_us import Microsimulation\n", + "from policyengine_core.reforms import Reform\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "MT_DATASET = \"hf://policyengine/policyengine-us-data/states/MT.h5\"\n", + "PERIOD = 2027" + ] + }, + { + "cell_type": "markdown", + "id": "reform-header", + "metadata": {}, + "source": [ + "## Define Reform\n", + "\n", + "The reform enables the Montana newborn credit by setting `in_effect` to `true` for 2027." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "define-reform", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reform function defined!\n" + ] + } + ], + "source": [ + "def create_newborn_credit_reform():\n", + " \"\"\"Enable Montana newborn credit reform for 2027.\"\"\"\n", + " reform = Reform.from_dict(\n", + " {\n", + " \"gov.contrib.states.mt.newborn_credit.in_effect\": {\n", + " \"2027-01-01.2100-12-31\": True\n", + " }\n", + " },\n", + " country_id=\"us\",\n", + " )\n", + " return reform\n", + "\n", + "print(\"Reform function defined!\")" + ] + }, + { + "cell_type": "markdown", + "id": "load-header", + "metadata": {}, + "source": [ + "## Load Simulations" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "load-sims", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading baseline (current law - no newborn credit)...\n", + "Baseline loaded\n", + "\n", + "Loading reform (newborn credit enabled)...\n", + "Reform loaded\n", + "\n", + "============================================================\n", + "All simulations ready!\n", + "============================================================\n" + ] + } + ], + "source": [ + "print(\"Loading baseline (current law - no newborn credit)...\")\n", + "baseline = Microsimulation(dataset=MT_DATASET)\n", + "print(\"Baseline loaded\")\n", + "\n", + "print(\"\\nLoading reform (newborn credit enabled)...\")\n", + "reform = create_newborn_credit_reform()\n", + "reformed = Microsimulation(dataset=MT_DATASET, reform=reform)\n", + "print(\"Reform loaded\")\n", + "\n", + "print(\"\\n\" + \"=\"*60)\n", + "print(\"All simulations ready!\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "children-header", + "metadata": {}, + "source": [ + "## Count Children Under Age 1" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "count-children", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "CHILDREN UNDER AGE 1 IN MONTANA (2027)\n", + "============================================================\n", + "Total children under age 1: 10,614\n", + "Dependent children under age 1: 10,614\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Get age and dependent status (MicroSeries with embedded weights)\n", + "age = baseline.calc(\"age\", period=PERIOD)\n", + "is_dependent = baseline.calc(\"is_tax_unit_dependent\", period=PERIOD)\n", + "\n", + "# Children under 1 (newborns) - MicroSeries handles weighting automatically\n", + "is_under_1 = age < 1\n", + "children_under_1 = is_under_1.sum()\n", + "\n", + "# Dependent children under 1\n", + "dependent_under_1 = is_under_1 & is_dependent\n", + "dependent_children_under_1 = dependent_under_1.sum()\n", + "\n", + "# Total population for percentage calculations\n", + "total_population = baseline.calc(\"person_weight\", period=PERIOD).sum()\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"CHILDREN UNDER AGE 1 IN MONTANA ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"Total children under age 1: {children_under_1:,.0f}\")\n", + "print(f\"Dependent children under age 1: {dependent_children_under_1:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "beneficiaries-header", + "metadata": {}, + "source": [ + "## Children Benefiting from Reform\n", + "\n", + "We measure how many children under 1 are in households that gain from the reform." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "beneficiaries", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "CHILDREN BENEFITING FROM NEWBORN CREDIT (2027)\n", + "============================================================\n", + "Children under 1 benefiting: 7,296\n", + "Dependent children under 1 benefiting: 7,296\n", + "Percentage of children under 1: 68.7%\n", + "Average benefit per benefiting child: $1,092\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Calculate household-level income change (MicroSeries handles weighting)\n", + "baseline_income = baseline.calc(\"household_net_income\", period=PERIOD, map_to=\"person\")\n", + "reformed_income = reformed.calc(\"household_net_income\", period=PERIOD, map_to=\"person\")\n", + "income_change = reformed_income - baseline_income\n", + "\n", + "# Children under 1 who benefit (in households gaining more than $1)\n", + "children_benefiting_mask = is_under_1 & (income_change > 1)\n", + "children_benefiting = children_benefiting_mask.sum()\n", + "\n", + "# Dependent children under 1 who benefit\n", + "dependent_children_benefiting_mask = dependent_under_1 & (income_change > 1)\n", + "dependent_children_benefiting = dependent_children_benefiting_mask.sum()\n", + "\n", + "# Average benefit for children who benefit\n", + "if children_benefiting > 0:\n", + " avg_benefit_children = (income_change * children_benefiting_mask).sum() / children_benefiting\n", + "else:\n", + " avg_benefit_children = 0\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"CHILDREN BENEFITING FROM NEWBORN CREDIT ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"Children under 1 benefiting: {children_benefiting:,.0f}\")\n", + "print(f\"Dependent children under 1 benefiting: {dependent_children_benefiting:,.0f}\")\n", + "print(f\"Percentage of children under 1: {children_benefiting / children_under_1 * 100:.1f}%\")\n", + "print(f\"Average benefit per benefiting child: ${avg_benefit_children:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "qgie4pp8pdl", + "metadata": {}, + "source": [ + "## Households and Children" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "oj2gcuvy4xb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "HOUSEHOLDS AND CHILDREN (2027)\n", + "============================================================\n", + "\n", + "HOUSEHOLDS WITH CHILDREN:\n", + " Total households with children: 144,771\n", + " Households with children benefiting: 6,691\n", + " Percentage benefiting: 4.6%\n", + "\n", + "CHILDREN (under 18):\n", + " Total children: 241,202\n", + " Children benefiting: 17,769\n", + " Percentage benefiting: 7.4%\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Calculate households and children metrics\n", + "is_child_person = age < 18\n", + "\n", + "# Household-level income change\n", + "baseline_hh_income = baseline.calc(\"household_net_income\", period=PERIOD, map_to=\"household\")\n", + "reformed_hh_income = reformed.calc(\"household_net_income\", period=PERIOD, map_to=\"household\")\n", + "hh_income_change = reformed_hh_income - baseline_hh_income\n", + "\n", + "# Household weights\n", + "household_weight = baseline.calc(\"household_weight\", period=PERIOD)\n", + "weight_arr = np.array(household_weight)\n", + "change_arr = np.array(hh_income_change)\n", + "\n", + "# Household-level: count children per household\n", + "children_per_hh = baseline.calc(\"tax_unit_count_dependents\", period=PERIOD, map_to=\"household\")\n", + "children_per_hh_arr = np.array(children_per_hh)\n", + "has_children = children_per_hh_arr > 0\n", + "\n", + "# Total households with children\n", + "hh_with_children = float(weight_arr[has_children].sum())\n", + "\n", + "# Households with children that benefit\n", + "hh_with_children_benefit = float(weight_arr[has_children & (change_arr > 1)].sum())\n", + "hh_with_children_benefit_pct = hh_with_children_benefit / hh_with_children * 100\n", + "\n", + "# Total children (using person weights)\n", + "person_weight = baseline.calc(\"person_weight\", period=PERIOD)\n", + "pw_arr = np.array(person_weight)\n", + "is_child_arr = np.array(is_child_person)\n", + "total_children = float((pw_arr * is_child_arr).sum())\n", + "\n", + "# Children benefiting\n", + "children_benefit_mask = is_child_arr & (np.array(income_change) > 1)\n", + "children_benefiting_total = float((pw_arr * children_benefit_mask).sum())\n", + "children_benefit_pct = children_benefiting_total / total_children * 100\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"HOUSEHOLDS AND CHILDREN ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"\\nHOUSEHOLDS WITH CHILDREN:\")\n", + "print(f\" Total households with children: {hh_with_children:,.0f}\")\n", + "print(f\" Households with children benefiting: {hh_with_children_benefit:,.0f}\")\n", + "print(f\" Percentage benefiting: {hh_with_children_benefit_pct:.1f}%\")\n", + "print(f\"\\nCHILDREN (under 18):\")\n", + "print(f\" Total children: {total_children:,.0f}\")\n", + "print(f\" Children benefiting: {children_benefiting_total:,.0f}\")\n", + "print(f\" Percentage benefiting: {children_benefit_pct:.1f}%\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "budget-header", + "metadata": {}, + "source": [ + "## Budgetary Impact" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "budget", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "BUDGETARY IMPACT (2027)\n", + "============================================================\n", + "Total cost of newborn credit: $6.85 million\n", + "Cost per benefiting child: $939\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Calculate total cost of the reform\n", + "baseline_total = baseline.calc(\"household_net_income\", period=PERIOD, map_to=\"household\").sum()\n", + "reformed_total = reformed.calc(\"household_net_income\", period=PERIOD, map_to=\"household\").sum()\n", + "total_cost = reformed_total - baseline_total\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"BUDGETARY IMPACT ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"Total cost of newborn credit: ${total_cost/1e6:,.2f} million\")\n", + "print(f\"Cost per benefiting child: ${total_cost / children_benefiting:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "poverty-header", + "metadata": {}, + "source": [ + "## Poverty Impact" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "poverty", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "POVERTY IMPACT (2027)\n", + "============================================================\n", + "\n", + "OVERALL POVERTY:\n", + " Baseline poverty rate: 14.19%\n", + " Reformed poverty rate: 14.18%\n", + " Change: -0.01 pp\n", + " People lifted: 87\n", + "\n", + "CHILD POVERTY (under 18):\n", + " Baseline child poverty rate: 12.84%\n", + " Reformed child poverty rate: 12.82%\n", + " Change: -0.02 pp\n", + " Children lifted: 43\n", + "============================================================\n" + ] + } + ], + "source": [ + "def calculate_poverty_metrics(sim, period, child_only=False):\n", + " \"\"\"Calculate poverty metrics using MicroSeries.\"\"\"\n", + " age = sim.calc(\"age\", period=period)\n", + " in_poverty = sim.calc(\"person_in_poverty\", period=period)\n", + " \n", + " if child_only:\n", + " mask = age < 18\n", + " poverty_rate = (in_poverty * mask).sum() / mask.sum()\n", + " people_in_poverty = (in_poverty * mask).sum()\n", + " else:\n", + " poverty_rate = in_poverty.mean()\n", + " people_in_poverty = in_poverty.sum()\n", + " \n", + " return poverty_rate, people_in_poverty\n", + "\n", + "# Overall poverty\n", + "baseline_poverty_rate, baseline_poverty_count = calculate_poverty_metrics(baseline, PERIOD)\n", + "reformed_poverty_rate, reformed_poverty_count = calculate_poverty_metrics(reformed, PERIOD)\n", + "\n", + "# Child poverty\n", + "baseline_child_poverty_rate, baseline_child_poverty_count = calculate_poverty_metrics(baseline, PERIOD, child_only=True)\n", + "reformed_child_poverty_rate, reformed_child_poverty_count = calculate_poverty_metrics(reformed, PERIOD, child_only=True)\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"POVERTY IMPACT ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(\"\\nOVERALL POVERTY:\")\n", + "print(f\" Baseline poverty rate: {baseline_poverty_rate*100:.2f}%\")\n", + "print(f\" Reformed poverty rate: {reformed_poverty_rate*100:.2f}%\")\n", + "print(f\" Change: {(reformed_poverty_rate - baseline_poverty_rate)*100:.2f} pp\")\n", + "print(f\" People lifted: {baseline_poverty_count - reformed_poverty_count:,.0f}\")\n", + "\n", + "print(\"\\nCHILD POVERTY (under 18):\")\n", + "print(f\" Baseline child poverty rate: {baseline_child_poverty_rate*100:.2f}%\")\n", + "print(f\" Reformed child poverty rate: {reformed_child_poverty_rate*100:.2f}%\")\n", + "print(f\" Change: {(reformed_child_poverty_rate - baseline_child_poverty_rate)*100:.2f} pp\")\n", + "print(f\" Children lifted: {baseline_child_poverty_count - reformed_child_poverty_count:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "winners-header", + "metadata": {}, + "source": [ + "## Winners and Losers" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "winners", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "WINNERS AND LOSERS (2027)\n", + "============================================================\n", + "Winners (gain > $1): 34,515 (0.0%)\n", + "Losers (lose > $1): 0 (0.0%)\n", + "Unchanged: 1,121,923 (0.9%)\n", + "\n", + "Average gain (winners): $1,044\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Person-level analysis (MicroSeries handles weighting)\n", + "person_income_change = reformed.calc(\"household_net_income\", period=PERIOD, map_to=\"person\") - \\\n", + " baseline.calc(\"household_net_income\", period=PERIOD, map_to=\"person\")\n", + "\n", + "winners = (person_income_change > 1).sum()\n", + "losers = (person_income_change < -1).sum()\n", + "unchanged = ((person_income_change >= -1) & (person_income_change <= 1)).sum()\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"WINNERS AND LOSERS ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"Winners (gain > $1): {winners:,.0f} ({winners/total_population*100:.1f}%)\")\n", + "print(f\"Losers (lose > $1): {losers:,.0f} ({losers/total_population*100:.1f}%)\")\n", + "print(f\"Unchanged: {unchanged:,.0f} ({unchanged/total_population*100:.1f}%)\")\n", + "print(f\"\\nAverage gain (winners): ${(person_income_change * (person_income_change > 1)).sum() / winners:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "summary-header", + "metadata": {}, + "source": [ + "## Summary" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "summary", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "======================================================================\n", + "MONTANA NEWBORN CREDIT REFORM SUMMARY\n", + "======================================================================\n", + "\n", + "Year: 2027\n", + "\n", + "Reform: $1,000 refundable credit per child under age 1\n", + " Phase-out starts at $60k (single) / $120k (joint) AGI\n", + "\n", + "----------------------------------------------------------------------\n", + "KEY FINDINGS:\n", + "----------------------------------------------------------------------\n", + " Children under age 1 in Montana: 10,614\n", + " Children benefiting from reform: 7,296\n", + " Share of newborns benefiting: 68.7%\n", + "\n", + " Total budgetary cost: $6.85 million\n", + " Average benefit per child: $939\n", + "\n", + " People gaining income: 34,515 (0.0% of population)\n", + " Overall poverty reduction: 87 people\n", + " Child poverty reduction: 43 children\n", + "======================================================================\n" + ] + } + ], + "source": [ + "print(\"\\n\" + \"=\"*70)\n", + "print(\"MONTANA NEWBORN CREDIT REFORM SUMMARY\")\n", + "print(\"=\"*70)\n", + "print(f\"\\nYear: {PERIOD}\")\n", + "print(f\"\\nReform: $1,000 refundable credit per child under age 1\")\n", + "print(f\" Phase-out starts at $60k (single) / $120k (joint) AGI\")\n", + "print(\"\\n\" + \"-\"*70)\n", + "print(\"KEY FINDINGS:\")\n", + "print(\"-\"*70)\n", + "print(f\" Children under age 1 in Montana: {children_under_1:,.0f}\")\n", + "print(f\" Children benefiting from reform: {children_benefiting:,.0f}\")\n", + "print(f\" Share of newborns benefiting: {children_benefiting / children_under_1 * 100:.1f}%\")\n", + "print(f\"\\n Total budgetary cost: ${total_cost/1e6:,.2f} million\")\n", + "print(f\" Average benefit per child: ${total_cost / children_benefiting:,.0f}\")\n", + "print(f\"\\n People gaining income: {winners:,.0f} ({winners/total_population*100:.1f}% of population)\")\n", + "print(f\" Overall poverty reduction: {(baseline_poverty_count - reformed_poverty_count):,.0f} people\")\n", + "print(f\" Child poverty reduction: {(baseline_child_poverty_count - reformed_child_poverty_count):,.0f} children\")\n", + "print(\"=\"*70)" + ] + }, + { + "cell_type": "markdown", + "id": "export-header", + "metadata": {}, + "source": [ + "## Export Results" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "export", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Metric Value\n", + " Children under age 1 10,614\n", + " Children benefiting 7,296\n", + " Share benefiting 68.7%\n", + " Total cost $6.85M\n", + " Average benefit per child $939\n", + " People gaining income 34,515\n", + "Share of population gaining 0.0%\n", + " Overall poverty reduction 87 people\n", + " Child poverty reduction 43 children\n", + "\n", + "Exported to: mt_newborn_credit_results.csv\n" + ] + } + ], + "source": [ + "results = {\n", + " \"Metric\": [\n", + " \"Children under age 1\",\n", + " \"Children benefiting\",\n", + " \"Share benefiting\",\n", + " \"Total cost\",\n", + " \"Average benefit per child\",\n", + " \"People gaining income\",\n", + " \"Share of population gaining\",\n", + " \"Overall poverty reduction\",\n", + " \"Child poverty reduction\"\n", + " ],\n", + " \"Value\": [\n", + " f\"{children_under_1:,.0f}\",\n", + " f\"{children_benefiting:,.0f}\",\n", + " f\"{children_benefiting / children_under_1 * 100:.1f}%\",\n", + " f\"${total_cost/1e6:,.2f}M\",\n", + " f\"${total_cost / children_benefiting:,.0f}\",\n", + " f\"{winners:,.0f}\",\n", + " f\"{winners/total_population*100:.1f}%\",\n", + " f\"{(baseline_poverty_count - reformed_poverty_count):,.0f} people\",\n", + " f\"{(baseline_child_poverty_count - reformed_child_poverty_count):,.0f} children\"\n", + " ]\n", + "}\n", + "\n", + "df_results = pd.DataFrame(results)\n", + "print(df_results.to_string(index=False))\n", + "\n", + "df_results.to_csv(\"mt_newborn_credit_results.csv\", index=False)\n", + "print(\"\\nExported to: mt_newborn_credit_results.csv\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/us/states/mt/young_child_credit_analysis.ipynb b/us/states/mt/young_child_credit_analysis.ipynb new file mode 100644 index 0000000..ffb9879 --- /dev/null +++ b/us/states/mt/young_child_credit_analysis.ipynb @@ -0,0 +1,668 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "header", + "metadata": {}, + "source": [ + "# Montana Young Child Credit Reform Analysis (2027)\n", + "\n", + "This notebook analyzes the impact of an expanded Montana child credit for children under 6.\n", + "\n", + "## Reform Details\n", + "- **Credit Amount**: $1,000 per qualifying child under age 6\n", + "- **Eligibility**: Children under 6 years old who are dependents with SSN\n", + "- **Phase-out thresholds**:\n", + " - Joint/Surviving Spouse: $80,000 AGI\n", + " - Single/Head of Household/Separate: $40,000 AGI\n", + "- **Phase-out**: $50 reduction per $1,000 of income above threshold\n", + "- **Refundable**: Yes\n", + "\n", + "## Analysis Goals\n", + "1. Count children under age 6 in Montana\n", + "2. Count children benefiting from the reform\n", + "3. Calculate budgetary impact\n", + "4. Measure poverty impacts" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from policyengine_us import Microsimulation\n", + "from policyengine_core.reforms import Reform\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "MT_DATASET = \"hf://policyengine/policyengine-us-data/states/MT.h5\"\n", + "PERIOD = 2027\n", + "AGE_LIMIT = 6" + ] + }, + { + "cell_type": "markdown", + "id": "reform-header", + "metadata": {}, + "source": [ + "## Define Reform\n", + "\n", + "The reform enables the Montana child credit with expanded age limit (under 6) and modified phase-out thresholds for 2027." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "define-reform", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reform function defined!\n" + ] + } + ], + "source": [ + "def create_young_child_credit_reform():\n", + " \"\"\"Enable Montana young child credit reform for 2027.\"\"\"\n", + " reform = Reform.from_dict(\n", + " {\n", + " \"gov.contrib.states.mt.newborn_credit.age_limit\": {\n", + " \"2027-01-01.2100-12-31\": 6\n", + " },\n", + " \"gov.contrib.states.mt.newborn_credit.in_effect\": {\n", + " \"2027-01-01.2100-12-31\": True\n", + " },\n", + " \"gov.contrib.states.mt.newborn_credit.reduction.threshold.JOINT\": {\n", + " \"2027-01-01.2100-12-31\": 80000\n", + " },\n", + " \"gov.contrib.states.mt.newborn_credit.reduction.threshold.SINGLE\": {\n", + " \"2027-01-01.2100-12-31\": 40000\n", + " },\n", + " \"gov.contrib.states.mt.newborn_credit.reduction.threshold.SEPARATE\": {\n", + " \"2027-01-01.2100-12-31\": 40000\n", + " },\n", + " \"gov.contrib.states.mt.newborn_credit.reduction.threshold.SURVIVING_SPOUSE\": {\n", + " \"2027-01-01.2100-12-31\": 80000\n", + " },\n", + " \"gov.contrib.states.mt.newborn_credit.reduction.threshold.HEAD_OF_HOUSEHOLD\": {\n", + " \"2027-01-01.2100-12-31\": 40000\n", + " }\n", + " },\n", + " country_id=\"us\",\n", + " )\n", + " return reform\n", + "\n", + "print(\"Reform function defined!\")" + ] + }, + { + "cell_type": "markdown", + "id": "load-header", + "metadata": {}, + "source": [ + "## Load Simulations" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "load-sims", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading baseline (current law - no young child credit)...\n", + "Baseline loaded\n", + "\n", + "Loading reform (young child credit enabled)...\n", + "Reform loaded\n", + "\n", + "============================================================\n", + "All simulations ready!\n", + "============================================================\n" + ] + } + ], + "source": [ + "print(\"Loading baseline (current law - no young child credit)...\")\n", + "baseline = Microsimulation(dataset=MT_DATASET)\n", + "print(\"Baseline loaded\")\n", + "\n", + "print(\"\\nLoading reform (young child credit enabled)...\")\n", + "reform = create_young_child_credit_reform()\n", + "reformed = Microsimulation(dataset=MT_DATASET, reform=reform)\n", + "print(\"Reform loaded\")\n", + "\n", + "print(\"\\n\" + \"=\"*60)\n", + "print(\"All simulations ready!\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "children-header", + "metadata": {}, + "source": [ + "## Count Children Under Age 6" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "count-children", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "CHILDREN UNDER AGE 6 IN MONTANA (2027)\n", + "============================================================\n", + "Total children under age 6: 65,366\n", + "Dependent children under age 6: 65,366\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Get age and dependent status (MicroSeries with embedded weights)\n", + "age = baseline.calc(\"age\", period=PERIOD)\n", + "is_dependent = baseline.calc(\"is_tax_unit_dependent\", period=PERIOD)\n", + "\n", + "# Children under 6 - MicroSeries handles weighting automatically\n", + "is_under_6 = age < AGE_LIMIT\n", + "children_under_6 = is_under_6.sum()\n", + "\n", + "# Dependent children under 6\n", + "dependent_under_6 = is_under_6 & is_dependent\n", + "dependent_children_under_6 = dependent_under_6.sum()\n", + "\n", + "# Total population for percentage calculations\n", + "total_population = baseline.calc(\"person_weight\", period=PERIOD).sum()\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"CHILDREN UNDER AGE {AGE_LIMIT} IN MONTANA ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"Total children under age {AGE_LIMIT}: {children_under_6:,.0f}\")\n", + "print(f\"Dependent children under age {AGE_LIMIT}: {dependent_children_under_6:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "beneficiaries-header", + "metadata": {}, + "source": [ + "## Children Benefiting from Reform\n", + "\n", + "We measure how many children under 6 are in households that gain from the reform." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "beneficiaries", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "CHILDREN BENEFITING FROM YOUNG CHILD CREDIT (2027)\n", + "============================================================\n", + "Children under 6 benefiting: 31,606\n", + "Dependent children under 6 benefiting: 31,606\n", + "Percentage of children under 6: 48.4%\n", + "Average benefit per benefiting child: $1,401\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Calculate household-level income change (MicroSeries handles weighting)\n", + "baseline_income = baseline.calc(\"household_net_income\", period=PERIOD, map_to=\"person\")\n", + "reformed_income = reformed.calc(\"household_net_income\", period=PERIOD, map_to=\"person\")\n", + "income_change = reformed_income - baseline_income\n", + "\n", + "# Children under 6 who benefit (in households gaining more than $1)\n", + "children_benefiting_mask = is_under_6 & (income_change > 1)\n", + "children_benefiting = children_benefiting_mask.sum()\n", + "\n", + "# Dependent children under 6 who benefit\n", + "dependent_children_benefiting_mask = dependent_under_6 & (income_change > 1)\n", + "dependent_children_benefiting = dependent_children_benefiting_mask.sum()\n", + "\n", + "# Average benefit for children who benefit\n", + "if children_benefiting > 0:\n", + " avg_benefit_children = (income_change * children_benefiting_mask).sum() / children_benefiting\n", + "else:\n", + " avg_benefit_children = 0\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"CHILDREN BENEFITING FROM YOUNG CHILD CREDIT ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"Children under {AGE_LIMIT} benefiting: {children_benefiting:,.0f}\")\n", + "print(f\"Dependent children under {AGE_LIMIT} benefiting: {dependent_children_benefiting:,.0f}\")\n", + "print(f\"Percentage of children under {AGE_LIMIT}: {children_benefiting / children_under_6 * 100:.1f}%\")\n", + "print(f\"Average benefit per benefiting child: ${avg_benefit_children:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "apzso409h7l", + "metadata": {}, + "source": [ + "## Households and Children" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "dswx3h8vx4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "HOUSEHOLDS AND CHILDREN (2027)\n", + "============================================================\n", + "\n", + "HOUSEHOLDS WITH CHILDREN:\n", + " Total households with children: 144,771\n", + " Households with children benefiting: 21,390\n", + " Percentage benefiting: 14.8%\n", + "\n", + "CHILDREN (under 18):\n", + " Total children: 241,202\n", + " Children benefiting: 49,126\n", + " Percentage benefiting: 20.4%\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Calculate households and children metrics\n", + "is_child_person = age < 18\n", + "\n", + "# Household-level income change\n", + "baseline_hh_income = baseline.calc(\"household_net_income\", period=PERIOD, map_to=\"household\")\n", + "reformed_hh_income = reformed.calc(\"household_net_income\", period=PERIOD, map_to=\"household\")\n", + "hh_income_change = reformed_hh_income - baseline_hh_income\n", + "\n", + "# Household weights\n", + "household_weight = baseline.calc(\"household_weight\", period=PERIOD)\n", + "weight_arr = np.array(household_weight)\n", + "change_arr = np.array(hh_income_change)\n", + "\n", + "# Household-level: count children per household\n", + "children_per_hh = baseline.calc(\"tax_unit_count_dependents\", period=PERIOD, map_to=\"household\")\n", + "children_per_hh_arr = np.array(children_per_hh)\n", + "has_children = children_per_hh_arr > 0\n", + "\n", + "# Total households with children\n", + "hh_with_children = float(weight_arr[has_children].sum())\n", + "\n", + "# Households with children that benefit\n", + "hh_with_children_benefit = float(weight_arr[has_children & (change_arr > 1)].sum())\n", + "hh_with_children_benefit_pct = hh_with_children_benefit / hh_with_children * 100\n", + "\n", + "# Total children (using person weights)\n", + "person_weight = baseline.calc(\"person_weight\", period=PERIOD)\n", + "pw_arr = np.array(person_weight)\n", + "is_child_arr = np.array(is_child_person)\n", + "total_children = float((pw_arr * is_child_arr).sum())\n", + "\n", + "# Children benefiting\n", + "children_benefit_mask = is_child_arr & (np.array(income_change) > 1)\n", + "children_benefiting_total = float((pw_arr * children_benefit_mask).sum())\n", + "children_benefit_pct = children_benefiting_total / total_children * 100\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"HOUSEHOLDS AND CHILDREN ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"\\nHOUSEHOLDS WITH CHILDREN:\")\n", + "print(f\" Total households with children: {hh_with_children:,.0f}\")\n", + "print(f\" Households with children benefiting: {hh_with_children_benefit:,.0f}\")\n", + "print(f\" Percentage benefiting: {hh_with_children_benefit_pct:.1f}%\")\n", + "print(f\"\\nCHILDREN (under 18):\")\n", + "print(f\" Total children: {total_children:,.0f}\")\n", + "print(f\" Children benefiting: {children_benefiting_total:,.0f}\")\n", + "print(f\" Percentage benefiting: {children_benefit_pct:.1f}%\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "budget-header", + "metadata": {}, + "source": [ + "## Budgetary Impact" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "budget", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "BUDGETARY IMPACT (2027)\n", + "============================================================\n", + "Total cost of young child credit: $25.50 million\n", + "Cost per benefiting child: $807\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Calculate total cost of the reform\n", + "baseline_total = baseline.calc(\"household_net_income\", period=PERIOD, map_to=\"household\").sum()\n", + "reformed_total = reformed.calc(\"household_net_income\", period=PERIOD, map_to=\"household\").sum()\n", + "total_cost = reformed_total - baseline_total\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"BUDGETARY IMPACT ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"Total cost of young child credit: ${total_cost/1e6:,.2f} million\")\n", + "print(f\"Cost per benefiting child: ${total_cost / children_benefiting:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "poverty-header", + "metadata": {}, + "source": [ + "## Poverty Impact" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "poverty", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "POVERTY IMPACT (2027)\n", + "============================================================\n", + "\n", + "OVERALL POVERTY:\n", + " Baseline poverty rate: 14.19%\n", + " Reformed poverty rate: 13.98%\n", + " Change: -0.20 pp\n", + " People lifted: 2,367\n", + "\n", + "CHILD POVERTY (under 18):\n", + " Baseline child poverty rate: 12.84%\n", + " Reformed child poverty rate: 12.35%\n", + " Change: -0.49 pp\n", + " Children lifted: 1,185\n", + "============================================================\n" + ] + } + ], + "source": [ + "def calculate_poverty_metrics(sim, period, child_only=False):\n", + " \"\"\"Calculate poverty metrics using MicroSeries.\"\"\"\n", + " age = sim.calc(\"age\", period=period)\n", + " in_poverty = sim.calc(\"person_in_poverty\", period=period)\n", + " \n", + " if child_only:\n", + " mask = age < 18\n", + " poverty_rate = (in_poverty * mask).sum() / mask.sum()\n", + " people_in_poverty = (in_poverty * mask).sum()\n", + " else:\n", + " poverty_rate = in_poverty.mean()\n", + " people_in_poverty = in_poverty.sum()\n", + " \n", + " return poverty_rate, people_in_poverty\n", + "\n", + "# Overall poverty\n", + "baseline_poverty_rate, baseline_poverty_count = calculate_poverty_metrics(baseline, PERIOD)\n", + "reformed_poverty_rate, reformed_poverty_count = calculate_poverty_metrics(reformed, PERIOD)\n", + "\n", + "# Child poverty\n", + "baseline_child_poverty_rate, baseline_child_poverty_count = calculate_poverty_metrics(baseline, PERIOD, child_only=True)\n", + "reformed_child_poverty_rate, reformed_child_poverty_count = calculate_poverty_metrics(reformed, PERIOD, child_only=True)\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"POVERTY IMPACT ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(\"\\nOVERALL POVERTY:\")\n", + "print(f\" Baseline poverty rate: {baseline_poverty_rate*100:.2f}%\")\n", + "print(f\" Reformed poverty rate: {reformed_poverty_rate*100:.2f}%\")\n", + "print(f\" Change: {(reformed_poverty_rate - baseline_poverty_rate)*100:.2f} pp\")\n", + "print(f\" People lifted: {baseline_poverty_count - reformed_poverty_count:,.0f}\")\n", + "\n", + "print(\"\\nCHILD POVERTY (under 18):\")\n", + "print(f\" Baseline child poverty rate: {baseline_child_poverty_rate*100:.2f}%\")\n", + "print(f\" Reformed child poverty rate: {reformed_child_poverty_rate*100:.2f}%\")\n", + "print(f\" Change: {(reformed_child_poverty_rate - baseline_child_poverty_rate)*100:.2f} pp\")\n", + "print(f\" Children lifted: {baseline_child_poverty_count - reformed_child_poverty_count:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "winners-header", + "metadata": {}, + "source": [ + "## Winners and Losers" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "winners", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================================================\n", + "WINNERS AND LOSERS (2027)\n", + "============================================================\n", + "Winners (gain > $1): 100,388 (0.1%)\n", + "Losers (lose > $1): 0 (0.0%)\n", + "Unchanged: 1,056,049 (0.8%)\n", + "\n", + "Average gain (winners): $1,240\n", + "============================================================\n" + ] + } + ], + "source": [ + "# Person-level analysis (MicroSeries handles weighting)\n", + "person_income_change = reformed.calc(\"household_net_income\", period=PERIOD, map_to=\"person\") - \\\n", + " baseline.calc(\"household_net_income\", period=PERIOD, map_to=\"person\")\n", + "\n", + "winners = (person_income_change > 1).sum()\n", + "losers = (person_income_change < -1).sum()\n", + "unchanged = ((person_income_change >= -1) & (person_income_change <= 1)).sum()\n", + "\n", + "print(\"=\"*60)\n", + "print(f\"WINNERS AND LOSERS ({PERIOD})\")\n", + "print(\"=\"*60)\n", + "print(f\"Winners (gain > $1): {winners:,.0f} ({winners/total_population*100:.1f}%)\")\n", + "print(f\"Losers (lose > $1): {losers:,.0f} ({losers/total_population*100:.1f}%)\")\n", + "print(f\"Unchanged: {unchanged:,.0f} ({unchanged/total_population*100:.1f}%)\")\n", + "print(f\"\\nAverage gain (winners): ${(person_income_change * (person_income_change > 1)).sum() / winners:,.0f}\")\n", + "print(\"=\"*60)" + ] + }, + { + "cell_type": "markdown", + "id": "summary-header", + "metadata": {}, + "source": [ + "## Summary" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "summary", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "======================================================================\n", + "MONTANA YOUNG CHILD CREDIT REFORM SUMMARY\n", + "======================================================================\n", + "\n", + "Year: 2027\n", + "\n", + "Reform: $1,000 refundable credit per child under age 6\n", + " Phase-out starts at $40k (single) / $80k (joint) AGI\n", + "\n", + "----------------------------------------------------------------------\n", + "KEY FINDINGS:\n", + "----------------------------------------------------------------------\n", + " Children under age 6 in Montana: 65,366\n", + " Children benefiting from reform: 31,606\n", + " Share of young children benefiting: 48.4%\n", + "\n", + " Total budgetary cost: $25.50 million\n", + " Average benefit per child: $807\n", + "\n", + " People gaining income: 100,388 (0.1% of population)\n", + " Overall poverty reduction: 2,367 people\n", + " Child poverty reduction: 1,185 children\n", + "======================================================================\n" + ] + } + ], + "source": [ + "print(\"\\n\" + \"=\"*70)\n", + "print(\"MONTANA YOUNG CHILD CREDIT REFORM SUMMARY\")\n", + "print(\"=\"*70)\n", + "print(f\"\\nYear: {PERIOD}\")\n", + "print(f\"\\nReform: $1,000 refundable credit per child under age {AGE_LIMIT}\")\n", + "print(f\" Phase-out starts at $40k (single) / $80k (joint) AGI\")\n", + "print(\"\\n\" + \"-\"*70)\n", + "print(\"KEY FINDINGS:\")\n", + "print(\"-\"*70)\n", + "print(f\" Children under age {AGE_LIMIT} in Montana: {children_under_6:,.0f}\")\n", + "print(f\" Children benefiting from reform: {children_benefiting:,.0f}\")\n", + "print(f\" Share of young children benefiting: {children_benefiting / children_under_6 * 100:.1f}%\")\n", + "print(f\"\\n Total budgetary cost: ${total_cost/1e6:,.2f} million\")\n", + "print(f\" Average benefit per child: ${total_cost / children_benefiting:,.0f}\")\n", + "print(f\"\\n People gaining income: {winners:,.0f} ({winners/total_population*100:.1f}% of population)\")\n", + "print(f\" Overall poverty reduction: {(baseline_poverty_count - reformed_poverty_count):,.0f} people\")\n", + "print(f\" Child poverty reduction: {(baseline_child_poverty_count - reformed_child_poverty_count):,.0f} children\")\n", + "print(\"=\"*70)" + ] + }, + { + "cell_type": "markdown", + "id": "export-header", + "metadata": {}, + "source": [ + "## Export Results" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "export", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Metric Value\n", + " Children under age 6 65,366\n", + " Children benefiting 31,606\n", + " Share benefiting 48.4%\n", + " Total cost $25.50M\n", + " Average benefit per child $807\n", + " People gaining income 100,388\n", + "Share of population gaining 0.1%\n", + " Overall poverty reduction 2,367 people\n", + " Child poverty reduction 1,185 children\n", + "\n", + "Exported to: mt_young_child_credit_results.csv\n" + ] + } + ], + "source": [ + "results = {\n", + " \"Metric\": [\n", + " f\"Children under age {AGE_LIMIT}\",\n", + " \"Children benefiting\",\n", + " \"Share benefiting\",\n", + " \"Total cost\",\n", + " \"Average benefit per child\",\n", + " \"People gaining income\",\n", + " \"Share of population gaining\",\n", + " \"Overall poverty reduction\",\n", + " \"Child poverty reduction\"\n", + " ],\n", + " \"Value\": [\n", + " f\"{children_under_6:,.0f}\",\n", + " f\"{children_benefiting:,.0f}\",\n", + " f\"{children_benefiting / children_under_6 * 100:.1f}%\",\n", + " f\"${total_cost/1e6:,.2f}M\",\n", + " f\"${total_cost / children_benefiting:,.0f}\",\n", + " f\"{winners:,.0f}\",\n", + " f\"{winners/total_population*100:.1f}%\",\n", + " f\"{(baseline_poverty_count - reformed_poverty_count):,.0f} people\",\n", + " f\"{(baseline_child_poverty_count - reformed_child_poverty_count):,.0f} children\"\n", + " ]\n", + "}\n", + "\n", + "df_results = pd.DataFrame(results)\n", + "print(df_results.to_string(index=False))\n", + "\n", + "df_results.to_csv(\"mt_young_child_credit_results.csv\", index=False)\n", + "print(\"\\nExported to: mt_young_child_credit_results.csv\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}