We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Issues with GClust
## Actual Behavior
1. `n_components_` is always wrong.
2. Doc issues.

## Template Code
Snippet from gclust test.
```GraSPy
np.random.seed(2)

n = 100
d = 3

X1 = np.random.normal(2, 0.5, size=(n, d))
X2 = np.random.normal(-2, 0.5, size=(n, d))
X = np.vstack((X1, X2))

gclust = GaussianCluster(min_components=5)
gclust.fit(X)

print(gclust.n_components_)

>>>>  1
```
Above should be 2. The model selection code based on bic is incorrect and **needs** to be simplified. Above test should be changed to check `n_compnents_ == 2`.

Doc issues (defaults are wrong):
1. https://github.com/neurodata/graspy/blob/master/graspy/cluster/gclust.py#L35
2. https://github.com/neurodata/graspy/blob/master/graspy/cluster/gclust.py#L40

## Your Environment
* Python version: 3.7
* GraSPy version: 0.0.3


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `graspy/cluster/gclust.py`
Content:
```
1 # Copyright 2019 NeuroData (http://neurodata.io)
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import numpy as np
16 import pandas as pd
17 from sklearn.metrics import adjusted_rand_score
18 from sklearn.mixture import GaussianMixture
19 from sklearn.model_selection import ParameterGrid
20 
21 from .base import BaseCluster
22 
23 
24 class GaussianCluster(BaseCluster):
25     r"""
26     Gaussian Mixture Model (GMM)
27 
28     Representation of a Gaussian mixture model probability distribution. 
29     This class allows to estimate the parameters of a Gaussian mixture 
30     distribution. It computes all possible models from one component to 
31     max_components. The best model is given by the lowest BIC score.
32 
33     Parameters
34     ----------
35     min_components : int, defaults to 1. 
36         The minimum number of mixture components to consider (unless
37         max_components=None, in which case this is the maximum number of
38         components to consider). If max_componens is not None, min_components
39         must be less than or equal to max_components.
40     max_components : int, defaults to 1.
41         The maximum number of mixture components to consider. Must be greater 
42         than or equal to min_components.
43 
44     covariance_type : {'full' (default), 'tied', 'diag', 'spherical'}, optional
45         String or list/array describing the type of covariance parameters to use.
46         If a string, it must be one of:
47         
48         - 'full'
49             each component has its own general covariance matrix
50         - 'tied'
51             all components share the same general covariance matrix
52         - 'diag'
53             each component has its own diagonal covariance matrix
54         - 'spherical'
55             each component has its own single variance
56         - 'all'
57             considers all covariance structures in ['spherical', 'diag', 'tied', 'full']
58         If a list/array, it must be a list/array of strings containing only
59             'spherical', 'tied', 'diag', and/or 'spherical'.
60     
61     random_state : int, RandomState instance or None, optional (default=None)
62         If int, random_state is the seed used by the random number generator;
63         If RandomState instance, random_state is the random number generator;
64         If None, the random number generator is the RandomState instance used
65         by ``np.random``.
66 
67     Attributes
68     ----------
69     n_components_ : int
70         Optimal number of components based on BIC.
71     model_ : GaussianMixture object
72         Fitted GaussianMixture object fitted with optimal numeber of components 
73         and optimal covariance structure.
74     bic_ : pandas.DataFrame
75         A pandas DataFrame of BIC values computed for all possible number of clusters
76         given by range(min_components, max_components + 1) and all covariance
77         structures given by covariance_type.
78     ari_ : pandas.DataFrame
79         Only computed when y is given. Pandas Dataframe containing ARI values computed
80         for all possible number of clusters given by range(min_components,
81         max_components) and all covariance structures given by covariance_type.
82     """
83 
84     def __init__(
85         self,
86         min_components=2,
87         max_components=None,
88         covariance_type="full",
89         random_state=None,
90     ):
91         if isinstance(min_components, int):
92             if min_components <= 0:
93                 msg = "min_components must be >= 1."
94                 raise ValueError(msg)
95         else:
96             msg = "min_components must be an integer, not {}.".format(
97                 type(min_components)
98             )
99             raise TypeError(msg)
100 
101         if isinstance(max_components, int):
102             if max_components <= 0:
103                 msg = "max_components must be >= 1 or None."
104                 raise ValueError(msg)
105             elif min_components > max_components:
106                 msg = "min_components must be less than or equal to max_components."
107                 raise ValueError(msg)
108         elif max_components is not None:
109             msg = "max_components must be an integer or None, not {}.".format(
110                 type(max_components)
111             )
112             raise TypeError(msg)
113 
114         if isinstance(covariance_type, np.ndarray):
115             covariance_type = np.unique(covariance_type)
116         elif isinstance(covariance_type, list):
117             covariance_type = np.unique(covariance_type)
118         elif isinstance(covariance_type, str):
119             if covariance_type == "all":
120                 covariance_type = ["spherical", "diag", "tied", "full"]
121             else:
122                 covariance_type = [covariance_type]
123         else:
124             msg = "covariance_type must be a numpy array, a list, or "
125             msg += "string, not {}".format(type(covariance_type))
126             raise TypeError(msg)
127 
128         for cov in covariance_type:
129             if cov not in ["spherical", "diag", "tied", "full"]:
130                 msg = (
131                     "covariance structure must be one of "
132                     + '["spherical", "diag", "tied", "full"]'
133                 )
134                 msg += " not {}".format(cov)
135                 raise ValueError(msg)
136 
137         new_covariance_type = []
138         for cov in ["spherical", "diag", "tied", "full"]:
139             if cov in covariance_type:
140                 new_covariance_type.append(cov)
141 
142         new_covariance_type = np.array(new_covariance_type)
143 
144         self.min_components = min_components
145         self.max_components = max_components
146         self.covariance_type = new_covariance_type
147         self.random_state = random_state
148 
149     def fit(self, X, y=None):
150         """
151         Fits gaussian mixure model to the data. 
152         Estimate model parameters with the EM algorithm.
153 
154         Parameters
155         ----------
156         X : array-like, shape (n_samples, n_features)
157             List of n_features-dimensional data points. Each row
158             corresponds to a single data point.
159         
160         y : array-like, shape (n_samples,), optional (default=None)
161             List of labels for X if available. Used to compute
162             ARI scores.
163 
164         Returns
165         -------
166         self
167         """
168 
169         # Deal with number of clusters
170         if self.max_components is None:
171             lower_ncomponents = 1
172             upper_ncomponents = self.min_components
173         else:
174             lower_ncomponents = self.min_components
175             upper_ncomponents = self.max_components
176 
177         n_mixture_components = upper_ncomponents - lower_ncomponents + 1
178 
179         if upper_ncomponents > X.shape[0]:
180             if self.max_components is None:
181                 msg = "if max_components is None then min_components must be >= "
182                 msg += "n_samples, but min_components = {}, n_samples = {}".format(
183                     upper_ncomponents, X.shape[0]
184                 )
185             else:
186                 msg = "max_components must be >= n_samples, but max_components = "
187                 msg += "{}, n_samples = {}".format(upper_ncomponents, X.shape[0])
188             raise ValueError(msg)
189         elif lower_ncomponents > X.shape[0]:
190             msg = "min_components must be <= n_samples, but min_components = "
191             msg += "{}, n_samples = {}".format(upper_ncomponents, X.shape[0])
192             raise ValueError(msg)
193 
194         # Get parameters
195         random_state = self.random_state
196 
197         param_grid = dict(
198             covariance_type=self.covariance_type,
199             n_components=range(lower_ncomponents, upper_ncomponents + 1),
200             random_state=[random_state],
201         )
202 
203         param_grid = list(ParameterGrid(param_grid))
204 
205         models = [[] for _ in range(n_mixture_components)]
206         bics = [[] for _ in range(n_mixture_components)]
207         aris = [[] for _ in range(n_mixture_components)]
208 
209         for i, params in enumerate(param_grid):
210             model = GaussianMixture(**params)
211             model.fit(X)
212             models[i % n_mixture_components].append(model)
213             bics[i % n_mixture_components].append(model.bic(X))
214             if y is not None:
215                 predictions = model.predict(X)
216                 aris[i % n_mixture_components].append(
217                     adjusted_rand_score(y, predictions)
218                 )
219 
220         self.bic_ = pd.DataFrame(
221             np.array(bics),
222             index=np.arange(lower_ncomponents, upper_ncomponents + 1),
223             columns=self.covariance_type,
224         )
225 
226         if y is not None:
227             self.ari_ = pd.DataFrame(
228                 np.array(aris),
229                 index=np.arange(lower_ncomponents, upper_ncomponents + 1),
230                 columns=self.covariance_type,
231             )
232         else:
233             self.ari_ = None
234 
235         # Finding the minimum bic for each covariance structure
236         bic_mins = [min(bic) for bic in bics]
237         bic_argmins = [np.argmin(bic) for bic in bics]
238 
239         # Find the index for the minimum bic amongst all covariance structures
240         model_type_argmin = np.argmin(bic_mins)
241 
242         self.n_components_ = np.argmin(bics[model_type_argmin]) + 1
243         self.model_ = models[model_type_argmin][bic_argmins[model_type_argmin]]
244 
245         return self
246
```
--- 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/graspy/cluster/gclust.py b/graspy/cluster/gclust.py
--- a/graspy/cluster/gclust.py
+++ b/graspy/cluster/gclust.py
@@ -32,12 +32,13 @@
 
     Parameters
     ----------
