{"action":"created","comment":{"url":"https://api.github.com/repos/statsmodels/statsmodels/pulls/comments/240017286","pull_request_review_id":182957714,"id":240017286,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDI0MDAxNzI4Ng==","diff_hunk":"@@ -0,0 +1,767 @@\n+# -*- coding: utf-8 -*-\n+\"\"\"\n+Generalized Additive Models\n+\n+Author: Luca Puggini\n+Author: Josef Perktold\n+\n+created on 08/07/2015\n+\"\"\"\n+\n+from __future__ import division\n+import numpy as np\n+from scipy import optimize\n+from statsmodels.discrete.discrete_model import Logit\n+from statsmodels.genmod.generalized_linear_model import (GLM, GLMResults,\n+    GLMResultsWrapper, lm, _check_convergence)\n+import statsmodels.regression._tools as reg_tools\n+from statsmodels.tools.sm_exceptions import PerfectSeparationError\n+from statsmodels.tools.decorators import cache_readonly\n+from statsmodels.base._penalized import PenalizedMixin\n+from statsmodels.gam.gam_penalties import MultivariateGamPenalty\n+from statsmodels.tools.linalg import matrix_sqrt\n+\n+\n+class GLMGAMResults(GLMResults):\n+    \"\"\"Results class for generalized additive models, GAM.\n+\n+    This inherits from GLMResults.\n+\n+    Warning: not all inherited methods might take correctly account of the\n+    penalization\n+\n+    GLMGAMResults inherits from GLMResults\n+\n+    Extra Attributes\n+    ----------------\n+    edf : list of effective degrees of freedom for each column of the\n+        design matrix.\n+\n+    hat_matrix_diag : diagonal of hat matrix\n+\n+    gcv : generalized cross-validation criterion computed as\n+        `gcv = scale / (1. - hat_matrix_trace / self.nobs)**2`\n+\n+    cv : cross-validation criterion computed as\n+        cv = ((resid_pearson / (1. - hat_matrix_diag))**2).sum() / self.nobs\n+\n+    Notes\n+    -----\n+    status: experimental\n+\n+    \"\"\"\n+\n+    def __init__(self, model, params, normalized_cov_params, scale, **kwds):\n+\n+        # this is a messy way to compute edf and update scale\n+        # need several attributes to compute edf\n+        self.model = model\n+        self.params = params\n+        self.normalized_cov_params = normalized_cov_params\n+        self.scale = scale\n+        edf = self.edf.sum()\n+        self.df_model = edf - 1  # assume constant\n+        # need to use nobs or wnobs attribute\n+        self.df_resid = self.df_resid = self.model.endog.shape[0] - edf\n+\n+        # we are setting the model df for the case when super is using it\n+        # df in model will be incorrect state when alpha/pen_weight changes\n+        self.model.df_model = self.df_model\n+        self.model.df_resid = self.df_resid\n+        mu = self.fittedvalues\n+        self.scale = scale = self.model.estimate_scale(mu)\n+        super(GLMGAMResults, self).__init__(model, params,\n+                                            normalized_cov_params, scale,\n+                                            **kwds)\n+\n+    def _tranform_predict_exog(self, exog=None, x=None, transform=False):\n+        \"\"\"Transform original explanatory variables for prediction\n+\n+        Parameters\n+        ----------\n+        exog : array_like, optional\n+            The values for the linear explanatory variables.\n+        x : array_like\n+            values for the variables in the smooth terms\n+        transform : bool, optional\n+            If transform is True, then the basis representation of the smooth\n+            term will be constructed from the provided ``x``.\n+\n+        Returns\n+        -------\n+        exog_transformed : ndarray\n+            design matrix for the prediction\n+\n+        \"\"\"\n+        if transform is False:\n+            ex = exog\n+        else:\n+            if x is not None:\n+                exog_smooth = self.model.smoother.transform(x)\n+                if exog is None:\n+                    ex = exog_smooth\n+                else:\n+                    ex = np.column_stack((exog, exog_smooth))\n+            else:\n+                ex = exog\n+\n+        return ex\n+\n+    def predict(self, exog=None, x=None, transform=True, **kwds):\n+        \"\"\"\"compute prediction\n+\n+        Parameters\n+        ----------\n+        exog : array_like, optional\n+            The values for the linear explanatory variables\n+        x : array_like\n+            values for the variables in the smooth terms\n+        transform : bool, optional\n+            If transform is True, then the basis representation of the smooth\n+            term will be constructed from the provided ``x``.\n+        kwargs :\n+            Some models can take additional arguments or keywords, see the\n+            predict method of the model for the details.\n+\n+        Returns\n+        -------\n+        prediction : ndarray, pandas.Series or pandas.DataFrame\n+            predicted values\n+        \"\"\"\n+        ex = self._tranform_predict_exog(exog=exog, x=x, transform=transform)\n+        return super(GLMGAMResults, self).predict(ex, **kwds)\n+\n+    def get_prediction(self, exog=None, x=None, transform=True, **kwds):\n+        \"\"\"compute prediction results\n+\n+        Parameters\n+        ----------\n+        exog : array_like, optional\n+            The values for which you want to predict.\n+        x : array_like\n+            values for the variables in the smooth terms\n+        transform : bool, optional\n+            If transform is True, then the basis representation of the smooth\n+            term will be constructed from the provided ``x``.\n+        kwargs :\n+            Some models can take additional arguments or keywords, see the\n+            predict method of the model for the details.\n+\n+        Returns\n+        -------\n+        prediction_results : generalized_linear_model.PredictionResults\n+            The prediction results instance contains prediction and prediction\n+            variance and can on demand calculate confidence intervals and summary\n+            tables for the prediction of the mean and of new observations.\n+\n+        \"\"\"\n+        ex = self._tranform_predict_exog(exog=exog, x=x, transform=transform)\n+        return super(GLMGAMResults, self).get_prediction(ex, transform=True,\n+                                                         **kwds)\n+\n+    def partial_values(self, smooth_index, include_constant=True):\n+        \"\"\"contribution of a smooth term to the linear prediction\n+\n+        Warning: This will be replaced by a predict method\n+\n+        Parameters\n+        ----------\n+        smooth_index : int\n+            index of the smooth term within list of smooth terms\n+        include_constant : bool\n+            If true, then the estimated intercept is added to the prediction\n+            and its standard errors. This avoids that the confidence interval\n+            has zero width at the imposed identification constraint, e.g.\n+            either at a reference point or at the mean.\n+\n+        Returns\n+        -------\n+        predicted : nd_array\n+            predicted value of linear term.\n+            This is not the expected response if the link function is not\n+            linear.\n+        se_pred : nd_array\n+            standard error of linear prediction\n+\n+        \"\"\"\n+        variable = smooth_index\n+        smoother = self.model.smoother\n+        mask = smoother.mask[variable]\n+\n+        start_idx = self.model.k_exog_linear\n+        idx = start_idx + np.nonzero(mask)[0]\n+\n+        # smoother has only smooth parts, not exog_linear\n+        exog_part = smoother.basis[:, mask]\n+\n+        const_idx = self.model.data.const_idx\n+        if include_constant and const_idx is not None:\n+            idx = np.concatenate(([const_idx], idx))\n+            exog_part = self.model.exog[:, idx]\n+\n+        linpred = np.dot(exog_part, self.params[idx])\n+        # select the submatrix corresponding to a single variable\n+        partial_cov_params = self.cov_params(column=idx)\n+\n+        covb = partial_cov_params\n+        var = (exog_part * np.dot(covb, exog_part.T).T).sum(1)\n+        se = np.sqrt(var)\n+\n+        return linpred, se\n+\n+    def plot_partial(self, smooth_index, plot_se=True, cpr=False,\n+                     include_constant=True, ax=None):\n+        \"\"\"plot the contribution of a smooth term to the linear prediction\n+\n+        Parameters\n+        ----------\n+        smooth_index : int\n+            index of the smooth term within list of smooth terms\n+        plot_se : book\n+            If plot_se is true, then the confidence interval for the linear\n+            prediction will be added to the plot.\n+        cpr : bool\n+            If cpr (component plus residual) is true, the a scatter plot of\n+            the partial residuals will be added to the plot.\n+        include_constant : bool\n+            If true, then the estimated intercept is added to the prediction\n+            and its standard errors. This avoids that the confidence interval\n+            has zero width at the imposed identification constraint, e.g.\n+            either at a reference point or at the mean.\n+        ax : None or matplotlib axis instance\n+           If ax is not None, then the plot will be added to it.\n+\n+        Returns\n+        -------\n+        fig : matplotlib figure instance\n+\n+        \"\"\"\n+        from statsmodels.graphics.utils import _import_mpl, create_mpl_ax\n+        _import_mpl()\n+\n+        variable = smooth_index\n+        y_est, se = self.partial_values(variable,\n+                                        include_constant=include_constant)\n+        smoother = self.model.smoother\n+        x = smoother.smoothers[variable].x\n+        sort_index = np.argsort(x)\n+        x = x[sort_index]\n+        y_est = y_est[sort_index]\n+        se = se[sort_index]\n+\n+        fig, ax = create_mpl_ax(ax)\n+        ax.plot(x, y_est, c='blue', lw=2)\n+        if plot_se:\n+            ax.plot(x, y_est + 1.96 * se, '-', c='blue')\n+            ax.plot(x, y_est - 1.96 * se, '-', c='blue')\n+        if cpr:\n+            # TODO: resid_response doesn't make sense with nonlinear link\n+            # use resid_working ?\n+            cpr_ = y_est + self.resid_working\n+            ax.plot(x, cpr_, '.', lw=2)\n+\n+        ax.set_xlabel(smoother.smoothers[variable].variable_name)\n+\n+        return fig\n+\n+    def test_significance(self, smooth_index):\n+        \"\"\"hypothesis test that a smooth component is zero.\n+\n+        This calls `wald_test` to compute the hypothesis test, but uses\n+        effective degrees of freedom.\n+\n+        Parameters\n+        ----------\n+        smooth_index : int\n+            index of the smooth term within list of smooth terms\n+\n+        Returns\n+        -------\n+        wald_test : ContrastResults instance\n+            the results instance created by `wald_test`\n+        \"\"\"\n+\n+        variable = smooth_index\n+        smoother = self.model.smoother\n+        start_idx = self.model.k_exog_linear\n+\n+        k_params = len(self.params)\n+        # a bit messy, we need first index plus length of smooth term\n+        mask = smoother.mask[variable]\n+        k_constraints = mask.sum()\n+        idx = start_idx + np.nonzero(mask)[0][0]\n+        constraints = np.eye(k_constraints, k_params, idx)\n+        df_constraints = self.edf[idx : idx + k_constraints].sum()\n+\n+        return self.wald_test(constraints, df_constraints=df_constraints)\n+\n+    def get_hat_matrix_diag(self, observed=True, _axis=1):\n+        \"\"\"\n+        Compute the diagonal of the hat matrix\n+\n+        Parameters\n+        ----------\n+        observed : bool\n+            If true, then observed hessian is used in the hat matrix\n+            computation. If false, then the expected hessian is used.\n+            In the case of a canonical link function both are the same.\n+\n+        Returns\n+        -------\n+        hat_matrix_diag : ndarray\n+            The diagonal of the hat matrix computed from the observed\n+            or expected hessian.\n+        \"\"\"\n+        weights = self.model.hessian_factor(self.params, observed=observed)\n+        wexog = np.sqrt(weights)[:, None] * self.model.exog\n+        # Note we needed to add a factor 2 in penalized_wls\n+        # pencov = 2 * self.model.penal.penalty_matrix(alpha=self.alpha)\n+\n+        # we can use inverse hessian directly instead of computing it from\n+        # WLS/IRLS as in GLM\n+\n+        # TODO: does `normalized_cov_params * scale` work in all cases?\n+        # avoids recomputing hessian\n+        hess_inv = self.normalized_cov_params * self.scale\n+        # hess_inv = np.linalg.inv(-self.model.hessian(self.params))\n+        hd = (wexog * hess_inv.dot(wexog.T).T).sum(axis=_axis)\n+        return hd\n+\n+    @cache_readonly\n+    def edf(self):\n+        return self.get_hat_matrix_diag(_axis=0)\n+\n+    @cache_readonly\n+    def hat_matrix_trace(self):\n+        return self.hat_matrix_diag.sum()\n+\n+    @cache_readonly\n+    def hat_matrix_diag(self):\n+        return self.get_hat_matrix_diag(observed=True)\n+\n+    @cache_readonly\n+    def gcv(self):\n+        return self.scale / (1. - self.hat_matrix_trace / self.nobs)**2\n+\n+    @cache_readonly\n+    def cv(self):\n+        cv_ = ((self.resid_pearson / (1. - self.hat_matrix_diag))**2).sum()\n+        cv_ /= self.nobs\n+        return cv_\n+\n+\n+class GLMGam(PenalizedMixin, GLM):\n+    \"\"\"Model class for generalized additive models, GAM.\n+\n+    This inherits from `GLM`.\n+\n+    Warning: Not all inherited methods might take correctly account of the\n+    penalization. Not all options including offset and exposure have been\n+    verified yet.\n+\n+    Parameters\n+    ----------\n+    endog : array_like\n+    exog : array_like or None,\n+    smoother : instance of additive smoother class\n+        required keyword argument\n+    alpha : list of floats\n+        penalization weights for smooth terms. The length of the list needs\n+        to be the same as the number of smooth terms in the ``smoother``\n+    family : instance of GLM family\n+        see GLM\n+    offset : None or array_like\n+        see GLM\n+    exposure : None or array_like\n+        see GLM\n+    missing : 'none'\n+        missing value handling is not supported in this class\n+    kwargs :\n+        extra keywords are used in call to the super classes.\n+\n+    Notes\n+    -----\n+    Status: experimental. This has full unit test coverage for the core\n+    results with Gaussian and Poisson (without offset and exposure). Other\n+    options and additional results might not be correctly supported yet.\n+    (Binomial with counts, i.e. with n_trials, is most likely wrong in pirls.\n+    User specified var or freq weights are most likely also not correct for\n+    all results.)\n+\n+    \"\"\"\n+\n+    _results_class = GLMGAMResults\n+\n+    def __init__(self, endog, exog=None, smoother=None, alpha=0, family=None,\n+                 offset=None, exposure=None, missing='none', **kwargs):\n+\n+        if exog is not None:\n+            exog_linear = np.asarray(exog)\n+            k_exog_linear = exog_linear.shape[1]\n+        else:\n+            exog_linear = None\n+            k_exog_linear = 0\n+        self.k_exog_linear = k_exog_linear\n+\n+        self.smoother = smoother\n+        self.k_smooths = smoother.k_variables\n+        self.alpha = self._check_alpha(alpha)\n+        penal = MultivariateGamPenalty(smoother, alpha=self.alpha,\n+                                       start_idx=k_exog_linear)\n+        kwargs.pop('penal', None)\n+        if exog_linear is not None:\n+            exog = np.column_stack((exog_linear, smoother.basis))\n+        else:\n+            exog = smoother.basis\n+        super(GLMGam, self).__init__(endog, exog=exog, family=family,\n+                                     offset=offset, exposure=exposure,\n+                                     penal=penal, missing=missing, **kwargs)\n+\n+    def _check_alpha(self, alpha):\n+        \"\"\"check and convert alpha to required list format\n+\n+        Parameters\n+        ----------\n+        alpha : scalar, list or array-like\n+            penalization weight\n+\n+        Returns\n+        ----------\n+        alpha : list\n+            penalization weight, list with length equal to the number of\n+            smooth terms\n+\n+        \"\"\"\n+        import collections\n+        if not isinstance(alpha, collections.Iterable):\n+            alpha = [alpha] * len(self.smoother.smoothers)\n+        elif not isinstance(alpha, list):\n+            # we want alpha to be a list\n+            alpha = list(alpha)\n+        return alpha\n+\n+    def fit(self, start_params=None, maxiter=1000, method='PIRLS', tol=1e-8,\n+            scale=None, cov_type='nonrobust', cov_kwds=None, use_t=None,\n+            full_output=True, disp=False, max_start_irls=3, **kwargs):\n+        \"\"\"estimate parameters and create instance of GLMGAMResults class\n+\n+        Parameters\n+        ----------\n+        most parameters are the same as for GLM\n+        method : optimization method\n+            The special optimization method is \"pirls\" which uses a penalized\n+            version of IRLS. Other methods are gradient optimizers as used in\n+            base.model.LikelihoodModel.\n+\n+        Returns\n+        -------\n+        res : instance of GLMGAMResults\n+        \"\"\"\n+        # TODO: alpha not allowed yet, but is in `_fit_pirls`\n+        # alpha = self._check_alpha()\n+\n+        if method.lower() == 'pirls':\n+            res = self._fit_pirls(self.alpha, start_params=start_params,\n+                                  maxiter=maxiter, tol=tol, scale=scale,\n+                                  cov_type=cov_type, cov_kwds=cov_kwds,\n+                                  use_t=use_t, **kwargs)\n+        else:\n+            res = super(GLMGam, self).fit(start_params=start_params,\n+                                          maxiter=maxiter, method=method,\n+                                          tol=tol, scale=scale,\n+                                          cov_type=cov_type, cov_kwds=cov_kwds,\n+                                          use_t=use_t,\n+                                          full_output=full_output, disp=disp,\n+                                          max_start_irls=max_start_irls,\n+                                          **kwargs)\n+        return res\n+\n+    # pag 165 4.3 # pag 136 PIRLS\n+    def _fit_pirls(self, alpha, start_params=None, maxiter=100, tol=1e-8,\n+                   scale=None, cov_type='nonrobust', cov_kwds=None, use_t=None,\n+                   weights=None):\n+        \"\"\"fit model with penalized reweighted least squares\n+\n+        \"\"\"\n+\n+        # alpha = alpha * len(y) * self.scale / 100\n+        # TODO: we need to rescale alpha\n+        endog = self.endog\n+        k_exog_linear = self.k_exog_linear\n+        wlsexog = self.exog  # smoother.basis\n+        spl_s = self.penal.penalty_matrix(alpha=alpha)\n+\n+        nobs, n_columns = wlsexog.shape\n+\n+        # TODO what are these values?\n+        if weights is None:\n+            self.data_weights = np.array([1.] * nobs)\n+        else:\n+            self.data_weights = weights\n+\n+        if not hasattr(self, '_offset_exposure'):\n+            self._offset_exposure = 0\n+\n+        self.scaletype = scale\n+        # TODO: check default scale types\n+        # self.scaletype = 'dev'\n+        # during iteration\n+        self.scale = 1\n+\n+        if start_params is None:\n+            mu = self.family.starting_mu(endog)\n+            lin_pred = self.family.predict(mu)\n+        else:\n+            lin_pred = np.dot(wlsexog, start_params) + self._offset_exposure\n+            mu = self.family.fitted(lin_pred)\n+        dev = self.family.deviance(endog, mu)\n+\n+        history = dict(params=[None, start_params], deviance=[np.inf, dev])\n+        converged = False\n+        criterion = history['deviance']\n+        # This special case is used to get the likelihood for a specific\n+        # params vector.\n+        if maxiter == 0:\n+            mu = self.family.fitted(lin_pred)\n+            self.scale = self.estimate_scale(mu)\n+            wls_results = lm.RegressionResults(self, start_params, None)\n+            iteration = 0\n+\n+        for iteration in range(maxiter):\n+\n+            # TODO: is this equivalent to point 1 of page 136:\n+            # w = 1 / (V(mu) * g'(mu))  ?\n+            self.weights = self.data_weights * self.family.weights(mu)\n+\n+            # TODO: is this equivalent to point 1 of page 136:\n+            # z = g(mu)(y - mu) + X beta  ?\n+            wlsendog = (lin_pred + self.family.link.deriv(mu) * (endog - mu)\n+                        - self._offset_exposure)\n+\n+            # this defines the augmented matrix point 2a on page 136\n+            wls_results = penalized_wls(wlsendog, wlsexog, spl_s, self.weights)\n+            lin_pred = np.dot(wlsexog, wls_results.params).ravel()\n+            lin_pred += self._offset_exposure\n+            mu = self.family.fitted(lin_pred)\n+\n+            # self.scale = self.estimate_scale(mu)\n+            history = self._update_history(wls_results, mu, history)\n+\n+            if endog.squeeze().ndim == 1 and np.allclose(mu - endog, 0):\n+                msg = \"Perfect separation detected, results not available\"\n+                raise PerfectSeparationError(msg)\n+\n+            # TODO need atol, rtol\n+            converged = _check_convergence(criterion, iteration, tol, 0)\n+            if converged:\n+                break\n+        self.mu = mu\n+        self.scale = self.estimate_scale(mu)\n+        glm_results = GLMGAMResults(self, wls_results.params,\n+                                    wls_results.normalized_cov_params,\n+                                    self.scale,\n+                                    cov_type=cov_type, cov_kwds=cov_kwds,\n+                                    use_t=use_t)\n+\n+        glm_results.method = \"PIRLS\"","path":"statsmodels/gam/gam.py","position":566,"original_position":566,"commit_id":"d7d5b847ddc4625390a7eb6e61d2095fdd1b3dbc","original_commit_id":"d7d5b847ddc4625390a7eb6e61d2095fdd1b3dbc","user":{"login":"jbrockmendel","id":8078968,"node_id":"MDQ6VXNlcjgwNzg5Njg=","avatar_url":"https://avatars1.githubusercontent.com/u/8078968?v=4","gravatar_id":"","url":"https://api.github.com/users/jbrockmendel","html_url":"https://github.com/jbrockmendel","followers_url":"https://api.github.com/users/jbrockmendel/followers","following_url":"https://api.github.com/users/jbrockmendel/following{/other_user}","gists_url":"https://api.github.com/users/jbrockmendel/gists{/gist_id}","starred_url":"https://api.github.com/users/jbrockmendel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jbrockmendel/subscriptions","organizations_url":"https://api.github.com/users/jbrockmendel/orgs","repos_url":"https://api.github.com/users/jbrockmendel/repos","events_url":"https://api.github.com/users/jbrockmendel/events{/privacy}","received_events_url":"https://api.github.com/users/jbrockmendel/received_events","type":"User","site_admin":false},"body":"Can any of these be set in `GLMGAMResults.__init__`?","created_at":"2018-12-08T21:33:38Z","updated_at":"2018-12-08T21:33:38Z","html_url":"https://github.com/statsmodels/statsmodels/pull/5370#discussion_r240017286","pull_request_url":"https://api.github.com/repos/statsmodels/statsmodels/pulls/5370","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/statsmodels/statsmodels/pulls/comments/240017286"},"html":{"href":"https://github.com/statsmodels/statsmodels/pull/5370#discussion_r240017286"},"pull_request":{"href":"https://api.github.com/repos/statsmodels/statsmodels/pulls/5370"}}},"pull_request":{"url":"https://api.github.com/repos/statsmodels/statsmodels/pulls/5370","id":228223262,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI4MjIzMjYy","html_url":"https://github.com/statsmodels/statsmodels/pull/5370","diff_url":"https://github.com/statsmodels/statsmodels/pull/5370.diff","patch_url":"https://github.com/statsmodels/statsmodels/pull/5370.patch","issue_url":"https://api.github.com/repos/statsmodels/statsmodels/issues/5370","number":5370,"state":"open","locked":false,"title":"WIP/ENH Gam 2744 rebased3","user":{"login":"josef-pkt","id":440735,"node_id":"MDQ6VXNlcjQ0MDczNQ==","avatar_url":"https://avatars1.githubusercontent.com/u/440735?v=4","gravatar_id":"","url":"https://api.github.com/users/josef-pkt","html_url":"https://github.com/josef-pkt","followers_url":"https://api.github.com/users/josef-pkt/followers","following_url":"https://api.github.com/users/josef-pkt/following{/other_user}","gists_url":"https://api.github.com/users/josef-pkt/gists{/gist_id}","starred_url":"https://api.github.com/users/josef-pkt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/josef-pkt/subscriptions","organizations_url":"https://api.github.com/users/josef-pkt/orgs","repos_url":"https://api.github.com/users/josef-pkt/repos","events_url":"https://api.github.com/users/josef-pkt/events{/privacy}","received_events_url":"https://api.github.com/users/josef-pkt/received_events","type":"User","site_admin":false},"body":"continuation of #5296 after rebase\r\n\r\ncommits for outside gam/penalized\r\n\r\n- BUG/REF: fix GLM cov_params in fit_gradient, see #5316 \r\n- REF: GLM protect against hessian inversion failure, use rtol in a test\r\n- BUG: RegressionResults attach use_t if not None, see #5331\r\n- BUG: correct GLM.estimate_scale using deviance\r\n- ENH/REF: contrast results: string for float df_num, df_denom\r\n- fix contrast summary formatting\r\n- ENH: base LikelihoodModelResults.wald_test add df_constraints kwd\r\n\r\n","created_at":"2018-11-05T02:10:43Z","updated_at":"2018-12-08T21:33:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"43e0e610ee5ec572ae5eba6e879252ca16cd3991","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":1098095765,"node_id":"MDU6TGFiZWwxMDk4MDk1NzY1","url":"https://api.github.com/repos/statsmodels/statsmodels/labels/comp-gam","name":"comp-gam","color":"ededed","default":false},{"id":142961,"node_id":"MDU6TGFiZWwxNDI5NjE=","url":"https://api.github.com/repos/statsmodels/statsmodels/labels/type-enh","name":"type-enh","color":"99ffff","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/statsmodels/statsmodels/pulls/5370/commits","review_comments_url":"https://api.github.com/repos/statsmodels/statsmodels/pulls/5370/comments","review_comment_url":"https://api.github.com/repos/statsmodels/statsmodels/pulls/comments{/number}","comments_url":"https://api.github.com/repos/statsmodels/statsmodels/issues/5370/comments","statuses_url":"https://api.github.com/repos/statsmodels/statsmodels/statuses/d7d5b847ddc4625390a7eb6e61d2095fdd1b3dbc","head":{"label":"josef-pkt:gam_2744_rebased3","ref":"gam_2744_rebased3","sha":"d7d5b847ddc4625390a7eb6e61d2095fdd1b3dbc","user":{"login":"josef-pkt","id":440735,"node_id":"MDQ6VXNlcjQ0MDczNQ==","avatar_url":"https://avatars1.githubusercontent.com/u/440735?v=4","gravatar_id":"","url":"https://api.github.com/users/josef-pkt","html_url":"https://github.com/josef-pkt","followers_url":"https://api.github.com/users/josef-pkt/followers","following_url":"https://api.github.com/users/josef-pkt/following{/other_user}","gists_url":"https://api.github.com/users/josef-pkt/gists{/gist_id}","starred_url":"https://api.github.com/users/josef-pkt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/josef-pkt/subscriptions","organizations_url":"https://api.github.com/users/josef-pkt/orgs","repos_url":"https://api.github.com/users/josef-pkt/repos","events_url":"https://api.github.com/users/josef-pkt/events{/privacy}","received_events_url":"https://api.github.com/users/josef-pkt/received_events","type":"User","site_admin":false},"repo":{"id":1885313,"node_id":"MDEwOlJlcG9zaXRvcnkxODg1MzEz","name":"statsmodels","full_name":"josef-pkt/statsmodels","private":false,"owner":{"login":"josef-pkt","id":440735,"node_id":"MDQ6VXNlcjQ0MDczNQ==","avatar_url":"https://avatars1.githubusercontent.com/u/440735?v=4","gravatar_id":"","url":"https://api.github.com/users/josef-pkt","html_url":"https://github.com/josef-pkt","followers_url":"https://api.github.com/users/josef-pkt/followers","following_url":"https://api.github.com/users/josef-pkt/following{/other_user}","gists_url":"https://api.github.com/users/josef-pkt/gists{/gist_id}","starred_url":"https://api.github.com/users/josef-pkt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/josef-pkt/subscriptions","organizations_url":"https://api.github.com/users/josef-pkt/orgs","repos_url":"https://api.github.com/users/josef-pkt/repos","events_url":"https://api.github.com/users/josef-pkt/events{/privacy}","received_events_url":"https://api.github.com/users/josef-pkt/received_events","type":"User","site_admin":false},"html_url":"https://github.com/josef-pkt/statsmodels","description":"main repo of statsmodels","fork":true,"url":"https://api.github.com/repos/josef-pkt/statsmodels","forks_url":"https://api.github.com/repos/josef-pkt/statsmodels/forks","keys_url":"https://api.github.com/repos/josef-pkt/statsmodels/keys{/key_id}","collaborators_url":"https://api.github.com/repos/josef-pkt/statsmodels/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/josef-pkt/statsmodels/teams","hooks_url":"https://api.github.com/repos/josef-pkt/statsmodels/hooks","issue_events_url":"https://api.github.com/repos/josef-pkt/statsmodels/issues/events{/number}","events_url":"https://api.github.com/repos/josef-pkt/statsmodels/events","assignees_url":"https://api.github.com/repos/josef-pkt/statsmodels/assignees{/user}","branches_url":"https://api.github.com/repos/josef-pkt/statsmodels/branches{/branch}","tags_url":"https://api.github.com/repos/josef-pkt/statsmodels/tags","blobs_url":"https://api.github.com/repos/josef-pkt/statsmodels/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/josef-pkt/statsmodels/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/josef-pkt/statsmodels/git/refs{/sha}","trees_url":"https://api.github.com/repos/josef-pkt/statsmodels/git/trees{/sha}","statuses_url":"https://api.github.com/repos/josef-pkt/statsmodels/statuses/{sha}","languages_url":"https://api.github.com/repos/josef-pkt/statsmodels/languages","stargazers_url":"https://api.github.com/repos/josef-pkt/statsmodels/stargazers","contributors_url":"https://api.github.com/repos/josef-pkt/statsmodels/contributors","subscribers_url":"https://api.github.com/repos/josef-pkt/statsmodels/subscribers","subscription_url":"https://api.github.com/repos/josef-pkt/statsmodels/subscription","commits_url":"https://api.github.com/repos/josef-pkt/statsmodels/commits{/sha}","git_commits_url":"https://api.github.com/repos/josef-pkt/statsmodels/git/commits{/sha}","comments_url":"https://api.github.com/repos/josef-pkt/statsmodels/comments{/number}","issue_comment_url":"https://api.github.com/repos/josef-pkt/statsmodels/issues/comments{/number}","contents_url":"https://api.github.com/repos/josef-pkt/statsmodels/contents/{+path}","compare_url":"https://api.github.com/repos/josef-pkt/statsmodels/compare/{base}...{head}","merges_url":"https://api.github.com/repos/josef-pkt/statsmodels/merges","archive_url":"https://api.github.com/repos/josef-pkt/statsmodels/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/josef-pkt/statsmodels/downloads","issues_url":"https://api.github.com/repos/josef-pkt/statsmodels/issues{/number}","pulls_url":"https://api.github.com/repos/josef-pkt/statsmodels/pulls{/number}","milestones_url":"https://api.github.com/repos/josef-pkt/statsmodels/milestones{/number}","notifications_url":"https://api.github.com/repos/josef-pkt/statsmodels/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/josef-pkt/statsmodels/labels{/name}","releases_url":"https://api.github.com/repos/josef-pkt/statsmodels/releases{/id}","deployments_url":"https://api.github.com/repos/josef-pkt/statsmodels/deployments","created_at":"2011-06-12T17:30:37Z","updated_at":"2018-07-13T08:47:55Z","pushed_at":"2018-11-25T19:00:49Z","git_url":"git://github.com/josef-pkt/statsmodels.git","ssh_url":"git@github.com:josef-pkt/statsmodels.git","clone_url":"https://github.com/josef-pkt/statsmodels.git","svn_url":"https://github.com/josef-pkt/statsmodels","homepage":"http://www.statsmodels.org/devel","size":32318,"stargazers_count":17,"watchers_count":17,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":20,"mirror_url":null,"archived":false,"open_issues_count":2,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":20,"open_issues":2,"watchers":17,"default_branch":"master"}},"base":{"label":"statsmodels:master","ref":"master","sha":"4a79c284843eb2ae38d16bbadcd542cd92f995cf","user":{"login":"statsmodels","id":717666,"node_id":"MDEyOk9yZ2FuaXphdGlvbjcxNzY2Ng==","avatar_url":"https://avatars3.githubusercontent.com/u/717666?v=4","gravatar_id":"","url":"https://api.github.com/users/statsmodels","html_url":"https://github.com/statsmodels","followers_url":"https://api.github.com/users/statsmodels/followers","following_url":"https://api.github.com/users/statsmodels/following{/other_user}","gists_url":"https://api.github.com/users/statsmodels/gists{/gist_id}","starred_url":"https://api.github.com/users/statsmodels/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/statsmodels/subscriptions","organizations_url":"https://api.github.com/users/statsmodels/orgs","repos_url":"https://api.github.com/users/statsmodels/repos","events_url":"https://api.github.com/users/statsmodels/events{/privacy}","received_events_url":"https://api.github.com/users/statsmodels/received_events","type":"Organization","site_admin":false},"repo":{"id":1885237,"node_id":"MDEwOlJlcG9zaXRvcnkxODg1MjM3","name":"statsmodels","full_name":"statsmodels/statsmodels","private":false,"owner":{"login":"statsmodels","id":717666,"node_id":"MDEyOk9yZ2FuaXphdGlvbjcxNzY2Ng==","avatar_url":"https://avatars3.githubusercontent.com/u/717666?v=4","gravatar_id":"","url":"https://api.github.com/users/statsmodels","html_url":"https://github.com/statsmodels","followers_url":"https://api.github.com/users/statsmodels/followers","following_url":"https://api.github.com/users/statsmodels/following{/other_user}","gists_url":"https://api.github.com/users/statsmodels/gists{/gist_id}","starred_url":"https://api.github.com/users/statsmodels/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/statsmodels/subscriptions","organizations_url":"https://api.github.com/users/statsmodels/orgs","repos_url":"https://api.github.com/users/statsmodels/repos","events_url":"https://api.github.com/users/statsmodels/events{/privacy}","received_events_url":"https://api.github.com/users/statsmodels/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/statsmodels/statsmodels","description":"Statsmodels: statistical modeling and econometrics in Python","fork":false,"url":"https://api.github.com/repos/statsmodels/statsmodels","forks_url":"https://api.github.com/repos/statsmodels/statsmodels/forks","keys_url":"https://api.github.com/repos/statsmodels/statsmodels/keys{/key_id}","collaborators_url":"https://api.github.com/repos/statsmodels/statsmodels/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/statsmodels/statsmodels/teams","hooks_url":"https://api.github.com/repos/statsmodels/statsmodels/hooks","issue_events_url":"https://api.github.com/repos/statsmodels/statsmodels/issues/events{/number}","events_url":"https://api.github.com/repos/statsmodels/statsmodels/events","assignees_url":"https://api.github.com/repos/statsmodels/statsmodels/assignees{/user}","branches_url":"https://api.github.com/repos/statsmodels/statsmodels/branches{/branch}","tags_url":"https://api.github.com/repos/statsmodels/statsmodels/tags","blobs_url":"https://api.github.com/repos/statsmodels/statsmodels/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/statsmodels/statsmodels/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/statsmodels/statsmodels/git/refs{/sha}","trees_url":"https://api.github.com/repos/statsmodels/statsmodels/git/trees{/sha}","statuses_url":"https://api.github.com/repos/statsmodels/statsmodels/statuses/{sha}","languages_url":"https://api.github.com/repos/statsmodels/statsmodels/languages","stargazers_url":"https://api.github.com/repos/statsmodels/statsmodels/stargazers","contributors_url":"https://api.github.com/repos/statsmodels/statsmodels/contributors","subscribers_url":"https://api.github.com/repos/statsmodels/statsmodels/subscribers","subscription_url":"https://api.github.com/repos/statsmodels/statsmodels/subscription","commits_url":"https://api.github.com/repos/statsmodels/statsmodels/commits{/sha}","git_commits_url":"https://api.github.com/repos/statsmodels/statsmodels/git/commits{/sha}","comments_url":"https://api.github.com/repos/statsmodels/statsmodels/comments{/number}","issue_comment_url":"https://api.github.com/repos/statsmodels/statsmodels/issues/comments{/number}","contents_url":"https://api.github.com/repos/statsmodels/statsmodels/contents/{+path}","compare_url":"https://api.github.com/repos/statsmodels/statsmodels/compare/{base}...{head}","merges_url":"https://api.github.com/repos/statsmodels/statsmodels/merges","archive_url":"https://api.github.com/repos/statsmodels/statsmodels/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/statsmodels/statsmodels/downloads","issues_url":"https://api.github.com/repos/statsmodels/statsmodels/issues{/number}","pulls_url":"https://api.github.com/repos/statsmodels/statsmodels/pulls{/number}","milestones_url":"https://api.github.com/repos/statsmodels/statsmodels/milestones{/number}","notifications_url":"https://api.github.com/repos/statsmodels/statsmodels/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/statsmodels/statsmodels/labels{/name}","releases_url":"https://api.github.com/repos/statsmodels/statsmodels/releases{/id}","deployments_url":"https://api.github.com/repos/statsmodels/statsmodels/deployments","created_at":"2011-06-12T17:04:50Z","updated_at":"2018-12-07T13:23:54Z","pushed_at":"2018-12-07T17:37:08Z","git_url":"git://github.com/statsmodels/statsmodels.git","ssh_url":"git@github.com:statsmodels/statsmodels.git","clone_url":"https://github.com/statsmodels/statsmodels.git","svn_url":"https://github.com/statsmodels/statsmodels","homepage":"http://www.statsmodels.org/devel/","size":30646,"stargazers_count":3443,"watchers_count":3443,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1453,"mirror_url":null,"archived":false,"open_issues_count":2100,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":1453,"open_issues":2100,"watchers":3443,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/statsmodels/statsmodels/pulls/5370"},"html":{"href":"https://github.com/statsmodels/statsmodels/pull/5370"},"issue":{"href":"https://api.github.com/repos/statsmodels/statsmodels/issues/5370"},"comments":{"href":"https://api.github.com/repos/statsmodels/statsmodels/issues/5370/comments"},"review_comments":{"href":"https://api.github.com/repos/statsmodels/statsmodels/pulls/5370/comments"},"review_comment":{"href":"https://api.github.com/repos/statsmodels/statsmodels/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/statsmodels/statsmodels/pulls/5370/commits"},"statuses":{"href":"https://api.github.com/repos/statsmodels/statsmodels/statuses/d7d5b847ddc4625390a7eb6e61d2095fdd1b3dbc"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"jbrockmendel"}}