Repository: DistrictDataLabs/yellowbrick

Issue:
ConfusionMatrix FontSize
The confusion matrix label font size is hardcoded to 8pt: https://github.com/DistrictDataLabs/yellowbrick/blob/develop/yellowbrick/classifier/confusion_matrix.py#L228

It appears that this may be a tad too small: https://stackoverflow.com/questions/47450804/yellowbrick-increasing-font-size-on-yellowbrick-generated-charts

We should probably remove this line and make it default to the matplotlib font size. 




Discussion:
+1! 

I'm changing my matplotlib defaults as on my UHD screen the tiny labels are hard to read, this is fine for the `ClassificationReport` but `ConfusionMatrix` is stubbornly unreadable. I'm planning to include these yb plots in a talk for the PyDataLondon conference this year. Unless there's a compelling reason to hard-code to size 8, I'd suggest using the `mpl` defaults. 

The hardcoding occurs 3 times here: https://github.com/DistrictDataLabs/yellowbrick/blob/develop/yellowbrick/classifier/confusion_matrix.py
+1 Was just going to file this

Resolving patch:
diff --git a/yellowbrick/__init__.py b/yellowbrick/__init__.py
--- a/yellowbrick/__init__.py
+++ b/yellowbrick/__init__.py
@@ -23,7 +23,7 @@
 _orig_rc_params = mpl.rcParams.copy()
 
 # Import the version number at the top level
-from .version import get_version
+from .version import get_version, __version_info__
 
 # Import the style management functions
 from .style.rcmod import reset_defaults, reset_orig
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
@@ -17,6 +17,7 @@
 ## Imports
 ##########################################################################
 
+import warnings
 import numpy as np
 
 from sklearn.metrics import confusion_matrix
@@ -31,43 +32,77 @@
 ## ConfusionMatrix
 ##########################################################################
 
+CMAP_UNDERCOLOR = 'w'
 CMAP_OVERCOLOR = '#2a7d4f'
+CMAP_MUTEDCOLOR = '0.75'
 
 
 class ConfusionMatrix(ClassificationScoreVisualizer):
     """
-    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.
+    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.
+    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
+    Requires a classification model.
 
     Parameters
     ----------
-    model : the Scikit-Learn estimator
-        Should be an instance of a classifier or __init__ will return an error.
+    model : estimator
+        Must be a classifier, otherwise raises YellowbrickTypeError
 
-    ax : the matplotlib axis to plot the figure on (if None, a new axis will be created)
+    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, values that appear at least once in y_true or y_pred are used in sorted order.
+        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.
+        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.
+
+    Attributes
+    ----------
+    confusion_matrix_ : array, shape = [n_classes, n_classes]
+        The numeric scores of the confusion matrix
+
+    class_counts_ : array, shape = [n_classes,]
+        The total number of each class supporting the confusion matrix
 
     Examples
     --------
-
     >>> from yellowbrick.classifier import ConfusionMatrix
     >>> from sklearn.linear_model import LogisticRegression
     >>> viz = ConfusionMatrix(LogisticRegression())
@@ -77,43 +112,56 @@ class ConfusionMatrix(ClassificationScoreVisualizer):
     """
 
 
-    def __init__(self, model, ax=None, classes=None, label_encoder=None, **kwargs):
+    def __init__(self, model, ax=None, classes=None, sample_weight=None,
+                 percent=False, label_encoder=None, cmap='YlOrRd',
+                 fontsize=None, **kwargs):
         super(ConfusionMatrix, self).__init__(
             model, ax=ax, classes=classes, **kwargs
         )
 
-        #Initialize all the other attributes we'll use (for coder clarity)
-        self.confusion_matrix = None
-
-        self.cmap = color_sequence(kwargs.pop('cmap', 'YlOrRd'))
-        self.cmap.set_under(color = 'w')
+        # Visual parameters
+        self.cmap = color_sequence(cmap)
+        self.cmap.set_under(color=CMAP_UNDERCOLOR)
         self.cmap.set_over(color=CMAP_OVERCOLOR)
-        self.edgecolors = [] #used to draw diagonal line for predicted class = true class
+        self.fontsize = fontsize
+
+        # Estimator parameters
         self.label_encoder = label_encoder
