Repository: DistrictDataLabs/yellowbrick

Issue:
[Feature Suggestion] Add histogram plot of residual errors to ResidualsPlot
The current `ResidualsPlot` shows training and testing residuals as a scatter plot, by eye we can get an idea of whether more errors are above or below the 0 line. By adding a histogram of testing errors we might more clearly be able to tell if errors have a Normal  distribution.

In the following examples I have some large positive and negative errors, from the histogram it looks as though I have a negatively skewed distribution which might tell me something about my training examples:
![image](https://user-images.githubusercontent.com/273210/27382522-40ac05a4-567f-11e7-927b-7c724a0faa0f.png)


```
from yellowbrick.regressor import ResidualsPlot
fig, ax = plt.subplots(figsize=(8,6)); 
model = ResidualsPlot(clone_estimator(clf), ax=ax)
model.fit(X_train, y_train)
model.score(X_test, y_test)

# add histogram of residual errors
left, bottom, width, height = [0.65, 0.17, 0.2, 0.2]
ax2 = fig.add_axes([left, bottom, width, height])

testing_residuals = pd.Series(model.predict(X_test) - y_test)
testing_residuals.plot(kind="hist", bins=50, title="Residuals on Predicted", ax=ax2);
ax2.vlines(0, ymin=0, ymax=ax2.get_ylim()[1] ) # add x==0 line

model.poof()
```

It isn't obvious where the best location would be for the histogram. Annoyingly I cannot get an `alpha` value for `ax2` either (I'd hoped to make this semi-transparent so location was less of an issue). 


