taxcalc 5.3.0__py3-none-any.whl → 6.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. taxcalc/__init__.py +1 -1
  2. taxcalc/calcfunctions.py +11 -11
  3. taxcalc/calculator.py +1 -1
  4. taxcalc/cli/tc.py +1 -8
  5. taxcalc/data.py +1 -2
  6. taxcalc/policy.py +7 -22
  7. taxcalc/policy_current_law.json +6 -6
  8. taxcalc/records.py +78 -82
  9. taxcalc/records_variables.json +106 -106
  10. taxcalc/reforms/2017_law.json +1 -1
  11. taxcalc/reforms/ARPA.json +1 -1
  12. taxcalc/reforms/Renacci.json +1 -1
  13. taxcalc/reforms/TCJA.json +1 -1
  14. taxcalc/reforms/Trump2016.json +1 -1
  15. taxcalc/reforms/Trump2017.json +1 -1
  16. taxcalc/taxcalcio.py +60 -51
  17. taxcalc/tests/conftest.py +19 -14
  18. taxcalc/tests/reforms.json +1 -1
  19. taxcalc/tests/reforms_expect.csv +54 -54
  20. taxcalc/tests/test_4package.py +3 -15
  21. taxcalc/tests/test_calcfunctions.py +2 -2
  22. taxcalc/tests/test_calculator.py +197 -160
  23. taxcalc/tests/test_cpscsv.py +0 -22
  24. taxcalc/tests/test_data.py +11 -3
  25. taxcalc/tests/test_parameters.py +42 -0
  26. taxcalc/tests/test_records.py +139 -8
  27. taxcalc/tests/test_reforms.py +5 -7
  28. taxcalc/tests/test_taxcalcio.py +3 -58
  29. {taxcalc-5.3.0.dist-info → taxcalc-6.1.0.dist-info}/METADATA +1 -1
  30. {taxcalc-5.3.0.dist-info → taxcalc-6.1.0.dist-info}/RECORD +34 -40
  31. taxcalc/puf_ratios.csv +0 -26
  32. taxcalc/puf_weights.csv.gz +0 -0
  33. taxcalc/tests/test_compare.py +0 -330
  34. taxcalc/tests/test_compatible_data.py +0 -334
  35. taxcalc/tests/test_puf_var_stats.py +0 -194
  36. taxcalc/tests/test_pufcsv.py +0 -328
  37. {taxcalc-5.3.0.dist-info → taxcalc-6.1.0.dist-info}/WHEEL +0 -0
  38. {taxcalc-5.3.0.dist-info → taxcalc-6.1.0.dist-info}/entry_points.txt +0 -0
  39. {taxcalc-5.3.0.dist-info → taxcalc-6.1.0.dist-info}/licenses/LICENSE +0 -0
  40. {taxcalc-5.3.0.dist-info → taxcalc-6.1.0.dist-info}/top_level.txt +0 -0
