Repository: statsmodels/statsmodels (Python)

Issue:
BUG: out-of-sample prediction with nan values get unexpected result
This is an OLS regression model from [this page](https://www.statsmodels.org/stable/example_formulas.html):
```
import statsmodels.api as sm
import statsmodels.formula.api as smf
import numpy as np
import pandas as pd

# example from https://www.statsmodels.org/stable/example_formulas.html
df = sm.datasets.get_rdataset("Guerry", "HistData").data
df = df[['Lottery', 'Literacy', 'Wealth', 'Region']].dropna()
mod = smf.ols(formula='Lottery ~ Literacy', data=df)
res = mod.fit()
```
When this model is fitted, we can use this model to predict: 

```
# out of sample prediction without NaN values. This works as expected.
res.predict(pd.Series([0,1,2,3], name='Literacy'))
```

But when we have NaN values in the exog data, the `predict()` method returns unexpected result, which is a pandas series with only NaN value indexed by None:
 
```
# ****out of sample prediction with NaN values. This gives a strange result!****
res.predict(pd.Series([0,1,2,None,3], name='Literacy'))
```



statsmodels: 0.13.5
pandas: 1.4.4
numpy: 1.23.5
python:3.9.16
ENH/BUG: allow fit keyword ridge_factor in newton
`model.fit(method="newton", ridge_factor= 1e-5)`

issues a warning about unused kwarg, and ridge_factor is not transmitted to the newton optimizer.
I wanted to try out fit with a large ridge factor to force convergence (to a possibly regularized solution)

I'm not sure whether this ever worked or was a refactoring victim.

AFAICS, it's because ridge_factor is explicit keyword and not in `kwargs` in signature, base.optimizer
```
        def _fit_newton(f, score, start_params, fargs, kwargs, disp=True,
                maxiter=100, callback=None, retall=False,
                full_output=True, hess=None, ridge_factor=1e-10)
```


Hints:

Patch:
diff --git a/statsmodels/base/model.py b/statsmodels/base/model.py
--- a/statsmodels/base/model.py
+++ b/statsmodels/base/model.py
@@ -1097,6 +1097,7 @@ def _transform_predict_exog(self, exog, transform=True):
                 else:
                     # assume we need a row
                     exog = pd.DataFrame(exog).T
+                exog_index = exog.index
             orig_exog_len = len(exog)
             is_dict = isinstance(exog, dict)
             try:
diff --git a/statsmodels/base/optimizer.py b/statsmodels/base/optimizer.py
--- a/statsmodels/base/optimizer.py
+++ b/statsmodels/base/optimizer.py
@@ -428,8 +428,9 @@ def _fit_newton(f, score, start_params, fargs, kwargs, disp=True,
         information returned from the solver used. If it is False, this is
         None.
     """
-    check_kwargs(kwargs, ("tol",), "newton")
+    check_kwargs(kwargs, ("tol", "ridge_factor"), "newton")
     tol = kwargs.setdefault('tol', 1e-8)
+    ridge_factor = kwargs.setdefault('ridge_factor', 1e-10)
     iterations = 0
     oldparams = np.inf
     newparams = np.asarray(start_params)
diff --git a/statsmodels/discrete/count_model.py b/statsmodels/discrete/count_model.py
--- a/statsmodels/discrete/count_model.py
+++ b/statsmodels/discrete/count_model.py
@@ -684,7 +684,9 @@ def _predict_var(self, params, mu, prob_infl):
         return var_
 
     def _get_start_params(self):
-        start_params = self.model_main.fit(disp=0, method="nm").params
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", category=ConvergenceWarning)
+            start_params = self.model_main.fit(disp=0, method="nm").params
         start_params = np.append(np.ones(self.k_inflate) * 0.1, start_params)
         return start_params
 
diff --git a/statsmodels/discrete/truncated_model.py b/statsmodels/discrete/truncated_model.py
--- a/statsmodels/discrete/truncated_model.py
+++ b/statsmodels/discrete/truncated_model.py
@@ -3,6 +3,7 @@
 __all__ = ["TruncatedLFPoisson", "TruncatedLFNegativeBinomialP",
            "HurdleCountModel"]
 
+import warnings
 import numpy as np
 import statsmodels.base.model as base
 import statsmodels.base.wrapper as wrap
@@ -23,6 +24,7 @@
     )
 from statsmodels.tools.numdiff import approx_hess
 from statsmodels.tools.decorators import cache_readonly
+from statsmodels.tools.sm_exceptions import ConvergenceWarning
 from copy import deepcopy
 
 
@@ -184,7 +186,9 @@ def fit(self, start_params=None, method='bfgs', maxiter=35,
                 offset = None
             model = self.model_main.__class__(self.endog, self.exog,
                                               offset=offset)
-            start_params = model.fit(disp=0).params
+            with warnings.catch_warnings():
+                warnings.simplefilter("ignore", category=ConvergenceWarning)
+                start_params = model.fit(disp=0).params
 
         # Todo: check how we can to this in __init__
         k_params = self.df_model + 1 + self.k_extra
@@ -748,7 +752,9 @@ def fit(self, start_params=None, method='bfgs', maxiter=35,
                 offset = None
             model = self.model_main.__class__(self.endog, self.exog,
                                               offset=offset)
-            start_params = model.fit(disp=0).params
+            with warnings.catch_warnings():
+                warnings.simplefilter("ignore", category=ConvergenceWarning)
+                start_params = model.fit(disp=0).params
         mlefit = super(_RCensoredGeneric, self).fit(
             start_params=start_params,
             method=method,
diff --git a/statsmodels/multivariate/manova.py b/statsmodels/multivariate/manova.py
--- a/statsmodels/multivariate/manova.py
+++ b/statsmodels/multivariate/manova.py
@@ -71,13 +71,19 @@ def fit(self):
                                   'mv_test directly on a MANOVA instance.')
 
     @Substitution(hypotheses_doc=_hypotheses_doc)
-    def mv_test(self, hypotheses=None):
+    def mv_test(self, hypotheses=None, skip_intercept_test=False):
         """
         Linear hypotheses testing
 
         Parameters
         ----------
         %(hypotheses_doc)s
+        skip_intercept_test : bool
+            If true, then testing the intercept is skipped, the model is not
+            changed.
+            Note: If a term has a numerically insignificant effect, then
+            an exception because of emtpy arrays may be raised. This can
+            happen for the intercept if the data has been demeaned.
 
         Returns
         -------
@@ -104,6 +110,8 @@ def mv_test(self, hypotheses=None):
                 terms = self.data.design_info.term_name_slices
                 hypotheses = []
                 for key in terms:
+                    if skip_intercept_test and key == 'Intercept':
+                        continue
                     L_contrast = np.eye(self.exog.shape[1])[terms[key], :]
                     hypotheses.append([key, L_contrast, None])
             else:
diff --git a/statsmodels/multivariate/multivariate_ols.py b/statsmodels/multivariate/multivariate_ols.py
--- a/statsmodels/multivariate/multivariate_ols.py
+++ b/statsmodels/multivariate/multivariate_ols.py
@@ -423,13 +423,19 @@ def __str__(self):
         return self.summary().__str__()
 
     @Substitution(hypotheses_doc=_hypotheses_doc)
-    def mv_test(self, hypotheses=None):
+    def mv_test(self, hypotheses=None, skip_intercept_test=False):
         """
         Linear hypotheses testing
 
         Parameters
         ----------
         %(hypotheses_doc)s
+        skip_intercept_test : bool
+            If true, then testing the intercept is skipped, the model is not
+            changed.
+            Note: If a term has a numerically insignificant effect, then
+            an exception because of emtpy arrays may be raised. This can
+            happen for the intercept if the data has been demeaned.
 
         Returns
         -------
@@ -451,6 +457,8 @@ def mv_test(self, hypotheses=None):
                 terms = self.design_info.term_name_slices
                 hypotheses = []
                 for key in terms:
+                    if skip_intercept_test and key == 'Intercept':
+                        continue
                     L_contrast = np.eye(k_xvar)[terms[key], :]
                     hypotheses.append([key, L_contrast, None])
             else:


Test patch:
diff --git a/statsmodels/multivariate/tests/test_manova.py b/statsmodels/multivariate/tests/test_manova.py
--- a/statsmodels/multivariate/tests/test_manova.py
+++ b/statsmodels/multivariate/tests/test_manova.py
@@ -3,7 +3,7 @@
 import numpy as np
 import pandas as pd
 import pytest
-from numpy.testing import assert_almost_equal, assert_raises
+from numpy.testing import assert_almost_equal, assert_raises, assert_allclose
 
 from statsmodels.multivariate.manova import MANOVA
 from statsmodels.multivariate.multivariate_ols import MultivariateTestResults
@@ -171,3 +171,29 @@ def test_manova_test_input_validation():
 
 def test_endog_1D_array():
     assert_raises(ValueError, MANOVA.from_formula, 'Basal ~ Loc', X)
+
+
+def test_manova_demeaned():
+    # see last example in #8713
+    # If a term has no effect, all eigenvalues below threshold, then computaion
+    # raised numpy exception with empty arrays.
+    # currently we have an option to skip the intercept test, but don't handle
+    # empty arrays directly
+    ng = 5
+    loc = ["Basal", "Occ", "Max"] * ng
+    y1 = (np.random.randn(ng, 3) + [0, 0.5, 1]).ravel()
+    y2 = (np.random.randn(ng, 3) + [0.25, 0.75, 1]).ravel()
+    y3 = (np.random.randn(ng, 3) + [0.3, 0.6, 1]).ravel()
+    dta = pd.DataFrame(dict(Loc=loc, Basal=y1, Occ=y2, Max=y3))
+    mod = MANOVA.from_formula('Basal + Occ + Max ~ C(Loc, Helmert)', data=dta)
+    res1 = mod.mv_test()
+
+    # subtract sample means to have insignificant intercept
+    means = dta[["Basal", "Occ", "Max"]].mean()
+    dta[["Basal", "Occ", "Max"]] = dta[["Basal", "Occ", "Max"]] - means
+    mod = MANOVA.from_formula('Basal + Occ + Max ~ C(Loc, Helmert)', data=dta)
+    res2 = mod.mv_test(skip_intercept_test=True)
+
+    stat1 = res1.results["C(Loc, Helmert)"]["stat"].to_numpy(float)
+    stat2 = res2.results["C(Loc, Helmert)"]["stat"].to_numpy(float)
+    assert_allclose(stat1, stat2, rtol=1e-10)