Repository: statsmodels/statsmodels

Issue:
BUG mnlogit does not store design info, predict fails with formula
Can't predict in mnlogit, test-code follows. Version: `0.8.0`

Independent SE question (author not me): https://stackoverflow.com/questions/44199054/how-to-specify-a-prediction-using-statsmodels-mnlogit-model

```
import statsmodels.formula.api as sm
import pandas as pd
df = pd.DataFrame({"A": [0, 1, 0, 0, 1], "B": [0, 1, 0, 1, 1]})
result = sm.mnlogit(formula="A ~ B", data=df).fit()
result.predict()  # works

# the following lead to 
# AttributeError: 'PandasData' object has no attribute 'design_info'

result.predict(df)
result.predict(df['B'])

```






Discussion:
Thanks for the report.

I get the same exception in master running the example as provided.

We refactored the location of design_info at some point to avoid similar problems, but maybe MNLogit has special code that doesn't do the same thing as the generic data and formula handling.


To the description title: I don't see any pickling involved here. @eXcuvator  ?

(The original refactoring of the design_info location was to avoid attaching anything directly to a dataframe)
current guess: `MultinomialModel(BinaryModel)` overrides  `_handle_data` and does not call `super`. So something might be missing there.
I don't see any special `from_formula` code in discrete_models.
I see only a single unit test for mnlogit with formula which is in test_discrete test_mnlogit_factor. 
So my guess would be that this never worked, i.e. formula support where MNLogit deviates from generic code.
got it, I think

at the end of `handle_data`, extra keys/kwargs are `pop`ed
base.model excludes design_info and formula, MNLogit does not
```
        for key in kwargs:
            if key in ['design_info', 'formula']:  # leave attached to data
                continue
```

After adding this to MNLogit, the example runs without exception. (but still a covergence warning maxiter exceeded)
I'm opening a PR with the fix, but my guess is that calling super would be the better solution, but refactorings need better unit test coverage.
temporary workaround for predict
If the entire design matrix for predict, exog_predict, is available, then `transform` can be set to False, which avoids going through patsy and the missing design_info

```
>>> from statsmodels.tools import add_constant
>>> result.predict(add_constant(df['B']), transform=False)
          0             1
0  1.000000  1.401148e-11
1  0.333333  6.666667e-01
2  1.000000  1.401148e-11
3  0.333333  6.666667e-01
4  0.333333  6.666667e-01
```
@josef-pkt  Yes, pickling not directly, I took that from related bugs from 2015. Great that there is already a quick work around!

Resolving patch:
diff --git a/statsmodels/discrete/discrete_model.py b/statsmodels/discrete/discrete_model.py
--- a/statsmodels/discrete/discrete_model.py
+++ b/statsmodels/discrete/discrete_model.py
@@ -541,6 +541,8 @@ def _handle_data(self, endog, exog, missing, hasconst, **kwargs):
 
         # repeating from upstream...
         for key in kwargs:
+            if key in ['design_info', 'formula']:  # leave attached to data
+                continue
             try:
                 setattr(self, key, data.__dict__.pop(key))
             except KeyError:


Test patch:
diff --git a/statsmodels/discrete/tests/test_discrete.py b/statsmodels/discrete/tests/test_discrete.py
--- a/statsmodels/discrete/tests/test_discrete.py
+++ b/statsmodels/discrete/tests/test_discrete.py
@@ -1555,19 +1555,22 @@ def test_non_binary():
 def test_mnlogit_factor():
     dta = sm.datasets.anes96.load_pandas()
     dta['endog'] = dta.endog.replace(dict(zip(range(7), 'ABCDEFG')))
-    dta.exog['constant'] = 1
-    mod = sm.MNLogit(dta.endog, dta.exog)
+    exog = sm.add_constant(dta.exog, prepend=True)
+    mod = sm.MNLogit(dta.endog, exog)
     res = mod.fit(disp=0)
     # smoke tests
     params = res.params
     summary = res.summary()
+    predicted = res.predict(exog.iloc[:5, :])
 
     # with patsy
-    del dta.exog['constant']
     mod = smf.mnlogit('PID ~ ' + ' + '.join(dta.exog.columns), dta.data)
     res2 = mod.fit(disp=0)
-    res2.params
+    params_f = res2.params
     summary = res2.summary()
+    assert_allclose(params_f, params, rtol=1e-10)
+    predicted_f = res2.predict(dta.exog.iloc[:5, :])
+    assert_allclose(predicted_f, predicted, rtol=1e-10)
 
 
 def test_formula_missing_exposure():