def test_attr_dict(): ad = AttrDict() ad['one'] = 'one' ad[1] = 1 ad[('a', 2)] = ('a', 2) assert list(ad.keys()) == ['one', 1, ('a', 2)] assert len(ad) == 3 ad2 = ad.copy() assert list(ad2.keys()) == list(ad.keys()) assert ad.get('one', None) == 'one' assert ad.get('two', False) is False k, v = ad.popitem() assert k == 'one' assert v == 'one' items = ad.items() assert (1, 1) in items assert (('a', 2), ('a', 2)) in items assert len(items) == 2 values = ad.values() assert 1 in values assert ('a', 2) in values assert len(values) == 2 ad2 = AttrDict() ad2[1] = 3 ad2['one'] = 'one' ad2['a'] = 'a' ad.update(ad2) assert ad[1] == 3 assert 'a' in ad ad.__str__() with pytest.raises(AttributeError): ad.__ordered_dict__ = None with pytest.raises(AttributeError): ad.some_other_key with pytest.raises(KeyError): ad['__ordered_dict__'] = None del ad[1] assert 1 not in ad.keys() ad.new_value = 'new_value' assert 'new_value' in ad.keys() assert ad.new_value == ad['new_value'] for key in ad.keys(): if isinstance(key, str): assert key in dir(ad) new_value = ad.pop('new_value') assert new_value == 'new_value' del ad.one assert 'one' not in ad.keys() ad.clear() assert list(ad.keys()) == []
class SystemResults(_CommonResults): """ Results from Seemingly Unrelated Regression Estimators Parameters ---------- results : AttrDict Dictionary of model estimation results """ def __init__(self, results): super(SystemResults, self).__init__(results) self._individual = AttrDict() for key in results.individual: self._individual[key] = SystemEquationResult( results.individual[key]) self._sigma = results.sigma self._model = results.model self._constraints = results.constraints self._num_constraints = 'None' if results.constraints is not None: self._num_constraints = str(results.constraints.r.shape[0]) self._weight_estimtor = results.get('weight_estimator', None) @property def model(self): """Model used in estimation""" return self._model @property def equations(self): """Individual equation results""" return self._individual @property def equation_labels(self): """Individual equation labels""" return list(self._individual.keys()) @property def resids(self): """Estimated residuals""" return DataFrame(self._resid, index=self._index, columns=self.equation_labels) @property def fitted_values(self): """Fitted values""" return DataFrame(self._fitted, index=self._index, columns=self.equation_labels) def _out_of_sample(self, equations, data, missing, dataframe): if equations is not None and data is not None: raise ValueError('Predictions can only be constructed using one ' 'of eqns or data, but not both.') pred = self.model.predict(self.params, equations=equations, data=data) # type: DataFrame if not dataframe: pred = {col: pred[[col]] for col in pred} if not missing: for key in pred: pred[key] = pred[key].dropna() else: pred = pred.dropna(how='all', axis=1) return pred def predict(self, equations=None, *, data=None, fitted=True, idiosyncratic=False, missing=False, dataframe=False): """ In- and out-of-sample predictions Parameters ---------- equations : dict Dictionary-like structure containing exogenous and endogenous variables. Each key is an equations label and must match the labels used to fir the model. Each value must be either a tuple of the form (exog, endog) or a dictionary with keys 'exog' and 'endog'. If predictions are not required for one of more of the model equations, these keys can be omitted. data : DataFrame DataFrame to use for out-of-sample predictions when model was constructed using a formula. fitted : bool, optional Flag indicating whether to include the fitted values idiosyncratic : bool, optional Flag indicating whether to include the estimated idiosyncratic shock missing : bool, optional Flag indicating to adjust for dropped observations. if True, the values returns will have the same size as the original input data before filtering missing values dataframe : bool, optional Flag indicating to return output as a dataframe. If False, a dictionary is returned using the equation labels as keys. Returns ------- predictions : DataFrame, dict DataFrame or dictionary containing selected outputs Notes ----- If `equations` and `data` are both `None`, in-sample predictions (fitted values) will be returned. If `data` is not none, then `equations` must be none. Predictions from models constructed using formulas can be computed using either `equations`, which will treat these are arrays of values corresponding to the formula-process data, or using `data` which will be processed using the formula used to construct the values corresponding to the original model specification. When using `exog` and `endog`, the regressor array for a particular equation is assembled as `[equations[eqn]['exog'], equations[eqn]['endog']]` where `eqn` is an equation label. These must correspond to the columns in the estimated model. """ if equations is not None or data is not None: return self._out_of_sample(equations, data, missing, dataframe) if not (fitted or idiosyncratic): raise ValueError('At least one output must be selected') if dataframe: if fitted and not idiosyncratic: out = self.fitted_values elif idiosyncratic and not fitted: out = self.resids else: out = { 'fitted_values': self.fitted_values, 'idiosyncratic': self.resids } else: out = {} for key in self.equation_labels: vals = [] if fitted: vals.append(self.fitted_values[[key]]) if idiosyncratic: vals.append(self.resids[[key]]) out[key] = concat(vals, 1) if missing: if isinstance(out, DataFrame): out = out.reindex(self._original_index) else: for key in out: out[key] = out[key].reindex(self._original_index) return out @property def wresids(self): """Weighted estimated residuals""" return DataFrame(self._wresid, index=self._index, columns=self.equation_labels) @property def sigma(self): """Estimated residual covariance""" return self._sigma @property def summary(self): """:obj:`statsmodels.iolib.summary.Summary` : Summary table of model estimation results Supports export to csv, html and latex using the methods ``summary.as_csv()``, ``summary.as_html()`` and ``summary.as_latex()``. """ title = 'System ' + self._method + ' Estimation Summary' top_left = [('Estimator:', self._method), ('No. Equations.:', str(len(self.equation_labels))), ('No. Observations:', str(self.resids.shape[0])), ('Date:', self._datetime.strftime('%a, %b %d %Y')), ('Time:', self._datetime.strftime('%H:%M:%S')), ('', ''), ('', '')] top_right = [('Overall R-squared:', _str(self.rsquared)), ('Cov. Estimator:', self._cov_type), ('Num. Constraints: ', self._num_constraints), ('', ''), ('', ''), ('', ''), ('', '')] stubs = [] vals = [] for stub, val in top_left: stubs.append(stub) vals.append([val]) table = SimpleTable(vals, txt_fmt=fmt_2cols, title=title, stubs=stubs) # create summary table instance smry = Summary() # Top Table # Parameter table fmt = fmt_2cols fmt['data_fmts'][1] = '%10s' top_right = [('%-21s' % (' ' + k), v) for k, v in top_right] stubs = [] vals = [] for stub, val in top_right: stubs.append(stub) vals.append([val]) table.extend_right(SimpleTable(vals, stubs=stubs)) smry.tables.append(table) for i, eqlabel in enumerate(self.equation_labels): last_row = i == (len(self.equation_labels) - 1) results = self.equations[eqlabel] dep_name = results.dependent title = 'Equation: {0}, Dependent Variable: {1}'.format( eqlabel, dep_name) pad_bottom = results.instruments is not None and not last_row smry.tables.append( param_table(results, title, pad_bottom=pad_bottom)) if results.instruments: formatted = format_wide(results.instruments, 80) if not last_row: formatted.append([' ']) smry.tables.append( SimpleTable(formatted, headers=['Instruments'])) extra_text = ['Covariance Estimator:'] for line in str(self._cov_estimator).split('\n'): extra_text.append(line) if self._weight_estimtor: extra_text.append('Weight Estimator:') for line in str(self._weight_estimtor).split('\n'): extra_text.append(line) smry.add_extra_txt(extra_text) return smry
class SystemResults(_CommonResults): """ Results from Seemingly Unrelated Regression Estimators Parameters ---------- results : AttrDict Dictionary of model estimation results """ def __init__(self, results: AttrDict) -> None: super(SystemResults, self).__init__(results) self._individual = AttrDict() for key in results.individual: self._individual[key] = SystemEquationResult( results.individual[key]) self._system_r2 = results.system_r2 self._sigma = results.sigma self._model = results.model self._constraints = results.constraints self._num_constraints = "None" if results.constraints is not None: self._num_constraints = str(results.constraints.r.shape[0]) self._weight_estimtor = results.get("weight_estimator", None) @property def model(self) -> "linearmodels.system.model.IV3SLS": """Model used in estimation""" return self._model @property def equations(self) -> AttrDict: """Individual equation results""" return self._individual @property def equation_labels(self) -> List[str]: """Individual equation labels""" return list(self._individual.keys()) @property def resids(self) -> DataFrame: """Estimated residuals""" return DataFrame(self._resid, index=self._index, columns=self.equation_labels) @property def fitted_values(self) -> DataFrame: """Fitted values""" return DataFrame(self._fitted, index=self._index, columns=self.equation_labels) def _out_of_sample( self, equations: Optional[Dict[str, Dict[str, ArrayLike]]], data: Optional[DataFrame], missing: bool, dataframe: bool, ) -> Union[Dict[str, Series], DataFrame]: if equations is not None and data is not None: raise ValueError("Predictions can only be constructed using one " "of eqns or data, but not both.") pred: DataFrame = self.model.predict(self.params, equations=equations, data=data) if dataframe: if missing: pred = pred.dropna(how="all", axis=1) return pred pred_dict = {col: pred[[col]] for col in pred} if missing: pred_dict = {col: pred_dict[[col]].dropna() for col in pred} return pred_dict def predict( self, equations: Optional[Dict[str, Dict[str, ArrayLike]]] = None, *, data: Optional[DataFrame] = None, fitted: bool = True, idiosyncratic: bool = False, missing: bool = False, dataframe: bool = False, ) -> Union[DataFrame, dict]: """ In- and out-of-sample predictions Parameters ---------- equations : dict Dictionary-like structure containing exogenous and endogenous variables. Each key is an equations label and must match the labels used to fit the model. Each value must be either a tuple of the form (exog, endog) or a dictionary with keys 'exog' and 'endog'. If predictions are not required for one of more of the model equations, these keys can be omitted. data : DataFrame DataFrame to use for out-of-sample predictions when model was constructed using a formula. fitted : bool, optional Flag indicating whether to include the fitted values idiosyncratic : bool, optional Flag indicating whether to include the estimated idiosyncratic shock missing : bool, optional Flag indicating to adjust for dropped observations. if True, the values returns will have the same size as the original input data before filtering missing values dataframe : bool, optional Flag indicating to return output as a dataframe. If False, a dictionary is returned using the equation labels as keys. Returns ------- predictions : {DataFrame, dict} DataFrame or dictionary containing selected outputs Notes ----- If `equations` and `data` are both `None`, in-sample predictions (fitted values) will be returned. If `data` is not none, then `equations` must be none. Predictions from models constructed using formulas can be computed using either `equations`, which will treat these are arrays of values corresponding to the formula-process data, or using `data` which will be processed using the formula used to construct the values corresponding to the original model specification. When using `exog` and `endog`, the regressor array for a particular equation is assembled as `[equations[eqn]['exog'], equations[eqn]['endog']]` where `eqn` is an equation label. These must correspond to the columns in the estimated model. """ if equations is not None or data is not None: return self._out_of_sample(equations, data, missing, dataframe) if not (fitted or idiosyncratic): raise ValueError("At least one output must be selected") if dataframe: if fitted and not idiosyncratic: out = self.fitted_values elif idiosyncratic and not fitted: out = self.resids else: out = { "fitted_values": self.fitted_values, "idiosyncratic": self.resids, } else: out = {} for key in self.equation_labels: vals = [] if fitted: vals.append(self.fitted_values[[key]]) if idiosyncratic: vals.append(self.resids[[key]]) out[key] = concat(vals, 1) if missing: if isinstance(out, DataFrame): out = out.reindex(self._original_index) else: for key in out: out[key] = out[key].reindex(self._original_index) return out @property def wresids(self) -> DataFrame: """Weighted estimated residuals""" return DataFrame(self._wresid, index=self._index, columns=self.equation_labels) @property def sigma(self) -> DataFrame: """Estimated residual covariance""" return self._sigma @property def system_rsquared(self) -> Series: r""" Alternative measure of system fit Returns ------- Series The measures of overall system fit. Notes ----- McElroy's R2 is defined as .. math:: 1 - \frac{SSR_{\Omega}}{TSS_{\Omega}} where .. math:: SSR_{\Omega} = \hat{\epsilon}^\prime\hat{\Omega}^{-1}\hat{\epsilon} and .. math:: TSS_{\Omega} = \hat{\eta}^\prime\hat{\Omega}^{-1}\hat{\eta} where :math:`\eta` is the residual from a regression on only a constant. Judge's system R2 is defined as .. math:: 1 - \frac{\sum_i \sum_j \hat{\epsilon}_ij^2}{\sum_i \sum_j \hat{\eta}_ij^2} where :math:`\eta` is the residual from a regression on only a constant. Berndt's system R2 is defined as .. math:: 1 - \frac{|\hat{\Sigma}_\epsilon|}{|\hat{\Sigma}_\eta|} where :math:`\hat{\Sigma}_\epsilon` and :math:`\hat{\Sigma}_\eta` are the estimated covariances :math:`\epsilon` and :math:`\eta`, respectively. Dhrymes's system R2 is defined as a weighted average of the R2 of each equation .. math:: \sum__i w_i R^2_i where the weight is .. math:: w_i = \frac{\hat{\Sigma}_{\eta}^{[ii]}}{\tr{\hat{\Sigma}_{\eta}}} the ratio of the variance the dependent in an equation to the total variance of all dependent variables. """ return self._system_r2 @property def summary(self) -> Summary: """ Model estimation summary. Returns ------- Summary Summary table of model estimation results Supports export to csv, html and latex using the methods ``summary.as_csv()``, ``summary.as_html()`` and ``summary.as_latex()``. """ title = "System " + self._method + " Estimation Summary" top_left = [ ("Estimator:", self._method), ("No. Equations.:", str(len(self.equation_labels))), ("No. Observations:", str(self.resids.shape[0])), ("Date:", self._datetime.strftime("%a, %b %d %Y")), ("Time:", self._datetime.strftime("%H:%M:%S")), ("", ""), ("", ""), ] top_right = [ ("Overall R-squared:", _str(self.rsquared)), ("McElroy's R-squared:", _str(self.system_rsquared.mcelroy)), ("Judge's (OLS) R-squared:", _str(self.system_rsquared.judge)), ("Berndt's R-squared:", _str(self.system_rsquared.berndt)), ("Dhrymes's R-squared:", _str(self.system_rsquared.dhrymes)), ("Cov. Estimator:", self._cov_type), ("Num. Constraints: ", self._num_constraints), ] stubs = [] vals = [] for stub, val in top_left: stubs.append(stub) vals.append([val]) table = SimpleTable(vals, txt_fmt=fmt_2cols, title=title, stubs=stubs) # create summary table instance smry = Summary() # Top Table # Parameter table fmt = fmt_2cols fmt["data_fmts"][1] = "%10s" top_right = [("%-21s" % (" " + k), v) for k, v in top_right] stubs = [] vals = [] for stub, val in top_right: stubs.append(stub) vals.append([val]) table.extend_right(SimpleTable(vals, stubs=stubs)) smry.tables.append(table) for i, eqlabel in enumerate(self.equation_labels): last_row = i == (len(self.equation_labels) - 1) results = self.equations[eqlabel] dep_name = results.dependent title = "Equation: {0}, Dependent Variable: {1}".format( eqlabel, dep_name) pad_bottom = results.instruments is not None and not last_row smry.tables.append( param_table(results, title, pad_bottom=pad_bottom)) if results.instruments: formatted = format_wide(results.instruments, 80) if not last_row: formatted.append([" "]) smry.tables.append( SimpleTable(formatted, headers=["Instruments"])) extra_text = ["Covariance Estimator:"] for line in str(self._cov_estimator).split("\n"): extra_text.append(line) if self._weight_estimtor: extra_text.append("Weight Estimator:") for line in str(self._weight_estimtor).split("\n"): extra_text.append(line) smry.add_extra_txt(extra_text) return smry
class SystemResults(_CommonResults): """ Results from Seemingly Unrelated Regression Estimators Parameters ---------- results : AttrDict Dictionary of model estimation results """ def __init__(self, results): super(SystemResults, self).__init__(results) self._individual = AttrDict() for key in results.individual: self._individual[key] = SystemEquationResult(results.individual[key]) self._sigma = results.sigma self._model = results.model self._constraints = results.constraints self._num_constraints = 'None' if results.constraints is not None: self._num_constraints = str(results.constraints.r.shape[0]) @property def model(self): """Model used in estimation""" return self._model @property def equations(self): """Individual equation results""" return self._individual @property def equation_labels(self): """Individual equation labels""" return list(self._individual.keys()) @property def resids(self): """Estimated residuals""" return DataFrame(self._resid, index=self._index, columns=self.equation_labels) @property def wresids(self): """Weighted estimated residuals""" return DataFrame(self._wresid, index=self._index, columns=self.equation_labels) @property def sigma(self): """Estimated residual covariance""" return self._sigma @property def summary(self): """Model summary""" title = 'System ' + self._method + ' Estimation Summary' top_left = [('Estimator:', self._method), ('No. Equations.:', str(len(self.equation_labels))), ('No. Observations:', str(self.resids.shape[0])), ('Date:', self._datetime.strftime('%a, %b %d %Y')), ('Time:', self._datetime.strftime('%H:%M:%S')), ('', ''), ('', '')] top_right = [('Overall R-squared:', _str(self.rsquared)), ('Cov. Estimator:', self._cov_type), ('Num. Constraints: ', self._num_constraints), ('', ''), ('', ''), ('', ''), ('', '')] stubs = [] vals = [] for stub, val in top_left: stubs.append(stub) vals.append([val]) table = SimpleTable(vals, txt_fmt=fmt_2cols, title=title, stubs=stubs) # create summary table instance smry = Summary() # Top Table # Parameter table fmt = fmt_2cols fmt['data_fmts'][1] = '%10s' top_right = [('%-21s' % (' ' + k), v) for k, v in top_right] stubs = [] vals = [] for stub, val in top_right: stubs.append(stub) vals.append([val]) table.extend_right(SimpleTable(vals, stubs=stubs)) smry.tables.append(table) for eqlabel in self.equation_labels: results = self.equations[eqlabel] dep_name = results.dependent title = 'Equation: {0}, Dependent Variable: {1}'.format(eqlabel, dep_name) smry.tables.append(param_table(results, title, pad_bottom=True)) return smry
def test_attr_dict(): ad = AttrDict() ad["one"] = "one" ad[1] = 1 ad[("a", 2)] = ("a", 2) assert list(ad.keys()) == ["one", 1, ("a", 2)] assert len(ad) == 3 ad2 = ad.copy() assert list(ad2.keys()) == list(ad.keys()) assert ad.get("one", None) == "one" assert ad.get("two", False) is False k, v = ad.popitem() assert k == "one" assert v == "one" items = ad.items() assert (1, 1) in items assert (("a", 2), ("a", 2)) in items assert len(items) == 2 values = ad.values() assert 1 in values assert ("a", 2) in values assert len(values) == 2 ad2 = AttrDict() ad2[1] = 3 ad2["one"] = "one" ad2["a"] = "a" ad.update(ad2) assert ad[1] == 3 assert "a" in ad ad.__str__() with pytest.raises(AttributeError): ad.__private_dict__ = None with pytest.raises(AttributeError): ad.some_other_key with pytest.raises(KeyError): ad["__private_dict__"] = None del ad[1] assert 1 not in ad.keys() ad.new_value = "new_value" assert "new_value" in ad.keys() assert ad.new_value == ad["new_value"] for key in ad.keys(): if isinstance(key, str): assert key in dir(ad) new_value = ad.pop("new_value") assert new_value == "new_value" del ad.one assert "one" not in ad.keys() ad.clear() assert list(ad.keys()) == []