Beispiel #1
0
    def _fit_mle(self):
        """Fits MLE of the common part of the model.

        This used to be called in the class instantiation, but there is no need to fit the GLM when
        there are no automatic priors. So this method is only called when needed.
        """
        missing = "drop" if self.model.dropna else "none"
        try:
            self.mle = GLM(
                endog=self.model.response.data,
                exog=self.dm,
                family=self.model.family.smfamily(self.model.family.smlink),
                missing=missing,
            ).fit()
        except PerfectSeparationError as error:
            msg = "Perfect separation detected, automatic priors are not available. "
            msg += "Please indicate priors manually."
            raise PerfectSeparationError(msg) from error
        except:
            print("Unexpected error:", sys.exc_info()[0])
            raise
Beispiel #2
0
    def _fit_pirls(self, alpha, start_params=None, maxiter=100, tol=1e-8,
                   scale=None, cov_type='nonrobust', cov_kwds=None, use_t=None,
                   weights=None):
        """fit model with penalized reweighted least squares

        """
        # TODO: this currently modifies several attributes
        # self.scale, self.scaletype, self.mu, self.weights
        # self.data_weights,
        # and possibly self._offset_exposure
        # several of those might not be necessary, e.g. mu and weights

        # alpha = alpha * len(y) * self.scale / 100
        # TODO: we need to rescale alpha
        endog = self.endog
        wlsexog = self.exog  # smoother.basis
        spl_s = self.penal.penalty_matrix(alpha=alpha)

        nobs, n_columns = wlsexog.shape

        # TODO what are these values?
        if weights is None:
            self.data_weights = np.array([1.] * nobs)
        else:
            self.data_weights = weights

        if not hasattr(self, '_offset_exposure'):
            self._offset_exposure = 0

        self.scaletype = scale
        # TODO: check default scale types
        # self.scaletype = 'dev'
        # during iteration
        self.scale = 1

        if start_params is None:
            mu = self.family.starting_mu(endog)
            lin_pred = self.family.predict(mu)
        else:
            lin_pred = np.dot(wlsexog, start_params) + self._offset_exposure
            mu = self.family.fitted(lin_pred)
        dev = self.family.deviance(endog, mu)

        history = dict(params=[None, start_params], deviance=[np.inf, dev])
        converged = False
        criterion = history['deviance']
        # This special case is used to get the likelihood for a specific
        # params vector.
        if maxiter == 0:
            mu = self.family.fitted(lin_pred)
            self.scale = self.estimate_scale(mu)
            wls_results = lm.RegressionResults(self, start_params, None)
            iteration = 0

        for iteration in range(maxiter):

            # TODO: is this equivalent to point 1 of page 136:
            # w = 1 / (V(mu) * g'(mu))  ?
            self.weights = self.data_weights * self.family.weights(mu)

            # TODO: is this equivalent to point 1 of page 136:
            # z = g(mu)(y - mu) + X beta  ?
            wlsendog = (lin_pred + self.family.link.deriv(mu) * (endog - mu)
                        - self._offset_exposure)

            # this defines the augmented matrix point 2a on page 136
            wls_results = penalized_wls(wlsendog, wlsexog, spl_s, self.weights)
            lin_pred = np.dot(wlsexog, wls_results.params).ravel()
            lin_pred += self._offset_exposure
            mu = self.family.fitted(lin_pred)

            # We don't need to update scale in GLM/LEF models
            # We might need it in dispersion models.
            # self.scale = self.estimate_scale(mu)
            history = self._update_history(wls_results, mu, history)

            if endog.squeeze().ndim == 1 and np.allclose(mu - endog, 0):
                msg = "Perfect separation detected, results not available"
                raise PerfectSeparationError(msg)

            # TODO need atol, rtol
            # args of _check_convergence: (criterion, iteration, atol, rtol)
            converged = _check_convergence(criterion, iteration, tol, 0)
            if converged:
                break
        self.mu = mu
        self.scale = self.estimate_scale(mu)
        glm_results = GLMGamResults(self, wls_results.params,
                                    wls_results.normalized_cov_params,
                                    self.scale,
                                    cov_type=cov_type, cov_kwds=cov_kwds,
                                    use_t=use_t)

        glm_results.method = "PIRLS"
        history['iteration'] = iteration + 1
        glm_results.fit_history = history
        glm_results.converged = converged

        return GLMGamResultsWrapper(glm_results)