+        self.sample_weight = sample_weight
+        self.percent = percent
 
-    def score(self, X, y, sample_weight=None, percent=True):
+        # Used to draw diagonal line for predicted class = true class
+        self._edgecolors = []
+
+    def score(self, X, y, **kwargs):
         """
-        Generates the Scikit-Learn confusion_matrix and applies this to the appropriate axis
+        Draws a confusion matrix based on the test data supplied by comparing
+        predictions on instances X with the true values specified by the
+        target vector y.
 
         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
-
-        sample_weight: optional, passed to the confusion_matrix
-
-        percent: optional, Boolean. Determines whether or not the confusion_matrix
-                should be displayed as raw numbers or as a percent of the true
-                predictions. Note, if using a subset of classes in __init__, percent should
-                be set to False or inaccurate percents will be displayed.
         """
+        # Perform deprecation warnings for attributes to score
+        # TODO: remove this in v0.9
+        for param in ("percent", "sample_weight"):
+            if param in kwargs:
+                warnings.warn(PendingDeprecationWarning((
+                    "specifying '{}' in score is no longer supported, "
+                    "pass to constructor of the visualizer instead."
+                ).format(param)))
+
+                setattr(self, param, kwargs[param])
+
+        # Create predictions from X (will raise not fitted error)
         y_pred = self.predict(X)
 
-
+        # Encode the target with the supplied label encoder
         if self.label_encoder:
             try :
                 y = self.label_encoder.inverse_transform(y)
@@ -123,128 +171,100 @@ def score(self, X, y, sample_weight=None, percent=True):
                 y = [self.label_encoder[x] for x in y]
                 y_pred = [self.label_encoder[x] for x in y_pred]
 
-        self.confusion_matrix = confusion_matrix(
-            y, y_pred, labels=self.classes_, sample_weight=sample_weight
+        # Compute the confusion matrix and class counts
+        self.confusion_matrix_ = confusion_matrix(
+            y, y_pred, labels=self.classes_, sample_weight=self.sample_weight
         )
-        self._class_counts = self.class_counts(y)
+        self.class_counts_ = self.class_counts(y)
 
-        #Make array of only the classes actually being used.
-        #Needed because sklearn confusion_matrix only returns counts for selected classes
-            #but percent should be calculated based on all classes
+        # Make array of only the classes actually being used.
+        # Needed because sklearn confusion_matrix only returns counts for
+        # selected classes but percent should be calculated on all classes
         selected_class_counts = []
         for c in self.classes_:
             try:
-                selected_class_counts.append(self._class_counts[c])
+                selected_class_counts.append(self.class_counts_[c])
             except KeyError:
                 selected_class_counts.append(0)
-        self.selected_class_counts = np.array(selected_class_counts)
+        self.class_counts_ = np.array(selected_class_counts)
 
-        return self.draw(percent)
+        return self.draw()
 
-    def draw(self, percent=True):
+    def draw(self):
+        """
+        Renders the classification report; must be called after score.
         """
-        Renders the classification report
-        Should only be called internally, as it uses values calculated in Score
-        and score calls this method.
-
-        Parameters
-        ----------
 
-        percent:    Boolean
-                    Whether the heatmap should represent "% of True" or raw counts
+        # Perform display related manipulations on the confusion matrix data
+        cm_display = self.confusion_matrix_
+
+        # Convert confusion matrix to percent of each row, i.e. the
+        # predicted as a percent of true in each class.
+        if self.percent == True:
+            # Note: div_safe function returns 0 instead of NAN.
+            cm_display = div_safe(self.confusion_matrix_, self.class_counts_)
+            cm_display = np.round(cm_display* 100, decimals=0)
+
+        # Y axis should be sorted top to bottom in pcolormesh
+        cm_display = cm_display[::-1,::]
+
+        # Set up the dimensions of the pcolormesh
+        n_classes = len(self.classes_)
+        X, Y = np.arange(n_classes+1), np.arange(n_classes+1)
+        self.ax.set_ylim(bottom=0, top=cm_display.shape[0])
+        self.ax.set_xlim(left=0, right=cm_display.shape[1])
+
+        # Fetch the grid labels from the classes in correct order; set ticks.
+        xticklabels = self.classes_
+        yticklabels = self.classes_[::-1]
+        ticks = np.arange(n_classes) + 0.5
+
+        self.ax.set(xticks=ticks, yticks=ticks)
+        self.ax.set_xticklabels(xticklabels, rotation="vertical", fontsize=self.fontsize)
+        self.ax.set_yticklabels(yticklabels, fontsize=self.fontsize)
+
+        # Set data labels in the grid enumerating over all x,y class pairs.
+        # NOTE: X and Y are one element longer than the confusion matrix, so
+        # skip the last element in the enumeration to label grids.
+        for x in X[:-1]:
+            for y in Y[:-1]:
+
+                # Extract the value and the text label
+                value = cm_display[x,y]
+                svalue = "{:0.0f}".format(value)
+                if self.percent:
+                    svalue += "%"
+
+                # Determine the grid and text colors
+                base_color = self.cmap(value / cm_display.max())
+                text_color = find_text_color(base_color)
+
+                # Make zero values more subtle
+                if cm_display[x,y] == 0:
+                    text_color = CMAP_MUTEDCOLOR
+
+                # 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, fontsize=self.fontsize,
+                )
+
+                # Add a dark line on the grid with the diagonal. Note that the
+                # tick labels have already been reversed.
+                lc = 'k' if xticklabels[x] == yticklabels[y] else 'w'
+                self._edgecolors.append(lc)
+
+
+        # Draw the heatmap with colors bounded by vmin,vmax
+        vmin = 0.00001
+        vmax = 99.999 if self.percent == True else cm_display.max()
+        self.ax.pcolormesh(
+            X, Y, cm_display, vmin=vmin, vmax=vmax,
+            edgecolor=self._edgecolors, cmap=self.cmap, linewidth='0.01'
+        )
 
