Last active
October 31, 2025 11:55
-
-
Save MaxGhenis/ca06de11cdcfba3f857002749f379d1f to your computer and use it in GitHub Desktop.
SNAP Recipients with Gross Income Above 130% FPL - PolicyEngine-US Analysis (2025)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "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