Repository: statsmodels/statsmodels

Issue:
BUG: mnlogit ynames fails with float endog
If endog in mnlogit is a float that is not an integer, then the construction of ynames raises an exception
https://stackoverflow.com/questions/46514008/mnlogit-with-float-dependent-variable
reproducible example below

Note: I'm not sure what the behavior with float should be. There is also the possibility to allow QMLE for simplex/compositional data.

```
import statsmodels.formula.api as sm
import pandas as pd
df = pd.DataFrame({"A": [0., 1.1, 0, 0, 1.1], "B": [0, 1, 0, 1, 1]})
result = sm.mnlogit(formula="A ~ B", data=df).fit()
```
```
>>> result.summary()
    ynames = ['='.join([yname, name]) for name in ynames]
TypeError: sequence item 1: expected str instance, numpy.float64 found
>>>     ynames = ['='.join([yname, name]) for name in ynames]
  File "m:\...\statsmodels\discrete\discrete_model.py", line 3916, in <listcomp>
  File "m:\...\statsmodels\discrete\discrete_model.py", line 3520, in summary
    yname, yname_list = self._get_endog_name(yname, yname_list)
  File "m:\...\statsmodels\discrete\discrete_model.py", line 3916, in _get_endog_name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
```


Discussion:
The specific code 

`MultinomialResults` has two methods to construct the multivariate specific ynames
`_get_endog_name` uses the endog level names to construct `yname_list`
`_maybe_convert_ynames_int` converts ints or integer valued floats to string, but leaves other 'levels' unchanged.

If the levels are then not strings, then the string join raises an exception, as in the exampl
`TypeError: sequence item 1: expected str instance, numpy.float64 found`

possible solution use `str` or `repr` for anything else in the string concatenation, i.e. extend `_maybe_convert_ynames_int`

aside `_ynames_map` seems to be just an alias, not other use found in search in module
`self._ynames_map = ynames`
QMLE with 2d endog float in simplex:

(not tried yet)
I guess that we can handle the special case when the user provided endog is already 2-d, and then assume that in that case the values are floats in a simplex or in {0, 1} multinomial.

possible problem: patsy also does some conversion, so we might not see the original user provided endog. (Related open issues ...?? where patsy does something different than our code.)
I haven't checked the details in how this works in current code.
suggested quickfix for 0.9:  add the `str` conversion for anything else 
Tangentially related:  https://github.com/statsmodels/statsmodels/blob/master/statsmodels/base/data.py#L371 will be incorrect if `arr.name` is Falsy but not None.
@jbrockmendel  Do you know a case where pandas series name is anything else than string or None?
AFAIR, we didn't not get failing examples for series `name`, but maybe the problems with some names and name concatenation in ARIMA is similar.  
I'm not aware of any in the existing test cases, but pandas will allow anything hashable as a `Series.name`.  Not a big deal (and definitely shouldn't be allowed to distract from the OP topic).
a reminder about not forgetting py 2 - 3 changes
statsmodels.compat.asstr2 ?  (asstr doesn't work for float)
more test cases:
`pds = pd.Series(np.arange(5), name=0)`

```
>>> pds = pd.Series(np.arange(5), name=3.5)
>>> pds.name
3.5
>>> type(pds.name)
<class 'float'>
```
...

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
@@ -33,7 +33,8 @@
 import statsmodels.tools.tools as tools
 from statsmodels.tools import data as data_tools
 from statsmodels.tools.decorators import cache_readonly
-from statsmodels.tools.sm_exceptions import PerfectSeparationError
+from statsmodels.tools.sm_exceptions import (PerfectSeparationError,
+                                             SpecificationWarning)
 from statsmodels.tools.numdiff import approx_fprime_cs
 import statsmodels.base.model as base
 from statsmodels.base.data import handle_data  # for mnlogit
@@ -4052,12 +4053,23 @@ def __init__(self, model, mlefit):
 
     def _maybe_convert_ynames_int(self, ynames):
         # see if they're integers
-        try:
-            for i in ynames:
+        issue_warning = False
+        msg = ('endog contains values are that not int-like. Uses string '
+               'representation of value. Use integer-valued endog to '
+               'suppress this warning.')
+        for i in ynames:
+            try:
                 if ynames[i] % 1 == 0:
                     ynames[i] = str(int(ynames[i]))
-        except TypeError:
-            pass
+                else:
+                    issue_warning = True
+                    ynames[i] = str(ynames[i])
+            except TypeError:
+                ynames[i] = str(ynames[i])
+        if issue_warning:
+            import warnings
+            warnings.warn(msg, SpecificationWarning)
+
         return ynames
 
     def _get_endog_name(self, yname, yname_list, all=False):


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
@@ -28,7 +28,8 @@
 import statsmodels.api as sm
 import statsmodels.formula.api as smf
 from .results.results_discrete import Spector, DiscreteL1, RandHIE, Anes
-from statsmodels.tools.sm_exceptions import PerfectSeparationError
+from statsmodels.tools.sm_exceptions import (PerfectSeparationError,
+                                             SpecificationWarning)
 from scipy.stats import nbinom
 
 try:
@@ -2357,6 +2358,16 @@ def test_unchanging_degrees_of_freedom():
     # If res2.df_model == res1.df_model, then this test is invalid.
 
     res3 = model.fit(start_params=params, disp=0)
-    # Test that the call to `fit_regularized` didn't modify model.df_model inplace.
+    # Test that the call to `fit_regularized` didn't
+    # modify model.df_model inplace.
     assert_equal(res3.df_model, res1.df_model)
     assert_equal(res3.df_resid, res1.df_resid)
+
+
+def test_mnlogit_float_name():
+    df = pd.DataFrame({"A": [0., 1.1, 0, 0, 1.1], "B": [0, 1, 0, 1, 1]})
+    result = smf.mnlogit(formula="A ~ B", data=df).fit()
+    with pytest.warns(SpecificationWarning,
+                      match='endog contains values are that not int-like'):
+        summ = result.summary().as_text()
+    assert 'A=1.1' in summ