import sys
import warnings
from copy import copy
from typing import Union, Tuple, List, Dict, Any
if sys.version_info.minor >= 11:
from typing import Self
else:
Self = Any
from iode.common import EqMethod, EqTest
from iode.time.period import Period
from iode.time.sample import Sample
from iode.util import enable_msgs, suppress_msgs
from iode.iode_cython import Equation as CythonEquation
[docs]
class Equation:
r"""
An *equation* represents an equality mixing *variables* and *scalars* (coefficients)
and is part of a model. Each equation is composed of the following elements:
- the *LEC* form (the formula scripting language in IODE)
- a free comment (title of the equation)
- the method by which it was estimated (if applicable)
- the possible estimation period
- the names of equations estimated simultaneously (block)
- the instruments used for the estimation
All these definition elements are present in each equation, but may be left empty
if not applicable.
The name of an equation is that of its endogenous variable.
An equation can never be renamed, but it can be deleted and redefined with a new name.
Attributes
----------
endogenous: str
name of the endogenous variable. It must the same as the name of the present
equation in the Equations database.
lec: str
LEC expression of the equation.
method: EqMethod
estimation method. Possible values are:
- LSQ
- ZELLNER
- INSTRUMENTAL
- GLS
- MAX_LIKELIHOOD
sample: Sample
estimaton sample.
comment: str
instruments: str
estimation instruments.
block: str
block of equations (to estimate) to which the equation belong.
tests: dict(str, float)
tests values associated with the estimation of the equation.
date: str
last time the equation has been estimated.
Parameters
----------
endogenous: str
Name of the endogenous variable. It must the same as the name of the present
equation in the Equations database.
lec: str
LEC expression of the equation.
method: EqMethod or str, optional
Method used to estimate the coefficients of the equation.
Defaults to LSQ.
from_period: str or Period, optional
Starting period for the estimation.
Defaults to the starting period of the sample associated with the IODE Variables database.
to_period: str or Period, optional
Ending period for the estimation.
Defaults to the ending period of the sample associated with the IODE Variables database.
comment: str, optional
Defaults to empty.
instruments: str, optional
Defaults to empty.
block: str, optional
block of equations (to estimate) to which the equation belong.
Defaults to empty.
Examples
--------
>>> from iode import Equation, variables
>>> variables.sample = "1960Y1:2015Y1"
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> eq_ACAF # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)',
method = 'LSQ',
from_period = '1960Y1',
to_period = '2015Y1')
"""
[docs]
def __init__(self, endogenous: str, lec: str, method: Union[EqMethod, str]=EqMethod.LSQ, from_period: Union[str, Period]=None,
to_period: Union[str, Period]=None, comment: str='', instruments: str='', block: str='') -> Self:
if from_period is None or to_period is None:
from iode.iode_database.variables_database import variables
vars_sample = variables.sample
if vars_sample.start is None or vars_sample.end is None:
warnings.warn("The sample of the Variables workspace is not defined. "
"Set estimation sample as undefined.")
from_period, to_period = '', ''
else:
if from_period is None:
from_period = vars_sample.start
if to_period is None:
to_period = vars_sample.end
if isinstance(from_period, Period):
from_period = str(from_period)
if isinstance(to_period, Period):
to_period = str(to_period)
self._cython_instance = CythonEquation(endogenous, lec, from_period, to_period,
comment, instruments, block)
self.method = method
@classmethod
def get_instance(cls) -> Self:
instance = cls.__new__(cls)
instance._cython_instance = CythonEquation.__new__(CythonEquation)
return instance
@property
def coefficients(self) -> List[str]:
r"""
Return the list of coefficients present in the current equation.
Returns
-------
list(str)
Examples
--------
>>> from iode import SAMPLE_DATA_DIR
>>> from iode import Equation, scalars, variables
>>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.scl
161 objects loaded
>>> variables.load(f"{SAMPLE_DATA_DIR}/fun.var") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.var
394 objects loaded
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> eq_ACAF # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)',
method = 'LSQ',
from_period = '1960Y1',
to_period = '2015Y1')
>>> eq_ACAF.coefficients
['acaf1', 'acaf2', 'acaf4']
"""
return self._cython_instance.get_coefficients()
def _get_and_create_coefficients(self) -> List[str]:
r"""
Return the list of coefficients present in the equation.
Create an entry in the Scalars database for each coefficient not found in it.
Returns
-------
list(str)
Examples
--------
>>> from iode import SAMPLE_DATA_DIR
>>> from iode import Equation, scalars, variables
>>> scalars.clear()
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> eq_ACAF # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)',
method = 'LSQ',
from_period = '1960Y1',
to_period = '2015Y1')
>>> # create scalars on the flight
>>> eq_ACAF._get_and_create_coefficients()
['acaf1', 'acaf2', 'acaf4']
>>> scalars.names
['acaf1', 'acaf2', 'acaf4']
"""
return self._cython_instance._get_and_create_coefficients()
@property
def variables(self) -> List[str]:
r"""
Return the list of variables present in the equation.
Returns
-------
list(str)
Examples
--------
>>> from iode import SAMPLE_DATA_DIR
>>> from iode import Equation, scalars, variables
>>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.scl
161 objects loaded
>>> variables.load(f"{SAMPLE_DATA_DIR}/fun.var") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.var
394 objects loaded
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> eq_ACAF # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)',
method = 'LSQ',
from_period = '1960Y1',
to_period = '2015Y1')
>>> eq_ACAF.variables
['ACAF', 'VAF', 'GOSF', 'TIME']
"""
return self._cython_instance.get_variables()
def _get_and_create_variables(self) -> List[str]:
r"""
Return the list of variables present in the equation.
Create an entry in the Variables database for each variable not found in it.
Returns
-------
list(str)
Examples
--------
>>> from iode import SAMPLE_DATA_DIR
>>> from iode import Equation, scalars, variables
>>> variables.clear()
>>> variables.sample = "1960Y1:2015Y1"
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> eq_ACAF # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)',
method = 'LSQ',
from_period = '1960Y1',
to_period = '2015Y1')
>>> # create variables on the flight
>>> eq_ACAF._get_and_create_variables()
['ACAF', 'VAF', 'GOSF', 'TIME']
>>> variables.names
['ACAF', 'GOSF', 'TIME', 'VAF']
"""
return self._cython_instance._get_and_create_variables()
[docs]
def split_equation(self) -> Tuple[str, str]:
r"""
Split an equation into its left-hand side and its right-hand side.
Returns
-------
tuple(str, str)
Examples
--------
>>> from iode import SAMPLE_DATA_DIR
>>> from iode import Equation
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> lhs, rhs = eq_ACAF.split_equation()
>>> # left-hand side
>>> lhs
'(ACAF / VAF[-1])'
>>> # right-hand side
>>> rhs
'acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)'
"""
return self._cython_instance.split_equation()
def _estimate(self, from_period: Union[str, Period]=None, to_period: Union[str, Period]=None,
maxit: int=100, epsilon: float=1.0e-6) -> bool:
if isinstance(from_period, Period):
from_period = str(from_period)
if isinstance(to_period, Period):
to_period = str(to_period)
return self._cython_instance.estimate(from_period, to_period, maxit, epsilon)
[docs]
def estimate(self, from_period: Union[str, Period]=None, to_period: Union[str, Period]=None,
maxit: int=100, epsilon: float=1.0e-6, quiet: bool=False) -> bool:
r"""
Estimate the present equation.
At the end of the estimation process, certain variables and scalars are automatically created
if the process has converged. These variables and scalars can be used for computational purposes and,
as they are part of the global workspace, can be saved for future use.
The tests resulting from the last estimation are saved as scalars. The same applies to residuals,
left-hand and right-hand members of equations.
Saved tests (as scalars) have the following names:
- `e0_n` : number of sample periods
- `e0_k` : number of estimated coefficients
- `e0_stdev` : std dev of residuals
- `e0_meany` : mean of Y
- `e0_ssres` : sum of squares of residuals
- `e0_stderr` : std error
- `e0_stderrp` : std error percent (in %)
- `e0_fstat` : F-Stat
- `e0_r2` : R square
- `e0_r2adj` : adjusted R-squared
- `e0_dw` : Durbin-Watson
- `e0_loglik` : Log Likelihood
Calculated series are saved in special variables:
- `_YCALC0` : right-hand side of the equation
- `_YOBS0` : left-hand side of the equation
- `_YRES0` : residuals of the equation
Outside the estimation sample, the series values are :math:`NA`.
Parameters
----------
from_period : str or Period, optional
The starting period of the execution range.
Defaults to the starting period of the current Variables sample.
to_period : str or Period, optional
The ending period of the execution range.
Defaults to the ending period of the current Variables sample.
maxit : int, optional
The maximum number of iterations for the estimation process.
Defaults to 100.
epsilon : float, optional
The convergence threshold for the estimation process.
Defaults to 1.0e-6.
quiet : bool, optional
If True, suppresses the log messages during the estimation process.
Default to False.
Returns
-------
bool
True if the estimation process has converged, False otherwise.
Warnings
--------
If the present equation belongs to a block, you must use the :meth:`Equations.estimate` method instead.
Examples
--------
>>> from iode import SAMPLE_DATA_DIR, equations, scalars, variables
>>> equations.load(f"{SAMPLE_DATA_DIR}/fun.eqs") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.eqs
274 objects loaded
>>> variables.load(f"{SAMPLE_DATA_DIR}/fun.var") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.var
394 objects loaded
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> eq_ACAF.lec
'(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)'
>>> # create scalars
>>> scalars["acaf1"] = 0., 1.
>>> scalars["acaf2"] = 0., 1.
>>> scalars["acaf4"] = 0., 1.
>>> # estimate the ACAF equation
>>> success = eq_ACAF.estimate("1980Y1", "2000Y1")
Estimating : iteration 1 (||eps|| = 0.173205)
Estimating : iteration 2 (||eps|| = 9.24137e-09)
Solution reached after 2 iteration(s). Creating results file ...
>>> success
True
>>> scalars["acaf1"]
Scalar(0.0150646, 1, 0.00118455)
>>> scalars["acaf2"]
Scalar(-6.90855e-06, 1, 1.07873e-06)
>>> scalars["acaf4"]
Scalar(-0.00915675, 1, 0.00209541)
>>> eq_ACAF # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)',
method = 'LSQ',
from_period = '1980Y1',
to_period = '2000Y1',
tests = {corr = 1,
dw = 1.87449,
fstat = 34.6629,
loglik = 102.07,
meany = 0.0075289,
r2 = 0.793875,
r2adj = 0.770973,
ssres = 7.37883e-05,
stderr = 0.00202468,
stderrp = 26.8922,
stdev = 0.00423072},
date = '...')
>>> # observed values (left hand side of the equation)
>>> variables["_YOBS0", "1980Y1:2000Y1"] # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Workspace: Variables
nb variables: 1
filename: ...fun.var
description: Modèle fun - Simulation 1
sample: 1980Y1:2000Y1
mode: LEVEL
<BLANKLINE>
name 1980Y1 1981Y1 1982Y1 ... 1998Y1 1999Y1 2000Y1
_YOBS0 0.01 0.02 0.01 ... 0.01 0.00 0.00
<BLANKLINE>
>>> # fitted values (right hand side of the equation)
>>> variables["_YCALC0", "1980Y1:2000Y1"] # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Workspace: Variables
nb variables: 1
filename: ...fun.var
description: Modèle fun - Simulation 1
sample: 1980Y1:2000Y1
mode: LEVEL
<BLANKLINE>
name 1980Y1 1981Y1 1982Y1 ... 1998Y1 1999Y1 2000Y1
_YCALC0 0.01 0.01 0.01 ... 0.00 0.00 0.00
<BLANKLINE>
>>> # residuals values
>>> variables["_YRES0", "1980Y1:2000Y1"] # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Workspace: Variables
nb variables: 1
filename: ...fun.var
description: Modèle fun - Simulation 1
sample: 1980Y1:2000Y1
mode: LEVEL
<BLANKLINE>
name 1980Y1 1981Y1 1982Y1 ... 1998Y1 1999Y1 2000Y1
_YRES0 -0.00 0.00 -0.00 ... 0.00 -0.00 -0.00
<BLANKLINE>
"""
from iode import equations
# NOTE: In the C API, the estimation is made on equations stored in the global equations workspace.
equations[self.endogenous] = self
if quiet:
suppress_msgs()
try:
success = self._estimate(from_period, to_period, maxit, epsilon)
if quiet:
enable_msgs()
return success
except Exception as e:
if quiet:
enable_msgs()
raise e
def _estimate_step_wise(self, from_period: Union[str, Period]=None, to_period: Union[str, Period]=None,
lec_condition: str=None, test: str="r2") -> bool:
if from_period is not None:
if not isinstance(from_period, (str, Period)):
raise TypeError("from_period must be of type str or Period")
# make sure that from_period is a valid period
if isinstance(from_period, str):
from_period = Period(from_period)
from_period = str(from_period)
if to_period is not None:
if not isinstance(to_period, (str, Period)):
raise TypeError("to_period must be of type str or Period")
# make sure that to_period is a valid period
if isinstance(to_period, str):
to_period = Period(to_period)
to_period = str(to_period)
if lec_condition is None:
# Note: in the C function B_StepWise(), the lec condition is considered as True if eval(lec) != 0
lec_condition = '1'
if not isinstance(lec_condition, str):
raise TypeError("LEC condition must be a string")
if not isinstance(test, str):
raise TypeError("Test must be a string and be either 'r2' or 'fstat'")
if test not in ['r2', 'fstat']:
raise ValueError("Test must be either 'r2' or 'fstat'")
success = self._cython_instance.estimate_step_wise(from_period, to_period, lec_condition, test)
if not success:
msg = f"Estimation of the equation '{self.endogenous}' for the period range [{from_period}, {to_period}]"
if lec_condition:
msg += f" with condition:\n\t'{lec_condition}'\nfailed. "
else:
msg += "failed.\n"
msg += "No coefficients have been estimated."
warnings.warn(msg)
return success
[docs]
def estimate_step_wise(self, from_period: Union[str, Period]=None, to_period: Union[str, Period]=None,
lec_condition: str=None, test: str="r2") -> bool:
r"""
Estimates an equation and searches for the best possible test values for all
possible combinations of coefficients.
Parameters
----------
from_period : str, optional
The starting period for the estimation.
Defaults to the starting period of the current Variables sample.
to_period : str, optional
The ending period for the estimation.
Defaults to the ending period of the current Variables sample.
lec_condition : str
Boolean condition written as a LEC expression which is evaluated for each
combination of coefficients. Defaults to None (no condition).
test : str, {r2|fstat}
Test name. It must be either 'r2' or 'fstat'.
Defaults to 'r2'.
Returns
-------
bool
True if the estimation process has converged, False otherwise.
Examples
--------
>>> from iode import SAMPLE_DATA_DIR, equations, scalars, variables, Scalar
>>> equations.load(f"{SAMPLE_DATA_DIR}/fun.eqs") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.eqs
274 objects loaded
>>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.scl
161 objects loaded
>>> variables.load(f"{SAMPLE_DATA_DIR}/fun.var") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.var
394 objects loaded
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> coefficient_names = eq_ACAF.coefficients
>>> coefficient_names
['acaf1', 'acaf2', 'acaf4']
Estimate the equation for the test 'r2' with no condition
>>> # reset the coefficients
>>> scalars[coefficient_names] = Scalar(0.9, 1.)
>>> # estimate
>>> success = eq_ACAF.estimate_step_wise("1980Y1", "2000Y1") # doctest: +NORMALIZE_WHITESPACE
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 2.4346e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf2 , r2=0.575200
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 1.75111e-10)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf4 , r2=0.324238
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 4.13603e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf2 acaf4 , r2=-1.058215
Estimating : iteration 1 (||eps|| = 1.55885)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 5.30092e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf2 acaf4 , r2=0.793875
Estimating : iteration 1 (||eps|| = 1.55885)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 5.30092e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf2 acaf4 , r2=0.793875
>>> success
True
>>> # r2
>>> eq_ACAF.tests["r2"]
0.7938754558563232
>>> # display the estimated coefficients
>>> for name in coefficient_names:
... print(f"{name} -> {scalars[name]}")
acaf1 -> Scalar(0.0150646, 1, 0.00118455)
acaf2 -> Scalar(-6.90855e-06, 1, 1.07873e-06)
acaf4 -> Scalar(-0.00915675, 1, 0.00209541)
Estimate the equation for the test 'fstat' with no condition
>>> # reset the coefficients
>>> scalars[coefficient_names] = Scalar(0.9, 1.)
>>> # estimate
>>> success = eq_ACAF.estimate_step_wise("1980Y1", "2000Y1", test="fstat") # doctest: +NORMALIZE_WHITESPACE
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 2.4346e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf2 , fstat=25.726931
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 1.75111e-10)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf4 , fstat=9.116395
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 4.13603e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf2 acaf4 , fstat=-9.768702
Estimating : iteration 1 (||eps|| = 1.55885)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 5.30092e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf2 acaf4 , fstat=34.662926
Estimating : iteration 1 (||eps|| = 1.55885)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 5.30092e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf2 acaf4 , fstat=34.662926
>>> success
True
>>> # fstat
>>> eq_ACAF.tests["fstat"]
34.662925720214844
>>> # display the estimated coefficients
>>> for name in coefficient_names:
... print(f"{name} -> {scalars[name]}")
acaf1 -> Scalar(0.0150646, 1, 0.00118455)
acaf2 -> Scalar(-6.90855e-06, 1, 1.07873e-06)
acaf4 -> Scalar(-0.00915675, 1, 0.00209541)
Estimate the equation for the test 'r2' with condition (acaf2 > 0)
>>> # write the condition (all coefficients > 0)
>>> lec_condition = "acaf2 > 0"
>>> lec_condition
'acaf2 > 0'
>>> # reset the coefficients
>>> scalars[coefficient_names] = Scalar(0.9, 1.)
>>> # estimate
>>> success = eq_ACAF.estimate_step_wise("1980Y1", "2000Y1", lec_condition) # doctest: +NORMALIZE_WHITESPACE
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 2.4346e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf2 , r2=0.575200
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 1.75111e-10)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf4 , r2=0.324238
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 4.13603e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf2 acaf4 , r2=-1.058215
Estimating : iteration 1 (||eps|| = 1.55885)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 5.30092e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf2 acaf4 , r2=0.793875
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 4.13603e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf2 acaf4 , r2=-1.058215
>>> success
True
>>> # r2
>>> eq_ACAF.tests["r2"]
-1.0582152605056763
>>> # display the estimated coefficients
>>> for name in coefficient_names:
... print(f"{name} -> {scalars[name]}")
acaf1 -> Scalar(0, 0, 0)
acaf2 -> Scalar(5.76933e-06, 1, 1.26806e-06)
acaf4 -> Scalar(-0.0104115, 1, 0.00643766)
Estimate the equation for the test 'fstat' with condition (acaf2 > 0)
>>> # reset the coefficients
>>> scalars[coefficient_names] = Scalar(0.9, 1.)
>>> # estimate
>>> success = eq_ACAF.estimate_step_wise("1980Y1", "2000Y1", lec_condition, "fstat") # doctest: +NORMALIZE_WHITESPACE
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 2.4346e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf2 , fstat=25.726931
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 1.75111e-10)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf4 , fstat=9.116395
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 4.13603e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf2 acaf4 , fstat=-9.768702
Estimating : iteration 1 (||eps|| = 1.55885)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 5.30092e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf1 acaf2 acaf4 , fstat=34.662926
Estimating : iteration 1 (||eps|| = 1.27279)
<BLANKLINE>
Estimating : iteration 2 (||eps|| = 4.13603e-08)
<BLANKLINE>
Solution reached after 2 iteration(s). Creating results file ...
<BLANKLINE>
ACAF: scalars : acaf2 acaf4 , fstat=-9.768702
>>> success
True
>>> # fstat
>>> eq_ACAF.tests["fstat"]
-9.768701553344727
>>> # display the estimated coefficients
>>> for name in coefficient_names:
... print(f"{name} -> {scalars[name]}")
acaf1 -> Scalar(0, 0, 0)
acaf2 -> Scalar(5.76933e-06, 1, 1.26806e-06)
acaf4 -> Scalar(-0.0104115, 1, 0.00643766)
"""
from iode import equations
# NOTE: In the C API, the estimation is made on equations stored in the global equations workspace.
equations[self.endogenous] = self
return self._estimate_step_wise(from_period, to_period, lec_condition, test)
@property
def endogenous(self) -> str:
return self._cython_instance.get_endogenous()
@property
def lec(self) -> str:
r"""
LEC expression of the current equation.
Paramaters
----------
value: str
New LEC expression.
Examples
--------
>>> from iode import Equation
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> eq_ACAF.endogenous
'ACAF'
>>> eq_ACAF.lec
'(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)'
>>> # remove acaf1 from the LEC expression of the ACAF equation
>>> eq_ACAF.lec = "(ACAF / VAF[-1]) := acaf2 * GOSF[-1] + acaf4 * (TIME=1995)"
>>> eq_ACAF.lec
'(ACAF / VAF[-1]) := acaf2 * GOSF[-1] + acaf4 * (TIME=1995)'
>>> # wrong name for the endogenous variable
>>> eq_ACAF.lec = "(ACAF_ / VAF[-1]) := acaf2 * GOSF[-1] + acaf4 * (TIME=1995)"
Traceback (most recent call last):
...
ValueError: Cannot set LEC '(ACAF_ / VAF[-1]) := acaf2 * GOSF[-1] + acaf4 * (TIME=1995)' to the equation named 'ACAF'
"""
return self._cython_instance.get_lec()
@lec.setter
def lec(self, value: str):
self._cython_instance.set_lec(value)
@property
def method(self) -> str:
r"""
Estimation method.
Parameters
----------
value: EqMethod or str
Possible values are LSQ, ZELLNER, INSTRUMENTAL, GLS, MAX_LIKELIHOOD.
Examples
--------
>>> from iode import Equation, EqMethod
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> # default value
>>> eq_ACAF.method
'LSQ'
>>> eq_ACAF.method = EqMethod.GLS
>>> eq_ACAF.method
'GLS'
>>> eq_ACAF.method = 'MAX_LIKELIHOOD'
>>> eq_ACAF.method
'MAX_LIKELIHOOD'
"""
return self._cython_instance.get_method()
@method.setter
def method(self, value: Union[EqMethod, str]):
if isinstance(value, str):
value = value.upper()
# warning: In the IODE ascii and binary files, the method is stored as a string (char*).
# In particular:
# - if no method have been specifed, the method is stored as an empty string
# - the GLS method is returned as 'GLS (3SLS)'
if not value:
value = EqMethod.LSQ
elif "GLS" in value:
value = EqMethod.GLS
else:
value = EqMethod[value]
value = int(value)
self._cython_instance.set_method(value)
@property
def sample(self) -> Sample:
r"""
Estimation sample of the current equation.
Parameters
----------
value: str or Sample
New estimation sample
Examples
--------
>>> from iode import Equation, variables
>>> variables.clear()
>>> variables.sample
None
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> eq_ACAF.sample
None
>>> # set Variables sample
>>> variables.sample = "1960Y1:2015Y1"
>>> variables.sample
Sample("1960Y1:2015Y1")
>>> # specify starting and ending periods
>>> eq_ACAF.sample = "1980Y1:2000Y1"
>>> eq_ACAF.sample
Sample("1980Y1:2000Y1")
>>> # specify only the starting period
>>> # -> ending period = ending period of the Variables sample
>>> eq_ACAF.sample = "1990Y1:"
>>> eq_ACAF.sample
Sample("1990Y1:2015Y1")
>>> # specify only the ending period
>>> # -> starting period = starting period of the Variables sample
>>> eq_ACAF.sample = ":2010Y1"
>>> eq_ACAF.sample
Sample("1960Y1:2010Y1")
>>> # specify nothing -> reset the sample
>>> eq_ACAF.sample = ":"
>>> eq_ACAF.sample
None
"""
sample = Sample.get_instance()
sample._cython_instance = self._cython_instance.get_sample()
return sample
@sample.setter
def sample(self, value: Union[str, Sample]):
if value is None:
raise ValueError("New sample value cannot be None")
if isinstance(value, Sample):
value = str(value)
if ':' not in value:
raise ValueError("New sample value must contain the colon character ':'. "
f"Got value '{value}'.")
from_period, to_period = value.split(':')
self._cython_instance.set_sample(from_period, to_period)
@property
def comment(self) -> str:
r"""
Equation comment.
"""
return self._cython_instance.get_comment()
@comment.setter
def comment(self, value: str):
self._cython_instance.set_comment(value)
@property
def instruments(self) -> Union[str, List[str]]:
r"""
Instrument(s) used for the estimation.
Parameters
----------
value: str or list(str)
If several instruments are required for the estimation, they can be passed either as
a unique string in which instruments are separated by a semi colon ';' or as a list of
strings.
"""
return self._cython_instance.get_instruments()
@instruments.setter
def instruments(self, value: Union[str, List[str]]):
if not isinstance(value, str):
value = ';'.join(value)
self._cython_instance.set_instruments(value)
@property
def block(self) -> str:
r"""
Estimation block of equations to which the equation belongs.
Parameters
----------
value: str or list(str)
Examples
--------
>>> from iode import SAMPLE_DATA_DIR, equations
>>> equations.load(f"{SAMPLE_DATA_DIR}/fun.eqs") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.eqs
274 objects loaded
>>> equations["ACAF"].block # doctest: +NORMALIZE_WHITESPACE
'ACAF'
"""
return self._cython_instance.get_block()
@block.setter
def block(self, value: Union[str, List[str]]):
if not isinstance(value, str):
value = ';'.join(value)
self._cython_instance.set_block(value)
@property
def tests(self) -> Dict[str, float]:
r"""
Estimation tests.
Returns
-------
dict(str, float)
Examples
--------
>>> from iode import SAMPLE_DATA_DIR, equations
>>> equations.load(f"{SAMPLE_DATA_DIR}/fun.eqs") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.eqs
274 objects loaded
>>> equations["ACAF"].tests # doctest: +NORMALIZE_WHITESPACE
{'corr': 1.0, 'dw': 2.329345941543579, 'fstat': 32.273193359375, 'loglik': 83.80752563476562,
'meany': 0.008184665814042091, 'r2': 0.8217613697052002, 'r2adj': 0.7962986826896667,
'ssres': 5.1994487876072526e-05, 'stderr': 0.0019271461060270667, 'stderrp': 23.545812606811523,
'stdev': 0.004269900266081095}
"""
return self._cython_instance.get_tests()
@property
def date(self) -> str:
r"""
Date of the last estimation.
Returns
-------
str
Examples
--------
>>> from iode import SAMPLE_DATA_DIR, equations
>>> equations.load(f"{SAMPLE_DATA_DIR}/fun.eqs") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.eqs
274 objects loaded
>>> equations["ACAF"].date
'12-06-1998'
"""
return self.get_formated_date()
def _set_tests_from_list(self, tests: List[float]):
r"""
Examples
--------
>>> from iode import Equation, variables
>>> variables.sample = "1960Y1:2015Y1"
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> eq_ACAF # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)',
method = 'LSQ',
from_period = '1960Y1',
to_period = '2015Y1')
>>> test_values = [1.0, 2.32935, 32.2732, 83.8075, 0.00818467, 0.821761, 0.796299,
... 5.19945e-05, 0.00192715, 23.5458, 0.0042699]
>>> eq_ACAF._set_tests_from_list(test_values)
>>> eq_ACAF # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)',
method = 'LSQ',
from_period = '1960Y1',
to_period = '2015Y1',
tests = {corr = 1,
dw = 23.5458,
fstat = 0.796299,
loglik = 0.0042699,
meany = 32.2732,
r2 = 5.19945e-05,
r2adj = 0.00192715,
ssres = 83.8075,
stderr = 0.00818467,
stderrp = 0.821761,
stdev = 2.32935})
"""
if len(tests) != len(EqTest):
raise ValueError("Cannot set equation test values. "
f"Expected vector of size {len(EqTest)} but got vector of size {len(tests)}.")
self._cython_instance._set_tests_from_list(tests)
def _set_date(self, value: str, format: str='dd-mm-yyyy'):
r"""
Examples
--------
>>> from iode import Equation, variables
>>> variables.sample = "1960Y1:2015Y1"
>>> eq_ACAF = Equation("ACAF", "(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)")
>>> eq_ACAF # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF / VAF[-1]) := acaf1 + acaf2 * GOSF[-1] + acaf4 * (TIME=1995)',
method = 'LSQ',
from_period = '1960Y1',
to_period = '2015Y1')
>>> eq_ACAF.date
''
>>> eq_ACAF._set_date("18-06-2024")
>>> eq_ACAF.date
'18-06-2024'
>>> eq_ACAF._set_date("20/06/2024", "dd/mm/yyyy")
>>> eq_ACAF.date
'20-06-2024'
>>> eq_ACAF._set_date("")
>>> eq_ACAF.date
''
"""
self._cython_instance._set_date(value, format)
def _as_tuple(self) -> Tuple:
r"""
Examples
--------
>>> from iode import SAMPLE_DATA_DIR, equations
>>> equations.load(f"{SAMPLE_DATA_DIR}/fun.eqs") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.eqs
274 objects loaded
>>> equations["ACAF"] # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF/VAF[-1]) :=acaf1+acaf2*GOSF[-1]+\nacaf4*(TIME=1995)',
method = 'LSQ',
from_period = '1980Y1',
to_period = '1996Y1',
block = 'ACAF',
tests = {corr = 1,
dw = 2.32935,
fstat = 32.2732,
loglik = 83.8075,
meany = 0.00818467,
r2 = 0.821761,
r2adj = 0.796299,
ssres = 5.19945e-05,
stderr = 0.00192715,
stderrp = 23.5458,
stdev = 0.0042699},
date = '12-06-1998')
>>> equations["ACAF"]._as_tuple() # doctest: +NORMALIZE_WHITESPACE
('ACAF', '(ACAF/VAF[-1]) :=acaf1+acaf2*GOSF[-1]+\nacaf4*(TIME=1995)', 'LSQ', '1980Y1:1996Y1', '', '', 'ACAF',
1.0, 0.004269900266081095, 0.008184665814042091, 5.1994487876072526e-05, 0.0019271461060270667, 23.545812606811523,
32.273193359375, 0.8217613697052002, 0.7962986826896667, 2.329345941543579, 83.80752563476562, '12-06-1998')
"""
tests = self._cython_instance._get_list_tests()
return self.endogenous, self.lec, self.method, str(self.sample), self.comment, self.instruments, \
self.block, *tests, self.date
[docs]
def copy(self) -> Self:
r"""
Return a copy of the current Equation.
Examples
--------
>>> from iode import SAMPLE_DATA_DIR, equations, variables
>>> equations.load(f"{SAMPLE_DATA_DIR}/fun.eqs") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.eqs
274 objects loaded
>>> variables.load(f"{SAMPLE_DATA_DIR}/fun.var") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.var
394 objects loaded
>>> equations["ACAF"] # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF/VAF[-1]) :=acaf1+acaf2*GOSF[-1]+\nacaf4*(TIME=1995)',
method = 'LSQ',
from_period = '1980Y1',
to_period = '1996Y1',
block = 'ACAF',
tests = {corr = 1,
dw = 2.32935,
fstat = 32.2732,
loglik = 83.8075,
meany = 0.00818467,
r2 = 0.821761,
r2adj = 0.796299,
ssres = 5.19945e-05,
stderr = 0.00192715,
stderrp = 23.5458,
stdev = 0.0042699},
date = '12-06-1998')
>>> copied_eq = equations["ACAF"].copy()
>>> copied_eq # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF/VAF[-1]) :=acaf1+acaf2*GOSF[-1]+\nacaf4*(TIME=1995)',
method = 'LSQ',
from_period = '1980Y1',
to_period = '1996Y1',
block = 'ACAF',
tests = {corr = 1,
dw = 2.32935,
fstat = 32.2732,
loglik = 83.8075,
meany = 0.00818467,
r2 = 0.821761,
r2adj = 0.796299,
ssres = 5.19945e-05,
stderr = 0.00192715,
stderrp = 23.5458,
stdev = 0.0042699},
date = '12-06-1998')
"""
return copy(self)
def __eq__(self, other: Self) -> bool:
return self._cython_instance.equal(other)
def __copy__(self) -> Self:
r"""
Return a copy of the current Equation.
Examples
--------
>>> from copy import copy
>>> from iode import SAMPLE_DATA_DIR, equations, variables
>>> equations.load(f"{SAMPLE_DATA_DIR}/fun.eqs") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.eqs
274 objects loaded
>>> variables.load(f"{SAMPLE_DATA_DIR}/fun.var") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
Loading .../fun.var
394 objects loaded
>>> equations["ACAF"] # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF/VAF[-1]) :=acaf1+acaf2*GOSF[-1]+\nacaf4*(TIME=1995)',
method = 'LSQ',
from_period = '1980Y1',
to_period = '1996Y1',
block = 'ACAF',
tests = {corr = 1,
dw = 2.32935,
fstat = 32.2732,
loglik = 83.8075,
meany = 0.00818467,
r2 = 0.821761,
r2adj = 0.796299,
ssres = 5.19945e-05,
stderr = 0.00192715,
stderrp = 23.5458,
stdev = 0.0042699},
date = '12-06-1998')
>>> copied_eq = copy(equations["ACAF"])
>>> copied_eq # doctest: +NORMALIZE_WHITESPACE
Equation(endogenous = 'ACAF',
lec = '(ACAF/VAF[-1]) :=acaf1+acaf2*GOSF[-1]+\nacaf4*(TIME=1995)',
method = 'LSQ',
from_period = '1980Y1',
to_period = '1996Y1',
block = 'ACAF',
tests = {corr = 1,
dw = 2.32935,
fstat = 32.2732,
loglik = 83.8075,
meany = 0.00818467,
r2 = 0.821761,
r2adj = 0.796299,
ssres = 5.19945e-05,
stderr = 0.00192715,
stderrp = 23.5458,
stdev = 0.0042699},
date = '12-06-1998')
"""
from_period = self.sample.start
to_period = self.sample.end
copied_eq = Equation(self.endogenous, self.lec, self.method, from_period, to_period,
self.comment, self.instruments, self.block)
tests = self._cython_instance._get_list_tests()
copied_eq._cython_instance._set_tests_from_list(tests)
copied_eq._cython_instance._set_date(self.date)
return copied_eq
def __str__(self) -> str:
return self._cython_instance._str_()
def __repr__(self) -> str:
return self._cython_instance._repr_()
def __hash__(self) -> int:
return self._cython_instance.__hash__()