Skip to content

Commit 8139ba5

Browse files
jdebackerclaude
andcommitted
Add calib_table function to summarize endogenous calibration targets
Adds calib_table() to output_tables.py, which produces a 5-column table showing each calibrated parameter's name/symbol, value (or range), data target description, model moment, and data moment. Supports wealth Gini (beta_annual), investment rate I/K (delta_annual), and income Gini (e) as initial target types. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 673b1f3 commit 8139ba5

1 file changed

Lines changed: 132 additions & 0 deletions

File tree

ogcore/output_tables.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import numpy as np
23
import pandas as pd
34
import os
@@ -987,3 +988,134 @@ def dynamic_revenue_decomposition(
987988
table = save_return_table(table_df, table_format, path)
988989

989990
return table
991+
992+
993+
def calib_table(
994+
param_list,
995+
targets_dict,
996+
params,
997+
tpi_output,
998+
t=0,
999+
table_format=None,
1000+
path=None,
1001+
):
1002+
"""
1003+
Creates a table summarizing endogenously calibrated parameters,
1004+
showing parameter values alongside the data targets used in
1005+
calibration and the corresponding model moments.
1006+
1007+
Supported target descriptions (used as keys in ``targets_dict``
1008+
values):
1009+
1010+
* ``"Gini coefficient of wealth"`` -- computed from ``b_sp1``
1011+
* ``"Investment rate (I/K)"`` -- computed from ``I`` and ``K``
1012+
* ``"Gini coefficient of income"`` -- computed from
1013+
``before_tax_income``
1014+
1015+
Args:
1016+
param_list (list): OG-Core parameter names to include in the
1017+
table, e.g. ``['beta_annual', 'delta_annual', 'e']``
1018+
targets_dict (dict): maps each parameter name to a one-item
1019+
dict ``{target_description: data_value}``, e.g.::
1020+
1021+
{
1022+
'beta_annual': {'Gini coefficient of wealth': 0.82},
1023+
'delta_annual': {'Investment rate (I/K)': 0.07},
1024+
'e': {'Gini coefficient of income': 0.55},
1025+
}
1026+
1027+
params (OG-Core Specifications class): model parameters object
1028+
tpi_output (dict): output dictionary returned by ``TPI.run_TPI``
1029+
t (int): period index used for model moment calculations.
1030+
Defaults to ``0`` (first period of the transition path).
1031+
Pass ``-1`` to use the last period, which approximates
1032+
steady-state values.
1033+
table_format (string): format to return table in: ``'csv'``,
1034+
``'tex'``, ``'excel'``, ``'json'``; if ``None`` a
1035+
DataFrame is returned
1036+
path (string): path to save table to
1037+
1038+
Returns:
1039+
table (various): table as a DataFrame, formatted string, or
1040+
``None`` if saved to disk
1041+
1042+
"""
1043+
# Load parameter metadata for title and notation
1044+
default_params_path = os.path.join(cur_path, "default_parameters.json")
1045+
with open(default_params_path, "r") as f:
1046+
default_params_meta = json.load(f)
1047+
1048+
table_dict = {
1049+
"Parameter": [],
1050+
"Value": [],
1051+
"Data Target": [],
1052+
"Model Moment": [],
1053+
"Data Moment": [],
1054+
}
1055+
1056+
for param_name in param_list:
1057+
# --- Column 1: human-readable name and LaTeX symbol ---
1058+
if param_name in default_params_meta:
1059+
meta = default_params_meta[param_name]
1060+
short_desc = meta.get(
1061+
"short_description", meta.get("title", param_name)
1062+
)
1063+
notation = meta.get("param_notation", "")
1064+
col1 = f"{short_desc} {notation}".strip()
1065+
else:
1066+
col1 = param_name
1067+
1068+
# --- Column 2: parameter value or range ---
1069+
param_val = getattr(params, param_name, None)
1070+
if param_val is None:
1071+
col2 = "N/A"
1072+
else:
1073+
arr = np.asarray(param_val, dtype=float)
1074+
if arr.ndim == 0 or arr.size == 1:
1075+
col2 = f"{float(arr.flat[0]):.4f}"
1076+
elif np.allclose(arr, arr.flat[0]):
1077+
col2 = f"{arr.flat[0]:.4f}"
1078+
else:
1079+
col2 = f"[{arr.min():.4f}, {arr.max():.4f}]"
1080+
1081+
# --- Columns 3-5: target description, model moment, data moment ---
1082+
target_info = targets_dict.get(param_name, {})
1083+
if target_info:
1084+
target_desc = next(iter(target_info))
1085+
data_val = target_info[target_desc]
1086+
else:
1087+
target_desc = ""
1088+
data_val = np.nan
1089+
1090+
# Compute the model moment corresponding to the target description
1091+
if target_desc == "Gini coefficient of wealth":
1092+
dist = tpi_output["b_sp1"][t]
1093+
pop_weights = params.omega[t]
1094+
pop_weights = pop_weights / pop_weights.sum()
1095+
ineq = Inequality(
1096+
dist, pop_weights, params.lambdas, params.S, params.J
1097+
)
1098+
model_val = ineq.gini()
1099+
elif target_desc == "Investment rate (I/K)":
1100+
model_val = tpi_output["I"][t] / tpi_output["K"][t]
1101+
elif target_desc == "Gini coefficient of income":
1102+
dist = tpi_output["before_tax_income"][t]
1103+
pop_weights = params.omega[t]
1104+
pop_weights = pop_weights / pop_weights.sum()
1105+
ineq = Inequality(
1106+
dist, pop_weights, params.lambdas, params.S, params.J
1107+
)
1108+
model_val = ineq.gini()
1109+
else:
1110+
model_val = np.nan
1111+
1112+
table_dict["Parameter"].append(col1)
1113+
table_dict["Value"].append(col2)
1114+
table_dict["Data Target"].append(target_desc)
1115+
table_dict["Model Moment"].append(model_val)
1116+
table_dict["Data Moment"].append(data_val)
1117+
1118+
table_df = pd.DataFrame.from_dict(table_dict)
1119+
table = save_return_table(table_df, table_format, path, precision=4)
1120+
1121+
return table

0 commit comments

Comments
 (0)