This identifies beneficiaries of the Child Tax Credit by modeling its repeal. Both repeal from current (2017) state and TCJA state are considered on a static basis. Change to after-tax income by decile and share of after-tax income held by top 10% are calculated.
Data: CPS | Tax year: 2018 | Type: Static | Author: Max Ghenis | Date run: 2018-02-20
import taxcalc as tc
import pandas as pd
import numpy as np
import copy
from bokeh.io import show, output_notebook
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import urllib as url_lib # On Python 3.6 use "import urllib.request as url_lib".
tc.__version__
'0.16.1'
sns.set_style('white')
DPI = 75
mpl.rc('savefig', dpi=DPI)
mpl.rcParams['figure.dpi']= DPI
mpl.rcParams['figure.figsize'] = 6.4, 4.8 # Default.
mpl.rcParams['font.sans-serif'] = 'Roboto-Light'
mpl.rcParams['font.family'] = 'sans-serif'
# Set title text color to dark gray (https://material.io/color) not black.
TITLE_COLOR = '#212121'
mpl.rcParams['text.color'] = TITLE_COLOR
# Axis titles and tick marks are medium gray.
AXIS_COLOR = '#757575'
mpl.rcParams['axes.labelcolor'] = AXIS_COLOR
mpl.rcParams['xtick.color'] = AXIS_COLOR
mpl.rcParams['ytick.color'] = AXIS_COLOR
# Use Seaborn's default color palette.
# https://stackoverflow.com/q/48958426/1840471 for reproducibility.
sns.set_palette(sns.color_palette())
# Show one decimal in tables.
pd.set_option('precision', 1)
def gini(x, weights=None):
if weights is None:
weights = np.ones_like(x)
# Calculate mean absolute deviation in two steps, for weights.
count = np.multiply.outer(weights, weights)
mad = np.abs(np.subtract.outer(x, x) * count).sum() / count.sum()
# Gini equals half the relative mean absolute deviation.
rmad = mad / np.average(x, weights=weights)
return 0.5 * rmad
Load reforms from GitHub. Policies and reforms are named according to this convention:
{reform}_{year}_{object}
For example:
noctc_2017_policy
: Policy without CTC using 2017 law.ubi_2018_calc
: Calculator replacing CTC with UBI using 2018 (current) law.# Folders where reforms live.
GITHUB_BASE_URL = 'https://raw.githubusercontent.com/'
TAXCALC_GITHUB_BASE_URL = (GITHUB_BASE_URL +
'open-source-economics/Tax-Calculator/master/' +
'taxcalc/reforms/')
def read_url(url):
return url_lib.urlopen(url).read()
def read_reform_taxcalc_github(reform_name):
return read_url(TAXCALC_GITHUB_BASE_URL + reform_name + '.json')
def policy_from_reform(reform):
pol = tc.Policy()
pol.implement_reform(reform['policy'])
if pol.reform_errors:
print(pol.reform_errors)
return pol
def create_static_policy_taxcalc_github(reform_name):
reform = tc.Calculator.read_json_param_objects(
read_reform_taxcalc_github(reform_name), None)
return policy_from_reform(reform)
y2017_policy = create_static_policy_taxcalc_github(
'2017_law')
noctc_reform = {
2018: {
'_CTC_c': [0],
'_DependentCredit_Child_c': [0]
}}
Calculator
objects for static analyses¶recs = tc.Records.cps_constructor()
def static_baseline_calc(year):
calc = tc.Calculator(records=recs, policy=tc.Policy())
calc.advance_to_year(year)
calc.calc_all()
return calc
def weighted_sum(df, col):
return (df[col] * df['s006']).sum()
def child_ubi_reform(amount):
return {2018: {'_UBI_u18': [amount],
'_UBI_ecrt': [1.0]}}
def add_weighted_quantiles(df, col):
df.sort_values(by=col, inplace=True)
col_pctile = col + '_percentile_exact'
df[col_pctile] = 100 * df['s006'].cumsum() / df['s006'].sum()
# "Null out" negatives using -1, since integer arrays can't be NaN.
# TODO: Should these be null floats?
df[col_pctile] = np.where(df[col] >= 0, df[col_pctile], 0)
# Reduce top record, otherwise it's incorrectly rounded up.
df[col_pctile] = np.where(df[col_pctile] >= 99.99999, 99.99999, df[col_pctile])
df[col + '_percentile'] = np.ceil(df[col_pctile]).astype(int)
df[col + '_decile'] = np.ceil(df[col_pctile] / 10).astype(int)
df[col + '_quintile'] = np.ceil(df[col_pctile] / 20).astype(int)
return df
def static_calc(use_2017_law=False,
ctc_treatment='keep',
child_ubi_amount=0,
year=2018,
cols=['s006', 'aftertax_income', 'expanded_income', 'nu18', 'n24', 'XTOT'],
child_tax_units_only=True):
"""Creates static Calculator.
Args:
use_2017_law: Whether to use 2017 law vs. current law. Defaults to False.
ctc_treatment: How the Child Tax Credit is treated. Options include:
* 'keep': No change. Default.
* 'repeal': End entirely.
* 'rev_neutral_ubi': Replace with revenue-neutral child UBI.
* 'no_cut_ubi': [NOT YET IMPLEMENTED]
Replace with a child UBI equal to the current maximum value.
child_ubi_amount: UBI for nu18. Defaults to 0.
year: Year to advance calculations to.
cols: Columns to extract per Calculator record.
Defaults to ['s006', 'expanded_income', 'aftertax_income', 'nu18', 'n24', 'XTOT'].
child_tax_units_only: Limit tax units to those with children, as defined by
(nu18 + n24 > 0). Quantiles are calculated after this filtering. Defaults to true.
Returns:
DataFrame with `cols` and percentile, decile, and quintile of after-tax income.
"""
# Initiate policy using either 2017 or current law.
if use_2017_law:
pol = copy.deepcopy(y2017_policy)
else:
pol = tc.Policy()
# Enact reform based on ctc_treatment.
# Repeal CTC unless it's kept.
if ctc_treatment != 'keep':
pol.implement_reform(noctc_reform)
if child_ubi_amount > 0:
pol.implement_reform(child_ubi_reform(child_ubi_amount))
# Calculate. This is needed to calculate the revenue-neutral UBI.
calc = tc.Calculator(records=recs, policy=pol)
calc.advance_to_year(year)
calc.calc_all()
# TODO: Calculate revenue for revenue-neutral UBI.
# Create DataFrame and add identifiers.
df = calc.dataframe(cols)
if child_tax_units_only:
df = df[(df['nu18'] + df['n24']) > 0]
# Add percentiles.
df = add_weighted_quantiles(df, 'expanded_income')
df = add_weighted_quantiles(df, 'aftertax_income')
# Add identifiers.
df['use_2017_law'] = use_2017_law
df['ctc_treatment'] = ctc_treatment
# What's the column for the ID?
df['id'] = df.index
# Add weighted sums.
df['expanded_income_b'] = df['expanded_income'] * df['s006'] / 1e9
df['aftertax_income_b'] = df['aftertax_income'] * df['s006'] / 1e9
df['n24_m'] = df['n24'] * df['s006'] / 1e6
df['nu18_m'] = df['nu18'] * df['s006'] / 1e6
df['XTOT_m'] = df['XTOT'] * df['s006'] / 1e6
return df
scenarios_pre_ubi = pd.concat([
static_calc(use_2017_law=False, ctc_treatment='keep'),
static_calc(use_2017_law=False, ctc_treatment='repeal'),
static_calc(use_2017_law=True, ctc_treatment='keep'),
static_calc(use_2017_law=True, ctc_treatment='repeal')])
You loaded data for 2014. Tax-Calculator startup automatically extrapolated your data to 2014. You loaded data for 2014. Tax-Calculator startup automatically extrapolated your data to 2014. You loaded data for 2014. Tax-Calculator startup automatically extrapolated your data to 2014. You loaded data for 2014. Tax-Calculator startup automatically extrapolated your data to 2014.
scenarios_pre_ubi.sample(5)
s006 | aftertax_income | expanded_income | nu18 | n24 | XTOT | expanded_income_percentile_exact | expanded_income_percentile | expanded_income_decile | expanded_income_quintile | ... | aftertax_income_decile | aftertax_income_quintile | use_2017_law | ctc_treatment | id | expanded_income_b | aftertax_income_b | n24_m | nu18_m | XTOT_m | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
408993 | 588.6 | 65440.7 | 77640.3 | 1.0 | 1.0 | 3.0 | 62.1 | 63 | 7 | 4 | ... | 7 | 4 | False | repeal | 408993 | 4.6e-02 | 3.9e-02 | 5.9e-04 | 5.9e-04 | 1.8e-03 |
52384 | 41.0 | 539356.3 | 794669.2 | 2.0 | 2.0 | 4.0 | 99.7 | 100 | 10 | 5 | ... | 10 | 5 | False | keep | 52384 | 3.3e-02 | 2.2e-02 | 8.2e-05 | 8.2e-05 | 1.6e-04 |
7149 | 3.9 | 123065.4 | 158325.7 | 1.0 | 1.0 | 3.0 | 86.8 | 87 | 9 | 5 | ... | 9 | 5 | True | repeal | 7149 | 6.2e-04 | 4.8e-04 | 3.9e-06 | 3.9e-06 | 1.2e-05 |
389982 | 610.6 | 41581.1 | 42148.4 | 4.0 | 2.0 | 6.0 | 38.6 | 39 | 4 | 2 | ... | 5 | 3 | False | keep | 389982 | 2.6e-02 | 2.5e-02 | 1.2e-03 | 2.4e-03 | 3.7e-03 |
245121 | 76.5 | 92311.2 | 99725.2 | 3.0 | 2.0 | 4.0 | 71.5 | 72 | 8 | 4 | ... | 8 | 4 | True | repeal | 245121 | 7.6e-03 | 7.1e-03 | 1.5e-04 | 2.3e-04 | 3.1e-04 |
5 rows × 22 columns
aftertax_income_summary = (
scenarios_pre_ubi.groupby(['use_2017_law', 'ctc_treatment'], as_index=False).
apply(lambda x:
pd.Series({
'aftertax_income': weighted_sum(x, 'aftertax_income'),
'nu18': weighted_sum(x, 'nu18')}))).reset_index()
nu18_total = aftertax_income_summary['nu18'][0]
aftertax_income_chg = aftertax_income_summary.pivot(
index='use_2017_law',
columns='ctc_treatment',
values='aftertax_income'
)
aftertax_income_chg['chg'] = aftertax_income_chg['keep'] - aftertax_income_chg['repeal']
aftertax_income_chg['chg_b'] = aftertax_income_chg['chg'] / 1e9
aftertax_income_chg['child_ubi'] = aftertax_income_chg['chg'] / nu18_total
aftertax_income_chg[['chg_b', 'child_ubi']]
ctc_treatment | chg_b | child_ubi |
---|---|---|
use_2017_law | ||
False | 120.1 | 1464.5 |
True | 51.8 | 632.1 |
$52B aligns with TPC's 2017 estimate.
$120B aligns with this $52B plus JCT's estimate that TCJA's non-SSN reforms would cost $68B starting in 2019. The 2018 estimate of $29B does not capture the refundable portions.
Maximum CTCs of $1,000 for 2017 and $2,000 for 2018 are verified below by comparing keep and repeal per tax unit.
scenarios = pd.concat([
scenarios_pre_ubi,
static_calc(use_2017_law=False, ctc_treatment='rev_neutral_ubi',
child_ubi_amount=aftertax_income_chg.loc[False, 'child_ubi']),
static_calc(use_2017_law=True, ctc_treatment='rev_neutral_ubi',
child_ubi_amount=aftertax_income_chg.loc[True, 'child_ubi']),
static_calc(use_2017_law=False, ctc_treatment='top_ubi',
child_ubi_amount=2000),
static_calc(use_2017_law=True, ctc_treatment='top_ubi',
child_ubi_amount=1000)
])
You loaded data for 2014. Tax-Calculator startup automatically extrapolated your data to 2014. You loaded data for 2014. Tax-Calculator startup automatically extrapolated your data to 2014. You loaded data for 2014. Tax-Calculator startup automatically extrapolated your data to 2014. You loaded data for 2014. Tax-Calculator startup automatically extrapolated your data to 2014.
Subtract top_ubi
from rev_neutral_ubi
.
scenarios.groupby(['use_2017_law', 'ctc_treatment'])['aftertax_income_b'].sum()
use_2017_law ctc_treatment False keep 3950.9 repeal 3830.7 rev_neutral_ubi 3950.9 top_ubi 3994.8 True keep 3877.9 repeal 3826.1 rev_neutral_ubi 3877.9 top_ubi 3908.1 Name: aftertax_income_b, dtype: float64
$44B to enact a $2,000 child dividend; $30B to enact a $1,000 child dividend under 2017 law.
def gini(x, w=None):
# Array indexing requires reset indexes.
x = pd.Series(x).reset_index(drop=True)
if w is None:
w = np.ones_like(x)
w = pd.Series(w).reset_index(drop=True)
n = x.size
wxsum = sum(w * x)
wsum = sum(w)
sxw = np.argsort(x)
sx = x[sxw] * w[sxw]
sw = w[sxw]
pxi = np.cumsum(sx) / wxsum
pci = np.cumsum(sw) / wsum
g = 0.0
for i in np.arange(1, n):
g = g + pxi[i] * pci[i - 1] - pci[i] * pxi[i - 1]
return g
Runtime: ~1min.
ginis = scenarios[scenarios['aftertax_income'] >= 0].groupby(
['use_2017_law', 'ctc_treatment']).apply(
lambda x: gini(x['aftertax_income'], x['s006']))
with pd.option_context('precision', 4):
print(ginis)
use_2017_law ctc_treatment False keep 0.4651 repeal 0.4726 rev_neutral_ubi 0.4627 top_ubi 0.4593 True keep 0.4645 repeal 0.4704 rev_neutral_ubi 0.4660 top_ubi 0.4636 dtype: float64
scenario_pivot = scenarios.pivot_table(values=['aftertax_income_b'],
index=['id', 'use_2017_law'],
columns='ctc_treatment').reset_index()
# Adapted from https://stackoverflow.com/q/42099024/1840471.
scenario_pivot.columns = ["_".join((j, i)) for i, j in scenario_pivot.columns]
scenario_pivot.columns = scenario_pivot.columns.str.lstrip('_')
# Dimensions based on tax unit and baseline.
base_aftiq = scenarios.loc[scenarios['ctc_treatment'] == 'keep',
['id', 'use_2017_law', 'nu18', 'n24', 's006', 'nu18_m', 'n24_m',
'aftertax_income_percentile', 'aftertax_income_decile',
'aftertax_income_quintile',
'expanded_income_percentile', 'expanded_income_decile',
'expanded_income_quintile']]
scenario_pivot = pd.merge(scenario_pivot, base_aftiq, on=['id', 'use_2017_law'])
scenarios.columns
Index([u's006', u'aftertax_income', u'expanded_income', u'nu18', u'n24', u'XTOT', u'expanded_income_percentile_exact', u'expanded_income_percentile', u'expanded_income_decile', u'expanded_income_quintile', u'aftertax_income_percentile_exact', u'aftertax_income_percentile', u'aftertax_income_decile', u'aftertax_income_quintile', u'use_2017_law', u'ctc_treatment', u'id', u'expanded_income_b', u'aftertax_income_b', u'n24_m', u'nu18_m', u'XTOT_m'], dtype='object')
def add_ratios(df):
df['repeal_afti_chg'] = df['repeal_aftertax_income_b'] - df['keep_aftertax_income_b']
df['repeal_afti_pctchg'] = 100 * df['repeal_afti_chg'] / df['keep_aftertax_income_b']
df['ubi_afti_chg'] = df['rev_neutral_ubi_aftertax_income_b'] - df['keep_aftertax_income_b']
df['ubi_afti_pctchg'] = 100 * df['ubi_afti_chg'] / df['keep_aftertax_income_b']
df['tubi_afti_chg'] = df['top_ubi_aftertax_income_b'] - df['keep_aftertax_income_b']
df['tubi_afti_pctchg'] = 100 * df['tubi_afti_chg'] / df['keep_aftertax_income_b']
#df['ubi_afti_chg_per_nu18'] = df['ubi_afti_chg'] / (df['nu18_m'] * 1000)
df['ctc_per_n24'] = (
(df['keep_aftertax_income_b'] - df['repeal_aftertax_income_b']) /
(df['n24_m'] / 1000)).round()
df['ctc_per_nu18'] = (
(df['keep_aftertax_income_b'] - df['repeal_aftertax_income_b']) /
(df['nu18_m'] * 1000)).round()
add_ratios(scenario_pivot)
scenario18 = scenario_pivot[~scenario_pivot['use_2017_law']].drop(columns='use_2017_law')
scenario17 = scenario_pivot[scenario_pivot['use_2017_law']].drop(columns='use_2017_law')
Verify maximum CTC.
scenario_pivot.groupby('use_2017_law')['ctc_per_n24'].max()
use_2017_law False 2000.0 True 1000.0 Name: ctc_per_n24, dtype: float64
Merge to baseline after-tax income quantiles.
def quantile_summary(df, groupby):
qs = df.groupby(groupby).sum()
add_ratios(qs)
# Exclude 0, the negative group.
return qs.loc[1:]
quint = quantile_summary(scenario18, 'aftertax_income_quintile')
dec = quantile_summary(scenario18, 'aftertax_income_decile')
pct = quantile_summary(scenario18, 'aftertax_income_percentile')
quint_e = quantile_summary(scenario18, 'expanded_income_quintile')
dec_e = quantile_summary(scenario18, 'expanded_income_decile')
pct_e = quantile_summary(scenario18, 'expanded_income_percentile')
pct_e['nu18_m']
expanded_income_percentile 1 0.7 2 0.5 3 0.4 4 0.4 5 0.4 6 0.4 7 0.4 8 0.4 9 0.4 10 0.4 11 0.4 12 0.5 13 0.5 14 0.5 15 0.6 16 0.5 17 0.6 18 0.6 19 0.6 20 0.6 21 0.6 22 0.6 23 0.7 24 0.7 25 0.7 26 0.7 27 0.7 28 0.7 29 0.7 30 0.7 ... 71 1.0 72 1.0 73 1.0 74 1.0 75 1.0 76 1.0 77 1.0 78 1.0 79 1.0 80 1.0 81 1.0 82 1.0 83 1.0 84 1.0 85 1.0 86 1.0 87 1.0 88 1.0 89 1.0 90 1.0 91 1.0 92 1.0 93 1.0 94 1.0 95 1.0 96 1.0 97 1.0 98 1.0 99 1.0 100 1.0 Name: nu18_m, Length: 100, dtype: float64
pct.loc[1:40, 'ubi_afti_pctchg']
aftertax_income_percentile 1 3.8e+04 2 2.4e+02 3 7.9e+01 4 4.5e+01 5 3.0e+01 6 2.3e+01 7 1.3e+01 8 1.0e+01 9 7.9e+00 10 7.1e+00 11 5.3e+00 12 4.2e+00 13 3.9e+00 14 2.9e+00 15 3.3e+00 16 3.2e+00 17 2.1e+00 18 2.6e+00 19 2.0e+00 20 1.2e+00 21 1.6e-01 22 1.2e+00 23 5.8e-01 24 6.0e-01 25 8.6e-02 26 -3.3e-01 27 1.8e-01 28 1.0e-01 29 1.6e-01 30 -7.1e-01 31 -1.7e-02 32 -4.1e-01 33 -1.1e-01 34 -1.6e-01 35 -4.1e-01 36 -3.4e-01 37 -2.1e-01 38 -4.9e-01 39 -3.7e-01 40 -4.3e-01 Name: ubi_afti_pctchg, dtype: float64
dec['ubi_afti_pctchg']
aftertax_income_decile 1 2.2e+01 2 2.9e+00 3 1.8e-01 4 -3.0e-01 5 -4.4e-01 6 -3.4e-01 7 -3.2e-01 8 -4.0e-01 9 -3.6e-01 10 -4.9e-02 Name: ubi_afti_pctchg, dtype: float64
Make all of these two lines: revenue-neutral and top-up!
# Make y-axis %
f, ax = plt.subplots()
dec['ubi_afti_pctchg'].plot(label='$1,445 child dividend (revenue-neutral)')
dec['tubi_afti_pctchg'].plot(label='$2,000 child dividend')
plt.legend()
sns.despine(left=True, bottom=True)
ax.set(xlabel='Decile of income after taxes and transfers (baseline)',
ylabel='% change to income after taxes and transfers') #,
# yticks=[0, 5, 10, 15, 20, 25])
plt.title('Distributional impact of replacing CTC with revenue-neutral child dividend', loc='left')
plt.show()
Zoom in on bottom decile.
(9.1e-1 - 2.6e-1) / 2.6e-1
2.5
pct.iloc[0]
id 2.1e+08 keep_aftertax_income_b 2.7e-03 repeal_aftertax_income_b 2.2e-03 rev_neutral_ubi_aftertax_income_b 1.0e+00 top_ubi_aftertax_income_b 1.4e+00 nu18 1.3e+03 n24 1.1e+03 s006 4.9e+05 nu18_m 7.1e-01 n24_m 6.0e-01 aftertax_income_decile 8.4e+02 aftertax_income_quintile 8.4e+02 expanded_income_percentile 8.5e+02 expanded_income_decile 8.4e+02 expanded_income_quintile 8.4e+02 repeal_afti_chg -5.7e-04 repeal_afti_pctchg -2.1e+01 ubi_afti_chg 1.0e+00 ubi_afti_pctchg 3.8e+04 tubi_afti_chg 1.4e+00 tubi_afti_pctchg 5.2e+04 ctc_per_n24 1.0e+00 ctc_per_nu18 0.0e+00 Name: 1, dtype: float64
pct.loc[1:30, 'ubi_afti_pctchg']
aftertax_income_percentile 1 3.8e+04 2 2.4e+02 3 7.9e+01 4 4.5e+01 5 3.0e+01 6 2.3e+01 7 1.3e+01 8 1.0e+01 9 7.9e+00 10 7.1e+00 11 5.3e+00 12 4.2e+00 13 3.9e+00 14 2.9e+00 15 3.3e+00 16 3.2e+00 17 2.1e+00 18 2.6e+00 19 2.0e+00 20 1.2e+00 21 1.6e-01 22 1.2e+00 23 5.8e-01 24 6.0e-01 25 8.6e-02 26 -3.3e-01 27 1.8e-01 28 1.0e-01 29 1.6e-01 30 -7.1e-01 Name: ubi_afti_pctchg, dtype: float64
f, ax = plt.subplots()
pct.loc[2:30, 'ubi_afti_pctchg'].plot(label='$1,445 child dividend (revenue-neutral)')
pct.loc[2:30, 'tubi_afti_pctchg'].plot(label='$2,000 child dividend')
plt.legend()
sns.despine(left=True, bottom=True)
ax.set(xlabel='Percentile of income after taxes and transfers (baseline)',
ylabel='% change to income after taxes and transfers',
ylim=0)
plt.title('Impact on bottom decile of replacing CTC with revenue-neutral child dividend', loc='left')
plt.show()
dec['ubi_afti_pctchg'].plot()
plt.show()
(-dec['repeal_afti_pctchg']).plot()
plt.show()
pct['ubi_afti_pctchg'].plot()
plt.show()
pct['ubi_afti_pctchg']
aftertax_income_percentile 1 0.997135 2 0.513500 3 0.220740 4 0.106696 5 0.076551 6 0.052030 7 0.040044 8 0.027583 9 0.021248 10 0.017078 11 0.016923 12 0.014500 13 0.012069 14 0.007707 15 0.009464 16 0.007828 17 0.009463 18 0.007793 19 0.007583 20 0.006218 21 0.005318 22 0.005642 23 0.007474 24 0.005099 25 0.003017 26 0.002001 27 0.004411 28 0.003208 29 0.002630 30 0.002863 ... 71 -0.001805 72 -0.001798 73 -0.001549 74 -0.001841 75 -0.001570 76 -0.001960 77 -0.001807 78 -0.001577 79 -0.001322 80 -0.001658 81 -0.001947 82 -0.001964 83 -0.002039 84 -0.001712 85 -0.001807 86 -0.001844 87 -0.002003 88 -0.001850 89 -0.001925 90 -0.001416 91 -0.001576 92 -0.001403 93 -0.001445 94 -0.001031 95 -0.000925 96 -0.000909 97 -0.001614 98 -0.001244 99 -0.000890 100 0.001819 Name: ubi_afti_pctchg, Length: 100, dtype: float64
pct['ubi_afti_chg'].plot()
plt.show()
pct['ubi_afti_chg_per_nu18'].plot()
plt.show()
dec['ctc_per_nu18'].plot()
plt.show()
pct_e['ctc_per_n24'].plot()
plt.show()
pct_e['ctc_per_nu18'].plot()
plt.show()
pct['ctc_per_nu18']
aftertax_income_percentile 1 1.0 2 19.0 3 44.0 4 122.0 5 137.0 6 268.0 7 376.0 8 416.0 9 478.0 10 543.0 11 541.0 12 574.0 13 711.0 14 861.0 15 769.0 16 874.0 17 785.0 18 865.0 19 850.0 20 973.0 21 1058.0 22 1029.0 23 960.0 24 1078.0 25 1220.0 26 1304.0 27 1140.0 28 1236.0 29 1289.0 30 1277.0 ... 71 1675.0 72 1673.0 73 1640.0 74 1671.0 75 1652.0 76 1700.0 77 1699.0 78 1683.0 79 1646.0 80 1685.0 81 1744.0 82 1760.0 83 1759.0 84 1714.0 85 1738.0 86 1751.0 87 1768.0 88 1777.0 89 1784.0 90 1721.0 91 1769.0 92 1760.0 93 1779.0 94 1707.0 95 1711.0 96 1664.0 97 1771.0 98 1770.0 99 1730.0 100 203.0 Name: ctc_per_nu18, Length: 100, dtype: float64
Tax units with greatest change to after-tax income from the UBI model.
scenario18[(scenario18['rev_neutral_ubi_aftertax_income_b'] >= 0) &
(scenario18['keep_aftertax_income_b'] >= 0) &
(scenario18['ubi_afti_chg'] < np.inf)].sort_values('ubi_afti_chg').tail()
id | keep_aftertax_income_b | repeal_aftertax_income_b | rev_neutral_ubi_aftertax_income_b | top_ubi_aftertax_income_b | nu18 | n24 | s006 | nu18_m | n24_m | ... | aftertax_income_quintile | expanded_income_percentile | expanded_income_decile | expanded_income_quintile | repeal_afti_chg | repeal_afti_pctchg | ubi_afti_chg | ubi_afti_pctchg | ctc_per_n24 | ctc_per_nu18 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
416420 | 208210 | 9.5e-02 | 9.5e-02 | 1.1e-01 | 1.1e-01 | 5.0 | 3.0 | 1960.3 | 9.8e-03 | 5.9e-03 | ... | 3 | 49 | 5 | 3 | 0.0e+00 | 0.0e+00 | 1.4e-02 | 0.1 | 0.0 | 0.0 |
474510 | 237255 | 4.6e-02 | 4.6e-02 | 6.0e-02 | 6.5e-02 | 5.0 | 0.0 | 1964.3 | 9.8e-03 | 0.0e+00 | ... | 2 | 25 | 3 | 2 | 0.0e+00 | 0.0e+00 | 1.4e-02 | 0.2 | NaN | 0.0 |
55834 | 27917 | 1.1e-01 | 1.1e-01 | 1.3e-01 | 1.3e-01 | 10.0 | 3.0 | 1195.8 | 1.2e-02 | 3.6e-03 | ... | 4 | 72 | 8 | 4 | -2.1e-03 | -1.9e-02 | 1.5e-02 | 0.1 | 576.0 | 0.0 |
519080 | 259540 | 0.0e+00 | 0.0e+00 | 1.7e-02 | 2.3e-02 | 4.0 | 3.0 | 2833.1 | 1.1e-02 | 8.5e-03 | ... | 1 | 1 | 1 | 1 | 0.0e+00 | NaN | 1.7e-02 | 1.0 | 0.0 | 0.0 |
646484 | 323242 | 9.8e-02 | 9.8e-02 | 1.2e-01 | 1.3e-01 | 9.0 | 0.0 | 1651.6 | 1.5e-02 | 0.0e+00 | ... | 4 | 57 | 6 | 3 | 0.0e+00 | 0.0e+00 | 2.2e-02 | 0.2 | NaN | 0.0 |
5 rows × 22 columns
nu18
and n24
¶What explains the difference by decile?
keep_cur = scenarios[(~scenarios['use_2017_law']) &
(scenarios['ctc_treatment'] == 'keep')]
print ('Total children under 18: ' +
'{:0.1f}M'.format((keep_cur['nu18_m'].sum())))
print ('Total children eligible for CTC: ' +
'{:0.1f}M'.format((keep_cur['n24_m'].sum())))
Total children under 18: 82.0M Total children eligible for CTC: 81.9M
How many households have nu18 > n24? n24 > nu18?
keep_cur.pivot_table(index='n24', columns='nu18', values='s006', aggfunc=sum)
nu18 | 0.0 | 1.0 | 2.0 | 3.0 | 4.0 | 5.0 | 6.0 | 7.0 | 8.0 | 9.0 | 10.0 | 11.0 | 12.0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
n24 | |||||||||||||
0.0 | 1.1e+08 | 5.8e+06 | 5.5e+05 | 2.1e+05 | 6.9e+04 | 18471.4 | 5404.8 | 2750.3 | 50.6 | 3008.3 | NaN | NaN | NaN |
1.0 | 4.8e+06 | 1.7e+07 | 1.8e+06 | 8.7e+04 | 1.6e+04 | 3154.0 | 218.9 | NaN | NaN | NaN | NaN | NaN | NaN |
2.0 | 2.2e+06 | 8.5e+05 | 1.3e+07 | 9.3e+05 | 4.7e+04 | 7316.6 | 1759.9 | 486.0 | NaN | NaN | NaN | NaN | NaN |
3.0 | 1.0e+06 | 4.8e+05 | 2.9e+04 | 4.6e+06 | 1.6e+06 | 459044.9 | 135175.9 | 47640.0 | 15654.6 | 5527.2 | 4263.2 | 119.0 | 147.7 |
keep_cur[keep_cur['nu18'] > keep_cur['n24']]['s006'].sum() / 1e6
11.757627969999998
keep_cur[keep_cur['n24'] > keep_cur['nu18']]['s006'].sum() / 1e6
9.3909378300000004
n18 / n24
by percentile¶pct_e_cur = keep_cur.groupby('expanded_income_percentile').sum()
pct_e_cur['n24_per_nu18'] = pct_e_cur['n24_m'] / pct_e_cur['nu18_m']
pct_e_cur['n24_per_nu18'].head(10)
expanded_income_percentile 0 0.8 1 0.8 2 1.9 3 2.1 4 1.8 5 1.9 6 1.9 7 1.7 8 1.6 9 1.7 Name: n24_per_nu18, dtype: float64
dec_e_cur = keep_cur.groupby('expanded_income_decile').sum()
dec_e_cur['n24_per_nu18'] = dec_e_cur['n24_m'] / dec_e_cur['nu18_m']
dec_e_cur[['n24_m', 'nu18_m', 'n24_per_nu18']]
n24_m | nu18_m | n24_per_nu18 | |
---|---|---|---|
expanded_income_decile | |||
0 | 8.2e-02 | 9.8e-02 | 0.8 |
1 | 7.9e+00 | 4.7e+00 | 1.7 |
2 | 5.7e+00 | 4.5e+00 | 1.3 |
3 | 6.0e+00 | 5.7e+00 | 1.1 |
4 | 6.6e+00 | 6.5e+00 | 1.0 |
5 | 7.5e+00 | 7.9e+00 | 1.0 |
6 | 8.1e+00 | 8.7e+00 | 0.9 |
7 | 8.5e+00 | 9.4e+00 | 0.9 |
8 | 9.4e+00 | 1.0e+01 | 0.9 |
9 | 1.1e+01 | 1.2e+01 | 0.9 |
10 | 1.2e+01 | 1.3e+01 | 0.9 |
f, ax = plt.subplots()
(pct['nu18_m'] / pct['nu18_m'].sum()).plot(label='Children under 18')
(pct['n24_m'] / pct['n24_m'].sum()).plot(label='CTC-eligible children')
sns.despine(left=True, bottom=True)
plt.legend()
ax.set(xlabel='Percentile of income after taxes and transfers',
ylabel='Share of children under 18')
plt.axhline(y=0.01, c='gray', linestyle='dashed', linewidth=0.3, zorder=-1)
plt.title('Share of children by after-tax income percentile', loc='left')
plt.show()
Ratio between the two.
f, ax = plt.subplots()
pct_e_cur['n24_per_nu18'].plot()
sns.despine(left=True, bottom=True)
ax.set(xlabel='Expanded Income Percentile',
ylabel='CTC-eligible children per child under 18')
plt.title('CTC-eligible children per child under 18, by income percentile', loc='left')
plt.axhline(y=1, c='gray', linestyle='dashed', linewidth=0.3, zorder=-1)
plt.show()
# Function that takes a set of dimensions and metrics, and
# creates a summary table where metrics are properly weighted.