Repository: statsmodels/statsmodels

Issue:
BUG? `predict` with pandas Series as exog broken?
https://stackoverflow.com/questions/60191610/statsmodels-ols-get-prediction-on-out-of-sample-data#comment106467824_60191610

according to the question and comments, if a formula is used and exog is a Series (one predictor besides constant) instead of a DataFrame, then `predict` uses insample prediction.
patsy handles the `transform`, and might not identify exog correctly. However, that should raise an exception in patsy and we shouldn't switch to in-sample predict.

I have not verified this yet.



Discussion:

Resolving patch:
diff --git a/statsmodels/genmod/_prediction.py b/statsmodels/genmod/_prediction.py
--- a/statsmodels/genmod/_prediction.py
+++ b/statsmodels/genmod/_prediction.py
@@ -9,7 +9,7 @@
 
 import numpy as np
 from scipy import stats
-
+import pandas as pd
 
 # this is similar to ContrastResults after t_test, partially copied and adjusted
 class PredictionResults(object):
@@ -122,7 +122,6 @@ def conf_int(self, method='endpoint', alpha=0.05, **kwds):
     def summary_frame(self, alpha=0.05):
         """Summary frame"""
         # TODO: finish and cleanup
-        import pandas as pd
         #ci_obs = self.conf_int(alpha=alpha, obs=True) # need to split
         ci_mean = self.conf_int(alpha=alpha)
         to_include = {}
@@ -178,8 +177,9 @@ def get_prediction_glm(self, exog=None, transform=True, weights=None,
     # prepare exog and row_labels, based on base Results.predict
     if transform and hasattr(self.model, 'formula') and exog is not None:
         from patsy import dmatrix
-        exog = dmatrix(self.model.data.design_info,
-                       exog)
+        if isinstance(exog, pd.Series):
+            exog = pd.DataFrame(exog)
+        exog = dmatrix(self.model.data.design_info, exog)
 
     if exog is not None:
         if row_labels is None:
diff --git a/statsmodels/regression/_prediction.py b/statsmodels/regression/_prediction.py
--- a/statsmodels/regression/_prediction.py
+++ b/statsmodels/regression/_prediction.py
@@ -9,6 +9,7 @@
 
 import numpy as np
 from scipy import stats
+import pandas as pd
 
 
 # this is similar to ContrastResults after t_test, copied and adjusted
@@ -88,7 +89,6 @@ def conf_int(self, obs=False, alpha=0.05):
 
     def summary_frame(self, alpha=0.05):
         # TODO: finish and cleanup
-        import pandas as pd
         ci_obs = self.conf_int(alpha=alpha, obs=True)  # need to split
         ci_mean = self.conf_int(alpha=alpha, obs=False)
         to_include = {}
@@ -145,6 +145,9 @@ def get_prediction(self, exog=None, transform=True, weights=None,
     # prepare exog and row_labels, based on base Results.predict
     if transform and hasattr(self.model, 'formula') and exog is not None:
         from patsy import dmatrix
+        if isinstance(exog, pd.Series):
+            # GH-6509
+            exog = pd.DataFrame(exog)
         exog = dmatrix(self.model.data.design_info, exog)
 
     if exog is not None:


Test patch:
diff --git a/statsmodels/formula/tests/test_formula.py b/statsmodels/formula/tests/test_formula.py
--- a/statsmodels/formula/tests/test_formula.py
+++ b/statsmodels/formula/tests/test_formula.py
@@ -233,3 +233,14 @@ def test_formula_environment():
     assert 'z' in model.exog_names
     with pytest.raises(TypeError):
         ols('y ~ x', eval_env='env', data=df)
+
+
+def test_formula_predict_series_exog():
+    # GH-6509
+    x = np.random.standard_normal((1000, 2))
+    data_full = pd.DataFrame(x, columns=["y", "x"])
+    data = data_full.iloc[:500]
+    res = ols(formula='y ~ x', data=data).fit()
+    oos = data_full.iloc[500:]["x"]
+    prediction = res.get_prediction(oos)
+    assert prediction.predicted_mean.shape[0] == 500