Repository: statsmodels/statsmodels

Issue:
Unreliable auto-casting of pandas data in model fitters
#### Describe the bug

As of now `np.asarray` with no flags is used to convert model input into a proper numerical format, when fitting models such as OLS, WLS etc.

https://github.com/statsmodels/statsmodels/blob/a0eca865c65ef9336c6403f8ff4bc29a1d3ec26b/statsmodels/base/data.py#L504-L511

This is non-reliable, particularly for mixed data types, as shown below:

#### Code Sample, a copy-pastable example if possible


```python
import pandas as pd
import numpy as np
import statsmodels.api as sm

Xy = pd.DataFrame(data=[[0.0,False,0.1],[1.0,True,0.9],[1.0,True,0.9]],columns=['y','x1','x2'])

X = Xy[['x1','x2']] # FIXME: + .astype(float), otherwise type casting fails
y = Xy['y']

sm.OLS(y,X)
```
<details>

**Note**: As you can see, there are many issues on our GitHub tracker, so it is very possible that your issue has been posted before. Please check first before submitting so that we do not have to handle and close duplicates.

**Note**: Please be sure you are using the latest released version of `statsmodels`, or a recent build of `main`. If your problem has been fixed in an unreleased version, you might be able to use `main` until a new release occurs. 

**Note**: If you are using a released version, have you verified that the bug exists in the main branch of this repository? It helps the limited resources if we know problems exist in the current main branch so that they do not need to check whether the code sample produces a bug in the next release.

</details>

#### Related Issues

Here https://github.com/statsmodels/statsmodels/issues/8794 opens a narrower discussion about non-reliability of handling nulls.

#### Suggested solution

I **suggest to inform numpy about numerical types** when casting
```python
np.asarray(Xy, dtype=np.float32)
```

#### Expected Output

Fitting does not produce errors.

#### Output of ``import statsmodels.api as sm; sm.show_versions()``

<details>

[paste the output of ``import statsmodels.api as sm; sm.show_versions()`` here below this line]

INSTALLED VERSIONS
------------------
Python: 3.10.12.final.0
OS: Linux 6.1.58+ #1 SMP PREEMPT_DYNAMIC Sat Nov 18 15:31:17 UTC 2023 x86_64
byteorder: little
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8

statsmodels
===========

Installed: 0.14.1 (/usr/local/lib/python3.10/dist-packages/statsmodels)

Required Dependencies
=====================

cython: 3.0.10 (/usr/local/lib/python3.10/dist-packages/Cython)
numpy: 1.25.2 (/usr/local/lib/python3.10/dist-packages/numpy)
scipy: 1.11.4 (/usr/local/lib/python3.10/dist-packages/scipy)
pandas: 2.0.3 (/usr/local/lib/python3.10/dist-packages/pandas)
    dateutil: 2.8.2 (/usr/local/lib/python3.10/dist-packages/dateutil)
patsy: 0.5.6 (/usr/local/lib/python3.10/dist-packages/patsy)

Optional Dependencies
=====================

matplotlib: 3.7.1 (/usr/local/lib/python3.10/dist-packages/matplotlib)
    backend: module://matplotlib_inline.backend_inline 
cvxopt: 1.3.2 (/usr/local/lib/python3.10/dist-packages/cvxopt)
joblib: 1.4.0 (/usr/local/lib/python3.10/dist-packages/joblib)

Developer Tools
================

IPython: 7.34.0 (/usr/local/lib/python3.10/dist-packages/IPython)
    jinja2: 3.1.3 (/usr/local/lib/python3.10/dist-packages/jinja2)
sphinx: 5.0.2 (/usr/local/lib/python3.10/dist-packages/sphinx)
    pygments: 2.16.1 (/usr/local/lib/python3.10/dist-packages/pygments)
pytest: 7.4.4 (/usr/local/lib/python3.10/dist-packages/pytest)
virtualenv: Not installed

</details>


Discussion:

Patch:
diff --git a/statsmodels/base/data.py b/statsmodels/base/data.py
index 61cada0b6..8cbf5eb6e 100644
--- a/statsmodels/base/data.py
+++ b/statsmodels/base/data.py
@@ -2,6 +2,7 @@
 Base tools for handling various kinds of data structures, attaching metadata to
 results, and doing data cleaning
 """
+
 from __future__ import annotations
 
 from statsmodels.compat.python import lmap
@@ -9,7 +10,7 @@ from statsmodels.compat.python import lmap
 from functools import reduce
 
 import numpy as np
-from pandas import DataFrame, Series, isnull, MultiIndex
+from pandas import DataFrame, MultiIndex, Series, isnull
 
 import statsmodels.tools.data as data_util
 from statsmodels.tools.decorators import cache_readonly, cache_writable
@@ -26,8 +27,8 @@ def _asarray_2d_null_rows(x):
     Makes sure input is an array and is 2d. Makes sure output is 2d. True
     indicates a null in the rows of 2d x.
     """
-    #Have to have the asarrays because isnull does not account for array_like
-    #input
+    # Have to have the asarrays because isnull does not account for array_like
+    # input
     x = np.asarray(x)
     if x.ndim == 1:
         x = x[:, None]
@@ -45,9 +46,11 @@ def _nan_rows(*arrs):
 
     def _nan_row_maybe_two_inputs(x, y):
         # check for dtype bc dataframe has dtypes
-        x_is_boolean_array = hasattr(x, 'dtype') and x.dtype == bool and x
-        return np.logical_or(_asarray_2d_null_rows(x),
-                             (x_is_boolean_array | _asarray_2d_null_rows(y)))
+        x_is_boolean_array = hasattr(x, "dtype") and x.dtype == bool and x
+        return np.logical_or(
+            _asarray_2d_null_rows(x), (x_is_boolean_array | _asarray_2d_null_rows(y))
+        )
+
     return reduce(_nan_row_maybe_two_inputs, arrs).squeeze()
 
 
