Repository: DistrictDataLabs/yellowbrick

Issue:
Lines in Classification Report 
First, this is an awesome module!!
I created a classification report (see attached) and the white lines make reading the numbers impossible.
![unknown](https://user-images.githubusercontent.com/5513150/27615699-2978e55c-5b78-11e7-9ceb-d63026f18778.png)
I wanted to ask if there is a way to either lighten or remove these lines so that you can read the various scores?
Thanks!


Discussion:
@cgivre thanks for the note, and for using Yellowbrick! You're right the numbers are tough to read with those lines, we'll definitely have to explore a fix on our end. Perhaps we can add a box behind the text with some opacity that makes it easier to read. I know that we did do a dark/light text algorithm so that we had white text on a dark background and vice versa. Hopefully we can use that to make things more readable. 

In the meantime you can get access to the gridlines through the `ax` property of the visualizer, and modify the color yourself:

```python
model = ClassificationReport(RandomForestClassifier())
model.fit(X_train, y_train)
model.score(X_test, y_test)

model.ax.grid(color="#666666", linestyle='--')

model.poof()
```

Which results in something like:

![occupancy](https://user-images.githubusercontent.com/745966/27749328-1fc25090-5da1-11e7-9c8f-794c8b9a8936.png)


When I've done these sort of plots in the past I've always wondered if I could move the grid lines to be _between_ the grid items to form box-edges, rather than running through the centre of the cells. This would turn the display into discrete cells. Might that be worth exploring?
Definitely worth exploring, but not 100% sure how to. @NealHumphrey -- you were taking a look at using the `pcolormeshgrid` instead of `imshow`, is it possible with this method?
@bbengfort and @ianozsvald - yes, it should be the default result if the classification report is converted to a `pcolormeshgrid`. You can see that the lines are outside the grids on the confusion matrix in these screenshots: https://github.com/DistrictDataLabs/yellowbrick/pull/144

Resolving patch:
diff --git a/docs/api/classifier/classification_report.py b/docs/api/classifier/classification_report.py
--- a/docs/api/classifier/classification_report.py
+++ b/docs/api/classifier/classification_report.py
@@ -1,29 +1,58 @@
+# classification_report
+# Generates images for the classification report documentation.
+#
+# Author:  Benjamin Bengfort <benjamin@bengfort.com>
+# Created: Sun Mar 18 16:35:30 2018 -0400
+#
+# ID: classification_report.py [] benjamin@bengfort.com $
+
+"""
+Generates images for the classification report documentation.
+"""
+
+##########################################################################
+## Imports
+##########################################################################
+
 import pandas as pd
+import matplotlib.pyplot as plt
 
 from sklearn.naive_bayes import GaussianNB
-from sklearn.model_selection import train_test_split
+from sklearn.model_selection import train_test_split as tts
 
 from yellowbrick.classifier import ClassificationReport
 
 
-if __name__ == '__main__':
-    # Load the regression data set
+##########################################################################
+## Quick Methods
+##########################################################################
+
+def make_dataset():
     data = pd.read_csv("../../../examples/data/occupancy/occupancy.csv")
 
-    features = ["temperature", "relative humidity", "light", "C02", "humidity"]
-    classes = ['unoccupied', 'occupied']
+    X = data[["temperature", "relative humidity", "light", "C02", "humidity"]]
+    y = data.occupancy
+
+    return tts(X, y, test_size=0.2)
+
 
-    # Extract the numpy arrays from the data frame
-    X = data[features].as_matrix()
-    y = data.occupancy.as_matrix()
+def make_gb_report(path="images/classification_report.png"):
+    X_train, X_test, y_train, y_test = make_dataset()
 
-    # Create the train and test data
-    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
+    _, ax = plt.subplots()
 
-    # Instantiate the classification model and visualizer
     bayes = GaussianNB()
-    visualizer = ClassificationReport(bayes, classes=classes)
+    viz = ClassificationReport(bayes, ax=ax, classes=['unoccupied', 'occupied'])
 
-    visualizer.fit(X_train, y_train)  # Fit the training data to the visualizer
-    visualizer.score(X_test, y_test)  # Evaluate the model on the test data
-    g = visualizer.poof(outpath="images/classification_report.png")             # Draw/show/poof the data
+    viz.fit(X_train, y_train)
+    viz.score(X_test, y_test)
+
+    viz.poof(outpath=path)
+
+
+##########################################################################
+## Main Method
+##########################################################################
+
+if __name__ == '__main__':
+    make_gb_report()
diff --git a/yellowbrick/classifier/classification_report.py b/yellowbrick/classifier/classification_report.py
--- a/yellowbrick/classifier/classification_report.py
+++ b/yellowbrick/classifier/classification_report.py
@@ -34,6 +34,11 @@
 ## Classification Report
 ##########################################################################
 
+CMAP_UNDERCOLOR = 'w'
+CMAP_OVERCOLOR = '#2a7d4f'
+SCORES_KEYS = ('precision', 'recall', 'f1')
+
+
 class ClassificationReport(ClassificationScoreVisualizer):
     """
     Classification report that shows the precision, recall, and F1 scores
@@ -41,7 +46,6 @@ class ClassificationReport(ClassificationScoreVisualizer):
 
     Parameters
     ----------
-
     ax : The axis to plot the figure on.
 
     model : the Scikit-Learn estimator
@@ -52,14 +56,14 @@ class ClassificationReport(ClassificationScoreVisualizer):
         If classes is None and a y value is passed to fit then the classes
         are selected from the target vector.
 
-    colormap : optional string or matplotlib cmap to colorize lines
-        Use sequential heatmap.
+    cmap : string, default: ``'YlOrRd'``
+        Specify a colormap to define the heatmap of the predicted class
+        against the actual class in the confusion matrix.
 
     kwargs : keyword arguments passed to the super class.
 
     Examples
     --------
-
     >>> from yellowbrick.classifier import ClassificationReport
     >>> from sklearn.linear_model import LogisticRegression
     >>> viz = ClassificationReport(LogisticRegression())
@@ -67,69 +71,91 @@ class ClassificationReport(ClassificationScoreVisualizer):
     >>> viz.score(X_test, y_test)
     >>> viz.poof()
 
+    Attributes
+    ----------
+    scores_ : dict of dicts
+        Outer dictionary composed of precision, recall, and f1 scores with
+        inner dictionaries specifiying the values for each class listed.
     """
-    def __init__(self, model, ax=None, classes=None, **kwargs):
+    def __init__(self, model, ax=None, classes=None, cmap='YlOrRd', **kwargs):
         super(ClassificationReport, self).__init__(
             model, ax=ax, classes=classes, **kwargs
         )
 
-        self.cmap = color_sequence(kwargs.pop('cmap', 'YlOrRd'))
+        self.cmap = color_sequence(cmap)
+        self.cmap.set_under(color=CMAP_UNDERCOLOR)
+        self.cmap.set_over(color=CMAP_OVERCOLOR)
 
     def score(self, X, y=None, **kwargs):
         """
-        Generates the Scikit-Learn classification_report
+        Generates the Scikit-Learn classification report.
 
         Parameters
         ----------
-
         X : ndarray or DataFrame of shape n x m
             A matrix of n instances with m features
 
         y : ndarray or Series of length n
             An array or series of target or class values
-
         """
         y_pred = self.predict(X)
-        keys   = ('precision', 'recall', 'f1')
-        self.scores = precision_recall_fscore_support(y, y_pred)
-        self.scores = map(lambda s: dict(zip(self.classes_, s)), self.scores[0:3])
-        self.scores = dict(zip(keys, self.scores))
 
-        return self.draw(y, y_pred)
+        scores = precision_recall_fscore_support(y, y_pred)
+        scores = map(lambda s: dict(zip(self.classes_, s)), scores[0:3])
+        self.scores_ = dict(zip(SCORES_KEYS, scores))
+
+        return self.draw()
 
-    def draw(self, y, y_pred):
+    def draw(self):
         """
         Renders the classification report across each axis.
-
-        Parameters
-        ----------
-
-        y : ndarray or Series of length n
-            An array or series of target or class values
-
-        y_pred : ndarray or Series of length n
-            An array or series of predicted target values
         """
-        self.matrix = []
-        for cls in self.classes_:
-            self.matrix.append([self.scores['precision'][cls],self.scores['recall'][cls],self.scores['f1'][cls]])
-
-        for column in range(0,3): #3 columns - prec,rec,f1
-            for row in range(len(self.classes_)):
-                current_score = self.matrix[row][column]
-                base_color = self.cmap(current_score)
-                text_color= find_text_color(base_color)
-
-                # Limit the current score to a precision of 3
-                current_score = "{:0.3f}".format(current_score)
-
-                self.ax.text(column,row,current_score,va='center',ha='center', color=text_color)
-
-        plt.imshow(self.matrix, interpolation='nearest', cmap=self.cmap, vmin=0, vmax=1, aspect='auto')
+        # Create display grid
+        cr_display = np.zeros((len(self.classes_), 3))
+
+        # For each class row, append columns for precision, recall, and f1
+        for idx, cls in enumerate(self.classes_):
+            for jdx, metric in enumerate(('precision', 'recall', 'f1')):
+                cr_display[idx, jdx] = self.scores_[metric][cls]
+
+        # Set up the dimensions of the pcolormesh
+        # NOTE: pcolormesh accepts grids that are (N+1,M+1)
+        X, Y = np.arange(len(self.classes_)+1), np.arange(4)
+        self.ax.set_ylim(bottom=0, top=cr_display.shape[0])
+        self.ax.set_xlim(left=0, right=cr_display.shape[1])
+
+        # Set data labels in the grid, enumerating over class, metric pairs
+        # NOTE: X and Y are one element longer than the classification report
+        # so skip the last element to label the grid correctly.
+        for x in X[:-1]:
+            for y in Y[:-1]:
+
+                # Extract the value and the text label
+                value = cr_display[x,y]
+                svalue = "{:0.3f}".format(value)
+
+                # Determine the grid and text colors
+                base_color = self.cmap(value)
+                text_color = find_text_color(base_color)
+
+                # Add the label to the middle of the grid
+                cx, cy = x+0.5, y+0.5
+                self.ax.text(
+                    cy, cx, svalue, va='center', ha='center', color=text_color
+                )
+
+
+        # Draw the heatmap with colors bounded by the min and max of the grid
+        # NOTE: I do not understand why this is Y, X instead of X, Y it works
+        # in this order but raises an exception with the other order.
+        g = self.ax.pcolormesh(
+            Y, X, cr_display, vmin=0, vmax=1, cmap=self.cmap, edgecolor='w',
+        )
 
         # Add the color bar
-        plt.colorbar()
+        plt.colorbar(g, ax=self.ax)
 
+        # Return the axes being drawn on
         return self.ax
 
     def finalize(self, **kwargs):
@@ -145,20 +171,14 @@ def finalize(self, **kwargs):
         # Set the title of the classifiation report
         self.set_title('{} Classification Report'.format(self.name))
 
-        # Compute the tick marks for both x and y
-        x_tick_marks = np.arange(len(self.classes_)+1)
-        y_tick_marks = np.arange(len(self.classes_))
-
         # Set the tick marks appropriately
-        self.ax.set_xticks(x_tick_marks)
-        self.ax.set_yticks(y_tick_marks)
+        self.ax.set_xticks(np.arange(3)+0.5)
+        self.ax.set_yticks(np.arange(len(self.classes_))+0.5)
 
         self.ax.set_xticklabels(['precision', 'recall', 'f1-score'], rotation=45)
         self.ax.set_yticklabels(self.classes_)
 
-        # Set the labels for the two axes
-        self.ax.set_ylabel('Classes')
-        self.ax.set_xlabel('Measures')
+        plt.tight_layout()
 
 
 def classification_report(model, X, y=None, ax=None, classes=None, **kwargs):
diff --git a/yellowbrick/classifier/confusion_matrix.py b/yellowbrick/classifier/confusion_matrix.py
--- a/yellowbrick/classifier/confusion_matrix.py
+++ b/yellowbrick/classifier/confusion_matrix.py
@@ -20,13 +20,14 @@
 import warnings
 import numpy as np
 
-from sklearn.metrics import confusion_matrix
-
 from ..utils import div_safe
 from ..style import find_text_color
 from ..style.palettes import color_sequence
 from .base import ClassificationScoreVisualizer
 
+from sklearn.model_selection import train_test_split
+from sklearn.metrics import confusion_matrix as confusion_matrix_metric
+
 
 ##########################################################################
 ## ConfusionMatrix
@@ -168,11 +169,11 @@ def score(self, X, y, **kwargs):
                 y_pred = self.label_encoder.inverse_transform(y_pred)
             except AttributeError:
                 # if a mapping is passed to class apply it here.
-                y = [self.label_encoder[x] for x in y]
-                y_pred = [self.label_encoder[x] for x in y_pred]
+                y = np.array([self.label_encoder[x] for x in y])
+                y_pred = np.array([self.label_encoder[x] for x in y_pred])
 
         # Compute the confusion matrix and class counts
-        self.confusion_matrix_ = confusion_matrix(
+        self.confusion_matrix_ = confusion_matrix_metric(
             y, y_pred, labels=self.classes_, sample_weight=self.sample_weight
         )
         self.class_counts_ = self.class_counts(y)
@@ -271,3 +272,95 @@ def finalize(self, **kwargs):
         self.set_title('{} Confusion Matrix'.format(self.name))
         self.ax.set_ylabel('True Class')
         self.ax.set_xlabel('Predicted Class')
+
+
+##########################################################################
+## Quick Method
+##########################################################################
+
+
+def confusion_matrix(model, X, y, ax=None, classes=None, sample_weight=None,
+                     percent=False, label_encoder=None, cmap='YlOrRd',
+                     fontsize=None, **kwargs):
+    """Quick method:
+
+    Creates a heatmap visualization of the sklearn.metrics.confusion_matrix().
+    A confusion matrix shows each combination of the true and predicted
+    classes for a test data set.
+
+    The default color map uses a yellow/orange/red color scale. The user can
+    choose between displaying values as the percent of true (cell value
+    divided by sum of row) or as direct counts. If percent of true mode is
+    selected, 100% accurate predictions are highlighted in green.
+
+    Requires a classification model.
+
+    Parameters
+    ----------
+    model : estimator
+        Must be a classifier, otherwise raises YellowbrickTypeError
+
+    X  : ndarray or DataFrame of shape n x m
+        A matrix of n instances with m features.
+
+    y  : ndarray or Series of length n
+        An array or series of target or class values.
+
+    ax : matplotlib Axes, default: None
+        The axes to plot the figure on. If None is passed in the current axes
+        will be used (or generated if required).
+
+    sample_weight: array-like of shape = [n_samples], optional
+        Passed to ``confusion_matrix`` to weight the samples.
+
+    percent: bool, default: False
+        Determines whether or not the confusion_matrix is displayed as counts
+        or as a percent of true predictions. Note, if specifying a subset of
+        classes, percent should be set to False or inaccurate figures will be
+        displayed.
+
+    classes : list, default: None
+        a list of class names to use in the confusion_matrix.
+        This is passed to the ``labels`` parameter of
+        ``sklearn.metrics.confusion_matrix()``, and follows the behaviour
+        indicated by that function. It may be used to reorder or select a
+        subset of labels. If None, classes that appear at least once in
+        ``y_true`` or ``y_pred`` are used in sorted order.
+
+    label_encoder : dict or LabelEncoder, default: None
+        When specifying the ``classes`` argument, the input to ``fit()``
+        and ``score()`` must match the expected labels. If the ``X`` and ``y``
+        datasets have been encoded prior to training and the labels must be
+        preserved for the visualization, use this argument to provide a
+        mapping from the encoded class to the correct label. Because typically
+        a Scikit-Learn ``LabelEncoder`` is used to perform this operation, you
+        may provide it directly to the class to utilize its fitted encoding.
+
+    cmap : string, default: ``'YlOrRd'``
+        Specify a colormap to define the heatmap of the predicted class
+        against the actual class in the confusion matrix.
+
+    fontsize : int, default: None
+        Specify the fontsize of the text in the grid and labels to make the
+        matrix a bit easier to read. Uses rcParams font size by default.
+
+    Returns
+    -------
+    ax : matplotlib axes
+        Returns the axes that the classification report was drawn on.
+    """
+    # Instantiate the visualizer
+    visualizer = ConfusionMatrix(
+        model, ax, classes, sample_weight, percent,
+        label_encoder, cmap, fontsize, **kwargs
+    )
+
+    # Create the train and test splits
+    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
+
+    # Fit and transform the visualizer (calls draw)
+    visualizer.fit(X_train, y_train, **kwargs)
+    visualizer.score(X_test, y_test)
+
+    # Return the axes object on the visualizer
+    return visualizer.ax


Test patch:
diff --git a/tests/baseline_images/test_classifier/test_classification_report/test_binary_class_report.png b/tests/baseline_images/test_classifier/test_classification_report/test_binary_class_report.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_classification_report/test_binary_class_report.png differ
diff --git a/tests/baseline_images/test_classifier/test_classification_report/test_class_report.png b/tests/baseline_images/test_classifier/test_classification_report/test_class_report.png
deleted file mode 100644
Binary files a/tests/baseline_images/test_classifier/test_classification_report/test_class_report.png and /dev/null differ
diff --git a/tests/baseline_images/test_classifier/test_classification_report/test_multiclass_class_report.png b/tests/baseline_images/test_classifier/test_classification_report/test_multiclass_class_report.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_classification_report/test_multiclass_class_report.png differ
diff --git a/tests/baseline_images/test_classifier/test_classification_report/test_pandas_integration.png b/tests/baseline_images/test_classifier/test_classification_report/test_pandas_integration.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_classification_report/test_pandas_integration.png differ
diff --git a/tests/baseline_images/test_classifier/test_classification_report/test_quick_method.png b/tests/baseline_images/test_classifier/test_classification_report/test_quick_method.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_classification_report/test_quick_method.png differ
diff --git a/tests/baseline_images/test_classifier/test_confusion_matrix/test_quick_method.png b/tests/baseline_images/test_classifier/test_confusion_matrix/test_quick_method.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_confusion_matrix/test_quick_method.png differ
diff --git a/tests/test_classifier/test_classification_report.py b/tests/test_classifier/test_classification_report.py
--- a/tests/test_classifier/test_classification_report.py
+++ b/tests/test_classifier/test_classification_report.py
@@ -1,36 +1,212 @@
+# tests.test_classifier.test_classification_report
+# Tests for the classification report visualizer
+#
+# Author:  Rebecca Bilbro <rbilbro@districtdatalabs.com>
+# Author:  Benjamin Bengfort <benjamin@bengfort.com>
+# Created: Sun Mar 18 16:57:27 2018 -0400
+#
+# ID: test_classification_report.py [] benjamin@bengfort.com $
+
+"""
+Tests for the classification report visualizer
+"""
+
+##########################################################################
+## Imports
+##########################################################################
+
+import pytest
+import yellowbrick as yb
+import matplotlib.pyplot as plt
+
+from collections import namedtuple
 
 from yellowbrick.classifier.classification_report import *
+
 from tests.base import VisualTestCase
+from tests.dataset import DatasetMixin
 
 from sklearn.svm import LinearSVC
+from sklearn.naive_bayes import GaussianNB
+from sklearn.tree import DecisionTreeClassifier
+from sklearn.datasets import make_classification
+from sklearn.model_selection import train_test_split as tts
+from sklearn.linear_model import LassoCV, LogisticRegression
+
+try:
+    import pandas as pd
+except ImportError:
+    pd = None
+
 
 ##########################################################################
-## Data
+## Fixtures
 ##########################################################################
 
-X = np.array(
-        [[ 2.318, 2.727, 4.260, 7.212, 4.792],
-         [ 2.315, 2.726, 4.295, 7.140, 4.783,],
-         [ 2.315, 2.724, 4.260, 7.135, 4.779,],
-         [ 2.110, 3.609, 4.330, 7.985, 5.595,],
-         [ 2.110, 3.626, 4.330, 8.203, 5.621,],
-         [ 2.110, 3.620, 4.470, 8.210, 5.612,]]
+# Helpers for fixtures
+Dataset = namedtuple('Dataset', 'X,y')
+Split = namedtuple('Split', 'train,test')
+
+
+@pytest.fixture(scope='class')
+def binary(request):
+    """
+    Creates a random binary classification dataset fixture
+    """
+    X, y = make_classification(
+        n_samples=500, n_features=20, n_informative=8, n_redundant=2,
+        n_classes=2, n_clusters_per_class=3, random_state=87
+    )
+
+    X_train, X_test, y_train, y_test = tts(
+        X, y, test_size=0.2, random_state=93
     )
 
-y = np.array([1, 1, 0, 1, 0, 0])
+    dataset = Dataset(Split(X_train, X_test), Split(y_train, y_test))
+    request.cls.binary = dataset
+
+
+@pytest.fixture(scope='class')
+def multiclass(request):
+    """
+    Creates a random multiclass classification dataset fixture
+    """
+    X, y = make_classification(
+        n_samples=500, n_features=20, n_informative=8, n_redundant=2,
+        n_classes=6, n_clusters_per_class=3, random_state=87
+    )
+
+    X_train, X_test, y_train, y_test = tts(
+        X, y, test_size=0.2, random_state=93
+    )
+
+    dataset = Dataset(Split(X_train, X_test), Split(y_train, y_test))
+    request.cls.multiclass = dataset
+
+
 
 ##########################################################################
 ##  Test for Classification Report
 ##########################################################################
 
-class ClassificationReportTests(VisualTestCase):
+@pytest.mark.usefixtures("binary", "multiclass")
+class ClassificationReportTests(VisualTestCase, DatasetMixin):
+    """
+    ClassificationReport visualizer tests
+    """
+
+    def test_binary_class_report(self):
+        """
+        Correctly generates a report for binary classification with LinearSVC
+        """
+        _, ax = plt.subplots()
+
+        viz = ClassificationReport(LinearSVC(), ax=ax)
+        viz.fit(self.binary.X.train, self.binary.y.train)
+        viz.score(self.binary.X.test, self.binary.y.test)
 
-    def test_class_report(self):
+        self.assert_images_similar(viz)
+
+        assert viz.scores_ == {
+            'precision': {0: 0.7446808510638298, 1: 0.8490566037735849},
+            'recall': {0: 0.813953488372093, 1: 0.7894736842105263},
+            'f1': {0: 0.7777777777777778, 1: 0.8181818181818182}
+        }
+
+    def test_multiclass_class_report(self):
         """
-        Assert no errors occur during classification report integration
+        Correctly generates report for multi-class with LogisticRegression
+        """
+        _, ax = plt.subplots()
+
+        viz = ClassificationReport(LogisticRegression(random_state=12), ax=ax)
+        viz.fit(self.multiclass.X.train, self.multiclass.y.train)
+        viz.score(self.multiclass.X.test, self.multiclass.y.test)
+
+        self.assert_images_similar(viz)
+
+        assert viz.scores_ == {
+            'precision': {
+                0: 0.5333333333333333, 1: 0.5, 2: 0.45,
+                3: 0.4, 4: 0.4, 5: 0.5882352941176471
+            }, 'recall': {
+                0: 0.42105263157894735, 1: 0.5625, 2: 0.6428571428571429,
+                3: 0.3157894736842105, 4: 0.375, 5: 0.625
+            }, 'f1': {
+                0: 0.47058823529411764, 1: 0.5294117647058824,
+                2: 0.5294117647058824, 3: 0.35294117647058826,
+                4: 0.38709677419354843, 5: 0.6060606060606061
+            }}
+
+    @pytest.mark.skipif(pd is None, reason="test requires pandas")
+    def test_pandas_integration(self):
         """
-        model = LinearSVC()
-        model.fit(X,y)
-        visualizer = ClassificationReport(model, classes=["A", "B"])
-        visualizer.score(X,y)
-        self.assert_images_similar(visualizer)
+        Test with Pandas DataFrame and Series input
+        """
+        _, ax = plt.subplots()
+
+        # Load the occupancy dataset from fixtures
+        data = self.load_data('occupancy')
+        target = 'occupancy'
+        features = [
+            "temperature", "relative_humidity", "light", "C02", "humidity"
+        ]
+
+        # Create instances and target
+        X = pd.DataFrame(data[features])
+        y = pd.Series(data[target].astype(int))
+
+        # Create train/test splits
+        splits = tts(X, y, test_size=0.2, random_state=4512)
+        X_train, X_test, y_train, y_test = splits
+
+        classes = ['unoccupied', 'occupied']
+
+        # Create classification report
+        model = GaussianNB()
+        viz = ClassificationReport(model, ax=ax, classes=classes)
+        viz.fit(X_train, y_train)
+        viz.score(X_test, y_test)
+
+        self.assert_images_similar(viz, tol=0.1)
+
+        # Ensure correct classification scores under the hood
+        assert viz.scores_ == {
+            'precision': {
+                'unoccupied': 0.999347471451876,
+                'occupied': 0.8825214899713467
+            }, 'recall': {
+                'unoccupied': 0.9613935969868174,
+                'occupied': 0.9978401727861771
+            }, 'f1': {
+                'unoccupied': 0.9800031994880819,
+                'occupied': 0.9366447034972124
+            }}
+
+    @pytest.mark.skip(reason="requires random state in quick method")
+    def test_quick_method(self):
+        """
+        Test the quick method with a random dataset
+        """
+        X, y = make_classification(
+            n_samples=400, n_features=20, n_informative=8, n_redundant=8,
+            n_classes=2, n_clusters_per_class=4, random_state=27
+        )
+
+        _, ax = plt.subplots()
+        classification_report(DecisionTreeClassifier(), X, y, ax=ax)
+
+        self.assert_images_similar(ax=ax)
+
+    def test_isclassifier(self):
+        """
+        Assert that only classifiers can be used with the visualizer.
+        """
+
+        message = (
+            'This estimator is not a classifier; '
+            'try a regression or clustering score visualizer instead!'
+        )
+
+        with self.assertRaisesRegexp(yb.exceptions.YellowbrickError, message):
+            ClassificationReport(LassoCV())
diff --git a/tests/test_classifier/test_confusion_matrix.py b/tests/test_classifier/test_confusion_matrix.py
--- a/tests/test_classifier/test_confusion_matrix.py
+++ b/tests/test_classifier/test_confusion_matrix.py
@@ -32,6 +32,8 @@
 from sklearn.datasets import load_digits
 from sklearn.naive_bayes import GaussianNB
 from sklearn.preprocessing import LabelEncoder
+from sklearn.tree import DecisionTreeClassifier
+from sklearn.datasets import make_classification
 from sklearn.linear_model import LogisticRegression
 from sklearn.linear_model import PassiveAggressiveRegressor
 from sklearn.model_selection import train_test_split as tts
@@ -41,6 +43,9 @@
 except ImportError:
     pd = None
 
+##########################################################################
+## Fixtures
+##########################################################################
 
 # Helpers for fixtures
 Dataset = namedtuple('Dataset', 'X,y')
@@ -64,6 +69,10 @@ def digits(request):
     )
 
 
+##########################################################################
+## Test Cases
+##########################################################################
+
 @pytest.mark.usefixtures("digits")
 class ConfusionMatrixTests(VisualTestCase, DatasetMixin):
     """
@@ -310,6 +319,20 @@ def test_pandas_integration(self):
             [   1,  985]
         ]))
 
+    @pytest.mark.skip(reason="requires random state in quick method")
+    def test_quick_method(self):
+        """
+        Test the quick method with a random dataset
+        """
+        X, y = make_classification(
+            n_samples=400, n_features=20, n_informative=8, n_redundant=8,
+            n_classes=2, n_clusters_per_class=4, random_state=27
+        )
+
+        _, ax = plt.subplots()
+        confusion_matrix(DecisionTreeClassifier(), X, y, ax=ax)
+
+        self.assert_images_similar(ax=ax)
 
     def test_isclassifier(self):
         """