Skip to content

Instantly share code, notes, and snippets.

@MaxGhenis
Created July 28, 2025 06:26
Show Gist options
  • Save MaxGhenis/279c9199ae42d435acea4f99bb324387 to your computer and use it in GitHub Desktop.
Save MaxGhenis/279c9199ae42d435acea4f99bb324387 to your computer and use it in GitHub Desktop.
NH Tax Comparison: PolicyEngine vs TAXSIM 35
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# New Hampshire Tax: PolicyEngine vs TAXSIM Comparison\n",
"\n",
"This notebook compares PolicyEngine's implementation of New Hampshire's Interest and Dividends tax with TAXSIM 35."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Setup and Test Cases\n",
"\n",
"First, let's define our test cases covering different years and filing statuses."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"execution": {
"iopub.execute_input": "2025-07-28T06:26:28.083209Z",
"iopub.status.busy": "2025-07-28T06:26:28.083097Z",
"iopub.status.idle": "2025-07-28T06:26:28.902186Z",
"shell.execute_reply": "2025-07-28T06:26:28.901908Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>id</th>\n",
" <th>year</th>\n",
" <th>status</th>\n",
" <th>age</th>\n",
" <th>interest</th>\n",
" <th>dividends</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>2020</td>\n",
" <td>Single</td>\n",
" <td>40</td>\n",
" <td>5000</td>\n",
" <td>5000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>2020</td>\n",
" <td>Joint</td>\n",
" <td>40</td>\n",
" <td>3000</td>\n",
" <td>2000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3</td>\n",
" <td>2020</td>\n",
" <td>Single</td>\n",
" <td>40</td>\n",
" <td>10000</td>\n",
" <td>10000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4</td>\n",
" <td>1990</td>\n",
" <td>Single</td>\n",
" <td>40</td>\n",
" <td>5000</td>\n",
" <td>5000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>5</td>\n",
" <td>1990</td>\n",
" <td>Joint</td>\n",
" <td>40</td>\n",
" <td>3000</td>\n",
" <td>2000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>6</td>\n",
" <td>1980</td>\n",
" <td>Single</td>\n",
" <td>40</td>\n",
" <td>5000</td>\n",
" <td>5000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>7</td>\n",
" <td>1980</td>\n",
" <td>Joint</td>\n",
" <td>40</td>\n",
" <td>3000</td>\n",
" <td>2000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>8</td>\n",
" <td>2020</td>\n",
" <td>Single</td>\n",
" <td>70</td>\n",
" <td>5000</td>\n",
" <td>5000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>9</td>\n",
" <td>2020</td>\n",
" <td>Joint</td>\n",
" <td>70</td>\n",
" <td>5000</td>\n",
" <td>5000</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" id year status age interest dividends\n",
"0 1 2020 Single 40 5000 5000\n",
"1 2 2020 Joint 40 3000 2000\n",
"2 3 2020 Single 40 10000 10000\n",
"3 4 1990 Single 40 5000 5000\n",
"4 5 1990 Joint 40 3000 2000\n",
"5 6 1980 Single 40 5000 5000\n",
"6 7 1980 Joint 40 3000 2000\n",
"7 8 2020 Single 70 5000 5000\n",
"8 9 2020 Joint 70 5000 5000"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"\n",
"# Define test cases\n",
"test_cases = [\n",
" {'id': 1, 'year': 2020, 'status': 'Single', 'age': 40, 'interest': 5000, 'dividends': 5000},\n",
" {'id': 2, 'year': 2020, 'status': 'Joint', 'age': 40, 'interest': 3000, 'dividends': 2000},\n",
" {'id': 3, 'year': 2020, 'status': 'Single', 'age': 40, 'interest': 10000, 'dividends': 10000},\n",
" {'id': 4, 'year': 1990, 'status': 'Single', 'age': 40, 'interest': 5000, 'dividends': 5000},\n",
" {'id': 5, 'year': 1990, 'status': 'Joint', 'age': 40, 'interest': 3000, 'dividends': 2000},\n",
" {'id': 6, 'year': 1980, 'status': 'Single', 'age': 40, 'interest': 5000, 'dividends': 5000},\n",
" {'id': 7, 'year': 1980, 'status': 'Joint', 'age': 40, 'interest': 3000, 'dividends': 2000},\n",
" {'id': 8, 'year': 2020, 'status': 'Single', 'age': 70, 'interest': 5000, 'dividends': 5000},\n",
" {'id': 9, 'year': 2020, 'status': 'Joint', 'age': 70, 'interest': 5000, 'dividends': 5000},\n",
"]\n",
"\n",
"test_df = pd.DataFrame(test_cases)\n",
"test_df"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. TAXSIM Results\n",
"\n",
"I ran these test cases through TAXSIM 35. Here are the results:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"execution": {
"iopub.execute_input": "2025-07-28T06:26:28.919066Z",
"iopub.status.busy": "2025-07-28T06:26:28.918903Z",
"iopub.status.idle": "2025-07-28T06:26:28.925176Z",
"shell.execute_reply": "2025-07-28T06:26:28.924950Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>id</th>\n",
" <th>year</th>\n",
" <th>siitax</th>\n",
" <th>nh_income</th>\n",
" <th>nh_exemptions</th>\n",
" <th>nh_taxable</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>2020</td>\n",
" <td>31.15</td>\n",
" <td>10000.01</td>\n",
" <td>8000.0</td>\n",
" <td>2000.01</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>2020</td>\n",
" <td>0.00</td>\n",
" <td>5001.01</td>\n",
" <td>16050.0</td>\n",
" <td>0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3</td>\n",
" <td>2020</td>\n",
" <td>454.75</td>\n",
" <td>20001.01</td>\n",
" <td>8000.0</td>\n",
" <td>12000.01</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4</td>\n",
" <td>1990</td>\n",
" <td>111.15</td>\n",
" <td>10001.01</td>\n",
" <td>6000.0</td>\n",
" <td>4000.01</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>5</td>\n",
" <td>1990</td>\n",
" <td>0.00</td>\n",
" <td>5001.01</td>\n",
" <td>9500.0</td>\n",
" <td>0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>6</td>\n",
" <td>1980</td>\n",
" <td>254.77</td>\n",
" <td>10001.01</td>\n",
" <td>1584.0</td>\n",
" <td>7566.01</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>7</td>\n",
" <td>1980</td>\n",
" <td>0.00</td>\n",
" <td>5001.01</td>\n",
" <td>1900.0</td>\n",
" <td>1400.01</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>8</td>\n",
" <td>2020</td>\n",
" <td>31.15</td>\n",
" <td>10001.01</td>\n",
" <td>8000.0</td>\n",
" <td>2000.01</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>9</td>\n",
" <td>2020</td>\n",
" <td>0.00</td>\n",
" <td>10001.01</td>\n",
" <td>16050.0</td>\n",
" <td>0.00</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" id year siitax nh_income nh_exemptions nh_taxable\n",
"0 1 2020 31.15 10000.01 8000.0 2000.01\n",
"1 2 2020 0.00 5001.01 16050.0 0.00\n",
"2 3 2020 454.75 20001.01 8000.0 12000.01\n",
"3 4 1990 111.15 10001.01 6000.0 4000.01\n",
"4 5 1990 0.00 5001.01 9500.0 0.00\n",
"5 6 1980 254.77 10001.01 1584.0 7566.01\n",
"6 7 1980 0.00 5001.01 1900.0 1400.01\n",
"7 8 2020 31.15 10001.01 8000.0 2000.01\n",
"8 9 2020 0.00 10001.01 16050.0 0.00"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# TAXSIM results (extracted from actual TAXSIM output)\n",
"taxsim_results = [\n",
" {'id': 1, 'year': 2020, 'siitax': 31.15, 'nh_income': 10000.01, 'nh_exemptions': 8000.00, 'nh_taxable': 2000.01},\n",
" {'id': 2, 'year': 2020, 'siitax': 0.00, 'nh_income': 5001.01, 'nh_exemptions': 16050.00, 'nh_taxable': 0.00},\n",
" {'id': 3, 'year': 2020, 'siitax': 454.75, 'nh_income': 20001.01, 'nh_exemptions': 8000.00, 'nh_taxable': 12000.01},\n",
" {'id': 4, 'year': 1990, 'siitax': 111.15, 'nh_income': 10001.01, 'nh_exemptions': 6000.00, 'nh_taxable': 4000.01},\n",
" {'id': 5, 'year': 1990, 'siitax': 0.00, 'nh_income': 5001.01, 'nh_exemptions': 9500.00, 'nh_taxable': 0.00},\n",
" {'id': 6, 'year': 1980, 'siitax': 254.77, 'nh_income': 10001.01, 'nh_exemptions': 1584.00, 'nh_taxable': 7566.01},\n",
" {'id': 7, 'year': 1980, 'siitax': 0.00, 'nh_income': 5001.01, 'nh_exemptions': 1900.00, 'nh_taxable': 1400.01},\n",
" {'id': 8, 'year': 2020, 'siitax': 31.15, 'nh_income': 10001.01, 'nh_exemptions': 8000.00, 'nh_taxable': 2000.01},\n",
" {'id': 9, 'year': 2020, 'siitax': 0.00, 'nh_income': 10001.01, 'nh_exemptions': 16050.00, 'nh_taxable': 0.00},\n",
"]\n",
"\n",
"taxsim_df = pd.DataFrame(taxsim_results)\n",
"taxsim_df"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. PolicyEngine Expected Results\n",
"\n",
"Based on NH tax forms and our implementation, here's what we expect:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"execution": {
"iopub.execute_input": "2025-07-28T06:26:28.926552Z",
"iopub.status.busy": "2025-07-28T06:26:28.926448Z",
"iopub.status.idle": "2025-07-28T06:26:28.931589Z",
"shell.execute_reply": "2025-07-28T06:26:28.931310Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>id</th>\n",
" <th>year</th>\n",
" <th>pe_income</th>\n",
" <th>pe_exemptions</th>\n",
" <th>pe_taxable</th>\n",
" <th>pe_tax</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>2020</td>\n",
" <td>10000</td>\n",
" <td>2400</td>\n",
" <td>7600</td>\n",
" <td>380.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>2020</td>\n",
" <td>5000</td>\n",
" <td>4800</td>\n",
" <td>200</td>\n",
" <td>10.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3</td>\n",
" <td>2020</td>\n",
" <td>20000</td>\n",
" <td>2400</td>\n",
" <td>17600</td>\n",
" <td>880.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4</td>\n",
" <td>1990</td>\n",
" <td>10000</td>\n",
" <td>1200</td>\n",
" <td>8800</td>\n",
" <td>440.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>5</td>\n",
" <td>1990</td>\n",
" <td>5000</td>\n",
" <td>2400</td>\n",
" <td>2600</td>\n",
" <td>130.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>6</td>\n",
" <td>1980</td>\n",
" <td>10000</td>\n",
" <td>600</td>\n",
" <td>9400</td>\n",
" <td>470.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>7</td>\n",
" <td>1980</td>\n",
" <td>5000</td>\n",
" <td>1200</td>\n",
" <td>3800</td>\n",
" <td>190.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>8</td>\n",
" <td>2020</td>\n",
" <td>10000</td>\n",
" <td>3600</td>\n",
" <td>6400</td>\n",
" <td>320.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>9</td>\n",
" <td>2020</td>\n",
" <td>10000</td>\n",
" <td>7200</td>\n",
" <td>2800</td>\n",
" <td>140.0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" id year pe_income pe_exemptions pe_taxable pe_tax\n",
"0 1 2020 10000 2400 7600 380.0\n",
"1 2 2020 5000 4800 200 10.0\n",
"2 3 2020 20000 2400 17600 880.0\n",
"3 4 1990 10000 1200 8800 440.0\n",
"4 5 1990 5000 2400 2600 130.0\n",
"5 6 1980 10000 600 9400 470.0\n",
"6 7 1980 5000 1200 3800 190.0\n",
"7 8 2020 10000 3600 6400 320.0\n",
"8 9 2020 10000 7200 2800 140.0"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# NH exemption amounts by year\n",
"nh_exemptions = {\n",
" 2020: {'single_base': 2400, 'joint_base': 4800, 'additional': 1200},\n",
" 1990: {'single_base': 1200, 'joint_base': 2400, 'additional': 1200},\n",
" 1980: {'single_base': 600, 'joint_base': 1200, 'additional': 600}\n",
"}\n",
"\n",
"# Calculate PolicyEngine expected results\n",
"pe_results = []\n",
"for case in test_cases:\n",
" year = case['year']\n",
" income = case['interest'] + case['dividends']\n",
" \n",
" # Get base exemption\n",
" if case['status'] == 'Single':\n",
" base_exemption = nh_exemptions[year]['single_base']\n",
" else:\n",
" base_exemption = nh_exemptions[year]['joint_base']\n",
" \n",
" # Add age exemption if 65+\n",
" age_exemption = 0\n",
" if case['age'] >= 65:\n",
" age_exemption = nh_exemptions[year]['additional']\n",
" if case['status'] == 'Joint': # Both spouses assumed same age\n",
" age_exemption *= 2\n",
" \n",
" total_exemptions = base_exemption + age_exemption\n",
" taxable_income = max(0, income - total_exemptions)\n",
" tax = taxable_income * 0.05 # 5% rate\n",
" \n",
" pe_results.append({\n",
" 'id': case['id'],\n",
" 'year': year,\n",
" 'pe_income': income,\n",
" 'pe_exemptions': total_exemptions,\n",
" 'pe_taxable': taxable_income,\n",
" 'pe_tax': tax\n",
" })\n",
"\n",
"pe_df = pd.DataFrame(pe_results)\n",
"pe_df"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. Comparison Analysis"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"execution": {
"iopub.execute_input": "2025-07-28T06:26:28.933066Z",
"iopub.status.busy": "2025-07-28T06:26:28.932956Z",
"iopub.status.idle": "2025-07-28T06:26:28.943755Z",
"shell.execute_reply": "2025-07-28T06:26:28.943518Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Comparison of TAXSIM vs PolicyEngine:\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>id</th>\n",
" <th>year</th>\n",
" <th>status</th>\n",
" <th>age</th>\n",
" <th>interest</th>\n",
" <th>dividends</th>\n",
" <th>nh_exemptions</th>\n",
" <th>pe_exemptions</th>\n",
" <th>exemption_diff</th>\n",
" <th>siitax</th>\n",
" <th>pe_tax</th>\n",
" <th>tax_diff</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>2020</td>\n",
" <td>Single</td>\n",
" <td>40</td>\n",
" <td>5000</td>\n",
" <td>5000</td>\n",
" <td>8000.0</td>\n",
" <td>2400</td>\n",
" <td>5600.0</td>\n",
" <td>31.15</td>\n",
" <td>380.0</td>\n",
" <td>-348.85</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>2020</td>\n",
" <td>Joint</td>\n",
" <td>40</td>\n",
" <td>3000</td>\n",
" <td>2000</td>\n",
" <td>16050.0</td>\n",
" <td>4800</td>\n",
" <td>11250.0</td>\n",
" <td>0.00</td>\n",
" <td>10.0</td>\n",
" <td>-10.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3</td>\n",
" <td>2020</td>\n",
" <td>Single</td>\n",
" <td>40</td>\n",
" <td>10000</td>\n",
" <td>10000</td>\n",
" <td>8000.0</td>\n",
" <td>2400</td>\n",
" <td>5600.0</td>\n",
" <td>454.75</td>\n",
" <td>880.0</td>\n",
" <td>-425.25</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4</td>\n",
" <td>1990</td>\n",
" <td>Single</td>\n",
" <td>40</td>\n",
" <td>5000</td>\n",
" <td>5000</td>\n",
" <td>6000.0</td>\n",
" <td>1200</td>\n",
" <td>4800.0</td>\n",
" <td>111.15</td>\n",
" <td>440.0</td>\n",
" <td>-328.85</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>5</td>\n",
" <td>1990</td>\n",
" <td>Joint</td>\n",
" <td>40</td>\n",
" <td>3000</td>\n",
" <td>2000</td>\n",
" <td>9500.0</td>\n",
" <td>2400</td>\n",
" <td>7100.0</td>\n",
" <td>0.00</td>\n",
" <td>130.0</td>\n",
" <td>-130.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>6</td>\n",
" <td>1980</td>\n",
" <td>Single</td>\n",
" <td>40</td>\n",
" <td>5000</td>\n",
" <td>5000</td>\n",
" <td>1584.0</td>\n",
" <td>600</td>\n",
" <td>984.0</td>\n",
" <td>254.77</td>\n",
" <td>470.0</td>\n",
" <td>-215.23</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>7</td>\n",
" <td>1980</td>\n",
" <td>Joint</td>\n",
" <td>40</td>\n",
" <td>3000</td>\n",
" <td>2000</td>\n",
" <td>1900.0</td>\n",
" <td>1200</td>\n",
" <td>700.0</td>\n",
" <td>0.00</td>\n",
" <td>190.0</td>\n",
" <td>-190.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>8</td>\n",
" <td>2020</td>\n",
" <td>Single</td>\n",
" <td>70</td>\n",
" <td>5000</td>\n",
" <td>5000</td>\n",
" <td>8000.0</td>\n",
" <td>3600</td>\n",
" <td>4400.0</td>\n",
" <td>31.15</td>\n",
" <td>320.0</td>\n",
" <td>-288.85</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>9</td>\n",
" <td>2020</td>\n",
" <td>Joint</td>\n",
" <td>70</td>\n",
" <td>5000</td>\n",
" <td>5000</td>\n",
" <td>16050.0</td>\n",
" <td>7200</td>\n",
" <td>8850.0</td>\n",
" <td>0.00</td>\n",
" <td>140.0</td>\n",
" <td>-140.00</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" id year status age interest dividends nh_exemptions pe_exemptions \\\n",
"0 1 2020 Single 40 5000 5000 8000.0 2400 \n",
"1 2 2020 Joint 40 3000 2000 16050.0 4800 \n",
"2 3 2020 Single 40 10000 10000 8000.0 2400 \n",
"3 4 1990 Single 40 5000 5000 6000.0 1200 \n",
"4 5 1990 Joint 40 3000 2000 9500.0 2400 \n",
"5 6 1980 Single 40 5000 5000 1584.0 600 \n",
"6 7 1980 Joint 40 3000 2000 1900.0 1200 \n",
"7 8 2020 Single 70 5000 5000 8000.0 3600 \n",
"8 9 2020 Joint 70 5000 5000 16050.0 7200 \n",
"\n",
" exemption_diff siitax pe_tax tax_diff \n",
"0 5600.0 31.15 380.0 -348.85 \n",
"1 11250.0 0.00 10.0 -10.00 \n",
"2 5600.0 454.75 880.0 -425.25 \n",
"3 4800.0 111.15 440.0 -328.85 \n",
"4 7100.0 0.00 130.0 -130.00 \n",
"5 984.0 254.77 470.0 -215.23 \n",
"6 700.0 0.00 190.0 -190.00 \n",
"7 4400.0 31.15 320.0 -288.85 \n",
"8 8850.0 0.00 140.0 -140.00 "
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Merge all results\n",
"comparison = test_df.merge(taxsim_df, on='id').merge(pe_df, on='id', suffixes=('', '_pe'))\n",
"\n",
"# Calculate differences\n",
"comparison['exemption_diff'] = comparison['nh_exemptions'] - comparison['pe_exemptions']\n",
"comparison['tax_diff'] = comparison['siitax'] - comparison['pe_tax']\n",
"\n",
"# Select key columns for display\n",
"display_cols = ['id', 'year', 'status', 'age', 'interest', 'dividends', \n",
" 'nh_exemptions', 'pe_exemptions', 'exemption_diff',\n",
" 'siitax', 'pe_tax', 'tax_diff']\n",
"\n",
"print(\"Comparison of TAXSIM vs PolicyEngine:\")\n",
"comparison[display_cols]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Key Findings\n",
"\n",
"### TAXSIM Issues Identified:\n",
"\n",
"1. **Incorrect Exemption Amounts**: TAXSIM shows much higher exemptions than the actual NH tax forms specify\n",
"2. **No Age-Based Exemptions**: TAXSIM doesn't apply the additional $1,200 exemption for taxpayers age 65+\n",
"3. **Inconsistent with Source Code**: TAXSIM's own source code shows the correct formula, but the output doesn't match"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"execution": {
"iopub.execute_input": "2025-07-28T06:26:28.945157Z",
"iopub.status.busy": "2025-07-28T06:26:28.945061Z",
"iopub.status.idle": "2025-07-28T06:26:28.947473Z",
"shell.execute_reply": "2025-07-28T06:26:28.947199Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Summary of Differences:\n",
"\n",
"Average exemption difference: $5,476.00\n",
"Average tax difference: $-230.78\n",
"\n",
"Cases where TAXSIM tax is lower: 9 out of 9\n",
"Cases where exemptions match: 0 out of 9\n"
]
}
],
"source": [
"# Summary statistics\n",
"print(\"Summary of Differences:\\n\")\n",
"print(f\"Average exemption difference: ${comparison['exemption_diff'].mean():,.2f}\")\n",
"print(f\"Average tax difference: ${comparison['tax_diff'].mean():,.2f}\")\n",
"print(f\"\\nCases where TAXSIM tax is lower: {(comparison['tax_diff'] < 0).sum()} out of {len(comparison)}\")\n",
"print(f\"Cases where exemptions match: {(comparison['exemption_diff'] == 0).sum()} out of {len(comparison)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 6. TAXSIM Source Code Analysis\n",
"\n",
"From TAXSIM's source code (`taxsim.f`), the NH tax calculation is:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"execution": {
"iopub.execute_input": "2025-07-28T06:26:28.948858Z",
"iopub.status.busy": "2025-07-28T06:26:28.948762Z",
"iopub.status.idle": "2025-07-28T06:26:28.950797Z",
"shell.execute_reply": "2025-07-28T06:26:28.950516Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"TAXSIM NH Tax Code (lines 21264-21269):\n",
"if(law.ge.1995) then\n",
" exemp = data(7)*2400.+(data(9)+data(10))*1200.\n",
"else if(law.ge.1981.and.law.le.1994) then\n",
" exemp = (data(7)+data(9)+data(10))*1200.\n",
"else\n",
" exemp = (data(7)+data(9)+data(10))*600.\n",
"endif\n",
"\n",
"Where:\n",
"- data(7) = number of tax units (1 for single, 2 for married)\n",
"- data(9) = number of people age 65+\n",
"- data(10) = number of blind/disabled people\n"
]
}
],
"source": [
"print(\"TAXSIM NH Tax Code (lines 21264-21269):\")\n",
"print(\"\"\"if(law.ge.1995) then\n",
" exemp = data(7)*2400.+(data(9)+data(10))*1200.\n",
"else if(law.ge.1981.and.law.le.1994) then\n",
" exemp = (data(7)+data(9)+data(10))*1200.\n",
"else\n",
" exemp = (data(7)+data(9)+data(10))*600.\n",
"endif\"\"\")\n",
"print(\"\\nWhere:\")\n",
"print(\"- data(7) = number of tax units (1 for single, 2 for married)\")\n",
"print(\"- data(9) = number of people age 65+\")\n",
"print(\"- data(10) = number of blind/disabled people\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 7. Verification of TAXSIM Bug\n",
"\n",
"Let's verify that TAXSIM's output doesn't match its own formula:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"execution": {
"iopub.execute_input": "2025-07-28T06:26:28.952159Z",
"iopub.status.busy": "2025-07-28T06:26:28.952064Z",
"iopub.status.idle": "2025-07-28T06:26:28.954001Z",
"shell.execute_reply": "2025-07-28T06:26:28.953760Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Checking TAXSIM's 2020 Single Filer Exemption:\n",
"TAXSIM shows: $8,000\n",
"\n",
"Using TAXSIM's formula for single filer with no age/disability:\n",
"exemp = data(7)*2400 + (data(9)+data(10))*1200\n",
"exemp = 1*2400 + (0+0)*1200 = $2,400\n",
"\n",
"TAXSIM's output ($8,000) doesn't match its formula ($2,400)!\n",
"\n",
"To get $8,000, we would need:\n",
"8000 = 2400 + (data(9)+data(10))*1200\n",
"(data(9)+data(10)) = 4.67\n",
"\n",
"This is impossible since these should be counts of people.\n"
]
}
],
"source": [
"print(\"Checking TAXSIM's 2020 Single Filer Exemption:\")\n",
"print(f\"TAXSIM shows: $8,000\")\n",
"print(f\"\\nUsing TAXSIM's formula for single filer with no age/disability:\")\n",
"print(f\"exemp = data(7)*2400 + (data(9)+data(10))*1200\")\n",
"print(f\"exemp = 1*2400 + (0+0)*1200 = $2,400\")\n",
"print(f\"\\nTAXSIM's output ($8,000) doesn't match its formula ($2,400)!\")\n",
"print(f\"\\nTo get $8,000, we would need:\")\n",
"print(f\"8000 = 2400 + (data(9)+data(10))*1200\")\n",
"print(f\"(data(9)+data(10)) = 4.67\")\n",
"print(f\"\\nThis is impossible since these should be counts of people.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 8. Conclusion\n",
"\n",
"PolicyEngine correctly implements NH's Interest and Dividends tax based on:\n",
"- Actual NH tax forms (DP-10)\n",
"- NH Revised Statutes Annotated (RSA) Chapter 77\n",
"- Historical tax rates and exemption amounts\n",
"\n",
"TAXSIM appears to have a bug in its NH tax calculation that results in:\n",
"- Exemptions that are 3-4x higher than specified in tax forms\n",
"- No recognition of age-based additional exemptions\n",
"- Tax amounts that are significantly too low\n",
"\n",
"PolicyEngine's implementation should be considered the accurate one for NH tax calculations."
]
}
],
"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.10.17"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment