Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ogcore/output_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,10 @@ def ss_profiles(
).sum(axis=1)
plt.plot(age_vec, reform_var, label="Reform", linestyle="--")
if plot_data is not None:
if var != "n":
# If not labor, normalize so data and model match in
# first period
plot_data = plot_data / plot_data.values[0] * base_var[0]
Comment thread
jdebacker marked this conversation as resolved.
Outdated
plt.plot(
age_vec, plot_data, linewidth=2.0, label="Data", linestyle=":"
)
Expand Down
198 changes: 198 additions & 0 deletions ogcore/output_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,3 +987,201 @@ def dynamic_revenue_decomposition(
table = save_return_table(table_df, table_format, path)

return table


def model_fit_table(
targets_dict,
params,
tpi_output,
t=0,
table_format=None,
path=None,
):
Comment thread
jdebacker marked this conversation as resolved.
Comment thread
jdebacker marked this conversation as resolved.
"""
Creates a table summarizing the model fit.

Supported target descriptions (used as keys in ``targets_dict``):

* ``"Gini coefficient of wealth"`` -- computed from ``b_sp1``
* ``"Investment rate (I/K)"`` -- computed from ``I`` and ``K``
* ``"Gini coefficient of income"`` -- computed from
``before_tax_income``

Comment thread
jdebacker marked this conversation as resolved.
Outdated
Args:
targets_dict (dict): maps each parameter name to a one-item
dict ``{target_description: data_value}``, e.g.::

{
'Gini coefficient of wealth': 0.82,
'Investment rate (I/K)': 0.07,
'Gini coefficient of income': 0.55,
}

Comment thread
jdebacker marked this conversation as resolved.
params (OG-Core Specifications class): model parameters object
tpi_output (dict): output dictionary returned by ``TPI.run_TPI``
t (int): period index used for model moment calculations.
Defaults to ``0`` (first period of the transition path).
Pass ``-1`` to use the last period, which approximates
steady-state values.
table_format (string): format to return table in: ``'csv'``,
``'tex'``, ``'excel'``, ``'json'``; if ``None`` a
DataFrame is returned
path (string): path to save table to

Returns:
table (various): table as a DataFrame, formatted string, or
``None`` if saved to disk

"""
# Ordered groups and the moment descriptions belonging to each
MOMENT_GROUPS = [
(
"Macroeconomic moments",
[
r"Investment rate $(I/K)$",
r"Capital-Output ratio $(K/Y)$",
r"Consumption-Output ratio $(C/Y)$",
r"Savings rate $(B/Y)$",
r"Interest rate $(r)$",
r"Capital share of output",
r"Labor share of output",
],
),
(
"Fiscal moments",
[
r"Revenue to GDP ratio $(T/Y)$",
r"Gov't consumption to GDP ratio $(G/Y)$",
r"Pension outlays to GDP ratio $(Pension/Y)$",
r"Infrastructure spending to GDP ratio $(I_g/Y)$",
r"Debt to GDP ratio $(D/Y)$",
],
),
(
"Distributional moments",
[
"Gini coefficient, wealth",
"Gini coefficient, income",
"Gini coefficient, after-tax income",
],
),
(
"Demographic moments",
[
r"Fraction 65+",
r"Pop growth rate",
],
),
]

# Compute model moments for all entries in targets_dict
computed = {}
for moment, data_val in targets_dict.items():
target_desc = moment

# Macroeconomic moments
if target_desc == r"Investment rate $(I/K)$":
model_val = tpi_output["I"][t] / tpi_output["K"][t]
elif target_desc == r"Capital-Output ratio $(K/Y)$":
model_val = tpi_output["K"][t] / tpi_output["Y"][t]
elif target_desc == r"Consumption-Output ratio $(C/Y)$":
model_val = tpi_output["C"][t] / tpi_output["Y"][t]
elif target_desc == r"Savings rate $(B/Y)$":
model_val = tpi_output["B"][t] / tpi_output["Y"][t]
elif target_desc == r"Interest rate $(r)$":
model_val = tpi_output["r"][t]
elif target_desc == r"Capital share of output":
model_val = (
1
- tpi_output["r"][t] * tpi_output["K"][t] / tpi_output["Y"][t]
)
Comment thread
jdebacker marked this conversation as resolved.
elif target_desc == r"Labor share of output":
model_val = (
tpi_output["w"][t] * tpi_output["L"][t] / tpi_output["Y"][t]
)
# Fiscal moments
elif target_desc == r"Revenue to GDP ratio $(T/Y)$":
model_val = tpi_output["total_tax_revenue"][t] / tpi_output["Y"][t]
elif target_desc == r"Gov't consumption to GDP ratio $(G/Y)$":
model_val = tpi_output["G"][t] / tpi_output["Y"][t]
elif target_desc == r"Pension outlays to GDP ratio $(Pension/Y)$":
model_val = (
tpi_output["agg_pension_outlays"][t] / tpi_output["Y"][t]
)
elif target_desc == r"Infrastructure spending to GDP ratio $(I_g/Y)$":
model_val = tpi_output["I_g"][t] / tpi_output["Y"][t]
elif target_desc == r"Debt to GDP ratio $(D/Y)$":
model_val = tpi_output["D"][t] / tpi_output["Y"][t]
# Distributional moments
elif target_desc == "Gini coefficient, wealth":
dist = tpi_output["b_sp1"][t]
pop_weights = params.omega[t]
pop_weights = pop_weights / pop_weights.sum()
ineq = Inequality(
dist, pop_weights, params.lambdas, params.S, params.J
)
model_val = ineq.gini()
elif target_desc == "Gini coefficient, income":
dist = tpi_output["before_tax_income"][t]
pop_weights = params.omega[t]
pop_weights = pop_weights / pop_weights.sum()
ineq = Inequality(
dist, pop_weights, params.lambdas, params.S, params.J
)
model_val = ineq.gini()
elif target_desc == "Gini coefficient, after-tax income":
dist = (
tpi_output["before_tax_income"][t]
+ tpi_output["hh_net_taxes"][t]
)
Comment thread
jdebacker marked this conversation as resolved.
pop_weights = params.omega[t]
pop_weights = pop_weights / pop_weights.sum()
ineq = Inequality(
dist, pop_weights, params.lambdas, params.S, params.J
)
model_val = ineq.gini()
# Demographic moments
elif target_desc == r"Fraction 65+":
model_val = (
params.omega[
t, -35:
].sum() # NOTE: not flexible with S, E changes
/ params.omega[t].sum()
)
Comment thread
jdebacker marked this conversation as resolved.
Outdated
elif target_desc == r"Pop growth rate":
model_val = params.g_n[t]
else:
model_val = np.nan

computed[target_desc] = (data_val, model_val)

# Build the grouped table; skip any group with no matching moments
all_grouped = {m for _, moments in MOMENT_GROUPS for m in moments}
table_dict = {"Moment": [], "Data": [], "Model": []}

for group_name, group_moments in MOMENT_GROUPS:
group_entries = [m for m in group_moments if m in computed]
if not group_entries:
continue
# Group header row (no data values)
table_dict["Moment"].append(group_name)
table_dict["Data"].append(np.nan)
table_dict["Model"].append(np.nan)
# Indented moment rows
for m in group_entries:
data_val, model_val = computed[m]
table_dict["Moment"].append(f" {m}")
table_dict["Data"].append(data_val)
table_dict["Model"].append(model_val)

# Append any moments not belonging to a known group
for target_desc, (data_val, model_val) in computed.items():
if target_desc not in all_grouped:
table_dict["Moment"].append(target_desc)
table_dict["Data"].append(data_val)
table_dict["Model"].append(model_val)

table_df = pd.DataFrame.from_dict(table_dict)
table = save_return_table(table_df, table_format, path, precision=4)

return table
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ authors = [
description = "A general equilibrium overlapping generations model for fiscal policy analysis"
readme = "README.md"
license = {text = "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication"}
requires-python = ">=3.11, <3.14"
requires-python = ">=3.12, <3.14"
classifiers = [
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers",
Expand All @@ -20,7 +20,6 @@ classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Python Modules",
Expand Down Expand Up @@ -109,3 +108,8 @@ markers = [
"real: marks tests using real OG-Core tax function code",
"platform: marks tests for platform-specific optimization",
]

[dependency-groups]
dev = [
"ipykernel>=7.2.0",
]
Loading
Loading