Beispiel #3
0
    def _fit_irls_sparse(self,
                         start_params=None,
                         maxiter=50,
                         tol=1e-3,
                         scale=None,
                         cov_type='nonrobust',
                         cov_kwds=None,
                         use_t=None,
                         **kwargs):
        """Fit a GLM using IRLS.

        Fit a generalized linear mode (GLM) for a given family using
        iteratively reweighted least squares (IRLS).
        """
        if not scipy.sparse.issparse(self.exog):
            raise ValueError("Matrix not sparse")

        endog = self.endog
        wlsexog = self.exog

        if start_params is None:
            mu = self.family.starting_mu(self.endog)
            lin_pred = self.family.predict(mu)
        else:
            # This is a hack for a faster warm start
            start_params[start_params > 1e2] = 1e2
            start_params[start_params < -1e2] = -1e2
            lin_pred = wlsexog.dot(start_params) + self._offset_exposure
            mu = self.family.fitted(lin_pred)

        dev = self.family.deviance(self.endog[self.endog[:, 0] > 0, :],
                                   mu[self.endog[:, 0] > 0, :])
        if np.isnan(dev):
            if not (start_params is None):
                # This is a hack for a faster warm start
                start_params[start_params > 1e1] = 1e1
                start_params[start_params < -1e1] = -1e1
                lin_pred = wlsexog.dot(start_params) + self._offset_exposure
                mu = self.family.fitted(lin_pred)

                dev = self.family.deviance(self.endog, mu)

                if np.isnan(dev):

                    dump_path = os.path.join(
                        self.tmp_dir,
                        'tmpdump.' + time.asctime().replace(' ', '_') + '.dat')
                    pickle.dump([lin_pred, mu, endog, exog, start_params],
                                open(dump_path, 'w'))
                    raise ValueError(
                        "The first guess on the deviance function returned a "
                        " nan. This could be a boundary problem and should "
                        " be reported. Please try to restart omniCLIP. "
                        "Parameter dump at: " + dump_path)
            else:
                raise ValueError(
                    "The first guess on the deviance function returned a "
                    " nan. This could be a boundary problem and should "
                    " be reported. Please try to restart omniCLIP. ")

        # first guess on the deviance is assumed to be scaled by 1.
        # params are none to start, so they line up with the deviance
        history = dict(params=[None, start_params], deviance=[np.inf, dev])
        converged = False
        criterion = history['deviance']
        # This special case is used to get the likelihood for a specific
        # params vector.

        if maxiter == 0:
            pass
        for iteration in range(maxiter):
            self.weights = self.data_weights * self.family.weights(mu)
            wlsendog = (lin_pred + self.family.link.deriv(mu) *
                        (self.endog - mu) - self._offset_exposure)
            W = scipy.sparse.diags(self.weights[:, 0], 0)

            # Compute x for current interation
            temp_mat = wlsexog.transpose().dot(W)
            lu = sla.splu(csc_matrix(temp_mat.dot(wlsexog)))
            wls_results = lu.solve(temp_mat.dot(wlsendog))
            wls_results[wls_results > 1e2] = 1e2
            wls_results[wls_results < -1e2] = -1e2

            lin_pred = self.exog.dot(wls_results) + self._offset_exposure
            mu = self.family.fitted(lin_pred)
            history['mu'] = mu
            history['params'].append(wls_results)
            temp_endog = self.endog[:]
            temp_endog[temp_endog < 0] = 0
            history['deviance'].append(self.family.deviance(self.endog, mu))

            if endog.squeeze().ndim == 1 and np.allclose(mu - endog, 0):
                msg = "Perfect separation detected, results not available"
                raise PerfectSeparationError(msg)
            converged = _check_convergence(criterion, iteration, tol)
            if converged:
                break
        self.mu = mu

        history['iteration'] = iteration + 1

        return [wls_results, history]
    def fit(self, start_params=None, maxiter=100, method='IRLS', tol=1e-8,
            scale=None):
        """
        Fits a generalized linear model for a given family.

        parameters
        ----------
        maxiter : int, optional
            Default is 100.
        method : string
            Default is 'IRLS' for iteratively reweighted least squares.  This
            is currently the only method available for GLM fit.
        scale : string or float, optional
            `scale` can be 'X2', 'dev', or a float
            The default value is None, which uses `X2` for Gamma, Gaussian,
            and Inverse Gaussian.
            `X2` is Pearson's chi-square divided by `df_resid`.
            The default is 1 for the Binomial and Poisson families.
            `dev` is the deviance divided by df_resid
        tol : float
            Convergence tolerance.  Default is 1e-8.
        start_params : array-like, optional
            Initial guess of the solution for the loglikelihood maximization.
            The default is family-specific and is given by the
            ``family.starting_mu(endog)``. If start_params is given then the
            initial mean will be calculated as ``np.dot(exog, start_params)``.
        """
        endog = self.endog
        if endog.ndim > 1 and endog.shape[1] == 2:
            data_weights = endog.sum(1)  # weights are total trials
        else:
            data_weights = np.ones((endog.shape[0]))
        self.data_weights = data_weights
        if np.shape(self.data_weights) == () and self.data_weights > 1:
            self.data_weights = self.data_weights * np.ones((endog.shape[0]))
        self.scaletype = scale
        if isinstance(self.family, families.Binomial):
        # this checks what kind of data is given for Binomial.
        # family will need a reference to endog if this is to be removed from
        # preprocessing
            self.endog = self.family.initialize(self.endog)

        if hasattr(self, 'offset'):
            offset = self.offset
        elif hasattr(self, 'exposure'):
            offset = self.exposure
        else:
            offset = 0
        #TODO: would there ever be both and exposure and an offset?

        wlsexog = self.exog
        if start_params is None:
            mu = self.family.starting_mu(self.endog)
        else:
            mu = self.family.fitted(np.dot(wlsexog, start_params))
        eta = self.family.predict(mu)
        dev = self.family.deviance(self.endog, mu)
        if np.isnan(dev):
            raise ValueError("The first guess on the deviance function "
                             "returned a nan.  This could be a boundary "
                             " problem and should be reported.")

        # first guess on the deviance is assumed to be scaled by 1.
        # params are none to start, so they line up with the deviance
        history = dict(params=[None, start_params], deviance=[np.inf, dev])
        iteration = 0
        converged = 0
        criterion = history['deviance']
        while not converged:
            self.weights = data_weights*self.family.weights(mu)
            wlsendog = (eta + self.family.link.deriv(mu) * (self.endog-mu)
                        - offset)
            wls_results = lm.WLS(wlsendog, wlsexog, self.weights).fit()
            eta = np.dot(self.exog, wls_results.params) + offset
            mu = self.family.fitted(eta)
            history = self._update_history(wls_results, mu, history)
            self.scale = self.estimate_scale(mu)
            iteration += 1
            if endog.squeeze().ndim == 1 and np.allclose(mu - endog, 0):
                msg = "Perfect separation detected, results not available"
                raise PerfectSeparationError(msg)
            converged = _check_convergence(criterion, iteration, tol, maxiter)
        self.mu = mu
        glm_results = GLMResults(self, wls_results.params,
                                 wls_results.normalized_cov_params,
                                 self.scale)
        history['iteration'] = iteration
        glm_results.fit_history = history
        return GLMResultsWrapper(glm_results)
