We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Error from KElbowVisualizer's distortion_score method
`KElbowVisualizer` is generating a `ValueError` from Sklearn.

### Issue

Using the Yellowbrick `hobbies` corpus and vectorizing with TFIDF, the KElbowVisualizer is generating an error from Sklearn: `ValueError: Found array with dim 3. check_pairwise_arrays expected <= 2`. The `distortion_score` method appears to be causing the error when calling Sklearn's `pairwise_distances` method.

### Code
```
corpus = load_corpus('hobbies')
tfidf  = TfidfVectorizer()
docs   = tfidf.fit_transform(corpus)
visualizer = KElbowVisualizer(KMeans(), k=(4, 8))
visualizer.fit(docs)
visualizer.poof()
```

### Error
```
Traceback (most recent call last):
  File "elbows.py", line 82, in <module>
    visualizer.fit(docs)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yellowbrick/cluster/elbow.py", line 245, in fit
    self.scoring_metric(X, self.estimator.labels_)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yellowbrick/cluster/elbow.py", line 87, in distortion_score
    distances = pairwise_distances(instances, [center], metric=metric)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/metrics/pairwise.py", line 1247, in pairwise_distances
    return _parallel_pairwise(X, Y, func, n_jobs, **kwds)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/metrics/pairwise.py", line 1090, in _parallel_pairwise
    return func(X, Y, **kwds)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/metrics/pairwise.py", line 223, in euclidean_distances
    X, Y = check_pairwise_arrays(X, Y)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/metrics/pairwise.py", line 112, in check_pairwise_arrays
    warn_on_dtype=warn_on_dtype, estimator=estimator)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/utils/validation.py", line 451, in check_array
    % (array.ndim, estimator_name))
ValueError: Found array with dim 3. check_pairwise_arrays expected <= 2.
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `yellowbrick/cluster/elbow.py`
Content:
```
1 # yellowbrick.cluster.elbow
2 # Implements the elbow method for determining the optimal number of clusters.
3 #
4 # Author:   Benjamin Bengfort <bbengfort@districtdatalabs.com>
5 # Created:  Thu Mar 23 22:36:31 2017 -0400
6 #
7 # Copyright (C) 2016 District Data Labs
8 # For license information, see LICENSE.txt
9 #
10 # ID: elbow.py [5a370c8] benjamin@bengfort.com $
11 
12 """
13 Implements the elbow method for determining the optimal number of clusters.
14 https://bl.ocks.org/rpgove/0060ff3b656618e9136b
15 """
16 
17 ##########################################################################
18 ## Imports
19 ##########################################################################
20 
21 import time
22 
23 from .base import ClusteringScoreVisualizer
24 from ..exceptions import YellowbrickValueError
25 
26 from sklearn.metrics import silhouette_score
27 from sklearn.metrics import calinski_harabaz_score
28 from sklearn.metrics.pairwise import pairwise_distances
29 from sklearn.preprocessing import LabelEncoder
30 
31 
32 ## Packages for export
33 __all__ = [
34     "KElbowVisualizer", "distortion_score"
35 ]
36 
37 
38 ##########################################################################
39 ## Metrics
40 ##########################################################################
41 
42 def distortion_score(X, labels, metric='euclidean'):
43     """
44     Compute the mean distortion of all samples.
45 
46     The distortion is computed as the the sum of the squared distances between
47     each observation and its closest centroid. Logically, this is the metric
48     that K-Means attempts to minimize as it is fitting the model.
49 
50     .. seealso:: http://kldavenport.com/the-cost-function-of-k-means/
51 
52     Parameters
53     ----------
54     X : array, shape = [n_samples, n_features] or [n_samples_a, n_samples_a]
55         Array of pairwise distances between samples if metric == "precomputed"
56         or a feature array for computing distances against the labels.
57 
58     labels : array, shape = [n_samples]
59         Predicted labels for each sample
60 
61     metric : string
62         The metric to use when calculating distance between instances in a
63         feature array. If metric is a string, it must be one of the options
64         allowed by `sklearn.metrics.pairwise.pairwise_distances
65         <http://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.pairwise_distances.html#sklearn.metrics.pairwise.pairwise_distances>`_
66 
67     .. todo:: add sample_size and random_state kwds similar to silhouette_score
68     """
69     # Encode labels to get unique centers and groups
70     le = LabelEncoder()
71     labels = le.fit_transform(labels)
72     unique_labels = le.classes_
73 
74     # Sum of the distortions
75     distortion = 0
76 
77     # Loop through each label (center) to compute the centroid
78     for current_label in unique_labels:
79         # Mask the instances that belong to the current label
80         mask = labels == current_label
81         instances = X[mask]
82 
83         # Compute the center of these instances
84         center = instances.mean(axis=0)
85 
86         # Compute the square distances from the instances to the center
87         distances = pairwise_distances(instances, [center], metric=metric)
88         distances = distances ** 2
89 
90         # Add the mean square distance to the distortion
91         distortion += distances.mean()
92 
93     return distortion
94 
95 
96 ##########################################################################
97 ## Elbow Method
98 ##########################################################################
99 
100 KELBOW_SCOREMAP = {
101     "distortion": distortion_score,
102     "silhouette": silhouette_score,
103     "calinski_harabaz": calinski_harabaz_score,
104 }
105 
106 
107 class KElbowVisualizer(ClusteringScoreVisualizer):
108     """
109     The K-Elbow Visualizer implements the "elbow" method of selecting the
110     optimal number of clusters for K-means clustering. K-means is a simple
111     unsupervised machine learning algorithm that groups data into a specified
112     number (k) of clusters. Because the user must specify in advance what k to
113     choose, the algorithm is somewhat naive -- it assigns all members to k
114     clusters even if that is not the right k for the dataset.
115 
116     The elbow method runs k-means clustering on the dataset for a range of
117     values for k (say from 1-10) and then for each value of k computes an
118     average score for all clusters. By default, the ``distortion_score`` is
119     computed, the sum of square distances from each point to its assigned
120     center. Other metrics can also be used such as the ``silhouette_score``,
121     the mean silhouette  coefficient for all samples or the
122     ``calinski_harabaz_score``, which computes the ratio of dispersion between
123     and within clusters.
124 
125     When these overall metrics for each model are plotted, it is possible to
126     visually determine the best value for K. If the line chart looks like an
127     arm, then the "elbow" (the point of inflection on the curve) is the best
128     value of k. The "arm" can be either up or down, but if there is a strong
129     inflection point, it is a good indication that the underlying model fits
130     best at that point.
131 
132     Parameters
133     ----------
134 
135     model : a Scikit-Learn clusterer
136         Should be an instance of a clusterer, specifically ``KMeans`` or
137         ``MiniBatchKMeans``. If it is not a clusterer, an exception is raised.
138 
139     ax : matplotlib Axes, default: None
140         The axes to plot the figure on. If None is passed in the current axes
141         will be used (or generated if required).
142 
143     k : integer or tuple
144         The range of k to compute silhouette scores for. If a single integer
145         is specified, then will compute the range (2,k) otherwise the
146         specified range in the tuple is used.
147 
148     metric : string, default: ``"distortion"``
149         Select the scoring metric to evaluate the clusters. The default is the
150         mean distortion, defined by the sum of squared distances between each
151         observation and its closest centroid. Other metrics include:
152 
153         - **distortion**: mean sum of squared distances to centers
154         - **silhouette**: mean ratio of intra-cluster and nearest-cluster distance
155         - **calinski_harabaz**: ratio of within to between cluster dispersion
156 
157     timings : bool, default: True
158         Display the fitting time per k to evaluate the amount of time required
159         to train the clustering model.
160 
161     kwargs : dict
162         Keyword arguments that are passed to the base class and may influence
163         the visualization as defined in other Visualizers.
164 
165     Examples
166     --------
167 
168     >>> from yellowbrick.cluster import KElbowVisualizer
169     >>> from sklearn.cluster import KMeans
170     >>> model = KElbowVisualizer(KMeans(), k=10)
171     >>> model.fit(X)
172     >>> model.poof()
173 
174     Notes
175     -----
176 
177     If you get a visualizer that doesn't have an elbow or inflection point,
178     then this method may not be working. The elbow method does not work well
179     if the data is not very clustered; in this case you might see a smooth
180     curve and the value of k is unclear. Other scoring methods such as BIC or
181     SSE also can be used to explore if clustering is a correct choice.
182 
183     For a discussion on the Elbow method, read more at
184     `Robert Gove's Block <https://bl.ocks.org/rpgove/0060ff3b656618e9136b>`_.
185 
186     .. todo:: add parallelization option for performance
187     .. todo:: add different metrics for scores and silhoutte
188     .. todo:: add timing information about how long its taking
189     """
190 
191     def __init__(self, model, ax=None, k=10,
192                  metric="distortion", timings=True, **kwargs):
193         super(KElbowVisualizer, self).__init__(model, ax=ax, **kwargs)
194 
195         # Get the scoring method
196         if metric not in KELBOW_SCOREMAP:
197             raise YellowbrickValueError(
198                 "'{}' is not a defined metric "
199                 "use one of distortion, silhouette, or calinski_harabaz"
200             )
201 
202         # Store the arguments
203         self.scoring_metric = KELBOW_SCOREMAP[metric]
204         self.timings = timings
205 
206         # Convert K into a tuple argument if an integer
207         if isinstance(k, int):
208             k = (2, k+1)
209 
210         # Expand k in to the values we will use, capturing exceptions
211         try:
212             k = tuple(k)
213             self.k_values_ = list(range(*k))
214         except:
215             raise YellowbrickValueError((
216                 "Specify a range or maximal K value, the value '{}' "
217                 "is not a valid argument for K.".format(k)
218             ))
219 
220 
221         # Holds the values of the silhoutte scores
222         self.k_scores_ = None
223 
224     def fit(self, X, y=None, **kwargs):
225         """
226         Fits n KMeans models where n is the length of ``self.k_values_``,
227         storing the silhoutte scores in the ``self.k_scores_`` attribute.
228         This method finishes up by calling draw to create the plot.
229         """
230 
231         self.k_scores_ = []
232         self.k_timers_ = []
233 
234         for k in self.k_values_:
235             # Compute the start time for each  model
236             start = time.time()
237 
238             # Set the k value and fit the model
239             self.estimator.set_params(n_clusters=k)
240             self.estimator.fit(X)
241 
242             # Append the time and score to our plottable metrics
243             self.k_timers_.append(time.time() - start)
244             self.k_scores_.append(
245                 self.scoring_metric(X, self.estimator.labels_)
246             )
247 
248         self.draw()
249 
250         return self
251 
252     def draw(self):
253         """
254         Draw the elbow curve for the specified scores and values of K.
255         """
256         # Plot the silhouette score against k
257         self.ax.plot(self.k_values_, self.k_scores_, marker="D", label="score")
258 
259         # If we're going to plot the timings, create a twinx axis
260         if self.timings:
261             self.axes = [self.ax, self.ax.twinx()]
262             self.axes[1].plot(
263                 self.k_values_, self.k_timers_, label="fit time",
264                 c='g', marker="o", linestyle="--", alpha=0.75,
265             )
266 
267         return self.ax
268 
269     def finalize(self):
270         """
271         Prepare the figure for rendering by setting the title as well as the
272         X and Y axis labels and adding the legend.
273         """
274         # Get the metric name
275         metric = self.scoring_metric.__name__.replace("_", " ").title()
276 
277         # Set the title
278         self.set_title(
279             '{} Elbow for {} Clustering'.format(metric, self.name)
280         )
281 
282         # Set the x and y labels
283         self.ax.set_xlabel('k')
284         self.ax.set_ylabel(metric.lower())
285 
286         # Set the second y axis labels
287         if self.timings:
288             self.axes[1].grid(False)
289             self.axes[1].set_ylabel("fit time (seconds)", color='g')
290             self.axes[1].tick_params('y', colors='g')
291
```
--- 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/yellowbrick/cluster/elbow.py b/yellowbrick/cluster/elbow.py
--- a/yellowbrick/cluster/elbow.py
+++ b/yellowbrick/cluster/elbow.py
@@ -19,6 +19,8 @@
 ##########################################################################
 
 import time
+import numpy as np
+import scipy.sparse as sp
 
 from .base import ClusteringScoreVisualizer
 from ..exceptions import YellowbrickValueError
@@ -83,8 +85,16 @@
         # Compute the center of these instances
         center = instances.mean(axis=0)
 
+        # NOTE: csc_matrix and csr_matrix mean returns a 2D array, numpy.mean
+        # returns an array of 1 dimension less than the input. We expect
+        # instances to be a 2D array, therefore to do pairwise computation we
+        # require center to be a 2D array with a single row (the center).
+        # See #370 for more detail.
+        if not sp.issparse(instances):
+            center = np.array([center])
+
         # Compute the square distances from the instances to the center
-        distances = pairwise_distances(instances, [center], metric=metric)
+        distances = pairwise_distances(instances, center, metric=metric)
         distances = distances ** 2
 
         # Add the mean square distance to the distortion

{"golden_diff": "diff --git a/yellowbrick/cluster/elbow.py b/yellowbrick/cluster/elbow.py\n--- a/yellowbrick/cluster/elbow.py\n+++ b/yellowbrick/cluster/elbow.py\n@@ -19,6 +19,8 @@\n ##########################################################################\n \n import time\n+import numpy as np\n+import scipy.sparse as sp\n \n from .base import ClusteringScoreVisualizer\n from ..exceptions import YellowbrickValueError\n@@ -83,8 +85,16 @@\n         # Compute the center of these instances\n         center = instances.mean(axis=0)\n \n+        # NOTE: csc_matrix and csr_matrix mean returns a 2D array, numpy.mean\n+        # returns an array of 1 dimension less than the input. We expect\n+        # instances to be a 2D array, therefore to do pairwise computation we\n+        # require center to be a 2D array with a single row (the center).\n+        # See #370 for more detail.\n+        if not sp.issparse(instances):\n+            center = np.array([center])\n+\n         # Compute the square distances from the instances to the center\n-        distances = pairwise_distances(instances, [center], metric=metric)\n+        distances = pairwise_distances(instances, center, metric=metric)\n         distances = distances ** 2\n \n         # Add the mean square distance to the distortion\n", "issue": "Error from KElbowVisualizer's distortion_score method\n`KElbowVisualizer` is generating a `ValueError` from Sklearn.\r\n\r\n### Issue\r\n\r\nUsing the Yellowbrick `hobbies` corpus and vectorizing with TFIDF, the KElbowVisualizer is generating an error from Sklearn: `ValueError: Found array with dim 3. check_pairwise_arrays expected <= 2`. The `distortion_score` method appears to be causing the error when calling Sklearn's `pairwise_distances` method.\r\n\r\n### Code\r\n```\r\ncorpus = load_corpus('hobbies')\r\ntfidf  = TfidfVectorizer()\r\ndocs   = tfidf.fit_transform(corpus)\r\nvisualizer = KElbowVisualizer(KMeans(), k=(4, 8))\r\nvisualizer.fit(docs)\r\nvisualizer.poof()\r\n```\r\n\r\n### Error\r\n```\r\nTraceback (most recent call last):\r\n  File \"elbows.py\", line 82, in <module>\r\n    visualizer.fit(docs)\r\n  File \"/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yellowbrick/cluster/elbow.py\", line 245, in fit\r\n    self.scoring_metric(X, self.estimator.labels_)\r\n  File \"/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yellowbrick/cluster/elbow.py\", line 87, in distortion_score\r\n    distances = pairwise_distances(instances, [center], metric=metric)\r\n  File \"/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/metrics/pairwise.py\", line 1247, in pairwise_distances\r\n    return _parallel_pairwise(X, Y, func, n_jobs, **kwds)\r\n  File \"/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/metrics/pairwise.py\", line 1090, in _parallel_pairwise\r\n    return func(X, Y, **kwds)\r\n  File \"/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/metrics/pairwise.py\", line 223, in euclidean_distances\r\n    X, Y = check_pairwise_arrays(X, Y)\r\n  File \"/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/metrics/pairwise.py\", line 112, in check_pairwise_arrays\r\n    warn_on_dtype=warn_on_dtype, estimator=estimator)\r\n  File \"/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sklearn/utils/validation.py\", line 451, in check_array\r\n    % (array.ndim, estimator_name))\r\nValueError: Found array with dim 3. check_pairwise_arrays expected <= 2.\r\n```\n", "before_files": [{"content": "# yellowbrick.cluster.elbow\n# Implements the elbow method for determining the optimal number of clusters.\n#\n# Author:   Benjamin Bengfort <bbengfort@districtdatalabs.com>\n# Created:  Thu Mar 23 22:36:31 2017 -0400\n#\n# Copyright (C) 2016 District Data Labs\n# For license information, see LICENSE.txt\n#\n# ID: elbow.py [5a370c8] benjamin@bengfort.com $\n\n\"\"\"\nImplements the elbow method for determining the optimal number of clusters.\nhttps://bl.ocks.org/rpgove/0060ff3b656618e9136b\n\"\"\"\n\n##########################################################################\n## Imports\n##########################################################################\n\nimport time\n\nfrom .base import ClusteringScoreVisualizer\nfrom ..exceptions import YellowbrickValueError\n\nfrom sklearn.metrics import silhouette_score\nfrom sklearn.metrics import calinski_harabaz_score\nfrom sklearn.metrics.pairwise import pairwise_distances\nfrom sklearn.preprocessing import LabelEncoder\n\n\n## Packages for export\n__all__ = [\n    \"KElbowVisualizer\", \"distortion_score\"\n]\n\n\n##########################################################################\n## Metrics\n##########################################################################\n\ndef distortion_score(X, labels, metric='euclidean'):\n    \"\"\"\n    Compute the mean distortion of all samples.\n\n    The distortion is computed as the the sum of the squared distances between\n    each observation and its closest centroid. Logically, this is the metric\n    that K-Means attempts to minimize as it is fitting the model.\n\n    .. seealso:: http://kldavenport.com/the-cost-function-of-k-means/\n\n    Parameters\n    ----------\n    X : array, shape = [n_samples, n_features] or [n_samples_a, n_samples_a]\n        Array of pairwise distances between samples if metric == \"precomputed\"\n        or a feature array for computing distances against the labels.\n\n    labels : array, shape = [n_samples]\n        Predicted labels for each sample\n\n    metric : string\n        The metric to use when calculating distance between instances in a\n        feature array. If metric is a string, it must be one of the options\n        allowed by `sklearn.metrics.pairwise.pairwise_distances\n        <http://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.pairwise_distances.html#sklearn.metrics.pairwise.pairwise_distances>`_\n\n    .. todo:: add sample_size and random_state kwds similar to silhouette_score\n    \"\"\"\n    # Encode labels to get unique centers and groups\n    le = LabelEncoder()\n    labels = le.fit_transform(labels)\n    unique_labels = le.classes_\n\n    # Sum of the distortions\n    distortion = 0\n\n    # Loop through each label (center) to compute the centroid\n    for current_label in unique_labels:\n        # Mask the instances that belong to the current label\n        mask = labels == current_label\n        instances = X[mask]\n\n        # Compute the center of these instances\n        center = instances.mean(axis=0)\n\n        # Compute the square distances from the instances to the center\n        distances = pairwise_distances(instances, [center], metric=metric)\n        distances = distances ** 2\n\n        # Add the mean square distance to the distortion\n        distortion += distances.mean()\n\n    return distortion\n\n\n##########################################################################\n## Elbow Method\n##########################################################################\n\nKELBOW_SCOREMAP = {\n    \"distortion\": distortion_score,\n    \"silhouette\": silhouette_score,\n    \"calinski_harabaz\": calinski_harabaz_score,\n}\n\n\nclass KElbowVisualizer(ClusteringScoreVisualizer):\n    \"\"\"\n    The K-Elbow Visualizer implements the \"elbow\" method of selecting the\n    optimal number of clusters for K-means clustering. K-means is a simple\n    unsupervised machine learning algorithm that groups data into a specified\n    number (k) of clusters. Because the user must specify in advance what k to\n    choose, the algorithm is somewhat naive -- it assigns all members to k\n    clusters even if that is not the right k for the dataset.\n\n    The elbow method runs k-means clustering on the dataset for a range of\n    values for k (say from 1-10) and then for each value of k computes an\n    average score for all clusters. By default, the ``distortion_score`` is\n    computed, the sum of square distances from each point to its assigned\n    center. Other metrics can also be used such as the ``silhouette_score``,\n    the mean silhouette  coefficient for all samples or the\n    ``calinski_harabaz_score``, which computes the ratio of dispersion between\n    and within clusters.\n\n    When these overall metrics for each model are plotted, it is possible to\n    visually determine the best value for K. If the line chart looks like an\n    arm, then the \"elbow\" (the point of inflection on the curve) is the best\n    value of k. The \"arm\" can be either up or down, but if there is a strong\n    inflection point, it is a good indication that the underlying model fits\n    best at that point.\n\n    Parameters\n    ----------\n\n    model : a Scikit-Learn clusterer\n        Should be an instance of a clusterer, specifically ``KMeans`` or\n        ``MiniBatchKMeans``. If it is not a clusterer, an exception is raised.\n\n    ax : matplotlib Axes, default: None\n        The axes to plot the figure on. If None is passed in the current axes\n        will be used (or generated if required).\n\n    k : integer or tuple\n        The range of k to compute silhouette scores for. If a single integer\n        is specified, then will compute the range (2,k) otherwise the\n        specified range in the tuple is used.\n\n    metric : string, default: ``\"distortion\"``\n        Select the scoring metric to evaluate the clusters. The default is the\n        mean distortion, defined by the sum of squared distances between each\n        observation and its closest centroid. Other metrics include:\n\n        - **distortion**: mean sum of squared distances to centers\n        - **silhouette**: mean ratio of intra-cluster and nearest-cluster distance\n        - **calinski_harabaz**: ratio of within to between cluster dispersion\n\n    timings : bool, default: True\n        Display the fitting time per k to evaluate the amount of time required\n        to train the clustering model.\n\n    kwargs : dict\n        Keyword arguments that are passed to the base class and may influence\n        the visualization as defined in other Visualizers.\n\n    Examples\n    --------\n\n    >>> from yellowbrick.cluster import KElbowVisualizer\n    >>> from sklearn.cluster import KMeans\n    >>> model = KElbowVisualizer(KMeans(), k=10)\n    >>> model.fit(X)\n    >>> model.poof()\n\n    Notes\n    -----\n\n    If you get a visualizer that doesn't have an elbow or inflection point,\n    then this method may not be working. The elbow method does not work well\n    if the data is not very clustered; in this case you might see a smooth\n    curve and the value of k is unclear. Other scoring methods such as BIC or\n    SSE also can be used to explore if clustering is a correct choice.\n\n    For a discussion on the Elbow method, read more at\n    `Robert Gove's Block <https://bl.ocks.org/rpgove/0060ff3b656618e9136b>`_.\n\n    .. todo:: add parallelization option for performance\n    .. todo:: add different metrics for scores and silhoutte\n    .. todo:: add timing information about how long its taking\n    \"\"\"\n\n    def __init__(self, model, ax=None, k=10,\n                 metric=\"distortion\", timings=True, **kwargs):\n        super(KElbowVisualizer, self).__init__(model, ax=ax, **kwargs)\n\n        # Get the scoring method\n        if metric not in KELBOW_SCOREMAP:\n            raise YellowbrickValueError(\n                \"'{}' is not a defined metric \"\n                \"use one of distortion, silhouette, or calinski_harabaz\"\n            )\n\n        # Store the arguments\n        self.scoring_metric = KELBOW_SCOREMAP[metric]\n        self.timings = timings\n\n        # Convert K into a tuple argument if an integer\n        if isinstance(k, int):\n            k = (2, k+1)\n\n        # Expand k in to the values we will use, capturing exceptions\n        try:\n            k = tuple(k)\n            self.k_values_ = list(range(*k))\n        except:\n            raise YellowbrickValueError((\n                \"Specify a range or maximal K value, the value '{}' \"\n                \"is not a valid argument for K.\".format(k)\n            ))\n\n\n        # Holds the values of the silhoutte scores\n        self.k_scores_ = None\n\n    def fit(self, X, y=None, **kwargs):\n        \"\"\"\n        Fits n KMeans models where n is the length of ``self.k_values_``,\n        storing the silhoutte scores in the ``self.k_scores_`` attribute.\n        This method finishes up by calling draw to create the plot.\n        \"\"\"\n\n        self.k_scores_ = []\n        self.k_timers_ = []\n\n        for k in self.k_values_:\n            # Compute the start time for each  model\n            start = time.time()\n\n            # Set the k value and fit the model\n            self.estimator.set_params(n_clusters=k)\n            self.estimator.fit(X)\n\n            # Append the time and score to our plottable metrics\n            self.k_timers_.append(time.time() - start)\n            self.k_scores_.append(\n                self.scoring_metric(X, self.estimator.labels_)\n            )\n\n        self.draw()\n\n        return self\n\n    def draw(self):\n        \"\"\"\n        Draw the elbow curve for the specified scores and values of K.\n        \"\"\"\n        # Plot the silhouette score against k\n        self.ax.plot(self.k_values_, self.k_scores_, marker=\"D\", label=\"score\")\n\n        # If we're going to plot the timings, create a twinx axis\n        if self.timings:\n            self.axes = [self.ax, self.ax.twinx()]\n            self.axes[1].plot(\n                self.k_values_, self.k_timers_, label=\"fit time\",\n                c='g', marker=\"o\", linestyle=\"--\", alpha=0.75,\n            )\n\n        return self.ax\n\n    def finalize(self):\n        \"\"\"\n        Prepare the figure for rendering by setting the title as well as the\n        X and Y axis labels and adding the legend.\n        \"\"\"\n        # Get the metric name\n        metric = self.scoring_metric.__name__.replace(\"_\", \" \").title()\n\n        # Set the title\n        self.set_title(\n            '{} Elbow for {} Clustering'.format(metric, self.name)\n        )\n\n        # Set the x and y labels\n        self.ax.set_xlabel('k')\n        self.ax.set_ylabel(metric.lower())\n\n        # Set the second y axis labels\n        if self.timings:\n            self.axes[1].grid(False)\n            self.axes[1].set_ylabel(\"fit time (seconds)\", color='g')\n            self.axes[1].tick_params('y', colors='g')\n", "path": "yellowbrick/cluster/elbow.py"}], "after_files": [{"content": "# yellowbrick.cluster.elbow\n# Implements the elbow method for determining the optimal number of clusters.\n#\n# Author:   Benjamin Bengfort <bbengfort@districtdatalabs.com>\n# Created:  Thu Mar 23 22:36:31 2017 -0400\n#\n# Copyright (C) 2016 District Data Labs\n# For license information, see LICENSE.txt\n#\n# ID: elbow.py [5a370c8] benjamin@bengfort.com $\n\n\"\"\"\nImplements the elbow method for determining the optimal number of clusters.\nhttps://bl.ocks.org/rpgove/0060ff3b656618e9136b\n\"\"\"\n\n##########################################################################\n## Imports\n##########################################################################\n\nimport time\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom .base import ClusteringScoreVisualizer\nfrom ..exceptions import YellowbrickValueError\n\nfrom sklearn.metrics import silhouette_score\nfrom sklearn.metrics import calinski_harabaz_score\nfrom sklearn.metrics.pairwise import pairwise_distances\nfrom sklearn.preprocessing import LabelEncoder\n\n\n## Packages for export\n__all__ = [\n    \"KElbowVisualizer\", \"distortion_score\"\n]\n\n\n##########################################################################\n## Metrics\n##########################################################################\n\ndef distortion_score(X, labels, metric='euclidean'):\n    \"\"\"\n    Compute the mean distortion of all samples.\n\n    The distortion is computed as the the sum of the squared distances between\n    each observation and its closest centroid. Logically, this is the metric\n    that K-Means attempts to minimize as it is fitting the model.\n\n    .. seealso:: http://kldavenport.com/the-cost-function-of-k-means/\n\n    Parameters\n    ----------\n    X : array, shape = [n_samples, n_features] or [n_samples_a, n_samples_a]\n        Array of pairwise distances between samples if metric == \"precomputed\"\n        or a feature array for computing distances against the labels.\n\n    labels : array, shape = [n_samples]\n        Predicted labels for each sample\n\n    metric : string\n        The metric to use when calculating distance between instances in a\n        feature array. If metric is a string, it must be one of the options\n        allowed by `sklearn.metrics.pairwise.pairwise_distances\n        <http://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.pairwise_distances.html#sklearn.metrics.pairwise.pairwise_distances>`_\n\n    .. todo:: add sample_size and random_state kwds similar to silhouette_score\n    \"\"\"\n    # Encode labels to get unique centers and groups\n    le = LabelEncoder()\n    labels = le.fit_transform(labels)\n    unique_labels = le.classes_\n\n    # Sum of the distortions\n    distortion = 0\n\n    # Loop through each label (center) to compute the centroid\n    for current_label in unique_labels:\n        # Mask the instances that belong to the current label\n        mask = labels == current_label\n        instances = X[mask]\n\n        # Compute the center of these instances\n        center = instances.mean(axis=0)\n\n        # NOTE: csc_matrix and csr_matrix mean returns a 2D array, numpy.mean\n        # returns an array of 1 dimension less than the input. We expect\n        # instances to be a 2D array, therefore to do pairwise computation we\n        # require center to be a 2D array with a single row (the center).\n        # See #370 for more detail.\n        if not sp.issparse(instances):\n            center = np.array([center])\n\n        # Compute the square distances from the instances to the center\n        distances = pairwise_distances(instances, center, metric=metric)\n        distances = distances ** 2\n\n        # Add the mean square distance to the distortion\n        distortion += distances.mean()\n\n    return distortion\n\n\n##########################################################################\n## Elbow Method\n##########################################################################\n\nKELBOW_SCOREMAP = {\n    \"distortion\": distortion_score,\n    \"silhouette\": silhouette_score,\n    \"calinski_harabaz\": calinski_harabaz_score,\n}\n\n\nclass KElbowVisualizer(ClusteringScoreVisualizer):\n    \"\"\"\n    The K-Elbow Visualizer implements the \"elbow\" method of selecting the\n    optimal number of clusters for K-means clustering. K-means is a simple\n    unsupervised machine learning algorithm that groups data into a specified\n    number (k) of clusters. Because the user must specify in advance what k to\n    choose, the algorithm is somewhat naive -- it assigns all members to k\n    clusters even if that is not the right k for the dataset.\n\n    The elbow method runs k-means clustering on the dataset for a range of\n    values for k (say from 1-10) and then for each value of k computes an\n    average score for all clusters. By default, the ``distortion_score`` is\n    computed, the sum of square distances from each point to its assigned\n    center. Other metrics can also be used such as the ``silhouette_score``,\n    the mean silhouette  coefficient for all samples or the\n    ``calinski_harabaz_score``, which computes the ratio of dispersion between\n    and within clusters.\n\n    When these overall metrics for each model are plotted, it is possible to\n    visually determine the best value for K. If the line chart looks like an\n    arm, then the \"elbow\" (the point of inflection on the curve) is the best\n    value of k. The \"arm\" can be either up or down, but if there is a strong\n    inflection point, it is a good indication that the underlying model fits\n    best at that point.\n\n    Parameters\n    ----------\n\n    model : a Scikit-Learn clusterer\n        Should be an instance of a clusterer, specifically ``KMeans`` or\n        ``MiniBatchKMeans``. If it is not a clusterer, an exception is raised.\n\n    ax : matplotlib Axes, default: None\n        The axes to plot the figure on. If None is passed in the current axes\n        will be used (or generated if required).\n\n    k : integer or tuple\n        The range of k to compute silhouette scores for. If a single integer\n        is specified, then will compute the range (2,k) otherwise the\n        specified range in the tuple is used.\n\n    metric : string, default: ``\"distortion\"``\n        Select the scoring metric to evaluate the clusters. The default is the\n        mean distortion, defined by the sum of squared distances between each\n        observation and its closest centroid. Other metrics include:\n\n        - **distortion**: mean sum of squared distances to centers\n        - **silhouette**: mean ratio of intra-cluster and nearest-cluster distance\n        - **calinski_harabaz**: ratio of within to between cluster dispersion\n\n    timings : bool, default: True\n        Display the fitting time per k to evaluate the amount of time required\n        to train the clustering model.\n\n    kwargs : dict\n        Keyword arguments that are passed to the base class and may influence\n        the visualization as defined in other Visualizers.\n\n    Examples\n    --------\n\n    >>> from yellowbrick.cluster import KElbowVisualizer\n    >>> from sklearn.cluster import KMeans\n    >>> model = KElbowVisualizer(KMeans(), k=10)\n    >>> model.fit(X)\n    >>> model.poof()\n\n    Notes\n    -----\n\n    If you get a visualizer that doesn't have an elbow or inflection point,\n    then this method may not be working. The elbow method does not work well\n    if the data is not very clustered; in this case you might see a smooth\n    curve and the value of k is unclear. Other scoring methods such as BIC or\n    SSE also can be used to explore if clustering is a correct choice.\n\n    For a discussion on the Elbow method, read more at\n    `Robert Gove's Block <https://bl.ocks.org/rpgove/0060ff3b656618e9136b>`_.\n\n    .. todo:: add parallelization option for performance\n    .. todo:: add different metrics for scores and silhoutte\n    .. todo:: add timing information about how long its taking\n    \"\"\"\n\n    def __init__(self, model, ax=None, k=10,\n                 metric=\"distortion\", timings=True, **kwargs):\n        super(KElbowVisualizer, self).__init__(model, ax=ax, **kwargs)\n\n        # Get the scoring method\n        if metric not in KELBOW_SCOREMAP:\n            raise YellowbrickValueError(\n                \"'{}' is not a defined metric \"\n                \"use one of distortion, silhouette, or calinski_harabaz\"\n            )\n\n        # Store the arguments\n        self.scoring_metric = KELBOW_SCOREMAP[metric]\n        self.timings = timings\n\n        # Convert K into a tuple argument if an integer\n        if isinstance(k, int):\n            k = (2, k+1)\n\n        # Expand k in to the values we will use, capturing exceptions\n        try:\n            k = tuple(k)\n            self.k_values_ = list(range(*k))\n        except:\n            raise YellowbrickValueError((\n                \"Specify a range or maximal K value, the value '{}' \"\n                \"is not a valid argument for K.\".format(k)\n            ))\n\n\n        # Holds the values of the silhoutte scores\n        self.k_scores_ = None\n\n    def fit(self, X, y=None, **kwargs):\n        \"\"\"\n        Fits n KMeans models where n is the length of ``self.k_values_``,\n        storing the silhoutte scores in the ``self.k_scores_`` attribute.\n        This method finishes up by calling draw to create the plot.\n        \"\"\"\n\n        self.k_scores_ = []\n        self.k_timers_ = []\n\n        for k in self.k_values_:\n            # Compute the start time for each  model\n            start = time.time()\n\n            # Set the k value and fit the model\n            self.estimator.set_params(n_clusters=k)\n            self.estimator.fit(X)\n\n            # Append the time and score to our plottable metrics\n            self.k_timers_.append(time.time() - start)\n            self.k_scores_.append(\n                self.scoring_metric(X, self.estimator.labels_)\n            )\n\n        self.draw()\n\n        return self\n\n    def draw(self):\n        \"\"\"\n        Draw the elbow curve for the specified scores and values of K.\n        \"\"\"\n        # Plot the silhouette score against k\n        self.ax.plot(self.k_values_, self.k_scores_, marker=\"D\", label=\"score\")\n\n        # If we're going to plot the timings, create a twinx axis\n        if self.timings:\n            self.axes = [self.ax, self.ax.twinx()]\n            self.axes[1].plot(\n                self.k_values_, self.k_timers_, label=\"fit time\",\n                c='g', marker=\"o\", linestyle=\"--\", alpha=0.75,\n            )\n\n        return self.ax\n\n    def finalize(self):\n        \"\"\"\n        Prepare the figure for rendering by setting the title as well as the\n        X and Y axis labels and adding the legend.\n        \"\"\"\n        # Get the metric name\n        metric = self.scoring_metric.__name__.replace(\"_\", \" \").title()\n\n        # Set the title\n        self.set_title(\n            '{} Elbow for {} Clustering'.format(metric, self.name)\n        )\n\n        # Set the x and y labels\n        self.ax.set_xlabel('k')\n        self.ax.set_ylabel(metric.lower())\n\n        # Set the second y axis labels\n        if self.timings:\n            self.axes[1].grid(False)\n            self.axes[1].set_ylabel(\"fit time (seconds)\", color='g')\n            self.axes[1].tick_params('y', colors='g')\n", "path": "yellowbrick/cluster/elbow.py"}]}