diff --git a/obbba_district_impacts/Congressional-Hackathon-2025 b/obbba_district_impacts/Congressional-Hackathon-2025 new file mode 160000 index 0000000..3f6d05e --- /dev/null +++ b/obbba_district_impacts/Congressional-Hackathon-2025 @@ -0,0 +1 @@ +Subproject commit 3f6d05e76400c6e396a3a4eddd34a7b3f6919fc3 diff --git a/us/states/ny/nyc/mamdani_income_tax/data_exploration.ipynb b/us/states/ny/nyc/mamdani_income_tax/data_exploration.ipynb new file mode 100644 index 0000000..a9fe5e0 --- /dev/null +++ b/us/states/ny/nyc/mamdani_income_tax/data_exploration.ipynb @@ -0,0 +1,474 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NYC Dataset Exploration\n", + "\n", + "This notebook explores the New York City (NYC) dataset to understand household counts, income distribution, and demographic characteristics relevant to the Mamdani Millionaire Income Tax analysis." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from policyengine_us import Microsimulation\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "NYC_DATASET = \"hf://policyengine/policyengine-us-data/cities/NYC.h5\"\n", + "YEAR = 2026" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Xet Storage is enabled for this repo, but the 'hf_xet' package is not installed. Falling back to regular HTTP download. For better performance, install the package with: `pip install huggingface_hub[hf_xet]` or `pip install hf_xet`\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3dcabd664b4d4de89a4fc47949c6fc63", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NYC.h5: 0%| | 0.00/139M [00:00= $500K: 119,432 (3.80%)\n", + "Households with income >= $1M: 100,112 (3.18%)\n", + "Households with income >= $2M: 75,965 (2.41%)\n", + "Households with income >= $5M: 250 (0.01%)\n", + "======================================================================\n" + ] + } + ], + "source": [ + "# High income households (relevant for millionaire tax)\n", + "weights = np.array(sim.calculate(\"household_weight\", period=YEAR))\n", + "nyc_taxable = np.array(nyc_taxable_income)\n", + "total_households = weights.sum()\n", + "\n", + "# Households above $1M (Mamdani threshold)\n", + "above_1m_mask = nyc_taxable >= 1_000_000\n", + "above_1m_count = weights[above_1m_mask].sum()\n", + "\n", + "# Households in various high-income brackets\n", + "above_500k_mask = nyc_taxable >= 500_000\n", + "above_500k_count = weights[above_500k_mask].sum()\n", + "\n", + "above_2m_mask = nyc_taxable >= 2_000_000\n", + "above_2m_count = weights[above_2m_mask].sum()\n", + "\n", + "above_5m_mask = nyc_taxable >= 5_000_000\n", + "above_5m_count = weights[above_5m_mask].sum()\n", + "\n", + "print(\"\\n\" + \"=\"*70)\n", + "print(\"HIGH INCOME HOUSEHOLDS (NYC Taxable Income)\")\n", + "print(\"=\"*70)\n", + "print(f\"Households with income >= $500K: {above_500k_count:,.0f} ({above_500k_count/total_households*100:.2f}%)\")\n", + "print(f\"Households with income >= $1M: {above_1m_count:,.0f} ({above_1m_count/total_households*100:.2f}%)\")\n", + "print(f\"Households with income >= $2M: {above_2m_count:,.0f} ({above_2m_count/total_households*100:.2f}%)\")\n", + "print(f\"Households with income >= $5M: {above_5m_count:,.0f} ({above_5m_count/total_households*100:.2f}%)\")\n", + "print(\"=\"*70)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "======================================================================\n", + "HOUSEHOLD COUNTS BY NYC TAXABLE INCOME BRACKET\n", + "======================================================================\n", + "Income Bracket Households % of All Households\n", + " $0-$50k 1,959,036 62.27%\n", + " $50k-$100k 535,118 17.01%\n", + " $100k-$200k 355,081 11.29%\n", + " $200k-$500k 177,351 5.64%\n", + " $500k-$1M 19,320 0.61%\n", + " $1M-$2M 24,147 0.77%\n", + " $2M-$5M 75,715 2.41%\n", + " $5M+ 250 0.01%\n", + "======================================================================\n" + ] + } + ], + "source": [ + "# Household counts by income brackets (focus on high income for millionaire tax)\n", + "income_brackets = [\n", + " (0, 50000, \"$0-$50k\"),\n", + " (50000, 100000, \"$50k-$100k\"),\n", + " (100000, 200000, \"$100k-$200k\"),\n", + " (200000, 500000, \"$200k-$500k\"),\n", + " (500000, 1000000, \"$500k-$1M\"),\n", + " (1000000, 2000000, \"$1M-$2M\"),\n", + " (2000000, 5000000, \"$2M-$5M\"),\n", + " (5000000, float('inf'), \"$5M+\")\n", + "]\n", + "\n", + "bracket_data = []\n", + "for lower, upper, label in income_brackets:\n", + " if upper == float('inf'):\n", + " mask = nyc_taxable >= lower\n", + " else:\n", + " mask = (nyc_taxable >= lower) & (nyc_taxable < upper)\n", + " count = weights[mask].sum()\n", + " pct_of_total = (count / total_households) * 100\n", + " \n", + " bracket_data.append({\n", + " \"Income Bracket\": label,\n", + " \"Households\": f\"{count:,.0f}\",\n", + " \"% of All Households\": f\"{pct_of_total:.2f}%\"\n", + " })\n", + "\n", + "income_df = pd.DataFrame(bracket_data)\n", + "\n", + "print(\"\\n\" + \"=\"*70)\n", + "print(\"HOUSEHOLD COUNTS BY NYC TAXABLE INCOME BRACKET\")\n", + "print(\"=\"*70)\n", + "print(income_df.to_string(index=False))\n", + "print(\"=\"*70)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "============================================================\n", + "NYC DATASET SUMMARY - WEIGHTED (Population Estimates)\n", + "============================================================\n", + " Metric Value\n", + " Household count (weighted) 3,146,018\n", + " Person count (weighted) 8,461,234\n", + " Median AGI $48,746\n", + " 75th percentile AGI $110,770\n", + " 90th percentile AGI $227,831\n", + " 95th percentile AGI $384,892\n", + " 99th percentile AGI $3,972,415\n", + " Max AGI $5,626,860\n", + " Median NYC Taxable Income $27,325\n", + " 99th percentile NYC Taxable Income $3,926,265\n", + " Households with income >= $1M 100,112\n", + "Pct of households with income >= $1M 3.18%\n", + "============================================================\n", + "\n", + "Summary saved to: nyc_dataset_summary_weighted.csv\n" + ] + } + ], + "source": [ + "# Create weighted summary table\n", + "weighted_summary_data = {\n", + " 'Metric': [\n", + " 'Household count (weighted)',\n", + " 'Person count (weighted)',\n", + " 'Median AGI',\n", + " '75th percentile AGI',\n", + " '90th percentile AGI',\n", + " '95th percentile AGI',\n", + " '99th percentile AGI',\n", + " 'Max AGI',\n", + " 'Median NYC Taxable Income',\n", + " '99th percentile NYC Taxable Income',\n", + " 'Households with income >= $1M',\n", + " 'Pct of households with income >= $1M'\n", + " ],\n", + " 'Value': [\n", + " f\"{household_count.sum():,.0f}\",\n", + " f\"{person_count.sum():,.0f}\",\n", + " f\"${agi.median():,.0f}\",\n", + " f\"${agi.quantile(0.75):,.0f}\",\n", + " f\"${agi.quantile(0.90):,.0f}\",\n", + " f\"${agi.quantile(0.95):,.0f}\",\n", + " f\"${agi.quantile(0.99):,.0f}\",\n", + " f\"${agi.max():,.0f}\",\n", + " f\"${nyc_taxable_income.median():,.0f}\",\n", + " f\"${nyc_taxable_income.quantile(0.99):,.0f}\",\n", + " f\"{above_1m_count:,.0f}\",\n", + " f\"{above_1m_count/total_households*100:.2f}%\"\n", + " ]\n", + "}\n", + "\n", + "weighted_df = pd.DataFrame(weighted_summary_data)\n", + "\n", + "print(\"\\n\" + \"=\"*60)\n", + "print(\"NYC DATASET SUMMARY - WEIGHTED (Population Estimates)\")\n", + "print(\"=\"*60)\n", + "print(weighted_df.to_string(index=False))\n", + "print(\"=\"*60)\n", + "\n", + "# Save table\n", + "weighted_df.to_csv('nyc_dataset_summary_weighted.csv', index=False)\n", + "print(\"\\nSummary saved to: nyc_dataset_summary_weighted.csv\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## County Discrepancy Investigation\n", + "\n", + "Ben reported seeing different county results when running the same dataset. His results showed multiple NYC counties (Queens, Bronx, etc.) instead of just Albany County. This may be related to local branch differences." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "County value counts:\n", + "QUEENS_COUNTY_NY 26719\n", + "KINGS_COUNTY_NY 26533\n", + "BRONX_COUNTY_NY 20787\n", + "NEW_YORK_COUNTY_NY 15029\n", + "RICHMOND_COUNTY_NY 5133\n", + "Name: count, dtype: int64\n", + "\n", + "DataFrame columns and sample:\n", + " household_id household_weight congressional_district_geoid state_fips \\\n", + "0 4550000 0.023717 3603 36 \n", + "1 4550001 16.290739 3603 36 \n", + "2 4550002 2.314028 3603 36 \n", + "3 4550003 22.233847 3603 36 \n", + "4 4550004 6.645908 3603 36 \n", + "\n", + " county county_str in_nyc \n", + "0 QUEENS_COUNTY_NY QUEENS_COUNTY_NY False \n", + "1 QUEENS_COUNTY_NY QUEENS_COUNTY_NY False \n", + "2 QUEENS_COUNTY_NY QUEENS_COUNTY_NY False \n", + "3 QUEENS_COUNTY_NY QUEENS_COUNTY_NY False \n", + "4 QUEENS_COUNTY_NY QUEENS_COUNTY_NY False \n", + "\n", + "Unique counties: ['QUEENS_COUNTY_NY' 'KINGS_COUNTY_NY' 'NEW_YORK_COUNTY_NY'\n", + " 'RICHMOND_COUNTY_NY' 'BRONX_COUNTY_NY']\n", + "in_nyc values: [False True]\n" + ] + } + ], + "source": [ + "# Ben's results (from his local environment - may be related to local branches):\n", + "#\n", + "# In [5]: from policyengine_us import Microsimulation\n", + "#\n", + "# In [6]: sim = Microsimulation(dataset=\"hf://policyengine/policyengine-us-data/cities/NYC.h5\")\n", + "#\n", + "# In [7]: sim.calculate(\"county\")\n", + "# Out[7]: \n", + "# value weight\n", + "# 0 QUEENS_COUNTY_NY 0.052011\n", + "# 1 QUEENS_COUNTY_NY 0.021776\n", + "# 2 QUEENS_COUNTY_NY 0.063064\n", + "# 3 QUEENS_COUNTY_NY 0.013337\n", + "# 4 QUEENS_COUNTY_NY 29.656933\n", + "# ... ... ...\n", + "# 51490 BRONX_COUNTY_NY 0.050521\n", + "# 51491 BRONX_COUNTY_NY 249.701233\n", + "# 51492 BRONX_COUNTY_NY 10.212130\n", + "# 51493 BRONX_COUNTY_NY 0.592128\n", + "# 51494 BRONX_COUNTY_NY 0.095578\n", + "#\n", + "# [51495 rows x 2 columns]\n", + "\n", + "# Compare with our results\n", + "county = sim.calculate(\"county\", period=YEAR)\n", + "print(\"County value counts:\")\n", + "print(county.value_counts())\n", + "\n", + "# Filtering demonstration (from Ben's testing)\n", + "df = sim.calculate_dataframe(['household_id', 'household_weight', 'congressional_district_geoid', 'state_fips', 'county', 'county_str', 'in_nyc'])\n", + "pdf = pd.DataFrame(df)\n", + "print(\"\\nDataFrame columns and sample:\")\n", + "print(pdf.head())\n", + "print(f\"\\nUnique counties: {pdf['county'].unique()}\")\n", + "print(f\"in_nyc values: {pdf['in_nyc'].unique()}\")" + ] + } + ], + "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": 4 +} diff --git a/us/states/ny/nyc/mamdani_income_tax/nyc_dataset_summary_weighted.csv b/us/states/ny/nyc/mamdani_income_tax/nyc_dataset_summary_weighted.csv new file mode 100644 index 0000000..4cc5cc5 --- /dev/null +++ b/us/states/ny/nyc/mamdani_income_tax/nyc_dataset_summary_weighted.csv @@ -0,0 +1,13 @@ +Metric,Value +Household count (weighted),"3,146,018" +Person count (weighted),"8,461,234" +Median AGI,"$48,746" +75th percentile AGI,"$110,770" +90th percentile AGI,"$227,831" +95th percentile AGI,"$384,892" +99th percentile AGI,"$3,972,415" +Max AGI,"$5,626,860" +Median NYC Taxable Income,"$27,325" +99th percentile NYC Taxable Income,"$3,926,265" +Households with income >= $1M,"100,112" +Pct of households with income >= $1M,3.18% diff --git a/us/states/ny/nyc/mamdani_income_tax/nyc_mamdani_decile_impacts.csv b/us/states/ny/nyc/mamdani_income_tax/nyc_mamdani_decile_impacts.csv new file mode 100644 index 0000000..c057173 --- /dev/null +++ b/us/states/ny/nyc/mamdani_income_tax/nyc_mamdani_decile_impacts.csv @@ -0,0 +1,11 @@ +Decile,Average Income Change +1,0.0 +2,0.0 +3,0.0 +4,0.0 +5,0.0 +6,0.0 +7,0.0 +8,0.0 +9,0.0 +10,-36149.48435367416 diff --git a/us/states/ny/nyc/mamdani_income_tax/nyc_mamdani_income_tax_analysis.ipynb b/us/states/ny/nyc/mamdani_income_tax/nyc_mamdani_income_tax_analysis.ipynb new file mode 100644 index 0000000..a02b4c2 --- /dev/null +++ b/us/states/ny/nyc/mamdani_income_tax/nyc_mamdani_income_tax_analysis.ipynb @@ -0,0 +1,458 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NYC Mamdani Millionaire Income Tax Analysis (2026)\n", + "\n", + "This notebook analyzes the fiscal and distributional impacts of Mayor-elect Zohran Mamdani's proposed income tax for New York City.\n", + "\n", + "## Baseline (Current Law)\n", + "- Current NYC income tax structure\n", + "\n", + "## Reform\n", + "- Mamdani Millionaire Income Tax proposal\n", + "\n", + "## Metrics\n", + "We calculate:\n", + "- Budgetary impact (revenue raised)\n", + "- Number and percentage of people/households affected\n", + "- Average change in net income for those affected\n", + "- Distributional analysis by income decile" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from policyengine_us import Microsimulation\n", + "from policyengine_us.reforms.local.ny.mamdani_income_tax import nyc_mamdani_income_tax\n", + "from policyengine_core.reforms import Reform\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "NYC_DATASET = \"hf://policyengine/policyengine-us-data/cities/NYC.h5\"\n", + "YEAR = 2026\n", + "\n", + "# Create combined reform: structural reform + enable the parameter\n", + "param_reform = Reform.from_dict(\n", + " {\n", + " \"gov.local.ny.mamdani_income_tax.in_effect\": {\n", + " \"2026-01-01.2100-12-31\": True\n", + " }\n", + " },\n", + " country_id=\"us\",\n", + ")\n", + "\n", + "# Combine reforms: parameter reform first, then structural reform\n", + "mamdani_reform = (param_reform, nyc_mamdani_income_tax)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Helper Functions" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def calculate_affected(baseline_sim, reform_sim, period=YEAR):\n", + " \"\"\"\n", + " Calculate people affected by the reform (losers who pay more taxes).\n", + " Returns weighted counts, percentages, and average changes.\n", + " \"\"\"\n", + " # Get household-level income change\n", + " baseline_income = np.array(baseline_sim.calculate(\"household_net_income\", period=period, map_to=\"household\"))\n", + " reform_income = np.array(reform_sim.calculate(\"household_net_income\", period=period, map_to=\"household\"))\n", + " household_weight = np.array(baseline_sim.calculate(\"household_weight\", period=period))\n", + " income_change = reform_income - baseline_income\n", + " \n", + " # Get person-level data\n", + " household_id_person = np.array(baseline_sim.calculate(\"household_id\", period=period, map_to=\"person\"))\n", + " household_id_household = np.array(baseline_sim.calculate(\"household_id\", period=period, map_to=\"household\"))\n", + " person_weight = np.array(baseline_sim.calculate(\"person_weight\", period=period))\n", + " \n", + " # Create mapping of household_id to income_change\n", + " income_change_dict = dict(zip(household_id_household, income_change))\n", + " \n", + " # Map income change to each person\n", + " person_income_change = np.array([income_change_dict.get(hh_id, 0) for hh_id in household_id_person])\n", + " \n", + " # Weighted count of people who are losers (lost more than $1 - paying more taxes)\n", + " losers_mask = person_income_change < -1\n", + " people_losing = person_weight[losers_mask].sum()\n", + " \n", + " total_people = person_weight.sum()\n", + " \n", + " # Calculate percentage\n", + " pct_losers = (people_losing / total_people * 100) if total_people > 0 else 0\n", + " \n", + " # Households affected\n", + " losing_hh_mask = income_change < -1\n", + " households_losing = household_weight[losing_hh_mask].sum()\n", + " total_households = household_weight.sum()\n", + " pct_households_losing = (households_losing / total_households * 100) if total_households > 0 else 0\n", + " \n", + " # Average loss for affected households (weighted)\n", + " avg_loss = np.average(income_change[losing_hh_mask], weights=household_weight[losing_hh_mask]) if losing_hh_mask.sum() > 0 else 0\n", + " \n", + " return {\n", + " \"people_losing\": people_losing,\n", + " \"total_people\": total_people,\n", + " \"pct_losers\": pct_losers,\n", + " \"households_losing\": households_losing,\n", + " \"total_households\": total_households,\n", + " \"pct_households_losing\": pct_households_losing,\n", + " \"avg_loss\": avg_loss\n", + " }\n", + "\n", + "def calculate_decile_impacts(baseline_sim, reform_sim, period=YEAR):\n", + " \"\"\"\n", + " Calculate average income change by income decile.\n", + " \"\"\"\n", + " from microdf import MicroSeries\n", + " \n", + " baseline_net_income = baseline_sim.calculate(\"household_net_income\", map_to=\"household\", period=period)\n", + " reform_net_income = reform_sim.calculate(\"household_net_income\", map_to=\"household\", period=period)\n", + " \n", + " count_people = baseline_sim.calculate(\"household_count_people\", period=period)\n", + " household_weight = baseline_sim.calculate(\"household_weight\", period=period)\n", + " \n", + " weighted_income = MicroSeries(\n", + " baseline_net_income, weights=household_weight * count_people\n", + " )\n", + " decile = weighted_income.decile_rank().values\n", + " \n", + " household_income_decile = (np.where(baseline_net_income < 0, -1, decile)).astype(int)\n", + " \n", + " income_change = reform_net_income - baseline_net_income\n", + " \n", + " # Calculate average change by decile\n", + " average_change = income_change.groupby(household_income_decile).mean()\n", + " \n", + " # Filter to valid deciles (1-10)\n", + " average_change = average_change[average_change.index > 0]\n", + " \n", + " return average_change\n", + "\n", + "def format_currency(value):\n", + " \"\"\"Format value as currency in millions or billions.\"\"\"\n", + " if abs(value) >= 1e9:\n", + " return f\"${value/1e9:.2f}B\"\n", + " else:\n", + " return f\"${value/1e6:.2f}M\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Simulations" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading baseline (current NYC income tax structure)...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Xet Storage is enabled for this repo, but the 'hf_xet' package is not installed. Falling back to regular HTTP download. For better performance, install the package with: `pip install huggingface_hub[hf_xet]` or `pip install hf_xet`\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4bf8b4830ba44333b462a21c996370b0", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NYC.h5: 0%| | 0.00/139M [00:0020}\")\n", + "print(\"-\"*30)\n", + "for decile in range(1, 11):\n", + " if decile in decile_impacts.index:\n", + " change = decile_impacts[decile]\n", + " print(f\"{decile:<10} ${change:>18,.2f}\")\n", + "print(\"=\"*80)\n", + "print(\"Note: Negative values indicate income loss (higher taxes)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Export Results" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "==============================================================================================================\n", + "NYC MAMDANI INCOME TAX REFORM SUMMARY\n", + "==============================================================================================================\n", + " Scenario Description Year Revenue Raised % Population Affected % Households Affected Avg Change for Affected\n", + "Mamdani Income Tax Millionaire Income Tax for NYC 2026 $8.87B 3.38% 3.18% $-88,650.45\n", + "==============================================================================================================\n", + "\n", + "Exported to: nyc_mamdani_income_tax_results.csv\n" + ] + } + ], + "source": [ + "# Create results DataFrame\n", + "results = [\n", + " {\n", + " \"Scenario\": \"Mamdani Income Tax\",\n", + " \"Description\": \"Millionaire Income Tax for NYC\",\n", + " \"Year\": YEAR,\n", + " \"Revenue Raised\": format_currency(revenue_raised),\n", + " \"% Population Affected\": f\"{affected['pct_losers']:.2f}%\",\n", + " \"% Households Affected\": f\"{affected['pct_households_losing']:.2f}%\",\n", + " \"Avg Change for Affected\": f\"${affected['avg_loss']:,.2f}\"\n", + " }\n", + "]\n", + "\n", + "df_results = pd.DataFrame(results)\n", + "\n", + "print(\"\\n\" + \"=\"*110)\n", + "print(\"NYC MAMDANI INCOME TAX REFORM SUMMARY\")\n", + "print(\"=\"*110)\n", + "print(df_results.to_string(index=False))\n", + "print(\"=\"*110)\n", + "\n", + "# Export to CSV\n", + "df_results.to_csv(\"nyc_mamdani_income_tax_results.csv\", index=False)\n", + "print(\"\\nExported to: nyc_mamdani_income_tax_results.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Decile impacts exported to: nyc_mamdani_decile_impacts.csv\n" + ] + } + ], + "source": [ + "# Export decile impacts\n", + "decile_df = pd.DataFrame({\n", + " 'Decile': decile_impacts.index,\n", + " 'Average Income Change': decile_impacts.values\n", + "})\n", + "decile_df.to_csv(\"nyc_mamdani_decile_impacts.csv\", index=False)\n", + "print(\"Decile impacts exported to: nyc_mamdani_decile_impacts.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": 4 +} diff --git a/us/states/ny/nyc/mamdani_income_tax/nyc_mamdani_income_tax_results.csv b/us/states/ny/nyc/mamdani_income_tax/nyc_mamdani_income_tax_results.csv new file mode 100644 index 0000000..f3ed7e0 --- /dev/null +++ b/us/states/ny/nyc/mamdani_income_tax/nyc_mamdani_income_tax_results.csv @@ -0,0 +1,2 @@ +Scenario,Description,Year,Revenue Raised,% Population Affected,% Households Affected,Avg Change for Affected +Mamdani Income Tax,Millionaire Income Tax for NYC,2026,$8.87B,3.38%,3.18%,"$-88,650.45"