Repository: graspologic-org/graspologic

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



Discussion:

Resolving patch:
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 @@ class GaussianCluster(BaseCluster):
 
     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 @@ class GaussianCluster(BaseCluster):
     ----------
     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 @@ def __init__(
             )
             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 @@ def __init__(
             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 @@ def fit(self, X, y=None):
                 )
 
         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


Test patch:
diff --git a/tests/cluster/test_gclust.py b/tests/cluster/test_gclust.py
--- a/tests/cluster/test_gclust.py
+++ b/tests/cluster/test_gclust.py
@@ -95,8 +95,7 @@ def test_no_y():
     gclust = GaussianCluster(min_components=5)
     gclust.fit(X)
 
-    bics = gclust.bic_
-    assert_equal(bics.iloc[:, 0].values.argmin(), 1)
+    assert_equal(gclust.n_components_, 2)
 
 
 def test_outputs():
@@ -118,15 +117,14 @@ def test_outputs():
 
         gclust = GaussianCluster(min_components=5)
         gclust.fit(X, y)
-        bics = gclust.bic_
-        aris = gclust.ari_
 
-        bic_argmin = bics.iloc[:, 0].values.argmin()
+        n_components = gclust.n_components_
 
         # Assert that the two cluster model is the best
-        assert_equal(bic_argmin, 1)
-        # The plus one is to adjust the index by min_components
-        assert_allclose(aris.iloc[:, 0][bic_argmin + 1], 1)
+        assert_equal(n_components, 2)
+
+        # Asser that we get perfect clustering
+        assert_allclose(gclust.ari_.loc[n_components], 1)
 
 
 def test_bic():
@@ -153,14 +151,13 @@ def test_bic():
         gclust = GaussianCluster(min_components=10)
         gclust.fit(X_hat, y)
 
-        bics = gclust.bic_
-        aris = gclust.ari_
+        n_components = gclust.n_components_
 
-        bic_argmin = bics.iloc[:, 0].values.argmin()
+        # Assert that the three cluster model is the best
+        assert_equal(n_components, 3)
 
-        assert_equal(2, bic_argmin)
-        # The plus one is to adjust the index by min_components
-        assert_allclose(1, aris.iloc[:, 0][bic_argmin + 1])
+        # Asser that we get perfect clustering
+        assert_allclose(gclust.ari_.loc[n_components], 1)
 
 
 def test_covariances():
@@ -183,9 +180,9 @@ def test_covariances():
 
     X = np.concatenate((X1, X2))
 
-    gclust_object = GaussianCluster(min_components=2, covariance_type="all")
-    gclust_object.fit(X)
-    assert_equal(gclust_object.bic_.iloc[1, :].values.argmin(), 0)
+    gclust = GaussianCluster(min_components=2, covariance_type="all")
+    gclust.fit(X)
+    assert_equal(gclust.covariance_type_, "spherical")
 
     # Diagonal
     np.random.seed(10)
@@ -197,9 +194,9 @@ def test_covariances():
 
     X = np.concatenate((X1, X2))
 
-    gclust_object = GaussianCluster(min_components=2, covariance_type="all")
-    gclust_object.fit(X)
-    assert_equal(gclust_object.bic_.iloc[1, :].values.argmin(), 1)
+    gclust = GaussianCluster(min_components=2, covariance_type="all")
+    gclust.fit(X)
+    assert_equal(gclust.covariance_type_, "diag")
 
     # Tied
     cov1 = np.array([[2, 1], [1, 2]])
@@ -210,9 +207,9 @@ def test_covariances():
 
     X = np.concatenate((X1, X2))
 
-    gclust_object = GaussianCluster(min_components=2, covariance_type="all")
-    gclust_object.fit(X)
-    assert_equal(gclust_object.bic_.iloc[1, :].values.argmin(), 2)
+    gclust = GaussianCluster(min_components=2, covariance_type="all")
+    gclust.fit(X)
+    assert_equal(gclust.covariance_type_, "tied")
 
     # Full
     cov1 = np.array([[2, -1], [-1, 2]])
@@ -223,6 +220,6 @@ def test_covariances():
 
     X = np.concatenate((X1, X2))
 
-    gclust_object = GaussianCluster(min_components=2, covariance_type="all")
-    gclust_object.fit(X)
-    assert_equal(gclust_object.bic_.iloc[1, :].values.argmin(), 3)
+    gclust = GaussianCluster(min_components=2, covariance_type="all")
+    gclust.fit(X)
+    assert_equal(gclust.covariance_type_, "full")