Discussion:
@ianozsvald thanks for this great feature enhancement suggestion. you're welcome to make a pull request with this enhancement =)
@bbengfort 
Sorry, I'm only going as far as sharing some proof-of-concept code, I'm still recovering from running PyDataLondon 2017 and I'm not up to extending any libraries at the moment! Maybe someone will find time to take this a little further. Cheers!
@ianozsvald thank you so much for your feature suggestions and the time that you've spent in writing up the detailed issues with examples. it is really fantastic and helpful. i'm sure that someone will move forward with these. 
Well, thank you all too for putting this library together, I've got a bunch of my own hacky viz tools but you've built something far more useful here. @rebeccabilbro's talk for us at the conference (and the book signing she joined me for) was ace :-)
Hey there @ianozsvald - thanks so much for all your work on PyData London - what a terrific conference! And thanks for checking out Yellowbrick -- I love this idea of plotting error distributions to make it easier to look for things like skew and heavy tails. As for how to best cope with subaxis locations, we might take a look at [GridSpec](https://matplotlib.org/api/gridspec_api.html#matplotlib.gridspec.GridSpec), similar to what @pdamodaran did with [JointPlot](https://github.com/DistrictDataLabs/yellowbrick/blob/develop/yellowbrick/features/jointplot.py#L208).
I would be up for making the enhancement to the ResidualsPlot, do you guys think adding the histogram should be on the top of the main plot or the bottom?
@pdamodaran @ianozsvald  my instinct actually says to put it on the right side, oriented vertically so that the histogram shares an access with the residuals (the y axis of the plot). In this orientation I think it might be easier to directly compare and would probably also have the effect of balancing the axes so that zero is in the middle, RE: the axes issues we had in #263 -- what do you guys think?
@ianozsvald sorry for the delay on this; we've been slammed - but I wanted to take a crack at this when you reminded me via the notebooks in the Slack channel the other day. 

What do you think about this?

![residuals_hist_annotation](https://user-images.githubusercontent.com/745966/41488599-dd7b7f6c-70ba-11e8-925f-f59bcfb2a2df.png)

It's an initial prototype, but I'm thinking we'll just make it an option right now (e.g. `hist=True`) and then this is displayed. 

Resolving patch:
diff --git a/docs/api/regressor/residuals.py b/docs/api/regressor/residuals.py
--- a/docs/api/regressor/residuals.py
+++ b/docs/api/regressor/residuals.py
@@ -1,4 +1,5 @@
 import pandas as pd
+import matplotlib.pyplot as plt
 
 from sklearn.linear_model import Ridge
 from sklearn.model_selection import train_test_split
@@ -6,24 +7,38 @@
 from yellowbrick.regressor import ResidualsPlot
 
 
-if __name__ == '__main__':
+def plot_residuals(X, y, model, outpath="images/residuals.png", **kwargs):
+    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
+
+    _, ax = plt.subplots()
+
+    visualizer = ResidualsPlot(model, ax=ax, **kwargs)
+    visualizer.fit(X_train, y_train)
+    visualizer.score(X_test, y_test)
+    visualizer.poof(outpath=outpath)
+
+
+def load_concrete():
     # Load the regression data set
     df = pd.read_csv("../../../examples/data/concrete/concrete.csv")
 
-    feature_names = ['cement', 'slag', 'ash', 'water', 'splast', 'coarse', 'fine', 'age']
+    feature_names = [
+        'cement', 'slag', 'ash', 'water', 'splast', 'coarse', 'fine', 'age'
+    ]
     target_name = 'strength'
 
     # Get the X and y data from the DataFrame
-    X = df[feature_names].as_matrix()
-    y = df[target_name].as_matrix()
+    X = df[feature_names]
+    y = df[target_name]
+
+    return X, y
 
-    # Create the train and test data
-    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
 
-    # Instantiate the linear model and visualizer
-    ridge = Ridge()
-    visualizer = ResidualsPlot(ridge)
 
-    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/residuals.png")             # Draw/show/poof the data
+if __name__ == '__main__':
+    # Draw the default residuals graph
+    X, y = load_concrete()
+    plot_residuals(X, y, Ridge())
+
+    # Draw the residuals graph with no histogram
+    plot_residuals(X, y, Ridge(), "images/residuals_no_hist.png", hist=False)
diff --git a/yellowbrick/regressor/residuals.py b/yellowbrick/regressor/residuals.py
--- a/yellowbrick/regressor/residuals.py
+++ b/yellowbrick/regressor/residuals.py
@@ -18,10 +18,22 @@
 ## Imports
 ##########################################################################
 
+
+import matplotlib.pyplot as plt
+
+try:
+    # Only available in Matplotlib >= 2.0.2
+    from mpl_toolkits.axes_grid1 import make_axes_locatable
+except ImportError:
+    make_axes_locatable = None
+
 from sklearn.model_selection import train_test_split
 
-from ..style.palettes import LINE_COLOR
 from .base import RegressionScoreVisualizer
+
+from ..style.palettes import LINE_COLOR
+from ..utils.decorators import memoized
+from ..exceptions import YellowbrickValueError
 from ..bestfit import draw_best_fit, draw_identity_line
 
 
@@ -31,6 +43,7 @@
     "ResidualsPlot", "residuals_plot"
 ]
 
+
 ##########################################################################
 ## Prediction Error Plots
 ##########################################################################
@@ -46,7 +59,7 @@ class PredictionError(RegressionScoreVisualizer):
     ----------
 
     model : a Scikit-Learn regressor
-        Should be an instance of a regressor, otherwise a will raise a
+        Should be an instance of a regressor, otherwise will raise a
         YellowbrickTypeError exception on instantiation.
 
     ax : matplotlib Axes, default: None
@@ -313,15 +326,18 @@ class ResidualsPlot(RegressionScoreVisualizer):
 
     Parameters
     ----------
-
     model : a Scikit-Learn regressor
-        Should be an instance of a regressor, otherwise a will raise a
+        Should be an instance of a regressor, otherwise will raise a
         YellowbrickTypeError exception on instantiation.
 
     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).
 
+    hist : bool, default: True
+        Draw a histogram showing the distribution of the residuals on the
+        right side of the figure. Requires Matplotlib >= 2.0.2.
+
     train_color : color, default: 'b'
         Residuals for training data are ploted with this color but also
         given an opacity of 0.5 to ensure that the test data residuals
@@ -331,7 +347,7 @@ class ResidualsPlot(RegressionScoreVisualizer):
         Residuals for test data are plotted with this color. In order to
         create generalizable models, reserved test data residuals are of
         the most analytical interest, so these points are highlighted by
