diff --git a/ogcore/aggregates.py b/ogcore/aggregates.py index 02b830f4a..9626ae926 100644 --- a/ogcore/aggregates.py +++ b/ogcore/aggregates.py @@ -399,7 +399,6 @@ def revenue( cons_tax_revenue = ( tax.cons_tax_liab(c, p_i, p, method) * pop_weights ).sum() - payroll_tax_revenue = p.frac_tax_payroll[-1] * iit_payroll_tax_revenue elif method == "TPI": p_i = ( np.tile(p.io_matrix.reshape(1, p.I, p.M), (p.T, 1, 1)) @@ -416,9 +415,15 @@ def revenue( cons_tax_revenue = ( (tax.cons_tax_liab(c, p_i, p, method) * pop_weights).sum(1).sum(1) ) - payroll_tax_revenue = ( - p.frac_tax_payroll[: p.T] * iit_payroll_tax_revenue - ) + payroll_tax_revenue = get_payroll_tax_revenue( + w, L, iit_payroll_tax_revenue, p, method + ) + # When payroll taxes are modeled explicitly via tau_payroll, they are + # excluded from the income and payroll tax functions, so payroll tax + # revenue must be added into iit_payroll_tax_revenue (which enters + # total revenue) before the income tax portion is separated out. + if np.any(p.tau_payroll != 0): + iit_payroll_tax_revenue += payroll_tax_revenue business_tax_revenue = tax.get_biz_tax(w, Y, L, K, p_m, p, m, method).sum( -1 ) @@ -446,6 +451,63 @@ def revenue( ) +def get_payroll_tax_revenue(w, L, iit_payroll_tax_revenue, p, method): + r""" + Calculate aggregate payroll tax revenue. + + How payroll tax revenue is computed depends on how the user has + chosen to represent payroll taxes in the model. If payroll taxes + are included directly through the ``tau_payroll`` parameter, then + revenue is the payroll tax rate times aggregate labor income: + + .. math:: + PR_{t} = \tau^{p}_{t}w_{t}L_{t} + + Otherwise, payroll taxes are assumed to be embedded in the estimated + income and payroll tax functions (the default), and payroll tax + revenue is separated out as a fraction ``frac_tax_payroll`` of the + combined income and payroll tax revenue. These two calculations are + identical when ``tau_payroll`` is zero. + + Args: + w (array_like): the real wage rate + L (array_like): aggregate labor by industry + iit_payroll_tax_revenue (array_like): aggregate income and + payroll tax revenue + p (OG-Core Specifications object): model parameters + method (str): adjusts calculation dimensions based on 'SS' or + 'TPI' + + Returns: + payroll_tax_revenue (array_like): aggregate payroll tax revenue + + """ + if np.any(p.tau_payroll != 0): + # Payroll taxes are modeled explicitly via tau_payroll, so + # revenue is the payroll tax rate times aggregate labor income + # (summing labor across industries). + L_total = L.sum(-1) + if method == "SS": + payroll_tax_revenue = p.tau_payroll[-1] * w * L_total + else: # TPI + payroll_tax_revenue = ( + p.tau_payroll[: p.T] * w[: p.T] * L_total[: p.T] + ) + else: + # Payroll taxes are embedded in the income and payroll tax + # functions, so revenue is a fraction of the combined revenue. + if method == "SS": + payroll_tax_revenue = ( + p.frac_tax_payroll[-1] * iit_payroll_tax_revenue + ) + else: # TPI + payroll_tax_revenue = ( + p.frac_tax_payroll[: p.T] * iit_payroll_tax_revenue + ) + + return payroll_tax_revenue + + def get_r_p(r, r_gov, p_m, K_vec, K_g, D, MPKg_vec, p, method): r""" Compute the interest rate on the household's portfolio of assets, diff --git a/tests/test_aggregates.py b/tests/test_aggregates.py index 4556018d5..751b72cdb 100644 --- a/tests/test_aggregates.py +++ b/tests/test_aggregates.py @@ -1474,111 +1474,111 @@ def test_get_C(c, p, method, expected): # vector of output prices p_m = np.ones((p.T, p.M)) -expected1 = 0.5688319028341413 +expected1 = 0.7598207140204407 expected2 = np.array( [ - 0.58978896, - 0.5318829, - 0.58291302, - 0.56616446, - 0.60152253, - 0.63685373, - 0.60718972, - 0.56236328, - 0.56929121, - 0.60536959, - 0.58763365, - 0.59627562, - 0.55409009, - 0.56782614, - 0.56400569, - 0.6636463, - 0.59160813, - 0.64735391, - 0.72066489, - 0.64096484, - 0.61899218, - 0.58806093, - 0.54783766, - 0.5393597, - 0.55685316, - 0.65395071, - 0.58946501, - 0.64129696, - 0.58759922, - 0.5580478, + 0.78077777, + 0.72068128, + 0.77564557, + 0.75220586, + 0.78498646, + 0.82226742, + 0.78953295, + 0.75499567, + 0.75017449, + 0.79555133, + 0.77677308, + 0.78748408, + 0.73859649, + 0.75895348, + 0.75059024, + 0.85599023, + 0.77630208, + 0.84016811, + 0.90505241, + 0.82363203, + 0.80717282, + 0.77141901, + 0.73973490, + 0.72241447, + 0.74309837, + 0.84927868, + 0.78312174, + 0.83015615, + 0.77981290, + 0.74258374, ] ) expected3 = ( np.array( [ - 0.58978896, - 0.5318829, - 0.58291302, - 0.56616446, - 0.60152253, - 0.63685373, - 0.60718972, - 0.56236328, - 0.56929121, - 0.60536959, - 0.58763365, - 0.59627562, - 0.55409009, - 0.56782614, - 0.56400569, - 0.6636463, - 0.59160813, - 0.64735391, - 0.72066489, - 0.64096484, - 0.61899218, - 0.58806093, - 0.54783766, - 0.5393597, - 0.55685316, - 0.65395071, - 0.58946501, - 0.64129696, - 0.58759922, - 0.5580478, + 0.78077777, + 0.72068128, + 0.77564557, + 0.75220586, + 0.78498646, + 0.82226742, + 0.78953295, + 0.75499567, + 0.75017449, + 0.79555133, + 0.77677308, + 0.78748408, + 0.73859649, + 0.75895348, + 0.75059024, + 0.85599023, + 0.77630208, + 0.84016811, + 0.90505241, + 0.82363203, + 0.80717282, + 0.77141901, + 0.73973490, + 0.72241447, + 0.74309837, + 0.84927868, + 0.78312174, + 0.83015615, + 0.77981290, + 0.74258374, ] ) - inv_tax_cred_rev3 ) -expected4 = 0.5688319028341413 +expected4 = 0.7598207140204407 expected5 = np.array( [ - 0.58978896, - 0.5318829, - 0.58291302, - 0.56616446, - 0.60152253, - 0.63685373, - 0.60718972, - 0.56236328, - 0.56929121, - 0.60536959, - 0.58763365, - 0.59627562, - 0.55409009, - 0.56782614, - 0.56400569, - 0.6636463, - 0.59160813, - 0.64735391, - 0.72066489, - 0.64096484, - 0.61899218, - 0.58806093, - 0.54783766, - 0.5393597, - 0.55685316, - 0.65395071, - 0.58946501, - 0.64129696, - 0.58759922, - 0.5580478, + 0.78077777, + 0.72068128, + 0.77564557, + 0.75220586, + 0.78498646, + 0.82226742, + 0.78953295, + 0.75499567, + 0.75017449, + 0.79555133, + 0.77677308, + 0.78748408, + 0.73859649, + 0.75895348, + 0.75059024, + 0.85599023, + 0.77630208, + 0.84016811, + 0.90505241, + 0.82363203, + 0.80717282, + 0.77141901, + 0.73973490, + 0.72241447, + 0.74309837, + 0.84927868, + 0.78312174, + 0.83015615, + 0.77981290, + 0.74258374, ] ) test_data = [ @@ -1744,6 +1744,45 @@ def test_revenue( assert np.allclose(revenue, expected) +def test_get_payroll_tax_revenue(): + """ + Test of the aggregates.get_payroll_tax_revenue function. + + Checks both ways of representing payroll taxes: embedded in the + income and payroll tax functions (tau_payroll == 0, the default) and + modeled explicitly via the tau_payroll parameter. + """ + p = Specifications() + p.T = 3 + iit_payroll_ss = 10.0 + iit_payroll_tpi = np.array([10.0, 11.0, 12.0]) + w_ss = 1.2 + L_ss = np.array([2.0, 3.0]) # labor by industry + w_tpi = np.array([1.0, 1.1, 1.2]) + L_tpi = np.array([[1.0, 2.0], [1.5, 2.5], [2.0, 3.0]]) # (T, M) + + # Payroll taxes embedded in the tax functions (tau_payroll == 0) + p.tau_payroll = np.zeros(p.T) + p.frac_tax_payroll = np.array([0.5, 0.5, 0.5]) + pr_ss = aggr.get_payroll_tax_revenue(w_ss, L_ss, iit_payroll_ss, p, "SS") + assert np.allclose(pr_ss, 0.5 * iit_payroll_ss) + pr_tpi = aggr.get_payroll_tax_revenue( + w_tpi, L_tpi, iit_payroll_tpi, p, "TPI" + ) + assert np.allclose(pr_tpi, 0.5 * iit_payroll_tpi) + + # Payroll taxes modeled explicitly via tau_payroll + p.tau_payroll = np.array([0.1, 0.2, 0.3]) + pr_ss = aggr.get_payroll_tax_revenue(w_ss, L_ss, iit_payroll_ss, p, "SS") + assert np.allclose(pr_ss, 0.3 * w_ss * L_ss.sum()) + pr_tpi = aggr.get_payroll_tax_revenue( + w_tpi, L_tpi, iit_payroll_tpi, p, "TPI" + ) + assert np.allclose( + pr_tpi, np.array([0.1, 0.2, 0.3]) * w_tpi * L_tpi.sum(-1) + ) + + test_data = [ ( 0.04,