Beispiel #5
0
    def fit(self,
            start_params=None,
            maxiter=100,
            method='IRLS',
            tol=1e-8,
            scale=None,
            cov_type='nonrobust',
            cov_kwds=None,
            use_t=None,
            **kwargs):
        """
        Fits a generalized linear model for a given family.

        parameters
        ----------
        maxiter : int, optional
            Default is 100.
        method : string
            Default is 'IRLS' for iteratively reweighted least squares.  This
            is currently the only method available for GLM fit.
        scale : string or float, optional
            `scale` can be 'X2', 'dev', or a float
            The default value is None, which uses `X2` for Gamma, Gaussian,
            and Inverse Gaussian.
            `X2` is Pearson's chi-square divided by `df_resid`.
            The default is 1 for the Binomial and Poisson families.
            `dev` is the deviance divided by df_resid
        tol : float
            Convergence tolerance.  Default is 1e-8.
        start_params : array-like, optional
            Initial guess of the solution for the loglikelihood maximization.
            The default is family-specific and is given by the
            ``family.starting_mu(endog)``. If start_params is given then the
            initial mean will be calculated as ``np.dot(exog, start_params)``.

        Notes
        -----
        This method does not take any extra undocumented ``kwargs``.
        """
        endog = self.endog
        if endog.ndim > 1 and endog.shape[1] == 2:
            data_weights = endog.sum(1)  # weights are total trials
        else:
            data_weights = np.ones((endog.shape[0]))
        self.data_weights = data_weights
        if np.shape(self.data_weights) == () and self.data_weights > 1:
            self.data_weights = self.data_weights * np.ones((endog.shape[0]))
        self.scaletype = scale
        if isinstance(self.family, families.Binomial):
            # this checks what kind of data is given for Binomial.
            # family will need a reference to endog if this is to be removed from
            # preprocessing
            self.endog = self.family.initialize(self.endog)

        # Construct a combined offset/exposure term.  Note that
        # exposure has already been logged if present.
        offset_exposure = 0.
        if hasattr(self, 'offset'):
            offset_exposure = self.offset
        if hasattr(self, 'exposure'):
            offset_exposure = offset_exposure + self.exposure
        self._offset_exposure = offset_exposure

        wlsexog = self.exog
        if start_params is None:
            mu = self.family.starting_mu(self.endog)
            lin_pred = self.family.predict(mu)
        else:
            lin_pred = np.dot(wlsexog, start_params) + offset_exposure
            mu = self.family.fitted(lin_pred)
        dev = self.family.deviance(self.endog, mu)
        if np.isnan(dev):
            raise ValueError("The first guess on the deviance function "
                             "returned a nan.  This could be a boundary "
                             " problem and should be reported.")

        # first guess on the deviance is assumed to be scaled by 1.
        # params are none to start, so they line up with the deviance
        history = dict(params=[None, start_params], deviance=[np.inf, dev])
        converged = False
        criterion = history['deviance']
        # This special case is used to get the likelihood for a specific
        # params vector.
        if maxiter == 0:
            mu = self.family.fitted(lin_pred)
            self.scale = self.estimate_scale(mu)
            wls_results = lm.RegressionResults(self, start_params, None)
            iteration = 0
        for iteration in range(maxiter):
            self.weights = data_weights * self.family.weights(mu)
            wlsendog = (lin_pred + self.family.link.deriv(mu) *
                        (self.endog - mu) - offset_exposure)
            wls_results = lm.WLS(wlsendog, wlsexog, self.weights).fit()
            lin_pred = np.dot(self.exog, wls_results.params) + offset_exposure
            mu = self.family.fitted(lin_pred)
            history = self._update_history(wls_results, mu, history)
            self.scale = self.estimate_scale(mu)
            if endog.squeeze().ndim == 1 and np.allclose(mu - endog, 0):
                msg = "Perfect separation detected, results not available"
                raise PerfectSeparationError(msg)
            converged = _check_convergence(criterion, iteration, tol)
            if converged:
                break
        self.mu = mu

        glm_results = GLMResults(self,
                                 wls_results.params,
                                 wls_results.normalized_cov_params,
                                 self.scale,
                                 cov_type=cov_type,
                                 cov_kwds=cov_kwds,
                                 use_t=use_t)

        history['iteration'] = iteration + 1
        glm_results.fit_history = history
        return GLMResultsWrapper(glm_results)