modelflowib 2.77__tar.gz → 2.78__tar.gz

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 (50) hide show
  1. {modelflowib-2.77 → modelflowib-2.78}/PKG-INFO +1 -1
  2. {modelflowib-2.77 → modelflowib-2.78}/modelconstruct_estimation.py +64 -7
  3. {modelflowib-2.77 → modelflowib-2.78}/modelestimator_new.py +133 -1
  4. {modelflowib-2.77 → modelflowib-2.78}/modelflowib.egg-info/PKG-INFO +1 -1
  5. {modelflowib-2.77 → modelflowib-2.78}/modelhtml.py +22 -5
  6. {modelflowib-2.77 → modelflowib-2.78}/pyproject.toml +1 -1
  7. {modelflowib-2.77 → modelflowib-2.78}/README.md +0 -0
  8. {modelflowib-2.77 → modelflowib-2.78}/license.md +0 -0
  9. {modelflowib-2.77 → modelflowib-2.78}/modelBLfunk.py +0 -0
  10. {modelflowib-2.77 → modelflowib-2.78}/model_Excel.py +0 -0
  11. {modelflowib-2.77 → modelflowib-2.78}/model_cvx.py +0 -0
  12. {modelflowib-2.77 → modelflowib-2.78}/model_dynare.py +0 -0
  13. {modelflowib-2.77 → modelflowib-2.78}/model_financial_stability.py +0 -0
  14. {modelflowib-2.77 → modelflowib-2.78}/model_latex.py +0 -0
  15. {modelflowib-2.77 → modelflowib-2.78}/model_latex_class.py +0 -0
  16. {modelflowib-2.77 → modelflowib-2.78}/model_parquet_mixin.py +0 -0
  17. {modelflowib-2.77 → modelflowib-2.78}/modelclass.py +0 -0
  18. {modelflowib-2.77 → modelflowib-2.78}/modelconstruct.py +0 -0
  19. {modelflowib-2.77 → modelflowib-2.78}/modeldash.py +0 -0
  20. {modelflowib-2.77 → modelflowib-2.78}/modeldashboot.py +0 -0
  21. {modelflowib-2.77 → modelflowib-2.78}/modeldashsidebar.py +0 -0
  22. {modelflowib-2.77 → modelflowib-2.78}/modeldekom.py +0 -0
  23. {modelflowib-2.77 → modelflowib-2.78}/modeldiff.py +0 -0
  24. {modelflowib-2.77 → modelflowib-2.78}/modeldisplay.py +0 -0
  25. {modelflowib-2.77 → modelflowib-2.78}/modelestimation.py +0 -0
  26. {modelflowib-2.77 → modelflowib-2.78}/modelflowib.egg-info/SOURCES.txt +0 -0
  27. {modelflowib-2.77 → modelflowib-2.78}/modelflowib.egg-info/dependency_links.txt +0 -0
  28. {modelflowib-2.77 → modelflowib-2.78}/modelflowib.egg-info/requires.txt +0 -0
  29. {modelflowib-2.77 → modelflowib-2.78}/modelflowib.egg-info/top_level.txt +0 -0
  30. {modelflowib-2.77 → modelflowib-2.78}/modelgrab.py +0 -0
  31. {modelflowib-2.77 → modelflowib-2.78}/modelgrabgdx.py +0 -0
  32. {modelflowib-2.77 → modelflowib-2.78}/modelgrabwf2.py +0 -0
  33. {modelflowib-2.77 → modelflowib-2.78}/modelhelp.py +0 -0
  34. {modelflowib-2.77 → modelflowib-2.78}/modelinvert.py +0 -0
  35. {modelflowib-2.77 → modelflowib-2.78}/modeljupyter.py +0 -0
  36. {modelflowib-2.77 → modelflowib-2.78}/modeljupytermagic.py +0 -0
  37. {modelflowib-2.77 → modelflowib-2.78}/modelmacrograb.py +0 -0
  38. {modelflowib-2.77 → modelflowib-2.78}/modelmanipulation.py +0 -0
  39. {modelflowib-2.77 → modelflowib-2.78}/modelmf.py +0 -0
  40. {modelflowib-2.77 → modelflowib-2.78}/modelnet.py +0 -0
  41. {modelflowib-2.77 → modelflowib-2.78}/modelnewton.py +0 -0
  42. {modelflowib-2.77 → modelflowib-2.78}/modelnormalize.py +0 -0
  43. {modelflowib-2.77 → modelflowib-2.78}/modelpattern.py +0 -0
  44. {modelflowib-2.77 → modelflowib-2.78}/modelreport.py +0 -0
  45. {modelflowib-2.77 → modelflowib-2.78}/modelsolver_ng.py +0 -0
  46. {modelflowib-2.77 → modelflowib-2.78}/modeluserfunk.py +0 -0
  47. {modelflowib-2.77 → modelflowib-2.78}/modelvis.py +0 -0
  48. {modelflowib-2.77 → modelflowib-2.78}/modelwidget.py +0 -0
  49. {modelflowib-2.77 → modelflowib-2.78}/modelwidget_input.py +0 -0
  50. {modelflowib-2.77 → modelflowib-2.78}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelflowib
