Extend with Indexing#
ParamTools provides out-of-the-box parameter indexing. This is helpful for projects that have parameters that change at some rate over time. For example, tax parameters like the standard deduction are often indexed to price inflation. So, the value of the standard deduction actually increases every year by 1 or 2% depending on that year’s inflation rate.
The extend documentation may be useful for gaining a better understanding of how ParamTools extends parameter values along label_to_extend
.
To use the indexing feature:
Set the
label_to_extend
class attribute to the label that should be extendedSet the
indexing_rates
class attribute to a dictionary of inflation rates where the keys correspond to the value oflabel_to_extend
and the values are the indexing rates.Set the
uses_extend_func
class attribute toTrue
.In
defaults
ordefaults.json
, setindexed
toTrue
for each parameter that needs to be indexed.
Example#
This is a continuation of the tax parameters example from the extend documentation. The differences are indexed
is set to True
for the standard_deducation
parameter, uses_extend_func
is set to True
, and index_rates
is specified with inflation rates obtained from the open-source tax modeling package, Tax-Calculator, using version 2.5.0.
import paramtools
class TaxParams(paramtools.Parameters):
defaults = {
"schema": {
"labels": {
"year": {
"type": "int",
"validators": {"range": {"min": 2013, "max": 2027}}
},
"marital_status": {
"type": "str",
"validators": {"choice": {"choices": ["single", "joint"]}}
},
}
},
"standard_deduction": {
"title": "Standard deduction amount",
"description": "Amount filing unit can use as a standard deduction.",
"type": "float",
# Set indexed to True to extend standard_deduction with the built-in
# extension logic.
"indexed": True,
"value": [
{"year": 2017, "marital_status": "single", "value": 6350},
{"year": 2017, "marital_status": "joint", "value": 12700},
{"year": 2018, "marital_status": "single", "value": 12000},
{"year": 2018, "marital_status": "joint", "value": 24000},
{"year": 2026, "marital_status": "single", "value": 7685},
{"year": 2026, "marital_status": "joint", "value": 15369}],
"validators": {
"range": {
"min": 0,
"max": 9e+99
}
}
},
}
array_first = True
label_to_extend = "year"
# Activate use of extend_func method.
uses_extend_func = True
# inflation rates from Tax-Calculator v2.5.0
index_rates = {
2013: 0.0148,
2014: 0.0159,
2015: 0.0012,
2016: 0.0127,
2017: 0.0187,
2018: 0.0224,
2019: 0.0186,
2020: 0.0233,
2021: 0.0229,
2022: 0.0228,
2023: 0.0221,
2024: 0.0211,
2025: 0.0209,
2026: 0.0211,
2027: 0.0208,
2028: 0.021,
2029: 0.021
}
params = TaxParams()
params.standard_deduction
array([[ 6074.92, 12149.84],
[ 6164.83, 12329.66],
[ 6262.85, 12525.7 ],
[ 6270.37, 12540.73],
[ 6350. , 12700. ],
[12000. , 24000. ],
[12268.8 , 24537.6 ],
[12497. , 24994. ],
[12788.18, 25576.36],
[13081.03, 26162.06],
[13379.28, 26758.55],
[13674.96, 27349.91],
[13963.5 , 27926.99],
[ 7685. , 15369. ],
[ 7847.15, 15693.29]])
Adjustments are also indexed. In the example below, standard_deduction
is set to 10,000 in 2017, increased to 15,000 for single tax units in 2020, and increased to 20,000 for joint tax units in 2021:
params.adjust(
{
"standard_deduction": [
{"year": 2017, "value": 10000},
{"year": 2020, "marital_status": "single", "value": 15000},
{"year": 2021, "marital_status": "joint", "value": 20000}
]
}
)
params.standard_deduction
array([[ 6074.92, 12149.84],
[ 6164.83, 12329.66],
[ 6262.85, 12525.7 ],
[ 6270.37, 12540.73],
[10000. , 10000. ],
[10187. , 10187. ],
[10415.19, 10415.19],
[15000. , 10608.91],
[15349.5 , 20000. ],
[15701. , 20458. ],
[16058.98, 20924.44],
[16413.88, 21386.87],
[16760.21, 21838.13],
[17110.5 , 22294.55],
[17471.53, 22764.97]])
All values that are added automatically via the extend
method are given an _auto
attribute. You can select them like this:
params = TaxParams()
params.select_eq(
"standard_deduction", strict=True, _auto=True
)
[OrderedDict([('value', 6074.92),
('year', 2013),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 6164.83),
('year', 2014),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 6262.85),
('year', 2015),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 6270.37),
('year', 2016),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 12268.8),
('year', 2019),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 12497.0),
('year', 2020),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 12788.18),
('year', 2021),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 13081.03),
('year', 2022),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 13379.28),
('year', 2023),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 13674.96),
('year', 2024),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 13963.5),
('year', 2025),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 7847.15),
('year', 2027),
('marital_status', 'single'),
('_auto', True)]),
OrderedDict([('value', 12149.84),
('year', 2013),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 12329.66),
('year', 2014),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 12525.7),
('year', 2015),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 12540.73),
('year', 2016),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 24537.6),
('year', 2019),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 24994.0),
('year', 2020),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 25576.36),
('year', 2021),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 26162.06),
('year', 2022),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 26758.55),
('year', 2023),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 27349.91),
('year', 2024),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 27926.99),
('year', 2025),
('marital_status', 'joint'),
('_auto', True)]),
OrderedDict([('value', 15693.29),
('year', 2027),
('marital_status', 'joint'),
('_auto', True)])]
If you want to update the index rates and apply them to your existing values, then all you need to do is remove the values that were added automatically. ParamTools will fill in the missing values using the updated index rates:
params = TaxParams()
offset = 0.0025
for year, rate in params.index_rates.items():
params.index_rates[year] = rate + offset
automatically_added = params.select_eq(
"standard_deduction", strict=True, _auto=True
)
params.delete(
{
"standard_deduction": automatically_added
}
)
params.standard_deduction
array([[ 6015.22, 12030.41],
[ 6119.28, 12238.54],
[ 6231.87, 12463.73],
[ 6254.93, 12509.85],
[ 6350. , 12700. ],
[12000. , 24000. ],
[12298.8 , 24597.6 ],
[12558.3 , 25116.61],
[12882.3 , 25764.62],
[13209.51, 26419.04],
[13543.71, 27087.44],
[13876.89, 27753.79],
[14204.38, 28408.78],
[ 7685. , 15369. ],
[ 7866.37, 15731.71]])
Code for getting Tax-Calculator index rates#
import taxcalc
pol = taxcalc.Policy()
index_rates = {
year: value
for year, value in zip(list(range(2013, 2029 + 1)), pol.inflation_rates())
}
Note that there are some subtle details that are implemented in Tax-Calculator’s indexing logic that are not implemented in this example. Tax-Calculator has a parameter called CPI_offset
that adjusts inflation rates up or down by a fixed amount. The indexed
property can also be turned on and off for each parameter. Implementing these nuanced features is left as the proverbial “trivial exercise to the reader.”