@@ -56,27 +59,26 @@ class ModelData:
     Class responsible for handling input data and extracting metadata into the
     appropriate form
     """
+
     _param_names = None
     _cov_names = None
 
-    def __init__(self, endog, exog=None, missing='none', hasconst=None,
-                 **kwargs):
+    def __init__(self, endog, exog=None, missing="none", hasconst=None, **kwargs):
         if data_util._is_recarray(endog) or data_util._is_recarray(exog):
             from statsmodels.tools.sm_exceptions import recarray_exception
+
             raise NotImplementedError(recarray_exception)
-        if 'design_info' in kwargs:
-            self.design_info = kwargs.pop('design_info')
-        if 'formula' in kwargs:
-            self.formula = kwargs.pop('formula')
-        if missing != 'none':
-            arrays, nan_idx = self.handle_missing(endog, exog, missing,
-                                                  **kwargs)
+        if "design_info" in kwargs:
+            self.design_info = kwargs.pop("design_info")
+        if "formula" in kwargs:
+            self.formula = kwargs.pop("formula")
+        if missing != "none":
+            arrays, nan_idx = self.handle_missing(endog, exog, missing, **kwargs)
             self.missing_row_idx = nan_idx
             self.__dict__.update(arrays)  # attach all the data arrays
             self.orig_endog = self.endog
             self.orig_exog = self.exog
-            self.endog, self.exog = self._convert_endog_exog(self.endog,
-                                                             self.exog)
+            self.endog, self.exog = self._convert_endog_exog(self.endog, self.exog)
         else:
             self.__dict__.update(kwargs)  # attach the extra arrays anyway
             self.orig_endog = endog
@@ -91,6 +93,7 @@ class ModelData:
 
     def __getstate__(self):
         from copy import copy
+
         d = copy(self.__dict__)
         if "design_info" in d:
             del d["design_info"]
@@ -100,20 +103,22 @@ class ModelData:
     def __setstate__(self, d):
         if "restore_design_info" in d:
             # NOTE: there may be a more performant way to do this
-            from patsy import dmatrices, PatsyError
+            from patsy import PatsyError, dmatrices
+
             exc = []
             try:
-                data = d['frame']
+                data = d["frame"]
             except KeyError:
-                data = d['orig_endog'].join(d['orig_exog'])
+                data = d["orig_endog"].join(d["orig_exog"])
 
             for depth in [2, 3, 1, 0, 4]:  # sequence is a guess where to likely find it
                 try:
-                    _, design = dmatrices(d['formula'], data, eval_env=depth,
-                                          return_type='dataframe')
+                    _, design = dmatrices(
+                        d["formula"], data, eval_env=depth, return_type="dataframe"
+                    )
                     break
                 except (NameError, PatsyError) as e:
-                    exc.append(e)   # why do I need a reference from outside except block
+                    exc.append(e)  # why do I need a reference from outside except block
                     pass
             else:
                 raise exc[-1]
@@ -131,7 +136,7 @@ class ModelData:
             check_implicit = False
             exog_max = np.max(self.exog, axis=0)
             if not np.isfinite(exog_max).all():
-                raise MissingDataError('exog contains inf or nans')
+                raise MissingDataError("exog contains inf or nans")
             exog_min = np.min(self.exog, axis=0)
             const_idx = np.where(exog_max == exog_min)[0].squeeze()
             self.k_constant = const_idx.size
@@ -155,7 +160,7 @@ class ModelData:
                     values.append(value)
                 else:
                     # we did not break, no column of ones
-                    pos = (np.array(values) != 0)
+                    pos = np.array(values) != 0
                     if pos.any():
                         # take the first nonzero column
                         self.k_constant = 1
@@ -173,7 +178,8 @@ class ModelData:
                 # look for implicit constant
                 # Compute rank of augmented matrix
                 augmented_exog = np.column_stack(
-                            (np.ones(self.exog.shape[0]), self.exog))
+                    (np.ones(self.exog.shape[0]), self.exog)
+                )
                 rank_augm = np.linalg.matrix_rank(augmented_exog)
                 rank_orig = np.linalg.matrix_rank(self.exog)
                 self.k_constant = int(rank_orig == rank_augm)
@@ -200,21 +206,21 @@ class ModelData:
         none_array_names = []
 
         # patsy's already dropped NaNs in y/X
-        missing_idx = kwargs.pop('missing_idx', None)
+        missing_idx = kwargs.pop("missing_idx", None)
 
         if missing_idx is not None:
             # y, X already handled by patsy. add back in later.
             combined = ()
             combined_names = []
             if exog is None:
-                none_array_names += ['exog']
+                none_array_names += ["exog"]
         elif exog is not None:
             combined = (endog, exog)
-            combined_names = ['endog', 'exog']
+            combined_names = ["endog", "exog"]
         else:
             combined = (endog,)
-            combined_names = ['endog']
-            none_array_names += ['exog']
+            combined_names = ["endog"]
+            none_array_names += ["exog"]
 
         # deal with other arrays
         combined_2d = ()
@@ -237,8 +243,9 @@ class ModelData:
                     combined_2d += (np.asarray(value_array),)
                     combined_2d_names += [key]
                 else:
-                    raise ValueError("Arrays with more than 2 dimensions "
-                                     "are not yet handled")
+                    raise ValueError(
+                        "Arrays with more than 2 dimensions " "are not yet handled"
+                    )
 
         if missing_idx is not None:
             nan_mask = missing_idx
@@ -246,16 +253,20 @@ class ModelData:
             if combined:  # there were extra arrays not handled by patsy
                 combined_nans = _nan_rows(*combined)
                 if combined_nans.shape[0] != nan_mask.shape[0]:
-                    raise ValueError("Shape mismatch between endog/exog "
-                                     "and extra arrays given to model.")
+                    raise ValueError(
+                        "Shape mismatch between endog/exog "
+                        "and extra arrays given to model."
+                    )
                 # for going back and updated endog/exog
                 updated_row_mask = combined_nans[~nan_mask]
                 nan_mask |= combined_nans  # for updating extra arrays only
             if combined_2d:
                 combined_2d_nans = _nan_rows(combined_2d)
                 if combined_2d_nans.shape[0] != nan_mask.shape[0]:
-                    raise ValueError("Shape mismatch between endog/exog "
-                                     "and extra 2d arrays given to model.")
+                    raise ValueError(
+                        "Shape mismatch between endog/exog "
+                        "and extra 2d arrays given to model."
+                    )
                 if updated_row_mask is not None:
                     updated_row_mask |= combined_2d_nans[~nan_mask]
                 else:
@@ -272,20 +283,19 @@ class ModelData:
             if combined_2d:
                 combined.update(dict(zip(combined_2d_names, combined_2d)))
             if none_array_names:
-                combined.update({k: kwargs.get(k, None)
-                                 for k in none_array_names})
+                combined.update({k: kwargs.get(k, None) for k in none_array_names})
 
             if missing_idx is not None:
-                combined.update({'endog': endog})
+                combined.update({"endog": endog})
                 if exog is not None:
-                    combined.update({'exog': exog})
+                    combined.update({"exog": exog})
 
             return combined, []
 
-        elif missing == 'raise':
+        elif missing == "raise":
             raise MissingDataError("NaNs were encountered in the data")
 
-        elif missing == 'drop':
+        elif missing == "drop":
             nan_mask = ~nan_mask
             drop_nans = lambda x: cls._drop_nans(x, nan_mask)
             drop_nans_2d = lambda x: cls._drop_nans_2d(x, nan_mask)
@@ -299,16 +309,16 @@ class ModelData:
                     if exog is not None:
                         exog = cls._drop_nans(exog, updated_row_mask)
 
-                combined.update({'endog': endog})
+                combined.update({"endog": endog})
                 if exog is not None:
-                    combined.update({'exog': exog})
+                    combined.update({"exog": exog})
 
             if combined_2d:
-                combined.update(dict(zip(combined_2d_names,
-                                         lmap(drop_nans_2d, combined_2d))))
+                combined.update(
+                    dict(zip(combined_2d_names, lmap(drop_nans_2d, combined_2d)))
+                )
             if none_array_names:
-                combined.update({k: kwargs.get(k, None)
-                                 for k in none_array_names})
+                combined.update({k: kwargs.get(k, None) for k in none_array_names})
 
             return combined, np.where(~nan_mask)[0].tolist()
         else:
@@ -396,8 +406,7 @@ class ModelData:
         if isinstance(arr, DataFrame):
             if isinstance(arr.columns, MultiIndex):
                 # Flatten MultiIndexes into "simple" column names
-                return ['_'.join(level for level in c if level)
-                        for c in arr.columns]
+                return ["_".join(level for level in c if level) for c in arr.columns]
             else:
                 return list(arr.columns)
         elif isinstance(arr, Series):
@@ -435,26 +444,26 @@ class ModelData:
             if len(self.exog) != len(self.endog):
                 raise ValueError("endog and exog matrices are different sizes")
 
-    def wrap_output(self, obj, how='columns', names=None):
-        if how == 'columns':
+    def wrap_output(self, obj, how="columns", names=None):
+        if how == "columns":
             return self.attach_columns(obj)
-        elif how == 'rows':
+        elif how == "rows":
             return self.attach_rows(obj)
-        elif how == 'cov':
+        elif how == "cov":
             return self.attach_cov(obj)
-        elif how == 'dates':
+        elif how == "dates":
             return self.attach_dates(obj)
-        elif how == 'columns_eq':
+        elif how == "columns_eq":
             return self.attach_columns_eq(obj)
-        elif how == 'cov_eq':
+        elif how == "cov_eq":
             return self.attach_cov_eq(obj)
-        elif how == 'generic_columns':
+        elif how == "generic_columns":
             return self.attach_generic_columns(obj, names)
-        elif how == 'generic_columns_2d':
+        elif how == "generic_columns_2d":
             return self.attach_generic_columns_2d(obj, names)
-        elif how == 'ynames':
+        elif how == "ynames":
             return self.attach_ynames(obj)
-        elif how == 'multivariate_confint':
+        elif how == "multivariate_confint":
             return self.attach_mv_confint(obj)
         else:
             return obj
@@ -502,12 +511,14 @@ class PandasData(ModelData):
     """
 
     def _convert_endog_exog(self, endog, exog=None):
-        #TODO: remove this when we handle dtype systematically
+        # TODO: remove this when we handle dtype systematically
         endog = np.asarray(endog)
-        exog = exog if exog is None else np.asarray(exog)
-        if endog.dtype == object or exog is not None and exog.dtype == object:
-            raise ValueError("Pandas data cast to numpy dtype of object. "
-                             "Check input data with np.asarray(data).")
+        exog = exog if exog is None else np.asarray(exog, dtype=float)
+        if endog.dtype == object:
+            raise ValueError(
+                "Pandas data cast to numpy dtype of object. "
+                "Check input data with np.asarray(data)."
+            )
         return super()._convert_endog_exog(endog, exog)
 
     @classmethod
@@ -527,9 +538,11 @@ class PandasData(ModelData):
     def _check_integrity(self):
         endog, exog = self.orig_endog, self.orig_exog
         # exog can be None and we could be upcasting one or the other
-        if (exog is not None and
-                (hasattr(endog, 'index') and hasattr(exog, 'index')) and
-                not self.orig_endog.index.equals(self.orig_exog.index)):
+        if (
+            exog is not None
+            and (hasattr(endog, "index") and hasattr(exog, "index"))
+            and not self.orig_endog.index.equals(self.orig_exog.index)
+        ):
             raise ValueError("The indices for endog and exog are not aligned")
         super()._check_integrity()
 
@@ -583,7 +596,7 @@ class PandasData(ModelData):
         else:
             out = DataFrame(result)
             out.columns = self.ynames
-        out.index = self.row_labels[-len(result):]
+        out.index = self.row_labels[-len(result) :]
         return out
 
     def attach_dates(self, result):
@@ -595,14 +608,14 @@ class PandasData(ModelData):
         if squeezed.ndim < 2:
             return Series(squeezed, index=self.predict_dates)
         else:
-            return DataFrame(np.asarray(result),
-                             index=self.predict_dates,
-                             columns=self.ynames)
+            return DataFrame(
+                np.asarray(result), index=self.predict_dates, columns=self.ynames
+            )
 
     def attach_mv_confint(self, result):
-        return DataFrame(result.reshape((-1, 2)),
-                         index=self.cov_names,
-                         columns=['lower', 'upper'])
+        return DataFrame(
+            result.reshape((-1, 2)), index=self.cov_names, columns=["lower", "upper"]
+        )
 
     def attach_ynames(self, result):
         squeezed = result.squeeze()
@@ -615,9 +628,9 @@ class PandasData(ModelData):
 
 def _make_endog_names(endog):
     if endog.ndim == 1 or endog.shape[1] == 1:
-        ynames = ['y']
+        ynames = ["y"]
     else:  # for VAR
-        ynames = ['y%d' % (i+1) for i in range(endog.shape[1])]
+        ynames = ["y%d" % (i + 1) for i in range(endog.shape[1])]
 
     return ynames
 
@@ -628,17 +641,17 @@ def _make_exog_names(exog):
         # assumes one constant in first or last position
         # avoid exception if more than one constant
         const_idx = exog_var.argmin()
-        exog_names = ['x%d' % i for i in range(1, exog.shape[1])]
-        exog_names.insert(const_idx, 'const')
+        exog_names = ["x%d" % i for i in range(1, exog.shape[1])]
+        exog_names.insert(const_idx, "const")
     else:
-        exog_names = ['x%d' % i for i in range(1, exog.shape[1]+1)]
+        exog_names = ["x%d" % i for i in range(1, exog.shape[1] + 1)]
 
     return exog_names
 
 
-def handle_missing(endog, exog=None, missing='none', **kwargs):
+def handle_missing(endog, exog=None, missing="none", **kwargs):
     klass = handle_data_class_factory(endog, exog)
-    if missing == 'none':
+    if missing == "none":
         ret_dict = dict(endog=endog, exog=exog)
         ret_dict.update(kwargs)
         return ret_dict, None
@@ -659,12 +672,13 @@ def handle_data_class_factory(endog, exog):
     elif data_util._is_using_ndarray(endog, exog):
         klass = ModelData
     else:
-        raise ValueError('unrecognized data structures: %s / %s' %
-                         (type(endog), type(exog)))
+        raise ValueError(
+            "unrecognized data structures: %s / %s" % (type(endog), type(exog))
+        )
     return klass
 
 
-def handle_data(endog, exog, missing='none', hasconst=None, **kwargs):
+def handle_data(endog, exog, missing="none", hasconst=None, **kwargs):
     # deal with lists and tuples up-front
     if isinstance(endog, (list, tuple)):
         endog = np.asarray(endog)
@@ -672,5 +686,4 @@ def handle_data(endog, exog, missing='none', hasconst=None, **kwargs):
         exog = np.asarray(exog)
 
     klass = handle_data_class_factory(endog, exog)
-    return klass(endog, exog=exog, missing=missing, hasconst=hasconst,
-                 **kwargs)
+    return klass(endog, exog=exog, missing=missing, hasconst=hasconst, **kwargs)


Test patch:
diff --git a/statsmodels/base/tests/test_data.py b/statsmodels/base/tests/test_data.py
index 870a971f4..ea04a297a 100644
--- a/statsmodels/base/tests/test_data.py
+++ b/statsmodels/base/tests/test_data.py
@@ -1,21 +1,20 @@
 from statsmodels.compat.pandas import (
-    assert_series_equal,
     assert_frame_equal,
+    assert_series_equal,
     make_dataframe,
 )
 
 import numpy as np
-from numpy.testing import assert_equal, assert_, assert_raises
+from numpy.testing import assert_, assert_equal, assert_raises
 import pandas as pd
 import pytest
 
 from statsmodels.base import data as sm_data
+from statsmodels.discrete.discrete_model import Logit
 from statsmodels.formula import handle_formula_data
-from statsmodels.regression.linear_model import OLS
-from statsmodels.genmod.generalized_linear_model import GLM
 from statsmodels.genmod import families
-from statsmodels.discrete.discrete_model import Logit
-
+from statsmodels.genmod.generalized_linear_model import GLM
+from statsmodels.regression.linear_model import OLS
 
 # FIXME: do not leave commented-out, enable or move/remove
 # class TestDates:
@@ -28,6 +27,7 @@ from statsmodels.discrete.discrete_model import Logit
 #        np.testing.assert_equal(data.wrap_output(self.dates_input, 'dates'),
 #                                self.dates_result)
 
+
 class TestArrays:
     @classmethod
     def setup_class(cls):
@@ -39,8 +39,8 @@ class TestArrays:
         cls.col_result = cls.col_input = np.random.random(nvars)
         cls.row_result = cls.row_input = np.random.random(nrows)
         cls.cov_result = cls.cov_input = np.random.random((nvars, nvars))
-        cls.xnames = ['const', 'x1', 'x2']
-        cls.ynames = 'y'
+        cls.xnames = ["const", "x1", "x2"]
+        cls.ynames = "y"
         cls.row_labels = None
 
     def test_orig(self):
@@ -55,12 +55,15 @@ class TestArrays:
         data = self.data
         # this makes sure what the wrappers need work but not the wrapped
         # results themselves
-        np.testing.assert_equal(data.wrap_output(self.col_input, 'columns'),
-                                self.col_result)
-        np.testing.assert_equal(data.wrap_output(self.row_input, 'rows'),
-                                self.row_result)
-        np.testing.assert_equal(data.wrap_output(self.cov_input, 'cov'),
-                                self.cov_result)
+        np.testing.assert_equal(
+            data.wrap_output(self.col_input, "columns"), self.col_result
+        )
+        np.testing.assert_equal(
+            data.wrap_output(self.row_input, "rows"), self.row_result
+        )
+        np.testing.assert_equal(
+            data.wrap_output(self.cov_input, "cov"), self.cov_result
+        )
 
     def test_names(self):
         data = self.data
@@ -95,8 +98,8 @@ class TestArrays1dExog(TestArrays):
         exog = np.random.random(10)
         cls.data = sm_data.handle_data(cls.endog, exog)
         cls.exog = exog[:, None]
-        cls.xnames = ['x1']
-        cls.ynames = 'y'
+        cls.xnames = ["x1"]
+        cls.ynames = "y"
 
     def test_orig(self):
         np.testing.assert_equal(self.data.orig_endog, self.endog)
@@ -106,26 +109,23 @@ class TestArrays1dExog(TestArrays):
 class TestDataFrames(TestArrays):
     @classmethod
     def setup_class(cls):
-        cls.endog = pd.DataFrame(np.random.random(10), columns=['y_1'])
-        exog = pd.DataFrame(np.random.random((10, 2)),
-                            columns=['x_1', 'x_2'])
-        exog.insert(0, 'const', 1)
+        cls.endog = pd.DataFrame(np.random.random(10), columns=["y_1"])
+        exog = pd.DataFrame(np.random.random((10, 2)), columns=["x_1", "x_2"])
+        exog.insert(0, "const", 1)
         cls.exog = exog
         cls.data = sm_data.handle_data(cls.endog, cls.exog)
         nrows = 10
         nvars = 3
         cls.col_input = np.random.random(nvars)
-        cls.col_result = pd.Series(cls.col_input,
-                                   index=exog.columns)
+        cls.col_result = pd.Series(cls.col_input, index=exog.columns)
         cls.row_input = np.random.random(nrows)
-        cls.row_result = pd.Series(cls.row_input,
-                                   index=exog.index)
+        cls.row_result = pd.Series(cls.row_input, index=exog.index)
         cls.cov_input = np.random.random((nvars, nvars))
-        cls.cov_result = pd.DataFrame(cls.cov_input,
-                                      index=exog.columns,
-                                      columns=exog.columns)
-        cls.xnames = ['const', 'x_1', 'x_2']
-        cls.ynames = 'y_1'
+        cls.cov_result = pd.DataFrame(
+            cls.cov_input, index=exog.columns, columns=exog.columns
+        )
+        cls.xnames = ["const", "x_1", "x_2"]
+        cls.ynames = "y_1"
         cls.row_labels = cls.exog.index
 
     def test_orig(self):
@@ -140,22 +140,21 @@ class TestDataFrames(TestArrays):
         data = self.data
         # this makes sure what the wrappers need work but not the wrapped
         # results themselves
-        assert_series_equal(data.wrap_output(self.col_input, 'columns'),
-                            self.col_result)
-        assert_series_equal(data.wrap_output(self.row_input, 'rows'),
-                            self.row_result)
-        assert_frame_equal(data.wrap_output(self.cov_input, 'cov'),
-                           self.cov_result)
+        assert_series_equal(
+            data.wrap_output(self.col_input, "columns"), self.col_result
+        )
+        assert_series_equal(data.wrap_output(self.row_input, "rows"), self.row_result)
+        assert_frame_equal(data.wrap_output(self.cov_input, "cov"), self.cov_result)
 
 
 class TestDataFramesWithMultiIndex(TestDataFrames):
     @classmethod
     def setup_class(cls):
-        cls.endog = pd.DataFrame(np.random.random(10), columns=['y_1'])
-        mi = pd.MultiIndex.from_product([['x'], ['1', '2']])
+        cls.endog = pd.DataFrame(np.random.random(10), columns=["y_1"])
+        mi = pd.MultiIndex.from_product([["x"], ["1", "2"]])
         exog = pd.DataFrame(np.random.random((10, 2)), columns=mi)
-        exog_flattened_idx = pd.Index(['const', 'x_1', 'x_2'])
-        exog.insert(0, 'const', 1)
+        exog_flattened_idx = pd.Index(["const", "x_1", "x_2"])
+        exog.insert(0, "const", 1)
         cls.exog = exog
         cls.data = sm_data.handle_data(cls.endog, cls.exog)
         nrows = 10
@@ -165,11 +164,11 @@ class TestDataFramesWithMultiIndex(TestDataFrames):
         cls.row_input = np.random.random(nrows)
         cls.row_result = pd.Series(cls.row_input, index=exog.index)
         cls.cov_input = np.random.random((nvars, nvars))
-        cls.cov_result = pd.DataFrame(cls.cov_input,
-                                      index=exog_flattened_idx,
-                                      columns=exog_flattened_idx)
-        cls.xnames = ['const', 'x_1', 'x_2']
-        cls.ynames = 'y_1'
+        cls.cov_result = pd.DataFrame(
+            cls.cov_input, index=exog_flattened_idx, columns=exog_flattened_idx
+        )
+        cls.xnames = ["const", "x_1", "x_2"]
+        cls.ynames = "y_1"
         cls.row_labels = cls.exog.index
 
 
@@ -187,25 +186,22 @@ class TestListDataFrame(TestDataFrames):
     def setup_class(cls):
         cls.endog = np.random.random(10).tolist()
 
-        exog = pd.DataFrame(np.random.random((10, 2)),
-                            columns=['x_1', 'x_2'])
-        exog.insert(0, 'const', 1)
+        exog = pd.DataFrame(np.random.random((10, 2)), columns=["x_1", "x_2"])
+        exog.insert(0, "const", 1)
         cls.exog = exog
         cls.data = sm_data.handle_data(cls.endog, cls.exog)
         nrows = 10
         nvars = 3
         cls.col_input = np.random.random(nvars)
-        cls.col_result = pd.Series(cls.col_input,
-                                   index=exog.columns)
+        cls.col_result = pd.Series(cls.col_input, index=exog.columns)
         cls.row_input = np.random.random(nrows)
-        cls.row_result = pd.Series(cls.row_input,
-                                   index=exog.index)
+        cls.row_result = pd.Series(cls.row_input, index=exog.index)
         cls.cov_input = np.random.random((nvars, nvars))
-        cls.cov_result = pd.DataFrame(cls.cov_input,
-                                      index=exog.columns,
-                                      columns=exog.columns)
-        cls.xnames = ['const', 'x_1', 'x_2']
-        cls.ynames = 'y'
+        cls.cov_result = pd.DataFrame(
+            cls.cov_input, index=exog.columns, columns=exog.columns
+        )
+        cls.xnames = ["const", "x_1", "x_2"]
+        cls.ynames = "y"
         cls.row_labels = cls.exog.index
 
     def test_endogexog(self):
@@ -220,27 +216,24 @@ class TestListDataFrame(TestDataFrames):
 class TestDataFrameList(TestDataFrames):
     @classmethod
     def setup_class(cls):
-        cls.endog = pd.DataFrame(np.random.random(10), columns=['y_1'])
+        cls.endog = pd.DataFrame(np.random.random(10), columns=["y_1"])
 
-        exog = pd.DataFrame(np.random.random((10, 2)),
-                            columns=['x1', 'x2'])
-        exog.insert(0, 'const', 1)
+        exog = pd.DataFrame(np.random.random((10, 2)), columns=["x1", "x2"])
+        exog.insert(0, "const", 1)
         cls.exog = exog.values.tolist()
         cls.data = sm_data.handle_data(cls.endog, cls.exog)
         nrows = 10
         nvars = 3
         cls.col_input = np.random.random(nvars)
-        cls.col_result = pd.Series(cls.col_input,
-                                   index=exog.columns)
+        cls.col_result = pd.Series(cls.col_input, index=exog.columns)
         cls.row_input = np.random.random(nrows)
-        cls.row_result = pd.Series(cls.row_input,
-                                   index=exog.index)
+        cls.row_result = pd.Series(cls.row_input, index=exog.index)
         cls.cov_input = np.random.random((nvars, nvars))
-        cls.cov_result = pd.DataFrame(cls.cov_input,
-                                      index=exog.columns,
-                                      columns=exog.columns)
-        cls.xnames = ['const', 'x1', 'x2']
-        cls.ynames = 'y_1'
+        cls.cov_result = pd.DataFrame(
+            cls.cov_input, index=exog.columns, columns=exog.columns
+        )
+        cls.xnames = ["const", "x1", "x2"]
+        cls.ynames = "y_1"
         cls.row_labels = cls.endog.index
 
     def test_endogexog(self):
@@ -257,25 +250,22 @@ class TestArrayDataFrame(TestDataFrames):
     def setup_class(cls):
         cls.endog = np.random.random(10)
 
-        exog = pd.DataFrame(np.random.random((10, 2)),
-                            columns=['x_1', 'x_2'])
-        exog.insert(0, 'const', 1)
+        exog = pd.DataFrame(np.random.random((10, 2)), columns=["x_1", "x_2"])
+        exog.insert(0, "const", 1)
         cls.exog = exog
         cls.data = sm_data.handle_data(cls.endog, exog)
         nrows = 10
         nvars = 3
         cls.col_input = np.random.random(nvars)
-        cls.col_result = pd.Series(cls.col_input,
-                                   index=exog.columns)
+        cls.col_result = pd.Series(cls.col_input, index=exog.columns)
         cls.row_input = np.random.random(nrows)
-        cls.row_result = pd.Series(cls.row_input,
-                                   index=exog.index)
+        cls.row_result = pd.Series(cls.row_input, index=exog.index)
         cls.cov_input = np.random.random((nvars, nvars))
-        cls.cov_result = pd.DataFrame(cls.cov_input,
-                                      index=exog.columns,
-                                      columns=exog.columns)
-        cls.xnames = ['const', 'x_1', 'x_2']
-        cls.ynames = 'y'
+        cls.cov_result = pd.DataFrame(
+            cls.cov_input, index=exog.columns, columns=exog.columns
+        )
+        cls.xnames = ["const", "x_1", "x_2"]
+        cls.ynames = "y"
         cls.row_labels = cls.exog.index
 
     def test_endogexog(self):
@@ -290,27 +280,26 @@ class TestArrayDataFrame(TestDataFrames):
 class TestDataFrameArray(TestDataFrames):
     @classmethod
     def setup_class(cls):
-        cls.endog = pd.DataFrame(np.random.random(10), columns=['y_1'])
+        cls.endog = pd.DataFrame(np.random.random(10), columns=["y_1"])
 
-        exog = pd.DataFrame(np.random.random((10, 2)),
-                            columns=['x1', 'x2'])  # names mimic defaults
-        exog.insert(0, 'const', 1)
+        exog = pd.DataFrame(
+            np.random.random((10, 2)), columns=["x1", "x2"]
+        )  # names mimic defaults
+        exog.insert(0, "const", 1)
         cls.exog = exog.values
         cls.data = sm_data.handle_data(cls.endog, cls.exog)
         nrows = 10
         nvars = 3
         cls.col_input = np.random.random(nvars)
-        cls.col_result = pd.Series(cls.col_input,
-                                   index=exog.columns)
+        cls.col_result = pd.Series(cls.col_input, index=exog.columns)
         cls.row_input = np.random.random(nrows)
-        cls.row_result = pd.Series(cls.row_input,
-                                   index=exog.index)
+        cls.row_result = pd.Series(cls.row_input, index=exog.index)
         cls.cov_input = np.random.random((nvars, nvars))
-        cls.cov_result = pd.DataFrame(cls.cov_input,
-                                      index=exog.columns,
-                                      columns=exog.columns)
-        cls.xnames = ['const', 'x1', 'x2']
-        cls.ynames = 'y_1'
+        cls.cov_result = pd.DataFrame(
+            cls.cov_input, index=exog.columns, columns=exog.columns
+        )
+        cls.xnames = ["const", "x1", "x2"]
+        cls.ynames = "y_1"
         cls.row_labels = cls.endog.index
 
     def test_endogexog(self):
@@ -325,27 +314,24 @@ class TestDataFrameArray(TestDataFrames):
 class TestSeriesDataFrame(TestDataFrames):
     @classmethod
     def setup_class(cls):
-        cls.endog = pd.Series(np.random.random(10), name='y_1')
+        cls.endog = pd.Series(np.random.random(10), name="y_1")
 
-        exog = pd.DataFrame(np.random.random((10, 2)),
-                            columns=['x_1', 'x_2'])
-        exog.insert(0, 'const', 1)
+        exog = pd.DataFrame(np.random.random((10, 2)), columns=["x_1", "x_2"])
+        exog.insert(0, "const", 1)
         cls.exog = exog
         cls.data = sm_data.handle_data(cls.endog, cls.exog)
         nrows = 10
         nvars = 3
         cls.col_input = np.random.random(nvars)
-        cls.col_result = pd.Series(cls.col_input,
-                                   index=exog.columns)
+        cls.col_result = pd.Series(cls.col_input, index=exog.columns)
         cls.row_input = np.random.random(nrows)
-        cls.row_result = pd.Series(cls.row_input,
-                                   index=exog.index)
+        cls.row_result = pd.Series(cls.row_input, index=exog.index)
         cls.cov_input = np.random.random((nvars, nvars))
-        cls.cov_result = pd.DataFrame(cls.cov_input,
-                                      index=exog.columns,
-                                      columns=exog.columns)
-        cls.xnames = ['const', 'x_1', 'x_2']
-        cls.ynames = 'y_1'
+        cls.cov_result = pd.DataFrame(
+            cls.cov_input, index=exog.columns, columns=exog.columns
+        )
+        cls.xnames = ["const", "x_1", "x_2"]
+        cls.ynames = "y_1"
         cls.row_labels = cls.exog.index
 
     def test_orig(self):
@@ -356,25 +342,23 @@ class TestSeriesDataFrame(TestDataFrames):
 class TestSeriesSeries(TestDataFrames):
     @classmethod
     def setup_class(cls):
-        cls.endog = pd.Series(np.random.random(10), name='y_1')
+        cls.endog = pd.Series(np.random.random(10), name="y_1")
 
-        exog = pd.Series(np.random.random(10), name='x_1')
+        exog = pd.Series(np.random.random(10), name="x_1")
         cls.exog = exog
         cls.data = sm_data.handle_data(cls.endog, cls.exog)
         nrows = 10
         nvars = 1
         cls.col_input = np.random.random(nvars)
-        cls.col_result = pd.Series(cls.col_input,
-                                   index=[exog.name])
+        cls.col_result = pd.Series(cls.col_input, index=[exog.name])
         cls.row_input = np.random.random(nrows)
-        cls.row_result = pd.Series(cls.row_input,
-                                   index=exog.index)
+        cls.row_result = pd.Series(cls.row_input, index=exog.index)
         cls.cov_input = np.random.random((nvars, nvars))
-        cls.cov_result = pd.DataFrame(cls.cov_input,
-                                      index=[exog.name],
-                                      columns=[exog.name])
-        cls.xnames = ['x_1']
-        cls.ynames = 'y_1'
+        cls.cov_result = pd.DataFrame(
+            cls.cov_input, index=[exog.name], columns=[exog.name]
+        )
+        cls.xnames = ["x_1"]
+        cls.ynames = "y_1"
         cls.row_labels = cls.exog.index
 
     def test_orig(self):
@@ -392,14 +376,14 @@ def test_alignment():
 
     d = load_pandas().data
     # growth rates
-    gs_l_realinv = 400 * np.log(d['realinv']).diff().dropna()
-    gs_l_realgdp = 400 * np.log(d['realgdp']).diff().dropna()
-    lint = d['realint'][:-1]  # incorrect indexing for test purposes
+    gs_l_realinv = 400 * np.log(d["realinv"]).diff().dropna()
+    gs_l_realgdp = 400 * np.log(d["realgdp"]).diff().dropna()
+    lint = d["realint"][:-1]  # incorrect indexing for test purposes
 
     endog = gs_l_realinv
 
     # re-index because they will not conform to lint
-    realgdp = gs_l_realgdp.reindex(lint.index, method='bfill')
+    realgdp = gs_l_realgdp.reindex(lint.index, method="bfill")
     data = dict(const=np.ones_like(lint), lrealgdp=realgdp, lint=lint)
     exog = pd.DataFrame(data)
 
@@ -421,74 +405,77 @@ class TestMultipleEqsArrays(TestArrays):
         cls.cov_result = cls.cov_input = np.random.random((nvars, nvars))
         cls.cov_eq_result = cls.cov_eq_input = np.random.random((neqs, neqs))
         cls.col_eq_result = cls.col_eq_input = np.array((neqs, nvars))
-        cls.xnames = ['const', 'x1', 'x2']
-        cls.ynames = ['y1', 'y2', 'y3', 'y4']
+        cls.xnames = ["const", "x1", "x2"]
+        cls.ynames = ["y1", "y2", "y3", "y4"]
         cls.row_labels = None
 
     def test_attach(self):
         data = self.data
         # this makes sure what the wrappers need work but not the wrapped
         # results themselves
-        np.testing.assert_equal(data.wrap_output(self.col_input, 'columns'),
-                                self.col_result)
-        np.testing.assert_equal(data.wrap_output(self.row_input, 'rows'),
-                                self.row_result)
-        np.testing.assert_equal(data.wrap_output(self.cov_input, 'cov'),
-                                self.cov_result)
-        np.testing.assert_equal(data.wrap_output(self.cov_eq_input, 'cov_eq'),
-                                self.cov_eq_result)
-        np.testing.assert_equal(data.wrap_output(self.col_eq_input,
-                                                 'columns_eq'),
-                                self.col_eq_result)
+        np.testing.assert_equal(
+            data.wrap_output(self.col_input, "columns"), self.col_result
+        )
+        np.testing.assert_equal(
+            data.wrap_output(self.row_input, "rows"), self.row_result
+        )
+        np.testing.assert_equal(
+            data.wrap_output(self.cov_input, "cov"), self.cov_result
+        )
+        np.testing.assert_equal(
+            data.wrap_output(self.cov_eq_input, "cov_eq"), self.cov_eq_result
+        )
+        np.testing.assert_equal(
+            data.wrap_output(self.col_eq_input, "columns_eq"), self.col_eq_result
+        )
 
 
 class TestMultipleEqsDataFrames(TestDataFrames):
     @classmethod
     def setup_class(cls):
-        cls.endog = endog = pd.DataFrame(np.random.random((10, 4)),
-                                         columns=['y_1', 'y_2', 'y_3', 'y_4'])
-        exog = pd.DataFrame(np.random.random((10, 2)),
-                            columns=['x_1', 'x_2'])
-        exog.insert(0, 'const', 1)
+        cls.endog = endog = pd.DataFrame(
+            np.random.random((10, 4)), columns=["y_1", "y_2", "y_3", "y_4"]
+        )
+        exog = pd.DataFrame(np.random.random((10, 2)), columns=["x_1", "x_2"])
+        exog.insert(0, "const", 1)
         cls.exog = exog
         cls.data = sm_data.handle_data(cls.endog, cls.exog)
         nrows = 10
         nvars = 3
         neqs = 4
         cls.col_input = np.random.random(nvars)
-        cls.col_result = pd.Series(cls.col_input,
-                                   index=exog.columns)
+        cls.col_result = pd.Series(cls.col_input, index=exog.columns)
         cls.row_input = np.random.random(nrows)
-        cls.row_result = pd.Series(cls.row_input,
-                                   index=exog.index)
+        cls.row_result = pd.Series(cls.row_input, index=exog.index)
         cls.cov_input = np.random.random((nvars, nvars))
-        cls.cov_result = pd.DataFrame(cls.cov_input,
-                                      index=exog.columns,
-                                      columns=exog.columns)
+        cls.cov_result = pd.DataFrame(
+            cls.cov_input, index=exog.columns, columns=exog.columns
+        )
         cls.cov_eq_input = np.random.random((neqs, neqs))
-        cls.cov_eq_result = pd.DataFrame(cls.cov_eq_input,
-                                         index=endog.columns,
-                                         columns=endog.columns)
+        cls.cov_eq_result = pd.DataFrame(
+            cls.cov_eq_input, index=endog.columns, columns=endog.columns
+        )
         cls.col_eq_input = np.random.random((nvars, neqs))
-        cls.col_eq_result = pd.DataFrame(cls.col_eq_input,
-                                         index=exog.columns,
-                                         columns=endog.columns)
-        cls.xnames = ['const', 'x_1', 'x_2']
-        cls.ynames = ['y_1', 'y_2', 'y_3', 'y_4']
+        cls.col_eq_result = pd.DataFrame(
+            cls.col_eq_input, index=exog.columns, columns=endog.columns
+        )
+        cls.xnames = ["const", "x_1", "x_2"]
+        cls.ynames = ["y_1", "y_2", "y_3", "y_4"]
         cls.row_labels = cls.exog.index
 
     def test_attach(self):
         data = self.data
-        assert_series_equal(data.wrap_output(self.col_input, 'columns'),
-                            self.col_result)
-        assert_series_equal(data.wrap_output(self.row_input, 'rows'),
-                            self.row_result)
-        assert_frame_equal(data.wrap_output(self.cov_input, 'cov'),
-                           self.cov_result)
-        assert_frame_equal(data.wrap_output(self.cov_eq_input, 'cov_eq'),
-                           self.cov_eq_result)
-        assert_frame_equal(data.wrap_output(self.col_eq_input, 'columns_eq'),
-                           self.col_eq_result)
+        assert_series_equal(
+            data.wrap_output(self.col_input, "columns"), self.col_result
+        )
+        assert_series_equal(data.wrap_output(self.row_input, "rows"), self.row_result)
+        assert_frame_equal(data.wrap_output(self.cov_input, "cov"), self.cov_result)
+        assert_frame_equal(
+            data.wrap_output(self.cov_eq_input, "cov_eq"), self.cov_eq_result
+        )
+        assert_frame_equal(
+            data.wrap_output(self.col_eq_input, "columns_eq"), self.col_eq_result
+        )
 
 
 class TestMissingArray:
@@ -504,13 +491,12 @@ class TestMissingArray:
     @pytest.mark.smoke
     def test_raise_no_missing(self):
         # GH#1700
-        sm_data.handle_data(np.random.random(20), np.random.random((20, 2)),
-                            'raise')
+        sm_data.handle_data(np.random.random(20), np.random.random((20, 2)), "raise")
 
     def test_raise(self):
         with pytest.raises(Exception):
             # TODO: be more specific about exception
-            sm_data.handle_data(self.y, self.X, 'raise')
+            sm_data.handle_data(self.y, self.X, "raise")
 
     def test_drop(self):
         y = self.y
@@ -519,12 +505,12 @@ class TestMissingArray:
         idx = ~np.isnan(combined).any(axis=1)
         y = y[idx]
         X = X[idx]
-        data = sm_data.handle_data(self.y, self.X, 'drop')
+        data = sm_data.handle_data(self.y, self.X, "drop")
         np.testing.assert_array_equal(data.endog, y)
         np.testing.assert_array_equal(data.exog, X)
 
     def test_none(self):
-        data = sm_data.handle_data(self.y, self.X, 'none', hasconst=False)
+        data = sm_data.handle_data(self.y, self.X, "none", hasconst=False)
         np.testing.assert_array_equal(data.endog, self.y)
         np.testing.assert_array_equal(data.exog, self.X)
         assert data.k_constant == 0
@@ -532,31 +518,31 @@ class TestMissingArray:
     def test_endog_only_raise(self):
         with pytest.raises(Exception):
             # TODO: be more specific about exception
-            sm_data.handle_data(self.y, None, 'raise')
+            sm_data.handle_data(self.y, None, "raise")
 
     def test_endog_only_drop(self):
         y = self.y
         y = y[~np.isnan(y)]
-        data = sm_data.handle_data(self.y, None, 'drop')
+        data = sm_data.handle_data(self.y, None, "drop")
         np.testing.assert_array_equal(data.endog, y)
 
     def test_mv_endog(self):
         y = self.X
         y = y[~np.isnan(y).any(axis=1)]
-        data = sm_data.handle_data(self.X, None, 'drop')
+        data = sm_data.handle_data(self.X, None, "drop")
         np.testing.assert_array_equal(data.endog, y)
 
     def test_extra_kwargs_2d(self):
         sigma = np.random.random((25, 25))
         sigma = sigma + sigma.T - np.diag(np.diag(sigma))
-        data = sm_data.handle_data(self.y, self.X, 'drop', sigma=sigma)
+        data = sm_data.handle_data(self.y, self.X, "drop", sigma=sigma)
         idx = ~np.isnan(np.c_[self.y, self.X]).any(axis=1)
         sigma = sigma[idx][:, idx]
         np.testing.assert_array_equal(data.sigma, sigma)
 
     def test_extra_kwargs_1d(self):
         weights = np.random.random(25)
-        data = sm_data.handle_data(self.y, self.X, 'drop', weights=weights)
+        data = sm_data.handle_data(self.y, self.X, "drop", weights=weights)
         idx = ~np.isnan(np.c_[self.y, self.X]).any(axis=1)
         weights = weights[idx]
         np.testing.assert_array_equal(data.weights, weights)
@@ -576,14 +562,16 @@ class TestMissingPandas:
     @pytest.mark.smoke
     def test_raise_no_missing(self):
         # GH#1700
-        sm_data.handle_data(pd.Series(np.random.random(20)),
-                            pd.DataFrame(np.random.random((20, 2))),
-                            'raise')
+        sm_data.handle_data(
+            pd.Series(np.random.random(20)),
+            pd.DataFrame(np.random.random((20, 2))),
+            "raise",
+        )
 
     def test_raise(self):
         with pytest.raises(Exception):
             # TODO: be more specific about exception
-            sm_data.handle_data(self.y, self.X, 'raise')
+            sm_data.handle_data(self.y, self.X, "raise")
 
     def test_drop(self):
         y = self.y
@@ -592,14 +580,14 @@ class TestMissingPandas:
         idx = ~np.isnan(combined).any(axis=1)
         y = y.loc[idx]
         X = X.loc[idx]
-        data = sm_data.handle_data(self.y, self.X, 'drop')
+        data = sm_data.handle_data(self.y, self.X, "drop")
         np.testing.assert_array_equal(data.endog, y.values)
         assert_series_equal(data.orig_endog, self.y.loc[idx])
         np.testing.assert_array_equal(data.exog, X.values)
         assert_frame_equal(data.orig_exog, self.X.loc[idx])
 
     def test_none(self):
-        data = sm_data.handle_data(self.y, self.X, 'none', hasconst=False)
+        data = sm_data.handle_data(self.y, self.X, "none", hasconst=False)
         np.testing.assert_array_equal(data.endog, self.y.values)
         np.testing.assert_array_equal(data.exog, self.X.values)
         assert data.k_constant == 0
@@ -607,24 +595,48 @@ class TestMissingPandas:
     def test_endog_only_raise(self):
         with pytest.raises(Exception):
             # TODO: be more specific about exception
-            sm_data.handle_data(self.y, None, 'raise')
+            sm_data.handle_data(self.y, None, "raise")
 
     def test_endog_only_drop(self):
         y = self.y
         y = y.dropna()
-        data = sm_data.handle_data(self.y, None, 'drop')
+        data = sm_data.handle_data(self.y, None, "drop")
         np.testing.assert_array_equal(data.endog, y.values)
 
     def test_mv_endog(self):
         y = self.X
         y = y.loc[~np.isnan(y.values).any(axis=1)]
-        data = sm_data.handle_data(self.X, None, 'drop')
+        data = sm_data.handle_data(self.X, None, "drop")
         np.testing.assert_array_equal(data.endog, y.values)
 
     def test_labels(self):
-        labels = pd.Index([0, 1, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15,
-                           16, 17, 18, 19, 20, 21, 22, 23, 24])
-        data = sm_data.handle_data(self.y, self.X, 'drop')
+        labels = pd.Index(
+            [
+                0,
+                1,
+                3,
+                4,
+                5,
+                6,
+                7,
+                8,
+                9,
+                11,
+                12,
+                13,
+                15,
+                16,
+                17,
+                18,
+                19,
+                20,
+                21,
+                22,
+                23,
+                24,
+            ]
+        )
+        data = sm_data.handle_data(self.y, self.X, "drop")
         np.testing.assert_(data.row_labels.equals(labels))
 
 
@@ -632,18 +644,19 @@ class TestConstant:
     @classmethod
     def setup_class(cls):
         from statsmodels.datasets.longley import load_pandas
+
         cls.data = load_pandas()
 
     def test_array_constant(self):
         exog = self.data.exog.copy()
-        exog['const'] = 1
+        exog["const"] = 1
         data = sm_data.handle_data(self.data.endog.values, exog.values)
         np.testing.assert_equal(data.k_constant, 1)
         np.testing.assert_equal(data.const_idx, 6)
 
     def test_pandas_constant(self):
         exog = self.data.exog.copy()
-        exog['const'] = 1
+        exog["const"] = 1
         data = sm_data.handle_data(self.data.endog, exog)
         np.testing.assert_equal(data.k_constant, 1)
         np.testing.assert_equal(data.const_idx, 6)
@@ -668,57 +681,57 @@ class TestHandleMissing:
         df = make_dataframe()
         df.iloc[[2, 5, 10], [2, 3, 1]] = np.nan
         y, X = df[df.columns[0]], df[df.columns[1:]]
-        data, _ = sm_data.handle_missing(y, X, missing='drop')
+        data, _ = sm_data.handle_missing(y, X, missing="drop")
 
         df = df.dropna()
         y_exp, X_exp = df[df.columns[0]], df[df.columns[1:]]
-        assert_frame_equal(data['exog'], X_exp)
-        assert_series_equal(data['endog'], y_exp)
+        assert_frame_equal(data["exog"], X_exp)
+        assert_series_equal(data["endog"], y_exp)
 
     def test_arrays(self):
         arr = np.random.randn(20, 4)
         arr[[2, 5, 10], [2, 3, 1]] = np.nan
         y, X = arr[:, 0], arr[:, 1:]
-        data, _ = sm_data.handle_missing(y, X, missing='drop')
+        data, _ = sm_data.handle_missing(y, X, missing="drop")
 
         bools_mask = np.ones(20, dtype=bool)
         bools_mask[[2, 5, 10]] = False
         y_exp = arr[bools_mask, 0]
         X_exp = arr[bools_mask, 1:]
-        np.testing.assert_array_equal(data['endog'], y_exp)
-        np.testing.assert_array_equal(data['exog'], X_exp)
+        np.testing.assert_array_equal(data["endog"], y_exp)
+        np.testing.assert_array_equal(data["exog"], X_exp)
 
     def test_pandas_array(self):
         df = make_dataframe()
         df.iloc[[2, 5, 10], [2, 3, 1]] = np.nan
         y, X = df[df.columns[0]], df[df.columns[1:]].values
-        data, _ = sm_data.handle_missing(y, X, missing='drop')
+        data, _ = sm_data.handle_missing(y, X, missing="drop")
 
         df = df.dropna()
         y_exp, X_exp = df[df.columns[0]], df[df.columns[1:]].values
-        np.testing.assert_array_equal(data['exog'], X_exp)
-        assert_series_equal(data['endog'], y_exp)
+        np.testing.assert_array_equal(data["exog"], X_exp)
+        assert_series_equal(data["endog"], y_exp)
 
     def test_array_pandas(self):
         df = make_dataframe()
         df.iloc[[2, 5, 10], [2, 3, 1]] = np.nan
         y, X = df[df.columns[0]].values, df[df.columns[1:]]
-        data, _ = sm_data.handle_missing(y, X, missing='drop')
+        data, _ = sm_data.handle_missing(y, X, missing="drop")
 
         df = df.dropna()
         y_exp, X_exp = df[df.columns[0]].values, df[df.columns[1:]]
-        assert_frame_equal(data['exog'], X_exp)
-        np.testing.assert_array_equal(data['endog'], y_exp)
+        assert_frame_equal(data["exog"], X_exp)
+        np.testing.assert_array_equal(data["endog"], y_exp)
 
     def test_noop(self):
         df = make_dataframe()
         df.iloc[[2, 5, 10], [2, 3, 1]] = np.nan
         y, X = df[df.columns[0]], df[df.columns[1:]]
-        data, _ = sm_data.handle_missing(y, X, missing='none')
+        data, _ = sm_data.handle_missing(y, X, missing="none")
 
         y_exp, X_exp = df[df.columns[0]], df[df.columns[1:]]
-        assert_frame_equal(data['exog'], X_exp)
-        assert_series_equal(data['endog'], y_exp)
+        assert_frame_equal(data["exog"], X_exp)
+        assert_series_equal(data["endog"], y_exp)
 
 
 class CheckHasConstant:
@@ -734,7 +747,7 @@ class CheckHasConstant:
                 assert_equal(mod.data.const_idx, result[1])
 
             # extra check after fit, some models raise on singular
-            fit_kwds = getattr(self, 'fit_kwds', {})
+            fit_kwds = getattr(self, "fit_kwds", {})
             try:
                 res = mod.fit(**fit_kwds)
             except np.linalg.LinAlgError:
@@ -751,8 +764,7 @@ class CheckHasConstant:
         cls.y_bin = (cls.y_c > 0).astype(int)
         x1 = np.column_stack((np.ones(20), np.zeros(20)))
         result1 = (1, 0)
-        x2 = np.column_stack((np.arange(20) < 10.5,
-                              np.arange(20) > 10.5)).astype(float)
+        x2 = np.column_stack((np.arange(20) < 10.5, np.arange(20) > 10.5)).astype(float)
         result2 = (1, None)
         x3 = np.column_stack((np.arange(20), np.zeros(20)))
         result3 = (0, None)
@@ -765,18 +777,27 @@ class CheckHasConstant:
         x5c = np.column_stack((np.arange(20), np.ones((20, 3)) * [0.5, 1, 1]))
         result5c = (1, 2)
         # implicit and zero column
-        x6 = np.column_stack((np.arange(20) < 10.5,
-                              np.arange(20) > 10.5,
-                              np.zeros(20))).astype(float)
+        x6 = np.column_stack(
+            (np.arange(20) < 10.5, np.arange(20) > 10.5, np.zeros(20))
+        ).astype(float)
         result6 = (1, None)
-        x7 = np.column_stack((np.arange(20) < 10.5,
-                              np.arange(20) > 10.5,
-                              np.zeros((20, 2)))).astype(float)
+        x7 = np.column_stack(
+            (np.arange(20) < 10.5, np.arange(20) > 10.5, np.zeros((20, 2)))
+        ).astype(float)
         result7 = (1, None)
 
         cls.exogs = (x1, x2, x3, x4, x5, x5b, x5c, x6, x7)
-        cls.results = (result1, result2, result3, result4, result5, result5b,
-                       result5c, result6, result7)
+        cls.results = (
+            result1,
+            result2,
+            result3,
+            result4,
+            result5,
+            result5b,
+            result5c,
+            result6,
+            result7,
+        )
         cls._initialize()
 
 
@@ -806,7 +827,7 @@ class TestHasConstantLogit(CheckHasConstant):
     def _initialize(cls):
         cls.mod = Logit
         cls.y = cls.y_bin
-        cls.fit_kwds = {'disp': False}
+        cls.fit_kwds = {"disp": False}
 
 
 def test_dtype_object():
@@ -814,12 +835,24 @@ def test_dtype_object():
 
     X = np.random.random((40, 2))
     df = pd.DataFrame(X)
-    df[2] = np.random.randint(2, size=40).astype('object')
-    df['constant'] = 1
+    df[2] = np.random.randint(2, size=40).astype("object")
+    df["constant"] = 1
 
     y = pd.Series(np.random.randint(2, size=40))
+    out = sm_data.handle_data(y, df)
+    assert isinstance(out, sm_data.PandasData)
+    assert_equal(out.endog, np.array(y))
+    assert_equal(out.exog, np.array(df))
+
 
-    np.testing.assert_raises(ValueError, sm_data.handle_data, y, df)
+def test_dtype_actual_object():
+    X = np.random.random((40, 2))
+    df = pd.DataFrame(X)
+    df[2] = np.random.randint(2, size=40).astype("object")
+    df["constant"] = "A"
+    y = pd.Series(np.random.randint(2, size=40))
+    with pytest.raises(ValueError):
+        sm_data.handle_data(y, df)
 
 
 def test_formula_missing_extra_arrays():
@@ -848,59 +881,62 @@ def test_formula_missing_extra_arrays():
 
     weights_wrong_size = np.random.randn(12)
 
-    data = {'y': y,
-            'X': X,
-            'y_missing': y_missing,
-            'X_missing': X_missing,
-            'weights': weights,
-            'weights_missing': weights_missing}
+    data = {
+        "y": y,
+        "X": X,
+        "y_missing": y_missing,
+        "X_missing": X_missing,
+        "weights": weights,
+        "weights_missing": weights_missing,
+    }
     data = pd.DataFrame.from_dict(data)
-    data['constant'] = 1
+    data["constant"] = 1
 
-    formula = 'y_missing ~ X_missing'
+    formula = "y_missing ~ X_missing"
 
-    ((endog, exog),
-     missing_idx, design_info) = handle_formula_data(data, None, formula,
-                                                     depth=2,
-                                                     missing='drop')
+    ((endog, exog), missing_idx, design_info) = handle_formula_data(
+        data, None, formula, depth=2, missing="drop"
+    )
 
-    kwargs = {'missing_idx': missing_idx, 'missing': 'drop',
-              'weights': data['weights_missing']}
+    kwargs = {
+        "missing_idx": missing_idx,
+        "missing": "drop",
+        "weights": data["weights_missing"],
+    }
 
     model_data = sm_data.handle_data(endog, exog, **kwargs)
     data_nona = data.dropna()
-    assert_equal(data_nona['y'].values, model_data.endog)
-    assert_equal(data_nona[['constant', 'X']].values, model_data.exog)
-    assert_equal(data_nona['weights'].values, model_data.weights)
+    assert_equal(data_nona["y"].values, model_data.endog)
+    assert_equal(data_nona[["constant", "X"]].values, model_data.exog)
+    assert_equal(data_nona["weights"].values, model_data.weights)
 
-    tmp = handle_formula_data(data, None, formula, depth=2, missing='drop')
+    tmp = handle_formula_data(data, None, formula, depth=2, missing="drop")
     (endog, exog), missing_idx, design_info = tmp
     weights_2d = np.random.randn(10, 10)
-    weights_2d[[8, 7], [7, 8]] = np.nan   # symmetric missing values
-    kwargs.update({'weights': weights_2d,
-                   'missing_idx': missing_idx})
+    weights_2d[[8, 7], [7, 8]] = np.nan  # symmetric missing values
+    kwargs.update({"weights": weights_2d, "missing_idx": missing_idx})
 
     model_data2 = sm_data.handle_data(endog, exog, **kwargs)
 
     good_idx = [0, 4, 6, 9]
-    assert_equal(data.loc[good_idx, 'y'], model_data2.endog)
-    assert_equal(data.loc[good_idx, ['constant', 'X']], model_data2.exog)
+    assert_equal(data.loc[good_idx, "y"], model_data2.endog)
+    assert_equal(data.loc[good_idx, ["constant", "X"]], model_data2.exog)
     assert_equal(weights_2d[good_idx][:, good_idx], model_data2.weights)
 
-    tmp = handle_formula_data(data, None, formula, depth=2, missing='drop')
+    tmp = handle_formula_data(data, None, formula, depth=2, missing="drop")
     (endog, exog), missing_idx, design_info = tmp
 
-    kwargs.update({'weights': weights_wrong_size,
-                   'missing_idx': missing_idx})
+    kwargs.update({"weights": weights_wrong_size, "missing_idx": missing_idx})
     assert_raises(ValueError, sm_data.handle_data, endog, exog, **kwargs)
 
 
 def test_raise_nonfinite_exog():
     # we raise now in the has constant check before hitting the linear algebra
     from statsmodels.tools.sm_exceptions import MissingDataError
-    x = np.arange(10)[:, None]**([0., 1.])
+
+    x = np.arange(10)[:, None] ** ([0.0, 1.0])
     # random numbers for y
-    y = np.array([-0.6, -0.1, 0., -0.7, -0.5, 0.5, 0.1, -0.8, -2., 1.1])
+    y = np.array([-0.6, -0.1, 0.0, -0.7, -0.5, 0.5, 0.1, -0.8, -2.0, 1.1])
 
     x[1, 1] = np.inf
     assert_raises(MissingDataError, OLS, y, x)