-        """
-        if percent == True:
-            #Convert confusion matrix to percent of each row, i.e. the predicted as a percent of true in each class
-            #div_safe function returns 0 instead of NAN.
-            self._confusion_matrix_display = div_safe(
-                    self.confusion_matrix,
-                    self.selected_class_counts
-                    )
-            self._confusion_matrix_display =np.round(self._confusion_matrix_display* 100, decimals=0)
-        else:
-            self._confusion_matrix_display = self.confusion_matrix
-
-        #Y axis should be sorted top to bottom in pcolormesh
-        self._confusion_matrix_plottable = self._confusion_matrix_display[::-1,::]
-
-        self.max = self._confusion_matrix_plottable.max()
-
-        #Set up the dimensions of the pcolormesh
-        X = np.linspace(start=0, stop=len(self.classes_), num=len(self.classes_)+1)
-        Y = np.linspace(start=0, stop=len(self.classes_), num=len(self.classes_)+1)
-        self.ax.set_ylim(bottom=0, top=self._confusion_matrix_plottable.shape[0])
-        self.ax.set_xlim(left=0, right=self._confusion_matrix_plottable.shape[1])
-
-        #Put in custom axis labels
-        self.xticklabels = self.classes_
-        self.yticklabels = self.classes_[::-1]
-        self.xticks = np.arange(0, len(self.classes_), 1) + .5
-        self.yticks = np.arange(0, len(self.classes_), 1) + .5
-        self.ax.set(xticks=self.xticks, yticks=self.yticks)
-        self.ax.set_xticklabels(self.xticklabels, rotation="vertical", fontsize=8)
-        self.ax.set_yticklabels(self.yticklabels, fontsize=8)
-
-        ######################
-        # Add the data labels to each square
-        ######################
-        for x_index, x in np.ndenumerate(X):
-            #np.ndenumerate returns a tuple for the index, must access first element using [0]
-            x_index = x_index[0]
-            for y_index, y in np.ndenumerate(Y):
-                #Clean up our iterators
-                #numpy doesn't like non integers as indexes; also np.ndenumerate returns tuple
-                x_int = int(x)
-                y_int = int(y)
-                y_index = y_index[0]
-
-                #X and Y are one element longer than the confusion_matrix. Don't want to add text for the last X or Y
-                if x_index == X[-1] or y_index == Y[-1]:
-                    break
-
-                #center the text in the middle of the block
-                text_x = x + 0.5
-                text_y = y + 0.5
-
-                #extract the value
-                grid_val = self._confusion_matrix_plottable[x_int,y_int]
-
-                #Determine text color
-                scaled_grid_val = grid_val / self.max
-                base_color = self.cmap(scaled_grid_val)
-                text_color= find_text_color(base_color)
-
-                #make zero values more subtle
-                if self._confusion_matrix_plottable[x_int,y_int] == 0:
-                    text_color = "0.75"
-
-                #Put the data labels in the middle of the heatmap square
-                self.ax.text(text_y,
-                            text_x,
-                            "{:.0f}{}".format(grid_val,"%" if percent==True else ""),
-                            va='center',
-                            ha='center',
-                            fontsize=8,
-                            color=text_color)
-
-                #If the prediction is correct, put a bounding box around that square to better highlight it to the user
-                #This will be used in ax.pcolormesh, setting now since we're iterating over the matrix
-                    #ticklabels are conveniently already reversed properly to match the _confusion_matrix_plottalbe order
-                if self.xticklabels[x_int] == self.yticklabels[y_int]:
-                    self.edgecolors.append('black')
-                else:
-                    self.edgecolors.append('w')
-
-        # Draw the heatmap. vmin and vmax operate in tandem with the cmap.set_under and cmap.set_over to alter the color of 0 and 100
-        highest_count = self._confusion_matrix_plottable.max()
-        vmax = 99.999 if percent == True else highest_count
-        self.ax.pcolormesh(X, Y,
-            self._confusion_matrix_plottable,
-            vmin=0.00001,
-            vmax=vmax,
-            edgecolor=self.edgecolors,
-            cmap=self.cmap,
-            linewidth='0.01') #edgecolor='0.75', linewidth='0.01'
+        # Return the axes being drawn on
         return self.ax
 
     def finalize(self, **kwargs):
diff --git a/yellowbrick/classifier/rocauc.py b/yellowbrick/classifier/rocauc.py
--- a/yellowbrick/classifier/rocauc.py
+++ b/yellowbrick/classifier/rocauc.py
@@ -60,11 +60,12 @@ class ROCAUC(ClassificationScoreVisualizer):
 
     Parameters
     ----------
-    ax : the axis to plot the figure on.
+    model : estimator
+        Must be a classifier, otherwise raises YellowbrickTypeError
 
-    model : the Scikit-Learn estimator
-        Should be an instance of a classifier, else the __init__ will
-        return an error.
+    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).
 
     classes : list
         A list of class names for the legend. If classes is None and a y value


Test patch:
diff --git a/tests/baseline_images/test_classifier/test_confusion_matrix/test_class_filter_eg_zoom_in.png b/tests/baseline_images/test_classifier/test_confusion_matrix/test_class_filter_eg_zoom_in.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_confusion_matrix/test_class_filter_eg_zoom_in.png differ
diff --git a/tests/baseline_images/test_classifier/test_confusion_matrix/test_confusion_matrix.png b/tests/baseline_images/test_classifier/test_confusion_matrix/test_confusion_matrix.png
Binary files a/tests/baseline_images/test_classifier/test_confusion_matrix/test_confusion_matrix.png and b/tests/baseline_images/test_classifier/test_confusion_matrix/test_confusion_matrix.png differ
diff --git a/tests/baseline_images/test_classifier/test_confusion_matrix/test_extra_classes.png b/tests/baseline_images/test_classifier/test_confusion_matrix/test_extra_classes.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_confusion_matrix/test_extra_classes.png differ
diff --git a/tests/baseline_images/test_classifier/test_confusion_matrix/test_fontsize.png b/tests/baseline_images/test_classifier/test_confusion_matrix/test_fontsize.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_confusion_matrix/test_fontsize.png differ
diff --git a/tests/baseline_images/test_classifier/test_confusion_matrix/test_inverse_mapping.png b/tests/baseline_images/test_classifier/test_confusion_matrix/test_inverse_mapping.png
Binary files a/tests/baseline_images/test_classifier/test_confusion_matrix/test_inverse_mapping.png and b/tests/baseline_images/test_classifier/test_confusion_matrix/test_inverse_mapping.png differ
diff --git a/tests/baseline_images/test_classifier/test_confusion_matrix/test_no_classes_provided.png b/tests/baseline_images/test_classifier/test_confusion_matrix/test_no_classes_provided.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_confusion_matrix/test_no_classes_provided.png differ
diff --git a/tests/baseline_images/test_classifier/test_confusion_matrix/test_one_class.png b/tests/baseline_images/test_classifier/test_confusion_matrix/test_one_class.png
Binary files a/tests/baseline_images/test_classifier/test_confusion_matrix/test_one_class.png and b/tests/baseline_images/test_classifier/test_confusion_matrix/test_one_class.png differ
diff --git a/tests/baseline_images/test_classifier/test_confusion_matrix/test_pandas_integration.png b/tests/baseline_images/test_classifier/test_confusion_matrix/test_pandas_integration.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_confusion_matrix/test_pandas_integration.png differ
diff --git a/tests/baseline_images/test_classifier/test_confusion_matrix/test_percent_mode.png b/tests/baseline_images/test_classifier/test_confusion_matrix/test_percent_mode.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_classifier/test_confusion_matrix/test_percent_mode.png differ
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
@@ -1,138 +1,325 @@
-import yellowbrick
+# tests.test_classifier.test_confusion_matrix
+# Tests for the confusion matrix visualizer
+#
+# Aithor:  Neal Humphrey
+# Author:  Benjamin Bengfort <bbengfort@districtdatalabs.com>
+# Created: Tue May 03 11:05:11 2017 -0700
+#
+# ID: test_confusion_matrix.py [] benjamin@bengfort.com $
+
+"""
+Tests for the confusion matrix visualizer
+"""
+
+##########################################################################
+## Imports
+##########################################################################
+
+import six
+import pytest
+import yellowbrick as yb
+import numpy.testing as npt
 import matplotlib.pyplot as plt
 
+from collections import namedtuple
+
 from yellowbrick.classifier.confusion_matrix import *
 
 from tests.base import VisualTestCase
+from tests.dataset import DatasetMixin
 
+from sklearn.svm import SVC
 from sklearn.datasets import load_digits
+from sklearn.naive_bayes import GaussianNB
 from sklearn.preprocessing import LabelEncoder
 from sklearn.linear_model import LogisticRegression
 from sklearn.linear_model import PassiveAggressiveRegressor
 from sklearn.model_selection import train_test_split as tts
 
+try:
+    import pandas as pd
+except ImportError:
+    pd = None
+
+
+# Helpers for fixtures
+Dataset = namedtuple('Dataset', 'X,y')
+Split = namedtuple('Split', 'train,test')
 
-class ConfusionMatrixTests(VisualTestCase):
+
+@pytest.fixture(scope='class')
+def digits(request):
     """
