modelflowib 2.75__tar.gz → 2.77__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.
- {modelflowib-2.75 → modelflowib-2.77}/PKG-INFO +1 -1
- {modelflowib-2.75 → modelflowib-2.77}/modelclass.py +51 -31
- {modelflowib-2.75 → modelflowib-2.77}/modelconstruct_estimation.py +220 -13
- {modelflowib-2.75 → modelflowib-2.77}/modelflowib.egg-info/PKG-INFO +1 -1
- {modelflowib-2.75 → modelflowib-2.77}/modeljupytermagic.py +5 -1
- {modelflowib-2.75 → modelflowib-2.77}/modelnormalize.py +31 -11
- {modelflowib-2.75 → modelflowib-2.77}/modelsolver_ng.py +3652 -3652
- {modelflowib-2.75 → modelflowib-2.77}/pyproject.toml +1 -1
- {modelflowib-2.75 → modelflowib-2.77}/README.md +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/license.md +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelBLfunk.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/model_Excel.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/model_cvx.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/model_dynare.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/model_financial_stability.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/model_latex.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/model_latex_class.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/model_parquet_mixin.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelconstruct.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modeldash.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modeldashboot.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modeldashsidebar.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modeldekom.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modeldiff.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modeldisplay.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelestimation.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelestimator_new.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelflowib.egg-info/SOURCES.txt +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelflowib.egg-info/dependency_links.txt +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelflowib.egg-info/requires.txt +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelflowib.egg-info/top_level.txt +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelgrab.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelgrabgdx.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelgrabwf2.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelhelp.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelhtml.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelinvert.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modeljupyter.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelmacrograb.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelmanipulation.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelmf.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelnet.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelnewton.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelpattern.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelreport.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modeluserfunk.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelvis.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelwidget.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/modelwidget_input.py +0 -0
- {modelflowib-2.75 → modelflowib-2.77}/setup.cfg +0 -0
|
@@ -3188,7 +3188,7 @@ class Modify_Mixin():
|
|
|
3188
3188
|
newnormalisation = [l.split() for l in lines]
|
|
3189
3189
|
vars_before_normalization = {beforeendo.upper() for beforeendo,afterendo in newnormalisation }
|
|
3190
3190
|
frml_normalize_strip = {(beforeendo.upper(),afterendo.upper()) : self.allvar[beforeendo]['frml'].replace('$','').split(' ',2) for beforeendo,afterendo in newnormalisation }
|
|
3191
|
-
frml_normalize_gross = {(beforeendo,afterendo) : (fname ,normal(expression,the_endo = afterendo,endo_lhs=False,
|
|
3191
|
+
frml_normalize_gross = {(beforeendo,afterendo) : (fname ,normal(expression,the_endo = afterendo,endo_lhs=False,add_add_factor=False)) for (beforeendo,afterendo),(frml,fname,expression) in frml_normalize_strip.items() }
|
|
3192
3192
|
frmldict_normalized = {afterendo : f'FRML {fname} {nexpression.normalized}$'
|
|
3193
3193
|
for (beforeendo,afterendo),(fname,nexpression) in frml_normalize_gross.items() }
|
|
3194
3194
|
# breakpoint()
|
|
@@ -3282,8 +3282,10 @@ class Modify_Mixin():
|
|
|
3282
3282
|
|
|
3283
3283
|
Parameters
|
|
3284
3284
|
----------
|
|
3285
|
-
updateeq :
|
|
3286
|
-
new equations seperated by newline
|
|
3285
|
+
updateeq : str or Makemodel
|
|
3286
|
+
new equations seperated by newline, or a Makemodel instance whose
|
|
3287
|
+
normalized equations (including per-equation add factors and
|
|
3288
|
+
fitted equations, when present) are used as the update.
|
|
3287
3289
|
newfunks : TYPE, optional
|
|
3288
3290
|
Additional userspecified functions. The default is [].
|
|
3289
3291
|
calc_add : bool, optional
|
|
@@ -3297,32 +3299,50 @@ class Modify_Mixin():
|
|
|
3297
3299
|
a dataframe with calculated add factors. Origin is the original models lastdf.
|
|
3298
3300
|
|
|
3299
3301
|
'''
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
updatefunks=list(set(self.funks+newfunks) ) #
|
|
3303
|
-
|
|
3304
|
-
newmodelname = newname if newname else self.name+' Updated'
|
|
3305
|
-
|
|
3306
|
-
dfvars = set(self.lastdf.columns)
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
frmldict_calc_add = {nexpression.endo_var: f'{frml}
|
|
3323
|
-
frml,fname,nexpression in frml2_normal
|
|
3324
|
-
|
|
3325
|
-
|
|
3302
|
+
from modelconstruct_estimation import Makemodel # local to avoid circular import
|
|
3303
|
+
|
|
3304
|
+
updatefunks=list(set(self.funks+newfunks) ) #
|
|
3305
|
+
|
|
3306
|
+
newmodelname = newname if newname else self.name+' Updated'
|
|
3307
|
+
|
|
3308
|
+
dfvars = set(self.lastdf.columns)
|
|
3309
|
+
new_var_description = self.var_description
|
|
3310
|
+
|
|
3311
|
+
if isinstance(updateeq, Makemodel):
|
|
3312
|
+
# the Makemodel has already expanded, optionally estimated and normalized the
|
|
3313
|
+
# equations, and decided add factor/fixable/fitted per equation from its flags
|
|
3314
|
+
updatefunks = list(set(updatefunks + updateeq.funks))
|
|
3315
|
+
new_var_description = {**self.var_description, **updateeq.var_description}
|
|
3316
|
+
frml2_normal = [['FRML', fname, nexpression]
|
|
3317
|
+
for fname, (parts, nexpression)
|
|
3318
|
+
in zip(updateeq.normal_output_frmlnames, updateeq.normal)]
|
|
3319
|
+
frmldict_update = {nexpression.endo_var: f'{frml} {fname} {nexpression.normalized}$' for
|
|
3320
|
+
frml,fname,nexpression in frml2_normal}
|
|
3321
|
+
frmldict_update = {**frmldict_update,
|
|
3322
|
+
**{f'{nexpression.endo_var}_FITTED': f'FRML <FIT> {nexpression.fitted}$'
|
|
3323
|
+
for frml,fname,nexpression in frml2_normal if len(nexpression.fitted)}}
|
|
3324
|
+
frmldict_calc_add = {nexpression.endo_var: f'{frml} <CALC_ADD_FACTOR> {nexpression.calc_add_factor}$' for
|
|
3325
|
+
frml,fname,nexpression in frml2_normal
|
|
3326
|
+
if len(nexpression.calc_add_factor) and nexpression.endo_var in self.endogene|self.exogene|dfvars }
|
|
3327
|
+
else:
|
|
3328
|
+
updatemodel = mp.tofrml(updateeq) # create the moedl with the usual processing of frml's
|
|
3329
|
+
while ' ' in updatemodel:
|
|
3330
|
+
updatemodel = updatemodel.replace(' ', ' ')
|
|
3331
|
+
frml2 = updatemodel.split('$')
|
|
3332
|
+
frml2_strip = [mp.split_frml(f+'$') for f in frml2 if len(f)>2]
|
|
3333
|
+
frml2_normal = [[frml,fname,
|
|
3334
|
+
normal(expression[:-1],do_preprocess=do_preprocess,add_add_factor=add_add_factor,
|
|
3335
|
+
make_fixable = pt.kw_frml_name(fname.upper(),'FIXABLE'))]
|
|
3336
|
+
for allfrml,frml,fname,expression in frml2_strip]
|
|
3337
|
+
frmldict_update = {nexpression.endo_var: f'{frml} {fname} {nexpression.normalized}$' for
|
|
3338
|
+
frml,fname,nexpression in frml2_normal}
|
|
3339
|
+
|
|
3340
|
+
|
|
3341
|
+
if add_add_factor:
|
|
3342
|
+
frmldict_calc_add = {nexpression.endo_var: f'{frml} {fname} {nexpression.calc_add_factor}$' for
|
|
3343
|
+
frml,fname,nexpression in frml2_normal if nexpression.endo_var in self.endogene|self.exogene|dfvars }
|
|
3344
|
+
else:
|
|
3345
|
+
frmldict_calc_add={}
|
|
3326
3346
|
# breakpoint()
|
|
3327
3347
|
|
|
3328
3348
|
frmldict = {k: v['frml'] for (k,v) in self.allvar.items() if k in self.endogene } # frml's in the existing model
|
|
@@ -3331,8 +3351,8 @@ class Modify_Mixin():
|
|
|
3331
3351
|
newfrml = '\n'.join([f for f in newfrmldict.values()])
|
|
3332
3352
|
newmodel = self.__class__(newfrml,modelname = f'updated {self.name}',
|
|
3333
3353
|
funks=updatefunks,
|
|
3334
|
-
var_description=
|
|
3335
|
-
model_description = self.model_description,
|
|
3354
|
+
var_description=new_var_description,
|
|
3355
|
+
model_description = self.model_description,
|
|
3336
3356
|
var_groups = self.var_groups ,
|
|
3337
3357
|
reports = self.reports ,
|
|
3338
3358
|
substitution = self.substitution,
|
|
@@ -982,6 +982,16 @@ from IPython.display import display, Markdown, Math
|
|
|
982
982
|
import re
|
|
983
983
|
from IPython.display import display, Markdown, Math
|
|
984
984
|
|
|
985
|
+
class Markdown(Markdown):
|
|
986
|
+
"""IPython Markdown, but with the markdown source as the text/plain fallback.
|
|
987
|
+
|
|
988
|
+
The stock class reprs as '<IPython.core.display.Markdown object>', and that
|
|
989
|
+
string is what latex/pdf exporters show, as they can not render the
|
|
990
|
+
text/markdown mime type and fall back to text/plain. With the source as
|
|
991
|
+
repr, exported documents show the model text instead."""
|
|
992
|
+
def __repr__(self):
|
|
993
|
+
return str(self.data)
|
|
994
|
+
|
|
985
995
|
def display_model(cell: str, spec: str = "markdown"):
|
|
986
996
|
"""
|
|
987
997
|
Display a model specification.
|
|
@@ -1165,8 +1175,12 @@ def _parse_smpl(smpl, df=None):
|
|
|
1165
1175
|
Preferred FRML syntax is::
|
|
1166
1176
|
|
|
1167
1177
|
<smpl=start end>
|
|
1178
|
+
<smpl=(start end)>
|
|
1168
1179
|
|
|
1169
|
-
where ``start`` and ``end`` are separated by one or more blanks
|
|
1180
|
+
where ``start`` and ``end`` are separated by one or more blanks, with the
|
|
1181
|
+
brackets optional. A comma may separate them when the value does not pass
|
|
1182
|
+
through a FRML tag -- ``smpl='2000, 2025'`` as a keyword argument -- but not
|
|
1183
|
+
inside ``<...>``, where the tag parser splits options on commas first. The
|
|
1170
1184
|
labels are **not** coerced to integers, because the dataframe index may be
|
|
1171
1185
|
a PeriodIndex, DatetimeIndex, quarterly strings, or another custom index
|
|
1172
1186
|
type. If ``df`` is supplied, its index is used via ``slice_locs`` to
|
|
@@ -1256,16 +1270,28 @@ def _parse_smpl(smpl, df=None):
|
|
|
1256
1270
|
if not text:
|
|
1257
1271
|
return None
|
|
1258
1272
|
|
|
1273
|
+
# Accept a parenthesised spelling, so <smpl=(2000 2025)> reads the same as
|
|
1274
|
+
# the Python-side smpl=(2000, 2025) tuple. Note that inside a FRML tag only
|
|
1275
|
+
# the blank-separated form works: kw_frml_name splits the tag's options on
|
|
1276
|
+
# commas without regard for brackets, so <smpl=(2000,2025)> would arrive
|
|
1277
|
+
# here already broken in two.
|
|
1278
|
+
closing = {'(': ')', '[': ']'}
|
|
1279
|
+
if len(text) > 1 and text[0] in closing and text[-1] == closing[text[0]]:
|
|
1280
|
+
text = text[1:-1].strip()
|
|
1281
|
+
if not text:
|
|
1282
|
+
return None
|
|
1283
|
+
|
|
1259
1284
|
# New preferred syntax: <smpl=start end>. Keep old colon syntax so older
|
|
1260
1285
|
# notebooks do not break.
|
|
1261
|
-
if ':' in text and len(text.split()) == 1:
|
|
1286
|
+
if ':' in text and ',' not in text and len(text.split()) == 1:
|
|
1262
1287
|
start, end = [p.strip() for p in text.split(':', 1)]
|
|
1263
1288
|
else:
|
|
1264
|
-
parts =
|
|
1289
|
+
parts = [p for p in re.split(r'[,\s]+', text) if p]
|
|
1265
1290
|
if len(parts) != 2:
|
|
1266
1291
|
raise ModelSpecificationError(
|
|
1267
|
-
"SMPL must be written as 'start end' separated by blanks "
|
|
1268
|
-
|
|
1292
|
+
"SMPL must be written as 'start end' separated by blanks, "
|
|
1293
|
+
"optionally bracketed as '(start end)' (or legacy "
|
|
1294
|
+
f"'start:end'); got {smpl!r}"
|
|
1269
1295
|
)
|
|
1270
1296
|
start, end = parts
|
|
1271
1297
|
|
|
@@ -1631,11 +1657,17 @@ def _estimate_and_bake_expression(
|
|
|
1631
1657
|
estimator_name,
|
|
1632
1658
|
input_df=None,
|
|
1633
1659
|
smpl=None,
|
|
1660
|
+
smpl_is_local: bool = False,
|
|
1634
1661
|
caption: Optional[str] = None,
|
|
1635
1662
|
estimator_kwargs: Optional[dict] = None,
|
|
1636
1663
|
estimator_classes: Optional[dict] = None,
|
|
1637
1664
|
):
|
|
1638
|
-
"""Instantiate an estimator/factory, run it, and return (baked_expression, estimator_obj).
|
|
1665
|
+
"""Instantiate an estimator/factory, run it, and return (baked_expression, estimator_obj).
|
|
1666
|
+
|
|
1667
|
+
``smpl_is_local`` marks a sample that came from this equation's own
|
|
1668
|
+
``<smpl=...>`` rather than from the model-wide default, which decides
|
|
1669
|
+
whether it may override a model-wide ``estimator_kwargs['smpl']``.
|
|
1670
|
+
"""
|
|
1639
1671
|
estimator_constructor = _get_estimator_class(estimator_name, estimator_classes)
|
|
1640
1672
|
kwargs = dict(estimator_kwargs or {})
|
|
1641
1673
|
|
|
@@ -1644,13 +1676,16 @@ def _estimate_and_bake_expression(
|
|
|
1644
1676
|
# still override the factory's stored defaults.
|
|
1645
1677
|
if input_df is not None and 'input_df' not in kwargs:
|
|
1646
1678
|
kwargs['input_df'] = input_df
|
|
1647
|
-
|
|
1679
|
+
# An equation-local <smpl=...> outranks the model-wide estimator_kwargs; a
|
|
1680
|
+
# model-wide Makemodel.smpl does not.
|
|
1681
|
+
if smpl is not None and (smpl_is_local or 'smpl' not in kwargs):
|
|
1648
1682
|
kwargs['smpl'] = smpl
|
|
1649
1683
|
if caption is not None and 'caption' not in kwargs:
|
|
1650
1684
|
kwargs['caption'] = caption
|
|
1651
1685
|
|
|
1652
1686
|
estimator_obj = _instantiate_estimator(estimator_constructor, expression, kwargs)
|
|
1653
|
-
|
|
1687
|
+
# Validates in place and raises; it returns the same object, so do not rebind.
|
|
1688
|
+
_require_estimator_backend_instance(estimator_obj, estimator_name)
|
|
1654
1689
|
fit_result = _maybe_run_estimator_fit(estimator_obj)
|
|
1655
1690
|
|
|
1656
1691
|
baked = _extract_expression_from_estimator(estimator_obj, fit_result)
|
|
@@ -1768,20 +1803,39 @@ def _markdown_with_estimation_blocks(original_text: str, estimation_records: lis
|
|
|
1768
1803
|
"""Insert estimation markdown blocks after estimator-tagged source lines.
|
|
1769
1804
|
|
|
1770
1805
|
This preserves the original user-facing Markdown as much as possible. For
|
|
1771
|
-
the common notebook syntax, each
|
|
1806
|
+
the common notebook syntax, each equation beginning with ``>`` and containing
|
|
1772
1807
|
``<estimator=...>`` gets the next estimation block inserted immediately
|
|
1773
1808
|
after it. If template expansion creates more estimated equations than can
|
|
1774
1809
|
be matched to source lines, the remaining blocks are appended at the end.
|
|
1810
|
+
|
|
1811
|
+
An equation can be spread over several lines: a ``>`` line followed by
|
|
1812
|
+
``>>`` continuations, which the parser joins into one statement. The block
|
|
1813
|
+
goes after the last of those, so the whole equation is shown before its
|
|
1814
|
+
estimation output rather than being split around it.
|
|
1775
1815
|
"""
|
|
1776
1816
|
records = list(estimation_records or [])
|
|
1777
1817
|
if not records:
|
|
1778
1818
|
return original_text
|
|
1779
1819
|
|
|
1820
|
+
lines = original_text.splitlines()
|
|
1780
1821
|
out = []
|
|
1781
1822
|
rec_i = 0
|
|
1782
|
-
|
|
1823
|
+
i = 0
|
|
1824
|
+
while i < len(lines):
|
|
1825
|
+
line = lines[i]
|
|
1783
1826
|
out.append(line)
|
|
1784
|
-
|
|
1827
|
+
i += 1
|
|
1828
|
+
|
|
1829
|
+
if not _line_has_estimator_tag(line):
|
|
1830
|
+
continue
|
|
1831
|
+
|
|
1832
|
+
# Carry the rest of the equation across before inserting the block.
|
|
1833
|
+
# The tag may sit on the '>' line or on any of its continuations.
|
|
1834
|
+
while i < len(lines) and lines[i].lstrip().startswith(">>"):
|
|
1835
|
+
out.append(lines[i])
|
|
1836
|
+
i += 1
|
|
1837
|
+
|
|
1838
|
+
if rec_i < len(records):
|
|
1785
1839
|
out.append(_estimation_record_to_markdown(records[rec_i]).rstrip())
|
|
1786
1840
|
rec_i += 1
|
|
1787
1841
|
|
|
@@ -1795,6 +1849,34 @@ def _markdown_with_estimation_blocks(original_text: str, estimation_records: lis
|
|
|
1795
1849
|
return "\n".join(out)
|
|
1796
1850
|
|
|
1797
1851
|
|
|
1852
|
+
def _strip_markdown_list_blocks(text: str) -> str:
|
|
1853
|
+
"""Remove ``>list`` / ``>tlist`` definition blocks from markdown model text.
|
|
1854
|
+
|
|
1855
|
+
A list block is a line whose content (after the leading ``>``) begins with
|
|
1856
|
+
``list`` or ``tlist``, together with any immediately following ``>>``
|
|
1857
|
+
continuation lines. Equation lines and their ``>>`` continuations are left
|
|
1858
|
+
untouched, because those continuations follow a ``>`` equation line rather
|
|
1859
|
+
than a ``>list`` line.
|
|
1860
|
+
|
|
1861
|
+
Used by ``markdown_with_estimation_no_list`` to support the
|
|
1862
|
+
``render_list=0`` rendering path of the ``%%Makemymodel`` magic.
|
|
1863
|
+
"""
|
|
1864
|
+
out = []
|
|
1865
|
+
in_list = False
|
|
1866
|
+
for line in text.splitlines():
|
|
1867
|
+
stripped = line.lstrip()
|
|
1868
|
+
if in_list:
|
|
1869
|
+
if stripped.startswith('>>'):
|
|
1870
|
+
# Continuation of the current list definition.
|
|
1871
|
+
continue
|
|
1872
|
+
in_list = False
|
|
1873
|
+
if re.match(r'>\s*(list|tlist)\b', stripped, flags=re.IGNORECASE):
|
|
1874
|
+
in_list = True
|
|
1875
|
+
continue
|
|
1876
|
+
out.append(line)
|
|
1877
|
+
return '\n'.join(out)
|
|
1878
|
+
|
|
1879
|
+
|
|
1798
1880
|
@dataclass
|
|
1799
1881
|
class BaseExplode:
|
|
1800
1882
|
"""Common parent for Makemodel and Listmodels."""
|
|
@@ -1939,9 +2021,80 @@ class BaseExplode:
|
|
|
1939
2021
|
|
|
1940
2022
|
@dataclass
|
|
1941
2023
|
class Makemodel(BaseExplode):
|
|
2024
|
+
"""Expand a model template (markdown or modelflow) into solvable FRMLs.
|
|
2025
|
+
|
|
2026
|
+
Every attribute below can be inspected with a ``show`` prefix, which prints
|
|
2027
|
+
it nicely, for instance ``consumption.showpost_sum``. The stages follow the
|
|
2028
|
+
order in which ``__post_init__`` produces them.
|
|
2029
|
+
|
|
2030
|
+
**1. Input and options (what you pass in)**
|
|
2031
|
+
|
|
2032
|
+
| Attribute | Description |
|
|
2033
|
+
|---|---|
|
|
2034
|
+
| `showoriginal_statements` | Input expressions |
|
|
2035
|
+
| `showlist_defs` | Lists definitions |
|
|
2036
|
+
| `showtype_input` | Originaal as type modelflow or markdown |
|
|
2037
|
+
| `showreplacements` | list of string tupels with string replacements |
|
|
2038
|
+
| `showfunks` | List of user specified functions to be used in model |
|
|
2039
|
+
| `showmodelname` | A optional name for this (sub) model |
|
|
2040
|
+
| `showvar_description` | Variable descriptions (user-supplied, merged with descriptions from estimated equations) |
|
|
2041
|
+
|
|
2042
|
+
**2. Estimation setup (used only for `<estimator=...>` equations)**
|
|
2043
|
+
|
|
2044
|
+
| Attribute | Description |
|
|
2045
|
+
|---|---|
|
|
2046
|
+
| `showinput_df` | DataFrame used when tagged equations are estimated |
|
|
2047
|
+
| `showestimator` | Default estimator for tagged equations: method name or callable factory |
|
|
2048
|
+
| `showsmpl` | Model-wide default estimation sample. `None` unless passed to the constructor or the magic -- a per-equation `<smpl=start end>` or a `with_defaults(smpl=...)` factory does not show up here. The sample each equation really used is in `showestimation_records` |
|
|
2049
|
+
| `showestimator_kwargs` | Shared kwargs passed to estimator constructors |
|
|
2050
|
+
| `showestimator_classes` | Optional method-name to estimator-class mapping |
|
|
2051
|
+
| `showestimator_namespace` | Optional namespace for resolving `<estimator=name>`; defaults to caller locals/globals |
|
|
2052
|
+
|
|
2053
|
+
**3. Expansion pipeline (in the order they are produced)**
|
|
2054
|
+
|
|
2055
|
+
| Attribute | Description |
|
|
2056
|
+
|---|---|
|
|
2057
|
+
| `showclean_frml_statements` | With frml and nice lists |
|
|
2058
|
+
| `showpost_doable` | Expanded after doable |
|
|
2059
|
+
| `showpost_do` | Frmls after do expansion |
|
|
2060
|
+
| `showpost_sum` | Frmls after expanding sums |
|
|
2061
|
+
| `showexpanded_frml` | frmls after expanding |
|
|
2062
|
+
|
|
2063
|
+
**4. Lists**
|
|
2064
|
+
|
|
2065
|
+
| Attribute | Description |
|
|
2066
|
+
|---|---|
|
|
2067
|
+
| `showmodellist` | The lists defined in string as a dictionary |
|
|
2068
|
+
| `showlists` | Same as `showmodellist` |
|
|
2069
|
+
| `showlist_specification` | All list specifications in string |
|
|
2070
|
+
|
|
2071
|
+
`modellist` is built between `post_do` and `post_sum` (it feeds the sum
|
|
2072
|
+
expansion), `list_specification` is derived at the very end.
|
|
2073
|
+
|
|
2074
|
+
**5. Normalization and estimation output**
|
|
2075
|
+
|
|
2076
|
+
| Attribute | Description |
|
|
2077
|
+
|---|---|
|
|
2078
|
+
| `shownormal_input_expressions` | Expressions sent to modelnormalize, after optional estimation |
|
|
2079
|
+
| `showestimation_records` | Information about equations estimated during construction |
|
|
2080
|
+
| `shownormal_expressions` | List of normal expressions |
|
|
2081
|
+
| `shownormal_output_frmlnames` | FRML names emitted; estimation flags are preserved |
|
|
2082
|
+
| `shownormal_main` | Normalized frmls |
|
|
2083
|
+
| `shownormal_fit` | Normalized frmls for fitted values |
|
|
2084
|
+
| `shownormal_calc_add` | Normalized frmls to calculate add factors |
|
|
2085
|
+
| `shownormal_frml` | Output normalized expressions (`normal_main` + `normal_fit` + `normal_calc_add`) |
|
|
2086
|
+
| `show` | The finished `normal_frml`, printed |
|
|
2087
|
+
|
|
2088
|
+
**6. Rendering**
|
|
2089
|
+
|
|
2090
|
+
| Attribute | Description |
|
|
2091
|
+
|---|---|
|
|
2092
|
+
| `showmarkdown_model` | As markdown |
|
|
2093
|
+
"""
|
|
2094
|
+
|
|
1942
2095
|
# original_statements : str = field(default="", metadata={"description": "Input expressions"})
|
|
1943
2096
|
# normal_frml : str = field(default="", metadata={"description": "Output normalized expressions"})
|
|
1944
|
-
|
|
2097
|
+
|
|
1945
2098
|
normal_main : str = field(init=False, metadata={"description": "Normalized frmls"})
|
|
1946
2099
|
normal_fit : str = field(init=False, metadata={"description": "Normalized frmls for fitted values"})
|
|
1947
2100
|
normal_calc_add : str = field(init=False, metadata={"description": "Normalized frmls to calculate add factors"})
|
|
@@ -2084,7 +2237,9 @@ class Makemodel(BaseExplode):
|
|
|
2084
2237
|
))
|
|
2085
2238
|
|
|
2086
2239
|
self.normal_expressions = [n for p,n in self.normal ]
|
|
2087
|
-
|
|
2240
|
+
|
|
2241
|
+
self._warn_on_repeated_endogenous()
|
|
2242
|
+
|
|
2088
2243
|
# udrullet = lagarray_unroll(udrullet,funks=funks )
|
|
2089
2244
|
# udrullet = creatematrix(udrullet,listin=modellist)
|
|
2090
2245
|
# udrullet = createarray(udrullet,listin=modellist)
|
|
@@ -2115,6 +2270,35 @@ class Makemodel(BaseExplode):
|
|
|
2115
2270
|
self.list_specification = self.get_lists()
|
|
2116
2271
|
return
|
|
2117
2272
|
|
|
2273
|
+
def _warn_on_repeated_endogenous(self):
|
|
2274
|
+
"""Warn when a variable is defined by more than one kept equation.
|
|
2275
|
+
|
|
2276
|
+
Nothing is removed: every equation stays in the emitted FRML, and
|
|
2277
|
+
``model()`` resolves the collision by letting the last definition win
|
|
2278
|
+
(``modelclass.py``, where each repeat overwrites the variable's entry in
|
|
2279
|
+
``allvar``). That is usually what a notebook author means when they
|
|
2280
|
+
re-estimate an equation further down, but it is worth saying out loud,
|
|
2281
|
+
because the earlier equations are then dead weight -- and because a
|
|
2282
|
+
typo in a mnemonic looks exactly like a deliberate redefinition.
|
|
2283
|
+
"""
|
|
2284
|
+
equations_by_endo = {}
|
|
2285
|
+
for position, (parts, normal) in enumerate(self.normal, start=1):
|
|
2286
|
+
endo = getattr(normal, 'endo_var', '')
|
|
2287
|
+
if endo:
|
|
2288
|
+
equations_by_endo.setdefault(endo, []).append(position)
|
|
2289
|
+
|
|
2290
|
+
for endo, positions in equations_by_endo.items():
|
|
2291
|
+
if len(positions) < 2:
|
|
2292
|
+
continue
|
|
2293
|
+
listed = ', '.join(str(p) for p in positions)
|
|
2294
|
+
print(
|
|
2295
|
+
f"⚠️ {endo} is defined by {len(positions)} equations "
|
|
2296
|
+
f"(kept equations {listed}). The last of them, equation "
|
|
2297
|
+
f"{positions[-1]}, is the one used when the model is solved; "
|
|
2298
|
+
f"the earlier ones go into the FRML but are never evaluated. "
|
|
2299
|
+
f"Tag any you do not want with <DROP>."+'\n'
|
|
2300
|
+
)
|
|
2301
|
+
|
|
2118
2302
|
def _expression_after_optional_estimation(self, parts, *, equation_index: Optional[int] = None,
|
|
2119
2303
|
estimator_flag: Any = ...) -> str:
|
|
2120
2304
|
"""Return parts.expression, or an estimated/baked version when tagged.
|
|
@@ -2175,6 +2359,7 @@ class Makemodel(BaseExplode):
|
|
|
2175
2359
|
estimator_name=estimator_name,
|
|
2176
2360
|
input_df=self.input_df,
|
|
2177
2361
|
smpl=smpl,
|
|
2362
|
+
smpl_is_local=local_smpl is not None,
|
|
2178
2363
|
caption=local_caption,
|
|
2179
2364
|
estimator_kwargs=self.estimator_kwargs,
|
|
2180
2365
|
estimator_classes=self.estimator_classes,
|
|
@@ -2263,6 +2448,20 @@ class Makemodel(BaseExplode):
|
|
|
2263
2448
|
"""Alias for :attr:`markdown_with_estimation`."""
|
|
2264
2449
|
return self.markdown_with_estimation
|
|
2265
2450
|
|
|
2451
|
+
@property
|
|
2452
|
+
def markdown_with_estimation_no_list(self) -> str:
|
|
2453
|
+
"""Like :attr:`markdown_with_estimation`, with ``>list`` blocks removed.
|
|
2454
|
+
|
|
2455
|
+
Same inline estimation tables as :attr:`markdown_with_estimation`, but
|
|
2456
|
+
``>list``/``>tlist`` definition blocks are stripped from the rendered
|
|
2457
|
+
Markdown. Used by the ``%%Makemymodel`` magic when ``render_list=0`` is
|
|
2458
|
+
combined with the default ``render_est`` rendering path.
|
|
2459
|
+
"""
|
|
2460
|
+
return _markdown_with_estimation_blocks(
|
|
2461
|
+
_strip_markdown_list_blocks(self.original_statements),
|
|
2462
|
+
self.estimation_records,
|
|
2463
|
+
)
|
|
2464
|
+
|
|
2266
2465
|
@property
|
|
2267
2466
|
def clean_frml(self) -> str:
|
|
2268
2467
|
"""Normalized equations with add-factors, exogenization, and fitted-value flags stripped."""
|
|
@@ -2630,6 +2829,14 @@ class Listmodels(BaseExplode):
|
|
|
2630
2829
|
"""Alias for :attr:`markdown_with_estimation`."""
|
|
2631
2830
|
return self.markdown_with_estimation
|
|
2632
2831
|
|
|
2832
|
+
@property
|
|
2833
|
+
def markdown_with_estimation_no_list(self) -> str:
|
|
2834
|
+
"""Concatenate member Makemodel markdown-with-estimation-no-list strings."""
|
|
2835
|
+
return "\n\n".join(
|
|
2836
|
+
mex.markdown_with_estimation_no_list
|
|
2837
|
+
for mex in self.makemodels
|
|
2838
|
+
)
|
|
2839
|
+
|
|
2633
2840
|
# __str__(self):
|
|
2634
2841
|
|
|
2635
2842
|
|
|
@@ -27,6 +27,10 @@ from modelclass import model
|
|
|
27
27
|
from modelmanipulation import explode
|
|
28
28
|
from model_latex_class import a_latex_model
|
|
29
29
|
from modelconstruct_estimation import Makemodel, display_model
|
|
30
|
+
# Markdown subclass whose text/plain fallback is the markdown source itself,
|
|
31
|
+
# so latex/pdf exports show the model text instead of
|
|
32
|
+
# '<IPython.core.display.Markdown object>'. Shadows the IPython import above.
|
|
33
|
+
from modelconstruct_estimation import Markdown
|
|
30
34
|
from modelhelp import debug_var
|
|
31
35
|
from modelreport import LatexRepo
|
|
32
36
|
|
|
@@ -589,7 +593,7 @@ try:
|
|
|
589
593
|
if dict_name in user_ns:
|
|
590
594
|
segs = list(user_ns[dict_name].keys())
|
|
591
595
|
print(f"Model `{name}` built from segments: {', '.join(segs)}")
|
|
592
|
-
print(f"✅ Created
|
|
596
|
+
print(f"✅ Created Makemodel: {name}")
|
|
593
597
|
print(emodel)
|
|
594
598
|
|
|
595
599
|
return emodel
|
|
@@ -129,8 +129,19 @@ def funk_find_arg(funk_match, streng):
|
|
|
129
129
|
return streng[:start], match[1:index], match[index + 1:]
|
|
130
130
|
assert 1==2,f'Parantese mismatch in {streng}'
|
|
131
131
|
|
|
132
|
+
def funk_check_arg(funk,arg,udtryk,udtryk_up):
|
|
133
|
+
'''Guards against variables named like a function.
|
|
134
|
+
|
|
135
|
+
A lagged variable with a function name, fx DIFF(-1), matches DIFF( and would
|
|
136
|
+
silently be swallowed as a function call. If the argument is a pure signed
|
|
137
|
+
integer it must be such a lag, so raise an informative error instead.'''
|
|
138
|
+
if re.fullmatch(r'[+-]?\d+',arg.replace(' ','')):
|
|
139
|
+
raise Exception(f"A variable can not be named {funk}, as {funk}( is a function in the business language.\n"
|
|
140
|
+
f"{funk}({arg}) looks like a lagged variable {funk}. Rename the variable.\n"
|
|
141
|
+
f"Original expression: {udtryk}\n"
|
|
142
|
+
f"Processed so far : {udtryk_up}")
|
|
143
|
+
|
|
132
144
|
|
|
133
|
-
|
|
134
145
|
def preprocess(udtryk,funks=[]):
|
|
135
146
|
'''
|
|
136
147
|
test processing expanding dlog,diff,movavg,pct,logit functions
|
|
@@ -146,16 +157,21 @@ def preprocess(udtryk,funks=[]):
|
|
|
146
157
|
|
|
147
158
|
'''
|
|
148
159
|
udtryk_up = udtryk.upper().replace(' ','')
|
|
160
|
+
udtryk_up = funk_replace('PCT','PCT_GROWTH',udtryk_up)
|
|
161
|
+
|
|
149
162
|
while dlog_match := funk_in('DLOG',udtryk_up):
|
|
150
163
|
fordlog,dlogudtryk_up,efterdlog=funk_find_arg(dlog_match,udtryk_up)
|
|
151
|
-
|
|
152
|
-
|
|
164
|
+
funk_check_arg('DLOG',dlogudtryk_up,udtryk,udtryk_up)
|
|
165
|
+
udtryk_up=fordlog+'DIFF(LOG('+dlogudtryk_up+'))'+efterdlog
|
|
166
|
+
|
|
153
167
|
while logit_match := funk_in('LOGIT',udtryk_up):
|
|
154
168
|
forlogit,logitudtryk_up,efterlogit=funk_find_arg(logit_match,udtryk_up)
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
funk_check_arg('LOGIT',logitudtryk_up,udtryk,udtryk_up)
|
|
170
|
+
udtryk_up=forlogit+'(LOG('+logitudtryk_up+'/(1.0 -'+logitudtryk_up+')))'+efterlogit
|
|
171
|
+
|
|
157
172
|
while movavg_match := funk_in('MOVAVG', udtryk_up):
|
|
158
173
|
forkaede,kaedeudtryk,efterkaede=funk_find_arg(movavg_match,udtryk_up)
|
|
174
|
+
funk_check_arg('MOVAVG',kaedeudtryk,udtryk,udtryk_up)
|
|
159
175
|
arg=kaedeudtryk.split(',',1)
|
|
160
176
|
avg='(('
|
|
161
177
|
term=arg[0]
|
|
@@ -168,19 +184,23 @@ def preprocess(udtryk,funks=[]):
|
|
|
168
184
|
|
|
169
185
|
while pct_match := funk_in('PCT_GROWTH',udtryk_up):
|
|
170
186
|
forpc,pcudtryk,efterpc=funk_find_arg(pct_match,udtryk_up)
|
|
171
|
-
|
|
187
|
+
funk_check_arg('PCT_GROWTH',pcudtryk,udtryk,udtryk_up)
|
|
188
|
+
udtryk_up=f'{forpc} (100 * ( ({pcudtryk}) / ({lagone(pcudtryk,funks=funks)}) -1)) {efterpc}'
|
|
172
189
|
|
|
173
190
|
while pct_match := funk_in('PCY',udtryk_up):
|
|
174
191
|
forpc,pcudtryk,efterpc=funk_find_arg(pct_match,udtryk_up)
|
|
175
|
-
|
|
176
|
-
|
|
192
|
+
funk_check_arg('PCY',pcudtryk,udtryk,udtryk_up)
|
|
193
|
+
udtryk_up=f'{forpc} (100 * ( ({pcudtryk}) / ({lagone(pcudtryk,funks=funks,laglead=-4)}) -1)) {efterpc}'
|
|
194
|
+
|
|
177
195
|
while diff_match := funk_in('DIFF' , udtryk_up):
|
|
178
196
|
fordif,difudtryk_up,efterdif=funk_find_arg(diff_match,udtryk_up)
|
|
179
|
-
|
|
180
|
-
|
|
197
|
+
funk_check_arg('DIFF',difudtryk_up,udtryk,udtryk_up)
|
|
198
|
+
udtryk_up=fordif+'(('+difudtryk_up+')-('+lagone(difudtryk_up+'',funks=funks)+'))'+efterdif
|
|
199
|
+
|
|
181
200
|
while diff_match := funk_in('D' , udtryk_up):
|
|
182
201
|
# print(f'{udtryk_up} on D')
|
|
183
202
|
fordif,difudtryk_up,efterdif=funk_find_arg(diff_match,udtryk_up)
|
|
203
|
+
funk_check_arg('D',difudtryk_up,udtryk,udtryk_up)
|
|
184
204
|
difudtryk_up = difudtryk_up.replace(' ','').replace(',0,1','') if difudtryk_up.endswith(',0,1') else difudtryk_up
|
|
185
205
|
udtryk_up=fordif+'(('+difudtryk_up+')-('+lagone(difudtryk_up+'',funks=funks)+'))'+efterdif
|
|
186
206
|
|
|
@@ -407,7 +427,7 @@ if __name__ == '__main__':
|
|
|
407
427
|
normal('a+b = c',add_add_factor=1,make_fitted=1).fprint
|
|
408
428
|
normal('PCT_growth(a) = n(-1)',add_add_factor=0).fprint
|
|
409
429
|
normal('a = movavg(pct(b),2)',add_add_factor=0).fprint
|
|
410
|
-
normal('pct_growth(c) = pct_growth(
|
|
430
|
+
normal('pct_growth(c) = pct_growth(dd)',add_add_factor=0).fprint
|
|
411
431
|
normal('pct_growth(c) = z+pct(b) + pct(e)').fprint
|
|
412
432
|
normal('pct_growth(c) = z+pct(b) + pct(e)').fprint
|
|
413
433
|
normal('a = pct_growth(b)',add_add_factor=0).fprint
|