Skip to content

Instantly share code, notes, and snippets.

@MaxGhenis
Last active October 31, 2025 11:55
Show Gist options
  • Select an option

  • Save MaxGhenis/ca06de11cdcfba3f857002749f379d1f to your computer and use it in GitHub Desktop.

Select an option

Save MaxGhenis/ca06de11cdcfba3f857002749f379d1f to your computer and use it in GitHub Desktop.
SNAP Recipients with Gross Income Above 130% FPL - PolicyEngine-US Analysis (2025)
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"id": "cell-0",
"metadata": {},
"source": [
"# SNAP Recipients and Benefits by Gross Income (October 2025)\n",
"\n",
"Analysis of SNAP distribution across income levels using PolicyEngine-US microsimulation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-1",
"metadata": {},
"outputs": [],
"source": [
"from policyengine_us import Microsimulation\n",
"import pandas as pd\n",
"import plotly.graph_objects as go\n",
"from policyengine.utils.charts import format_figure, COLOUR_SCHEMES\n",
"\n",
"sim = Microsimulation()"
]
},
{
"cell_type": "markdown",
"id": "cell-2",
"metadata": {},
"source": [
"## Cumulative Distribution by Income\n",
"\n",
"This chart shows what percentage of SNAP recipients and benefits go to households below various income thresholds."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-3",
"metadata": {},
"outputs": [],
"source": [
"# Calculate for recipients (person level)\n",
"snap_person = sim.calculate('snap', period='2025-10', map_to='person')\n",
"income_ratio_person = sim.calculate('snap_gross_income_fpg_ratio', period='2025-10', map_to='person')\n",
"\n",
"# Filter to only SNAP recipients and create dataframe\n",
"df_person = pd.DataFrame({\n",
" 'income_ratio': income_ratio_person.values,\n",
" 'weight': income_ratio_person.weights,\n",
" 'has_snap': snap_person.values > 0\n",
"})\n",
"df_person = df_person[df_person['has_snap']].copy()\n",
"df_person = df_person.sort_values('income_ratio')\n",
"\n",
"# Calculate cumulative shares for recipients\n",
"df_person['cumsum_weight'] = df_person['weight'].cumsum()\n",
"df_person['cumsum_pct'] = df_person['cumsum_weight'] / df_person['weight'].sum() * 100\n",
"df_person['income_pct'] = df_person['income_ratio'] * 100\n",
"\n",
"# Calculate for benefits (SPM unit level)\n",
"snap_spm = sim.calculate('snap', period='2025-10', map_to='spm_unit')\n",
"income_ratio_spm = sim.calculate('snap_gross_income_fpg_ratio', period='2025-10', map_to='spm_unit')\n",
"\n",
"# Create dataframe for benefits\n",
"df_spm = pd.DataFrame({\n",
" 'income_ratio': income_ratio_spm.values,\n",
" 'weight': income_ratio_spm.weights,\n",
" 'snap': snap_spm.values,\n",
" 'has_snap': snap_spm.values > 0\n",
"})\n",
"df_spm = df_spm[df_spm['has_snap']].copy()\n",
"df_spm = df_spm.sort_values('income_ratio')\n",
"df_spm['weighted_snap'] = df_spm['snap'] * df_spm['weight']\n",
"\n",
"# Calculate cumulative shares for benefits\n",
"df_spm['cumsum_benefits'] = df_spm['weighted_snap'].cumsum()\n",
"df_spm['cumsum_pct'] = df_spm['cumsum_benefits'] / df_spm['weighted_snap'].sum() * 100\n",
"df_spm['income_pct'] = df_spm['income_ratio'] * 100"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-4",
"metadata": {},
"outputs": [],
"source": [
"# Create figure\n",
"fig = go.Figure()\n",
"\n",
"# Add recipients line (teal)\n",
"fig.add_trace(go.Scatter(\n",
" x=df_person['income_pct'],\n",
" y=df_person['cumsum_pct'],\n",
" mode='lines',\n",
" name='Recipients',\n",
" line=dict(color=COLOUR_SCHEMES['teal']['primary'], width=2),\n",
" hovertemplate='%{x:.0f}% FPL<br>%{y:.1f}% of recipients<extra></extra>'\n",
"))\n",
"\n",
"# Add benefits line (blue)\n",
"fig.add_trace(go.Scatter(\n",
" x=df_spm['income_pct'],\n",
" y=df_spm['cumsum_pct'],\n",
" mode='lines',\n",
" name='Benefits',\n",
" line=dict(color=COLOUR_SCHEMES['blue']['primary'], width=2),\n",
" hovertemplate='%{x:.0f}% FPL<br>%{y:.1f}% of benefits<extra></extra>'\n",
"))\n",
"\n",
"# Add vertical lines at 130% and 200%\n",
"fig.add_vline(x=130, line_dash=\"dash\", line_color=COLOUR_SCHEMES['gray']['primary'], opacity=0.5)\n",
"fig.add_vline(x=200, line_dash=\"dash\", line_color=COLOUR_SCHEMES['gray']['primary'], opacity=0.5)\n",
"\n",
"# Add annotations for the vertical lines\n",
"fig.add_annotation(x=130, y=95, text=\"130% FPL\", showarrow=False, \n",
" font=dict(size=12, color=COLOUR_SCHEMES['gray']['dark']))\n",
"fig.add_annotation(x=200, y=95, text=\"200% FPL\", showarrow=False,\n",
" font=dict(size=12, color=COLOUR_SCHEMES['gray']['dark']))\n",
"\n",
"# Apply PolicyEngine formatting\n",
"fig = format_figure(\n",
" fig,\n",
" title='Cumulative Distribution of SNAP Recipients and Benefits by Gross Income',\n",
" x_title='Gross Income (% of Federal Poverty Level)',\n",
" y_title='Cumulative Share (%)',\n",
" colour_scheme='teal',\n",
" height=500,\n",
" width=800\n",
")\n",
"\n",
"# Add PolicyEngine logo at bottom right\n",
"fig.add_layout_image(\n",
" dict(\n",
" source=\"https://policyengine.org/images/logos/policyengine/blue.png\",\n",
" xref=\"paper\",\n",
" yref=\"paper\",\n",
" x=0.98,\n",
" y=0.02,\n",
" sizex=0.15,\n",
" sizey=0.15,\n",
" xanchor=\"right\",\n",
" yanchor=\"bottom\",\n",
" opacity=0.5\n",
" )\n",
")\n",
"\n",
"fig.update_xaxes(range=[0, 300])\n",
"fig.update_yaxes(range=[0, 100])\n",
"\n",
"fig.show()"
]
},
{
"cell_type": "markdown",
"id": "cell-5",
"metadata": {},
"source": [
"## Key Statistics"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== Above 130% FPL ===\n",
"Recipients: 23.86% (11,032,938 of 46,239,099)\n",
"Benefits: 11.33% ($834,789,722 of $7,365,073,059)\n",
"\n",
"=== Above 200% FPL ===\n",
"Recipients: 2.86% (1,322,911 of 46,239,099)\n",
"Benefits: 2.52% ($185,661,097 of $7,365,073,059)\n"
]
}
],
"source": [
"# Calculate summary statistics\n",
"snap = sim.calculate('snap', period='2025-10', map_to='person')\n",
"income_ratio = sim.calculate('snap_gross_income_fpg_ratio', period='2025-10', map_to='person')\n",
"\n",
"# Recipients above 130%\n",
"snap_recipients = snap > 0\n",
"above_130_fpl = (snap > 0) & (income_ratio > 1.3)\n",
"total_snap_recipients = snap_recipients.sum()\n",
"total_above_130 = above_130_fpl.sum()\n",
"share_130 = total_above_130 / total_snap_recipients\n",
"\n",
"# Recipients above 200%\n",
"above_200_fpl = (snap > 0) & (income_ratio > 2.0)\n",
"total_above_200 = above_200_fpl.sum()\n",
"share_200 = total_above_200 / total_snap_recipients\n",
"\n",
"# Benefits above 130% and 200%\n",
"snap_spm = sim.calculate('snap', period='2025-10', map_to='spm_unit')\n",
"spm_gross_income_ratio = sim.calculate('snap_gross_income_fpg_ratio', period='2025-10', map_to='spm_unit')\n",
"\n",
"total_snap_benefits = snap_spm.sum()\n",
"snap_above_130_spm = snap_spm * (spm_gross_income_ratio > 1.3)\n",
"snap_benefits_above_130 = snap_above_130_spm.sum()\n",
"share_benefits_130 = snap_benefits_above_130 / total_snap_benefits\n",
"\n",
"snap_above_200_spm = snap_spm * (spm_gross_income_ratio > 2.0)\n",
"snap_benefits_above_200 = snap_above_200_spm.sum()\n",
"share_benefits_200 = snap_benefits_above_200 / total_snap_benefits\n",
"\n",
"print(\"=== Above 130% FPL ===\")\n",
"print(f\"Recipients: {share_130:.2%} ({total_above_130:,.0f} of {total_snap_recipients:,.0f})\")\n",
"print(f\"Benefits: {share_benefits_130:.2%} (${snap_benefits_above_130:,.0f} of ${total_snap_benefits:,.0f})\")\n",
"\n",
"print(\"\\n=== Above 200% FPL ===\")\n",
"print(f\"Recipients: {share_200:.2%} ({total_above_200:,.0f} of {total_snap_recipients:,.0f})\")\n",
"print(f\"Benefits: {share_benefits_200:.2%} (${snap_benefits_above_200:,.0f} of ${total_snap_benefits:,.0f})\")"
]
},
{
"cell_type": "markdown",
"id": "cell-7",
"metadata": {},
"source": [
"## Analysis: Who Receives SNAP Above 200% FPL?\n",
"\n",
"To understand who qualifies for SNAP with income exceeding 200% FPL (both gross and net), we examine the eligibility pathways."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== SNAP Recipients Above 200% FPL (Both Gross and Net Income) ===\n",
"\n",
"Total recipients: 159,723\n",
"Total SPM units: 79,228\n",
"\n",
"Eligibility pathway:\n",
" In SPM units with elderly (60+) or disabled members: 159,723 (100.00%)\n",
" Receiving SSI: 79,228 (49.60%)\n",
" \n",
"Key finding: 100% of SNAP recipients above 200% FPL (both gross and net income)\n",
"live in households with at least one elderly or disabled member.\n",
"\n",
"The elderly/disabled exemption:\n",
" - Exempts households from the gross income test (can exceed 130% FPL or state BBCE limits)\n",
" - Still requires passing the net income test (200% FPL after deductions)\n",
" - Applies to entire household if any member is 60+ or disabled\n"
]
}
],
"source": [
"# Analyze those above 200% FPL (both gross and net)\n",
"net_ratio = sim.calculate('snap_net_income_fpg_ratio', period='2025-10', map_to='person')\n",
"has_elderly_disabled = sim.calculate('has_usda_elderly_disabled', period='2025-10', map_to='person')\n",
"ssi = sim.calculate('ssi', period='2025-10', map_to='person')\n",
"\n",
"# Filter to those above 200% both gross and net\n",
"above_200_both = (snap > 0) & (income_ratio > 2.0) & (net_ratio > 2.0)\n",
"\n",
"total_above_200_both = above_200_both.sum()\n",
"with_elderly_disabled = (above_200_both & has_elderly_disabled).sum()\n",
"with_ssi = (above_200_both & (ssi > 0)).sum()\n",
"\n",
"# At SPM unit level\n",
"net_ratio_spm = sim.calculate('snap_net_income_fpg_ratio', period='2025-10', map_to='spm_unit')\n",
"above_200_both_spm = (snap_spm > 0) & (spm_gross_income_ratio > 2.0) & (net_ratio_spm > 2.0)\n",
"total_units_above_200_both = above_200_both_spm.sum()\n",
"\n",
"print(\"=== SNAP Recipients Above 200% FPL (Both Gross and Net Income) ===\")\n",
"print(f\"\\nTotal recipients: {total_above_200_both:,.0f}\")\n",
"print(f\"Total SPM units: {total_units_above_200_both:,.0f}\")\n",
"print(f\"\\nEligibility pathway:\")\n",
"print(f\" In SPM units with elderly (60+) or disabled members: {with_elderly_disabled:,.0f} ({with_elderly_disabled/total_above_200_both*100:.2f}%)\")\n",
"print(f\" Receiving SSI: {with_ssi:,.0f} ({with_ssi/total_above_200_both*100:.2f}%)\")\n",
"print(f\" \")\n",
"print(f\"Key finding: 100% of SNAP recipients above 200% FPL (both gross and net income)\")\n",
"print(f\"live in households with at least one elderly or disabled member.\")\n",
"print(f\"\\nThe elderly/disabled exemption:\")\n",
"print(f\" - Exempts households from the gross income test (can exceed 130% FPL or state BBCE limits)\")\n",
"print(f\" - Still requires passing the net income test (200% FPL after deductions)\")\n",
"print(f\" - Applies to entire household if any member is 60+ or disabled\")"
]
},
{
"cell_type": "markdown",
"id": "cell-9",
"metadata": {},
"source": [
"## Eligibility Context\n",
"\n",
"SNAP recipients can have gross income above 130% FPL through three pathways:\n",
"\n",
"### 1. Categorical Eligibility (28% of those above 200% gross)\n",
"Receipt of certain benefits completely bypasses all income and asset tests:\n",
"- **SSI (Supplemental Security Income)**: Federal program for elderly, blind, or disabled with limited income\n",
"- **TANF non-cash benefits**: Through Broad-Based Categorical Eligibility (BBCE), states can provide minimal TANF services (like informational materials) to make households categorically eligible\n",
"- **State BBCE limits**: Many states set BBCE income limits at 200% FPL (CA, MA, MD, MI, NC, PA, VA, WA, and others)\n",
"\n",
"### 2. Elderly/Disabled Household Exemption (100% of those above 200% both gross and net)\n",
"Households with members aged 60+ or disabled:\n",
"- **Exempt from gross income test**: Can have income exceeding 130% FPL (or state BBCE limits)\n",
"- **Must still pass net income test**: Net income (after deductions) must be ≤200% FPL\n",
"- **Applies to entire household**: All members benefit if any one member is elderly or disabled\n",
"- **Example**: A 49-year-old working person living with an 80-year-old disabled parent can qualify with high gross income if deductions (medical expenses, shelter costs) bring net income below 200% FPL\n",
"\n",
"### Key Observations\n",
"- Benefits are concentrated at lower incomes: While 24% of recipients are above 130% FPL, they receive only 11% of benefits\n",
"- The elderly/disabled exemption is the dominant pathway for very high-income SNAP receipt (>200% FPL both gross and net)\n",
"- PolicyEngine-US models these rules based on [7 CFR § 273.9](https://www.law.cornell.edu/cfr/text/7/273.9) and state-specific BBCE parameters"
]
}
],
"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.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment