We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
PatsyData detection buglet
If exog is None, then is_using_patsy data utility function returns False. It's a quick fix, but I'm starting to use patsy a bit more internally for convenience (e.g., passing in names to plotting functions rather than having to pull out the data) so I'll make a ticket.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `statsmodels/tools/data.py`
Content:
```
1 """
2 Compatibility tools for various data structure inputs
3 """
4 
5 #TODO: question: interpret_data
6 # looks good and could/should be merged with other check convertion functions we also have
7 # similar also to what Nathaniel mentioned for Formula
8 # good: if ndarray check passes then loading pandas is not triggered,
9 
10 
11 import numpy as np
12 
13 def have_pandas():
14     try:
15         import pandas
16         return True
17     except ImportError:
18         return False
19     except Exception:
20         return False
21 
22 def have_patsy():
23     try:
24         import patsy
25         return True
26     except ImportError:
27         return False
28     except Exception:
29         return False
30 
31 def is_data_frame(obj):
32     if not have_pandas():
33         return False
34 
35     import pandas as pn
36 
37     return isinstance(obj, pn.DataFrame)
38 
39 def is_design_matrix(obj):
40     if not have_patsy():
41         return False
42 
43     from patsy import DesignMatrix
44     return isinstance(obj, DesignMatrix)
45 
46 def _is_structured_ndarray(obj):
47     return isinstance(obj, np.ndarray) and obj.dtype.names is not None
48 
49 def interpret_data(data, colnames=None, rownames=None):
50     """
51     Convert passed data structure to form required by estimation classes
52 
53     Parameters
54     ----------
55     data : ndarray-like
56     colnames : sequence or None
57         May be part of data structure
58     rownames : sequence or None
59 
60     Returns
61     -------
62     (values, colnames, rownames) : (homogeneous ndarray, list)
63     """
64     if isinstance(data, np.ndarray):
65         if _is_structured_ndarray(data):
66             if colnames is None:
67                 colnames = data.dtype.names
68             values = struct_to_ndarray(data)
69         else:
70             values = data
71 
72         if colnames is None:
73             colnames = ['Y_%d' % i for i in range(values.shape[1])]
74     elif is_data_frame(data):
75         # XXX: hack
76         data = data.dropna()
77         values = data.values
78         colnames = data.columns
79         rownames = data.index
80     else: # pragma: no cover
81         raise Exception('cannot handle other input types at the moment')
82 
83     if not isinstance(colnames, list):
84         colnames = list(colnames)
85 
86     # sanity check
87     if len(colnames) != values.shape[1]:
88         raise ValueError('length of colnames does not match number '
89                          'of columns in data')
90 
91     if rownames is not None and len(rownames) != len(values):
92         raise ValueError('length of rownames does not match number '
93                          'of rows in data')
94 
95     return values, colnames, rownames
96 
97 def struct_to_ndarray(arr):
98     return arr.view((float, len(arr.dtype.names)))
99 
100 def _is_using_ndarray_type(endog, exog):
101     return (type(endog) is np.ndarray and
102             (type(exog) is np.ndarray or exog is None))
103 
104 def _is_using_ndarray(endog, exog):
105     return (isinstance(endog, np.ndarray) and
106             (isinstance(exog, np.ndarray) or exog is None))
107 
108 def _is_using_pandas(endog, exog):
109     if not have_pandas():
110         return False
111     from pandas import Series, DataFrame, WidePanel
112     klasses = (Series, DataFrame, WidePanel)
113     return (isinstance(endog, klasses) or isinstance(exog, klasses))
114 
115 def _is_array_like(endog, exog):
116     try: # do it like this in case of mixed types, ie., ndarray and list
117         endog = np.asarray(endog)
118         exog = np.asarray(exog)
119         return True
120     except:
121         return False
122 
123 def _is_using_patsy(endog, exog):
124     # we get this when a structured array is passed through a formula
125     return is_design_matrix(endog) and is_design_matrix(exog)
126 
127
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/statsmodels/tools/data.py b/statsmodels/tools/data.py
--- a/statsmodels/tools/data.py
+++ b/statsmodels/tools/data.py
@@ -122,5 +122,6 @@
 
 def _is_using_patsy(endog, exog):
     # we get this when a structured array is passed through a formula
-    return is_design_matrix(endog) and is_design_matrix(exog)
+    return (is_design_matrix(endog) and
+            (is_design_matrix(exog) or exog is None))

{"golden_diff": "diff --git a/statsmodels/tools/data.py b/statsmodels/tools/data.py\n--- a/statsmodels/tools/data.py\n+++ b/statsmodels/tools/data.py\n@@ -122,5 +122,6 @@\n \n def _is_using_patsy(endog, exog):\n     # we get this when a structured array is passed through a formula\n-    return is_design_matrix(endog) and is_design_matrix(exog)\n+    return (is_design_matrix(endog) and\n+            (is_design_matrix(exog) or exog is None))\n", "issue": "PatsyData detection buglet\nIf exog is None, then is_using_patsy data utility function returns False. It's a quick fix, but I'm starting to use patsy a bit more internally for convenience (e.g., passing in names to plotting functions rather than having to pull out the data) so I'll make a ticket.\n\n", "before_files": [{"content": "\"\"\"\nCompatibility tools for various data structure inputs\n\"\"\"\n\n#TODO: question: interpret_data\n# looks good and could/should be merged with other check convertion functions we also have\n# similar also to what Nathaniel mentioned for Formula\n# good: if ndarray check passes then loading pandas is not triggered,\n\n\nimport numpy as np\n\ndef have_pandas():\n    try:\n        import pandas\n        return True\n    except ImportError:\n        return False\n    except Exception:\n        return False\n\ndef have_patsy():\n    try:\n        import patsy\n        return True\n    except ImportError:\n        return False\n    except Exception:\n        return False\n\ndef is_data_frame(obj):\n    if not have_pandas():\n        return False\n\n    import pandas as pn\n\n    return isinstance(obj, pn.DataFrame)\n\ndef is_design_matrix(obj):\n    if not have_patsy():\n        return False\n\n    from patsy import DesignMatrix\n    return isinstance(obj, DesignMatrix)\n\ndef _is_structured_ndarray(obj):\n    return isinstance(obj, np.ndarray) and obj.dtype.names is not None\n\ndef interpret_data(data, colnames=None, rownames=None):\n    \"\"\"\n    Convert passed data structure to form required by estimation classes\n\n    Parameters\n    ----------\n    data : ndarray-like\n    colnames : sequence or None\n        May be part of data structure\n    rownames : sequence or None\n\n    Returns\n    -------\n    (values, colnames, rownames) : (homogeneous ndarray, list)\n    \"\"\"\n    if isinstance(data, np.ndarray):\n        if _is_structured_ndarray(data):\n            if colnames is None:\n                colnames = data.dtype.names\n            values = struct_to_ndarray(data)\n        else:\n            values = data\n\n        if colnames is None:\n            colnames = ['Y_%d' % i for i in range(values.shape[1])]\n    elif is_data_frame(data):\n        # XXX: hack\n        data = data.dropna()\n        values = data.values\n        colnames = data.columns\n        rownames = data.index\n    else: # pragma: no cover\n        raise Exception('cannot handle other input types at the moment')\n\n    if not isinstance(colnames, list):\n        colnames = list(colnames)\n\n    # sanity check\n    if len(colnames) != values.shape[1]:\n        raise ValueError('length of colnames does not match number '\n                         'of columns in data')\n\n    if rownames is not None and len(rownames) != len(values):\n        raise ValueError('length of rownames does not match number '\n                         'of rows in data')\n\n    return values, colnames, rownames\n\ndef struct_to_ndarray(arr):\n    return arr.view((float, len(arr.dtype.names)))\n\ndef _is_using_ndarray_type(endog, exog):\n    return (type(endog) is np.ndarray and\n            (type(exog) is np.ndarray or exog is None))\n\ndef _is_using_ndarray(endog, exog):\n    return (isinstance(endog, np.ndarray) and\n            (isinstance(exog, np.ndarray) or exog is None))\n\ndef _is_using_pandas(endog, exog):\n    if not have_pandas():\n        return False\n    from pandas import Series, DataFrame, WidePanel\n    klasses = (Series, DataFrame, WidePanel)\n    return (isinstance(endog, klasses) or isinstance(exog, klasses))\n\ndef _is_array_like(endog, exog):\n    try: # do it like this in case of mixed types, ie., ndarray and list\n        endog = np.asarray(endog)\n        exog = np.asarray(exog)\n        return True\n    except:\n        return False\n\ndef _is_using_patsy(endog, exog):\n    # we get this when a structured array is passed through a formula\n    return is_design_matrix(endog) and is_design_matrix(exog)\n\n", "path": "statsmodels/tools/data.py"}], "after_files": [{"content": "\"\"\"\nCompatibility tools for various data structure inputs\n\"\"\"\n\n#TODO: question: interpret_data\n# looks good and could/should be merged with other check convertion functions we also have\n# similar also to what Nathaniel mentioned for Formula\n# good: if ndarray check passes then loading pandas is not triggered,\n\n\nimport numpy as np\n\ndef have_pandas():\n    try:\n        import pandas\n        return True\n    except ImportError:\n        return False\n    except Exception:\n        return False\n\ndef have_patsy():\n    try:\n        import patsy\n        return True\n    except ImportError:\n        return False\n    except Exception:\n        return False\n\ndef is_data_frame(obj):\n    if not have_pandas():\n        return False\n\n    import pandas as pn\n\n    return isinstance(obj, pn.DataFrame)\n\ndef is_design_matrix(obj):\n    if not have_patsy():\n        return False\n\n    from patsy import DesignMatrix\n    return isinstance(obj, DesignMatrix)\n\ndef _is_structured_ndarray(obj):\n    return isinstance(obj, np.ndarray) and obj.dtype.names is not None\n\ndef interpret_data(data, colnames=None, rownames=None):\n    \"\"\"\n    Convert passed data structure to form required by estimation classes\n\n    Parameters\n    ----------\n    data : ndarray-like\n    colnames : sequence or None\n        May be part of data structure\n    rownames : sequence or None\n\n    Returns\n    -------\n    (values, colnames, rownames) : (homogeneous ndarray, list)\n    \"\"\"\n    if isinstance(data, np.ndarray):\n        if _is_structured_ndarray(data):\n            if colnames is None:\n                colnames = data.dtype.names\n            values = struct_to_ndarray(data)\n        else:\n            values = data\n\n        if colnames is None:\n            colnames = ['Y_%d' % i for i in range(values.shape[1])]\n    elif is_data_frame(data):\n        # XXX: hack\n        data = data.dropna()\n        values = data.values\n        colnames = data.columns\n        rownames = data.index\n    else: # pragma: no cover\n        raise Exception('cannot handle other input types at the moment')\n\n    if not isinstance(colnames, list):\n        colnames = list(colnames)\n\n    # sanity check\n    if len(colnames) != values.shape[1]:\n        raise ValueError('length of colnames does not match number '\n                         'of columns in data')\n\n    if rownames is not None and len(rownames) != len(values):\n        raise ValueError('length of rownames does not match number '\n                         'of rows in data')\n\n    return values, colnames, rownames\n\ndef struct_to_ndarray(arr):\n    return arr.view((float, len(arr.dtype.names)))\n\ndef _is_using_ndarray_type(endog, exog):\n    return (type(endog) is np.ndarray and\n            (type(exog) is np.ndarray or exog is None))\n\ndef _is_using_ndarray(endog, exog):\n    return (isinstance(endog, np.ndarray) and\n            (isinstance(exog, np.ndarray) or exog is None))\n\ndef _is_using_pandas(endog, exog):\n    if not have_pandas():\n        return False\n    from pandas import Series, DataFrame, WidePanel\n    klasses = (Series, DataFrame, WidePanel)\n    return (isinstance(endog, klasses) or isinstance(exog, klasses))\n\ndef _is_array_like(endog, exog):\n    try: # do it like this in case of mixed types, ie., ndarray and list\n        endog = np.asarray(endog)\n        exog = np.asarray(exog)\n        return True\n    except:\n        return False\n\ndef _is_using_patsy(endog, exog):\n    # we get this when a structured array is passed through a formula\n    return (is_design_matrix(endog) and\n            (is_design_matrix(exog) or exog is None))\n\n", "path": "statsmodels/tools/data.py"}]}