-    ConfusionMatrix visualizer
+    Creates a fixture of train and test splits for the sklearn digits dataset
+    For ease of use returns a Dataset named tuple composed of two Split tuples.
     """
+    data = load_digits()
+    X_train, X_test, y_train, y_test = tts(
+        data.data, data.target, test_size=0.2, random_state=11
+    )
 
-    def setUp(self):
-        #Use the same data for all the tests
-        self.digits = load_digits()
+    # Set a class attribute for digits
+    request.cls.digits = Dataset(
+        Split(X_train, X_test), Split(y_train, y_test)
+    )
 
-        X = self.digits.data
-        y = self.digits.target
 
-        X_train, X_test, y_train, y_test = tts(X,y, test_size =0.2, random_state=11)
-        self.X_train = X_train
-        self.X_test = X_test
-        self.y_train = y_train
-        self.y_test = y_test
+@pytest.mark.usefixtures("digits")
+class ConfusionMatrixTests(VisualTestCase, DatasetMixin):
+    """
+    ConfusionMatrix visualizer tests
+    """
 
     def test_confusion_matrix(self):
         """
-        Integration test of visualizer
+        Integration test on digits dataset with LogisticRegression
         """
-        fig = plt.figure()
-        ax = fig.add_subplot()
+        _, ax = plt.subplots()
 
-        model = LogisticRegression()
+        model = LogisticRegression(random_state=93)
         cm = ConfusionMatrix(model, ax=ax, classes=[0,1,2,3,4,5,6,7,8,9])
-        cm.fit(self.X_train, self.y_train)
-        cm.score(self.X_test, self.y_test)
+        cm.fit(self.digits.X.train, self.digits.y.train)
+        cm.score(self.digits.X.test, self.digits.y.test)
 
-        self.assert_images_similar(cm)
+        self.assert_images_similar(cm, tol=10)
+
+        # Ensure correct confusion matrix under the hood
+        npt.assert_array_equal(cm.confusion_matrix_, np.array([
+           [38,  0,  0,  0,  0,  0,  0,  0,  0,  0],
+           [ 0, 35,  0,  0,  0,  0,  0,  0,  2,  0],
+           [ 0,  0, 39,  0,  0,  0,  0,  0,  0,  0],
+           [ 0,  0,  0, 38,  0,  1,  0,  0,  2,  0],
+           [ 0,  0,  0,  0, 40,  0,  0,  1,  0,  0],
+           [ 0,  0,  0,  0,  0, 27,  0,  0,  0,  0],
+           [ 0,  0,  0,  0,  0,  1, 29,  0,  0,  0],
+           [ 0,  0,  0,  0,  0,  0,  0, 35,  0,  1],
+           [ 0,  2,  0,  0,  0,  0,  0,  0, 32,  0],
+           [ 0,  0,  0,  0,  0,  0,  0,  1,  1, 35]]))
 
     def test_no_classes_provided(self):
         """