-    min_components : int, defaults to 1. 
+    min_components : int, default=2. 
         The minimum number of mixture components to consider (unless
         max_components=None, in which case this is the maximum number of
         components to consider). If max_componens is not None, min_components
         must be less than or equal to max_components.
-    max_components : int, defaults to 1.
+
+    max_components : int or None, default=None.
         The maximum number of mixture components to consider. Must be greater 
         than or equal to min_components.
 
@@ -68,6 +69,8 @@
     ----------
     n_components_ : int
         Optimal number of components based on BIC.
+    covariance_type_ : str
+        Optimal covariance type based on BIC.
     model_ : GaussianMixture object
         Fitted GaussianMixture object fitted with optimal numeber of components 
         and optimal covariance structure.
@@ -111,9 +114,7 @@
             )
             raise TypeError(msg)
 
-        if isinstance(covariance_type, np.ndarray):
-            covariance_type = np.unique(covariance_type)
-        elif isinstance(covariance_type, list):
+        if isinstance(covariance_type, (np.ndarray, list)):
             covariance_type = np.unique(covariance_type)
         elif isinstance(covariance_type, str):
             if covariance_type == "all":
@@ -139,8 +140,6 @@
             if cov in covariance_type:
                 new_covariance_type.append(cov)
 
-        new_covariance_type = np.array(new_covariance_type)
-
         self.min_components = min_components
         self.max_components = max_components
         self.covariance_type = new_covariance_type
@@ -218,28 +217,29 @@
                 )
 
         self.bic_ = pd.DataFrame(
-            np.array(bics),
+            bics,
             index=np.arange(lower_ncomponents, upper_ncomponents + 1),
             columns=self.covariance_type,
         )
 
         if y is not None:
             self.ari_ = pd.DataFrame(
-                np.array(aris),
+                aris,
                 index=np.arange(lower_ncomponents, upper_ncomponents + 1),
                 columns=self.covariance_type,
             )
         else:
             self.ari_ = None
 
-        # Finding the minimum bic for each covariance structure
-        bic_mins = [min(bic) for bic in bics]
-        bic_argmins = [np.argmin(bic) for bic in bics]
+        # Get the best cov type and its index within the dataframe
+        best_covariance = self.bic_.min(axis=0).idxmin()
+        best_covariance_idx = self.covariance_type.index(best_covariance)
 
-        # Find the index for the minimum bic amongst all covariance structures
-        model_type_argmin = np.argmin(bic_mins)
+        # Get the index best component for best_covariance
+        best_component = self.bic_.idxmin()[best_covariance]
 
-        self.n_components_ = np.argmin(bics[model_type_argmin]) + 1
-        self.model_ = models[model_type_argmin][bic_argmins[model_type_argmin]]
+        self.n_components_ = best_component
+        self.covariance_type_ = best_covariance
+        self.model_ = models[best_component - 1][best_covariance_idx]
 
         return self

{"golden_diff": "diff --git a/graspy/cluster/gclust.py b/graspy/cluster/gclust.py\n--- a/graspy/cluster/gclust.py\n+++ b/graspy/cluster/gclust.py\n@@ -32,12 +32,13 @@\n \n     Parameters\n     ----------\n-    min_components : int, defaults to 1. \n+    min_components : int, default=2. \n         The minimum number of mixture components to consider (unless\n         max_components=None, in which case this is the maximum number of\n         components to consider). If max_componens is not None, min_components\n         must be less than or equal to max_components.\n-    max_components : int, defaults to 1.\n+\n+    max_components : int or None, default=None.\n         The maximum number of mixture components to consider. Must be greater \n         than or equal to min_components.\n \n@@ -68,6 +69,8 @@\n     ----------\n     n_components_ : int\n         Optimal number of components based on BIC.\n+    covariance_type_ : str\n+        Optimal covariance type based on BIC.\n     model_ : GaussianMixture object\n         Fitted GaussianMixture object fitted with optimal numeber of components \n         and optimal covariance structure.\n@@ -111,9 +114,7 @@\n             )\n             raise TypeError(msg)\n \n-        if isinstance(covariance_type, np.ndarray):\n-            covariance_type = np.unique(covariance_type)\n-        elif isinstance(covariance_type, list):\n+        if isinstance(covariance_type, (np.ndarray, list)):\n             covariance_type = np.unique(covariance_type)\n         elif isinstance(covariance_type, str):\n             if covariance_type == \"all\":\n@@ -139,8 +140,6 @@\n             if cov in covariance_type:\n                 new_covariance_type.append(cov)\n \n-        new_covariance_type = np.array(new_covariance_type)\n-\n         self.min_components = min_components\n         self.max_components = max_components\n         self.covariance_type = new_covariance_type\n@@ -218,28 +217,29 @@\n                 )\n \n         self.bic_ = pd.DataFrame(\n-            np.array(bics),\n+            bics,\n             index=np.arange(lower_ncomponents, upper_ncomponents + 1),\n             columns=self.covariance_type,\n         )\n \n         if y is not None:\n             self.ari_ = pd.DataFrame(\n-                np.array(aris),\n+                aris,\n                 index=np.arange(lower_ncomponents, upper_ncomponents + 1),\n                 columns=self.covariance_type,\n             )\n         else:\n             self.ari_ = None\n \n-        # Finding the minimum bic for each covariance structure\n-        bic_mins = [min(bic) for bic in bics]\n-        bic_argmins = [np.argmin(bic) for bic in bics]\n+        # Get the best cov type and its index within the dataframe\n+        best_covariance = self.bic_.min(axis=0).idxmin()\n+        best_covariance_idx = self.covariance_type.index(best_covariance)\n \n-        # Find the index for the minimum bic amongst all covariance structures\n-        model_type_argmin = np.argmin(bic_mins)\n+        # Get the index best component for best_covariance\n+        best_component = self.bic_.idxmin()[best_covariance]\n \n-        self.n_components_ = np.argmin(bics[model_type_argmin]) + 1\n-        self.model_ = models[model_type_argmin][bic_argmins[model_type_argmin]]\n+        self.n_components_ = best_component\n+        self.covariance_type_ = best_covariance\n+        self.model_ = models[best_component - 1][best_covariance_idx]\n \n         return self\n", "issue": "Issues with GClust\n## Actual Behavior\r\n1. `n_components_` is always wrong.\r\n2. Doc issues.\r\n\r\n## Template Code\r\nSnippet from gclust test.\r\n```GraSPy\r\nnp.random.seed(2)\r\n\r\nn = 100\r\nd = 3\r\n\r\nX1 = np.random.normal(2, 0.5, size=(n, d))\r\nX2 = np.random.normal(-2, 0.5, size=(n, d))\r\nX = np.vstack((X1, X2))\r\n\r\ngclust = GaussianCluster(min_components=5)\r\ngclust.fit(X)\r\n\r\nprint(gclust.n_components_)\r\n\r\n>>>>  1\r\n```\r\nAbove should be 2. The model selection code based on bic is incorrect and **needs** to be simplified. Above test should be changed to check `n_compnents_ == 2`.\r\n\r\nDoc issues (defaults are wrong):\r\n1. https://github.com/neurodata/graspy/blob/master/graspy/cluster/gclust.py#L35\r\n2. https://github.com/neurodata/graspy/blob/master/graspy/cluster/gclust.py#L40\r\n\r\n## Your Environment\r\n* Python version: 3.7\r\n* GraSPy version: 0.0.3\r\n\n", "before_files": [{"content": "# Copyright 2019 NeuroData (http://neurodata.io)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.metrics import adjusted_rand_score\nfrom sklearn.mixture import GaussianMixture\nfrom sklearn.model_selection import ParameterGrid\n\nfrom .base import BaseCluster\n\n\nclass GaussianCluster(BaseCluster):\n    r\"\"\"\n    Gaussian Mixture Model (GMM)\n\n    Representation of a Gaussian mixture model probability distribution. \n    This class allows to estimate the parameters of a Gaussian mixture \n    distribution. It computes all possible models from one component to \n    max_components. The best model is given by the lowest BIC score.\n\n    Parameters\n    ----------\n    min_components : int, defaults to 1. \n        The minimum number of mixture components to consider (unless\n        max_components=None, in which case this is the maximum number of\n        components to consider). If max_componens is not None, min_components\n        must be less than or equal to max_components.\n    max_components : int, defaults to 1.\n        The maximum number of mixture components to consider. Must be greater \n        than or equal to min_components.\n\n    covariance_type : {'full' (default), 'tied', 'diag', 'spherical'}, optional\n        String or list/array describing the type of covariance parameters to use.\n        If a string, it must be one of:\n        \n        - 'full'\n            each component has its own general covariance matrix\n        - 'tied'\n            all components share the same general covariance matrix\n        - 'diag'\n            each component has its own diagonal covariance matrix\n        - 'spherical'\n            each component has its own single variance\n        - 'all'\n            considers all covariance structures in ['spherical', 'diag', 'tied', 'full']\n        If a list/array, it must be a list/array of strings containing only\n            'spherical', 'tied', 'diag', and/or 'spherical'.\n    \n    random_state : int, RandomState instance or None, optional (default=None)\n        If int, random_state is the seed used by the random number generator;\n        If RandomState instance, random_state is the random number generator;\n        If None, the random number generator is the RandomState instance used\n        by ``np.random``.\n\n    Attributes\n    ----------\n    n_components_ : int\n        Optimal number of components based on BIC.\n    model_ : GaussianMixture object\n        Fitted GaussianMixture object fitted with optimal numeber of components \n        and optimal covariance structure.\n    bic_ : pandas.DataFrame\n        A pandas DataFrame of BIC values computed for all possible number of clusters\n        given by range(min_components, max_components + 1) and all covariance\n        structures given by covariance_type.\n    ari_ : pandas.DataFrame\n        Only computed when y is given. Pandas Dataframe containing ARI values computed\n        for all possible number of clusters given by range(min_components,\n        max_components) and all covariance structures given by covariance_type.\n    \"\"\"\n\n    def __init__(\n        self,\n        min_components=2,\n        max_components=None,\n        covariance_type=\"full\",\n        random_state=None,\n    ):\n        if isinstance(min_components, int):\n            if min_components <= 0:\n                msg = \"min_components must be >= 1.\"\n                raise ValueError(msg)\n        else:\n            msg = \"min_components must be an integer, not {}.\".format(\n                type(min_components)\n            )\n            raise TypeError(msg)\n\n        if isinstance(max_components, int):\n            if max_components <= 0:\n                msg = \"max_components must be >= 1 or None.\"\n                raise ValueError(msg)\n            elif min_components > max_components:\n                msg = \"min_components must be less than or equal to max_components.\"\n                raise ValueError(msg)\n        elif max_components is not None:\n            msg = \"max_components must be an integer or None, not {}.\".format(\n                type(max_components)\n            )\n            raise TypeError(msg)\n\n        if isinstance(covariance_type, np.ndarray):\n            covariance_type = np.unique(covariance_type)\n        elif isinstance(covariance_type, list):\n            covariance_type = np.unique(covariance_type)\n        elif isinstance(covariance_type, str):\n            if covariance_type == \"all\":\n                covariance_type = [\"spherical\", \"diag\", \"tied\", \"full\"]\n            else:\n                covariance_type = [covariance_type]\n        else:\n            msg = \"covariance_type must be a numpy array, a list, or \"\n            msg += \"string, not {}\".format(type(covariance_type))\n            raise TypeError(msg)\n\n        for cov in covariance_type:\n            if cov not in [\"spherical\", \"diag\", \"tied\", \"full\"]:\n                msg = (\n                    \"covariance structure must be one of \"\n                    + '[\"spherical\", \"diag\", \"tied\", \"full\"]'\n                )\n                msg += \" not {}\".format(cov)\n                raise ValueError(msg)\n\n        new_covariance_type = []\n        for cov in [\"spherical\", \"diag\", \"tied\", \"full\"]:\n            if cov in covariance_type:\n                new_covariance_type.append(cov)\n\n        new_covariance_type = np.array(new_covariance_type)\n\n        self.min_components = min_components\n        self.max_components = max_components\n        self.covariance_type = new_covariance_type\n        self.random_state = random_state\n\n    def fit(self, X, y=None):\n        \"\"\"\n        Fits gaussian mixure model to the data. \n        Estimate model parameters with the EM algorithm.\n\n        Parameters\n        ----------\n        X : array-like, shape (n_samples, n_features)\n            List of n_features-dimensional data points. Each row\n            corresponds to a single data point.\n        \n        y : array-like, shape (n_samples,), optional (default=None)\n            List of labels for X if available. Used to compute\n            ARI scores.\n\n        Returns\n        -------\n        self\n        \"\"\"\n\n        # Deal with number of clusters\n        if self.max_components is None:\n            lower_ncomponents = 1\n            upper_ncomponents = self.min_components\n        else:\n            lower_ncomponents = self.min_components\n            upper_ncomponents = self.max_components\n\n        n_mixture_components = upper_ncomponents - lower_ncomponents + 1\n\n        if upper_ncomponents > X.shape[0]:\n            if self.max_components is None:\n                msg = \"if max_components is None then min_components must be >= \"\n                msg += \"n_samples, but min_components = {}, n_samples = {}\".format(\n                    upper_ncomponents, X.shape[0]\n                )\n            else:\n                msg = \"max_components must be >= n_samples, but max_components = \"\n                msg += \"{}, n_samples = {}\".format(upper_ncomponents, X.shape[0])\n            raise ValueError(msg)\n        elif lower_ncomponents > X.shape[0]:\n            msg = \"min_components must be <= n_samples, but min_components = \"\n            msg += \"{}, n_samples = {}\".format(upper_ncomponents, X.shape[0])\n            raise ValueError(msg)\n\n        # Get parameters\n        random_state = self.random_state\n\n        param_grid = dict(\n            covariance_type=self.covariance_type,\n            n_components=range(lower_ncomponents, upper_ncomponents + 1),\n            random_state=[random_state],\n        )\n\n        param_grid = list(ParameterGrid(param_grid))\n\n        models = [[] for _ in range(n_mixture_components)]\n        bics = [[] for _ in range(n_mixture_components)]\n        aris = [[] for _ in range(n_mixture_components)]\n\n        for i, params in enumerate(param_grid):\n            model = GaussianMixture(**params)\n            model.fit(X)\n            models[i % n_mixture_components].append(model)\n            bics[i % n_mixture_components].append(model.bic(X))\n            if y is not None:\n                predictions = model.predict(X)\n                aris[i % n_mixture_components].append(\n                    adjusted_rand_score(y, predictions)\n                )\n\n        self.bic_ = pd.DataFrame(\n            np.array(bics),\n            index=np.arange(lower_ncomponents, upper_ncomponents + 1),\n            columns=self.covariance_type,\n        )\n\n        if y is not None:\n            self.ari_ = pd.DataFrame(\n                np.array(aris),\n                index=np.arange(lower_ncomponents, upper_ncomponents + 1),\n                columns=self.covariance_type,\n            )\n        else:\n            self.ari_ = None\n\n        # Finding the minimum bic for each covariance structure\n        bic_mins = [min(bic) for bic in bics]\n        bic_argmins = [np.argmin(bic) for bic in bics]\n\n        # Find the index for the minimum bic amongst all covariance structures\n        model_type_argmin = np.argmin(bic_mins)\n\n        self.n_components_ = np.argmin(bics[model_type_argmin]) + 1\n        self.model_ = models[model_type_argmin][bic_argmins[model_type_argmin]]\n\n        return self\n", "path": "graspy/cluster/gclust.py"}], "after_files": [{"content": "# Copyright 2019 NeuroData (http://neurodata.io)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.metrics import adjusted_rand_score\nfrom sklearn.mixture import GaussianMixture\nfrom sklearn.model_selection import ParameterGrid\n\nfrom .base import BaseCluster\n\n\nclass GaussianCluster(BaseCluster):\n    r\"\"\"\n    Gaussian Mixture Model (GMM)\n\n    Representation of a Gaussian mixture model probability distribution. \n    This class allows to estimate the parameters of a Gaussian mixture \n    distribution. It computes all possible models from one component to \n    max_components. The best model is given by the lowest BIC score.\n\n    Parameters\n    ----------\n    min_components : int, default=2. \n        The minimum number of mixture components to consider (unless\n        max_components=None, in which case this is the maximum number of\n        components to consider). If max_componens is not None, min_components\n        must be less than or equal to max_components.\n\n    max_components : int or None, default=None.\n        The maximum number of mixture components to consider. Must be greater \n        than or equal to min_components.\n\n    covariance_type : {'full' (default), 'tied', 'diag', 'spherical'}, optional\n        String or list/array describing the type of covariance parameters to use.\n        If a string, it must be one of:\n        \n        - 'full'\n            each component has its own general covariance matrix\n        - 'tied'\n            all components share the same general covariance matrix\n        - 'diag'\n            each component has its own diagonal covariance matrix\n        - 'spherical'\n            each component has its own single variance\n        - 'all'\n            considers all covariance structures in ['spherical', 'diag', 'tied', 'full']\n        If a list/array, it must be a list/array of strings containing only\n            'spherical', 'tied', 'diag', and/or 'spherical'.\n    \n    random_state : int, RandomState instance or None, optional (default=None)\n        If int, random_state is the seed used by the random number generator;\n        If RandomState instance, random_state is the random number generator;\n        If None, the random number generator is the RandomState instance used\n        by ``np.random``.\n\n    Attributes\n    ----------\n    n_components_ : int\n        Optimal number of components based on BIC.\n    covariance_type_ : str\n        Optimal covariance type based on BIC.\n    model_ : GaussianMixture object\n        Fitted GaussianMixture object fitted with optimal numeber of components \n        and optimal covariance structure.\n    bic_ : pandas.DataFrame\n        A pandas DataFrame of BIC values computed for all possible number of clusters\n        given by range(min_components, max_components + 1) and all covariance\n        structures given by covariance_type.\n    ari_ : pandas.DataFrame\n        Only computed when y is given. Pandas Dataframe containing ARI values computed\n        for all possible number of clusters given by range(min_components,\n        max_components) and all covariance structures given by covariance_type.\n    \"\"\"\n\n    def __init__(\n        self,\n        min_components=2,\n        max_components=None,\n        covariance_type=\"full\",\n        random_state=None,\n    ):\n        if isinstance(min_components, int):\n            if min_components <= 0:\n                msg = \"min_components must be >= 1.\"\n                raise ValueError(msg)\n        else:\n            msg = \"min_components must be an integer, not {}.\".format(\n                type(min_components)\n            )\n            raise TypeError(msg)\n\n        if isinstance(max_components, int):\n            if max_components <= 0:\n                msg = \"max_components must be >= 1 or None.\"\n                raise ValueError(msg)\n            elif min_components > max_components:\n                msg = \"min_components must be less than or equal to max_components.\"\n                raise ValueError(msg)\n        elif max_components is not None:\n            msg = \"max_components must be an integer or None, not {}.\".format(\n                type(max_components)\n            )\n            raise TypeError(msg)\n\n        if isinstance(covariance_type, (np.ndarray, list)):\n            covariance_type = np.unique(covariance_type)\n        elif isinstance(covariance_type, str):\n            if covariance_type == \"all\":\n                covariance_type = [\"spherical\", \"diag\", \"tied\", \"full\"]\n            else:\n                covariance_type = [covariance_type]\n        else:\n            msg = \"covariance_type must be a numpy array, a list, or \"\n            msg += \"string, not {}\".format(type(covariance_type))\n            raise TypeError(msg)\n\n        for cov in covariance_type:\n            if cov not in [\"spherical\", \"diag\", \"tied\", \"full\"]:\n                msg = (\n                    \"covariance structure must be one of \"\n                    + '[\"spherical\", \"diag\", \"tied\", \"full\"]'\n                )\n                msg += \" not {}\".format(cov)\n                raise ValueError(msg)\n\n        new_covariance_type = []\n        for cov in [\"spherical\", \"diag\", \"tied\", \"full\"]:\n            if cov in covariance_type:\n                new_covariance_type.append(cov)\n\n        self.min_components = min_components\n        self.max_components = max_components\n        self.covariance_type = new_covariance_type\n        self.random_state = random_state\n\n    def fit(self, X, y=None):\n        \"\"\"\n        Fits gaussian mixure model to the data. \n        Estimate model parameters with the EM algorithm.\n\n        Parameters\n        ----------\n        X : array-like, shape (n_samples, n_features)\n            List of n_features-dimensional data points. Each row\n            corresponds to a single data point.\n        \n        y : array-like, shape (n_samples,), optional (default=None)\n            List of labels for X if available. Used to compute\n            ARI scores.\n\n        Returns\n        -------\n        self\n        \"\"\"\n\n        # Deal with number of clusters\n        if self.max_components is None:\n            lower_ncomponents = 1\n            upper_ncomponents = self.min_components\n        else:\n            lower_ncomponents = self.min_components\n            upper_ncomponents = self.max_components\n\n        n_mixture_components = upper_ncomponents - lower_ncomponents + 1\n\n        if upper_ncomponents > X.shape[0]:\n            if self.max_components is None:\n                msg = \"if max_components is None then min_components must be >= \"\n                msg += \"n_samples, but min_components = {}, n_samples = {}\".format(\n                    upper_ncomponents, X.shape[0]\n                )\n            else:\n                msg = \"max_components must be >= n_samples, but max_components = \"\n                msg += \"{}, n_samples = {}\".format(upper_ncomponents, X.shape[0])\n            raise ValueError(msg)\n        elif lower_ncomponents > X.shape[0]:\n            msg = \"min_components must be <= n_samples, but min_components = \"\n            msg += \"{}, n_samples = {}\".format(upper_ncomponents, X.shape[0])\n            raise ValueError(msg)\n\n        # Get parameters\n        random_state = self.random_state\n\n        param_grid = dict(\n            covariance_type=self.covariance_type,\n            n_components=range(lower_ncomponents, upper_ncomponents + 1),\n            random_state=[random_state],\n        )\n\n        param_grid = list(ParameterGrid(param_grid))\n\n        models = [[] for _ in range(n_mixture_components)]\n        bics = [[] for _ in range(n_mixture_components)]\n        aris = [[] for _ in range(n_mixture_components)]\n\n        for i, params in enumerate(param_grid):\n            model = GaussianMixture(**params)\n            model.fit(X)\n            models[i % n_mixture_components].append(model)\n            bics[i % n_mixture_components].append(model.bic(X))\n            if y is not None:\n                predictions = model.predict(X)\n                aris[i % n_mixture_components].append(\n                    adjusted_rand_score(y, predictions)\n                )\n\n        self.bic_ = pd.DataFrame(\n            bics,\n            index=np.arange(lower_ncomponents, upper_ncomponents + 1),\n            columns=self.covariance_type,\n        )\n\n        if y is not None:\n            self.ari_ = pd.DataFrame(\n                aris,\n                index=np.arange(lower_ncomponents, upper_ncomponents + 1),\n                columns=self.covariance_type,\n            )\n        else:\n            self.ari_ = None\n\n        # Get the best cov type and its index within the dataframe\n        best_covariance = self.bic_.min(axis=0).idxmin()\n        best_covariance_idx = self.covariance_type.index(best_covariance)\n\n        # Get the index best component for best_covariance\n        best_component = self.bic_.idxmin()[best_covariance]\n\n        self.n_components_ = best_component\n        self.covariance_type_ = best_covariance\n        self.model_ = models[best_component - 1][best_covariance_idx]\n\n        return self\n", "path": "graspy/cluster/gclust.py"}]}