-        hvaing full opacity. Can be any matplotlib color.
+        having full opacity. Can be any matplotlib color.
 
     line_color : color, default: dark grey
         Defines the color of the zero error line, can be any matplotlib color.
@@ -352,28 +368,51 @@ class ResidualsPlot(RegressionScoreVisualizer):
 
     Notes
     -----
-
     ResidualsPlot is a ScoreVisualizer, meaning that it wraps a model and
-    its primary entry point is the `score()` method.
+    its primary entry point is the ``score()`` method.
+
+    The residuals histogram feature requires matplotlib 2.0.2 or greater.
     """
-    def __init__(self, model, ax=None, **kwargs):
+    def __init__(self, model, ax=None, hist=True, train_color='b',
+                 test_color='g', line_color=LINE_COLOR, **kwargs):
 
         super(ResidualsPlot, self).__init__(model, ax=ax, **kwargs)
 
-        # TODO Is there a better way to differentiate between train and test points?
-        # We'd like to color them differently in draw...
-        # Can the user pass those in as keyword arguments?
+        # TODO: allow more scatter plot arguments for train and test points
+        # See #475 (RE: ScatterPlotMixin)
         self.colors = {
-            'train_point': kwargs.pop('train_color', 'b'),
-            'test_point': kwargs.pop('test_color', 'g'),
-            'line': kwargs.pop('line_color', LINE_COLOR),
+            'train_point': train_color,
+            'test_point': test_color,
+            'line': line_color,
         }
 
-    def fit(self, X, y=None, **kwargs):
+        self.hist = hist
+        if self.hist:
+            self.hax # If hist is True, test the version availability
+
+    @memoized
+    def hax(self):
+        """
+        Returns the histogram axes, creating it only on demand.
+        """
+        if make_axes_locatable is None:
+            raise YellowbrickValueError((
+                "residuals histogram requires matplotlib 2.0.2 or greater "
+                "please upgrade matplotlib or set hist=False on the visualizer"
+            ))
+
+        divider = make_axes_locatable(self.ax)
+
+        hax = divider.append_axes("right", size=1, pad=0.1, sharey=self.ax)
+        hax.yaxis.tick_right()
+        hax.grid(False, axis='x')
+
+        return hax
+
+    def fit(self, X, y, **kwargs):
         """
         Parameters
         ----------
-
         X : ndarray or DataFrame of shape n x m
             A matrix of n instances with m features
 
@@ -381,9 +420,14 @@ def fit(self, X, y=None, **kwargs):
             An array or series of target values
 
         kwargs: keyword arguments passed to Scikit-Learn API.
+
+        Returns
+        -------
+        self : visualizer instance
         """
         super(ResidualsPlot, self).fit(X, y, **kwargs)
         self.score(X, y, train=True)
+        return self
 
     def score(self, X, y=None, train=False, **kwargs):
         """
@@ -405,9 +449,9 @@ def score(self, X, y=None, train=False, **kwargs):
 
         Returns
         ------
-
-        ax : the axis with the plotted figure
-
+        score : float
+            The score of the underlying estimator, usually the R-squared score
+            for regression estimators.
         """
         score = self.estimator.score(X, y, **kwargs)
         if train:
@@ -423,6 +467,11 @@ def score(self, X, y=None, train=False, **kwargs):
 
     def draw(self, y_pred, residuals, train=False, **kwargs):
         """
+        Draw the residuals against the predicted value for the specified split.
+        It is best to draw the training split first, then the test split so
+        that the test split (usually smaller) is above the training split;
+        particularly if the histogram is turned on.
+
         Parameters
         ----------
         y_pred : ndarray or Series of length n
@@ -432,16 +481,14 @@ def draw(self, y_pred, residuals, train=False, **kwargs):
             An array or series of the difference between the predicted and the
             target values
 
-        train : boolean
+        train : boolean, default: False
             If False, `draw` assumes that the residual points being plotted
             are from the test data; if True, `draw` assumes the residuals
             are the train data.
 
         Returns
         ------
-
         ax : the axis with the plotted figure