-        Assert no errors when no classes are provided
+        Integration test on digits dataset with GaussianNB, no classes
+        """
+        _, ax = plt.subplots()
+
+        model = GaussianNB()
+        cm = ConfusionMatrix(model, ax=ax, classes=None)
+        cm.fit(self.digits.X.train, self.digits.y.train)
+        cm.score(self.digits.X.test, self.digits.y.test)
+
+        self.assert_images_similar(cm, tol=10)
+
+        # Ensure correct confusion matrix under the hood
+        npt.assert_array_equal(cm.confusion_matrix_, np.array([
+           [36,  0,  0,  0,  1,  0,  0,  1,  0,  0],
+           [ 0, 31,  0,  0,  0,  0,  0,  1,  3,  2],
+           [ 0,  1, 34,  0,  0,  0,  0,  0,  4,  0],
+           [ 0,  1,  0, 33,  0,  2,  0,  2,  3,  0],
+           [ 0,  0,  0,  0, 36,  0,  0,  5,  0,  0],
+           [ 0,  0,  0,  0,  0, 27,  0,  0,  0,  0],
+           [ 0,  0,  1,  0,  1,  0, 28,  0,  0,  0],
+           [ 0,  0,  0,  0,  0,  0,  0, 36,  0,  0],
+           [ 0,  3,  0,  1,  0,  1,  0,  4, 25,  0],
+           [ 1,  2,  0,  0,  1,  0,  0,  8,  3, 22]]))
+
+    def test_fontsize(self):
+        """
+        Test confusion matrix with smaller fontsize on digits dataset with SVC
+        """
+        _, ax = plt.subplots()
+
+        model = SVC(random_state=93)
+        cm = ConfusionMatrix(model, ax=ax, fontsize=8)
+
+        cm.fit(self.digits.X.train, self.digits.y.train)
+        cm.score(self.digits.X.test, self.digits.y.test)
+
+        self.assert_images_similar(cm, tol=10)
+
+    def test_percent_mode(self):
+        """
+        Test confusion matrix in percent mode on digits dataset with SVC
         """
-        model = LogisticRegression()
-        cm = ConfusionMatrix(model)
-        cm.fit(self.X_train, self.y_train)
-        cm.score(self.X_test, self.y_test)
+        _, ax = plt.subplots()
+
+        model = SVC(random_state=93)
+        cm = ConfusionMatrix(model, ax=ax, percent=True)
+
+        cm.fit(self.digits.X.train, self.digits.y.train)
+        cm.score(self.digits.X.test, self.digits.y.test)
 
-    def test_raw_count_mode(self):
+        self.assert_images_similar(cm, tol=10)
+
+        # Ensure correct confusion matrix under the hood
+        npt.assert_array_equal(cm.confusion_matrix_, np.array([
+           [16,  0,  0,  0,  0, 22,  0,  0,  0,  0],
+           [ 0, 11,  0,  0,  0, 26,  0,  0,  0,  0],
+           [ 0,  0, 10,  0,  0, 29,  0,  0,  0,  0],
+           [ 0,  0,  0,  6,  0, 35,  0,  0,  0,  0],
+           [ 0,  0,  0,  0, 11, 30,  0,  0,  0,  0],
+           [ 0,  0,  0,  0,  0, 27,  0,  0,  0,  0],
+           [ 0,  0,  0,  0,  0,  9, 21,  0,  0,  0],
+           [ 0,  0,  0,  0,  0, 29,  0,  7,  0,  0],
+           [ 0,  0,  0,  0,  0, 32,  0,  0,  2,  0],
+           [ 0,  0,  0,  0,  0, 34,  0,  0,  0,  3]]))
+
+    def test_deprecated_fit_kwargs(self):
         """
