diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6ba1b78b..7dd0b0f4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ The format is based on `Keep a Changelog ` Unreleased ---------- +- Add a vignette for testing vector data plots. (@nkorinek, #208) - Changed how `assert_lines` checks the limits of lines (@nkorinek, #243) - Changed changelog to an rst file. (@nkorinek, #266) - Add a vignette for testing vector data plots. (@nkorinek, #208) diff --git a/dev-requirements.txt b/dev-requirements.txt index 36d65867..c0ae0afc 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -12,3 +12,4 @@ pip==19.0.3 descartes==1.1.0 seaborn>=0.9.1 pillow==7.1.2 +seaborn>=0.9.1 diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py new file mode 100644 index 00000000..6a8cfc34 --- /dev/null +++ b/examples/plot_line_testing.py @@ -0,0 +1,165 @@ +""" +Test Regression and 1:1 Lines On Plots Using Matplotcheck +=========================================================== + +You can use MatPlotcheck to test lines on plots. In this example you will +learn how to test that a 1:1 line or a regression line is correct as +rendered on a scatter plot. +""" + +################################################################################ +# To begin, import the required Python packages. + +import matplotlib.pyplot as plt +import seaborn as sns +import numpy as np +from scipy import stats +import pandas as pd +import matplotcheck.base as pt + +################################################################################ +# Create Example Data +# ------------------- +# Below you create some data to add to a scatter plot. You will use the +# points to calculate and create a linear regression fit line to your +# plot. You will also add a 1:1 line to your plot. This will allow you to +# compare the slope of the regression output to a standard 1:1 fit. + +# Create Pandas DataFrame containing data points +col1 = np.random.randint(25, size=15) +col2 = np.random.randint(25, size=15) +data = pd.DataFrame({'data1':col1, 'data2':col2}) + +# Plot data points, regression line, and one-to-one (1:1) line for reference +fig, ax = plt.subplots() + +# Use Seaborn to calculate and plot a regression line + associated points +sns.regplot('data1', 'data2', + data=data, + color='purple', + ax=ax, + scatter=True) + +# Add 1:1 line to your plot +ax.plot((0, 1), (0, 1), transform=ax.transAxes, ls='--', c='k') + +ax.set(xlabel='data1', + ylabel='data2', + title='Example Data Regression Plot', + xlim=(0, 25), + ylim=(0, 25)); + +################################################################################ +# Test Line Plots Using a matplotcheck.PlotTester Object +# ------------------------------------------------------------ +# Once you have your plot, you can test the lines on the plot using +# MatPlotCheck. To begin, create a ``matplotcheck.PlotTester`` +# object. MatPlotCheck can test to see if there is a 1:1 and / or a regression +# line on your plot. It will also test that the line has the correct +# y-intercept and slope. + +# Convert matplotlib plot axes object into a matplotcheck PlotTester object +line_plot_tester = pt.PlotTester(ax) + +################################################################################ +# Test For Regression and 1:1 Lines on a Plot +# -------------------------------------------- +# You can use the method ``assert_lines_of_type()`` to test if a 1:1 or +# linear regression line (or both line types) are present in the plot. + +# Check regression line is accurate to the points in the plot +line_plot_tester.assert_lines_of_type(line_types=['linear-regression']) + +# Check onetoone line is present in the data. Set check_coverage to false since +# the onetoone line goes past the extent of the points. +line_plot_tester.assert_lines_of_type( + line_types=['onetoone'], check_coverage=False + ) + +################################################################################ +# Test Slope and Y Intercept +# -------------------------- +# You can also test the slope and y-intercept of a line to ensure +# that the line is correct. If you made +# your line from a list of vertices, you can use the +# ``line_figure_tests.get_slope_yintercept()`` method of the PlotTester object, +# to get the slope and y-intercept. However, if you made your line +# from a regression function, it will take an extra step to get the slope and +# intercept data. In this example, ``stats.linregress`` is used to calculate +# the slope and intercept data. Once you have created that data, you can plug +# it into the ``assert_line()`` function to ensure your line in your plot has +# the correct values. + +# Get slope and y intercept data of regression line for testing +slope_data, intercept_data, _, _, _ = stats.linregress( + data.data1, data.data2 + ) + +# Check that slope and y intercept are correct (expected) values +line_plot_tester.assert_line(slope_exp=slope_data, intercept_exp=intercept_data) + + +################################################################################ +# Test Other Aspects of the Plot +# ------------------------------ +# Plots with linear regression lines and one to one lines generally have other +# important aspects to the plots aside from the lines themselves, such as the +# points the regression is based off of, or the labels of the plot. You can +# test those aspects as well to ensure they are accurate. + +# Checking key words are found in the title +line_plot_tester.assert_title_contains( + strings_expected=[["Example"], ["Regression"], ["Plot"]] + ) + +# Check labels contain key words +line_plot_tester.assert_axis_label_contains( + axis="x", strings_expected=["data1"] + ) +line_plot_tester.assert_axis_label_contains( + axis="y", strings_expected=["data2"] + ) + +# Checking point data matches the expected data +line_plot_tester.assert_xydata( + xy_expected=data, xcol=['data1'], ycol=['data2'], points_only=True + ) + +################################################################################ +# +# .. note:: +# Matplotcheck can be used to test plots in Jupyter Notebooks as well. The +# main difference is how you access the axes objects from the plot that you +# want to test. Below is an example of how you could access the axes of a +# plot you want to test in a Jupyter Notebook. + +# First, import the Notebook module from Matplotcheck +import matplotcheck.notebook as nb + +# Plot the data +fig, ax = plt.subplots() + +# Points and regression line +sns.regplot('data1', 'data2', + data=data, + color='purple', + ax=ax) + +# 1:1 line +ax.plot((0, 1), (0, 1), transform=ax.transAxes, ls='--', c='k') + +ax.set(xlabel='data1', + ylabel='data2', + title='Example Data Regression Plot', + xlim=(0, 25), + ylim=(0, 25)); + +# Here is where you access the axes objects of the plot for testing. +# You can add the code line below to the end of any plot cell to store all axes +# objects created by matplotlib in that cell. +axis_object = nb.convert_axes(plt, which_axes="current") + +# This object can then be turned into a PlotTester object. +line_plot_tester_2 = pt.PlotTester(axis_object) + +# Now you can run the tests as you did earlier! diff --git a/matplotcheck/base.py b/matplotcheck/base.py index 37e91e98..61ae752c 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -930,8 +930,8 @@ def assert_xydata( the assertion being run.""" try: np.testing.assert_array_max_ulp( - xy_data["x"].to_numpy(dtype=np.float64), - xy_expected[xcol].to_numpy(dtype=np.float64), + xy_data["x"].to_numpy(dtype=np.float64).flatten(), + xy_expected[xcol].to_numpy(dtype=np.float64).flatten(), 5, ) except AssertionError: @@ -944,8 +944,8 @@ def assert_xydata( ) try: np.testing.assert_array_max_ulp( - xy_data["y"].to_numpy(dtype=np.float64), - xy_expected[ycol].to_numpy(dtype=np.float64), + xy_data["y"].to_numpy(dtype=np.float64).flatten(), + xy_expected[ycol].to_numpy(dtype=np.float64).flatten(), 5, )