-
         """
 
         if train:
@@ -453,7 +500,15 @@ def draw(self, y_pred, residuals, train=False, **kwargs):
             alpha = 0.9
             label = "Test $R^2 = {:0.3f}$".format(self.test_score_)
 
+        # Draw the residuals scatter plot
         self.ax.scatter(y_pred, residuals, c=color, alpha=alpha, label=label)
+
+        # Add residuals histogram histogram
+        if self.hist:
+            self.hax.hist(residuals, bins=50, orientation="horizontal")
+
+        # Ensure the current axes is always the main residuals axes
+        plt.sca(self.ax)
         return self.ax
 
     def finalize(self, **kwargs):
@@ -464,7 +519,6 @@ def finalize(self, **kwargs):
         Parameters
         ----------
         kwargs: generic keyword arguments.
-
         """
         # Add the title to the plot
         self.set_title('Residuals for {} Model'.format(self.name))
@@ -479,28 +533,77 @@ def finalize(self, **kwargs):
         self.ax.set_ylabel('Residuals')
         self.ax.set_xlabel("Predicted Value")
 
-
-def residuals_plot(model, X, y=None, ax=None, **kwargs):
+        # Finalize the histogram axes
+        if self.hist:
+            self.hax.axhline(y=0, c=self.colors['line'])
+            self.hax.set_xlabel("Distribution")
+
+
+def residuals_plot(model,
+                   X,
+                   y,
+                   ax=None,
+                   hist=True,
+                   test_size=0.25,
+                   train_color='b',
+                   test_color='g',
+                   line_color=LINE_COLOR,
+                   random_state=None,
+                   **kwargs):
     """Quick method:
 
-    Plot  the residuals on the vertical axis and the
-    independent variable on the horizontal axis.
+    Divides the dataset X, y into a train and test split (the size of the
+    splits determined by test_size) then plots the training and test residuals
+    agains the predicted value for the given model.
 
     This helper function is a quick wrapper to utilize the ResidualsPlot
     ScoreVisualizer for one-off analysis.
 
     Parameters
     ----------
+    model : a Scikit-Learn regressor
+        Should be an instance of a regressor, otherwise will raise a
+        YellowbrickTypeError exception on instantiation.
+
     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
-        The axes to plot the figure on.
+    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).
 
-    model : the Scikit-Learn estimator (should be a regressor)
+    hist : bool, default: True
+        Draw a histogram showing the distribution of the residuals on the
+        right side of the figure. Requires Matplotlib >= 2.0.2.
+
+    test_size : float, int default: 0.25
+        If float, should be between 0.0 and 1.0 and represent the proportion
+        of the dataset to include in the test split. If int, represents the
+        absolute number of test samples.
+
+    train_color : color, default: 'b'
+        Residuals for training data are ploted with this color but also
+        given an opacity of 0.5 to ensure that the test data residuals
+        are more visible. Can be any matplotlib color.
+
+    test_color : color, default: 'g'
+        Residuals for test data are plotted with this color. In order to
+        create generalizable models, reserved test data residuals are of
+        the most analytical interest, so these points are highlighted by
+        having full opacity. Can be any matplotlib color.
+
+    line_color : color, default: dark grey
+        Defines the color of the zero error line, can be any matplotlib color.
+
+    random_state : int, RandomState instance or None, optional
+        Passed to the train_test_split function.
+
+    kwargs : dict
+        Keyword arguments that are passed to the base class and may influence
+        the visualization as defined in other Visualizers.
 
     Returns
     -------
@@ -508,13 +611,18 @@ def residuals_plot(model, X, y=None, ax=None, **kwargs):
         Returns the axes that the residuals plot was drawn on.
     """
     # Instantiate the visualizer
-    visualizer = ResidualsPlot(model, ax, **kwargs)
+    visualizer = ResidualsPlot(
+        model=model, ax=ax, hist=hist, train_color=train_color,
+        test_color=train_color, line_color=line_color, **kwargs
+    )
 
     # Create the train and test splits
-    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
+    X_train, X_test, y_train, y_test = train_test_split(
+        X, y, test_size=test_size, random_state=random_state
+    )
 
     # Fit and transform the visualizer (calls draw)