-        Assert that raw count mode works as expected
+        Test that passing percent or sample_weight is deprecated
         """
-        model = LogisticRegression()
-        cm = ConfusionMatrix(model)
-        cm.fit(self.X_train, self.y_train)
-        cm.score(self.X_test, self.y_test, percent=False)
+        if yb.__version_info__['minor'] >= 9:
+            pytest.fail("deprecation warnings should be removed after 0.9")
+
+        args = (self.digits.X.test, self.digits.y.test)
+        cm = ConfusionMatrix(LogisticRegression())
+        cm.fit(self.digits.X.train, self.digits.y.train)
 
-    def test_zoomed_in(self):
+        # Deprecated percent in score
+        pytest.deprecated_call(cm.score, *args, percent=True)
+
+        # Deprecated sample_weight in score
+        pytest.deprecated_call(cm.score, *args, sample_weight=np.arange(360))
+
+    def test_class_filter_eg_zoom_in(self):
         """
-        Test zoomed in classes works as expected
+        Test filtering classes zooms in on the confusion matrix.
         """
-        model = LogisticRegression()
-        cm = ConfusionMatrix(model, classes=[0,1,2])
-        cm.fit(self.X_train, self.y_train)
-        cm.score(self.X_test, self.y_test)
+        _, ax = plt.subplots()
+
+        model = LogisticRegression(random_state=93)
+        cm = ConfusionMatrix(model, ax=ax, classes=[0,1,2])
+        cm.fit(self.digits.X.train, self.digits.y.train)
+        cm.score(self.digits.X.test, self.digits.y.test)
+
+        self.assert_images_similar(cm, tol=10)
+
+        # Ensure correct confusion matrix under the hood
+        npt.assert_array_equal(cm.confusion_matrix_, np.array([
+           [38,  0,  0],
+           [ 0, 35,  0],
+           [ 0,  0, 39]]))
 
     def test_extra_classes(self):
         """
