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

    plk = pickle.dumps(ad)
    pad = pickle.loads(plk)
    assert list(pad.keys()) == ["one", 1, ("a", 2)]
    assert len(pad) == 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()) == []
Example #2
0
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