-    visualizer.fit(X_train, y_train, **kwargs)
+    visualizer.fit(X_train, y_train)
     visualizer.score(X_test, y_test)
     visualizer.finalize()
 


Test patch:
diff --git a/tests/base.py b/tests/base.py
--- a/tests/base.py
+++ b/tests/base.py
@@ -72,7 +72,7 @@ def setUp(self):
         self.assertEqual(self._backend, 'agg')
         super(VisualTestCase, self).setUp()
 
-    def assert_images_similar(self, visualizer=None, ax=None, tol=0.01):
+    def assert_images_similar(self, visualizer=None, ax=None, tol=0.01, **kwargs):
         """Accessible testing method for testing generation of a Visualizer.
 
         Requires the placement of a baseline image for comparison in the
@@ -103,13 +103,16 @@ def assert_images_similar(self, visualizer=None, ax=None, tol=0.01):
             The tolerance (a color value difference, where 255 is the
             maximal difference).  The test fails if the average pixel
             difference is greater than this value.
+
+        kwargs : dict
+            Options to pass to the ImageComparison class.
         """
         # Hide this method from the pytest traceback on test failure.
         __tracebackhide__ = True
 
         # Build and execute the image comparison
         compare = ImageComparison(
-            inspect.stack(), visualizer=visualizer, ax=ax, tol=tol
+            inspect.stack(), visualizer=visualizer, ax=ax, tol=tol, **kwargs
         )
         compare()
 
@@ -167,7 +170,7 @@ class ImageComparison(object):
     """
 
     def __init__(self, stack, visualizer=None, ax=None, tol=0.01, ext=".png",
-                 remove_ticks=True, remove_title=True):
+                 remove_ticks=True, remove_title=True, remove_legend=False):
 
         # Ensure we have something to draw on
         if visualizer is None and ax is None:
@@ -201,6 +204,7 @@ def __init__(self, stack, visualizer=None, ax=None, tol=0.01, ext=".png",
         self.ext = ext
         self.remove_ticks = remove_ticks
         self.remove_title = remove_title
+        self.remove_legend = remove_legend
 
     def __call__(self):
         """
@@ -267,6 +271,9 @@ def cleanup(self):
                 except AttributeError:
                     continue
 