-        Test that extra classes are ignored
+        Assert that any extra classes are simply ignored
         """
-        model = LogisticRegression()
-        cm = ConfusionMatrix(model, classes=[0,1,2,11])
-        cm.fit(self.X_train, self.y_train)
-        cm.score(self.X_test, self.y_test)
-        self.assertTrue(cm.selected_class_counts[3]==0)
+        # TODO: raise exception instead
+        _, ax = plt.subplots()
+
+        model = LogisticRegression(random_state=93)
+        cm = ConfusionMatrix(model, ax=ax, classes=[0,1,2,11])
+        cm.fit(self.digits.X.train, self.digits.y.train)
+        cm.score(self.digits.X.test, self.digits.y.test)
+
+        npt.assert_array_equal(cm.class_counts_, [38, 37, 39,  0])
+
+        # Ensure correct confusion matrix under the hood
+        npt.assert_array_equal(cm.confusion_matrix_, np.array([
+           [38,  0,  0, 0],
+           [ 0, 35,  0, 0],
+           [ 0,  0, 39, 0],
+           [ 0,  0,  0, 0]]))
+
+        self.assert_images_similar(cm, tol=10)
 
     def test_one_class(self):
         """
-        Test single class confusion matrix
+        Test single class confusion matrix with LogisticRegression
         """
-        fig = plt.figure()
-        ax = fig.add_subplot()
+        _, ax = plt.subplots()
 
-        model = LogisticRegression()
+        model = LogisticRegression(random_state=93)
         cm = ConfusionMatrix(model, ax=ax, classes=[0])
-        cm.fit(self.X_train, self.y_train)
-        cm.score(self.X_test, self.y_test)
+        cm.fit(self.digits.X.train, self.digits.y.train)
+        cm.score(self.digits.X.test, self.digits.y.test)
 
-        self.assert_images_similar(cm)
+        self.assert_images_similar(cm, tol=10)
 
     def test_defined_mapping(self):
         """