3
- Version: 2.77
3
+ Version: 2.78
4
4
  Summary: A tool to solve and manage dynamic economic and financial models
5
5
  Author-email: Ib Hansen <Ib.Hansen.Iv@gmail.com>
6
6
  License-Expression: MIT
@@ -1781,15 +1781,34 @@ def _estimation_record_to_markdown(record: dict) -> str:
1781
1781
 
1782
1782
  coefs = _estimator_coefficients_for_markdown(est)
1783
1783
  if coefs:
1784
- lines.extend([
1785
- "",
1786
- "| Parameter | Estimate |",
1787
- "|:--|--:|",
1788
- ])
1784
+ try:
1785
+ tvals = est.tvalues.to_dict()
1786
+ except Exception:
1787
+ tvals = {}
1788
+ # NaN != NaN — only add the column when at least one t-stat exists.
1789
+ has_t = any(v == v for v in tvals.values())
1790
+ if has_t:
1791
+ lines.extend([
1792
+ "",
1793
+ "| Parameter | Estimate | t-stat |",
1794
+ "|:--|--:|--:|",
1795
+ ])
1796
+ else:
1797
+ lines.extend([
1798
+ "",
1799
+ "| Parameter | Estimate |",
1800
+ "|:--|--:|",
1801
+ ])
1789
1802
  for key in sorted(coefs, key=lambda x: (str(x).split("__")[0], int(str(x).split("__")[-1]) if str(x).split("__")[-1].lstrip("-").isdigit() else str(x))):