+        if self.remove_legend:
+            self.ax.legend_.remove()
+
     def save(self):
         """
         Save the actual image to disk after cleaning it up.
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_peplot_no_lines.png b/tests/baseline_images/test_regressor/test_residuals/test_peplot_no_lines.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_regressor/test_residuals/test_peplot_no_lines.png differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_peplot_no_shared_limits.png b/tests/baseline_images/test_regressor/test_residuals/test_peplot_no_shared_limits.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_regressor/test_residuals/test_peplot_no_shared_limits.png differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_pred_error.png b/tests/baseline_images/test_regressor/test_residuals/test_pred_error.png
deleted file mode 100644
Binary files a/tests/baseline_images/test_regressor/test_residuals/test_pred_error.png and /dev/null differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_pred_error_integration.png b/tests/baseline_images/test_regressor/test_residuals/test_pred_error_integration.png
deleted file mode 100644
Binary files a/tests/baseline_images/test_regressor/test_residuals/test_pred_error_integration.png and /dev/null differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_pred_error_integration_pandas.png b/tests/baseline_images/test_regressor/test_residuals/test_pred_error_integration_pandas.png
deleted file mode 100644
Binary files a/tests/baseline_images/test_regressor/test_residuals/test_pred_error_integration_pandas.png and /dev/null differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_prediction_error.png b/tests/baseline_images/test_regressor/test_residuals/test_prediction_error.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_regressor/test_residuals/test_prediction_error.png differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_prediction_error_pandas.png b/tests/baseline_images/test_regressor/test_residuals/test_prediction_error_pandas.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_regressor/test_residuals/test_prediction_error_pandas.png differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_resid_plots.png b/tests/baseline_images/test_regressor/test_residuals/test_resid_plots.png
deleted file mode 100644
Binary files a/tests/baseline_images/test_regressor/test_residuals/test_resid_plots.png and /dev/null differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot.png b/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot.png differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_integration.png b/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_integration.png
deleted file mode 100644
Binary files a/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_integration.png and /dev/null differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_integration_pandas.png b/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_integration_pandas.png
deleted file mode 100644
Binary files a/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_integration_pandas.png and /dev/null differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_no_histogram.png b/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_no_histogram.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_no_histogram.png differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_pandas.png b/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_pandas.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_regressor/test_residuals/test_residuals_plot_pandas.png differ
diff --git a/tests/baseline_images/test_regressor/test_residuals/test_residuals_quick_method.png b/tests/baseline_images/test_regressor/test_residuals/test_residuals_quick_method.png
new file mode 100644
Binary files /dev/null and b/tests/baseline_images/test_regressor/test_residuals/test_residuals_quick_method.png differ
diff --git a/tests/test_regressor/test_residuals.py b/tests/test_regressor/test_residuals.py
--- a/tests/test_regressor/test_residuals.py
+++ b/tests/test_regressor/test_residuals.py
@@ -2,6 +2,7 @@
 # Ensure that the regressor residuals visualizations work.
 #
 # Author:   Rebecca Bilbro <rbilbro@districtdatalabs.com>
+# Author:   Benjamin Bengfort <bbengfort@districtdatalabs.com>
 # Created:  Sat Oct 8 16:30:39 2016 -0400
 #
 # Copyright (C) 2016 District Data Labs
@@ -17,17 +18,20 @@
 ## Imports
 ##########################################################################
 
+import sys
 import pytest
+import matplotlib as mpl
 import matplotlib.pyplot as plt
 
 from yellowbrick.regressor.residuals import *
+from yellowbrick.exceptions import YellowbrickValueError
 
 from tests.base import VisualTestCase
 from tests.dataset import DatasetMixin, Dataset, Split
 
-from sklearn.svm import SVR
 from sklearn.linear_model import Ridge, Lasso
 from sklearn.linear_model import LinearRegression
+from sklearn.neural_network import MLPRegressor
 
 from sklearn.datasets import make_regression
 from sklearn.model_selection import train_test_split as tts
@@ -39,6 +43,10 @@
     pd = None
 
 
+# Determine version of matplotlib
+MPL_VERS_MAJ = int(mpl.__version__.split(".")[0])
+
+
 ##########################################################################
 ## Data
 ##########################################################################
@@ -50,7 +58,8 @@ def data(request):
     For ease of use returns a Dataset named tuple composed of two Split tuples.
     """
     X, y = make_regression(
-        n_samples=500, n_features=22, n_informative=8, random_state=42
+        n_samples=500, n_features=22, n_informative=8, random_state=42,
+        noise=0.2, bias=0.2,
     )
 
     X_train, X_test, y_train, y_test = tts(
@@ -74,22 +83,25 @@ class TestPredictionError(VisualTestCase, DatasetMixin):
     Test the PredictionError visualizer
     """
 
-    def test_pred_error_integration(self):
+    @pytest.mark.filterwarnings("ignore:Stochastic Optimizer")
+    @pytest.mark.filterwarnings("ignore:internal gelsd driver lwork query error")
+    def test_prediction_error(self):
         """
-        Integration test with image similarity on random data with SVR
+        Test image similarity of prediction error on random data
         """
         _, ax = plt.subplots()
 
-        visualizer = PredictionError(SVR(), ax=ax)
+        model = MLPRegressor(random_state=229)
+        visualizer = PredictionError(model, ax=ax)
 
         visualizer.fit(self.data.X.train, self.data.y.train)
         visualizer.score(self.data.X.test, self.data.y.test)
         visualizer.finalize()
 
-        self.assert_images_similar(visualizer, tol=10)
+        self.assert_images_similar(visualizer, tol=1, remove_legend=True)
 
     @pytest.mark.skipif(pd is None, reason="pandas is required")
-    def test_pred_error_integration_pandas(self):
+    def test_prediction_error_pandas(self):
         """
         Test Pandas real world dataset with image similarity on Ridge
         """
@@ -112,12 +124,12 @@ def test_pred_error_integration_pandas(self):
         splits = tts(X, y, test_size=0.2, random_state=8873)
         X_train, X_test, y_train, y_test = splits
 
-        visualizer = PredictionError(Ridge(), ax=ax)
+        visualizer = PredictionError(Ridge(random_state=22), ax=ax)
         visualizer.fit(X_train, y_train)
         visualizer.score(X_test, y_test)
         visualizer.finalize()
 
-        self.assert_images_similar(visualizer, tol=10)
+        self.assert_images_similar(visualizer, tol=1, remove_legend=True)
 
     def test_score(self):
         """
@@ -128,22 +140,53 @@ def test_score(self):
         visualizer.fit(self.data.X.train, self.data.y.train)
         score = visualizer.score(self.data.X.test, self.data.y.test)
 
-        assert score == pytest.approx(1.0)
+        assert score == pytest.approx(0.9999983124154965)
         assert visualizer.score_ == score
 
-    @pytest.mark.skip(reason="not implemented yet")
     def test_peplot_shared_limits(self):
         """
         Test shared limits on the peplot
         """
-        raise NotImplementedError("not yet implemented")
+        visualizer = PredictionError(LinearRegression(), shared_limits=False)
+
+        visualizer.fit(self.data.X.train, self.data.y.train)
+        visualizer.score(self.data.X.test, self.data.y.test)
+        visualizer.finalize()
+
+        xlim = tuple(map(int, visualizer.ax.get_xlim()))
+        ylim = tuple(map(int, visualizer.ax.get_ylim()))
+        assert xlim == ylim
 
-    @pytest.mark.skip(reason="not implemented yet")
-    def test_peplot_draw_bounds(self):
+    @pytest.mark.filterwarnings("ignore:internal gelsd driver lwork query error")
+    def test_peplot_no_shared_limits(self):
         """
-        Test the peplot +/- one bounding in draw
+        Test image similarity with no shared limits on the peplot
         """
-        raise NotImplementedError("not yet implemented")
+        visualizer = PredictionError(Ridge(random_state=43), shared_limits=False)
+
+        visualizer.fit(self.data.X.train, self.data.y.train)
+        visualizer.score(self.data.X.test, self.data.y.test)
+        visualizer.finalize()
+
+        xlim = tuple(map(int, visualizer.ax.get_xlim()))
+        ylim = tuple(map(int, visualizer.ax.get_ylim()))
+        assert not xlim == ylim
+
+        self.assert_images_similar(visualizer, tol=1.0, remove_legend=True)
+
+    def test_peplot_no_lines(self):
+        """
+        Test image similarity with no lines drawn on the plot
+        """
+        visualizer = PredictionError(
+            Lasso(random_state=23, alpha=10), bestfit=False, identity=False
+        )
+
+        visualizer.fit(self.data.X.train, self.data.y.train)
+        visualizer.score(self.data.X.test, self.data.y.test)
+        visualizer.finalize()
+
+        self.assert_images_similar(visualizer, tol=1.0, remove_legend=True)
 
 
 ##########################################################################
@@ -156,9 +199,12 @@ class TestResidualsPlot(VisualTestCase, DatasetMixin):
     Test ResidualPlot visualizer
     """
 
-    def test_residuals_plot_integration(self):
+    @pytest.mark.xfail(
+        sys.platform == 'win32', reason="images not close on windows (RMSE=32)"
+    )
+    def test_residuals_plot(self):
         """
-        Integration test with image similarity on random data with OLS
+        Image similarity of residuals plot on random data with OLS
         """
         _, ax = plt.subplots()
 
@@ -168,10 +214,74 @@ def test_residuals_plot_integration(self):
         visualizer.score(self.data.X.test, self.data.y.test)
         visualizer.finalize()
 
-        self.assert_images_similar(visualizer, tol=10)
+        self.assert_images_similar(visualizer, tol=1, remove_legend=True)
 
+    @pytest.mark.xfail(
+        sys.platform == 'win32', reason="images not close on windows (RMSE=32)"
+    )
+    @pytest.mark.filterwarnings("ignore:Stochastic Optimizer")
+    def test_residuals_plot_no_histogram(self):
+        """
+        Image similarity test when hist=False
+        """
+        _, ax = plt.subplots()
+
+        model = MLPRegressor(random_state=19)
+        visualizer = ResidualsPlot(model, ax=ax, hist=False)
+
+        visualizer.fit(self.data.X.train, self.data.y.train)
+        visualizer.score(self.data.X.test, self.data.y.test)
+        visualizer.finalize()
+
+        self.assert_images_similar(visualizer, tol=1, remove_legend=True)
+
+    @pytest.mark.skipif(MPL_VERS_MAJ >= 2, reason="test requires mpl earlier than 2.0.2")
+    def test_hist_matplotlib_version(self, mock_toolkit):
+        """
+        ValueError is raised when matplotlib version is incorrect and hist=True
+        """
+        with pytst.raises(ImportError):
+            from mpl_toolkits.axes_grid1 import make_axes_locatable
+            assert not make_axes_locatable
+
+        with pytest.raises(YellowbrickValueError, match="requires matplotlib 2.0.2"):
+            ResidualsPlot(LinearRegression(), hist=True)
+
+    @pytest.mark.skipif(MPL_VERS_MAJ >= 2, reason="test requires mpl earlier than 2.0.2")
+    def test_no_hist_matplotlib_version(self, mock_toolkit):
+        """
+        No error is raised when matplotlib version is incorrect and hist=False
+        """
+        with pytst.raises(ImportError):
+            from mpl_toolkits.axes_grid1 import make_axes_locatable
+            assert not make_axes_locatable
+
+        try:
+            ResidualsPlot(LinearRegression(), hist=False)
+        except YellowbrickValueError as e:
+            self.fail(e)
+
+    @pytest.mark.xfail(
+        sys.platform == 'win32', reason="images not close on windows (RMSE=32)"
+    )
+    def test_residuals_quick_method(self):
+        """
+        Image similarity test using the residuals plot quick method
+        """
+        _, ax = plt.subplots()
+
+        model = Lasso(random_state=19)
+        ax = residuals_plot(
+            model, self.data.X.train, self.data.y.train, ax=ax, random_state=23
+        )
+
+        self.assert_images_similar(ax=ax, tol=1, remove_legend=True)
+
+    @pytest.mark.xfail(
+        sys.platform == 'win32', reason="images not close on windows (RMSE=32)"
+    )
     @pytest.mark.skipif(pd is None, reason="pandas is required")
-    def test_residuals_plot_integration_pandas(self):
+    def test_residuals_plot_pandas(self):
         """
         Test Pandas real world dataset with image similarity on Lasso
         """
@@ -194,22 +304,22 @@ def test_residuals_plot_integration_pandas(self):
         splits = tts(X, y, test_size=0.2, random_state=231)
         X_train, X_test, y_train, y_test = splits
 
-        visualizer = ResidualsPlot(Lasso(), ax=ax)
+        visualizer = ResidualsPlot(Lasso(random_state=44), ax=ax)
         visualizer.fit(X_train, y_train)
         visualizer.score(X_test, y_test)
         visualizer.finalize()
 
-        self.assert_images_similar(visualizer, tol=10)
+        self.assert_images_similar(visualizer, tol=1, remove_legend=True)
 
     def test_score(self):
         """
         Assert returns R2 score
         """
-        visualizer = ResidualsPlot(SVR())
+        visualizer = ResidualsPlot(Ridge(random_state=8893))
 
         visualizer.fit(self.data.X.train, self.data.y.train)
         score = visualizer.score(self.data.X.test, self.data.y.test)
 
-        assert score == pytest.approx(0.03344393985277794)
-        assert visualizer.train_score_ == pytest.approx(0.04743502276335876)
+        assert score == pytest.approx(0.9999888484, rel=1e-4)
+        assert visualizer.train_score_ == pytest.approx(0.9999906, rel=1e-4)
         assert visualizer.test_score_ == score