-        Test mapping as label encoder
+        Test mapping as label encoder to define tick labels
         """
-        model = LogisticRegression()
+        _, ax = plt.subplots()
+
+        model = LogisticRegression(random_state=93)
         classes = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
         mapping = {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five',
                    6: 'six', 7: 'seven', 8: 'eight', 9: 'nine'}
-        cm = ConfusionMatrix(model, classes=classes, label_encoder = mapping)
-        cm.fit(self.X_train, self.y_train)
-        cm.score(self.X_test, self.y_test)
+        cm = ConfusionMatrix(model, ax=ax, classes=classes, label_encoder=mapping)
+        cm.fit(self.digits.X.train, self.digits.y.train)
+        cm.score(self.digits.X.test, self.digits.y.test)
+
+        assert [l.get_text() for l in ax.get_xticklabels()] == classes
+        ylabels = [l.get_text() for l in ax.get_yticklabels()]
+        ylabels.reverse()
 
     def test_inverse_mapping(self):
         """
-        Test LabelEncoder as label encoder
+        Test LabelEncoder as label encoder to define tick labels
         """
-        fig = plt.figure()
-        ax = fig.add_subplot()
+        _, ax = plt.subplots()
 
-        model = LogisticRegression()
+        model = LogisticRegression(random_state=93)
         le = LabelEncoder()
         classes = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
         le.fit(['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'])
 
         cm = ConfusionMatrix(model, ax=ax, classes=classes, label_encoder=le)
-        cm.fit(self.X_train, self.y_train)
-        cm.score(self.X_test, self.y_test)
+        cm.fit(self.digits.X.train, self.digits.y.train)
+        cm.score(self.digits.X.test, self.digits.y.test)
+
+        assert [l.get_text() for l in ax.get_xticklabels()] == classes
+        ylabels = [l.get_text() for l in ax.get_yticklabels()]
+        ylabels.reverse()
+        assert  ylabels == classes
+
+    @pytest.mark.skipif(pd is None, reason="test requires pandas")
+    def test_pandas_integration(self):
+        """
+        Test with Pandas DataFrame and Series input
+        """
+        _, ax = plt.subplots()
 
-        self.assert_images_similar(cm)
+        # 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=8873)
+        X_train, X_test, y_train, y_test = splits
+
+        # Create confusion matrix
+        model = GaussianNB()
+        cm = ConfusionMatrix(model, ax=ax, classes=None)
+        cm.fit(X_train, y_train)
+        cm.score(X_test, y_test)
+
+        tol = 0.1 if six.PY3 else 40
+        self.assert_images_similar(cm, tol=tol)
+
+        # Ensure correct confusion matrix under the hood
+        npt.assert_array_equal(cm.confusion_matrix_, np.array([
+            [3012,  114],
+            [   1,  985]
+        ]))
 
 
     def test_isclassifier(self):
         """
-        Test taht non-classifiers raise exceptions
+        Assert that only classifiers can be used with the visualizer.
         """
         model = PassiveAggressiveRegressor()
-        message = 'This estimator is not a classifier; try a regression or clustering score visualizer instead!'
-        classes = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
+        message = (
+            'This estimator is not a classifier; '
+            'try a regression or clustering score visualizer instead!'
+        )
 
-        with self.assertRaisesRegexp(yellowbrick.exceptions.YellowbrickError, message):
-            ConfusionMatrix(model, classes=classes)
+        with self.assertRaisesRegexp(yb.exceptions.YellowbrickError, message):
+            ConfusionMatrix(model)