New York State income tax#

New York households pay progressive tax rates on income, and have access to several refundable and non-refundable tax credits. Consider a set of New York family types, each with $1,000 monthly rent and $50 monthly broadband costs, and varying in terms of marital status and number of children. Their net income—after state and federal taxes and benefits modeled by PolicyEngine US—is shown in the graph below.

The cliff is due to New York’s emergency SNAP allotment, which entitles SNAP-eligible households to the maximum benefit for their household size; this also affects other benefits through categorical eligibility.

Hide code cell source
from policyengine_us import IndividualSim
import pandas as pd
import plotly.express as px

LIGHT_GRAY = "#F5F5F5"
GRAY = "#BDBDBD"
BLUE = "#5091cc"
LIGHT_BLUE = "lightblue"
DARK_BLUE = "darkblue"


def make_tax(adults, children):
    sim = IndividualSim(year=2022)
    sim.add_person(name="head", age=25, rent=12_000)
    members = ["head"]
    if adults == 2:
        sim.add_person(name="spouse", age=25)
        members += ["spouse"]
    for i in range(children):
        child = "child{}".format(i)
        sim.add_person(name=child, age=6)
        members += [child]
    sim.add_tax_unit(name="tax_unit", members=members, premium_tax_credit=0)
    # $1,000 monthly rent, $50 monthly broadband.
    sim.add_spm_unit(name="spm_unit", members=members, broadband_cost=600)
    sim.add_household(name="household", members=members, state_code="NY")
    sim.vary("employment_income", max=100_000, step=100)
    employment_income = sim.calc("employment_income")[0]
    spm_unit_net_income = sim.calc("spm_unit_net_income")[0].round()
    mtr = 1 - sim.deriv(
        "spm_unit_net_income", "employment_income", wrt_target="head"
    )
    return pd.DataFrame(
        dict(
            employment_income=employment_income,
            spm_unit_net_income=spm_unit_net_income,
            mtr=mtr,
            adults=adults,
            children=str(children),
        )
    )


# Make a table of state taxes for different numbers of adults and children.
l = []
for adults in range(1, 3):
    for children in range(0, 4):
        l.append(make_tax(adults, children))

df = pd.concat(l)

LABELS = dict(
    employment_income="Employment income",
    spm_unit_net_income="Net income",
    mtr="Marginal tax rate",
    adults="Adults",
    children="Children",
)

COLOR_MAP = {"0": GRAY, "1": LIGHT_BLUE, "2": BLUE, "3": DARK_BLUE}

fig = px.line(
    df,
    "employment_income",
    "spm_unit_net_income",
    color="children",
    animation_frame="adults",
    labels=LABELS,
    title="Net income for a New York household",
    color_discrete_map=COLOR_MAP,
)
fig.update_layout(
    xaxis_tickformat="$,",
    yaxis_tickformat="$,",
    plot_bgcolor="white",
    xaxis_gridcolor=LIGHT_GRAY,
    yaxis_gridcolor=LIGHT_GRAY,
)
fig.show()

Marginal tax rates can reach as low as -84% and as high (excluding cliffs) as 65%.

Hide code cell source
fig = px.line(
    df,
    "employment_income",
    "mtr",
    color="children",
    animation_frame="adults",
    labels=LABELS,
    title="Marginal tax rate for a New York household",
    color_discrete_map=COLOR_MAP,
)
fig.update_layout(
    xaxis_tickformat="$,",
    yaxis_tickformat=".1%",
    yaxis_range=[-1, 1],
    plot_bgcolor="white",
    xaxis_gridcolor=LIGHT_GRAY,
    yaxis_gridcolor=LIGHT_GRAY,
)
fig.show()