Repository: statsmodels/statsmodels

Issue:
BUG/ENH: fit_regularized elasticnet does not show non-convergence
I didn't get results with GLM fit_regularized with alpha=0 that match the unpenalized fit.

The problem is that maxiter was reached before convergence. There is no warning or `converged` attribute or mle_retvals to show the information about convergence status.

If I start with the unpenalized res.params as start_values, then fit_regularized returns immediately.

(I'm using Star98 dataset with GLM-Binomial with and without counts, using proportions and no n_trials)


Discussion:
I had this same problem using High dimensional data (p=4088, n=71). Any update?

Resolving patch:
diff --git a/statsmodels/base/elastic_net.py b/statsmodels/base/elastic_net.py
--- a/statsmodels/base/elastic_net.py
+++ b/statsmodels/base/elastic_net.py
@@ -165,6 +165,8 @@ def fit_elasticnet(model, method="coord_descent", maxiter=100,
         _gen_npfuncs(k, L1_wt, alpha, loglike_kwds, score_kwds, hess_kwds)
         for k in range(k_exog)]
 
+    converged = False
+
     for itr in range(maxiter):
 
         # Sweep through the parameters
@@ -204,6 +206,7 @@ def fit_elasticnet(model, method="coord_descent", maxiter=100,
         # Check for convergence
         pchange = np.max(np.abs(params - params_save))
         if pchange < cnvrg_tol:
+            converged = True
             break
 
     # Set approximate zero coefficients to be exactly zero
@@ -211,6 +214,7 @@ def fit_elasticnet(model, method="coord_descent", maxiter=100,
 
     if not refit:
         results = RegularizedResults(model, params)
+        results.converged = converged
         return RegularizedResultsWrapper(results)
 
     # Fit the reduced model to get standard errors and other
@@ -255,6 +259,7 @@ def fit_elasticnet(model, method="coord_descent", maxiter=100,
     # Assuming a standard signature for creating results classes.
     refit = klass(model, params, cov, scale=scale)
     refit.regularized = True
+    refit.converged = converged
     refit.method = method
     refit.fit_history = {'iteration': itr + 1}
 
diff --git a/statsmodels/genmod/generalized_linear_model.py b/statsmodels/genmod/generalized_linear_model.py
--- a/statsmodels/genmod/generalized_linear_model.py
+++ b/statsmodels/genmod/generalized_linear_model.py
@@ -29,6 +29,7 @@
 import statsmodels.regression.linear_model as lm
 import statsmodels.base.wrapper as wrap
 import statsmodels.regression._tools as reg_tools
+from warnings import warn
 
 from statsmodels.graphics._regressionplots_doc import (
     _plot_added_variable_doc,
@@ -270,12 +271,10 @@ def __init__(self, endog, exog, family=None, offset=None,
         if (family is not None) and not isinstance(family.link,
                                                    tuple(family.safe_links)):
 
-            import warnings
-            warnings.warn(("The %s link function does not respect the domain "
-                           "of the %s family.") %
-                          (family.link.__class__.__name__,
-                           family.__class__.__name__),
-                          DomainWarning)
+            warn(("The %s link function does not respect the domain "
+                  "of the %s family.") %
+                  (family.link.__class__.__name__,
+                  family.__class__.__name__), DomainWarning)
 
         if exposure is not None:
             exposure = np.log(exposure)
@@ -1088,7 +1087,6 @@ def _fit_gradient(self, start_params=None, method="newton",
         try:
             cov_p = np.linalg.inv(-self.hessian(rslt.params, observed=oim)) / scale
         except LinAlgError:
-            from warnings import warn
             warn('Inverting hessian failed, no bse or cov_params '
                  'available', HessianInversionWarning)
             cov_p = None
@@ -1255,7 +1253,8 @@ def fit_regularized(self, method="elastic_net", alpha=0.,
             Must be in [0, 1].  The L1 penalty has weight L1_wt and the
             L2 penalty has weight 1 - L1_wt.
         cnvrg_tol : float
-            Convergence threshold for line searches
+            Convergence threshold for maximum parameter change after
+            one sweep through all coefficients.
         zero_tol : float
             Coefficients below this threshold are treated as zero.
         """
@@ -1281,6 +1280,10 @@ def fit_regularized(self, method="elastic_net", alpha=0.,
         self.mu = self.predict(result.params)
         self.scale = self.estimate_scale(self.mu)
 
+        if not result.converged:
+            msg = "Elastic net fitting did not converge"
+            warn(msg)
+
         return result
 
     def _fit_ridge(self, alpha, start_params, method):


Test patch:
diff --git a/statsmodels/genmod/tests/test_glm.py b/statsmodels/genmod/tests/test_glm.py
--- a/statsmodels/genmod/tests/test_glm.py
+++ b/statsmodels/genmod/tests/test_glm.py
@@ -1970,14 +1970,13 @@ def test_tweedie_EQL():
        rtol=1e-5, atol=1e-5)
 
     # Lasso fit using coordinate-wise descent
+    # TODO: The search gets trapped in an infinite oscillation, so use
+    # a slack convergence tolerance.
     model2 = sm.GLM(y, x, family=fam)
-    result2 = model2.fit_regularized(L1_wt=1, alpha=0.07)
-    import sys
-    ver = sys.version_info[0]
-    if ver >= 3:
-        rtol, atol = 1e-5, 1e-5
-    else:
-        rtol, atol = 1e-2, 1e-2
+    result2 = model2.fit_regularized(L1_wt=1, alpha=0.07, maxiter=200,
+                   cnvrg_tol=0.01)
+
+    rtol, atol = 1e-2, 1e-4
     assert_allclose(result2.params,
         np.array([1.00281192, -0.99182638, 0., 0.50448516]),
         rtol=rtol, atol=atol)