1790
- lines.append(
1791
- f"| {_markdown_escape_cell(key)} | {_markdown_escape_cell(_markdown_format_number(coefs[key]))} |"
1803
+ row = (
1804
+ f"| {_markdown_escape_cell(key)} "
1805
+ f"| {_markdown_escape_cell(_markdown_format_number(coefs[key]))} |"
1792
1806
  )
1807
+ if has_t:
1808
+ t = tvals.get(key)
1809
+ t_cell = f"{t:.3f}" if t is not None and t == t else ""
1810
+ row += f" {_markdown_escape_cell(t_cell)} |"
1811
+ lines.append(row)
1793
1812
 
1794
1813
  return "\n".join(lines) + "\n"
1795
1814
 
@@ -2429,6 +2448,31 @@ class Makemodel(BaseExplode):
2429
2448
  """
2430
2449
  return MakeModelReport(self, title=title, plot_format=plot_format, report_all=report_all)
2431
2450
 
2451
+ @property
2452
+ def tvalues(self):
2453
+ """t-statistics for all estimated equations as a DataFrame.
2454
+
2455
+ Rows are parameter tokens (``C__1``, ``C__2``, ...), one column per
2456
+ estimated equation named by its endogenous variable. NaN where a
2457
+ parameter is not in that equation or the backend provides no
2458
+ t-statistic (fixed/derived parameters, failed covariance, EViews
2459
+ ``NA`` rows).
2460
+
2461
+ Per-equation access: ``rec['estimator_object'].tvalues`` on the
2462
+ entries of :attr:`estimation_records`.
2463
+ """
2464
+ import pandas as pd
2465
+ cols = {}
2466
+ for rec in self.estimation_records:
2467
+ est = rec.get('estimator_object')
2468
+ try:
2469
+ ser = est.tvalues
2470
+ except Exception:
2471
+ continue
2472
+ name = getattr(est, 'endo_var', '') or rec.get('frmlname', '')
2473
+ cols[name] = ser
2474
+ return pd.DataFrame(cols)
2475
+
2432
2476
  @property
2433
2477
  def markdown_with_estimation(self) -> str:
2434
2478
  """Original Markdown input with compact estimation tables inserted.
@@ -2816,6 +2860,19 @@ class Listmodels(BaseExplode):
2816
2860
  """
2817
2861
  return MakeModelReport(self, title=title, plot_format=plot_format, report_all=report_all)
2818
2862
 
2863
+ @property
2864
+ def tvalues(self):
2865
+ """t-statistics of all member Makemodels, combined column-wise.
2866
+
2867
+ See :attr:`Makemodel.tvalues` for the layout.
2868
+ """
2869
+ import pandas as pd
2870
+ frames = [mex.tvalues for mex in self.makemodels]
2871
+ frames = [f for f in frames if not f.empty]
2872
+ if not frames:
2873
+ return pd.DataFrame()
2874
+ return pd.concat(frames, axis=1)
2875
+
2819
2876
  @property
2820
2877
  def markdown_with_estimation(self) -> str:
2821
2878
  """Concatenate member Makemodel markdown-with-estimation strings."""
@@ -99,6 +99,7 @@ from lmfit import Parameters, minimize
99
99
  from matplotlib.gridspec import GridSpec
100
100
 
101
101
  from modelclass import model
102
+ from modelmanipulation import check_syntax
102
103
  import modelnormalize as nz
103
104
 
104
105
 
@@ -904,6 +905,13 @@ class Eq_parent:
904
905
  raw = self.org_eq.upper()
905
906
  eq_body, constraint_text = _split_st_clause(raw)
906
907
 
908
+ # 2a) Syntax-check the equation text BEFORE parameter-token
909
+ # normalization. Typos like ``C(1)2020`` (missing ``*D``) or a
910
+ # missing ``+`` between terms are clean SyntaxErrors here, but
911
+ # after normalization they can merge into single valid
912
+ # identifiers and slip through to give degenerate fits.
913
+ self._check_eq_syntax(eq_body)
914
+
907
915
  # 2) Normalize parameter tokens in both equation body AND constraints
908
916
  # so names align downstream (e.g. ``C(1)`` -> ``C__1``).
909
917
  eq = _normalize_param_tokens(eq_body, self.est_param, self.param_names)
@@ -946,6 +954,7 @@ class Eq_parent:
946
954
  # Subclasses that need a custom dataframe layout (e.g. mfcalc-built
947
955
  # regressors for OLS) handle that themselves.
948
956
  if self.input_df is not None:
957
+ self._check_unknown_variables()
949
958
  self.eq_var_df = (
950
959
  self.mdummy.insertModelVar(self.input_df)
951
960
  .loc[:, self.varname_all]
@@ -953,7 +962,49 @@ class Eq_parent:
953
962
  )
954
963
  self.estimation_df = self.eq_var_df
955
964
 
956
- # -- variable discovery (cached, derived from mdummy) ---------------------
965
+ @staticmethod
966
+ def _check_eq_syntax(eq_body: str) -> None:
967
+ """Raise ``SyntaxError`` (with a pinpointed location) if the equation
968
+ text does not parse as Python expressions.
969
+
970
+ Each side of the first ``=`` is checked separately — the full
971
+ statement would be an invalid Python assignment whenever the LHS is
972
+ a transformation like ``DLOG(...)``. Whitespace is preserved, which
973
+ is what makes missing-operator typos detectable.
974
+ """
975
+ checkable = eq_body.replace("@ABS(", "ABS(")
976
+ sides = [side.strip() for side in checkable.split("=", 1)]
977
+ check_syntax([side for side in sides if side])
978
+
979
+ def _check_unknown_variables(self) -> None:
980
+ """Print a diagnosis and raise for variables with no data column.
981
+
982
+ Every variable in the equation that is not a parameter placeholder
983
+ (active prefix or ``param_names``) or a helper slot must exist in
984
+ ``input_df``. Anything else would be inserted as an all-NaN column
985
+ by ``insertModelVar`` and silently degrade or break the fit —
986
+ typically a misspelled variable name or a forgotten ``param_names``
987
+ entry (``ALFA(1)`` with ``'alfa'`` not declared reads as a lead of
988
+ an unknown variable ALFA).
989
+ """
990
+ helpers = {"ACTUAL", "FITTED", "RESIDUALS"}
991
+ known = set(self.input_df.columns) | set(self.c_params) | helpers
992
+ unknown = sorted(set(self.varname_all) - known)
993
+ if unknown:
994
+ # Print the diagnosis, then raise a bare SyntaxError — same
995
+ # style as check_syntax. IPython renders SyntaxError without
996
+ # the frame stack, so the notebook shows the message above
997
+ # instead of a long construction call stack.
998
+ print(
999
+ f"{type(self).__name__}({self.endo_var}): the equation "
1000
+ f"references variables with no column in input_df:\n"
1001
+ f" {unknown}\n"
1002
+ f"Equation: {self.org_eq_clean}\n"
1003
+ "Check for misspelled variable names or missing param_names "
1004
+ "entries — such variables would otherwise enter the "
1005
+ "estimation as all-NaN columns."
1006
+ )
1007
+ raise SyntaxError("Unknown variables in equation - see above") from None
957
1008
 
958
1009
  @property
959
1010
  def mdummy(self):
@@ -1079,6 +1130,10 @@ class EstimatorBackend(Eq_parent, ABC):
1079
1130
  Default is a no-op. Override to reject equation forms the backend
1080
1131
  cannot handle.
1081
1132
 
1133
+ :meth:`_tvalues_dict`
1134
+ Default returns ``{}`` (no t-statistics). Override to return
1135
+ ``{<prefix>__n: t}`` so the :attr:`tvalues` Series is populated.
1136
+
1082
1137
  Standard attributes set during ``__post_init__``
1083
1138
  ------------------------------------------------
1084
1139
  regression_model : Any
@@ -1230,6 +1285,29 @@ class EstimatorBackend(Eq_parent, ABC):
1230
1285
  break
1231
1286
  return mapped
1232
1287
 
1288
+ # ---- t-statistics ------------------------------------------------------
1289
+
1290
+ def _tvalues_dict(self) -> Dict[str, float]:
1291
+ """Backend-specific t-statistics keyed by ``{est_param}__n`` token.
1292
+
1293
+ Default: no t-statistics available. Backends override where the
1294
+ fitted result exposes (or allows computing) them.
1295
+ """
1296
+ return {}
1297
+
1298
+ @cached_property
1299
+ def tvalues(self) -> pd.Series:
1300
+ """t-statistics as a Series keyed like :attr:`coef_ser`.
1301
+
1302
+ Parameters without an available t-statistic (fixed, derived, or the
1303
+ backend could not provide one) are NaN.
1304
+ """
1305
+ tvals = self._tvalues_dict()
1306
+ return pd.Series(
1307
+ {p: tvals.get(p, float("nan")) for p in self.c_params},
1308
+ name=self.caption, dtype=float,
1309
+ )
1310
+
1233
1311
  # ---- A/F and residuals (computed once, cached) -------------------------
1234
1312
 
1235
1313
  @cached_property
@@ -1486,6 +1564,13 @@ class Estimate_ols(EstimatorBackend):
1486
1564
  "<caption><h3>OLS Regression Results</h3></caption>",
1487
1565
  )
1488
1566
 
1567
+ def _tvalues_dict(self) -> Dict[str, float]:
1568
+ """statsmodels exposes t-statistics directly, keyed by regressor
1569
+ column name — map those back to the canonical parameter tokens."""
1570
+ return self._map_statsmodels_params(
1571
+ self.regression_model.tvalues.to_dict()
1572
+ )
1573
+
1489
1574
  # ---- override estimation_smpl: OLS reports its EFFECTIVE sample --------
1490
1575
 
1491
1576
  @cached_property
@@ -1610,6 +1695,33 @@ class Estimate_nls_lmfit(EstimatorBackend):
1610
1695
  "<h2>Fit Result</h2>", "<h3>NLS Regression Results</h3>"
1611
1696
  )
1612
1697
 
1698
+ def _tvalues_dict(self) -> Dict[str, float]:
1699
+ """lmfit provides standard errors (from the covariance matrix when
1700
+ ``calc_covar=True``); t = value / stderr. Fixed parameters, derived
1701
+ (``expr``) parameters without propagated errors, and fits where the
1702
+ covariance could not be estimated have ``stderr`` None (or NaN) and
1703
+ are omitted (→ NaN in :attr:`tvalues`)."""
1704
+ res = self.regression_model
1705
+ out: Dict[str, float] = {}
1706
+ for name, p in res.params.items():
1707
+ # stderr is None when lmfit produced no uncertainties, and can
1708
+ # be NaN when the covariance matrix is ill-conditioned.
1709
+ if p.stderr and p.stderr == p.stderr:
1710
+ out[name] = p.value / p.stderr
1711
+ if not out and any(p.vary for p in res.params.values()):
1712
+ print(
1713
+ f"[{type(self).__name__}({self.endo_var})] WARNING: lmfit "
1714
+ f"produced no parameter uncertainties "
1715
+ f"(errorbars={getattr(res, 'errorbars', None)}), so no "
1716
+ "t-values are available. Common causes: singular or "
1717
+ "ill-conditioned Jacobian (collinear or huge-scale "
1718
+ "regressors), parameters stuck at a bound, or a fit that "
1719
+ "did not converge. Inspect "
1720
+ "est.regression_model.params.pretty_print() and "
1721
+ "est.regression_model.message."
1722
+ )
1723
+ return out
1724
+
1613
1725
 
1614
1726
  # =============================================================================
1615
1727
  # Estimate_nls_eviews — nonlinear least squares via EViews (py2eviews)
@@ -1702,6 +1814,26 @@ class Estimate_nls_eviews(EstimatorBackend):
1702
1814
  def _render_summary_html(self) -> str:
1703
1815
  return eviews_output_to_html(self.regression_model)
1704
1816
 
1817
+ def _tvalues_dict(self) -> Dict[str, float]:
1818
+ """Parse the t-Statistic column from the EViews spool text.
1819
+
1820
+ Only the coefficient vector is read back from EViews numerically;
1821
+ the coefficient table (Coefficient, Std. Error, t-Statistic, Prob.)
1822
+ exists only as text in the spool. Rows where EViews prints ``NA``
1823
+ simply do not match and stay NaN in :attr:`tvalues`.
1824
+ """
1825
+ if not isinstance(self.regression_model, str):
1826
+ return {}
1827
+ num = r"(-?[\d.]+(?:[eE][+-]?\d+)?)"
1828
+ out: Dict[str, float] = {}
1829
+ for m in re.finditer(
1830
+ rf"^\s*C\((\d+)\)\s+{num}\s+{num}\s+{num}",
1831
+ self.regression_model,
1832
+ flags=re.MULTILINE,
1833
+ ):
1834
+ out[f"{self.est_param}__{m.group(1)}"] = float(m.group(4))
1835
+ return out
1836
+
1705
1837
 
1706
1838
  # =============================================================================
1707
1839
  # Estimate_nls — factory class dispatching on solver
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelflowib
3
- Version: 2.77
3
+ Version: 2.78
4
4
  Summary: A tool to solve and manage dynamic economic and financial models
5
5
  Author-email: Ib Hansen <Ib.Hansen.Iv@gmail.com>
6
6
  License-Expression: MIT
@@ -118,6 +118,7 @@ def _mmr_md_to_html(
118
118
 
119
119
  in_p = in_ul = in_ol = in_bll = in_fence = in_table = False
120
120
  tbl_hdr_done = False
121
+ pending_est = False
121
122
 
122
123
  def ep():
123
124
  nonlocal in_p
@@ -148,9 +149,24 @@ def _mmr_md_to_html(
148
149
  def end_all():
149
150
  ep(); eul(); eol(); ebll(); etbl()
150
151
 
152
+ def flush_est():
153
+ # Emit the panel for the last estimated equation once all of its
154
+ # '>>' continuation lines have been rendered, so the equation is not
155
+ # split around its estimation output.
156
+ nonlocal pending_est, rec_i, panel_i
157
+ if pending_est:
158
+ pending_est = False
159
+ if rec_i < len(est_records):
160
+ ebll()
161
+ out.append(_mmr_est_panel(est_records[rec_i], panel_i, plot_format))
162
+ panel_i += 1; rec_i += 1
163
+
151
164
  for raw in lines:
152
165
  s = raw.strip()
153
166
 
167
+ if pending_est and not s.startswith('>>'):
168
+ flush_est()
169
+
154
170
  # code fence (```)
155
171
  if s.startswith('```'):
156
172
  if in_fence:
@@ -192,20 +208,20 @@ def _mmr_md_to_html(
192
208
  end_all()
193
209
  out.append('<pre class="mmr-bll"><code>'); in_bll = True
194
210
  out.append(_mmr_annotate_bll(content, var_desc))
211
+ # The estimator tag may sit on a continuation line.
212
+ if _mmr_has_estimator_tag(raw):
213
+ pending_est = True
195
214
  continue
196
215
 
197
216
  # BLL equation >
198
217
  if s.startswith('>'):
199
218
  content = s[1:].strip()
200
- has_est = _mmr_has_estimator_tag(raw)
201
219
  if not in_bll:
202
220
  end_all()
203
221
  out.append('<pre class="mmr-bll"><code>'); in_bll = True
204
222
  out.append(_mmr_annotate_bll(content, var_desc))
205
- if has_est and rec_i < len(est_records):
206
- ebll()
207
- out.append(_mmr_est_panel(est_records[rec_i], panel_i, plot_format))
208
- panel_i += 1; rec_i += 1
223
+ if _mmr_has_estimator_tag(raw):
224
+ pending_est = True
209
225
  continue
210
226
 
211
227
  # markdown table row
@@ -262,6 +278,7 @@ def _mmr_md_to_html(
262
278
 
263
279
  if in_fence:
264
280
  out.append('</code></pre>')
281
+ flush_est()
265
282
  end_all()
266
283
 
267
284
  # leftover estimation records not matched to any source line
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "modelflowib" # PyPI distribution name
7
- version = "2.77" # OVERWRITTEN at build time from MF_VERSION
7
+ version = "2.78" # OVERWRITTEN at build time from MF_VERSION
8
8
  # (set_pyproject_version.py / make_pip.py -v)
9
9
  description = "A tool to solve and manage dynamic economic and financial models"
10
10
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes