Repository: DistrictDataLabs/yellowbrick

Issue:
TSNE size & title bug
**Describe the bug**
Looks like our `TSNEVisualizer` might have a bug that causes an error on instantiation if either the `size` or `title` parameters are used.

**To Reproduce**

```python
from yellowbrick.text import TSNEVisualizer
from sklearn.feature_extraction.text import TfidfVectorizer

corpus = load_data('hobbies')
tfidf = TfidfVectorizer()
docs = tfidf.fit_transform(corpus.data)
labels = corpus.target

tsne = TSNEVisualizer(size=(1080, 720))
```

or 

```
tsne = TSNEVisualizer(title="My Special TSNE Visualizer")
```

**Dataset**
This bug was triggered using the YB hobbies corpus.

**Expected behavior**
Users should be able to influence the size of the visualizer on instantiation using the `size` parameter and a tuple with `(width, height)` in pixels, and the title of the visualizer using the `title` parameter and a string.

**Traceback**

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-59-120fbfcec07c> in <module>()
----> 1 tsne = TSNEVisualizer(size=(1080, 720))
      2 tsne.fit(labels)
      3 tsne.poof()

/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yellowbrick/text/tsne.py in __init__(self, ax, decompose, decompose_by, labels, classes, colors, colormap, random_state, **kwargs)
    180 
    181         # TSNE Parameters
--> 182         self.transformer_ = self.make_transformer(decompose, decompose_by, kwargs)
    183 
    184     def make_transformer(self, decompose='svd', decompose_by=50, tsne_kwargs={}):

/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yellowbrick/text/tsne.py in make_transformer(self, decompose, decompose_by, tsne_kwargs)
    234         # Add the TSNE manifold
    235         steps.append(('tsne', TSNE(
--> 236             n_components=2, random_state=self.random_state, **tsne_kwargs)))
    237 
    238         # return the pipeline

TypeError: __init__() got an unexpected keyword argument 'size'

```
or for `title`:

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-64-92c88e0bdd33> in <module>()
----> 1 tsne = TSNEVisualizer(title="My Special TSNE Visualizer")
      2 tsne.fit(labels)
      3 tsne.poof()

/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yellowbrick/text/tsne.py in __init__(self, ax, decompose, decompose_by, labels, classes, colors, colormap, random_state, **kwargs)
    180 
    181         # TSNE Parameters
--> 182         self.transformer_ = self.make_transformer(decompose, decompose_by, kwargs)
    183 
    184     def make_transformer(self, decompose='svd', decompose_by=50, tsne_kwargs={}):

/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yellowbrick/text/tsne.py in make_transformer(self, decompose, decompose_by, tsne_kwargs)
    234         # Add the TSNE manifold
    235         steps.append(('tsne', TSNE(
--> 236             n_components=2, random_state=self.random_state, **tsne_kwargs)))
    237 
    238         # return the pipeline

TypeError: __init__() got an unexpected keyword argument 'title'
```

**Desktop (please complete the following information):**
 - macOS
 - Python Version 3.6
 - Yellowbrick Version 0.8



Discussion:

Resolving patch:
diff --git a/yellowbrick/text/tsne.py b/yellowbrick/text/tsne.py
--- a/yellowbrick/text/tsne.py
+++ b/yellowbrick/text/tsne.py
@@ -2,6 +2,7 @@
 # Implements TSNE visualizations of documents in 2D space.
 #
 # Author:   Benjamin Bengfort <benjamin@bengfort.com>
+# Author:   Rebecca Bilbro <bilbro@gmail.com>
 # Created:  Mon Feb 20 06:33:29 2017 -0500
 #
 # Copyright (C) 2016 Bengfort.com
@@ -166,11 +167,7 @@ class TSNEVisualizer(TextVisualizer):
     NULL_CLASS = None
 
     def __init__(self, ax=None, decompose='svd', decompose_by=50, labels=None,
-               classes=None, colors=None, colormap=None, random_state=None, **kwargs):
-        """
-        Initialize the TSNE visualizer with visual hyperparameters.
-        """
-        super(TSNEVisualizer, self).__init__(ax=ax, **kwargs)
+                 classes=None, colors=None, colormap=None, random_state=None, **kwargs):
 
         # Visual Parameters
         self.labels = labels
@@ -178,8 +175,16 @@ def __init__(self, ax=None, decompose='svd', decompose_by=50, labels=None,
         self.colormap = colormap
         self.random_state = random_state
 
-        # TSNE Parameters
-        self.transformer_ = self.make_transformer(decompose, decompose_by, kwargs)
+        # Fetch TSNE kwargs from kwargs by popping only keys belonging to TSNE params
+        tsne_kwargs = {
+            key: kwargs.pop(key)
+            for key in TSNE().get_params()
+            if key in kwargs
+        }
+        self.transformer_ = self.make_transformer(decompose, decompose_by, tsne_kwargs)
+
+        # Call super at the end so that size and title are set correctly
+        super(TSNEVisualizer, self).__init__(ax=ax, **kwargs)
 
     def make_transformer(self, decompose='svd', decompose_by=50, tsne_kwargs={}):
         """
@@ -338,8 +343,6 @@ def finalize(self, **kwargs):
         Finalize the drawing by adding a title and legend, and removing the
         axes objects that do not convey information about TNSE.
         """
-
-        # Add a title
         self.set_title(
             "TSNE Projection of {} Documents".format(self.n_instances_)
         )


Test patch:
diff --git a/tests/test_text/test_tsne.py b/tests/test_text/test_tsne.py
--- a/tests/test_text/test_tsne.py
+++ b/tests/test_text/test_tsne.py
@@ -26,6 +26,7 @@
 from tests.dataset import DatasetMixin
 from yellowbrick.exceptions import YellowbrickValueError
 
+from sklearn.manifold import TSNE
 from sklearn.datasets import make_classification
 from sklearn.feature_extraction.text import TfidfVectorizer
 
@@ -87,6 +88,44 @@ def test_integrated_tsne(self):
         tol = 40 if six.PY3 else 55
         self.assert_images_similar(tsne, tol=tol)
 
+    def test_sklearn_tsne_size(self):
+        """
+        Check to make sure sklearn's TSNE doesn't use the size param
+        """
+        # In TSNEVisualizer, the internal sklearn TSNE transform consumes
+        # some but not all kwargs passed in by user. Those not in get_params(),
+        # like size, are passed through to YB's finalize method. This test should
+        # notify us  if TSNE's params change on the sklearn side.
+        with pytest.raises(TypeError):
+            TSNE(size=(100,100))
+
+    def test_sklearn_tsne_title(self):
+        """
+        Check to make sure sklearn's TSNE doesn't use the title param
+        """
+        # In TSNEVisualizer, the internal sklearn TSNE transform consumes
+        # some but not all kwargs passed in by user. Those not in get_params(),
+        # like title, are passed through to YB's finalize method. This test should
+        # notify us  if TSNE's params change on the sklearn side.
+        with pytest.raises(TypeError):
+            TSNE(title="custom_title")
+
+    def test_custom_title_tsne(self):
+        """
+        Check tSNE can accept a custom title (string) from the user
+        """
+        tsne = TSNEVisualizer(title="custom_title")
+
+        assert tsne.title == "custom_title"
+
+    def test_custom_size_tsne(self):
+        """
+        Check tSNE can accept a custom size (tuple of pixels) from the user
+        """
+        tsne = TSNEVisualizer(size=(100, 50))
+
+        assert tsne._size == (100, 50)
+        
     def test_make_classification_tsne(self):
         """
         Test tSNE integrated visualization on a sklearn classifier dataset