@@ -1,194 +0,0 @@
1
- """
2
- Test generates statistics for puf.csv variables.
3
- """
4
- # CODING-STYLE CHECKS:
5
- # pycodestyle test_puf_var_stats.py
6
- # pylint --disable=locally-disabled test_puf_var_stats.py
7
-
8
- import os
9
- import json
10
- import copy
11
- import numpy as np
12
- import pandas as pd
13
- import pytest
14
- from taxcalc.policy import Policy
15
- from taxcalc.records import Records
16
- from taxcalc.calculator import Calculator
17
-
18
-
19
- def create_base_table(test_path):
20
- """
21
- Create and return base table.
22
- """
23
- # specify calculated variable names and descriptions
24
- calc_dict = {'eitc': 'Federal EITC',
25
- 'iitax': 'Federal income tax liability',
26
- 'payrolltax': 'Payroll taxes (ee+er) for OASDI+HI',
27
- 'c00100': 'Federal AGI',
28
- 'c02500': 'OASDI benefits in AGI',
29
- 'c04600': 'Post-phase-out personal exemption',
30
- 'c21040': 'Itemized deduction that is phased out',
31
- 'c04470': 'Post-phase-out itemized deduction',
32
- 'c04800': 'Federal regular taxable income',
33
- 'c05200': 'Regular tax on taxable income',
34
- 'c07220': 'Child tax credit (adjusted)',
35
- 'c11070': 'Extra child tax credit (refunded)',
36
- 'c07180': 'Child care credit',
37
- 'c09600': 'Federal AMT liability'}
38
- # specify read variable names and descriptions
39
- unused_var_set = set(['DSI', 'EIC',
40
- 'h_seq', 'a_lineno', 'ffpos', 'fips', 'agi_bin',
41
- 'FLPDYR', 'FLPDMO', 'f2441', 'f3800', 'f6251',
42
- 'f8582', 'f8606', 'f8829', 'f8910', 'f8936', 'n20',
43
- 'n24', 'n25', 'n30', 'PREP', 'SCHB', 'SCHCF', 'SCHE',
44
- 'TFORM', 'IE', 'TXST', 'XFPT', 'XFST', 'XOCAH',
45
- 'XOCAWH', 'XOODEP', 'XOPAR', 'XTOT', 'MARS', 'MIDR',
46
- 'RECID', 'gender', 'wage_head', 'wage_spouse',
47
- 'earnsplit', 'agedp1', 'agedp2', 'agedp3',
48
- 's006', 's008', 's009', 'WSAMP', 'TXRT',
49
- 'matched_weight', 'e00200p', 'e00200s',
50
- 'e00900p', 'e00900s', 'e02100p', 'e02100s',
51
- 'age_head', 'age_spouse',
52
- 'nu18', 'n1820', 'n21',
53
- 'ssi_ben', 'snap_ben', 'other_ben',
54
- 'mcare_ben', 'mcaid_ben', 'vet_ben',
55
- 'housing_ben', 'tanf_ben', 'wic_ben',
56
- 'blind_head', 'blind_spouse',
57
- 'PT_SSTB_income',
58
- 'PT_binc_w2_wages',
59
- 'PT_ubia_property'])
60
- records_varinfo = Records(data=None)
61
- read_vars = list(records_varinfo.USABLE_READ_VARS - unused_var_set)
62
- # get read variable information from JSON file
63
- rec_vars_path = os.path.join(test_path, '..', 'records_variables.json')
64
- with open(rec_vars_path, 'r', encoding='utf-8') as rvfile:
65
- read_var_dict = json.load(rvfile)
66
- # create table_dict with sorted read vars followed by sorted calc vars
67
- table_dict = {}
68
- for var in sorted(read_vars):
69
- if 'taxdata_puf' in read_var_dict['read'][var]['availability']:
70
- table_dict[var] = read_var_dict['read'][var]['desc']
71
- else:
72
- pass
73
- sorted_calc_vars = sorted(calc_dict.keys())
74
- for var in sorted_calc_vars:
75
- table_dict[var] = calc_dict[var]
76
- # construct DataFrame table from table_dict
77
- table = pd.DataFrame.from_dict(table_dict, orient='index')
78
- table.columns = ['description']
79
- return table
80
-
81
-
82
- def calculate_corr_stats(calc, table):
83
- """
84
- Calculate correlation coefficient matrix.
85
- """
86
- errmsg = ''
87
- for varname1 in table.index:
88
- var1 = calc.array(varname1)
89
- var1_cc = []
90
- for varname2 in table.index:
91
- var2 = calc.array(varname2)
92
- try:
93
- cor = np.corrcoef(var1, var2)[0][1]
94
- except FloatingPointError:
95
- msg = f'corr-coef error for {varname1} and {varname2}\n'
96
- errmsg += msg
97
- cor = 9.99 # because could not compute it
98
- var1_cc.append(cor)
99
- table[varname1] = var1_cc
100
- if errmsg:
101
- raise ValueError('\n' + errmsg)
102
-
103
-
104
- def calculate_mean_stats(calc, table, year):
105
- """
106
- Calculate weighted means for year.
107
- """
108
- total_weight = calc.total_weight()
109
- means = []
110
- for varname in table.index:
111
- wmean = calc.weighted_total(varname) / total_weight
112
- means.append(wmean)
113
- table[str(year)] = means
114
-
115
-
116
- def differences(new_filename, old_filename, stat_kind):
117
- """
118
- Return message string if differences detected by np.allclose();
119
- otherwise return empty string.
120
- """
121
- new_df = pd.read_csv(new_filename)
122
- old_df = pd.read_csv(old_filename)
123
- diffs = False
124
- if list(new_df.columns.values) == list(old_df.columns.values):
125
- for col in new_df.columns[1:]:
126
- if col == 'description':
127
- continue # skip description column
128
- if not np.allclose(new_df[col], old_df[col]):
129
- diffs = True
130
- else:
131
- diffs = True
132
- if diffs:
133
- new_name = os.path.basename(new_filename)
134
- old_name = os.path.basename(old_filename)
135
- msg = f'{stat_kind} RESULTS DIFFER:\n'
136
- msg += '-------------------------------------------------'
137
- msg += '-------------\n'
138
- msg += f'--- NEW RESULTS IN {new_name} FILE ---\n'
139
- msg += f'--- if new OK, copy {new_name} to\n'
140
- msg += f'--- {old_name} \n'
141
- msg += '--- and rerun test. '
142
- msg += '-------------------------------------------------'
143
- msg += '-------------\n'
144
- else:
145
- msg = ''
146
- os.remove(new_filename)
147
- return msg
148
-
149
-
150
- MEAN_FILENAME = 'puf_var_wght_means_by_year.csv'
151
- CORR_FILENAME = 'puf_var_correl_coeffs_2016.csv'
152
-
153
-
154
- @pytest.mark.requires_pufcsv
155
- def test_puf_var_stats(tests_path, puf_fullsample):
156
- """
157
- Main logic of test.
158
- """
159
- # create a baseline Policy object containing 2017_law.json parameters
160
- pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json')
161
- pre_tcja = Policy.read_json_reform(pre_tcja_jrf)
162
- baseline_policy = Policy()
163
- baseline_policy.implement_reform(pre_tcja)
164
- # create a Calculator object using baseline_policy and full puf.csv sample
165
- rec = Records(data=puf_fullsample)
166
- calc = Calculator(policy=baseline_policy, records=rec, verbose=False)
167
- # create base tables
168
- table_mean = create_base_table(tests_path)
169
- table_corr = copy.deepcopy(table_mean)
170
- del table_corr['description']
171
- # add statistics to tables
172
- year_headers = ['description']
173
- for year in range(Policy.JSON_START_YEAR, 2024 + 1):
174
- assert year == calc.current_year
175
- year_headers.append(str(year))
176
- calc.calc_all()
177
- calculate_mean_stats(calc, table_mean, year)
178
- if year == 2016:
179
- calculate_corr_stats(calc, table_corr)
180
- if year < 2034:
181
- calc.increment_year()
182
- # write tables to new CSV files
183
- mean_path = os.path.join(tests_path, MEAN_FILENAME + '-new')
184
- table_mean.sort_index(inplace=True)
185
- table_mean.to_csv(mean_path, header=year_headers, float_format='%8.0f')
186
- corr_path = os.path.join(tests_path, CORR_FILENAME + '-new')
187
- table_corr.sort_index(inplace=True)
188
- table_corr.to_csv(corr_path, float_format='%8.2f',
189
- columns=table_corr.index)
190
- # compare new and old CSV files for differences
191
- mean_msg = differences(mean_path, mean_path[:-4], 'MEAN')
192
- corr_msg = differences(corr_path, corr_path[:-4], 'CORR')
193
- if mean_msg or corr_msg:
194
- raise ValueError(mean_msg + corr_msg)
@@ -1,328 +0,0 @@
1
- """
2
- Tests of Tax-Calculator using puf.csv input.
3
-
4
- Note that the puf.csv file that is required to run this program has
5
- been constructed by the Tax-Calculator development team by merging
6
- information from the most recent publicly available IRS SOI PUF file
7
- and from the Census CPS file for the corresponding year. If you have
8
- acquired from IRS the most recent SOI PUF file and want to execute
9
- this program, contact the Tax-Calculator development team to discuss
10
- your options.
11
- """
12
- # CODING-STYLE CHECKS:
13
- # pycodestyle test_pufcsv.py
14
- # pylint --disable=locally-disabled test_pufcsv.py
15
-
16
- import os
17
- import json
18
- import pytest
19
- import numpy as np
20
- import pandas as pd
21
- from taxcalc.policy import Policy
22
- from taxcalc.records import Records
23
- from taxcalc.calculator import Calculator
24
-
25
-
26
- START_YEAR = 2017
27
- NUM_YEARS = 19
28
-
29
-
30
- @pytest.mark.pufcsv_agg
31
- @pytest.mark.requires_pufcsv
32
- def test_agg(tests_path, puf_fullsample):
33
- """
34
- Test Tax-Calculator aggregate taxes with no policy reform using
35
- the full-sample puf.csv and a small sub-sample of puf.csv
36
- """
37
- # pylint: disable=too-many-locals,too-many-statements
38
- nyrs = NUM_YEARS
39
- # create a baseline Policy object with current-law policy parameters
40
- baseline_policy = Policy()
41
- # create a Records object (rec) containing all puf.csv input records
42
- recs = Records(data=puf_fullsample)
43
- # create a Calculator object using baseline policy and puf records
44
- calc = Calculator(policy=baseline_policy, records=recs)
45
- calc.advance_to_year(START_YEAR)
46
- calc_start_year = calc.current_year
47
- # create aggregate diagnostic table (adt) as a Pandas DataFrame object
48
- adt = calc.diagnostic_table(nyrs).round(1) # column labels are int
49
- taxes_fullsample = adt.loc['Combined Liability ($b)']
50
- # compare actual DataFrame, adt, with the expected DataFrame, edt
51
- aggres_path = os.path.join(tests_path, 'pufcsv_agg_expect.csv')
52
- edt = pd.read_csv(aggres_path, index_col=False) # column labels are str
53
- edt.drop('Unnamed: 0', axis='columns', inplace=True)
54
- assert len(adt.columns.values) == len(edt.columns.values)
55
- diffs = False
56
- for icol in adt.columns.values:
57
- if not np.allclose(adt[icol].values, edt[str(icol)].values):
58
- diffs = True
59
- if diffs:
60
- new_filename = f'{aggres_path[:-10]}actual.csv'
61
- adt.to_csv(new_filename, float_format='%.1f')
62
- msg = 'PUFCSV AGG RESULTS DIFFER FOR FULL-SAMPLE\n'
63
- msg += '-------------------------------------------------\n'
64
- msg += '--- NEW RESULTS IN pufcsv_agg_actual.csv FILE ---\n'
65
- msg += '--- if new OK, copy pufcsv_agg_actual.csv to ---\n'
66
- msg += '--- pufcsv_agg_expect.csv ---\n'
67
- msg += '--- and rerun test. ---\n'
68
- msg += '--- (both are in taxcalc/tests) ---\n'
69
- msg += '-------------------------------------------------\n'
70
- raise ValueError(msg)
71
- # create aggregate diagnostic table using unweighted sub-sample of records
72
- fullsample = puf_fullsample
73
- rn_seed = 2222 # to ensure sub-sample is always the same
74
- subfrac = 0.05 # sub-sample fraction
75
- subsample = fullsample.sample(frac=subfrac, random_state=rn_seed)
76
- recs_subsample = Records(data=subsample)
77
- calc_subsample = Calculator(policy=baseline_policy, records=recs_subsample)
78
- calc_subsample.advance_to_year(START_YEAR)
79
- adt_subsample = calc_subsample.diagnostic_table(nyrs)
80
- # compare combined tax liability from full and sub samples for each year
81
- taxes_subsample = adt_subsample.loc['Combined Liability ($b)']
82
- msg = ''
83
- for cyr in range(calc_start_year, calc_start_year + nyrs):
84
- reltol = 0.045 # maximum allowed relative difference in tax liability
85
- if not np.allclose(taxes_subsample[cyr], taxes_fullsample[cyr],
86
- atol=0.0, rtol=reltol):
87
- reldiff = (taxes_subsample[cyr] / taxes_fullsample[cyr]) - 1.
88
- line1 = '\nPUFCSV AGG SUB-vs-FULL RESULTS DIFFER IN {}'
89
- line2 = '\n when subfrac={:.3f}, rtol={:.4f}, seed={}'
90
- line3 = '\n with sub={:.3f}, full={:.3f}, rdiff={:.4f}'
91
- msg += line1.format(cyr)
92
- msg += line2.format(subfrac, reltol, rn_seed)
93
- msg += line3.format(taxes_subsample[cyr],
94
- taxes_fullsample[cyr],
95
- reldiff)
96
- if msg:
97
- raise ValueError(msg)
98
-
99
-
100
- MTR_TAX_YEAR = 2013
101
- MTR_NEG_DIFF = False # set True to subtract (rather than add) small amount
102
- # specify payrolltax mtr histogram bin boundaries (or edges):
103
- PTAX_MTR_BIN_EDGES = [0.0, 0.02, 0.04, 0.06, 0.08,
104
- 0.10, 0.12, 0.14, 0.16, 0.18, 1.0]
105
- # the bin boundaries above are arbitrary, so users
106
- # may want to experiment with alternative boundaries
107
- # specify incometax mtr histogram bin boundaries (or edges):
108
- ITAX_MTR_BIN_EDGES = [-1.0, -0.30, -0.20, -0.10, 0.0,
109
- 0.10, 0.20, 0.30, 0.40, 0.50, 1.0]
110
- # the bin boundaries above are arbitrary, so users
111
- # may want to experiment with alternative boundaries
112
-
113
-
114
- def mtr_bin_counts(mtr_data, bin_edges, recid):
115
- """
116
- Compute mtr histogram bin counts and return results as a string.
117
- """
118
- res = ''
119
- (bincount, _) = np.histogram(mtr_data.round(decimals=4), bins=bin_edges)
120
- sum_bincount = np.sum(bincount)
121
- res += f'{sum_bincount} :'
122
- for idx in range(len(bin_edges) - 1):
123
- res += f' {bincount[idx]:6d}'
124
- res += '\n'
125
- if sum_bincount < mtr_data.size:
126
- res += 'WARNING: sum of bin counts is too low\n'
127
- mtr_min = mtr_data.min()
128
- mtr_max = mtr_data.max()
129
- bin_min = min(bin_edges)
130
- bin_max = max(bin_edges)
131
- if mtr_min < bin_min:
132
- res += f' min(mtr)={mtr_min:.2f}\n'
133
- for idx in range(mtr_data.size):
134
- if mtr_data[idx] < bin_min:
135
- res += (
136
- f' mtr={mtr_data[idx]:.2f} '
137
- f'for recid={recid[idx]}\n'
138
- )
139
- if mtr_max > bin_max:
140
- res += f' max(mtr)={mtr_max:.2f}\n'
141
- for idx in range(mtr_data.size):
142
- if mtr_data[idx] > bin_max:
143
- res += (
144
- f' mtr={mtr_data[idx]:.2f} '
145
- f'for recid={recid[idx]}\n'
146
- )
147
- return res
148
-
149
-
150
- def nonsmall_diffs(linelist1, linelist2, small=0.0):
151
- """
152
- Return True if line lists differ significantly; otherwise return False.
153
- Significant numerical difference means one or more numbers differ (between
154
- linelist1 and linelist2) by more than the specified small amount.
155
- """
156
- # embedded function used only in nonsmall_diffs function
157
- def isfloat(value):
158
- """
159
- Return True if value can be cast to float; otherwise return False.
160
- """
161
- try:
162
- float(value)
163
- return True
164
- except ValueError:
165
- return False
166
- # begin nonsmall_diffs logic
167
- assert isinstance(linelist1, list)
168
- assert isinstance(linelist2, list)
169
- if len(linelist1) != len(linelist2):
170
- return True
171
- assert 0.0 <= small <= 1.0
172
- epsilon = 1e-6
173
- smallamt = small + epsilon
174
- for line1, line2 in zip(linelist1, linelist2):
175
- if line1 == line2:
176
- continue
177
- tokens1 = line1.replace(',', '').split()
178
- tokens2 = line2.replace(',', '').split()
179
- for tok1, tok2 in zip(tokens1, tokens2):
180
- tok1_isfloat = isfloat(tok1)
181
- tok2_isfloat = isfloat(tok2)
182
- if tok1_isfloat and tok2_isfloat:
183
- if abs(float(tok1) - float(tok2)) <= smallamt:
184
- continue
185
- return True
186
- if not tok1_isfloat and not tok2_isfloat:
187
- if tok1 == tok2:
188
- continue
189
- return True
190
- return True
191
- return False
192
-
193
-
194
- @pytest.mark.requires_pufcsv
195
- def test_mtr(tests_path, puf_path):
196
- """
197
- Test Tax-Calculator marginal tax rates with no policy reform using puf.csv
198
-
199
- Compute histograms for each marginal tax rate income type using
200
- sample input from the puf.csv file and writing output to a string,
201
- which is then compared for differences with EXPECTED_MTR_RESULTS.
202
- """
203
- # pylint: disable=too-many-locals,too-many-statements
204
- assert len(PTAX_MTR_BIN_EDGES) == len(ITAX_MTR_BIN_EDGES)
205
- # construct actual results string, res
206
- res = ''
207
- if MTR_NEG_DIFF:
208
- res += 'MTR computed using NEGATIVE finite_diff '
209
- else:
210
- res += 'MTR computed using POSITIVE finite_diff '
211
- res += f'for tax year {MTR_TAX_YEAR}\n'
212
- # create a Policy object (clp) containing current-law policy parameters
213
- clp = Policy()
214
- clp.set_year(MTR_TAX_YEAR)
215
- # create a Records object (puf) containing puf.csv input records
216
- puf = Records(data=puf_path)
217
- recid = puf.RECID # pylint: disable=no-member
218
- # create a Calculator object using clp policy and puf records
219
- calc = Calculator(policy=clp, records=puf)
220
- res += f'Total number of data records = {puf.array_length}\n'
221
- res += 'PTAX mtr histogram bin edges:\n'
222
- res += f' {PTAX_MTR_BIN_EDGES}\n'
223
- res += 'ITAX mtr histogram bin edges:\n'
224
- res += f' {ITAX_MTR_BIN_EDGES}\n'
225
- variable_header = 'PTAX and ITAX mtr histogram bin counts for'
226
- # compute marginal tax rate (mtr) histograms for each mtr variable
227
- for var_str in Calculator.MTR_VALID_VARIABLES:
228
- zero_out = var_str == 'e01400'
229
- (mtr_ptax, mtr_itax, _) = calc.mtr(variable_str=var_str,
230
- negative_finite_diff=MTR_NEG_DIFF,
231
- zero_out_calculated_vars=zero_out,
232
- wrt_full_compensation=False)
233
- if zero_out:
234
- # check that calculated variables are consistent
235
- assert np.allclose((calc.array('iitax') +
236
- calc.array('payrolltax')),
237
- calc.array('combined'))
238
- assert np.allclose(calc.array('ptax_was'),
239
- calc.array('payrolltax'))
240
- assert np.allclose(calc.array('c21060') - calc.array('c21040'),
241
- calc.array('c04470'))
242
- assert np.allclose(calc.array('taxbc') + calc.array('c09600'),
243
- calc.array('c05800'))
244
- assert np.allclose((calc.array('c05800') +
245
- calc.array('othertaxes') -
246
- calc.array('c07100')),
247
- calc.array('c09200'))
248
- assert np.allclose(calc.array('c09200') - calc.array('refund'),
249
- calc.array('iitax'))
250
- if var_str == 'e00200s':
251
- # only MARS==2 filing units have valid MTR values
252
- mtr_ptax = mtr_ptax[calc.array('MARS') == 2]
253
- mtr_itax = mtr_itax[calc.array('MARS') == 2]
254
- res += f'{variable_header} {var_str}:\n'
255
- res += mtr_bin_counts(mtr_ptax, PTAX_MTR_BIN_EDGES, recid)
256
- res += mtr_bin_counts(mtr_itax, ITAX_MTR_BIN_EDGES, recid)
257
- # check for differences between actual and expected results
258
- mtrres_path = os.path.join(tests_path, 'pufcsv_mtr_expect.txt')
259
- with open(mtrres_path, 'r', encoding='utf-8') as expected_file:
260
- txt = expected_file.read()
261
- expected_results = txt.rstrip('\n\t ') + '\n' # cleanup end of file txt
262
- if nonsmall_diffs(res.splitlines(True), expected_results.splitlines(True)):
263
- new_filename = f'{mtrres_path[:-10]}actual.txt'
264
- with open(new_filename, 'w', encoding='utf-8') as new_file:
265
- new_file.write(res)
266
- msg = 'PUFCSV MTR RESULTS DIFFER\n'
267
- msg += '-------------------------------------------------\n'
268
- msg += '--- NEW RESULTS IN pufcsv_mtr_actual.txt FILE ---\n'
269
- msg += '--- if new OK, copy pufcsv_mtr_actual.txt to ---\n'
270
- msg += '--- pufcsv_mtr_expect.txt ---\n'
271
- msg += '--- and rerun test. ---\n'
272
- msg += '-------------------------------------------------\n'
273
- raise ValueError(msg)
274
-
275
-
276
- @pytest.mark.requires_pufcsv
277
- def test_credit_reforms(puf_subsample):
278
- """
279
- Test personal credit reforms using puf.csv subsample
280
- """
281
- rec = Records(data=puf_subsample)
282
- reform_year = 2017
283
- # create current-law Calculator object, calc1
284
- pol = Policy()
285
- calc1 = Calculator(policy=pol, records=rec)
286
- calc1.advance_to_year(reform_year)
287
- calc1.calc_all()
288
- itax1 = calc1.weighted_total('iitax')
289
- # create personal-refundable-credit-reform Calculator object, calc2
290
- reform = {'II_credit': {reform_year: [1000, 1000, 1000, 1000, 1000]}}
291
- pol.implement_reform(reform)
292
- calc2 = Calculator(policy=pol, records=rec)
293
- calc2.advance_to_year(reform_year)
294
- calc2.calc_all()
295
- itax2 = calc2.weighted_total('iitax')
296
- # create personal-nonrefundable-credit-reform Calculator object, calc3
297
- reform = {'II_credit_nr': {reform_year: [1000, 1000, 1000, 1000, 1000]}}
298
- pol = Policy()
299
- pol.implement_reform(reform)
300
- calc3 = Calculator(policy=pol, records=rec)
301
- calc3.advance_to_year(reform_year)
302
- calc3.calc_all()
303
- itax3 = calc3.weighted_total('iitax')
304
- # check income tax revenues generated by the three Calculator objects
305
- assert itax2 < itax1 # because refundable credits lower revenues
306
- assert itax3 > itax2 # because nonrefundable credits lower revenues less
307
- assert itax3 < itax1 # because nonrefundable credits lower revenues some
308
-
309
-
310
- @pytest.mark.requires_pufcsv
311
- def test_puf_availability(tests_path, puf_path):
312
- """
313
- Cross-check records_variables.json data with variables in puf.csv file
314
- """
315
- # make set of variable names in puf.csv file
316
- pufdf = pd.read_csv(puf_path)
317
- pufvars = set(list(pufdf))
318
- # make set of variable names that are marked as puf.csv available
319
- rvpath = os.path.join(tests_path, '..', 'records_variables.json')
320
- with open(rvpath, 'r', encoding='utf-8') as rvfile:
321
- rvdict = json.load(rvfile)
322
- recvars = set()
323
- for vname, vdict in rvdict['read'].items():
324
- if 'taxdata_puf' in vdict.get('availability', ''):
325
- recvars.add(vname)
326
- # check that pufvars and recvars sets are the same
327
- assert (pufvars - recvars) == set()
328
- assert (recvars - pufvars) == set()