From f967e8a0a87954fc5a1f51ff28e544bdabf85c7f Mon Sep 17 00:00:00 2001 From: nkorinek Date: Mon, 23 Mar 2020 14:37:56 -0600 Subject: [PATCH 01/53] Vignette for Testing Lines --- examples/plot_line_testing.py | 101 ++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 examples/plot_line_testing.py diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py new file mode 100644 index 00000000..dc2be9e8 --- /dev/null +++ b/examples/plot_line_testing.py @@ -0,0 +1,101 @@ +""" +Testing Line Plots +================== + +These are some examples of using the basic functionality of MatPlotCheck +to test line plots in Python. +""" + +################################################################################ +# Setup +# ----- +# You will start by importing the required packages and plotting a creating +# a line plot. + +import numpy as np +from scipy import stats +import seaborn as sns +import pandas as pd +import matplotlib.pyplot as plt +import matplotcheck.base as pt + +# Create the data for the line plot + +col1 = list(np.random.randint(25, size=15)) +col2 = list(np.random.randint(25, size=15)) +data = pd.DataFrame(list(zip(col1, col2)), columns=['Data1', 'Data2']) + +# Plot the points, line of regression, and a one to one line for reference + +fig, ax = plt.subplots() + +# Points and line of regression +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)) + +plt.show() + +# Convert axes object into a PlotTester object + +line_figure_tests = pt.PlotTester(ax) + +############################################################################### +# +# .. note:: +# If you are testing a plot that is created in a Jupyter Notebook - for +# example a student assignment - and you want to get a copy of the student's +# figure created in a cell you can use the following approach: +# ``ax_object = nb.convert_axes(plt, which_axes="current")`` +# then in the cell below where you write your tests, you can create a +# PlotTester object by calling: +# ``PlotTester(ax_object)`` + +################################################################################ +# Testing the Line Plot +# --------------------- +# Now you can make a PlotTester object and test the line plot. You can test +# the line type is a one to one line or a regression line, and you can test +# that the line has the correct y intercept and slope. + +################################################################################ +# Testing the Line Types +# ---------------------- +# As you can see, there are two line types on this plot. A one to one line for +# reference, and a regression line of the points plotted. You can use the +# function ``assert_lines_of_type()`` to test if a one to one or regression line +# (or both types of line) are present in your plot. These are the only types +# of lines you can currently test for with this function. + +# Check line types +line_figure_tests.assert_lines_of_type(line_types=['regression', 'onetoone']) + +################################################################################ +# Testing the Slope and Y Intercept +# --------------------------------- +# Another aspect of a line plot that you can test is the slope and Y intercept, +# which checks to ensure the line has the correct values. If you made your +# line from a list of vertices, you can use the ``get_slope_yintercept()`` +# function in the PlotTester object in order 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. + +# Create the slope and intercept data for the line in the plot to check against +slope_data, intercept_data, _, _, _ = stats.linregress( + data.Data1, data.Data2) + +# Check line is correct +line_figure_tests.assert_line(slope_exp=slope_data, intercept_exp=intercept_data) From 663fedce386ecad634d95758293884546ecc08dd Mon Sep 17 00:00:00 2001 From: nkorinek Date: Mon, 23 Mar 2020 14:43:54 -0600 Subject: [PATCH 02/53] codacy --- examples/plot_line_testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index dc2be9e8..b5328efe 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -91,7 +91,7 @@ # 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. +# function to ensure your line in your plot has the correct values. # Create the slope and intercept data for the line in the plot to check against slope_data, intercept_data, _, _, _ = stats.linregress( From 8025984d2c9098cec518533d1e4c27d5dd6c51e3 Mon Sep 17 00:00:00 2001 From: nkorinek Date: Thu, 2 Apr 2020 14:43:33 -0600 Subject: [PATCH 03/53] Added in seaborn to dev-requirements --- dev-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-requirements.txt b/dev-requirements.txt index ce9e8fad..00544528 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -11,3 +11,4 @@ setuptools==46.1.1 pre-commit==1.20.0 pip==19.0.3 descartes==1.1.0 +seaborn==0.10.0 From 11c57f994358f9413a1bcd4b57d24bea900f5b71 Mon Sep 17 00:00:00 2001 From: nkorinek Date: Thu, 2 Apr 2020 15:21:17 -0600 Subject: [PATCH 04/53] Added in seaborn to dev-requirements right v --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 00544528..76157e24 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -11,4 +11,4 @@ setuptools==46.1.1 pre-commit==1.20.0 pip==19.0.3 descartes==1.1.0 -seaborn==0.10.0 +seaborn==0.9.1 From 7f6d8a77b0fcc6de14629d5f8faaf32bde90058d Mon Sep 17 00:00:00 2001 From: nkorinek Date: Thu, 2 Apr 2020 15:38:31 -0600 Subject: [PATCH 05/53] Triggering rebuild --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af32256c..dda04480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -* Created tests for the vector module (@nkorinek, #209) +* Created tests for the vector module. (@nkorinek, #209) * Created functions to test point geometries in VectorTester (@nkorinek, #176) * made `assert_string_contains()` accept correct strings with spaces in them (@nkorinek, #182) * added contributors file and updated README to remove that information (@nkorinek, #121) From f9b64ddf4c712719905e97b3ca3c0f2477727c2c Mon Sep 17 00:00:00 2001 From: nkorinek Date: Fri, 10 Apr 2020 16:38:20 -0600 Subject: [PATCH 06/53] Adding changes discussed on github --- examples/plot_line_testing.py | 58 ++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index b5328efe..562efbaa 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -19,6 +19,13 @@ import matplotlib.pyplot as plt import matplotcheck.base as pt +################################################################################ +# Create Example Data +# ------------------- +# Before we can grade a plot we have to create example data. This data could be +# maximum lidar height readings in an area over time, or some other data +# in which you are looking for a trend over time. + # Create the data for the line plot col1 = list(np.random.randint(25, size=15)) @@ -50,17 +57,6 @@ line_figure_tests = pt.PlotTester(ax) -############################################################################### -# -# .. note:: -# If you are testing a plot that is created in a Jupyter Notebook - for -# example a student assignment - and you want to get a copy of the student's -# figure created in a cell you can use the following approach: -# ``ax_object = nb.convert_axes(plt, which_axes="current")`` -# then in the cell below where you write your tests, you can create a -# PlotTester object by calling: -# ``PlotTester(ax_object)`` - ################################################################################ # Testing the Line Plot # --------------------- @@ -99,3 +95,43 @@ # Check line is correct line_figure_tests.assert_line(slope_exp=slope_data, intercept_exp=intercept_data) + + +################################################################################ +# Access the Axes object in a Jupyter Notebook +# -------------------------------------------- +# MatPlotCheck can be used to help grade Jupyter Notebooks as well. The main +# difference is in how you would store the Axes from the plot you are grading. +# Below is an example of how you could store the Axes of a plot you are hoping +# to grade in a notebook. + +# First, import the Notebook module from MatPlotCheck +import matplotcheck.notebook as nb + +# Plot the data +fig, ax = plt.subplots() + +# Points and line of regression +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'S WHERE YOU STORE THE PLOT! +# This line at the end of a cell you are expecting a plot in will store any +# matplotlib plot made in that cell so you can test it at a later time. +plot_test_hold = nb.convert_axes(plt, which_axes="current") + +# This can then be turned into a PlotTester object. +line_figure_tests = pt.PlotTester(plot_test_hold) + +# Now you can run the tests as you did earlier! From 2050f4b7ef2023286e6e33c285222e33d5435f0c Mon Sep 17 00:00:00 2001 From: nkorinek Date: Mon, 13 Apr 2020 17:16:16 -0600 Subject: [PATCH 07/53] Fixed bug with how line limits are checke --- CHANGELOG.md | 1 + matplotcheck/base.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02f32683..e5f03dd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* Changed how `assert_lines` checks the limits of lines (@nkorinek, #243) * Made `assert_string_contains()` accept a string instead of a list (@nkorinek, #53) * Added functions to get and assert the midpoint values of bins in a histogram (@nkorinek, #184) * Created tests for the autograde module (@nkorinek, #105) diff --git a/matplotcheck/base.py b/matplotcheck/base.py index bef8cec2..d9b2bded 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -1102,7 +1102,9 @@ def assert_line( ): flag_exist = True line_x_vals = [coord[0] for coord in path_verts] - if min(line_x_vals) <= min_val and max(line_x_vals) >= max_val: + if math.isclose( + min(line_x_vals), min_val, abs_tol=1e-4 + ) and math.isclose(max(line_x_vals), max_val, abs_tol=1e-4): flag_length = True break From 90b81280acbad2cc80ca07ea9f16a21b6dd43897 Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Tue, 11 Feb 2020 04:22:13 +0200 Subject: [PATCH 08/53] Update setuptools from 42.0.2 to 45.2.0 (#190) --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index a4664dc8..43d610df 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -7,6 +7,6 @@ pytest-vcr==1.0.2 pytest-cov==2.7.1 pytest==5.3.5 codecov==2.0.15 -setuptools==42.0.2 +setuptools==45.2.0 pre-commit==1.20.0 pip==19.0.3 From c96321c1c1057868ee089984e88498111004e85f Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Wed, 12 Feb 2020 12:37:22 -0700 Subject: [PATCH 09/53] raster inherits from vector rather than base (#76) --- matplotcheck/raster.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matplotcheck/raster.py b/matplotcheck/raster.py index a8f8e4da..9e8aa5f0 100644 --- a/matplotcheck/raster.py +++ b/matplotcheck/raster.py @@ -1,9 +1,10 @@ import numpy as np from .base import PlotTester +from .vector import VectorTester -class RasterTester(PlotTester): +class RasterTester(VectorTester): """A PlotTester for spatial raster plots. Parameters From 8e26cba1c0de2137cd04626c013f1e3493d07de3 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Thu, 13 Feb 2020 10:35:20 -0700 Subject: [PATCH 10/53] Allow geodataframes in assert_polygons (#188) --- CHANGELOG.md | 1 + dev-requirements.txt | 1 + matplotcheck/tests/conftest.py | 26 +++++++++ matplotcheck/tests/test_vector.py | 87 +++++++++++++++++++++++++++++++ matplotcheck/vector.py | 42 ++++++++++----- 5 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 matplotcheck/tests/test_vector.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 046dccfa..415c27fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] * Created a vignette covering the testing of histograms (@ryla5068, #149) +* Allowed `assert_polygons()` to accept GeoDataFrames and added tests (@nkorinek, #175) ## [0.1.1] * Added test for bin heights of histograms (@ryla5068, #124) diff --git a/dev-requirements.txt b/dev-requirements.txt index 43d610df..635fa63a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -10,3 +10,4 @@ codecov==2.0.15 setuptools==45.2.0 pre-commit==1.20.0 pip==19.0.3 +descartes==1.1.0 diff --git a/matplotcheck/tests/conftest.py b/matplotcheck/tests/conftest.py index 4931fd81..278b3380 100644 --- a/matplotcheck/tests/conftest.py +++ b/matplotcheck/tests/conftest.py @@ -2,6 +2,7 @@ import pytest import pandas as pd import geopandas as gpd +from shapely.geometry import Polygon import numpy as np import matplotlib.pyplot as plt from matplotcheck.base import PlotTester @@ -42,6 +43,31 @@ def pd_gdf(): return gdf +@pytest.fixture +def basic_polygon(): + """ + A square polygon spanning (2, 2) to (4.25, 4.25) in x and y directions. + Borrowed from rasterio/tests/conftest.py. + Returns + ------- + dict: GeoJSON-style geometry object. + Coordinates are in grid coordinates (Affine.identity()). + """ + return Polygon([(2, 2), (2, 4.25), (4.25, 4.25), (4.25, 2), (2, 2)]) + + +@pytest.fixture +def basic_polygon_gdf(basic_polygon): + """ + A GeoDataFrame containing the basic polygon geometry. + Returns + ------- + GeoDataFrame containing the basic_polygon polygon. + """ + gdf = gpd.GeoDataFrame(geometry=[basic_polygon], crs={"init": "epsg:4326"}) + return gdf + + @pytest.fixture def pd_xlabels(): """Create a DataFrame which uses the column labels as x-data.""" diff --git a/matplotcheck/tests/test_vector.py b/matplotcheck/tests/test_vector.py new file mode 100644 index 00000000..e65a0611 --- /dev/null +++ b/matplotcheck/tests/test_vector.py @@ -0,0 +1,87 @@ +"""Tests for the vector module""" +import pytest +import matplotlib.pyplot as plt +import geopandas as gpd +from matplotcheck.vector import VectorTester + + +@pytest.fixture +def poly_geo_plot(basic_polygon_gdf): + """Create a polygon vector tester object.""" + _, ax = plt.subplots() + + basic_polygon_gdf.plot(ax=ax) + ax.set_title("My Plot Title", fontsize=30) + ax.set_xlabel("x label") + ax.set_ylabel("y label") + + axis = plt.gca() + + return VectorTester(axis) + + +def test_list_of_polygons_check(poly_geo_plot, basic_polygon): + """Check that the polygon assert works with a list of polygons.""" + x, y = basic_polygon.exterior.coords.xy + poly_list = [list(zip(x, y))] + poly_geo_plot.assert_polygons(poly_list) + plt.close() + + +def test_polygon_geodataframe_check(poly_geo_plot, basic_polygon_gdf): + """Check that the polygon assert works with a polygon geodataframe""" + poly_geo_plot.assert_polygons(basic_polygon_gdf) + plt.close() + + +def test_empty_list_polygon_check(poly_geo_plot): + """Check that the polygon assert fails an empty list.""" + with pytest.raises(ValueError, match="Empty list or GeoDataFrame "): + poly_geo_plot.assert_polygons([]) + plt.close() + + +def test_empty_list_entry_polygon_check(poly_geo_plot): + """Check that the polygon assert fails a list with an empty entry.""" + with pytest.raises(ValueError, match="Empty list or GeoDataFrame "): + poly_geo_plot.assert_polygons([[]]) + plt.close() + + +def test_empty_gdf_polygon_check(poly_geo_plot): + """Check that the polygon assert fails an empty GeoDataFrame.""" + with pytest.raises(ValueError, match="Empty list or GeoDataFrame "): + poly_geo_plot.assert_polygons(gpd.GeoDataFrame([])) + plt.close() + + +def test_polygon_dec_check(poly_geo_plot, basic_polygon): + """ + Check that the polygon assert passes when the polygon is off by less than + the maximum decimal precision. + """ + x, y = basic_polygon.exterior.coords.xy + poly_list = [[(x[0] + 0.1, x[1]) for x in list(zip(x, y))]] + poly_geo_plot.assert_polygons(poly_list, dec=1) + plt.close() + + +def test_polygon_dec_check_fail(poly_geo_plot, basic_polygon): + """ + Check that the polygon assert fails when the polygon is off by more than + the maximum decimal precision. + """ + with pytest.raises(AssertionError, match="Incorrect Polygon"): + x, y = basic_polygon.exterior.coords.xy + poly_list = [(x[0] + 0.5, x[1]) for x in list(zip(x, y))] + poly_geo_plot.assert_polygons(poly_list, dec=1) + plt.close() + + +def test_polygon_custom_fail_message(poly_geo_plot, basic_polygon): + """Check that the corrct error message is raised when polygons fail""" + with pytest.raises(AssertionError, match="Test Message"): + x, y = basic_polygon.exterior.coords.xy + poly_list = [(x[0] + 0.5, x[1]) for x in list(zip(x, y))] + poly_geo_plot.assert_polygons(poly_list, m="Test Message") + plt.close() diff --git a/matplotcheck/vector.py b/matplotcheck/vector.py index acc44459..1996b4bc 100644 --- a/matplotcheck/vector.py +++ b/matplotcheck/vector.py @@ -80,7 +80,7 @@ def assert_no_legend_overlap(self, m="Legends overlap eachother"): def _convert_length(self, arr, n): """ helper function for 'get_points_by_attributes' and 'get_lines_by_attributes' - takes an array of either legnth 1 or n. + takes an array of either legnth 1 or n. If array is length 1: array of array's only element repeating n times is returned If array is length n: original array is returned Else: function raises value error @@ -106,7 +106,7 @@ def _convert_length(self, arr, n): ) def get_points_by_attributes(self): - """Returns a sorted list of lists where each list contains tuples of xycoords for points of + """Returns a sorted list of lists where each list contains tuples of xycoords for points of the same attributes: color, marker, and markersize Returns @@ -287,11 +287,11 @@ def _convert_linestyle(self, ls): return (ls[0], onoffseq) def get_lines(self): - """Returns a dataframe with all lines on ax + """Returns a dataframe with all lines on ax Returns ------- - output: DataFrame with column 'lines'. Each row represents one line segment. + output: DataFrame with column 'lines'. Each row represents one line segment. Its value in 'lines' is a list of tuples representing the line segement. """ lines = [ @@ -472,15 +472,29 @@ def assert_polygons( self, polygons_expected, dec=None, m="Incorrect Polygon Data" ): """Asserts the polygon data in Axes ax is equal to polygons_expected to decimal place dec with error message m - If polygons_expected is am empty list or None, assertion is passed - - Parameters - ---------- - polygons_expected: list of polygons expected to be founds on Axes ax - dec: int stating the desired decimal precision. If None, polygons must be exact - m: string error message if assertion is not met + If polygons_expected is am empty list or None, assertion is passed. + + Parameters + ---------- + polygons_expected : List or GeoDataFrame + List of polygons expected to be founds on Axes ax or a GeoDataFrame + containing the expected polygons. + dec : int (Optional) + Int stating the desired decimal precision. If None, polygons must + be exact. + m : string (default = "Incorrect Polygon Data") + String error message if assertion is not met. """ - if polygons_expected: + if len(polygons_expected) != 0: + if isinstance(polygons_expected, list): + if len(polygons_expected[0]) == 0: + raise ValueError( + "Empty list or GeoDataFrame passed into assert_polygons." + ) + if isinstance(polygons_expected, gpd.geodataframe.GeoDataFrame): + polygons_expected = self._convert_multipolygons( + polygons_expected["geometry"] + ) polygons = self.get_polygons() if dec: assert len(polygons_expected) == len(polygons), m @@ -494,3 +508,7 @@ def assert_polygons( ) else: np.testing.assert_equal(polygons, sorted(polygons_expected), m) + else: + raise ValueError( + "Empty list or GeoDataFrame passed into assert_polygons." + ) From 49c4c24ee593d6b8679e266ebba9e28f8b694d7d Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 13 Feb 2020 11:26:00 -0700 Subject: [PATCH 11/53] fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek --- CHANGELOG.md | 1 + matplotcheck/raster.py | 32 +++- matplotcheck/tests/test_raster.py | 6 + matplotcheck/vector.py | 255 +++++++++++++++--------------- 4 files changed, 162 insertions(+), 132 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 415c27fc..eca75ef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] * Created a vignette covering the testing of histograms (@ryla5068, #149) +* Created `get_plot_image()` function for the RasterTester object (@nkorinek, #192) * Allowed `assert_polygons()` to accept GeoDataFrames and added tests (@nkorinek, #175) ## [0.1.1] diff --git a/matplotcheck/raster.py b/matplotcheck/raster.py index 9e8aa5f0..4f62a8ce 100644 --- a/matplotcheck/raster.py +++ b/matplotcheck/raster.py @@ -164,6 +164,24 @@ def assert_legend_accuracy_classified_image( ### IMAGE TESTS/HELPER FUNCTIONS ### + def get_plot_image(self): + """Returns images stored on the Axes object as a list of numpy arrays. + + Returns + ------- + im_data: List + Numpy array of images stored on Axes object. + """ + im_data = [] + if self.ax.get_images(): + im_data = self.ax.get_images()[0].get_array() + assert list(im_data), "No Image Displayed" + + # If image array has 3 dims (e.g. rgb image), remove alpha channel + if len(im_data.shape) == 3: + im_data = im_data[:, :, :3] + return im_data + def assert_image( self, im_expected, im_classified=False, m="Incorrect Image Displayed" ): @@ -171,12 +189,14 @@ def assert_image( Parameters ---------- - im_expected: array containing the expected image data - im_classified: boolean, set to True image has been classified. - Since classified images values can be reversed or shifted and still - produce the same image, setting this to True will allow those - changes. - m: string error message if assertion is not met + im_expected: Numpy Array + Array containing the expected image data. + im_classified: boolean + Set to True image has been classified. Since classified images + values can be reversed or shifted and still produce the same image, + setting this to True will allow those changes. + m: string + String error message if assertion is not met. Returns ---------- diff --git a/matplotcheck/tests/test_raster.py b/matplotcheck/tests/test_raster.py index ab59e62e..dd623660 100644 --- a/matplotcheck/tests/test_raster.py +++ b/matplotcheck/tests/test_raster.py @@ -317,3 +317,9 @@ def test_raster_assert_image_fullscreen_blank(raster_plt_blank): with pytest.raises(AssertionError, match="No image found on axes"): raster_plt_blank.assert_image_full_screen() plt.close() + + +def test_get_plot_images(raster_plt_rgb): + """get_plot_image should get correct image from ax object""" + ax_im = raster_plt_rgb.get_plot_image() + raster_plt_rgb.assert_image(ax_im) diff --git a/matplotcheck/vector.py b/matplotcheck/vector.py index 1996b4bc..10610602 100644 --- a/matplotcheck/vector.py +++ b/matplotcheck/vector.py @@ -11,11 +11,11 @@ class VectorTester(PlotTester): """A PlotTester for spatial vector plots. - Parameters - ---------- - ax: ```matplotlib.axes.Axes``` object + Parameters + ---------- + ax: ```matplotlib.axes.Axes``` object - """ + """ def __init__(self, ax): """Initialize the vector tester""" @@ -26,10 +26,11 @@ def assert_legend_no_overlay_content( ): """Asserts that each legend does not overlay plot contents with error message m - Parameters - --------- - m: string error message if assertion is not met - """ + Parameters + --------- + m: string + error message if assertion is not met + """ plot_extent = self.ax.get_window_extent().get_points() legends = self.get_legends() for leg in legends: @@ -41,17 +42,17 @@ def assert_legend_no_overlay_content( def _legends_overlap(self, b1, b2): """Helper function for assert_no_legend_overlap - Boolean value if points of window extents for b1 and b2 overlap + Boolean value if points of window extents for b1 and b2 overlap - parmeters - --------- - b1: bounding box of window extents - b2: bounding box of window extents + Parameters + ---------- + b1: bounding box of window extents + b2: bounding box of window extents - Returns - ------ - boolean value that says if bounding boxes b1 and b2 overlap - """ + Returns + ------ + boolean value that says if bounding boxes b1 and b2 overlap + """ x_overlap = (b1[0][0] <= b2[1][0] and b1[0][0] >= b2[0][0]) or ( b1[1][0] <= b2[1][0] and b1[1][0] >= b2[0][0] ) @@ -63,10 +64,10 @@ def _legends_overlap(self, b1, b2): def assert_no_legend_overlap(self, m="Legends overlap eachother"): """Asserts there are no two legends in Axes ax that overlap each other with error message m - Parameters - ---------- - m: string error message if assertion is not met - """ + Parameters + ---------- + m: string error message if assertion is not met + """ legends = self.get_legends() n = len(legends) for i in range(n - 1): @@ -80,20 +81,22 @@ def assert_no_legend_overlap(self, m="Legends overlap eachother"): def _convert_length(self, arr, n): """ helper function for 'get_points_by_attributes' and 'get_lines_by_attributes' - takes an array of either legnth 1 or n. - If array is length 1: array of array's only element repeating n times is returned - If array is length n: original array is returned - Else: function raises value error - - Parameters - ---------- - arr: array of either length 1 or n - n: length of return array - - Returns - ------- - array of length n - """ + takes an array of either length 1 or n. + If array is length 1: array of array's only element repeating n times is returned + If array is length n: original array is returned + Else: function raises value error + + Parameters + ---------- + arr: array + A numpy array of either length 1 or n + n: int + length of return array + + Returns + ------- + array of length n + """ if len(arr) == 1: return list(arr) * n elif len(arr) == n: @@ -107,13 +110,13 @@ def _convert_length(self, arr, n): def get_points_by_attributes(self): """Returns a sorted list of lists where each list contains tuples of xycoords for points of - the same attributes: color, marker, and markersize + the same attributes: color, marker, and markersize - Returns - ------- - sorted list where each list represents all points with the same color. - each point is represented by a tuple with its coordinates. - """ + Returns + ------- + sorted list where each list represents all points with the same color. + each point is represented by a tuple with its coordinates. + """ points_dataframe = pd.DataFrame( columns=["offset", "color", "msize", "mstyle"] ) @@ -160,16 +163,16 @@ def assert_points_grouped_by_type( self, data_exp, sort_column, m="Point attribtues not accurate by type" ): """Asserts that the points on Axes ax display attributes based on their type with error message m - attributes tested are: color, marker, and markersize + attributes tested are: color, marker, and markersize - Parameters - ---------- - data_exp: Geopandas Dataframe with Point objects in column 'geometry' - an additional column with title sort_column, denotes a category for each point - sort_column: string of column label in dataframe data_exp. - this column contains values expressing which points belong to which group - m: string error message if assertion is not met - """ + Parameters + ---------- + data_exp: Geopandas Dataframe with Point objects in column 'geometry' + an additional column with title sort_column, denotes a category for each point + sort_column: string of column label in dataframe data_exp. + this column contains values expressing which points belong to which group + m: string error message if assertion is not met + """ groups = self.get_points_by_attributes() grouped_exp = [ @@ -183,10 +186,10 @@ def assert_points_grouped_by_type( def sort_collection_by_markersize(self): """ Returns a pandas dataframe of points in collections on Axes ax. - Returns - -------- - pandas dataframe with columns x, y, point_size. Each row reprsents a point on Axes ax with location x,y and markersize pointsize - """ + Returns + -------- + pandas dataframe with columns x, y, point_size. Each row reprsents a point on Axes ax with location x,y and markersize pointsize + """ df = pd.DataFrame(columns=("x", "y", "markersize")) for c in self.ax.collections: offsets, markersizes = c.get_offsets(), c.get_sizes() @@ -211,12 +214,12 @@ def sort_collection_by_markersize(self): def assert_collection_sorted_by_markersize(self, df_expected, sort_column): """Asserts a collection of points vary in size by column expresse din sort_column - Parameters - ---------- - df_expected: geopandas dataframe with geometry column of expected point locations - sort_column: column title from df_expected that points are expected to be sorted by - if None, assertion is passed - """ + Parameters + ---------- + df_expected: geopandas dataframe with geometry column of expected point locations + sort_column: column title from df_expected that points are expected to be sorted by + if None, assertion is passed + """ df = self.sort_collection_by_markersize() df_expected = df_expected.sort_values(by=sort_column).reset_index( drop=True @@ -238,20 +241,20 @@ def assert_collection_sorted_by_markersize(self, df_expected, sort_column): def _convert_multilines(self, df, column_title): """Helper function for get_lines_by_attribute - converts a pandas dataframe containing a column of LineString and MultiLinestring objects - to a pandas dataframe where each row represents a single line. Line segment values are converted - to a list of tuples. - - Parameters - --------- - df: pandas Dataframe containing a column of LineString and MultiLinestring objects - column_title: string of column title which holds LineString and MultLinestring objects - - Returns - ------- - Dataframe where each row repsrents a single line. - Line segments values are converted to a list of tuples in column column_title - """ + converts a pandas dataframe containing a column of LineString and MultiLinestring objects + to a pandas dataframe where each row represents a single line. Line segment values are converted + to a list of tuples. + + Parameters + --------- + df: pandas Dataframe containing a column of LineString and MultiLinestring objects + column_title: string of column title which holds LineString and MultLinestring objects + + Returns + ------- + Dataframe where each row repsrents a single line. + Line segments values are converted to a list of tuples in column column_title + """ dfout = df.copy() for i, row in dfout.iterrows(): seg = row[column_title] @@ -271,16 +274,16 @@ def _convert_multilines(self, df, column_title): def _convert_linestyle(self, ls): """helper function for get_lines_by_attributes. - converts linestyle to a tuple of (offset, onoffseq) to get hashable datatypes + converts linestyle to a tuple of (offset, onoffseq) to get hashable datatypes - Parameters - ---------- - ls: linesytle from a LineCollection retreived by get_linestyle() + Parameters + ---------- + ls: linesytle from a LineCollection retreived by get_linestyle() - Returns - ------- - tuple containing (offset, onoffseq) of linestyle - """ + Returns + ------- + tuple containing (offset, onoffseq) of linestyle + """ onoffseq = ls[1] if onoffseq: onoffseq = tuple(ls[1]) @@ -289,11 +292,11 @@ def _convert_linestyle(self, ls): def get_lines(self): """Returns a dataframe with all lines on ax - Returns - ------- - output: DataFrame with column 'lines'. Each row represents one line segment. - Its value in 'lines' is a list of tuples representing the line segement. - """ + Returns + ------- + output: DataFrame with column 'lines'. Each row represents one line segment. + Its value in 'lines' is a list of tuples representing the line segement. + """ lines = [ [tuple(coords) for coords in seg] for c in self.ax.collections @@ -305,10 +308,10 @@ def get_lines(self): def get_lines_by_collection(self): """Returns a sorted list of list where each list contains line segments from the same collections - Returns - ------- - sorted list where each list represents all lines from the same collection - """ + Returns + ------- + sorted list where each list represents all lines from the same collection + """ lines_grouped = [ [[tuple(coords) for coords in seg] for seg in c.get_segments()] for c in self.ax.collections @@ -318,12 +321,12 @@ def get_lines_by_collection(self): def get_lines_by_attributes(self): """Returns a sorted list of lists where each list contains line segments of the same attributes: - color, linewidth, and linestyle + color, linewidth, and linestyle - Returns - ------ - sorted list where each list represents all lines with the same attributes - """ + Returns + ------ + sorted list where each list represents all lines with the same attributes + """ lines_dataframe = pd.DataFrame( columns=["seg", "color", "lwidth", "lstyle"] ) @@ -366,13 +369,13 @@ def get_lines_by_attributes(self): def assert_lines(self, lines_expected, m="Incorrect Line Data"): """Asserts the line data in Axes ax is equal to lines_expected with error message m. - If line_expected is None or an empty list, assertion is passed + If line_expected is None or an empty list, assertion is passed - Parameters - ---------- - lines_expected: Geopandas Dataframe with a geometry column consisting of MultilineString and LineString objects - m: string error message if assertion is not met - """ + Parameters + ---------- + lines_expected: Geopandas Dataframe with a geometry column consisting of MultilineString and LineString objects + m: string error message if assertion is not met + """ if type(lines_expected) == gpd.geodataframe.GeoDataFrame: lines_expected = lines_expected[ lines_expected["geometry"].is_empty == False @@ -398,14 +401,14 @@ def assert_lines_grouped_by_type( m="Line attributes not accurate by type", ): """Asserts that the lines on Axes ax display like attributes based on their type with error message m - attributes tested are: color, linewidth, linestyle - - Parameters - ---------- - lines_expected: Geopandas Dataframe with geometry column consisting of MultiLineString and LineString objects - sort_column: string of column title in lines_expected that contains types lines are expected to be grouped by - m: string error message if assertion is not met - """ + attributes tested are: color, linewidth, linestyle + + Parameters + ---------- + lines_expected: Geopandas Dataframe with geometry column consisting of MultiLineString and LineString objects + sort_column: string of column title in lines_expected that contains types lines are expected to be grouped by + m: string error message if assertion is not met + """ if type(lines_expected) == gpd.geodataframe.GeoDataFrame: groups = self.get_lines_by_attributes() lines_expected = lines_expected[ @@ -434,10 +437,10 @@ def assert_lines_grouped_by_type( def get_polygons(self): """Returns all polygons on Axes ax as a sorted list of polygons where each polygon is a list of coord tuples - Returns - ------- - output: sorted list of polygons. Each polygon is a list tuples. Ecah tuples is a coordinate. - """ + Returns + ------- + output: sorted list of polygons. Each polygon is a list tuples. Ecah tuples is a coordinate. + """ output = [ [tuple(coords) for coords in path.vertices] for c in self.ax.collections @@ -448,17 +451,17 @@ def get_polygons(self): def _convert_multipolygons(self, series): """Helper function for assert_polygons - converts a pandas series of Polygon and MultiPolygon objects to a list of lines, - where each line is a list of coord tuples for the exterior + converts a pandas series of Polygon and MultiPolygon objects to a list of lines, + where each line is a list of coord tuples for the exterior - Parameters - ---------- - series: series where each entry is a Polygon or MultiPolygon + Parameters + ---------- + series: series where each entry is a Polygon or MultiPolygon - Returns - ------- - list of lines where each line is a list of coord tuples for the exterior polygon - """ + Returns + ------- + list of lines where each line is a list of coord tuples for the exterior polygon + """ output = [] for entry in series: if type(entry) == shapely.geometry.multipolygon.MultiPolygon: @@ -472,19 +475,19 @@ def assert_polygons( self, polygons_expected, dec=None, m="Incorrect Polygon Data" ): """Asserts the polygon data in Axes ax is equal to polygons_expected to decimal place dec with error message m - If polygons_expected is am empty list or None, assertion is passed. + If polygons_expected is am empty list or None, assertion is passed. Parameters ---------- polygons_expected : List or GeoDataFrame List of polygons expected to be founds on Axes ax or a GeoDataFrame containing the expected polygons. - dec : int (Optional) + dec : int (Optional) Int stating the desired decimal precision. If None, polygons must be exact. m : string (default = "Incorrect Polygon Data") String error message if assertion is not met. - """ + """ if len(polygons_expected) != 0: if isinstance(polygons_expected, list): if len(polygons_expected[0]) == 0: From da32ef9c91c298bbe347256bdb86562a28849aaa Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Fri, 14 Feb 2020 08:17:51 -0700 Subject: [PATCH 12/53] update changelog for release --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eca75ef4..de1bc962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - ## [Unreleased] + +## [0.1.2] * Created a vignette covering the testing of histograms (@ryla5068, #149) * Created `get_plot_image()` function for the RasterTester object (@nkorinek, #192) * Allowed `assert_polygons()` to accept GeoDataFrames and added tests (@nkorinek, #175) +* raster test inherits from vector to allow for multi layer plot testing (@lwasser, #75) ## [0.1.1] * Added test for bin heights of histograms (@ryla5068, #124) From f842822887e2d959696fd698f3b00ff6d6cc4eab Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Fri, 14 Feb 2020 08:18:02 -0700 Subject: [PATCH 13/53] =?UTF-8?q?Bump=20version:=200.1.1=20=E2=86=92=200.1?= =?UTF-8?q?.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/conf.py | 4 ++-- setup.cfg | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 12688690..8b9fd2f9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,9 +25,9 @@ author = "Leah Wasser, Kristin Curry" # The short X.Y version -version = "0.1.1" +version = "0.1.2" # The full version, including alpha/beta/rc tags -release = "0.1.1" +release = "0.1.2" # -- General configuration --------------------------------------------------- diff --git a/setup.cfg b/setup.cfg index 398d3476..97579d2f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.1 +current_version = 0.1.2 commit = True tag = True diff --git a/setup.py b/setup.py index 4497e4bc..f7eee81b 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type="text/markdown", - version="0.1.1", + version="0.1.2", packages=["matplotcheck"], install_requires=[ "numpy>=1.14.0", From eee471f426e1f1444f88f63b33d9ffe77514d216 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Mon, 2 Mar 2020 10:15:59 -0700 Subject: [PATCH 14/53] Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser --- .pre-commit-config.yaml | 5 + CHANGELOG.md | 3 +- matplotcheck/autograde.py | 7 +- matplotcheck/base.py | 119 ++++---- matplotcheck/cases.py | 279 ++++++++++++------ matplotcheck/folium.py | 18 +- matplotcheck/notebook.py | 36 ++- matplotcheck/raster.py | 24 +- matplotcheck/tests/conftest.py | 5 +- matplotcheck/tests/test_base.py | 18 +- matplotcheck/tests/test_base_axis.py | 7 +- matplotcheck/tests/test_base_data.py | 3 +- matplotcheck/tests/test_base_legends.py | 14 +- .../tests/test_base_titles_captions.py | 9 +- matplotcheck/tests/test_raster.py | 20 +- matplotcheck/tests/test_timeseries_module.py | 6 +- matplotcheck/timeseries.py | 38 +-- matplotcheck/vector.py | 138 +++++---- tox.ini | 2 + 19 files changed, 462 insertions(+), 289 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 21fb8d75..7c98033a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,3 +4,8 @@ repos: hooks: - id: black language_version: python3.7 +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.7.7 + hooks: + - id: flake8 + language: python_venv diff --git a/CHANGELOG.md b/CHANGELOG.md index de1bc962..bbb28c03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - -## [0.1.2] +* Adding flake8 for format and other syntax issues! yay! (@lwasser, #195) * Created a vignette covering the testing of histograms (@ryla5068, #149) * Created `get_plot_image()` function for the RasterTester object (@nkorinek, #192) * Allowed `assert_polygons()` to accept GeoDataFrames and added tests (@nkorinek, #175) diff --git a/matplotcheck/autograde.py b/matplotcheck/autograde.py index fe565e59..f2a5a3f5 100644 --- a/matplotcheck/autograde.py +++ b/matplotcheck/autograde.py @@ -41,10 +41,10 @@ def run_test( pass : bool : passing status of test function description : str : test function name that was run message : str : custom message returned based on passing status - traceback : AssertionError : returned from test function when pass is False + traceback : AssertionError : returned from test function when pass is + False """ results = {"points": 0, "pass": False} - score = 0 try: fname = func.__name__ results["description"] = fname @@ -72,7 +72,8 @@ def output_results(results): pass : bool : passing status of test function description : str : test function name that was run message : str : custom message returned based on passing status - traceback : AssertionError : returned from test function when pass is False + traceback : AssertionError : returned from test function when pass is + False Returns ------- diff --git a/matplotcheck/base.py b/matplotcheck/base.py index fe828643..46cc940b 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -109,15 +109,16 @@ def assert_string_contains( Similar to `message_default`, `message_or` is the error message to be displated if `string` does not contain at least one of the strings in an inner list in `strings_expected`. If `message` - contains ``'{0}'``, it will be replaced with the first failing inner - list in `strings_expected`. + contains ``'{0}'``, it will be replaced with the first failing + inner list in `strings_expected`. Raises ------- AssertionError if `string` does not contain expected strings """ - # Assertion passes if strings_expected == [] or strings_expected == None + # Assertion passes if strings_expected == [] or + # strings_expected == None if not strings_expected: return @@ -149,8 +150,8 @@ def assert_plot_type( String specifying the expected plot type. Options: `scatter`, `bar`, `line` message : string - The error message to be displayed if Plot does not match `plot_type`. If - `message` contains ``'{0}'``, it will be replaced + The error message to be displayed if Plot does not match + `plot_type`. If `message` contains ``'{0}'``, it will be replaced with the epected plot type. Raises @@ -219,18 +220,19 @@ def assert_title_contains( assertion. The combined title will be tested. message_default : string - The error message to be displayed if the axis label does not contain - a string in strings_expected. If `message` contains ``'{0}'``, it - will be replaced with the first expected string not found in the - label. + The error message to be displayed if the axis label does not + contain a string in strings_expected. If `message` contains + ``'{0}'``, it will be replaced with the first expected string not + found in the label. message_or : string Similar to `message_default`, `message_or` is the error message to be displated if the axis label does not contain at least one of the strings in an inner list in `strings_expected`. If `message` - contains ``'{0}'``, it will be replaced with the first failing inner - list in `strings_expected`. + contains ``'{0}'``, it will be replaced with the first failing + inner list in `strings_expected`. message_no_title : string - The error message to be displayed if the expected title is not displayed. + The error message to be displayed if the expected title is not + displayed. Raises ------- @@ -246,7 +248,8 @@ def assert_title_contains( title = axtitle else: raise ValueError( - 'title_type must be one of the following ["figure", "axes", "either"]' + "title_type must be one of the following " + + '["figure", "axes", "either"]' ) assert title, message_no_title @@ -268,7 +271,8 @@ def get_caption(self): Returns ------- caption : string - the text that is found in bottom right, ``None`` if no text is found + the text that is found in bottom right, ``None`` if no text is + found """ caption = None ax_position = self.ax.get_position() @@ -308,16 +312,16 @@ def assert_caption_contains( ``'a'`` AND ``'b'`` AND (at least one of: ``'c'``, ``'d'``) must be in the title for the assertion to pass. Case insensitive. message_default : string - The error message to be displayed if the axis label does not contain - a string in strings_expected. If `message` contains ``'{0}'``, it - will be replaced with the first expected string not found in the - label. + The error message to be displayed if the axis label does not + contain a string in strings_expected. If `message` contains + ``'{0}'``, it will be replaced with the first expected string + not found in the label. message_or : string Similar to `message_default`, `message_or` is the error message to be displated if the axis label does not contain at least one of the strings in an inner list in `strings_expected`. If `message` - contains ``'{0}'``, it will be replaced with the first failing inner - list in `strings_expected`. + contains ``'{0}'``, it will be replaced with the first failing + inner list in `strings_expected`. message_no_caption : string The error message to be displayed if no caption exists in the appropriate location. @@ -360,12 +364,10 @@ def assert_axis_off(self, message="Axis lines are displayed on plot"): """ flag = False # Case 1: check if axis have been turned off - if self.ax.axison == False: + if not self.ax.axison: flag = True # Case 2: Check if both axis visibilities set to false - elif ( - self.ax.xaxis._visible == False and self.ax.yaxis._visible == False - ): + elif not self.ax.xaxis._visible and not self.ax.yaxis._visible: flag = True # Case 3: Check if both axis ticks are set to empty lists elif ( @@ -403,18 +405,18 @@ def assert_axis_label_contains( ``'a'`` AND ``'b'`` AND (at least one of: ``'c'``, ``'d'``) must be in the title for the assertion to pass. Case insensitive. message_default : string - The error message to be displayed if the axis label does not contain - a string in strings_expected. If `message` contains ``'{1}'``, it - will be replaced with `axis`. If `message` contains ``'{0}'``, it - will be replaced with the first expected string not found in the - label. + The error message to be displayed if the axis label does not + contain a string in strings_expected. If `message` contains + ``'{1}'``, it will be replaced with `axis`. If `message` contains + ``'{0}'``, it will be replaced with the first expected string not + found in the label. message_or : string Similar to `message_default`, `message_or` is the error message to be displated if the axis label does not contain at least one of the strings in an inner list in `strings_expected`. If `message` contains ``'{1}'``, it will be replaced with `axis`. If `message` - contains ``'{0}'``, it will be replaced with the first failing inner - list in `strings_expected`. + contains ``'{0}'``, it will be replaced with the first failing + inner list in `strings_expected`. message_not_displayed : string The error message to be displayed if the expected axis label is not displayed. If `message_not_displayed` contains ``'{0}'``, it will @@ -574,7 +576,8 @@ def assert_legend_titles( self, titles_exp, message="Legend title does not contain expected string: {0}", - message_num_titles="I was expecting {0} legend titles but instead found {1}", + message_num_titles="I was expecting {0} legend titles but instead " + + "found {1}", ): """Asserts legend titles contain expected text in titles_exp list. @@ -592,8 +595,8 @@ def assert_legend_titles( The error message to be displayed if there exist a different number of legend titles than expected. If `message_num_titles` contains ``'{0}'`` it will be replaced with the number of titles found. If - `message_num_titles` contains ``'{1}'`` it will be replaced with the - expected number of titles. + `message_num_titles` contains ``'{1}'`` it will be replaced with + the expected number of titles. Raises ------- @@ -625,7 +628,8 @@ def assert_legend_labels( labels_exp, message="Legend does not have expected labels", message_no_legend="Legend does not exist", - message_num_labels="I was expecting {0} legend entries, but found {1}. Are there extra labels in your legend?", + message_num_labels="I was expecting {0} legend entries, but found " + + "{1}. Are there extra labels in your legend?", ): """Asserts legends on ax have the correct entry labels @@ -643,8 +647,8 @@ def assert_legend_labels( The error message to be displayed if there exist a different number of legend labels than expected. If `message_num_labels` contains ``'{0}'`` it will be replaced with the number of labels found. If - `message_num_labels` contains ``'{1}'`` it will be replaced with the - expected number of labels. + `message_num_labels` contains ``'{1}'`` it will be replaced with + the expected number of labels. Raises @@ -750,8 +754,8 @@ def assert_no_legend_overlap(self, message="Legends overlap eachother"): leg_extent2 = ( legends[j].get_window_extent(RendererBase()).get_points() ) - assert ( - self.legends_overlap(leg_extent1, leg_extent2) == False + assert not self.legends_overlap( + leg_extent1, leg_extent2 ), message """ BASIC PLOT DATA FUNCTIONS """ @@ -765,9 +769,11 @@ def get_xy(self, points_only=False, xtime=False): ax : matplotlib.axes.Axes Matplotlib Axes object to be tested points_only : boolean - Set ``True`` to check only points, set ``False`` to check all data on plot. + Set ``True`` to check only points, set ``False`` to check all data + on plot. xtime : boolean - Set equal to True if the x axis of the plot contains datetime values + Set equal to True if the x axis of the plot contains datetime + values Returns ------- @@ -872,12 +878,13 @@ def assert_xydata( return elif not isinstance(xy_expected, pd.DataFrame): raise ValueError( - "xy_expected must be of type: pandas dataframe or Geopandas Dataframe" + "xy_expected must be of type: pandas dataframe or Geopandas " + + "Dataframe" ) # If xy_expected is a GeoDataFrame, then we make is a normal DataFrame - # with the coordinates of the geometry in that GeoDataFrame as the x and - # y data + # with the coordinates of the geometry in that GeoDataFrame as the x + # and y data if isinstance(xy_expected, gpd.geodataframe.GeoDataFrame) and not xcol: xy_expected = pd.DataFrame( data={ @@ -1018,7 +1025,7 @@ def assert_xlabel_ydata( except AssertionError: raise AssertionError(message) - ### LINE TESTS/HELPER FUNCTIONS ### + # LINE TESTS/HELPER FUNCTIONS def get_slope_yintercept(self, path_verts): """Returns the y-intercept of line based on the average slope of the @@ -1125,13 +1132,14 @@ def assert_lines_of_type(self, line_types): slope_exp, intercept_exp = 1, 0 else: raise ValueError( - 'each string in line_types must be from the following ["regression","onetoone"]' + "each string in line_types must be from the following " + + '["regression","onetoone"]' ) self.assert_line( slope_exp, intercept_exp, - message_no_line="{0} line is not displayed properly".format( + message_no_line="{0} line not displayed properly".format( line_type ), message_data="{0} line does not cover dataset".format( @@ -1139,7 +1147,7 @@ def assert_lines_of_type(self, line_types): ), ) - ## HISTOGRAM FUNCTIONS ## + # HISTOGRAM FUNCTIONS def get_num_bins(self): """Gets the number of bins in histogram with a unique x-position. @@ -1149,8 +1157,9 @@ def get_num_bins(self): Int : Returns the number of bins with a unique x-position. For a normal histogram, this is just the number of bins. If there are two - overlapping or stacked histograms in the same `matplotlib.axis.Axis` - object, then this returns the number of bins with unique edges. """ + overlapping or stacked histograms in the same + `matplotlib.axis.Axis` object, then this returns the number of bins + with unique edges. """ x_data = self.get_xy(xtime=False)["x"] unique_x_data = list(set(x_data)) num_bins = len(unique_x_data) @@ -1170,9 +1179,9 @@ def assert_num_bins( Number of bins expected. message : string The error message to be displayed if plot does not contain - `num_bins`. If `message` contains ``'{0}'`` it will be replaced with - expected number of bins. If `message` contains ``'{1}'``, it will - be replaced with the number of bins found. + `num_bins`. If `message` contains ``'{0}'`` it will be replaced + with expected number of bins. If `message` contains ``'{1}'``, it + will be replaced with the number of bins found. Raises ------- @@ -1210,8 +1219,8 @@ def assert_bin_values( Parameters ---------- bin_values : list - A list of numbers representing the expected values of each consecutive - bin (i.e. the heights of the bars in the histogram). + A list of numbers representing the expected values of each + consecutive bin (i.e. the heights of the bars in the histogram). tolerence : float Measure of relative error allowed. For example: Given a tolerance ``tolerence=0.1``, an expected value diff --git a/matplotcheck/cases.py b/matplotcheck/cases.py index 58b78f19..c2370987 100644 --- a/matplotcheck/cases.py +++ b/matplotcheck/cases.py @@ -1,5 +1,3 @@ -## Adding levels of abstraction.. -# import all plottester and folium tester import unittest from .base import PlotTester from .timeseries import TimeSeriesTester @@ -28,29 +26,40 @@ class PlotBasicSuite(object): xcol: string column title in data_exp that contains xaxis data ycol: string column title in data_exp that contains yaxis data plot_type: string from list ["scatter","bar"] of expected plot type - line_types: list of strings. Acceptable strings in line_types are as follows ["regression", "onetoone"]. + line_types: list of strings. Acceptable strings in line_types are as + follows + ["regression", "onetoone"]. if list is empty, assert is passed xlabels: boolean if using x axis labels rather than x data lims_equal: boolean expressing if x and y limits are expected to be equal - title_contains: list of lower case strings where each string is expected to be in title, barring capitalization. - If value is an empty list: test is just to see that title exists and is not an empty string + title_contains: list of lower case strings where each string is expected to + be in title, barring capitalization. + If value is an empty list: test is just to see that title exists and is + not an empty string If value is None: no tests are run title_type: one of the following strings ["figure", "axes", "either"] "figure": only the figure title (suptitle) will be tested "axes": only the axes title (suptitle) will be tested - "either": either the figure title or axes title will pass this assertion. + "either": either the figure title or axes title will pass this + assertion. The combined title will be tested. - xlabel_contains: list of lower case strings where each string is expected to be in x-axis label, barring capitalization. - If value is an empty list: test is just to see that x-axis label exists and is not an empty string + xlabel_contains: list of lower case strings where each string is expected + to be in x-axis label, barring capitalization. + If value is an empty list: test is just to see that x-axis label exists + and is not an empty string If value is None: no tests are run - ylabel_contains: list of lower case strings where each string is expected to be in y-axis label, barring capitalization. - If value is an empty list: test is just to see that y-axis label exists and is not an empty string + ylabel_contains: list of lower case strings where each string is expected + to be in y-axis label, barring capitalization. + If value is an empty list: test is just to see that y-axis label exists + and is not an empty string If value is None: no tests are run - caption_string: list of lists. Each internal list is a list of lower case strings where at least one string must be + caption_string: list of lists. Each internal list is a list of lower case + strings where at least one string must be found in the caption, barring capitalization if empty list: asserts caption exists and not an empty string if None: no tests are run - legend_labels: list of lower case stings. Each string is an expected entry label in the legend, barring capitalization. + legend_labels: list of lower case stings. Each string is an expected entry + label in the legend, barring capitalization. """ def __init__( @@ -75,9 +84,12 @@ def __init__( ): class PlotLabelsTest(unittest.TestCase): """A unittest.TestCase containing 3 tests: - 1. title_exist: ax has a title that contains each string in list of strings title_contains, barring capitalization - 2. xlab_exist: ax has a x label that contains each string in list of strings xlabel_contains, barring capitalization - 3. ylab_exist: ax has a y label that that contains each string in list of strings ylabel_contains, barring capitalization + 1. title_exist: ax has a title that contains each string in list of + strings title_contains, barring capitalization + 2. xlab_exist: ax has a x label that contains each string in list + of strings xlabel_contains, barring capitalization + 3. ylab_exist: ax has a y label that that contains each string in + list of strings ylabel_contains, barring capitalization """ def setUp(self): @@ -106,8 +118,8 @@ def tearDown(self): class LegendTest(unittest.TestCase): """A unittest.TestCase containing 2 tests checking the legend(s): - 1. legend_labels: Asserts the legend has labels specified in labels_ - exp (barring capitalization), and only those labels + 1. legend_labels: Asserts the legend has labels specified in + labels_ exp (barring capitalization), and only those labels 2. legend_location: Asserts legend does not cover data and no legends overlap each other """ @@ -149,8 +161,10 @@ class PlotBasic(unittest.TestCase): """A unittest.TestCase containing 4 tests on Matplotlib Axes ax 1. data: asserts that the x and y data of ax matches data_exp 2. lines: asserts each of lines in line_types is displayed on ax - 3. plot_type: asserts plot is of expected type expressed by plot_type - 4. lims: asserts the x and y limits are equal if boolean lims_equal expresses it should be + 3. plot_type: asserts plot is of expected type expressed by + plot_type + 4. lims: asserts the x and y limits are equal if boolean + lims_equal expresses it should be """ def setUp(self): @@ -196,12 +210,16 @@ def tearDown(self): @property def cases(self): - """Returns a list of TestCases to be run in a TestLoader for basic 2d plots (scatter, bar, line, etc.). + """Returns a list of TestCases to be run in a TestLoader for basic 2d + plots (scatter, bar, line, etc.). Testcases are as follows: - 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are as expected - 2. BasicCase: Asserts data matches data_exp, and other assertions based on params listed below - For more on tests, see init method above. For more on assertions, see the autograde package. + 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are + as expected + 2. BasicCase: Asserts data matches data_exp, and other assertions + based on params listed below + For more on tests, see init method above. For more on assertions, + see the autograde package. """ return [self.LabelsCase, self.BasicCase] @@ -237,8 +255,8 @@ class PlotHistogramSuite(PlotBasicSuite): If value is an empty list: test is just to see that title exists and is not an empty string If value is None: asserts no title - xlabel_contains: list of lower case strings where each string is expected to - be in x-axis label, barring capitalization. + xlabel_contains: list of lower case strings where each string is expected + to be in x-axis label, barring capitalization. If value is an empty list: test is just to see that x-axis label exists and is not an empty string If value is None: asserts no label is expressed @@ -269,11 +287,16 @@ def __init__( class PlotHistogram(unittest.TestCase): """A unittest.TestCase containing 4 tests for a histogram: - Test 1 - num_neg_bins: number of bins centered at a negative value is greater than n_bins[0] - Test 2- num_pos_bins: number of bins centered at a positive value is greater than n_bins[1] - Test 3 - x-lims: x-axis left limits are within the bounds [xlims[0][0], xlims[0][1]] and - x-axis right limits are within the bounds [xlims[1][0], xlims[1][1]] - Test 4 - y_lims: y-axis bottom limits are within the bounds [ylims[0][0], ylims[0][1]] and + Test 1 - num_neg_bins: number of bins centered at a negative + value is greater than n_bins[0] + Test 2- num_pos_bins: number of bins centered at a positive value + is greater than n_bins[1] + Test 3 - x-lims: x-axis left limits are within the bounds [xlims[ + 0][0], xlims[0][1]] and + x-axis right limits are within the bounds [xlims[1][0], + xlims[1][1]] + Test 4 - y_lims: y-axis bottom limits are within the bounds [ + ylims[0][0], ylims[0][1]] and y-axis top limits are within the bounds [ylims[1][0], ylims[1][1]] """ @@ -308,15 +331,18 @@ def tearDown(self): @property def cases(self): """Returns list of cases for a histogram. Testcases are as follows: - 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are as expected - 2. HistogramCase: number of negative and positive bins as declares in n_bins. x axis limits and y axis limits + 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are + as expected + 2. HistogramCase: number of negative and positive bins as declares in + n_bins. x axis limits and y axis limits are in range declared by xlims and y lims. - For more on tests, see init method above. For more on assertions, see the autograde package. + For more on tests, see init method above. For more on assertions, + see the autograde package. """ return [self.LabelsCase, self.HistogramCase] -### TIME SERIES PLOT ### +""" TIME SERIES PLOTS """ class PlotTimeSeriesSuite(PlotBasicSuite): @@ -329,21 +355,29 @@ class PlotTimeSeriesSuite(PlotBasicSuite): x_col: string column title in data_exp that contains xaxis data y_col: string column title in data_exp that contains yaxis data no_data_val: float representing no data, as stated by the input data - major_locator_exp: one of the following ['decade', 'year', 'month', 'week', 'day', None] + major_locator_exp: one of the following ['decade', 'year', 'month', + 'week', 'day', None] decade: if tick should be shown every ten years year: if tick should be shown every new year month: if tick should be shown every new month week: if tick should be shown every new week day: if tick should be shown every new day - minor_locator_exp: one of the following ['decade', 'year', 'month', 'week', 'day', None], as expressed above - title_contains: list of lower case strings where each string is expected to be in title, barring capitalization. - If value is an empty list: test is just to see that title exists and is not an empty string + minor_locator_exp: one of the following ['decade', 'year', 'month', + 'week', 'day', None], as expressed above + title_contains: list of lower case strings where each string is expected + to be in title, barring capitalization. + If value is an empty list: test is just to see that title exists and + is not an empty string If value is None: asserts no title - xlabel_contains: list of lower case strings where each string is expected to be in x-axis label, barring capitalization. - If value is an empty list: test is just to see that x-axis label exists and is not an empty string + xlabel_contains: list of lower case strings where each string is expected + to be in x-axis label, barring capitalization. + If value is an empty list: test is just to see that x-axis label + exists and is not an empty string If value is None: asserts no label is expressed - ylabel_contains: list of lower case strings where each string is expected to be in y-axis label, barring capitalization. - If value is an empty list: test is just to see that y-axis label exists and is not an empty string + ylabel_contains: list of lower case strings where each string is expected + to be in y-axis label, barring capitalization. + If value is an empty list: test is just to see that y-axis label + exists and is not an empty string If value is None: asserts no label is expressed """ @@ -372,10 +406,14 @@ def __init__( ) class PlotTicksReformat(unittest.TestCase): - """A unittest.TestCase containing 3 tests checking the xaxis ticks and labels: - 1. x_major_formatter: large ticks on x axis have been reformatted as expressed in major_locator_exp - 2. x_major_locs: large ticks on x axis are located as expressed in major_locator_exp - 3. x_minor_locs: small ticks on x axis are located as expressed in minor_locator_exp + """A unittest.TestCase containing 3 tests checking the xaxis + ticks and labels: + 1. x_major_formatter: large ticks on x axis have been reformatted + as expressed in major_locator_exp + 2. x_major_locs: large ticks on x axis are located as expressed + in major_locator_exp + 3. x_minor_locs: small ticks on x axis are located as expressed + in minor_locator_exp """ def setUp(self): @@ -443,47 +481,65 @@ def tearDown(self): def cases(self): """ Returns a list of TestCases for time series plots. Testcase are as follows: - 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are as expected - 2. TickReformatCase: Asserts x-axis ticks have large ticks as express in major_locator_exp and small + 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are + as expected + 2. TickReformatCase: Asserts x-axis ticks have large ticks as express + in major_locator_exp and small ticks as express in minor_locator_exp - 3. TimeSeriesCase: Asserts data matches data_exp and is converted to time objects - For more on tests, see init method above. For more on assertions, see the autograde package. + 3. TimeSeriesCase: Asserts data matches data_exp and is converted to + time objects + For more on tests, see init method above. For more on assertions, + see the autograde package. """ return [self.LabelsCase, self.TickReformatCase, self.TimeSeriesCase] -### VECTOR PLOT ### +""" VECTOR PLOTS """ class PlotVectorSuite(PlotBasicSuite): - """A PlotBasicSuite object to test a Matplotlib plot with spatial vector data. + """A PlotBasicSuite object to test a Matplotlib plot with spatial vector + data. Parameters --------- ax: Matplotlib Axes to be tested - caption_strings: list of lists. Each internal list is a list of lower case strings where at least one string must be + caption_strings: list of lists. Each internal list is a list of lower + case strings where at least one string must be found in the caption, barring capitalization if None: assert caption does not exist if empty list: asserts caption exists and not an empty string - legend_labels: list of lower case stings. Each string is an expected entry label in the legend. + legend_labels: list of lower case stings. Each string is an expected + entry label in the legend. title_type: one of the following strings ["figure", "axes", "either"] "figure": only the figure title (suptitle) will be tested "axes": only the axes title (suptitle) will be tested - "either": either the figure title or axes title will pass this assertion. + "either": either the figure title or axes title will pass this + assertion. The combined title will be tested. - title_contains: list of lower case strings where each string is expected to be in title, barring capitalization. - If value is an empty list: test is just to see that title exists and is not an empty string + title_contains: list of lower case strings where each string is expected + to be in title, barring capitalization. + If value is an empty list: test is just to see that title exists and + is not an empty string If value is None: asserts no title - markers: Geopandas dataframe with geometry column containing expected Point objects - lines: Geopandas dataframe with geometry column containing expected LineString and MultiLineString objects - polygons: list of lines where each line is a list of coord tuples for the exterior polygon - markers_groupby: column title from markers_exp that points are expected to be grouped by/contain - like attributes. Attributes tested are: marker type, markersize, and color + markers: Geopandas dataframe with geometry column containing expected + Point objects + lines: Geopandas dataframe with geometry column containing expected + LineString and MultiLineString objects + polygons: list of lines where each line is a list of coord tuples for the + exterior polygon + markers_groupby: column title from markers_exp that points are expected + to be grouped by/contain + like attributes. Attributes tested are: marker type, markersize, + and color if None, assertion is passed - lines_groupby: column title from line_exp that lines are expected to be grouped by/contain - like attributes. Attributes tested are: line style, line width, and color + lines_groupby: column title from line_exp that lines are expected to be + grouped by/contain + like attributes. Attributes tested are: line style, line width, + and color if None, assertion is passed - markers_by_size: column title from markers_exp that points are expected to be sorted by + markers_by_size: column title from markers_exp that points are expected + to be sorted by if None, assertion is passed """ @@ -515,10 +571,13 @@ def __init__( class PlotVector(unittest.TestCase): """A unittest.TestCase containing tests for a spatial vector plot. 1. marker_location: points on ax match markers - 2. markers_by_size: asserts points on ax vary in size by column expressed in markers_by_size - 3. markers_grouped: asserts markers of the same group contain like attributes + 2. markers_by_size: asserts points on ax vary in size by column + expressed in markers_by_size + 3. markers_grouped: asserts markers of the same group contain + like attributes 4. lines_location: lines on ax match lines - 5. lines_grouped: asserts lines of the same group contain like attributes + 5. lines_grouped: asserts lines of the same group contain like + attributes 6. polygons_location: polygons on ax match polygons """ @@ -576,11 +635,16 @@ def tearDown(self): def cases(self): """ Returns a list of TestCases for spatial vector plots. Testcase are as follows: - 1. CaptionCase: assert caption is in appropriate location with strings expressed in caption_contains - 2. LabelsCase: asserts the title contains strings in title_contains, and x and y labels are empty - 3. LegendCase: assert legend(s) is/are in appropriate location with legend_labels - 4. VectorCase: assert vector data is as expected in markers, lines, and polygons - For more on tests, see init method above. For more on assertions, see the autograde package. + 1. CaptionCase: assert caption is in appropriate location with + strings expressed in caption_contains + 2. LabelsCase: asserts the title contains strings in title_contains, + and x and y labels are empty + 3. LegendCase: assert legend(s) is/are in appropriate location with + legend_labels + 4. VectorCase: assert vector data is as expected in markers, lines, + and polygons + For more on tests, see init method above. For more on assertions, + see the autograde package. """ return [ self.CaptionCase, @@ -590,7 +654,7 @@ def cases(self): ] -### RASTER PLOT ### +""" RASTER PLOT """ class PlotRasterSuite(PlotVectorSuite): @@ -600,27 +664,39 @@ class PlotRasterSuite(PlotVectorSuite): --------- ax: Matplotlib Axes to be tested im_expected: array containing values of an expected image - caption_strings: list of lists. Each internal list is a list of strings where at least one string must be + caption_strings: list of lists. Each internal list is a list of strings + where at least one string must be found in the caption, barring capitalization if empty list: asserts caption exists and not an empty string if None: assertion is passed im_classified: boolean if image on ax is classfied - legend_labels: list of lists. Each internal list represents a classification category. - Said list is a list of strings where at least one string is expected to be in the legend label for this category. + legend_labels: list of lists. Each internal list represents a + classification category. + Said list is a list of strings where at least one string is expected + to be in the legend label for this category. Internal lists must be in the same order as bins in im_expected. - title_type: one of the following strings ["figure", "axes", "either"], stating which title to test + title_type: one of the following strings ["figure", "axes", "either"], + stating which title to test title_contains: list of strings expected to be in title - markers: Geopandas dataframe with geometry column containing expected Point objects - markers_by_size: column title from markers_exp that points are expected to be sorted by + markers: Geopandas dataframe with geometry column containing expected + Point objects + markers_by_size: column title from markers_exp that points are expected + to be sorted by if None, assertion is passed - markers_groupby: column title from markers_exp that points are expected to be grouped by/contain - like attributes. Attributes tested are: marker type, markersize, and color + markers_groupby: column title from markers_exp that points are expected + to be grouped by/contain + like attributes. Attributes tested are: marker type, markersize, + and color if None, assertion is passed - lines: Geopandas dataframe with geometry column containing expected LineString and MultiLineString objects - lines_groupby: column title from line_exp that lines are expected to be grouped by/contain - like attributes. Attributes tested are: line style, line width, and color + lines: Geopandas dataframe with geometry column containing expected + LineString and MultiLineString objects + lines_groupby: column title from line_exp that lines are expected to be + grouped by/contain + like attributes. Attributes tested are: line style, line width, + and color if None, assertion is passed - polygons: list of lines where each line is a list of coord tuples for the exterior polygon + polygons: list of lines where each line is a list of coord tuples for the + exterior polygon colorbar_range: tuple of (min, max) for colorbar. If empty tuple: asserts a colorbar exists, but does not check values If None: assertion is passed @@ -660,12 +736,15 @@ def __init__( class PlotRaster(unittest.TestCase): """A unittest.TestCase containing tests for a spatial raster plot. - 1. image_data: asserts image is as expected. If Image is classified image classification may be shifted or reversed. + 1. image_data: asserts image is as expected. If Image is + classified image classification may be shifted or reversed. 2. image_stretch: asserts image takes up entire display as expected 3. image_mask: - 4. legend_accuracy: if image is classified, asserts legend exists and correctly describes image. + 4. legend_accuracy: if image is classified, asserts legend exists + and correctly describes image. if image is not classified, assertion is passed. - 5. colorbar_accuracy: asserts colorbar exists and has range descrbied in colorbar_range + 5. colorbar_accuracy: asserts colorbar exists and has range + descrbied in colorbar_range 6. axis_off: axis lines are not displayed """ @@ -710,9 +789,12 @@ def tearDown(self): def cases(self): """ Returns a list of TestCases for spatial raster plots. Testcase are as follows: - 1. CaptionCase: assert caption is in appropriate location with strings expressed in caption_strings - 2. LabelsCase: asserts the title contains strings in title_contains, and x and y labels are empty - 3. RasterCase: asserts raster image matches im_expected and legend is correct if image is classified + 1. CaptionCase: assert caption is in appropriate location with + strings expressed in caption_strings + 2. LabelsCase: asserts the title contains strings in title_contains, + and x and y labels are empty + 3. RasterCase: asserts raster image matches im_expected and legend is + correct if image is classified 4. VectorCase: assert vector data is as expected """ return [ @@ -723,21 +805,25 @@ def cases(self): ] -#### FOLIUM ### +""" FOLIUM """ + + class PlotFoliumSuite(object): """A generic object to test Folium Maps. Parameters --------- fmap: folium map to be tested - markers: set of tuples where each tuple represents the x and y coord of an expected marker + markers: set of tuples where each tuple represents the x and y coord of + an expected marker """ def __init__(self, fmap, markers): class MapFolium(unittest.TestCase): """Returns a unittest.TestCase containing 2 tests on a Folium map. 1. map_folium: map is of type folium.folium.Map - 2. marker_locs: map contains all markers in markers_exp and no additional markers + 2. marker_locs: map contains all markers in markers_exp and no + additional markers """ def setUp(self): @@ -759,7 +845,8 @@ def tearDown(self): def cases(self): """ Returns a TestSuite for Folium Maps. Testcase are as follows: - 1. FoliumCase: asserts map is of type folium.map and contains expected markers + 1. FoliumCase: asserts map is of type folium.map and contains + expected markers """ return [self.FoliumCase] diff --git a/matplotcheck/folium.py b/matplotcheck/folium.py index b1ab9e10..27e9a302 100644 --- a/matplotcheck/folium.py +++ b/matplotcheck/folium.py @@ -1,9 +1,8 @@ -import numpy as np import folium class FoliumTester(object): - """object to test Folium plots + """Object to test Folium plots Parameters ---------- @@ -16,18 +15,25 @@ def __init__(self, fmap): self.fmap = fmap def assert_map_type_folium(self): - """Asserts fmap is of type folium.folium.Map""" + """ + Asserts fmap is of type folium.folium.Map + """ assert type(self.fmap) == folium.folium.Map def assert_folium_marker_locs( self, markers, m="Markers not shown in appropriate location" ): - """Asserts folium contains markers with locations described in markers_exp and only those markers, with error message m + """ + Asserts folium contains markers with locations described in + markers_exp and only those markers, with error message m Parameters ---------- - markers: set of tuples where each tuple represents the x and y coord of an expected marker - m: string error message if assertion is not met + markers: tuples + Set of tuples where each tuple represents the x and y coord of + an expected marker. + m: string + Error message if assertion is not met. """ marker_locs = set() while self.fmap._children: diff --git a/matplotcheck/notebook.py b/matplotcheck/notebook.py index 00716017..2e62870d 100644 --- a/matplotcheck/notebook.py +++ b/matplotcheck/notebook.py @@ -8,12 +8,16 @@ def convert_axes(plt, which_axes="current"): Parameters --------- - plt: Matplotlib plot to be tested - which_axes: string from the following list ['current', 'last', 'first', 'all'] stating which axes we are saving for testing + plt: matplotlib plot + Matplotlib plot to be tested. + which_axes: string + String from the following list ['current', 'last', 'first', 'all'] + stating which axes we are saving for testing. Returns ------- - ax: Matplotlib axes or list of axes as express by which_axes + ax: Matplotlib axes or list + Matplotlib axes or list of axes as express by which_axes """ fig = plt.gcf() if which_axes == "current": @@ -26,12 +30,13 @@ def convert_axes(plt, which_axes="current"): ax = fig.axes else: raise ValueError( - 'which_axes must be one of the following strings ["current", "last", "first", "all"]' + "which_axes must be one of the following strings " + + '["current", "last", "first", "all"]' ) return ax -### JUPYTER NOTEBOOK TEST HELPER FUNCTIONS! ### +# JUPYTER NOTEBOOK TEST HELPER FUNCTIONS! def error_test(n, n_exp): @@ -39,8 +44,10 @@ def error_test(n, n_exp): Parameters ---------- - n: int of number of cells that that did not produce an error - n_exp: int of number of cell that are checked if producing an error + n: int + Number of cells that that did not produce an error. + n_exp: int + Number of cell that are checked if producing an error. Returns ------- @@ -61,22 +68,29 @@ def remove_comments(input_string): Parameters ---------- input_string: string + String to be modified. Returns ------- - string where all parts commented out by a '#' are removed from input_string + string + Sting where all parts commented out by a '#' are removed from + input_string. """ split_lines = input_string.split("\n") return "".join([line.split("#")[0] for line in split_lines]) def import_test(var_dict, n): - """Tests no import statements are found after the first cell in a Jupyter Notebook + """ + Tests no import statements are found after the first cell in a Jupyter + Notebook Parameters ---------- - vars_dict: dictionary produced by 'locals()' in notebook - n: number of cells to be tested for import statement in Jupyter Notebook + vars_dict: dictionary + Dictionary produced by 'locals()' in notebook. + n: int + number of cells to be tested for import statement in Jupyter Notebook Returns ------- diff --git a/matplotcheck/raster.py b/matplotcheck/raster.py index 4f62a8ce..822daea2 100644 --- a/matplotcheck/raster.py +++ b/matplotcheck/raster.py @@ -1,6 +1,4 @@ import numpy as np - -from .base import PlotTester from .vector import VectorTester @@ -95,9 +93,9 @@ def assert_legend_accuracy_classified_image( all_label_options: list of lists Each internal list represents a class and said list is a list of strings where at least one string is expected to be in the legend - label for this category. Internal lists must be in the same order as - bins in im_expected, e.g. first internal list has the expected label - options for class 0. + label for this category. Internal lists must be in the same order + as bins in im_expected, e.g. first internal list has the expected + label options for class 0. Returns ---------- @@ -107,13 +105,13 @@ def assert_legend_accuracy_classified_image( Notes ---------- First compares all_label_options against the legend labels to find - which element of all_label_options matches that entry. E.g. if the first - legend entry has a match in the first list in all_label_options, then - that legend entry corresponds to the first class (value 0). - Then the plot image array is copied and the values are set to the legend - label that match the values (i.e. the element in all_label_options). - The same is done for the expected image array. Finally those two arrays - of strings are compared. Passes if they match. + which element of all_label_options matches that entry. E.g. if the + first legend entry has a match in the first list in all_label_options, + then that legend entry corresponds to the first class (value 0). + Then the plot image array is copied and the values are set to the + legend label that match the values (i.e. the element in + all_label_options). The same is done for the expected image array. + Finally those two arrays of strings are compared. Passes if they match. """ # Retrieve image array im_data = [] @@ -162,7 +160,7 @@ def assert_legend_accuracy_classified_image( im_data_labels, im_expected_labels ), "Incorrect legend to data relation" - ### IMAGE TESTS/HELPER FUNCTIONS ### + # IMAGE TESTS/HELPER FUNCTIONS def get_plot_image(self): """Returns images stored on the Axes object as a list of numpy arrays. diff --git a/matplotcheck/tests/conftest.py b/matplotcheck/tests/conftest.py index 278b3380..17217454 100644 --- a/matplotcheck/tests/conftest.py +++ b/matplotcheck/tests/conftest.py @@ -64,14 +64,15 @@ def basic_polygon_gdf(basic_polygon): ------- GeoDataFrame containing the basic_polygon polygon. """ - gdf = gpd.GeoDataFrame(geometry=[basic_polygon], crs={"init": "epsg:4326"}) + gdf = gpd.GeoDataFrame(geometry=[basic_polygon], crs="epsg:4326") return gdf @pytest.fixture def pd_xlabels(): """Create a DataFrame which uses the column labels as x-data.""" - df = pd.DataFrame({"B": bp.random.randint(0, 100, size=100)}) + df = pd.DataFrame({"B": np.random.randint(0, 100, size=100)}) + return df @pytest.fixture diff --git a/matplotcheck/tests/test_base.py b/matplotcheck/tests/test_base.py index f9f5999b..23b1bf0f 100644 --- a/matplotcheck/tests/test_base.py +++ b/matplotcheck/tests/test_base.py @@ -55,7 +55,8 @@ def test_options(pt_line_plt): def test_assert_string_contains(pt_line_plt): - """Tests that assert_string_contains passes with correct expected strings.""" + """Tests that assert_string_contains passes with correct expected + strings.""" test_string = "this is a test string" string_expected = ["this", "is", "a", "test"] pt_line_plt.assert_string_contains(test_string, string_expected) @@ -63,7 +64,8 @@ def test_assert_string_contains(pt_line_plt): def test_assert_string_contains_fails(pt_line_plt): - """Tests that assert_string_contains fails with incorrect expected strings.""" + """Tests that assert_string_contains fails with incorrect expected + strings.""" test_string = "this is a test string" string_expected = ["this", "is", "not", "a", "test"] with pytest.raises( @@ -74,7 +76,8 @@ def test_assert_string_contains_fails(pt_line_plt): def test_assert_string_contains_or(pt_line_plt): - """Tests that assert_string_contains correctly passes when using OR logic.""" + """Tests that assert_string_contains correctly passes when using OR + logic.""" test_string = "this is a test string" string_expected = ["this", ["is", "not"], "a", "test"] pt_line_plt.assert_string_contains(test_string, string_expected) @@ -82,7 +85,8 @@ def test_assert_string_contains_or(pt_line_plt): def test_assert_string_contains_or_fails(pt_line_plt): - """Tests that assert_string_contains correctly fails when using OR logic.""" + """Tests that assert_string_contains correctly fails when using OR + logic.""" test_string = "this is a test string" string_expected = ["this", "is", ["not", "jambalaya"], "a", "test"] with pytest.raises( @@ -114,7 +118,8 @@ def test_assert_string_contains_handles_short_list_fails(pt_line_plt): def test_assert_string_contains_passes_with_none(pt_line_plt): - """Tests that assert_string_contains passes when strings_expected is None""" + """Tests that assert_string_contains passes when strings_expected is + None""" test_string = "this is a test string" string_expected = None pt_line_plt.assert_string_contains(test_string, string_expected) @@ -122,7 +127,8 @@ def test_assert_string_contains_passes_with_none(pt_line_plt): def test_assert_string_contains_passes_with_empty(pt_line_plt): - """Tests that assert_string_contains passes when strings_expected is empty""" + """Tests that assert_string_contains passes when strings_expected is + empty""" test_string = "this is a test string" string_expected = [] pt_line_plt.assert_string_contains(test_string, string_expected) diff --git a/matplotcheck/tests/test_base_axis.py b/matplotcheck/tests/test_base_axis.py index 83baddbe..e16f2885 100644 --- a/matplotcheck/tests/test_base_axis.py +++ b/matplotcheck/tests/test_base_axis.py @@ -2,7 +2,6 @@ import pytest import matplotlib.pyplot as plt - """ AXIS TESTS """ @@ -52,7 +51,8 @@ def test_axis_off_one_visible(pt_line_plt): def test_axis_off_empty_ticks(pt_line_plt): - """Check assert_axis_off for case when axis tick labels set to empty lists""" + """Check assert_axis_off for case when axis tick labels set to empty + lists""" pt_line_plt.ax.xaxis.set_ticks([]) pt_line_plt.ax.yaxis.set_ticks([]) pt_line_plt.assert_axis_off() @@ -93,7 +93,8 @@ def test_axis_label_contains_y(pt_line_plt): def test_axis_label_contains_invalid_axis(pt_line_plt): - """Check that assert_axis_label_contains fails when given unexpected axis""" + """Check that assert_axis_label_contains fails when given unexpected + axis""" # Fails when given an invalid axies with pytest.raises(ValueError, match="axis must be one of the following"): pt_line_plt.assert_axis_label_contains( diff --git a/matplotcheck/tests/test_base_data.py b/matplotcheck/tests/test_base_data.py index fee04b3f..fdcdade9 100644 --- a/matplotcheck/tests/test_base_data.py +++ b/matplotcheck/tests/test_base_data.py @@ -178,7 +178,8 @@ def test_assert_xydata_xlabel_text_fails(pd_df_monthly_data, pt_monthly_data): def test_assert_xydata_xlabel_numeric( pd_df_monthly_data_numeric, pt_monthly_data_numeric ): - """Tests the xlabels flag on xydata works with numeric expected x-labels.""" + """Tests the xlabels flag on xydata works with numeric expected + x-labels.""" pt_monthly_data_numeric.assert_xydata( pd_df_monthly_data_numeric, xcol="months", ycol="data", xlabels=True diff --git a/matplotcheck/tests/test_base_legends.py b/matplotcheck/tests/test_base_legends.py index 01803fc2..66884d66 100644 --- a/matplotcheck/tests/test_base_legends.py +++ b/matplotcheck/tests/test_base_legends.py @@ -2,7 +2,6 @@ import pytest import matplotlib.pyplot as plt - """ LEGEND TESTS """ @@ -53,7 +52,8 @@ def test_assert_legend_not_case_sensitive(pt_multi_line_plt): def test_assert_legend_labels_bad_text(pt_multi_line_plt): - """Check that assert_legend_labels raises expected error when given wrong text""" + """Check that assert_legend_labels raises expected error when given + wrong text""" with pytest.raises( AssertionError, match="Legend does not have expected labels" ): @@ -62,7 +62,8 @@ def test_assert_legend_labels_bad_text(pt_multi_line_plt): def test_assert_legend_labels_wrong_num(pt_multi_line_plt): - """Check that assert_legend_labels raises expected error given wrong number of labels""" + """Check that assert_legend_labels raises expected error given wrong + number of labels""" with pytest.raises( AssertionError, match="I was expecting 3 legend entries" ): @@ -91,9 +92,10 @@ def test_assert_no_legend_overlap_single(pt_multi_line_plt): def test_assert_no_legend_overlap_double(pt_multi_line_plt): - """Checks that assert_no_legend_overlap passes when two legends don't overlap""" + """Checks that assert_no_legend_overlap passes when two legends don't + overlap""" leg_1 = plt.legend(loc=[0.8, 0.8]) - leg_2 = plt.legend(loc=[0.1, 0.1]) + plt.legend(loc=[0.1, 0.1]) pt_multi_line_plt.ax.add_artist(leg_1) pt_multi_line_plt.assert_no_legend_overlap() plt.close() @@ -102,7 +104,7 @@ def test_assert_no_legend_overlap_double(pt_multi_line_plt): def test_assert_no_legend_overlap_fail(pt_multi_line_plt): """Checks that assert_no_legend_overlap fails with overlapping legends""" leg_1 = plt.legend(loc=[0.12, 0.12]) - leg_2 = plt.legend(loc=[0.1, 0.1]) + plt.legend(loc=[0.1, 0.1]) pt_multi_line_plt.ax.add_artist(leg_1) with pytest.raises(AssertionError, match="Legends overlap eachother"): pt_multi_line_plt.assert_no_legend_overlap() diff --git a/matplotcheck/tests/test_base_titles_captions.py b/matplotcheck/tests/test_base_titles_captions.py index 303fe9c4..1506b77e 100644 --- a/matplotcheck/tests/test_base_titles_captions.py +++ b/matplotcheck/tests/test_base_titles_captions.py @@ -22,7 +22,8 @@ def test_get_titles(pt_line_plt): def test_get_titles_suptitle(pt_line_plt): - """Check that the correct suptitle gets grabbed from a figure with 2 subplots""" + """Check that the correct suptitle gets grabbed from a figure with 2 + subplots""" assert "My Figure Title" == pt_line_plt.get_titles()[0] plt.close() @@ -76,7 +77,8 @@ def test_title_contains_figure(pt_line_plt): def test_title_contains_figure_nosuptitle(pt_bar_plt): - """Check title_contains tester for figure title fails when there is no suptitle""" + """Check title_contains tester for figure title fails when there is no + suptitle""" with pytest.raises( AssertionError, match="Expected title is not displayed" ): @@ -95,7 +97,8 @@ def test_title_contains_both_axes_figure(pt_line_plt): def test_title_contains_both_axes_figure_badtext(pt_line_plt): - """Check title_contains tester for combined titles, should fail with bad text""" + """Check title_contains tester for combined titles, should fail with bad + text""" with pytest.raises( AssertionError, match="Title does not contain expected string: foo" ): diff --git a/matplotcheck/tests/test_raster.py b/matplotcheck/tests/test_raster.py index dd623660..7c4d7fd8 100644 --- a/matplotcheck/tests/test_raster.py +++ b/matplotcheck/tests/test_raster.py @@ -1,6 +1,5 @@ """Tests for the raster module""" import pytest -import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.patches as mpatches @@ -71,7 +70,9 @@ def raster_plt_class(np_ar_discrete): # Create legend colors = [im.cmap(im.norm(val)) for val in values] patches = [ - mpatches.Patch(color=colors[i], label="Level {l}".format(l=values[i])) + mpatches.Patch( + color=colors[i], label="Level {lev}".format(lev=values[i]) + ) for i in range(values.shape[0]) ] plt.legend(handles=patches) @@ -85,7 +86,8 @@ def raster_plt_class(np_ar_discrete): def test_raster_get_colorbars_length(raster_plt): - """Check that get_colorbars correctly retrieves 1 colorbar from raster_plt1""" + """Check that get_colorbars correctly retrieves 1 colorbar from + raster_plt1""" # Should only be 1 object, and should be a colorbar object cb = raster_plt.get_colorbars() assert len(cb) == 1 @@ -156,7 +158,8 @@ def test_raster_assert_colorbar_range_blank(raster_plt_blank, np_ar): def test_raster_assert_legend_accuracy(raster_plt_class, np_ar_discrete): - """Checks that legend matches image, checking both the labels and color patches""" + """Checks that legend matches image, checking both the labels and color + patches""" values = np.sort(np.unique(np_ar_discrete)) label_options = [[str(i)] for i in values] @@ -184,7 +187,8 @@ def test_raster_assert_legend_accuracy_badlabel( def test_raster_assert_legend_accuracy_badvalues( raster_plt_class, np_ar_discrete ): - """Checks that legend matches image, should fail if you swap image values""" + """Checks that legend matches image, should fail if you swap image + values""" values = np.sort(np.unique(np_ar_discrete)) label_options = [[str(i)] for i in values] @@ -276,7 +280,8 @@ def test_raster_assert_image_class(raster_plt_class, np_ar_discrete): def test_raster_assert_image_class_baddata(raster_plt_class, np_ar_discrete): - """Check that assert_image with bad data fails for a discrete, classified image""" + """Check that assert_image with bad data fails for a discrete, classified + image""" bad_ar = np_ar_discrete + 1 with pytest.raises(AssertionError, match="Arrays are not equal"): raster_plt_class.assert_image(bad_ar) @@ -291,7 +296,8 @@ def test_raster_assert_image_fullscreen(raster_plt): def test_raster_assert_image_fullscreen_fail_xlims(raster_plt): """assert fullscreen should fail if we modify the x-axis limits""" - cur_xlim, cur_ylim = raster_plt.ax.get_xlim(), raster_plt.ax.get_ylim() + cur_xlim = raster_plt.ax.get_xlim() + raster_plt.ax.get_ylim() raster_plt.ax.set_xlim([cur_xlim[0], cur_xlim[1] + 5]) with pytest.raises( AssertionError, match="Image is stretched inaccurately" diff --git a/matplotcheck/tests/test_timeseries_module.py b/matplotcheck/tests/test_timeseries_module.py index 32aa53f4..b1be1448 100644 --- a/matplotcheck/tests/test_timeseries_module.py +++ b/matplotcheck/tests/test_timeseries_module.py @@ -1,9 +1,9 @@ -import pytest - ''' def test_assert_xydata_timeseries(pt_time_line_plt, pd_df_timeseries): """Commenting this out for now as this requires a time series data object this is failing because the time data needs to be in seconds like how mpl saves it. """ - pt_time_line_plt.assert_xydata(pd_df_timeseries, xcol='time', ycol='A', xtime=True) + pt_time_line_plt.assert_xydata( + pd_df_timeseries, xcol='time', ycol='A', xtime=True + ) ''' diff --git a/matplotcheck/timeseries.py b/matplotcheck/timeseries.py index 7923aaeb..80a0071a 100644 --- a/matplotcheck/timeseries.py +++ b/matplotcheck/timeseries.py @@ -40,8 +40,10 @@ def assert_xticks_reformatted( 'month': if tick should be shown every new month 'week': if tick should be shown every new week 'day': if tick should be shown every new day - None: if no tick format has been specified. This will automatically assert True - m: string error message if assertion is not met + None: if no tick format has been specified. This will automatically + assert True + m: string + string error message if assertion is not met """ if loc_exp: if tick_size == "large": @@ -60,7 +62,8 @@ def assert_xticks_reformatted( ) # September 30, 2013 else: raise ValueError( - "tick_size must be on of the following string ['large', 'small']" + "tick_size must be on of the following string " + + "['large', 'small']" ) if loc_exp == "decade" or loc_exp == "year": accepted_responses = ["2013"] @@ -70,7 +73,7 @@ def assert_xticks_reformatted( accepted_responses = ["sep30", "september30"] else: raise ValueError( - """loc_exp must be one of the following strings ['decade', + """loc_exp must be one of the following strings ['decade', 'year', 'month', 'week', 'day', None]""" ) assert test_date in accepted_responses, m @@ -81,7 +84,8 @@ def assert_xticks_locs( loc_exp=None, m="Incorrect X axis tick locations", ): - """Asserts that Axes ax has xaxis ticks as noted by tick_size and loc_exp + """Asserts that Axes ax has xaxis ticks as noted by tick_size and + loc_exp Parameters ---------- @@ -107,7 +111,7 @@ def assert_xticks_locs( ticks = self.ax.xaxis.get_minorticklocs() else: raise ValueError( - """"Tick_size must be one of the following strings + """"Tick_size must be one of the following strings ['large', 'small']""" ) @@ -123,7 +127,7 @@ def assert_xticks_locs( inc = relativedelta(days=1) else: raise ValueError( - """"loc_exp must be one of the following strings ['decade', + """"loc_exp must be one of the following strings ['decade', 'year', 'month', 'week', 'day'] or None""" ) @@ -162,18 +166,16 @@ def assert_no_data_value(self, nodata=999.99): xtime: boolean does the x-axis contains datetime values? """ - if nodata != None: + if nodata: xy = self.get_xy(xtime=False) - assert ~np.isin( - nodata, xy["x"] - ), "Values of {0} have been found in data. Be sure to remove no data values".format( - nodata - ) - assert ~np.isin( - nodata, xy["y"] - ), "Values of {0} have been found in data. Be sure to remove no data values".format( - nodata - ) + assert ~np.isin(nodata, xy["x"]), ( + "Values of {0} have been found in data. Be sure to remove no " + "data values" + ).format(nodata) + assert ~np.isin(nodata, xy["y"]), ( + "Values of {0} have been found in data. Be sure to remove no " + "data values" + ).format(nodata) def assert_xdata_date( self, x_exp, m="X-axis is not in appropriate date format" diff --git a/matplotcheck/vector.py b/matplotcheck/vector.py index 10610602..f91f8681 100644 --- a/matplotcheck/vector.py +++ b/matplotcheck/vector.py @@ -24,7 +24,8 @@ def __init__(self, ax): def assert_legend_no_overlay_content( self, m="Legend overlays plot contents" ): - """Asserts that each legend does not overlay plot contents with error message m + """Asserts that each legend does not overlay plot contents with error + message m Parameters --------- @@ -62,7 +63,8 @@ def _legends_overlap(self, b1, b2): return x_overlap and y_overlap def assert_no_legend_overlap(self, m="Legends overlap eachother"): - """Asserts there are no two legends in Axes ax that overlap each other with error message m + """Asserts there are no two legends in Axes ax that overlap each other + with error message m Parameters ---------- @@ -76,13 +78,14 @@ def assert_no_legend_overlap(self, m="Legends overlap eachother"): leg_extent2 = legends[j].get_window_extent().get_points() assert not self._legends_overlap(leg_extent1, leg_extent2), m - ### VECTOR DATA FUNCTIONS ### - ## MARKER POINTS ### + """ Check Data """ def _convert_length(self, arr, n): - """ helper function for 'get_points_by_attributes' and 'get_lines_by_attributes' + """ helper function for 'get_points_by_attributes' and + 'get_lines_by_attributes' takes an array of either length 1 or n. - If array is length 1: array of array's only element repeating n times is returned + If array is length 1: array of array's only element repeating n times + is returned If array is length n: original array is returned Else: function raises value error @@ -102,14 +105,11 @@ def _convert_length(self, arr, n): elif len(arr) == n: return arr else: - raise ValueError( - "Input array length is not of either expected values:1 or {0}".format( - n - ) - ) + raise ValueError("Input array length is not: 1 or {0}".format(n)) def get_points_by_attributes(self): - """Returns a sorted list of lists where each list contains tuples of xycoords for points of + """Returns a sorted list of lists where each list contains tuples of + xycoords for points of the same attributes: color, marker, and markersize Returns @@ -162,15 +162,18 @@ def get_points_by_attributes(self): def assert_points_grouped_by_type( self, data_exp, sort_column, m="Point attribtues not accurate by type" ): - """Asserts that the points on Axes ax display attributes based on their type with error message m + """Asserts that the points on Axes ax display attributes based on their + type with error message m attributes tested are: color, marker, and markersize Parameters ---------- data_exp: Geopandas Dataframe with Point objects in column 'geometry' - an additional column with title sort_column, denotes a category for each point + an additional column with title sort_column, denotes a category for + each point sort_column: string of column label in dataframe data_exp. - this column contains values expressing which points belong to which group + this column contains values expressing which points belong to which + group m: string error message if assertion is not met """ @@ -188,7 +191,8 @@ def sort_collection_by_markersize(self): Returns -------- - pandas dataframe with columns x, y, point_size. Each row reprsents a point on Axes ax with location x,y and markersize pointsize + pandas dataframe with columns x, y, point_size. Each row reprsents a + point on Axes ax with location x,y and markersize pointsize """ df = pd.DataFrame(columns=("x", "y", "markersize")) for c in self.ax.collections: @@ -212,12 +216,15 @@ def sort_collection_by_markersize(self): return df def assert_collection_sorted_by_markersize(self, df_expected, sort_column): - """Asserts a collection of points vary in size by column expresse din sort_column + """Asserts a collection of points vary in size by column expresse din + sort_column Parameters ---------- - df_expected: geopandas dataframe with geometry column of expected point locations - sort_column: column title from df_expected that points are expected to be sorted by + df_expected: geopandas dataframe with geometry column of expected point + locations + sort_column: column title from df_expected that points are expected to + be sorted by if None, assertion is passed """ df = self.sort_collection_by_markersize() @@ -237,23 +244,28 @@ def assert_collection_sorted_by_markersize(self, df_expected, sort_column): err_msg="Markersize not based on {0} values".format(sort_column), ) - ### LINES ### + """Check lines""" def _convert_multilines(self, df, column_title): """Helper function for get_lines_by_attribute - converts a pandas dataframe containing a column of LineString and MultiLinestring objects - to a pandas dataframe where each row represents a single line. Line segment values are converted + converts a pandas dataframe containing a column of LineString and + MultiLinestring objects + to a pandas dataframe where each row represents a single line. Line + segment values are converted to a list of tuples. Parameters --------- - df: pandas Dataframe containing a column of LineString and MultiLinestring objects - column_title: string of column title which holds LineString and MultLinestring objects + df: pandas Dataframe containing a column of LineString and + MultiLinestring objects + column_title: string of column title which holds LineString and + MultLinestring objects Returns ------- - Dataframe where each row repsrents a single line. - Line segments values are converted to a list of tuples in column column_title + Dataframe where each row represents a single line. + Line segments values are converted to a list of tuples in column + column_title """ dfout = df.copy() for i, row in dfout.iterrows(): @@ -268,13 +280,15 @@ def _convert_multilines(self, df, column_title): dfout = dfout.append(new_row).reset_index(drop=True) else: raise ValueError( - "Segment is not of either expected type: MultiLinestring, LineString" + "Segment is not of either expected type: MultiLinestring, " + "LineString" ) return dfout def _convert_linestyle(self, ls): """helper function for get_lines_by_attributes. - converts linestyle to a tuple of (offset, onoffseq) to get hashable datatypes + converts linestyle to a tuple of (offset, onoffseq) to get hashable + datatypes Parameters ---------- @@ -294,8 +308,9 @@ def get_lines(self): Returns ------- - output: DataFrame with column 'lines'. Each row represents one line segment. - Its value in 'lines' is a list of tuples representing the line segement. + output: DataFrame with column 'lines'. Each row represents one line + segment. Its value in 'lines' is a list of tuples representing the + line segment. """ lines = [ [tuple(coords) for coords in seg] @@ -306,11 +321,13 @@ def get_lines(self): return pd.DataFrame({"lines": lines}) def get_lines_by_collection(self): - """Returns a sorted list of list where each list contains line segments from the same collections + """Returns a sorted list of list where each list contains line segments + from the same collections Returns ------- - sorted list where each list represents all lines from the same collection + sorted list where each list represents all lines from the same + collection """ lines_grouped = [ [[tuple(coords) for coords in seg] for seg in c.get_segments()] @@ -320,12 +337,14 @@ def get_lines_by_collection(self): return sorted([sorted(l) for l in lines_grouped]) def get_lines_by_attributes(self): - """Returns a sorted list of lists where each list contains line segments of the same attributes: - color, linewidth, and linestyle + """Returns a sorted list of lists where each list contains line + segments of the same attributes: + color, linewidth, and linestyle Returns ------ - sorted list where each list represents all lines with the same attributes + sorted list where each list represents all lines with the same + attributes """ lines_dataframe = pd.DataFrame( columns=["seg", "color", "lwidth", "lstyle"] @@ -368,17 +387,19 @@ def get_lines_by_attributes(self): return sorted([sorted(l) for l in lines_grouped]) def assert_lines(self, lines_expected, m="Incorrect Line Data"): - """Asserts the line data in Axes ax is equal to lines_expected with error message m. + """Asserts the line data in Axes ax is equal to lines_expected with + error message m. If line_expected is None or an empty list, assertion is passed Parameters ---------- - lines_expected: Geopandas Dataframe with a geometry column consisting of MultilineString and LineString objects + lines_expected: Geopandas Dataframe with a geometry column consisting + of MultilineString and LineString objects m: string error message if assertion is not met """ if type(lines_expected) == gpd.geodataframe.GeoDataFrame: lines_expected = lines_expected[ - lines_expected["geometry"].is_empty == False + ~lines_expected["geometry"].is_empty ].reset_index(drop=True) fig, ax_exp = plt.subplots() lines_expected.plot(ax=ax_exp) @@ -400,19 +421,22 @@ def assert_lines_grouped_by_type( sort_column, m="Line attributes not accurate by type", ): - """Asserts that the lines on Axes ax display like attributes based on their type with error message m + """Asserts that the lines on Axes ax display like attributes based on + their type with error message m attributes tested are: color, linewidth, linestyle Parameters ---------- - lines_expected: Geopandas Dataframe with geometry column consisting of MultiLineString and LineString objects - sort_column: string of column title in lines_expected that contains types lines are expected to be grouped by + lines_expected: Geopandas Dataframe with geometry column consisting of + MultiLineString and LineString objects + sort_column: string of column title in lines_expected that contains + types lines are expected to be grouped by m: string error message if assertion is not met """ if type(lines_expected) == gpd.geodataframe.GeoDataFrame: groups = self.get_lines_by_attributes() lines_expected = lines_expected[ - lines_expected["geometry"].is_empty == False + ~lines_expected["geometry"].is_empty ].reset_index(drop=True) fig, ax_exp = plt.subplots() for typ, data in lines_expected.groupby(sort_column): @@ -432,14 +456,16 @@ def assert_lines_grouped_by_type( "lines_expected is not of expected type: GeoDataFrame" ) - ### POLYGONS ### + """ Check Polygons """ def get_polygons(self): - """Returns all polygons on Axes ax as a sorted list of polygons where each polygon is a list of coord tuples + """Returns all polygons on Axes ax as a sorted list of polygons where + each polygon is a list of coord tuples Returns ------- - output: sorted list of polygons. Each polygon is a list tuples. Ecah tuples is a coordinate. + output: sorted list of polygons. Each polygon is a list tuples. Each + tuple is a coordinate. """ output = [ [tuple(coords) for coords in path.vertices] @@ -451,7 +477,8 @@ def get_polygons(self): def _convert_multipolygons(self, series): """Helper function for assert_polygons - converts a pandas series of Polygon and MultiPolygon objects to a list of lines, + converts a pandas series of Polygon and MultiPolygon objects to a list + of lines, where each line is a list of coord tuples for the exterior Parameters @@ -460,7 +487,8 @@ def _convert_multipolygons(self, series): Returns ------- - list of lines where each line is a list of coord tuples for the exterior polygon + list of lines where each line is a list of coord tuples for the + exterior polygon """ output = [] for entry in series: @@ -474,25 +502,27 @@ def _convert_multipolygons(self, series): def assert_polygons( self, polygons_expected, dec=None, m="Incorrect Polygon Data" ): - """Asserts the polygon data in Axes ax is equal to polygons_expected to decimal place dec with error message m + """Asserts the polygon data in Axes ax is equal to polygons_expected to + decimal place dec with error message m If polygons_expected is am empty list or None, assertion is passed. Parameters ---------- polygons_expected : List or GeoDataFrame - List of polygons expected to be founds on Axes ax or a GeoDataFrame - containing the expected polygons. + List of polygons expected to be founds on Axes ax or a GeoDataFrame + containing the expected polygons. dec : int (Optional) - Int stating the desired decimal precision. If None, polygons must - be exact. + Int stating the desired decimal precision. If None, polygons must + be exact. m : string (default = "Incorrect Polygon Data") - String error message if assertion is not met. + String error message if assertion is not met. """ if len(polygons_expected) != 0: if isinstance(polygons_expected, list): if len(polygons_expected[0]) == 0: raise ValueError( - "Empty list or GeoDataFrame passed into assert_polygons." + "Empty list or GeoDataFrame passed into assert_" + "polygons." ) if isinstance(polygons_expected, gpd.geodataframe.GeoDataFrame): polygons_expected = self._convert_multipolygons( diff --git a/tox.ini b/tox.ini index e037d539..4a4951e6 100644 --- a/tox.ini +++ b/tox.ini @@ -20,8 +20,10 @@ commands = deps = pip>=19.0 black + flake8 basepython = python3 commands = black --check --verbose matplotcheck + flake8 matplotcheck [testenv:docs] whitelist_externals = make From 77e8f761e212c474bcdff94677083dfd47e7f1a0 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Fri, 6 Mar 2020 15:23:07 -0700 Subject: [PATCH 15/53] Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette --- CHANGELOG.md | 3 +++ examples/plot_testing_basics.py | 4 ++-- matplotcheck/base.py | 6 ++++-- matplotcheck/tests/test_base_axis.py | 16 ++++++++++++++++ matplotcheck/tests/test_base_titles_captions.py | 12 ++++++++++++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbb28c03..6717f783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* made `assert_string_contains()` accept correct strings with spaces in them (@nkorinek, #182) + +## [0.1.2] * Adding flake8 for format and other syntax issues! yay! (@lwasser, #195) * Created a vignette covering the testing of histograms (@ryla5068, #149) * Created `get_plot_image()` function for the RasterTester object (@nkorinek, #192) diff --git a/examples/plot_testing_basics.py b/examples/plot_testing_basics.py index 839a6919..45b182a7 100644 --- a/examples/plot_testing_basics.py +++ b/examples/plot_testing_basics.py @@ -55,7 +55,7 @@ fig, ax = plt.subplots() ax.bar(months, percip, color="blue") ax.set( - title="Average Monthly Percipitation in Boulder, CO", + title="Average Monthly Precipitation in Boulder, CO", xlabel="Month", ylabel="Percipitation (in)", ) @@ -86,7 +86,7 @@ plot_tester_1.assert_plot_type("bar") # Test that the plot title contains specific words -plot_tester_1.assert_title_contains(["average", "month", "percip", "boulder"]) +plot_tester_1.assert_title_contains(["average", "monthly precip", "boulder"]) # Test that the axis labels contain specific words plot_tester_1.assert_axis_label_contains(axis="x", strings_expected=["month"]) diff --git a/matplotcheck/base.py b/matplotcheck/base.py index 46cc940b..29be8d9b 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -125,10 +125,12 @@ def assert_string_contains( string = string.lower().replace(" ", "") for check in strings_expected: if isinstance(check, str): - if not check.lower() in string: + if not check.lower().replace(" ", "") in string: raise AssertionError(message_default.format(check)) elif isinstance(check, list): - if not any([c.lower() in string for c in check]): + if not any( + [c.lower().replace(" ", "") in string for c in check] + ): if len(check) == 1: raise AssertionError(message_default.format(check[0])) else: diff --git a/matplotcheck/tests/test_base_axis.py b/matplotcheck/tests/test_base_axis.py index e16f2885..dfd70af7 100644 --- a/matplotcheck/tests/test_base_axis.py +++ b/matplotcheck/tests/test_base_axis.py @@ -92,6 +92,22 @@ def test_axis_label_contains_y(pt_line_plt): plt.close() +def test_axis_label_contains_x_spaces(pt_line_plt): + """Checks for assert_axis_label_contains for x axis with spaces""" + pt_line_plt.assert_axis_label_contains( + axis="x", strings_expected=["x label"] + ) + plt.close() + + +def test_axis_label_contains_y_spaces(pt_line_plt): + """Checks for assert_axis_label_contains for y axis with spaces""" + pt_line_plt.assert_axis_label_contains( + axis="y", strings_expected=["y label"] + ) + plt.close() + + def test_axis_label_contains_invalid_axis(pt_line_plt): """Check that assert_axis_label_contains fails when given unexpected axis""" diff --git a/matplotcheck/tests/test_base_titles_captions.py b/matplotcheck/tests/test_base_titles_captions.py index 1506b77e..8fd65581 100644 --- a/matplotcheck/tests/test_base_titles_captions.py +++ b/matplotcheck/tests/test_base_titles_captions.py @@ -48,6 +48,12 @@ def test_title_contains_axes(pt_line_plt): plt.close() +def test_title_contains_axes_spaces(pt_line_plt): + """Check title_contains for axes title with spaces""" + pt_line_plt.assert_title_contains(["My Plot Title"], title_type="axes") + plt.close() + + def test_title_contains_axes_badtext(pt_line_plt): """Check title_contains fails when given bad text""" with pytest.raises( @@ -123,6 +129,12 @@ def test_assert_caption_contains(pt_line_plt): plt.close() +def test_assert_caption_contains_spaces(pt_line_plt): + """Test that caption contains passes given right text with spaces""" + pt_line_plt.assert_caption_contains([["Figure Caption"]]) + plt.close() + + def test_assert_caption_contains_expect_empty(pt_line_plt): """Test that caption contains passes when expected text list is empty""" pt_line_plt.assert_caption_contains([]) From 99e961aa945706d4f1e49e637a6fa457dfc3b93e Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Sat, 7 Mar 2020 03:48:13 +0200 Subject: [PATCH 16/53] Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 635fa63a..986e532c 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -6,7 +6,7 @@ m2r==0.2.1 pytest-vcr==1.0.2 pytest-cov==2.7.1 pytest==5.3.5 -codecov==2.0.15 +codecov==2.0.16 setuptools==45.2.0 pre-commit==1.20.0 pip==19.0.3 From 3c37b7dfed273e36caa67db187448e408d56a9d6 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Tue, 10 Mar 2020 14:41:19 -0600 Subject: [PATCH 17/53] Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README --- .zenodo.json | 5 +++++ CHANGELOG.md | 1 + README.md | 25 ++++--------------------- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index 2355ff48..63868a86 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -5,6 +5,11 @@ "name": "Wasser, Leah", "orcid": "0000-0002-8177-6550" }, + { + "affiliation": "Earth Lab, University of Colorado - Boulder", + "name": "Korinek, Nathan", + "orcid": "0000-0003-0859-7246" + }, { "name": "LaRocque, Ryan", "orcid": "0000-0003-2540-1428" diff --git a/CHANGELOG.md b/CHANGELOG.md index 6717f783..b061a7af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] * made `assert_string_contains()` accept correct strings with spaces in them (@nkorinek, #182) +* added contributors file and updated README to remove that information (@nkorinek, #121) ## [0.1.2] * Adding flake8 for format and other syntax issues! yay! (@lwasser, #195) diff --git a/README.md b/README.md index 614d97c6..75cada70 100644 --- a/README.md +++ b/README.md @@ -111,35 +111,18 @@ vt.assert_polygons(polygons_expected=polygons) Caveats: This repo likely misses edge cases of the many ways matplotlib plots can be created. Please feel free to submit bugs! - ## Active Contributors -- Leah Wasser - -## Dev Setup (to be moved to contributing) - -setup the matplotcheck envt - -``` -conda env create -f environment.yml -conda activate matplotcheck-dev -``` - -Then setup all of the development requirements. - -``` -pip install -e . -pip install -r dev-requirements.txt -pre-commit install -``` +Leah Wasser +Nathan Korinek ## Contributors We've welcome any and all contributions. Below are some of the contributors to MatPlotCheck. -Kylen Solvik -Kylen Solvik +Ryan Larocque +Kylen Solvik Kristen Curry ## How to Contribute From d2dfa5ac2f7c6f02aad44c114e7debb0d1cfc23d Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Tue, 10 Mar 2020 22:41:44 +0200 Subject: [PATCH 18/53] Update setuptools from 45.2.0 to 46.0.0 (#215) --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 986e532c..18dd35be 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -7,7 +7,7 @@ pytest-vcr==1.0.2 pytest-cov==2.7.1 pytest==5.3.5 codecov==2.0.16 -setuptools==45.2.0 +setuptools==46.0.0 pre-commit==1.20.0 pip==19.0.3 descartes==1.1.0 From 6b142e8bc9edf62de95f33d2d6b58de5db39b703 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Thu, 19 Mar 2020 10:17:42 -0600 Subject: [PATCH 19/53] Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe --- CHANGELOG.md | 1 + matplotcheck/tests/test_vector.py | 60 ++++++++++++ matplotcheck/vector.py | 158 ++++++++++++++++-------------- 3 files changed, 143 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b061a7af..86f7f067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* Created functions to test point geometries in VectorTester (@nkorinek, #176) * made `assert_string_contains()` accept correct strings with spaces in them (@nkorinek, #182) * added contributors file and updated README to remove that information (@nkorinek, #121) diff --git a/matplotcheck/tests/test_vector.py b/matplotcheck/tests/test_vector.py index e65a0611..952df150 100644 --- a/matplotcheck/tests/test_vector.py +++ b/matplotcheck/tests/test_vector.py @@ -20,6 +20,31 @@ def poly_geo_plot(basic_polygon_gdf): return VectorTester(axis) +@pytest.fixture +def point_geo_plot(pd_gdf): + """Create a point plot for testing""" + _, ax = plt.subplots() + + pd_gdf.plot(ax=ax) + ax.set_title("My Plot Title", fontsize=30) + ax.set_xlabel("x label") + ax.set_ylabel("y label") + + axis = plt.gca() + + return VectorTester(axis) + + +@pytest.fixture +def bad_pd_gdf(pd_gdf): + """Create a point geodataframe with slightly wrong values for testing""" + return gpd.GeoDataFrame( + geometry=gpd.points_from_xy( + pd_gdf.geometry.x + 1, pd_gdf.geometry.y + 1 + ) + ) + + def test_list_of_polygons_check(poly_geo_plot, basic_polygon): """Check that the polygon assert works with a list of polygons.""" x, y = basic_polygon.exterior.coords.xy @@ -85,3 +110,38 @@ def test_polygon_custom_fail_message(poly_geo_plot, basic_polygon): poly_list = [(x[0] + 0.5, x[1]) for x in list(zip(x, y))] poly_geo_plot.assert_polygons(poly_list, m="Test Message") plt.close() + + +def test_point_geometry_pass(point_geo_plot, pd_gdf): + """Check that the point geometry test recognizes correct points.""" + point_geo_plot.assert_points(points_expected=pd_gdf) + + +def test_point_geometry_fail(point_geo_plot, bad_pd_gdf): + """Check that the point geometry test recognizes incorrect points.""" + with pytest.raises(AssertionError, match="Incorrect Point Data"): + point_geo_plot.assert_points(points_expected=bad_pd_gdf) + + +def test_assert_point_fails_list(point_geo_plot, pd_gdf): + """ + Check that the point geometry test fails anything that's not a + GeoDataFrame + """ + list_geo = [list(pd_gdf.geometry.x), list(pd_gdf.geometry.y)] + with pytest.raises(ValueError, match="points_expected is not expected"): + point_geo_plot.assert_points(points_expected=list_geo) + + +def test_get_points(point_geo_plot, pd_gdf): + """Tests that get_points returns correct values""" + xy_values = point_geo_plot.get_points() + assert list(sorted(xy_values.x)) == sorted(list(pd_gdf.geometry.x)) + assert list(sorted(xy_values.y)) == sorted(list(pd_gdf.geometry.y)) + + +def test_assert_points_custom_message(point_geo_plot, bad_pd_gdf): + """Tests that a custom error message is passed.""" + message = "Test message" + with pytest.raises(AssertionError, match="Test message"): + point_geo_plot.assert_points(points_expected=bad_pd_gdf, m=message) diff --git a/matplotcheck/vector.py b/matplotcheck/vector.py index f91f8681..ab77c8da 100644 --- a/matplotcheck/vector.py +++ b/matplotcheck/vector.py @@ -21,63 +21,6 @@ def __init__(self, ax): """Initialize the vector tester""" super(VectorTester, self).__init__(ax) - def assert_legend_no_overlay_content( - self, m="Legend overlays plot contents" - ): - """Asserts that each legend does not overlay plot contents with error - message m - - Parameters - --------- - m: string - error message if assertion is not met - """ - plot_extent = self.ax.get_window_extent().get_points() - legends = self.get_legends() - for leg in legends: - leg_extent = leg.get_window_extent().get_points() - legend_left = leg_extent[1][0] < plot_extent[0][0] - legend_right = leg_extent[0][0] > plot_extent[1][0] - legend_below = leg_extent[1][1] < plot_extent[0][1] - assert legend_left or legend_right or legend_below, m - - def _legends_overlap(self, b1, b2): - """Helper function for assert_no_legend_overlap - Boolean value if points of window extents for b1 and b2 overlap - - Parameters - ---------- - b1: bounding box of window extents - b2: bounding box of window extents - - Returns - ------ - boolean value that says if bounding boxes b1 and b2 overlap - """ - x_overlap = (b1[0][0] <= b2[1][0] and b1[0][0] >= b2[0][0]) or ( - b1[1][0] <= b2[1][0] and b1[1][0] >= b2[0][0] - ) - y_overlap = (b1[0][1] <= b2[1][1] and b1[0][1] >= b2[0][1]) or ( - b1[1][1] <= b2[1][1] and b1[1][1] >= b2[0][1] - ) - return x_overlap and y_overlap - - def assert_no_legend_overlap(self, m="Legends overlap eachother"): - """Asserts there are no two legends in Axes ax that overlap each other - with error message m - - Parameters - ---------- - m: string error message if assertion is not met - """ - legends = self.get_legends() - n = len(legends) - for i in range(n - 1): - leg_extent1 = legends[i].get_window_extent().get_points() - for j in range(i + 1, n): - leg_extent2 = legends[j].get_window_extent().get_points() - assert not self._legends_overlap(leg_extent1, leg_extent2), m - """ Check Data """ def _convert_length(self, arr, n): @@ -160,7 +103,7 @@ def get_points_by_attributes(self): return sorted([sorted(p) for p in points_grouped]) def assert_points_grouped_by_type( - self, data_exp, sort_column, m="Point attribtues not accurate by type" + self, data_exp, sort_column, m="Point attributes not accurate by type" ): """Asserts that the points on Axes ax display attributes based on their type with error message m @@ -196,27 +139,28 @@ def sort_collection_by_markersize(self): """ df = pd.DataFrame(columns=("x", "y", "markersize")) for c in self.ax.collections: - offsets, markersizes = c.get_offsets(), c.get_sizes() - x_data, y_data = ( - [offset[0] for offset in offsets], - [offset[1] for offset in offsets], - ) - if len(markersizes) == 1: - markersize = [markersizes[0]] * len(offsets) - df2 = pd.DataFrame( - {"x": x_data, "y": y_data, "markersize": markersize} - ) - df = df.append(df2) - elif len(markersizes) == len(offsets): - df2 = pd.DataFrame( - {"x": x_data, "y": y_data, "markersize": markersizes} + if isinstance(c, matplotlib.collections.PathCollection): + offsets, markersizes = c.get_offsets(), c.get_sizes() + x_data, y_data = ( + [offset[0] for offset in offsets], + [offset[1] for offset in offsets], ) - df = df.append(df2) + if len(markersizes) == 1: + markersize = [markersizes[0]] * len(offsets) + df2 = pd.DataFrame( + {"x": x_data, "y": y_data, "markersize": markersize} + ) + df = df.append(df2) + elif len(markersizes) == len(offsets): + df2 = pd.DataFrame( + {"x": x_data, "y": y_data, "markersize": markersizes} + ) + df = df.append(df2) df = df.sort_values(by="markersize").reset_index(drop=True) return df def assert_collection_sorted_by_markersize(self, df_expected, sort_column): - """Asserts a collection of points vary in size by column expresse din + """Asserts a collection of points vary in size by column expressed in sort_column Parameters @@ -244,7 +188,69 @@ def assert_collection_sorted_by_markersize(self, df_expected, sort_column): err_msg="Markersize not based on {0} values".format(sort_column), ) - """Check lines""" + def get_points(self): + """Returns a Pandas dataframe with all x, y values for points on axis. + + Returns + ------- + output: DataFrame with columns 'x' and 'y'. Each row represents one + points coordinates. + """ + points = self.get_xy(points_only=True).sort_values(by=["x", "y"]) + points.reset_index(inplace=True, drop=True) + return points + + def assert_points(self, points_expected, m="Incorrect Point Data"): + """ + Asserts the point data in Axes ax is equal to points_expected data + with error message m. + If points_expected not a GeoDataFrame, test fails. + + Parameters + ---------- + points_expected : GeoDataFrame + GeoDataFrame with the expected points for the axis. + m : string (default = "Incorrect Point Data") + String error message if assertion is not met. + """ + if isinstance(points_expected, gpd.geodataframe.GeoDataFrame): + points = self.get_points() + xy_expected = pd.DataFrame(columns=["x", "y"]) + xy_expected["x"] = points_expected.geometry.x + xy_expected["y"] = points_expected.geometry.y + xy_expected = xy_expected.sort_values(by=["x", "y"]) + xy_expected.reset_index(inplace=True, drop=True) + # Fix for failure if more than points were plotted in matplotlib + if len(points) != len(xy_expected): + # Checks if there are extra 0, 0 coords in the DataFrame + # returned from self.get_points and removes them. + points_zeros = (points["x"] == 0) & (points["y"] == 0) + if points_zeros.any(): + expected_zeros = (xy_expected["x"] == 0) & ( + xy_expected["y"] == 0 + ) + keep = expected_zeros.sum() + zeros_index_vals = points_zeros.index[ + points_zeros.tolist() + ] + for i in range(keep): + points_zeros.at[zeros_index_vals[i]] = False + points = points[~points_zeros].reset_index(drop=True) + else: + raise AssertionError( + "points_expected's length does not match the stored" + "data's length." + ) + try: + pd.testing.assert_frame_equal(left=points, right=xy_expected) + except AssertionError: + raise AssertionError(m) + else: + raise ValueError( + "points_expected is not expected type: GeoDataFrame" + ) + + # Lines def _convert_multilines(self, df, column_title): """Helper function for get_lines_by_attribute @@ -449,7 +455,7 @@ def assert_lines_grouped_by_type( grouped_exp = sorted([sorted(l) for l in grouped_exp]) plt.close(fig) np.testing.assert_equal(groups, grouped_exp, m) - elif not lines_expected: + elif lines_expected is None: pass else: raise ValueError( From 7ea9490d13dc5aa64a028a017c0f410a30c481b0 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 13 Mar 2020 14:37:12 -0600 Subject: [PATCH 20/53] Update pytest from 5.3.5 to 5.4.1 --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 18dd35be..6e6ecd5e 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -5,7 +5,7 @@ sphinx-autobuild==0.7.1 m2r==0.2.1 pytest-vcr==1.0.2 pytest-cov==2.7.1 -pytest==5.3.5 +pytest==5.4.1 codecov==2.0.16 setuptools==46.0.0 pre-commit==1.20.0 From a78227113e58f611b21a2882262cb4c189f3b032 Mon Sep 17 00:00:00 2001 From: nkorinek Date: Mon, 20 Apr 2020 12:23:01 -0600 Subject: [PATCH 21/53] merge --- dev-requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 6e6ecd5e..d6226b30 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -6,8 +6,7 @@ m2r==0.2.1 pytest-vcr==1.0.2 pytest-cov==2.7.1 pytest==5.4.1 -codecov==2.0.16 -setuptools==46.0.0 +setuptools==46.1.1 pre-commit==1.20.0 pip==19.0.3 descartes==1.1.0 From e7b4652c4d6a32e716b13edd1b44e3b96f9420d8 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Mon, 23 Mar 2020 12:18:53 -0600 Subject: [PATCH 22/53] Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser --- CHANGELOG.md | 1 + matplotcheck/tests/conftest.py | 58 +++--- matplotcheck/tests/test_lines.py | 171 ++++++++++++++++++ matplotcheck/tests/test_points.py | 161 +++++++++++++++++ .../{test_vector.py => test_polygons.py} | 108 +++++------ 5 files changed, 404 insertions(+), 95 deletions(-) create mode 100644 matplotcheck/tests/test_lines.py create mode 100644 matplotcheck/tests/test_points.py rename matplotcheck/tests/{test_vector.py => test_polygons.py} (56%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86f7f067..af32256c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* Created tests for the vector module (@nkorinek, #209) * Created functions to test point geometries in VectorTester (@nkorinek, #176) * made `assert_string_contains()` accept correct strings with spaces in them (@nkorinek, #182) * added contributors file and updated README to remove that information (@nkorinek, #121) diff --git a/matplotcheck/tests/conftest.py b/matplotcheck/tests/conftest.py index 17217454..0bad338a 100644 --- a/matplotcheck/tests/conftest.py +++ b/matplotcheck/tests/conftest.py @@ -1,10 +1,10 @@ """Pytest fixtures for matplotcheck tests""" import pytest -import pandas as pd -import geopandas as gpd -from shapely.geometry import Polygon import numpy as np import matplotlib.pyplot as plt +import pandas as pd +import geopandas as gpd +from shapely.geometry import Polygon, LineString from matplotcheck.base import PlotTester @@ -32,14 +32,14 @@ def pd_gdf(): """Create a geopandas GeoDataFrame for testing""" df = pd.DataFrame( { - "lat": np.random.randint(-85, 85, size=100), - "lon": np.random.randint(-180, 180, size=100), + "lat": np.random.randint(-85, 85, size=5), + "lon": np.random.randint(-180, 180, size=5), } ) gdf = gpd.GeoDataFrame( - {"A": np.arange(100)}, geometry=gpd.points_from_xy(df.lon, df.lat) + {"A": np.arange(5)}, geometry=gpd.points_from_xy(df.lon, df.lat) ) - + gdf["attr"] = ["Tree", "Tree", "Bush", "Bush", "Bush"] return gdf @@ -68,6 +68,15 @@ def basic_polygon_gdf(basic_polygon): return gdf +@pytest.fixture +def two_line_gdf(): + """ Create Line Objects For Testing """ + linea = LineString([(1, 1), (2, 2), (3, 2), (5, 3)]) + lineb = LineString([(3, 4), (5, 7), (12, 2), (10, 5), (9, 7.5)]) + gdf = gpd.GeoDataFrame([1, 2], geometry=[linea, lineb], crs="epsg:4326") + return gdf + + @pytest.fixture def pd_xlabels(): """Create a DataFrame which uses the column labels as x-data.""" @@ -85,9 +94,7 @@ def pt_scatter_plt(pd_df): ax.set_xlabel("x label") ax.set_ylabel("y label") - axis = plt.gca() - - return PlotTester(axis) + return PlotTester(ax) @pytest.fixture @@ -110,9 +117,7 @@ def pt_line_plt(pd_df): ax_position.ymax - 0.25, ax_position.ymin - 0.075, "Figure Caption" ) - axis = plt.gca() - - return PlotTester(axis) + return PlotTester(ax) @pytest.fixture @@ -123,9 +128,7 @@ def pt_multi_line_plt(pd_df): ax.set_ylim((0, 140)) ax.legend(loc="center left", title="Legend", bbox_to_anchor=(1, 0.5)) - axis = plt.gca() - - return PlotTester(axis) + return PlotTester(ax) @pytest.fixture @@ -138,9 +141,7 @@ def pt_bar_plt(pd_df): ax.set_xlabel("x label") ax.set_ylabel("y label") - axis = plt.gca() - - return PlotTester(axis) + return PlotTester(ax) @pytest.fixture @@ -150,21 +151,22 @@ def pt_time_line_plt(pd_df_timeseries): pd_df_timeseries.plot("time", "A", kind="line", ax=ax) - axis = plt.gca() - - return PlotTester(axis) + return PlotTester(ax) @pytest.fixture def pt_geo_plot(pd_gdf): """Create a geo plot for testing""" fig, ax = plt.subplots() + size = 0 + point_symb = {"Tree": "green", "Bush": "brown"} - pd_gdf.plot(ax=ax) - ax.set_title("My Plot Title", fontsize=30) - ax.set_xlabel("x label") - ax.set_ylabel("y label") + for ctype, points in pd_gdf.groupby("attr"): + color = point_symb[ctype] + label = ctype + size += 100 + points.plot(color=color, ax=ax, label=label, markersize=size) - axis = plt.gca() + ax.legend(title="Legend", loc=(1.1, 0.1)) - return PlotTester(axis) + return PlotTester(ax) diff --git a/matplotcheck/tests/test_lines.py b/matplotcheck/tests/test_lines.py new file mode 100644 index 00000000..14d68c12 --- /dev/null +++ b/matplotcheck/tests/test_lines.py @@ -0,0 +1,171 @@ +"""Tests for the vector module""" +import matplotlib +import matplotlib.pyplot as plt +import pytest +import geopandas as gpd +from shapely.geometry import LineString + +from matplotcheck.vector import VectorTester + +matplotlib.use("Agg") + + +@pytest.fixture +def multi_line_gdf(two_line_gdf): + """ Create a multi-line GeoDataFrame. + This has one multi line and another regular line. + """ + # Create a single and multi line object + multiline_feat = two_line_gdf.unary_union + linec = LineString([(2, 1), (3, 1), (4, 1), (5, 2)]) + out_df = gpd.GeoDataFrame( + geometry=gpd.GeoSeries([multiline_feat, linec]), crs="epsg:4326", + ) + out_df["attr"] = ["road", "stream"] + return out_df + + +@pytest.fixture +def mixed_type_geo_plot(pd_gdf, multi_line_gdf): + """Create a point plot for testing""" + _, ax = plt.subplots() + + pd_gdf.plot(ax=ax) + multi_line_gdf.plot(ax=ax) + + return VectorTester(ax) + + +@pytest.fixture +def line_geo_plot(two_line_gdf): + """Create a line vector tester object.""" + _, ax = plt.subplots() + + two_line_gdf.plot(ax=ax) + + return VectorTester(ax) + + +@pytest.fixture +def multiline_geo_plot(multi_line_gdf): + """Create a multiline vector tester object.""" + _, ax = plt.subplots() + + multi_line_gdf.plot(ax=ax, column="attr") + + return VectorTester(ax) + + +@pytest.fixture +def multiline_geo_plot_bad(multi_line_gdf): + """Create a multiline vector tester object.""" + _, ax = plt.subplots() + + multi_line_gdf.plot(ax=ax) + + return VectorTester(ax) + + +def test_assert_line_geo(line_geo_plot, two_line_gdf): + """Test that lines are asserted correctly""" + line_geo_plot.assert_lines(two_line_gdf) + plt.close("all") + + +def test_assert_multiline_geo(multiline_geo_plot, multi_line_gdf): + """Test that multi lines are asserted correctly""" + multiline_geo_plot.assert_lines(multi_line_gdf) + plt.close("all") + + +def test_assert_line_geo_fail(line_geo_plot, multi_line_gdf): + """Test that lines fail correctly""" + with pytest.raises(AssertionError, match="Incorrect Line Data"): + line_geo_plot.assert_lines(multi_line_gdf) + plt.close("all") + + +def test_assert_multiline_geo_fail(multiline_geo_plot, two_line_gdf): + """Test that multi lines fail correctly""" + with pytest.raises(AssertionError, match="Incorrect Line Data"): + multiline_geo_plot.assert_lines(two_line_gdf) + plt.close("all") + + +def test_assert_line_fails_list(line_geo_plot): + """Test that assert_lines fails when passed a list""" + linelist = [ + [(1, 1), (2, 2), (3, 2), (5, 3)], + [(3, 4), (5, 7), (12, 2), (10, 5), (9, 7.5)], + ] + with pytest.raises(ValueError, match="lines_expected is not expected ty"): + line_geo_plot.assert_lines(linelist) + plt.close("all") + + +def test_assert_line_geo_passed_nothing(line_geo_plot): + """Test that assertion passes when passed None""" + line_geo_plot.assert_lines(None) + plt.close("all") + + +def test_get_lines_geometry(line_geo_plot): + """Test that get_lines returns the proper values""" + lines = [(LineString(i[0])) for i in line_geo_plot.get_lines().values] + geometries = gpd.GeoDataFrame(geometry=lines) + line_geo_plot.assert_lines(geometries) + plt.close("all") + + +def test_assert_lines_grouped_by_type(multiline_geo_plot, multi_line_gdf): + """Test that assert works for grouped line plots""" + multiline_geo_plot.assert_lines_grouped_by_type(multi_line_gdf, "attr") + plt.close("all") + + +def test_assert_lines_grouped_by_type_fail( + multiline_geo_plot_bad, multi_line_gdf +): + """Test that assert fails for incorrectly grouped line plots""" + with pytest.raises(AssertionError, match="Line attributes not accurate "): + multiline_geo_plot_bad.assert_lines_grouped_by_type( + multi_line_gdf, "attr" + ) + plt.close("all") + + +def test_assert_lines_grouped_by_type_passes_with_none(multiline_geo_plot): + """Test that assert passes if nothing is passed into it""" + multiline_geo_plot.assert_lines_grouped_by_type(None, None) + plt.close("all") + + +def test_assert_lines_grouped_by_type_fails_non_gdf( + multiline_geo_plot, multi_line_gdf +): + """Test that assert fails if a list is passed into it""" + with pytest.raises(ValueError, match="lines_expected is not of expected "): + multiline_geo_plot.assert_lines_grouped_by_type( + multi_line_gdf.to_numpy(), "attr" + ) + plt.close("all") + + +def test_mixed_type_passes(mixed_type_geo_plot, pd_gdf): + """Tests that points passes with a mixed type plot""" + mixed_type_geo_plot.assert_points(pd_gdf) + plt.close("all") + + +def test_get_lines_by_collection(multiline_geo_plot): + """Test that get_lines_by_collection returns the correct values""" + lines_list = [ + [ + [(1, 1), (2, 2), (3, 2), (5, 3)], + [(3, 4), (5, 7), (12, 2), (10, 5), (9, 7.5)], + [(2, 1), (3, 1), (4, 1), (5, 2)], + ] + ] + sorted_lines_list = sorted([sorted(l) for l in lines_list]) + assert sorted_lines_list == multiline_geo_plot.get_lines_by_collection() + plt.close("all") diff --git a/matplotcheck/tests/test_points.py b/matplotcheck/tests/test_points.py new file mode 100644 index 00000000..f2ead5f7 --- /dev/null +++ b/matplotcheck/tests/test_points.py @@ -0,0 +1,161 @@ +"""Tests for the vector module""" +import numpy as np +import matplotlib +import matplotlib.pyplot as plt +import pytest +import geopandas as gpd + +from matplotcheck.vector import VectorTester + +matplotlib.use("Agg") + + +@pytest.fixture +def bad_pd_gdf(pd_gdf): + """Create a point geodataframe with slightly wrong values for testing""" + return gpd.GeoDataFrame( + geometry=gpd.points_from_xy( + pd_gdf.geometry.x + 1, pd_gdf.geometry.y + 1 + ) + ) + + +@pytest.fixture +def origin_pt_gdf(pd_gdf): + """Create a point geodataframe to test assert_points when a point at the + origin of the plot (0, 0) is present in the dataframe. This checks + for a specific bug fix that was added to the assert_points function.""" + origin_pt_gdf = pd_gdf.append( + gpd.GeoDataFrame(geometry=gpd.points_from_xy([0], [0])) + ) + origin_pt_gdf.reset_index(inplace=True, drop=True) + return origin_pt_gdf + + +@pytest.fixture +def pt_geo_plot(pd_gdf): + """Create a geo plot for testing""" + _, ax = plt.subplots() + size = 0 + point_symb = {"Tree": "green", "Bush": "brown"} + + for ctype, points in pd_gdf.groupby("attr"): + color = point_symb[ctype] + label = ctype + size += 100 + points.plot(color=color, ax=ax, label=label, markersize=size) + + ax.legend(title="Legend", loc=(1.1, 0.1)) + + return VectorTester(ax) + + +@pytest.fixture +def pt_geo_plot_bad(pd_gdf): + """Create a geo plot for testing""" + _, ax = plt.subplots() + + pd_gdf.plot(ax=ax) + + return VectorTester(ax) + + +@pytest.fixture +def pt_geo_plot_origin(origin_pt_gdf, two_line_gdf): + """Create a point plot for testing assert_points with a point at the + origin""" + _, ax = plt.subplots() + + origin_pt_gdf.plot(ax=ax) + + two_line_gdf.plot(ax=ax) + + return VectorTester(ax) + + +def test_points_sorted_by_markersize_pass(pt_geo_plot, pd_gdf): + """Tests that points are plotted as different sizes based on an attribute + value passes""" + pt_geo_plot.assert_collection_sorted_by_markersize(pd_gdf, "attr") + plt.close("all") + + +def test_points_sorted_by_markersize_fail(pt_geo_plot_bad, pd_gdf): + """Tests that points are plotted as different sizes based on an attribute + value fails""" + with pytest.raises(AssertionError, match="Markersize not based on"): + pt_geo_plot_bad.assert_collection_sorted_by_markersize(pd_gdf, "attr") + plt.close("all") + + +def test_points_grouped_by_type(pt_geo_plot, pd_gdf): + """Tests that points grouped by type passes""" + pt_geo_plot.assert_points_grouped_by_type(pd_gdf, "attr") + plt.close("all") + + +def test_points_grouped_by_type_fail(pt_geo_plot_bad, pd_gdf): + """Tests that points grouped by type passes""" + with pytest.raises(AssertionError, match="Point attributes not accurate"): + pt_geo_plot_bad.assert_points_grouped_by_type(pd_gdf, "attr") + plt.close("all") + + +def test_point_geometry_pass(pt_geo_plot, pd_gdf): + """Check that the point geometry test recognizes correct points.""" + pt_geo_plot.assert_points(points_expected=pd_gdf) + plt.close("all") + + +def test_point_geometry_fail(pt_geo_plot, bad_pd_gdf): + """Check that the point geometry test recognizes incorrect points.""" + with pytest.raises(AssertionError, match="Incorrect Point Data"): + pt_geo_plot.assert_points(points_expected=bad_pd_gdf) + plt.close("all") + + +def test_assert_point_fails_list(pt_geo_plot, pd_gdf): + """ + Check that the point geometry test fails anything that's not a + GeoDataFrame + """ + list_geo = [list(pd_gdf.geometry.x), list(pd_gdf.geometry.y)] + with pytest.raises(ValueError, match="points_expected is not expected"): + pt_geo_plot.assert_points(points_expected=list_geo) + plt.close("all") + + +def test_get_points(pt_geo_plot, pd_gdf): + """Tests that get_points returns correct values""" + xy_values = pt_geo_plot.get_points() + assert list(sorted(xy_values.x)) == sorted(list(pd_gdf.geometry.x)) + assert list(sorted(xy_values.y)) == sorted(list(pd_gdf.geometry.y)) + plt.close("all") + + +def test_assert_points_custom_message(pt_geo_plot, bad_pd_gdf): + """Tests that a custom error message is passed.""" + message = "Test message" + with pytest.raises(AssertionError, match="Test message"): + pt_geo_plot.assert_points(points_expected=bad_pd_gdf, m=message) + plt.close("all") + + +def test_wrong_length_points_expected(pt_geo_plot, pd_gdf, bad_pd_gdf): + """Tests that error is thrown for incorrect length of a gdf""" + with pytest.raises(AssertionError, match="points_expected's length does "): + pt_geo_plot.assert_points(bad_pd_gdf.append(pd_gdf), "attr") + plt.close("all") + + +def test_convert_length_function_error(pt_geo_plot): + """Test that the convert length function throws an error when given + incorrect inputs""" + with pytest.raises(ValueError, match="Input array length is not: 1 or 9"): + pt_geo_plot._convert_length(np.array([1, 2, 3, 4]), 9) + + +def test_point_gdf_with_point_at_origin(pt_geo_plot_origin, origin_pt_gdf): + """Test that assert_points works when there's a point at the origin in the + gdf""" + pt_geo_plot_origin.assert_points(origin_pt_gdf) diff --git a/matplotcheck/tests/test_vector.py b/matplotcheck/tests/test_polygons.py similarity index 56% rename from matplotcheck/tests/test_vector.py rename to matplotcheck/tests/test_polygons.py index 952df150..d626de3a 100644 --- a/matplotcheck/tests/test_vector.py +++ b/matplotcheck/tests/test_polygons.py @@ -1,9 +1,32 @@ """Tests for the vector module""" -import pytest +import matplotlib import matplotlib.pyplot as plt +import pytest import geopandas as gpd +from shapely.geometry import Polygon + from matplotcheck.vector import VectorTester +matplotlib.use("Agg") + + +@pytest.fixture +def multi_polygon_gdf(basic_polygon): + """ + A GeoDataFrame containing the basic polygon geometry. + Returns + ------- + GeoDataFrame containing the basic_polygon polygon. + """ + poly_a = Polygon([(3, 5), (2, 3.25), (5.25, 6), (2.25, 2), (2, 2)]) + gdf = gpd.GeoDataFrame( + [1, 2], geometry=[poly_a, basic_polygon], crs="epsg:4326", + ) + multi_gdf = gpd.GeoDataFrame( + geometry=gpd.GeoSeries(gdf.unary_union), crs="epsg:4326" + ) + return multi_gdf + @pytest.fixture def poly_geo_plot(basic_polygon_gdf): @@ -11,38 +34,18 @@ def poly_geo_plot(basic_polygon_gdf): _, ax = plt.subplots() basic_polygon_gdf.plot(ax=ax) - ax.set_title("My Plot Title", fontsize=30) - ax.set_xlabel("x label") - ax.set_ylabel("y label") - axis = plt.gca() - - return VectorTester(axis) + return VectorTester(ax) @pytest.fixture -def point_geo_plot(pd_gdf): - """Create a point plot for testing""" +def multi_poly_geo_plot(multi_polygon_gdf): + """Create a mutlipolygon vector tester object.""" _, ax = plt.subplots() - pd_gdf.plot(ax=ax) - ax.set_title("My Plot Title", fontsize=30) - ax.set_xlabel("x label") - ax.set_ylabel("y label") - - axis = plt.gca() + multi_polygon_gdf.plot(ax=ax) - return VectorTester(axis) - - -@pytest.fixture -def bad_pd_gdf(pd_gdf): - """Create a point geodataframe with slightly wrong values for testing""" - return gpd.GeoDataFrame( - geometry=gpd.points_from_xy( - pd_gdf.geometry.x + 1, pd_gdf.geometry.y + 1 - ) - ) + return VectorTester(ax) def test_list_of_polygons_check(poly_geo_plot, basic_polygon): @@ -50,34 +53,34 @@ def test_list_of_polygons_check(poly_geo_plot, basic_polygon): x, y = basic_polygon.exterior.coords.xy poly_list = [list(zip(x, y))] poly_geo_plot.assert_polygons(poly_list) - plt.close() + plt.close("all") def test_polygon_geodataframe_check(poly_geo_plot, basic_polygon_gdf): """Check that the polygon assert works with a polygon geodataframe""" poly_geo_plot.assert_polygons(basic_polygon_gdf) - plt.close() + plt.close("all") def test_empty_list_polygon_check(poly_geo_plot): """Check that the polygon assert fails an empty list.""" with pytest.raises(ValueError, match="Empty list or GeoDataFrame "): poly_geo_plot.assert_polygons([]) - plt.close() + plt.close("all") def test_empty_list_entry_polygon_check(poly_geo_plot): """Check that the polygon assert fails a list with an empty entry.""" with pytest.raises(ValueError, match="Empty list or GeoDataFrame "): poly_geo_plot.assert_polygons([[]]) - plt.close() + plt.close("all") def test_empty_gdf_polygon_check(poly_geo_plot): """Check that the polygon assert fails an empty GeoDataFrame.""" with pytest.raises(ValueError, match="Empty list or GeoDataFrame "): poly_geo_plot.assert_polygons(gpd.GeoDataFrame([])) - plt.close() + plt.close("all") def test_polygon_dec_check(poly_geo_plot, basic_polygon): @@ -88,7 +91,7 @@ def test_polygon_dec_check(poly_geo_plot, basic_polygon): x, y = basic_polygon.exterior.coords.xy poly_list = [[(x[0] + 0.1, x[1]) for x in list(zip(x, y))]] poly_geo_plot.assert_polygons(poly_list, dec=1) - plt.close() + plt.close("all") def test_polygon_dec_check_fail(poly_geo_plot, basic_polygon): @@ -100,7 +103,7 @@ def test_polygon_dec_check_fail(poly_geo_plot, basic_polygon): x, y = basic_polygon.exterior.coords.xy poly_list = [(x[0] + 0.5, x[1]) for x in list(zip(x, y))] poly_geo_plot.assert_polygons(poly_list, dec=1) - plt.close() + plt.close("all") def test_polygon_custom_fail_message(poly_geo_plot, basic_polygon): @@ -109,39 +112,10 @@ def test_polygon_custom_fail_message(poly_geo_plot, basic_polygon): x, y = basic_polygon.exterior.coords.xy poly_list = [(x[0] + 0.5, x[1]) for x in list(zip(x, y))] poly_geo_plot.assert_polygons(poly_list, m="Test Message") - plt.close() - - -def test_point_geometry_pass(point_geo_plot, pd_gdf): - """Check that the point geometry test recognizes correct points.""" - point_geo_plot.assert_points(points_expected=pd_gdf) - - -def test_point_geometry_fail(point_geo_plot, bad_pd_gdf): - """Check that the point geometry test recognizes incorrect points.""" - with pytest.raises(AssertionError, match="Incorrect Point Data"): - point_geo_plot.assert_points(points_expected=bad_pd_gdf) - - -def test_assert_point_fails_list(point_geo_plot, pd_gdf): - """ - Check that the point geometry test fails anything that's not a - GeoDataFrame - """ - list_geo = [list(pd_gdf.geometry.x), list(pd_gdf.geometry.y)] - with pytest.raises(ValueError, match="points_expected is not expected"): - point_geo_plot.assert_points(points_expected=list_geo) - - -def test_get_points(point_geo_plot, pd_gdf): - """Tests that get_points returns correct values""" - xy_values = point_geo_plot.get_points() - assert list(sorted(xy_values.x)) == sorted(list(pd_gdf.geometry.x)) - assert list(sorted(xy_values.y)) == sorted(list(pd_gdf.geometry.y)) + plt.close("all") -def test_assert_points_custom_message(point_geo_plot, bad_pd_gdf): - """Tests that a custom error message is passed.""" - message = "Test message" - with pytest.raises(AssertionError, match="Test message"): - point_geo_plot.assert_points(points_expected=bad_pd_gdf, m=message) +def test_multi_polygon_pass(multi_poly_geo_plot, multi_polygon_gdf): + """Check a multipolygon passes""" + multi_poly_geo_plot.assert_polygons(multi_polygon_gdf) + plt.close("all") From 0f2fc16e8d3b3fbd01ce7f27ab01936a0eb7f8f6 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Tue, 24 Mar 2020 16:37:57 -0600 Subject: [PATCH 23/53] Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser --- CHANGELOG.md | 1 + matplotcheck/tests/test_autograde.py | 84 ++++++++++++++++++++++++++++ matplotcheck/tests/test_base_data.py | 21 ++++--- 3 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 matplotcheck/tests/test_autograde.py diff --git a/CHANGELOG.md b/CHANGELOG.md index af32256c..f5c2db8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* Created tests for the autograde module (@nkorinek, #105) * Created tests for the vector module (@nkorinek, #209) * Created functions to test point geometries in VectorTester (@nkorinek, #176) * made `assert_string_contains()` accept correct strings with spaces in them (@nkorinek, #182) diff --git a/matplotcheck/tests/test_autograde.py b/matplotcheck/tests/test_autograde.py new file mode 100644 index 00000000..353ca776 --- /dev/null +++ b/matplotcheck/tests/test_autograde.py @@ -0,0 +1,84 @@ +"""Tests for the autograder module""" + +import matplotcheck.autograde as ag + + +def test_autograde_runs_assert_pass(pt_scatter_plt): + """Test that a normal test is run with autograder and passes""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["Plot"], + ) + assert result["description"] == "assert_title_contains" + assert result["pass"] + assert result["message"] == "default correct" + assert result["points"] == 2 + + +def test_autograde_runs_assert_fail(pt_scatter_plt): + """Test that a when a test fails, an assertion error is returned""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["NotAWord"], + ) + assert result["description"] == "assert_title_contains" + assert not result["pass"] + assert result["message"] == "default error" + assert result["points"] == 0 + assert isinstance(result["traceback"], AssertionError) + + +def test_autograde_runs_assert_pass_custom_message(pt_scatter_plt): + """Test that when a custom message is provided, it is correctly returned + to the user.""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["Plot"], + correct_message="This is correct!", + ) + assert result["message"] == "This is correct!" + + +def test_autograde_runs_assert_fail_custom_message(pt_scatter_plt): + """Test that a custom message for a failed test gets returned""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["NotAWord"], + error_message="This is wrong!", + ) + assert result["message"] == "This is wrong!" + + +def test_output_results_pass(pt_scatter_plt, capsys): + """Test that the correct number of points are returned when a test + passes""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["Plot"], + ) + assert 2 == ag.output_results([result]) + assert ( + "Results for test 'assert_title_contains':\n" + " Pass! default correct (2 points)\n" == capsys.readouterr().out + ) + + +def test_output_results_fail(pt_scatter_plt, capsys): + """Test that the correct points are returned when a test fails""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["NotAWord"], + ) + assert 0 == ag.output_results([result]) + assert ( + "Results for test 'assert_title_contains':\n" + " Fail! default error (0 points)\n" + " Traceback: Title does not contain expected string: NotAWord\n" + == capsys.readouterr().out + ) diff --git a/matplotcheck/tests/test_base_data.py b/matplotcheck/tests/test_base_data.py index fdcdade9..a6416806 100644 --- a/matplotcheck/tests/test_base_data.py +++ b/matplotcheck/tests/test_base_data.py @@ -52,11 +52,14 @@ def pt_monthly_data_numeric(pd_df_monthly_data_numeric): @pytest.fixture def pt_hist(): - dataframe_a = pd.DataFrame({"A": np.exp(np.arange(1, 2, 0.01))}) + df_a = pd.DataFrame({"A": np.exp(np.arange(1, 2, 0.01))}) bins = [2, 3, 4, 5, 6, 7, 8] - plt.hist(dataframe_a["A"], bins=bins, alpha=0.5, color="seagreen") - axis = plt.gca() - return PlotTester(axis) + + _, ax = plt.subplots() + + ax.hist(df_a["A"], bins=bins, alpha=0.5, color="seagreen") + + return PlotTester(ax) @pytest.fixture @@ -67,10 +70,12 @@ def pt_hist_overlapping(): ) bins = [2, 3, 4, 5, 6, 7, 8] - plt.hist(dataframe_a["A"], bins=bins, alpha=0.5, color="seagreen") - plt.hist(dataframe_b["B"], bins=bins, alpha=0.5, color="coral") - axis = plt.gca() - return PlotTester(axis) + _, ax = plt.subplots() + + ax.hist(dataframe_a["A"], bins=bins, alpha=0.5, color="seagreen") + ax.hist(dataframe_b["B"], bins=bins, alpha=0.5, color="coral") + + return PlotTester(ax) """DATACHECK TESTS""" From 27251c3e7101a4afe78433971e828dc157f99dc2 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Thu, 26 Mar 2020 10:09:42 -0600 Subject: [PATCH 24/53] Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser --- CHANGELOG.md | 1 + examples/plot_histogram_testing.py | 198 -------------------- examples/plot_test_histogram.py | 261 +++++++++++++++++++++++++++ matplotcheck/base.py | 55 +++++- matplotcheck/tests/test_base_data.py | 77 ++++++++ 5 files changed, 393 insertions(+), 199 deletions(-) delete mode 100644 examples/plot_histogram_testing.py create mode 100644 examples/plot_test_histogram.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f5c2db8a..42bec8d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* Added functions to get and assert the midpoint values of bins in a histogram (@nkorinek, #184) * Created tests for the autograde module (@nkorinek, #105) * Created tests for the vector module (@nkorinek, #209) * Created functions to test point geometries in VectorTester (@nkorinek, #176) diff --git a/examples/plot_histogram_testing.py b/examples/plot_histogram_testing.py deleted file mode 100644 index 454e406a..00000000 --- a/examples/plot_histogram_testing.py +++ /dev/null @@ -1,198 +0,0 @@ -""" -Testing Histograms -================== - -These are some examples of using the basic functionality of MatPlotCheck -to test histogram plots in Python. - -""" - -################################################################################ -# Setup -# ----- -# You will start by importing the required packages and plotting a histogram. - -import matplotlib.pyplot as plt -import matplotcheck.base as mpc -import matplotcheck.notebook as nb -import numpy as np - - -data = np.exp(np.arange(0, 5, 0.01)) - -fig, ax = plt.subplots() -ax.hist(data, bins=5, color="gold") - -plot_1_hold = nb.convert_axes(plt, which_axes="current") - -################################################################################ -# Testing the Histogram -# --------------------- -# Now you can make a PlotTester object and test the histogram. We'll test both -# the number of bins and the values of those bins. - -############################################################################### -# -# .. note:: -# Throughout this vignette, the term `bin value` is used to describe the -# number of datapoints that fall within a bin. In other words, a bin's value -# is equal to the height of the bar correspondign to that bin. For example, -# the value of the first bin in the above histogram is 341. Note that the -# height of the first bar is also 341. - -plot_tester_1 = mpc.PlotTester(plot_1_hold) - -plot_tester_1.assert_num_bins(5) - -expected_bin_values = [341, 68, 40, 28, 23] -plot_tester_1.assert_bin_values(expected_bin_values) -################################################################################ -# And we can also run some tests that will fail. -# -try: - plot_tester_1.assert_num_bins(6) -except AssertionError as message: - print("AssertionError:", message) - -try: - plot_tester_1.assert_bin_values([1, 4, 1, 3, 4]) -except AssertionError as message: - print("AssertionError:", message) - -################################################################################ -# Determining Expected Values -# --------------------------- -# With a histogram, you may not know the values you expect to find for each bin -# before you begin testing. (More simply, you probably know how you expect a -# histogram to look and how you expect it to be made. But you might not know -# the exact height of each bar in that histogram.) In this case, matplotcheck -# provides a method for extracting the bin values from an existing histogram: -# ``get_bin_values()``. -# -# To use this, you can create a histogram however you think it should be created -# (this will be called the expected histogram) and use it as a reference. Then -# you can extract the bin values from it (called the expected values). These -# expected values can be used to test whether another histogram (e.g. a -# student's histogram) also contains the expected values. -# -# For this example, you will start by creating a histogram that will serve as -# your expected histogram, and then extracting the expected values from it. To -# do this, you need to create a `PlotTester` object from it and use the -# ``get_bin_values()`` method. - -expected_data = np.sin(np.arange(0, 2 * np.pi, np.pi / 50)) - -fig, ax = plt.subplots() -ax.hist(expected_data, bins=8, color="gold") - -expected_plot_hold = nb.convert_axes(plt, which_axes="current") -plot_tester_expected = mpc.PlotTester(expected_plot_hold) -print(plot_tester_expected.get_bin_values()) - -################################################################################ -# Great! Now you know the bin values that you expect to see when you test a -# plot. -# -# Now you can create another histogram (our testing histogram) and check -# whether it matches the expected histogram (i.e. check wether its bin values -# match the expected bin values). - -# Create and plot the testing histogram -testing_data = np.sin(np.arange(2 * np.pi, 4 * np.pi, np.pi / 50)) -fig, ax = plt.subplots() -ax.hist(testing_data, bins=8, color="orange") -testing_plot_hold = nb.convert_axes(plt, which_axes="current") - -# Testing the histogram against the expected bin values -plot_tester_testing = mpc.PlotTester(testing_plot_hold) -plot_tester_testing.assert_bin_values( - [23.0, 10.0, 8.0, 9.0, 9.0, 8.0, 10.0, 23.0] -) - -################################################################################ -# Since ``assert_bin_values()`` did not raise an ``AssertionError``, you know -# that the test passed. This means the testing histogram had the same bin values -# as the expected histogram. - -############################################################################### -# -# .. note:: -# In this example, you have created the expected histogram and the testing -# histogram in the same file. Normally you would create the expected histogram -# in one location, extract the expected bin values from it, and use those to -# test the testing histogram in another location (e.g. within a student's -# homework assignment.) - - -################################################################################ -# Testing with Tolerances -# ----------------------- -# In some cases, you might want to run a test that doesn't require the bin -# values to match exactly. For this, you can use the ``tolerance`` argument of -# the ``assert_bin_values()`` method. -# -# You will start by making two histograms with slightly different data and -# storing the plots with ``nb.convert_axes()``. The gold plot will serve as the -# expected plot, and the orange plot will serve as the testing plot. - -expected_data = 0.1 * np.power(np.arange(0, 10, 0.1), 2) -bins = np.arange(0, 10, 1) - -fig1, ax2 = plt.subplots() -ax2.hist(expected_data, color="gold", bins=bins) - -expected_plot_2_hold = nb.convert_axes(plt, which_axes="current") - -################################################################################ - -test_data = 0.1995 * np.power(np.arange(0, 10, 0.1), 1.7) - -fig2, ax2 = plt.subplots() -ax2.hist(test_data, color="orange", bins=bins) - -testing_plot_2_hold = nb.convert_axes(plt, which_axes="current") - -################################################################################ -# Now you will create a `PlotTester` object for each plot. This allows you to -# extract the expected bin values from the expected plot and allows you to -# test the testing plot. - -plot_tester_expected_2 = mpc.PlotTester(expected_plot_2_hold) -bins_expected_2 = plot_tester_expected_2.get_bin_values() - -plot_tester_testing_2 = mpc.PlotTester(testing_plot_2_hold) - -################################################################################ -# You'll notice that the test (orange) plot differs somewhat from the -# expected (gold) plot, but still has a similar shape and similar bin -# values. -# -# If you test it without the ``tolerance`` argument, the assertion will fail. - -try: - plot_tester_testing_2.assert_bin_values(bins_expected_2) -except AssertionError as message: - print("AssertionError:", message) - -################################################################################ -# However, if you set a tolerance, the assertion can pass. Here you will test it -# with ``tolerance=0.2``. - -plot_tester_testing_2.assert_bin_values(bins_expected_2, tolerance=0.2) - -################################################################################ -# Because no ``AssertionError`` is raised, you know that the test passed with -# a tolerance of 0.2. However, the test will not pass with a tolerance that is -# too small; the test will fail with ``tolerance=0.1``. - -try: - plot_tester_testing_2.assert_bin_values(bins_expected_2, tolerance=0.1) -except AssertionError as message: - print("AssertionError:", message) - -############################################################################### -# -# .. note:: -# When using tolerances, the ``tolerance`` argument is taken as a relative -# tolerance. For more information, see the documentation for the -# ``base.assert_bin_heights()`` method. diff --git a/examples/plot_test_histogram.py b/examples/plot_test_histogram.py new file mode 100644 index 00000000..5db81094 --- /dev/null +++ b/examples/plot_test_histogram.py @@ -0,0 +1,261 @@ +""" +Test Histogram Plots with Matplotcheck +====================================== + +Below you will find some examples of how to use MatPlotCheck +to test histogram plots created with Matplotlib in Python. + +""" + +############################################################################### +# Setup +# ----- +# You will start by importing the required packages and plotting a histogram. +# Once you have created your plot, you will created a Matplotcheck +# ``PlotTester`` object by providing the Matplotlib axis object to +# ``PlotTester``. + +import matplotlib.pyplot as plt +import matplotcheck.base as mpc +import numpy as np + + +data = np.exp(np.arange(0, 5, 0.01)) + +fig, ax = plt.subplots() +ax.hist(data, bins=5, color="gold") + +# Create a Matplotcheck PlotTester object +plot_tester_1 = mpc.PlotTester(ax) + +############################################################################### +# Test a Histogram Plot +# --------------------- +# Once you have created a PlotTester object, you are ready to test various +# parts of your plot. Below, you test both +# the number of bins and the values associated with those bins. + +############################################################################### +# +# .. note:: +# Throughout this vignette, the term `bin value` is used to describe the +# number of datapoints that fall within a bin. In other words, a bin's value +# is equal to the height of the bar corresponding to that bin. For example, +# the value of the first bin in the above histogram is 341. Note that the +# height of the first bar is also 341. + +# Test that the histogram plot has 5 bins +plot_tester_1.assert_num_bins(5) + +# Test that the histogram bin values (the height of each bin) is as expected +expected_bin_values = [341, 68, 40, 28, 23] +plot_tester_1.assert_bin_values(expected_bin_values) + +############################################################################### +# And you can also run some tests that will fail. +# +try: + plot_tester_1.assert_num_bins(6) +except AssertionError as message: + print("AssertionError:", message) + +try: + plot_tester_1.assert_bin_values([1, 4, 1, 3, 4]) +except AssertionError as message: + print("AssertionError:", message) + +############################################################################### +# Determining Expected Values +# --------------------------- +# You can use the MatPlotCheck ``get_bin_values()`` method to extract the bin +# values that are expected for a plot. This is helpful if you are using a tool +# like nbgrader to create the the expected plot outcomes in a homework +# assignment. +# +# To extract bin values from an expected plot you first create the expected +# histogram plot that you will use to grade your assignment (or htat you expect +# as an outcome from a test). Next, you create a PlotTester object from that +# plot. Finally, you call the ``get_bin_values()`` method to grab the expected +# bin values from that plot. +# +# The steps outlined above are implemented below. + +expected_data = np.sin(np.arange(0, 2 * np.pi, np.pi / 50)) + +# Create the expected plot +fig, ax = plt.subplots() +ax.hist(expected_data, bins=8, color="gold") + +# Create a Matplotcheck PlotTester object from the axis object +plot_tester_expected = mpc.PlotTester(ax) +# Get bin values from the expected plot +print(plot_tester_expected.get_bin_values()) + +############################################################################### +# This example assumes that you are creating tests for a student +# assignment. Once you have created the PlotTester object for the expected +# plot (this is the answer to the assignment that you expect the student to +# come to), +# you can then test the student plot to see if it matches expected bin values. +# Below another plot is created that represents the student submitted plot. + +# Create and plot the student submitted histogram +data = np.sin(np.arange(2 * np.pi, 4 * np.pi, np.pi / 50)) +fig, ax = plt.subplots() +ax.hist(data, bins=8, color="orange") + +# Test the student submitted histogram bin values against the expected +# bin values (the correct answer to the assigned plot) +plot_tester_testing = mpc.PlotTester(ax) +plot_tester_testing.assert_bin_values( + [23.0, 10.0, 8.0, 9.0, 9.0, 8.0, 10.0, 23.0] +) + +############################################################################### +# Above, ``assert_bin_values()`` did not raise an ``AssertionError``. This +# means that the test passed and the student submitted plot has the correct +# histogram bins. +# + +############################################################################### +# +# .. note:: +# In this example, you created the expected histogram (the homework answer) +# and the student submitted histogram in the same file. +# + +############################################################################### +# Testing with Tolerances +# ----------------------- +# In some cases, you might want to run a test that doesn't require the bin +# values to match exactly. For example, it might be ok if the values are +# a few tenths off. To allow for some "wiggle room" in the expected answer, +# you can use the ``tolerance`` parameter of the ``assert_bin_values()`` +# method. +# +# You will start by making two histograms with slightly different data and +# storing the plots with ``nb.convert_axes()``. The gold plot will serve as the +# expected plot, and the orange plot will serve as the testing plot. +# +# You will then create a `PlotTester` object for each plot. This allows you to +# extract the expected bin values from the expected plot and use those value to +# test the testing plot. + +expected_data = 0.1 * np.power(np.arange(0, 10, 0.1), 2) +bins = np.arange(0, 10, 1) + +fig1, ax1 = plt.subplots() +ax1.hist(expected_data, color="gold", bins=bins) + +# Create plot tester object +plot_tester_expected_1 = mpc.PlotTester(ax1) +# Get expected bin values +bins_expected_1 = plot_tester_expected_1.get_bin_values() + +############################################################################### + +test_data = 0.1995 * np.power(np.arange(0, 10, 0.1), 1.7) +fig2, ax2 = plt.subplots() +ax2.hist(test_data, color="orange", bins=bins) +# Create plot tester object +plot_tester_testing_2 = mpc.PlotTester(ax2) + +############################################################################### +# You'll notice that the test (orange) plot differs somewhat from the +# expected (gold) plot, but still has a similar shape and similar bin +# values. +# +# If you test it without the ``tolerance`` argument, the assertion will fail. + +try: + plot_tester_testing_2.assert_bin_values(bins_expected_1) +except AssertionError as message: + print("AssertionError:", message) + +############################################################################### +# However, if you set a tolerance, the assertion can pass. Here you will test +# it with ``tolerance=0.2``. + +plot_tester_testing_2.assert_bin_values(bins_expected_1, tolerance=0.2) + +############################################################################### +# Because no ``AssertionError`` is raised, you know that the test passed with +# a tolerance of 0.2. However, the test will not pass with a tolerance that is +# too small; the test will fail with ``tolerance=0.1``. + +try: + plot_tester_testing_2.assert_bin_values(bins_expected_1, tolerance=0.1) +except AssertionError as message: + print("AssertionError:", message) + +############################################################################### +# +# .. note:: +# When using tolerances, the ``tolerance`` argument is taken as a relative +# tolerance. For more information, see the documentation for the +# ``base.assert_bin_heights()`` method. + +############################################################################### +# Test Histogram Midpoints +# ------------------------ +# So far, you have tested the histogram values as well as the number of bins +# the histogram has. It may also be useful to test that the data bins cover +# the range of values that they were expected to. In order to do this, you can +# test the midpoints of each bin to ensure that the data covered by each +# bin is as expected. This is tested very similarly to the bins values. +# Simply provide ``assert_bin_midpoints()`` with a list of the expected +# midpoints, and it will assert if they are accurate or not. In order to obtain +# the midpoints in a PlotTester object, you can use ``get_bin_midpoints()``, +# much like ``get_bin_values()``. +# +# For this example, you will create a plot tester object from a histogram plot, +# the same way you did for the bin values example. + +fig, ax = plt.subplots() +ax.hist(test_data, bins=8, color="gold") + +# If you were running this in a notebook, the commented out line below would +# store the matplotlib object. However, in this example, you can just grab the +# axes object directly. + +# midpoints_plot_hold = nb.convert_axes(plt, which_axes="current") + +plot_tester_expected_3 = mpc.PlotTester(ax) +print(plot_tester_expected_3.get_bin_midpoints()) + +############################################################################### +# You got the values from the plot tester object! As you can see, the values +# that were collected are the midpoints for the values each histogram bin +# covers. Now you can test that they are asserted indeed correct with an +# assertion test. + +try: + plot_tester_expected_3.assert_bin_midpoints( + [-0.875, -0.625, -0.375, -0.125, 0.125, 0.375, 0.625, 0.875] + ) +except AssertionError as message: + print("AssertionError:", message) + +############################################################################### +# Here you can see that this will fail when given incorrect values. + +try: + plot_tester_expected_3.assert_bin_midpoints( + [-0.75, -0.5, -0.25, -0, 0.25, 0.5, 0.75, 1] + ) +except AssertionError as message: + print("AssertionError:", message) + +############################################################################### +# +# .. note:: +# Keep in mind this test is for the midpoints of the range that each bin +# covers. So if a bin covers all data that's in between 0 and 1, than the +# value given for that bin will be .5, not 0 or 1. + + +# .. note:: +# If you are working on tests for jupyter notebooks, you can call the +# line below to capture the student cell in a notebook. Then you can +# Use that object for testing. +# testing_plot_2_hold = nb.convert_axes(plt, which_axes="current"). diff --git a/matplotcheck/base.py b/matplotcheck/base.py index 29be8d9b..6bdcc12c 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -1206,10 +1206,22 @@ def get_bin_values(self): Int : The number of bins in the histogram""" - bin_values = self.get_xy(xtime=False)["y"].tolist() + bin_values = self.get_xy()["y"].tolist() return bin_values + def get_bin_midpoints(self): + """Returns the mid point value of each bin in a histogram + + Returns + ------- + Int : + The number of bins in the histogram""" + + bin_midpoints = self.get_xy()["x"].tolist() + + return bin_midpoints + def assert_bin_values( self, bin_values, @@ -1272,3 +1284,44 @@ def assert_bin_values( ) except AssertionError: raise AssertionError(message) + + def assert_bin_midpoints( + self, + bin_midpoints, + message="Did not find expected bin midpoints in plot", + ): + """ + Asserts that the middle values of histogram bins match `bin_midpoints`. + + Parameters + ---------- + bin_midpoints : list + A list of numbers representing the expected middles of bin values + covered by each consecutive bin (i.e. the midpoint of the bars in + the histogram). + message : string + The error message to be displayed if the bin mid point values do + not match `bin_midpoints` + + Raises + ------ + AssertionError + if the Values of histogram bins do not match `bin_midpoints` + """ + + plot_bin_midpoints = self.get_bin_midpoints() + + if not isinstance(bin_midpoints, list): + raise ValueError( + "Need to submit a list for expected bin midpoints." + ) + + if len(plot_bin_midpoints) != len(bin_midpoints): + raise ValueError("Bin midpoints lists lengths do no match.") + + try: + np.testing.assert_array_max_ulp( + np.array(plot_bin_midpoints), np.array(bin_midpoints) + ) + except AssertionError: + raise AssertionError(message) diff --git a/matplotcheck/tests/test_base_data.py b/matplotcheck/tests/test_base_data.py index a6416806..a9cea54d 100644 --- a/matplotcheck/tests/test_base_data.py +++ b/matplotcheck/tests/test_base_data.py @@ -375,3 +375,80 @@ def test_assert_bin_values_tolerance_fails(pt_hist_overlapping): pt_hist_overlapping.assert_bin_values(bin_values, tolerance=0.09) plt.close() + + +def test_assert_bin_midpoints_pass(pt_hist): + """Test that bin midpoints are correct""" + bins = [2.5, 3.5, 4.5, 5.5, 6.5, 7.5] + pt_hist.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_fail(pt_hist): + """Test that bin midpoints fail when incorrect""" + bins = [2, 3, 4, 5, 6, 7] + with pytest.raises(AssertionError, match="Did not find expected bin midp"): + pt_hist.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_fails_wrong_type(pt_hist): + """Test that bin midpoints fails when not handed a list""" + bins = (2.5, 3.5, 4.5, 5.5, 6.5, 7.5) + with pytest.raises(ValueError, match="Need to submit a list for expected"): + pt_hist.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_fails_wrong_length(pt_hist): + """Test that bin midpoints fails when not handed a list with the wrong + length""" + bins = [2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8] + with pytest.raises(ValueError, match="Bin midpoints lists lengths do no "): + pt_hist.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_fail_custom_message(pt_hist): + """Test that correct error message is thrown when bin midpoints fail""" + bins = [2, 3, 4, 5, 6, 7] + with pytest.raises(AssertionError, match="Test Message"): + pt_hist.assert_bin_midpoints(bins, message="Test Message") + + plt.close() + + +def test_assert_bin_midpoints_overlap_pass(pt_hist_overlapping): + """Test that bin midpoints are correct with overlapping histograms""" + bins = [2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5] + pt_hist_overlapping.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_overlap_fail(pt_hist_overlapping): + """Test that bin midpoints fail with overlapping histograms when + incorrect""" + bins = [2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7] + with pytest.raises( + AssertionError, match="Did not find expected bin midpo" + ): + pt_hist_overlapping.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_overlap_length_fail(pt_hist_overlapping): + """Test that bin midpoints fail with overlapping histograms when + incorrect length""" + bins = [2.5, 3.5, 4.5, 5.5, 6.5, 7.5] + with pytest.raises( + ValueError, match="Bin midpoints lists lengths do no match" + ): + pt_hist_overlapping.assert_bin_midpoints(bins) + + plt.close() From f3c384f60bb5d6b580d05d7c47090eb8e15aa274 Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Thu, 26 Mar 2020 18:14:54 +0200 Subject: [PATCH 25/53] Update setuptools from 46.1.1 to 46.1.3 (#231) --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index d6226b30..a9b66ad7 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -6,7 +6,7 @@ m2r==0.2.1 pytest-vcr==1.0.2 pytest-cov==2.7.1 pytest==5.4.1 -setuptools==46.1.1 +setuptools==46.1.3 pre-commit==1.20.0 pip==19.0.3 descartes==1.1.0 From 654b3d1c68a81ac632a3b5f2ee2d109daea16193 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Thu, 2 Apr 2020 10:28:34 -0600 Subject: [PATCH 26/53] Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser --- CHANGELOG.md | 1 + matplotcheck/base.py | 4 ++++ matplotcheck/tests/test_base_titles_captions.py | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42bec8d7..02f32683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* Made `assert_string_contains()` accept a string instead of a list (@nkorinek, #53) * Added functions to get and assert the midpoint values of bins in a histogram (@nkorinek, #184) * Created tests for the autograde module (@nkorinek, #105) * Created tests for the vector module (@nkorinek, #209) diff --git a/matplotcheck/base.py b/matplotcheck/base.py index 6bdcc12c..bef8cec2 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -123,6 +123,10 @@ def assert_string_contains( return string = string.lower().replace(" ", "") + + if isinstance(strings_expected, str): + strings_expected = [strings_expected] + for check in strings_expected: if isinstance(check, str): if not check.lower().replace(" ", "") in string: diff --git a/matplotcheck/tests/test_base_titles_captions.py b/matplotcheck/tests/test_base_titles_captions.py index 8fd65581..50c2ac96 100644 --- a/matplotcheck/tests/test_base_titles_captions.py +++ b/matplotcheck/tests/test_base_titles_captions.py @@ -54,6 +54,12 @@ def test_title_contains_axes_spaces(pt_line_plt): plt.close() +def test_title_contains_axes_string(pt_line_plt): + """Check title_contains for axes title as a string, not a list""" + pt_line_plt.assert_title_contains("My Plot Title", title_type="axes") + plt.close() + + def test_title_contains_axes_badtext(pt_line_plt): """Check title_contains fails when given bad text""" with pytest.raises( From 34e20da31daf162f7513f44860de86ec49f5a12c Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Mon, 20 Apr 2020 10:25:28 -0600 Subject: [PATCH 27/53] M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates --- CHANGELOG.md | 1 + CODE_OF_CONDUCT.md | 76 ---------------------------------------- CODE_OF_CONDUCT.rst | 76 ++++++++++++++++++++++++++++++++++++++++ dev-requirements.txt | 1 - docs/code-of-conduct.rst | 2 +- docs/conf.py | 6 ++-- 6 files changed, 80 insertions(+), 82 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md create mode 100644 CODE_OF_CONDUCT.rst diff --git a/CHANGELOG.md b/CHANGELOG.md index 02f32683..2b656515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* Removed the `m2r` package from MatPlotCheck (@nkorinek, #247) * Made `assert_string_contains()` accept a string instead of a list (@nkorinek, #53) * Added functions to get and assert the midpoint values of bins in a histogram (@nkorinek, #184) * Created tests for the autograde module (@nkorinek, #105) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index f0376b7e..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# MatPlotCheck Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at earth.lab@colorado.edu. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst new file mode 100644 index 00000000..8e94b9dd --- /dev/null +++ b/CODE_OF_CONDUCT.rst @@ -0,0 +1,76 @@ +MatPlotCheck Code of Conduct +============================ + +Our Pledge +---------- + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our +project and our community a harassment-free experience for everyone, +regardless of age, body size, disability, ethnicity, sex +characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +Our Standards +------------- + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual + attention or advances +- Trolling, insulting/derogatory comments, and personal or political + attacks +- Public or private harassment +- Publishing others’ private information, such as a physical or + electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +Our Responsibilities +-------------------- + +Project maintainers are responsible for clarifying the standards of +acceptable behavior and are expected to take appropriate and fair +corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, +or reject comments, commits, code, wiki edits, issues, and other +contributions that are not aligned to this Code of Conduct, or to ban +temporarily or permanently any contributor for other behaviors that they +deem inappropriate, threatening, offensive, or harmful. + +Scope +----- + +This Code of Conduct applies both within project spaces and in public +spaces when an individual is representing the project or its community. +Examples of representing a project or community include using an +official project e-mail address, posting via an official social media +account, or acting as an appointed representative at an online or +offline event. Representation of a project may be further defined and +clarified by project maintainers. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may +be reported by contacting the project team at earth.lab@colorado.edu. +All complaints will be reviewed and investigated and will result in a +response that is deemed necessary and appropriate to the circumstances. +The project team is obligated to maintain confidentiality with regard to +the reporter of an incident. Further details of specific enforcement +policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in +good faith may face temporary or permanent repercussions as determined +by other members of the project’s leaders diff --git a/dev-requirements.txt b/dev-requirements.txt index a9b66ad7..0ba2e5b0 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,7 +2,6 @@ bumpversion==0.5.3 sphinx_rtd_theme==0.4.3 sphinx_gallery==0.5.0 sphinx-autobuild==0.7.1 -m2r==0.2.1 pytest-vcr==1.0.2 pytest-cov==2.7.1 pytest==5.4.1 diff --git a/docs/code-of-conduct.rst b/docs/code-of-conduct.rst index 220a963d..96e0ba2f 100644 --- a/docs/code-of-conduct.rst +++ b/docs/code-of-conduct.rst @@ -1 +1 @@ -.. mdinclude:: ../CODE_OF_CONDUCT.md +.. include:: ../CODE_OF_CONDUCT.rst diff --git a/docs/conf.py b/docs/conf.py index 8b9fd2f9..130b0909 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -40,7 +40,6 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "m2r", "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.napoleon", @@ -65,9 +64,8 @@ # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # -# source_suffix = ['.rst', '.md'] -# "m2r" is needed to support markdown includes in sphinx -source_suffix = [".rst", ".md"] +# source_suffix = ['.rst'] +source_suffix = [".rst"] # The master toctree document. master_doc = "index" From 3f07d9b0cb13a987ff45d9bd7e469a7759a743af Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Tue, 11 Feb 2020 04:22:13 +0200 Subject: [PATCH 28/53] # This is a combination of 2 commits. # This is the 1st commit message: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update setuptools from 42.0.2 to 45.2.0 (#190) raster inherits from vector rather than base (#76) Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek update changelog for release Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Update setuptools from 45.2.0 to 46.0.0 (#215) Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Update pytest from 5.3.5 to 5.4.1 merge Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser Update setuptools from 46.1.1 to 46.1.3 (#231) Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek Get images function (#193) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser Co-authored-by: Leah Wasser Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser # This is the commit message #2: Update setuptools from 46.1.1 to 46.1.3 (#231) --- .pre-commit-config.yaml | 5 + .zenodo.json | 5 + CHANGELOG.md | 15 +- CODE_OF_CONDUCT.md | 76 --- CODE_OF_CONDUCT.rst | 76 +++ README.md | 25 +- dev-requirements.txt | 7 +- docs/code-of-conduct.rst | 2 +- docs/conf.py | 10 +- examples/plot_histogram_testing.py | 198 ------- examples/plot_test_histogram.py | 261 +++++++++ examples/plot_testing_basics.py | 4 +- matplotcheck/autograde.py | 7 +- matplotcheck/base.py | 184 +++++-- matplotcheck/cases.py | 279 ++++++---- matplotcheck/folium.py | 18 +- matplotcheck/notebook.py | 36 +- matplotcheck/raster.py | 77 ++- matplotcheck/tests/conftest.py | 117 ++++- matplotcheck/tests/test_autograde.py | 84 +++ matplotcheck/tests/test_base.py | 18 +- matplotcheck/tests/test_base_axis.py | 23 +- matplotcheck/tests/test_base_data.py | 101 +++- matplotcheck/tests/test_base_legends.py | 14 +- .../tests/test_base_titles_captions.py | 27 +- matplotcheck/tests/test_lines.py | 171 ++++++ matplotcheck/tests/test_points.py | 161 ++++++ matplotcheck/tests/test_polygons.py | 121 +++++ matplotcheck/tests/test_raster.py | 26 +- matplotcheck/tests/test_timeseries_module.py | 6 +- matplotcheck/timeseries.py | 38 +- matplotcheck/vector.py | 495 ++++++++++-------- setup.cfg | 2 +- setup.py | 2 +- tox.ini | 2 + 35 files changed, 1887 insertions(+), 806 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md create mode 100644 CODE_OF_CONDUCT.rst delete mode 100644 examples/plot_histogram_testing.py create mode 100644 examples/plot_test_histogram.py create mode 100644 matplotcheck/tests/test_autograde.py create mode 100644 matplotcheck/tests/test_lines.py create mode 100644 matplotcheck/tests/test_points.py create mode 100644 matplotcheck/tests/test_polygons.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 21fb8d75..7c98033a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,3 +4,8 @@ repos: hooks: - id: black language_version: python3.7 +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.7.7 + hooks: + - id: flake8 + language: python_venv diff --git a/.zenodo.json b/.zenodo.json index 2355ff48..63868a86 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -5,6 +5,11 @@ "name": "Wasser, Leah", "orcid": "0000-0002-8177-6550" }, + { + "affiliation": "Earth Lab, University of Colorado - Boulder", + "name": "Korinek, Nathan", + "orcid": "0000-0003-0859-7246" + }, { "name": "LaRocque, Ryan", "orcid": "0000-0003-2540-1428" diff --git a/CHANGELOG.md b/CHANGELOG.md index 046dccfa..2b656515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - ## [Unreleased] +* Removed the `m2r` package from MatPlotCheck (@nkorinek, #247) +* Made `assert_string_contains()` accept a string instead of a list (@nkorinek, #53) +* Added functions to get and assert the midpoint values of bins in a histogram (@nkorinek, #184) +* Created tests for the autograde module (@nkorinek, #105) +* Created tests for the vector module (@nkorinek, #209) +* Created functions to test point geometries in VectorTester (@nkorinek, #176) +* made `assert_string_contains()` accept correct strings with spaces in them (@nkorinek, #182) +* added contributors file and updated README to remove that information (@nkorinek, #121) + +## [0.1.2] +* Adding flake8 for format and other syntax issues! yay! (@lwasser, #195) * Created a vignette covering the testing of histograms (@ryla5068, #149) +* Created `get_plot_image()` function for the RasterTester object (@nkorinek, #192) +* Allowed `assert_polygons()` to accept GeoDataFrames and added tests (@nkorinek, #175) +* raster test inherits from vector to allow for multi layer plot testing (@lwasser, #75) ## [0.1.1] * Added test for bin heights of histograms (@ryla5068, #124) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index f0376b7e..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# MatPlotCheck Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at earth.lab@colorado.edu. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst new file mode 100644 index 00000000..8e94b9dd --- /dev/null +++ b/CODE_OF_CONDUCT.rst @@ -0,0 +1,76 @@ +MatPlotCheck Code of Conduct +============================ + +Our Pledge +---------- + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our +project and our community a harassment-free experience for everyone, +regardless of age, body size, disability, ethnicity, sex +characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +Our Standards +------------- + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual + attention or advances +- Trolling, insulting/derogatory comments, and personal or political + attacks +- Public or private harassment +- Publishing others’ private information, such as a physical or + electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +Our Responsibilities +-------------------- + +Project maintainers are responsible for clarifying the standards of +acceptable behavior and are expected to take appropriate and fair +corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, +or reject comments, commits, code, wiki edits, issues, and other +contributions that are not aligned to this Code of Conduct, or to ban +temporarily or permanently any contributor for other behaviors that they +deem inappropriate, threatening, offensive, or harmful. + +Scope +----- + +This Code of Conduct applies both within project spaces and in public +spaces when an individual is representing the project or its community. +Examples of representing a project or community include using an +official project e-mail address, posting via an official social media +account, or acting as an appointed representative at an online or +offline event. Representation of a project may be further defined and +clarified by project maintainers. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may +be reported by contacting the project team at earth.lab@colorado.edu. +All complaints will be reviewed and investigated and will result in a +response that is deemed necessary and appropriate to the circumstances. +The project team is obligated to maintain confidentiality with regard to +the reporter of an incident. Further details of specific enforcement +policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in +good faith may face temporary or permanent repercussions as determined +by other members of the project’s leaders diff --git a/README.md b/README.md index 614d97c6..75cada70 100644 --- a/README.md +++ b/README.md @@ -111,35 +111,18 @@ vt.assert_polygons(polygons_expected=polygons) Caveats: This repo likely misses edge cases of the many ways matplotlib plots can be created. Please feel free to submit bugs! - ## Active Contributors -- Leah Wasser - -## Dev Setup (to be moved to contributing) - -setup the matplotcheck envt - -``` -conda env create -f environment.yml -conda activate matplotcheck-dev -``` - -Then setup all of the development requirements. - -``` -pip install -e . -pip install -r dev-requirements.txt -pre-commit install -``` +Leah Wasser +Nathan Korinek ## Contributors We've welcome any and all contributions. Below are some of the contributors to MatPlotCheck. -Kylen Solvik -Kylen Solvik +Ryan Larocque +Kylen Solvik Kristen Curry ## How to Contribute diff --git a/dev-requirements.txt b/dev-requirements.txt index a4664dc8..0ba2e5b0 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,11 +2,10 @@ bumpversion==0.5.3 sphinx_rtd_theme==0.4.3 sphinx_gallery==0.5.0 sphinx-autobuild==0.7.1 -m2r==0.2.1 pytest-vcr==1.0.2 pytest-cov==2.7.1 -pytest==5.3.5 -codecov==2.0.15 -setuptools==42.0.2 +pytest==5.4.1 +setuptools==46.1.3 pre-commit==1.20.0 pip==19.0.3 +descartes==1.1.0 diff --git a/docs/code-of-conduct.rst b/docs/code-of-conduct.rst index 220a963d..96e0ba2f 100644 --- a/docs/code-of-conduct.rst +++ b/docs/code-of-conduct.rst @@ -1 +1 @@ -.. mdinclude:: ../CODE_OF_CONDUCT.md +.. include:: ../CODE_OF_CONDUCT.rst diff --git a/docs/conf.py b/docs/conf.py index 12688690..130b0909 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,9 +25,9 @@ author = "Leah Wasser, Kristin Curry" # The short X.Y version -version = "0.1.1" +version = "0.1.2" # The full version, including alpha/beta/rc tags -release = "0.1.1" +release = "0.1.2" # -- General configuration --------------------------------------------------- @@ -40,7 +40,6 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "m2r", "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.napoleon", @@ -65,9 +64,8 @@ # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # -# source_suffix = ['.rst', '.md'] -# "m2r" is needed to support markdown includes in sphinx -source_suffix = [".rst", ".md"] +# source_suffix = ['.rst'] +source_suffix = [".rst"] # The master toctree document. master_doc = "index" diff --git a/examples/plot_histogram_testing.py b/examples/plot_histogram_testing.py deleted file mode 100644 index 454e406a..00000000 --- a/examples/plot_histogram_testing.py +++ /dev/null @@ -1,198 +0,0 @@ -""" -Testing Histograms -================== - -These are some examples of using the basic functionality of MatPlotCheck -to test histogram plots in Python. - -""" - -################################################################################ -# Setup -# ----- -# You will start by importing the required packages and plotting a histogram. - -import matplotlib.pyplot as plt -import matplotcheck.base as mpc -import matplotcheck.notebook as nb -import numpy as np - - -data = np.exp(np.arange(0, 5, 0.01)) - -fig, ax = plt.subplots() -ax.hist(data, bins=5, color="gold") - -plot_1_hold = nb.convert_axes(plt, which_axes="current") - -################################################################################ -# Testing the Histogram -# --------------------- -# Now you can make a PlotTester object and test the histogram. We'll test both -# the number of bins and the values of those bins. - -############################################################################### -# -# .. note:: -# Throughout this vignette, the term `bin value` is used to describe the -# number of datapoints that fall within a bin. In other words, a bin's value -# is equal to the height of the bar correspondign to that bin. For example, -# the value of the first bin in the above histogram is 341. Note that the -# height of the first bar is also 341. - -plot_tester_1 = mpc.PlotTester(plot_1_hold) - -plot_tester_1.assert_num_bins(5) - -expected_bin_values = [341, 68, 40, 28, 23] -plot_tester_1.assert_bin_values(expected_bin_values) -################################################################################ -# And we can also run some tests that will fail. -# -try: - plot_tester_1.assert_num_bins(6) -except AssertionError as message: - print("AssertionError:", message) - -try: - plot_tester_1.assert_bin_values([1, 4, 1, 3, 4]) -except AssertionError as message: - print("AssertionError:", message) - -################################################################################ -# Determining Expected Values -# --------------------------- -# With a histogram, you may not know the values you expect to find for each bin -# before you begin testing. (More simply, you probably know how you expect a -# histogram to look and how you expect it to be made. But you might not know -# the exact height of each bar in that histogram.) In this case, matplotcheck -# provides a method for extracting the bin values from an existing histogram: -# ``get_bin_values()``. -# -# To use this, you can create a histogram however you think it should be created -# (this will be called the expected histogram) and use it as a reference. Then -# you can extract the bin values from it (called the expected values). These -# expected values can be used to test whether another histogram (e.g. a -# student's histogram) also contains the expected values. -# -# For this example, you will start by creating a histogram that will serve as -# your expected histogram, and then extracting the expected values from it. To -# do this, you need to create a `PlotTester` object from it and use the -# ``get_bin_values()`` method. - -expected_data = np.sin(np.arange(0, 2 * np.pi, np.pi / 50)) - -fig, ax = plt.subplots() -ax.hist(expected_data, bins=8, color="gold") - -expected_plot_hold = nb.convert_axes(plt, which_axes="current") -plot_tester_expected = mpc.PlotTester(expected_plot_hold) -print(plot_tester_expected.get_bin_values()) - -################################################################################ -# Great! Now you know the bin values that you expect to see when you test a -# plot. -# -# Now you can create another histogram (our testing histogram) and check -# whether it matches the expected histogram (i.e. check wether its bin values -# match the expected bin values). - -# Create and plot the testing histogram -testing_data = np.sin(np.arange(2 * np.pi, 4 * np.pi, np.pi / 50)) -fig, ax = plt.subplots() -ax.hist(testing_data, bins=8, color="orange") -testing_plot_hold = nb.convert_axes(plt, which_axes="current") - -# Testing the histogram against the expected bin values -plot_tester_testing = mpc.PlotTester(testing_plot_hold) -plot_tester_testing.assert_bin_values( - [23.0, 10.0, 8.0, 9.0, 9.0, 8.0, 10.0, 23.0] -) - -################################################################################ -# Since ``assert_bin_values()`` did not raise an ``AssertionError``, you know -# that the test passed. This means the testing histogram had the same bin values -# as the expected histogram. - -############################################################################### -# -# .. note:: -# In this example, you have created the expected histogram and the testing -# histogram in the same file. Normally you would create the expected histogram -# in one location, extract the expected bin values from it, and use those to -# test the testing histogram in another location (e.g. within a student's -# homework assignment.) - - -################################################################################ -# Testing with Tolerances -# ----------------------- -# In some cases, you might want to run a test that doesn't require the bin -# values to match exactly. For this, you can use the ``tolerance`` argument of -# the ``assert_bin_values()`` method. -# -# You will start by making two histograms with slightly different data and -# storing the plots with ``nb.convert_axes()``. The gold plot will serve as the -# expected plot, and the orange plot will serve as the testing plot. - -expected_data = 0.1 * np.power(np.arange(0, 10, 0.1), 2) -bins = np.arange(0, 10, 1) - -fig1, ax2 = plt.subplots() -ax2.hist(expected_data, color="gold", bins=bins) - -expected_plot_2_hold = nb.convert_axes(plt, which_axes="current") - -################################################################################ - -test_data = 0.1995 * np.power(np.arange(0, 10, 0.1), 1.7) - -fig2, ax2 = plt.subplots() -ax2.hist(test_data, color="orange", bins=bins) - -testing_plot_2_hold = nb.convert_axes(plt, which_axes="current") - -################################################################################ -# Now you will create a `PlotTester` object for each plot. This allows you to -# extract the expected bin values from the expected plot and allows you to -# test the testing plot. - -plot_tester_expected_2 = mpc.PlotTester(expected_plot_2_hold) -bins_expected_2 = plot_tester_expected_2.get_bin_values() - -plot_tester_testing_2 = mpc.PlotTester(testing_plot_2_hold) - -################################################################################ -# You'll notice that the test (orange) plot differs somewhat from the -# expected (gold) plot, but still has a similar shape and similar bin -# values. -# -# If you test it without the ``tolerance`` argument, the assertion will fail. - -try: - plot_tester_testing_2.assert_bin_values(bins_expected_2) -except AssertionError as message: - print("AssertionError:", message) - -################################################################################ -# However, if you set a tolerance, the assertion can pass. Here you will test it -# with ``tolerance=0.2``. - -plot_tester_testing_2.assert_bin_values(bins_expected_2, tolerance=0.2) - -################################################################################ -# Because no ``AssertionError`` is raised, you know that the test passed with -# a tolerance of 0.2. However, the test will not pass with a tolerance that is -# too small; the test will fail with ``tolerance=0.1``. - -try: - plot_tester_testing_2.assert_bin_values(bins_expected_2, tolerance=0.1) -except AssertionError as message: - print("AssertionError:", message) - -############################################################################### -# -# .. note:: -# When using tolerances, the ``tolerance`` argument is taken as a relative -# tolerance. For more information, see the documentation for the -# ``base.assert_bin_heights()`` method. diff --git a/examples/plot_test_histogram.py b/examples/plot_test_histogram.py new file mode 100644 index 00000000..5db81094 --- /dev/null +++ b/examples/plot_test_histogram.py @@ -0,0 +1,261 @@ +""" +Test Histogram Plots with Matplotcheck +====================================== + +Below you will find some examples of how to use MatPlotCheck +to test histogram plots created with Matplotlib in Python. + +""" + +############################################################################### +# Setup +# ----- +# You will start by importing the required packages and plotting a histogram. +# Once you have created your plot, you will created a Matplotcheck +# ``PlotTester`` object by providing the Matplotlib axis object to +# ``PlotTester``. + +import matplotlib.pyplot as plt +import matplotcheck.base as mpc +import numpy as np + + +data = np.exp(np.arange(0, 5, 0.01)) + +fig, ax = plt.subplots() +ax.hist(data, bins=5, color="gold") + +# Create a Matplotcheck PlotTester object +plot_tester_1 = mpc.PlotTester(ax) + +############################################################################### +# Test a Histogram Plot +# --------------------- +# Once you have created a PlotTester object, you are ready to test various +# parts of your plot. Below, you test both +# the number of bins and the values associated with those bins. + +############################################################################### +# +# .. note:: +# Throughout this vignette, the term `bin value` is used to describe the +# number of datapoints that fall within a bin. In other words, a bin's value +# is equal to the height of the bar corresponding to that bin. For example, +# the value of the first bin in the above histogram is 341. Note that the +# height of the first bar is also 341. + +# Test that the histogram plot has 5 bins +plot_tester_1.assert_num_bins(5) + +# Test that the histogram bin values (the height of each bin) is as expected +expected_bin_values = [341, 68, 40, 28, 23] +plot_tester_1.assert_bin_values(expected_bin_values) + +############################################################################### +# And you can also run some tests that will fail. +# +try: + plot_tester_1.assert_num_bins(6) +except AssertionError as message: + print("AssertionError:", message) + +try: + plot_tester_1.assert_bin_values([1, 4, 1, 3, 4]) +except AssertionError as message: + print("AssertionError:", message) + +############################################################################### +# Determining Expected Values +# --------------------------- +# You can use the MatPlotCheck ``get_bin_values()`` method to extract the bin +# values that are expected for a plot. This is helpful if you are using a tool +# like nbgrader to create the the expected plot outcomes in a homework +# assignment. +# +# To extract bin values from an expected plot you first create the expected +# histogram plot that you will use to grade your assignment (or htat you expect +# as an outcome from a test). Next, you create a PlotTester object from that +# plot. Finally, you call the ``get_bin_values()`` method to grab the expected +# bin values from that plot. +# +# The steps outlined above are implemented below. + +expected_data = np.sin(np.arange(0, 2 * np.pi, np.pi / 50)) + +# Create the expected plot +fig, ax = plt.subplots() +ax.hist(expected_data, bins=8, color="gold") + +# Create a Matplotcheck PlotTester object from the axis object +plot_tester_expected = mpc.PlotTester(ax) +# Get bin values from the expected plot +print(plot_tester_expected.get_bin_values()) + +############################################################################### +# This example assumes that you are creating tests for a student +# assignment. Once you have created the PlotTester object for the expected +# plot (this is the answer to the assignment that you expect the student to +# come to), +# you can then test the student plot to see if it matches expected bin values. +# Below another plot is created that represents the student submitted plot. + +# Create and plot the student submitted histogram +data = np.sin(np.arange(2 * np.pi, 4 * np.pi, np.pi / 50)) +fig, ax = plt.subplots() +ax.hist(data, bins=8, color="orange") + +# Test the student submitted histogram bin values against the expected +# bin values (the correct answer to the assigned plot) +plot_tester_testing = mpc.PlotTester(ax) +plot_tester_testing.assert_bin_values( + [23.0, 10.0, 8.0, 9.0, 9.0, 8.0, 10.0, 23.0] +) + +############################################################################### +# Above, ``assert_bin_values()`` did not raise an ``AssertionError``. This +# means that the test passed and the student submitted plot has the correct +# histogram bins. +# + +############################################################################### +# +# .. note:: +# In this example, you created the expected histogram (the homework answer) +# and the student submitted histogram in the same file. +# + +############################################################################### +# Testing with Tolerances +# ----------------------- +# In some cases, you might want to run a test that doesn't require the bin +# values to match exactly. For example, it might be ok if the values are +# a few tenths off. To allow for some "wiggle room" in the expected answer, +# you can use the ``tolerance`` parameter of the ``assert_bin_values()`` +# method. +# +# You will start by making two histograms with slightly different data and +# storing the plots with ``nb.convert_axes()``. The gold plot will serve as the +# expected plot, and the orange plot will serve as the testing plot. +# +# You will then create a `PlotTester` object for each plot. This allows you to +# extract the expected bin values from the expected plot and use those value to +# test the testing plot. + +expected_data = 0.1 * np.power(np.arange(0, 10, 0.1), 2) +bins = np.arange(0, 10, 1) + +fig1, ax1 = plt.subplots() +ax1.hist(expected_data, color="gold", bins=bins) + +# Create plot tester object +plot_tester_expected_1 = mpc.PlotTester(ax1) +# Get expected bin values +bins_expected_1 = plot_tester_expected_1.get_bin_values() + +############################################################################### + +test_data = 0.1995 * np.power(np.arange(0, 10, 0.1), 1.7) +fig2, ax2 = plt.subplots() +ax2.hist(test_data, color="orange", bins=bins) +# Create plot tester object +plot_tester_testing_2 = mpc.PlotTester(ax2) + +############################################################################### +# You'll notice that the test (orange) plot differs somewhat from the +# expected (gold) plot, but still has a similar shape and similar bin +# values. +# +# If you test it without the ``tolerance`` argument, the assertion will fail. + +try: + plot_tester_testing_2.assert_bin_values(bins_expected_1) +except AssertionError as message: + print("AssertionError:", message) + +############################################################################### +# However, if you set a tolerance, the assertion can pass. Here you will test +# it with ``tolerance=0.2``. + +plot_tester_testing_2.assert_bin_values(bins_expected_1, tolerance=0.2) + +############################################################################### +# Because no ``AssertionError`` is raised, you know that the test passed with +# a tolerance of 0.2. However, the test will not pass with a tolerance that is +# too small; the test will fail with ``tolerance=0.1``. + +try: + plot_tester_testing_2.assert_bin_values(bins_expected_1, tolerance=0.1) +except AssertionError as message: + print("AssertionError:", message) + +############################################################################### +# +# .. note:: +# When using tolerances, the ``tolerance`` argument is taken as a relative +# tolerance. For more information, see the documentation for the +# ``base.assert_bin_heights()`` method. + +############################################################################### +# Test Histogram Midpoints +# ------------------------ +# So far, you have tested the histogram values as well as the number of bins +# the histogram has. It may also be useful to test that the data bins cover +# the range of values that they were expected to. In order to do this, you can +# test the midpoints of each bin to ensure that the data covered by each +# bin is as expected. This is tested very similarly to the bins values. +# Simply provide ``assert_bin_midpoints()`` with a list of the expected +# midpoints, and it will assert if they are accurate or not. In order to obtain +# the midpoints in a PlotTester object, you can use ``get_bin_midpoints()``, +# much like ``get_bin_values()``. +# +# For this example, you will create a plot tester object from a histogram plot, +# the same way you did for the bin values example. + +fig, ax = plt.subplots() +ax.hist(test_data, bins=8, color="gold") + +# If you were running this in a notebook, the commented out line below would +# store the matplotlib object. However, in this example, you can just grab the +# axes object directly. + +# midpoints_plot_hold = nb.convert_axes(plt, which_axes="current") + +plot_tester_expected_3 = mpc.PlotTester(ax) +print(plot_tester_expected_3.get_bin_midpoints()) + +############################################################################### +# You got the values from the plot tester object! As you can see, the values +# that were collected are the midpoints for the values each histogram bin +# covers. Now you can test that they are asserted indeed correct with an +# assertion test. + +try: + plot_tester_expected_3.assert_bin_midpoints( + [-0.875, -0.625, -0.375, -0.125, 0.125, 0.375, 0.625, 0.875] + ) +except AssertionError as message: + print("AssertionError:", message) + +############################################################################### +# Here you can see that this will fail when given incorrect values. + +try: + plot_tester_expected_3.assert_bin_midpoints( + [-0.75, -0.5, -0.25, -0, 0.25, 0.5, 0.75, 1] + ) +except AssertionError as message: + print("AssertionError:", message) + +############################################################################### +# +# .. note:: +# Keep in mind this test is for the midpoints of the range that each bin +# covers. So if a bin covers all data that's in between 0 and 1, than the +# value given for that bin will be .5, not 0 or 1. + + +# .. note:: +# If you are working on tests for jupyter notebooks, you can call the +# line below to capture the student cell in a notebook. Then you can +# Use that object for testing. +# testing_plot_2_hold = nb.convert_axes(plt, which_axes="current"). diff --git a/examples/plot_testing_basics.py b/examples/plot_testing_basics.py index 839a6919..45b182a7 100644 --- a/examples/plot_testing_basics.py +++ b/examples/plot_testing_basics.py @@ -55,7 +55,7 @@ fig, ax = plt.subplots() ax.bar(months, percip, color="blue") ax.set( - title="Average Monthly Percipitation in Boulder, CO", + title="Average Monthly Precipitation in Boulder, CO", xlabel="Month", ylabel="Percipitation (in)", ) @@ -86,7 +86,7 @@ plot_tester_1.assert_plot_type("bar") # Test that the plot title contains specific words -plot_tester_1.assert_title_contains(["average", "month", "percip", "boulder"]) +plot_tester_1.assert_title_contains(["average", "monthly precip", "boulder"]) # Test that the axis labels contain specific words plot_tester_1.assert_axis_label_contains(axis="x", strings_expected=["month"]) diff --git a/matplotcheck/autograde.py b/matplotcheck/autograde.py index fe565e59..f2a5a3f5 100644 --- a/matplotcheck/autograde.py +++ b/matplotcheck/autograde.py @@ -41,10 +41,10 @@ def run_test( pass : bool : passing status of test function description : str : test function name that was run message : str : custom message returned based on passing status - traceback : AssertionError : returned from test function when pass is False + traceback : AssertionError : returned from test function when pass is + False """ results = {"points": 0, "pass": False} - score = 0 try: fname = func.__name__ results["description"] = fname @@ -72,7 +72,8 @@ def output_results(results): pass : bool : passing status of test function description : str : test function name that was run message : str : custom message returned based on passing status - traceback : AssertionError : returned from test function when pass is False + traceback : AssertionError : returned from test function when pass is + False Returns ------- diff --git a/matplotcheck/base.py b/matplotcheck/base.py index fe828643..bef8cec2 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -109,25 +109,32 @@ def assert_string_contains( Similar to `message_default`, `message_or` is the error message to be displated if `string` does not contain at least one of the strings in an inner list in `strings_expected`. If `message` - contains ``'{0}'``, it will be replaced with the first failing inner - list in `strings_expected`. + contains ``'{0}'``, it will be replaced with the first failing + inner list in `strings_expected`. Raises ------- AssertionError if `string` does not contain expected strings """ - # Assertion passes if strings_expected == [] or strings_expected == None + # Assertion passes if strings_expected == [] or + # strings_expected == None if not strings_expected: return string = string.lower().replace(" ", "") + + if isinstance(strings_expected, str): + strings_expected = [strings_expected] + for check in strings_expected: if isinstance(check, str): - if not check.lower() in string: + if not check.lower().replace(" ", "") in string: raise AssertionError(message_default.format(check)) elif isinstance(check, list): - if not any([c.lower() in string for c in check]): + if not any( + [c.lower().replace(" ", "") in string for c in check] + ): if len(check) == 1: raise AssertionError(message_default.format(check[0])) else: @@ -149,8 +156,8 @@ def assert_plot_type( String specifying the expected plot type. Options: `scatter`, `bar`, `line` message : string - The error message to be displayed if Plot does not match `plot_type`. If - `message` contains ``'{0}'``, it will be replaced + The error message to be displayed if Plot does not match + `plot_type`. If `message` contains ``'{0}'``, it will be replaced with the epected plot type. Raises @@ -219,18 +226,19 @@ def assert_title_contains( assertion. The combined title will be tested. message_default : string - The error message to be displayed if the axis label does not contain - a string in strings_expected. If `message` contains ``'{0}'``, it - will be replaced with the first expected string not found in the - label. + The error message to be displayed if the axis label does not + contain a string in strings_expected. If `message` contains + ``'{0}'``, it will be replaced with the first expected string not + found in the label. message_or : string Similar to `message_default`, `message_or` is the error message to be displated if the axis label does not contain at least one of the strings in an inner list in `strings_expected`. If `message` - contains ``'{0}'``, it will be replaced with the first failing inner - list in `strings_expected`. + contains ``'{0}'``, it will be replaced with the first failing + inner list in `strings_expected`. message_no_title : string - The error message to be displayed if the expected title is not displayed. + The error message to be displayed if the expected title is not + displayed. Raises ------- @@ -246,7 +254,8 @@ def assert_title_contains( title = axtitle else: raise ValueError( - 'title_type must be one of the following ["figure", "axes", "either"]' + "title_type must be one of the following " + + '["figure", "axes", "either"]' ) assert title, message_no_title @@ -268,7 +277,8 @@ def get_caption(self): Returns ------- caption : string - the text that is found in bottom right, ``None`` if no text is found + the text that is found in bottom right, ``None`` if no text is + found """ caption = None ax_position = self.ax.get_position() @@ -308,16 +318,16 @@ def assert_caption_contains( ``'a'`` AND ``'b'`` AND (at least one of: ``'c'``, ``'d'``) must be in the title for the assertion to pass. Case insensitive. message_default : string - The error message to be displayed if the axis label does not contain - a string in strings_expected. If `message` contains ``'{0}'``, it - will be replaced with the first expected string not found in the - label. + The error message to be displayed if the axis label does not + contain a string in strings_expected. If `message` contains + ``'{0}'``, it will be replaced with the first expected string + not found in the label. message_or : string Similar to `message_default`, `message_or` is the error message to be displated if the axis label does not contain at least one of the strings in an inner list in `strings_expected`. If `message` - contains ``'{0}'``, it will be replaced with the first failing inner - list in `strings_expected`. + contains ``'{0}'``, it will be replaced with the first failing + inner list in `strings_expected`. message_no_caption : string The error message to be displayed if no caption exists in the appropriate location. @@ -360,12 +370,10 @@ def assert_axis_off(self, message="Axis lines are displayed on plot"): """ flag = False # Case 1: check if axis have been turned off - if self.ax.axison == False: + if not self.ax.axison: flag = True # Case 2: Check if both axis visibilities set to false - elif ( - self.ax.xaxis._visible == False and self.ax.yaxis._visible == False - ): + elif not self.ax.xaxis._visible and not self.ax.yaxis._visible: flag = True # Case 3: Check if both axis ticks are set to empty lists elif ( @@ -403,18 +411,18 @@ def assert_axis_label_contains( ``'a'`` AND ``'b'`` AND (at least one of: ``'c'``, ``'d'``) must be in the title for the assertion to pass. Case insensitive. message_default : string - The error message to be displayed if the axis label does not contain - a string in strings_expected. If `message` contains ``'{1}'``, it - will be replaced with `axis`. If `message` contains ``'{0}'``, it - will be replaced with the first expected string not found in the - label. + The error message to be displayed if the axis label does not + contain a string in strings_expected. If `message` contains + ``'{1}'``, it will be replaced with `axis`. If `message` contains + ``'{0}'``, it will be replaced with the first expected string not + found in the label. message_or : string Similar to `message_default`, `message_or` is the error message to be displated if the axis label does not contain at least one of the strings in an inner list in `strings_expected`. If `message` contains ``'{1}'``, it will be replaced with `axis`. If `message` - contains ``'{0}'``, it will be replaced with the first failing inner - list in `strings_expected`. + contains ``'{0}'``, it will be replaced with the first failing + inner list in `strings_expected`. message_not_displayed : string The error message to be displayed if the expected axis label is not displayed. If `message_not_displayed` contains ``'{0}'``, it will @@ -574,7 +582,8 @@ def assert_legend_titles( self, titles_exp, message="Legend title does not contain expected string: {0}", - message_num_titles="I was expecting {0} legend titles but instead found {1}", + message_num_titles="I was expecting {0} legend titles but instead " + + "found {1}", ): """Asserts legend titles contain expected text in titles_exp list. @@ -592,8 +601,8 @@ def assert_legend_titles( The error message to be displayed if there exist a different number of legend titles than expected. If `message_num_titles` contains ``'{0}'`` it will be replaced with the number of titles found. If - `message_num_titles` contains ``'{1}'`` it will be replaced with the - expected number of titles. + `message_num_titles` contains ``'{1}'`` it will be replaced with + the expected number of titles. Raises ------- @@ -625,7 +634,8 @@ def assert_legend_labels( labels_exp, message="Legend does not have expected labels", message_no_legend="Legend does not exist", - message_num_labels="I was expecting {0} legend entries, but found {1}. Are there extra labels in your legend?", + message_num_labels="I was expecting {0} legend entries, but found " + + "{1}. Are there extra labels in your legend?", ): """Asserts legends on ax have the correct entry labels @@ -643,8 +653,8 @@ def assert_legend_labels( The error message to be displayed if there exist a different number of legend labels than expected. If `message_num_labels` contains ``'{0}'`` it will be replaced with the number of labels found. If - `message_num_labels` contains ``'{1}'`` it will be replaced with the - expected number of labels. + `message_num_labels` contains ``'{1}'`` it will be replaced with + the expected number of labels. Raises @@ -750,8 +760,8 @@ def assert_no_legend_overlap(self, message="Legends overlap eachother"): leg_extent2 = ( legends[j].get_window_extent(RendererBase()).get_points() ) - assert ( - self.legends_overlap(leg_extent1, leg_extent2) == False + assert not self.legends_overlap( + leg_extent1, leg_extent2 ), message """ BASIC PLOT DATA FUNCTIONS """ @@ -765,9 +775,11 @@ def get_xy(self, points_only=False, xtime=False): ax : matplotlib.axes.Axes Matplotlib Axes object to be tested points_only : boolean - Set ``True`` to check only points, set ``False`` to check all data on plot. + Set ``True`` to check only points, set ``False`` to check all data + on plot. xtime : boolean - Set equal to True if the x axis of the plot contains datetime values + Set equal to True if the x axis of the plot contains datetime + values Returns ------- @@ -872,12 +884,13 @@ def assert_xydata( return elif not isinstance(xy_expected, pd.DataFrame): raise ValueError( - "xy_expected must be of type: pandas dataframe or Geopandas Dataframe" + "xy_expected must be of type: pandas dataframe or Geopandas " + + "Dataframe" ) # If xy_expected is a GeoDataFrame, then we make is a normal DataFrame - # with the coordinates of the geometry in that GeoDataFrame as the x and - # y data + # with the coordinates of the geometry in that GeoDataFrame as the x + # and y data if isinstance(xy_expected, gpd.geodataframe.GeoDataFrame) and not xcol: xy_expected = pd.DataFrame( data={ @@ -1018,7 +1031,7 @@ def assert_xlabel_ydata( except AssertionError: raise AssertionError(message) - ### LINE TESTS/HELPER FUNCTIONS ### + # LINE TESTS/HELPER FUNCTIONS def get_slope_yintercept(self, path_verts): """Returns the y-intercept of line based on the average slope of the @@ -1125,13 +1138,14 @@ def assert_lines_of_type(self, line_types): slope_exp, intercept_exp = 1, 0 else: raise ValueError( - 'each string in line_types must be from the following ["regression","onetoone"]' + "each string in line_types must be from the following " + + '["regression","onetoone"]' ) self.assert_line( slope_exp, intercept_exp, - message_no_line="{0} line is not displayed properly".format( + message_no_line="{0} line not displayed properly".format( line_type ), message_data="{0} line does not cover dataset".format( @@ -1139,7 +1153,7 @@ def assert_lines_of_type(self, line_types): ), ) - ## HISTOGRAM FUNCTIONS ## + # HISTOGRAM FUNCTIONS def get_num_bins(self): """Gets the number of bins in histogram with a unique x-position. @@ -1149,8 +1163,9 @@ def get_num_bins(self): Int : Returns the number of bins with a unique x-position. For a normal histogram, this is just the number of bins. If there are two - overlapping or stacked histograms in the same `matplotlib.axis.Axis` - object, then this returns the number of bins with unique edges. """ + overlapping or stacked histograms in the same + `matplotlib.axis.Axis` object, then this returns the number of bins + with unique edges. """ x_data = self.get_xy(xtime=False)["x"] unique_x_data = list(set(x_data)) num_bins = len(unique_x_data) @@ -1170,9 +1185,9 @@ def assert_num_bins( Number of bins expected. message : string The error message to be displayed if plot does not contain - `num_bins`. If `message` contains ``'{0}'`` it will be replaced with - expected number of bins. If `message` contains ``'{1}'``, it will - be replaced with the number of bins found. + `num_bins`. If `message` contains ``'{0}'`` it will be replaced + with expected number of bins. If `message` contains ``'{1}'``, it + will be replaced with the number of bins found. Raises ------- @@ -1195,10 +1210,22 @@ def get_bin_values(self): Int : The number of bins in the histogram""" - bin_values = self.get_xy(xtime=False)["y"].tolist() + bin_values = self.get_xy()["y"].tolist() return bin_values + def get_bin_midpoints(self): + """Returns the mid point value of each bin in a histogram + + Returns + ------- + Int : + The number of bins in the histogram""" + + bin_midpoints = self.get_xy()["x"].tolist() + + return bin_midpoints + def assert_bin_values( self, bin_values, @@ -1210,8 +1237,8 @@ def assert_bin_values( Parameters ---------- bin_values : list - A list of numbers representing the expected values of each consecutive - bin (i.e. the heights of the bars in the histogram). + A list of numbers representing the expected values of each + consecutive bin (i.e. the heights of the bars in the histogram). tolerence : float Measure of relative error allowed. For example: Given a tolerance ``tolerence=0.1``, an expected value @@ -1261,3 +1288,44 @@ def assert_bin_values( ) except AssertionError: raise AssertionError(message) + + def assert_bin_midpoints( + self, + bin_midpoints, + message="Did not find expected bin midpoints in plot", + ): + """ + Asserts that the middle values of histogram bins match `bin_midpoints`. + + Parameters + ---------- + bin_midpoints : list + A list of numbers representing the expected middles of bin values + covered by each consecutive bin (i.e. the midpoint of the bars in + the histogram). + message : string + The error message to be displayed if the bin mid point values do + not match `bin_midpoints` + + Raises + ------ + AssertionError + if the Values of histogram bins do not match `bin_midpoints` + """ + + plot_bin_midpoints = self.get_bin_midpoints() + + if not isinstance(bin_midpoints, list): + raise ValueError( + "Need to submit a list for expected bin midpoints." + ) + + if len(plot_bin_midpoints) != len(bin_midpoints): + raise ValueError("Bin midpoints lists lengths do no match.") + + try: + np.testing.assert_array_max_ulp( + np.array(plot_bin_midpoints), np.array(bin_midpoints) + ) + except AssertionError: + raise AssertionError(message) diff --git a/matplotcheck/cases.py b/matplotcheck/cases.py index 58b78f19..c2370987 100644 --- a/matplotcheck/cases.py +++ b/matplotcheck/cases.py @@ -1,5 +1,3 @@ -## Adding levels of abstraction.. -# import all plottester and folium tester import unittest from .base import PlotTester from .timeseries import TimeSeriesTester @@ -28,29 +26,40 @@ class PlotBasicSuite(object): xcol: string column title in data_exp that contains xaxis data ycol: string column title in data_exp that contains yaxis data plot_type: string from list ["scatter","bar"] of expected plot type - line_types: list of strings. Acceptable strings in line_types are as follows ["regression", "onetoone"]. + line_types: list of strings. Acceptable strings in line_types are as + follows + ["regression", "onetoone"]. if list is empty, assert is passed xlabels: boolean if using x axis labels rather than x data lims_equal: boolean expressing if x and y limits are expected to be equal - title_contains: list of lower case strings where each string is expected to be in title, barring capitalization. - If value is an empty list: test is just to see that title exists and is not an empty string + title_contains: list of lower case strings where each string is expected to + be in title, barring capitalization. + If value is an empty list: test is just to see that title exists and is + not an empty string If value is None: no tests are run title_type: one of the following strings ["figure", "axes", "either"] "figure": only the figure title (suptitle) will be tested "axes": only the axes title (suptitle) will be tested - "either": either the figure title or axes title will pass this assertion. + "either": either the figure title or axes title will pass this + assertion. The combined title will be tested. - xlabel_contains: list of lower case strings where each string is expected to be in x-axis label, barring capitalization. - If value is an empty list: test is just to see that x-axis label exists and is not an empty string + xlabel_contains: list of lower case strings where each string is expected + to be in x-axis label, barring capitalization. + If value is an empty list: test is just to see that x-axis label exists + and is not an empty string If value is None: no tests are run - ylabel_contains: list of lower case strings where each string is expected to be in y-axis label, barring capitalization. - If value is an empty list: test is just to see that y-axis label exists and is not an empty string + ylabel_contains: list of lower case strings where each string is expected + to be in y-axis label, barring capitalization. + If value is an empty list: test is just to see that y-axis label exists + and is not an empty string If value is None: no tests are run - caption_string: list of lists. Each internal list is a list of lower case strings where at least one string must be + caption_string: list of lists. Each internal list is a list of lower case + strings where at least one string must be found in the caption, barring capitalization if empty list: asserts caption exists and not an empty string if None: no tests are run - legend_labels: list of lower case stings. Each string is an expected entry label in the legend, barring capitalization. + legend_labels: list of lower case stings. Each string is an expected entry + label in the legend, barring capitalization. """ def __init__( @@ -75,9 +84,12 @@ def __init__( ): class PlotLabelsTest(unittest.TestCase): """A unittest.TestCase containing 3 tests: - 1. title_exist: ax has a title that contains each string in list of strings title_contains, barring capitalization - 2. xlab_exist: ax has a x label that contains each string in list of strings xlabel_contains, barring capitalization - 3. ylab_exist: ax has a y label that that contains each string in list of strings ylabel_contains, barring capitalization + 1. title_exist: ax has a title that contains each string in list of + strings title_contains, barring capitalization + 2. xlab_exist: ax has a x label that contains each string in list + of strings xlabel_contains, barring capitalization + 3. ylab_exist: ax has a y label that that contains each string in + list of strings ylabel_contains, barring capitalization """ def setUp(self): @@ -106,8 +118,8 @@ def tearDown(self): class LegendTest(unittest.TestCase): """A unittest.TestCase containing 2 tests checking the legend(s): - 1. legend_labels: Asserts the legend has labels specified in labels_ - exp (barring capitalization), and only those labels + 1. legend_labels: Asserts the legend has labels specified in + labels_ exp (barring capitalization), and only those labels 2. legend_location: Asserts legend does not cover data and no legends overlap each other """ @@ -149,8 +161,10 @@ class PlotBasic(unittest.TestCase): """A unittest.TestCase containing 4 tests on Matplotlib Axes ax 1. data: asserts that the x and y data of ax matches data_exp 2. lines: asserts each of lines in line_types is displayed on ax - 3. plot_type: asserts plot is of expected type expressed by plot_type - 4. lims: asserts the x and y limits are equal if boolean lims_equal expresses it should be + 3. plot_type: asserts plot is of expected type expressed by + plot_type + 4. lims: asserts the x and y limits are equal if boolean + lims_equal expresses it should be """ def setUp(self): @@ -196,12 +210,16 @@ def tearDown(self): @property def cases(self): - """Returns a list of TestCases to be run in a TestLoader for basic 2d plots (scatter, bar, line, etc.). + """Returns a list of TestCases to be run in a TestLoader for basic 2d + plots (scatter, bar, line, etc.). Testcases are as follows: - 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are as expected - 2. BasicCase: Asserts data matches data_exp, and other assertions based on params listed below - For more on tests, see init method above. For more on assertions, see the autograde package. + 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are + as expected + 2. BasicCase: Asserts data matches data_exp, and other assertions + based on params listed below + For more on tests, see init method above. For more on assertions, + see the autograde package. """ return [self.LabelsCase, self.BasicCase] @@ -237,8 +255,8 @@ class PlotHistogramSuite(PlotBasicSuite): If value is an empty list: test is just to see that title exists and is not an empty string If value is None: asserts no title - xlabel_contains: list of lower case strings where each string is expected to - be in x-axis label, barring capitalization. + xlabel_contains: list of lower case strings where each string is expected + to be in x-axis label, barring capitalization. If value is an empty list: test is just to see that x-axis label exists and is not an empty string If value is None: asserts no label is expressed @@ -269,11 +287,16 @@ def __init__( class PlotHistogram(unittest.TestCase): """A unittest.TestCase containing 4 tests for a histogram: - Test 1 - num_neg_bins: number of bins centered at a negative value is greater than n_bins[0] - Test 2- num_pos_bins: number of bins centered at a positive value is greater than n_bins[1] - Test 3 - x-lims: x-axis left limits are within the bounds [xlims[0][0], xlims[0][1]] and - x-axis right limits are within the bounds [xlims[1][0], xlims[1][1]] - Test 4 - y_lims: y-axis bottom limits are within the bounds [ylims[0][0], ylims[0][1]] and + Test 1 - num_neg_bins: number of bins centered at a negative + value is greater than n_bins[0] + Test 2- num_pos_bins: number of bins centered at a positive value + is greater than n_bins[1] + Test 3 - x-lims: x-axis left limits are within the bounds [xlims[ + 0][0], xlims[0][1]] and + x-axis right limits are within the bounds [xlims[1][0], + xlims[1][1]] + Test 4 - y_lims: y-axis bottom limits are within the bounds [ + ylims[0][0], ylims[0][1]] and y-axis top limits are within the bounds [ylims[1][0], ylims[1][1]] """ @@ -308,15 +331,18 @@ def tearDown(self): @property def cases(self): """Returns list of cases for a histogram. Testcases are as follows: - 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are as expected - 2. HistogramCase: number of negative and positive bins as declares in n_bins. x axis limits and y axis limits + 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are + as expected + 2. HistogramCase: number of negative and positive bins as declares in + n_bins. x axis limits and y axis limits are in range declared by xlims and y lims. - For more on tests, see init method above. For more on assertions, see the autograde package. + For more on tests, see init method above. For more on assertions, + see the autograde package. """ return [self.LabelsCase, self.HistogramCase] -### TIME SERIES PLOT ### +""" TIME SERIES PLOTS """ class PlotTimeSeriesSuite(PlotBasicSuite): @@ -329,21 +355,29 @@ class PlotTimeSeriesSuite(PlotBasicSuite): x_col: string column title in data_exp that contains xaxis data y_col: string column title in data_exp that contains yaxis data no_data_val: float representing no data, as stated by the input data - major_locator_exp: one of the following ['decade', 'year', 'month', 'week', 'day', None] + major_locator_exp: one of the following ['decade', 'year', 'month', + 'week', 'day', None] decade: if tick should be shown every ten years year: if tick should be shown every new year month: if tick should be shown every new month week: if tick should be shown every new week day: if tick should be shown every new day - minor_locator_exp: one of the following ['decade', 'year', 'month', 'week', 'day', None], as expressed above - title_contains: list of lower case strings where each string is expected to be in title, barring capitalization. - If value is an empty list: test is just to see that title exists and is not an empty string + minor_locator_exp: one of the following ['decade', 'year', 'month', + 'week', 'day', None], as expressed above + title_contains: list of lower case strings where each string is expected + to be in title, barring capitalization. + If value is an empty list: test is just to see that title exists and + is not an empty string If value is None: asserts no title - xlabel_contains: list of lower case strings where each string is expected to be in x-axis label, barring capitalization. - If value is an empty list: test is just to see that x-axis label exists and is not an empty string + xlabel_contains: list of lower case strings where each string is expected + to be in x-axis label, barring capitalization. + If value is an empty list: test is just to see that x-axis label + exists and is not an empty string If value is None: asserts no label is expressed - ylabel_contains: list of lower case strings where each string is expected to be in y-axis label, barring capitalization. - If value is an empty list: test is just to see that y-axis label exists and is not an empty string + ylabel_contains: list of lower case strings where each string is expected + to be in y-axis label, barring capitalization. + If value is an empty list: test is just to see that y-axis label + exists and is not an empty string If value is None: asserts no label is expressed """ @@ -372,10 +406,14 @@ def __init__( ) class PlotTicksReformat(unittest.TestCase): - """A unittest.TestCase containing 3 tests checking the xaxis ticks and labels: - 1. x_major_formatter: large ticks on x axis have been reformatted as expressed in major_locator_exp - 2. x_major_locs: large ticks on x axis are located as expressed in major_locator_exp - 3. x_minor_locs: small ticks on x axis are located as expressed in minor_locator_exp + """A unittest.TestCase containing 3 tests checking the xaxis + ticks and labels: + 1. x_major_formatter: large ticks on x axis have been reformatted + as expressed in major_locator_exp + 2. x_major_locs: large ticks on x axis are located as expressed + in major_locator_exp + 3. x_minor_locs: small ticks on x axis are located as expressed + in minor_locator_exp """ def setUp(self): @@ -443,47 +481,65 @@ def tearDown(self): def cases(self): """ Returns a list of TestCases for time series plots. Testcase are as follows: - 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are as expected - 2. TickReformatCase: Asserts x-axis ticks have large ticks as express in major_locator_exp and small + 1. LabelsCase: Asserts the title, x-axis label, and y-axis label are + as expected + 2. TickReformatCase: Asserts x-axis ticks have large ticks as express + in major_locator_exp and small ticks as express in minor_locator_exp - 3. TimeSeriesCase: Asserts data matches data_exp and is converted to time objects - For more on tests, see init method above. For more on assertions, see the autograde package. + 3. TimeSeriesCase: Asserts data matches data_exp and is converted to + time objects + For more on tests, see init method above. For more on assertions, + see the autograde package. """ return [self.LabelsCase, self.TickReformatCase, self.TimeSeriesCase] -### VECTOR PLOT ### +""" VECTOR PLOTS """ class PlotVectorSuite(PlotBasicSuite): - """A PlotBasicSuite object to test a Matplotlib plot with spatial vector data. + """A PlotBasicSuite object to test a Matplotlib plot with spatial vector + data. Parameters --------- ax: Matplotlib Axes to be tested - caption_strings: list of lists. Each internal list is a list of lower case strings where at least one string must be + caption_strings: list of lists. Each internal list is a list of lower + case strings where at least one string must be found in the caption, barring capitalization if None: assert caption does not exist if empty list: asserts caption exists and not an empty string - legend_labels: list of lower case stings. Each string is an expected entry label in the legend. + legend_labels: list of lower case stings. Each string is an expected + entry label in the legend. title_type: one of the following strings ["figure", "axes", "either"] "figure": only the figure title (suptitle) will be tested "axes": only the axes title (suptitle) will be tested - "either": either the figure title or axes title will pass this assertion. + "either": either the figure title or axes title will pass this + assertion. The combined title will be tested. - title_contains: list of lower case strings where each string is expected to be in title, barring capitalization. - If value is an empty list: test is just to see that title exists and is not an empty string + title_contains: list of lower case strings where each string is expected + to be in title, barring capitalization. + If value is an empty list: test is just to see that title exists and + is not an empty string If value is None: asserts no title - markers: Geopandas dataframe with geometry column containing expected Point objects - lines: Geopandas dataframe with geometry column containing expected LineString and MultiLineString objects - polygons: list of lines where each line is a list of coord tuples for the exterior polygon - markers_groupby: column title from markers_exp that points are expected to be grouped by/contain - like attributes. Attributes tested are: marker type, markersize, and color + markers: Geopandas dataframe with geometry column containing expected + Point objects + lines: Geopandas dataframe with geometry column containing expected + LineString and MultiLineString objects + polygons: list of lines where each line is a list of coord tuples for the + exterior polygon + markers_groupby: column title from markers_exp that points are expected + to be grouped by/contain + like attributes. Attributes tested are: marker type, markersize, + and color if None, assertion is passed - lines_groupby: column title from line_exp that lines are expected to be grouped by/contain - like attributes. Attributes tested are: line style, line width, and color + lines_groupby: column title from line_exp that lines are expected to be + grouped by/contain + like attributes. Attributes tested are: line style, line width, + and color if None, assertion is passed - markers_by_size: column title from markers_exp that points are expected to be sorted by + markers_by_size: column title from markers_exp that points are expected + to be sorted by if None, assertion is passed """ @@ -515,10 +571,13 @@ def __init__( class PlotVector(unittest.TestCase): """A unittest.TestCase containing tests for a spatial vector plot. 1. marker_location: points on ax match markers - 2. markers_by_size: asserts points on ax vary in size by column expressed in markers_by_size - 3. markers_grouped: asserts markers of the same group contain like attributes + 2. markers_by_size: asserts points on ax vary in size by column + expressed in markers_by_size + 3. markers_grouped: asserts markers of the same group contain + like attributes 4. lines_location: lines on ax match lines - 5. lines_grouped: asserts lines of the same group contain like attributes + 5. lines_grouped: asserts lines of the same group contain like + attributes 6. polygons_location: polygons on ax match polygons """ @@ -576,11 +635,16 @@ def tearDown(self): def cases(self): """ Returns a list of TestCases for spatial vector plots. Testcase are as follows: - 1. CaptionCase: assert caption is in appropriate location with strings expressed in caption_contains - 2. LabelsCase: asserts the title contains strings in title_contains, and x and y labels are empty - 3. LegendCase: assert legend(s) is/are in appropriate location with legend_labels - 4. VectorCase: assert vector data is as expected in markers, lines, and polygons - For more on tests, see init method above. For more on assertions, see the autograde package. + 1. CaptionCase: assert caption is in appropriate location with + strings expressed in caption_contains + 2. LabelsCase: asserts the title contains strings in title_contains, + and x and y labels are empty + 3. LegendCase: assert legend(s) is/are in appropriate location with + legend_labels + 4. VectorCase: assert vector data is as expected in markers, lines, + and polygons + For more on tests, see init method above. For more on assertions, + see the autograde package. """ return [ self.CaptionCase, @@ -590,7 +654,7 @@ def cases(self): ] -### RASTER PLOT ### +""" RASTER PLOT """ class PlotRasterSuite(PlotVectorSuite): @@ -600,27 +664,39 @@ class PlotRasterSuite(PlotVectorSuite): --------- ax: Matplotlib Axes to be tested im_expected: array containing values of an expected image - caption_strings: list of lists. Each internal list is a list of strings where at least one string must be + caption_strings: list of lists. Each internal list is a list of strings + where at least one string must be found in the caption, barring capitalization if empty list: asserts caption exists and not an empty string if None: assertion is passed im_classified: boolean if image on ax is classfied - legend_labels: list of lists. Each internal list represents a classification category. - Said list is a list of strings where at least one string is expected to be in the legend label for this category. + legend_labels: list of lists. Each internal list represents a + classification category. + Said list is a list of strings where at least one string is expected + to be in the legend label for this category. Internal lists must be in the same order as bins in im_expected. - title_type: one of the following strings ["figure", "axes", "either"], stating which title to test + title_type: one of the following strings ["figure", "axes", "either"], + stating which title to test title_contains: list of strings expected to be in title - markers: Geopandas dataframe with geometry column containing expected Point objects - markers_by_size: column title from markers_exp that points are expected to be sorted by + markers: Geopandas dataframe with geometry column containing expected + Point objects + markers_by_size: column title from markers_exp that points are expected + to be sorted by if None, assertion is passed - markers_groupby: column title from markers_exp that points are expected to be grouped by/contain - like attributes. Attributes tested are: marker type, markersize, and color + markers_groupby: column title from markers_exp that points are expected + to be grouped by/contain + like attributes. Attributes tested are: marker type, markersize, + and color if None, assertion is passed - lines: Geopandas dataframe with geometry column containing expected LineString and MultiLineString objects - lines_groupby: column title from line_exp that lines are expected to be grouped by/contain - like attributes. Attributes tested are: line style, line width, and color + lines: Geopandas dataframe with geometry column containing expected + LineString and MultiLineString objects + lines_groupby: column title from line_exp that lines are expected to be + grouped by/contain + like attributes. Attributes tested are: line style, line width, + and color if None, assertion is passed - polygons: list of lines where each line is a list of coord tuples for the exterior polygon + polygons: list of lines where each line is a list of coord tuples for the + exterior polygon colorbar_range: tuple of (min, max) for colorbar. If empty tuple: asserts a colorbar exists, but does not check values If None: assertion is passed @@ -660,12 +736,15 @@ def __init__( class PlotRaster(unittest.TestCase): """A unittest.TestCase containing tests for a spatial raster plot. - 1. image_data: asserts image is as expected. If Image is classified image classification may be shifted or reversed. + 1. image_data: asserts image is as expected. If Image is + classified image classification may be shifted or reversed. 2. image_stretch: asserts image takes up entire display as expected 3. image_mask: - 4. legend_accuracy: if image is classified, asserts legend exists and correctly describes image. + 4. legend_accuracy: if image is classified, asserts legend exists + and correctly describes image. if image is not classified, assertion is passed. - 5. colorbar_accuracy: asserts colorbar exists and has range descrbied in colorbar_range + 5. colorbar_accuracy: asserts colorbar exists and has range + descrbied in colorbar_range 6. axis_off: axis lines are not displayed """ @@ -710,9 +789,12 @@ def tearDown(self): def cases(self): """ Returns a list of TestCases for spatial raster plots. Testcase are as follows: - 1. CaptionCase: assert caption is in appropriate location with strings expressed in caption_strings - 2. LabelsCase: asserts the title contains strings in title_contains, and x and y labels are empty - 3. RasterCase: asserts raster image matches im_expected and legend is correct if image is classified + 1. CaptionCase: assert caption is in appropriate location with + strings expressed in caption_strings + 2. LabelsCase: asserts the title contains strings in title_contains, + and x and y labels are empty + 3. RasterCase: asserts raster image matches im_expected and legend is + correct if image is classified 4. VectorCase: assert vector data is as expected """ return [ @@ -723,21 +805,25 @@ def cases(self): ] -#### FOLIUM ### +""" FOLIUM """ + + class PlotFoliumSuite(object): """A generic object to test Folium Maps. Parameters --------- fmap: folium map to be tested - markers: set of tuples where each tuple represents the x and y coord of an expected marker + markers: set of tuples where each tuple represents the x and y coord of + an expected marker """ def __init__(self, fmap, markers): class MapFolium(unittest.TestCase): """Returns a unittest.TestCase containing 2 tests on a Folium map. 1. map_folium: map is of type folium.folium.Map - 2. marker_locs: map contains all markers in markers_exp and no additional markers + 2. marker_locs: map contains all markers in markers_exp and no + additional markers """ def setUp(self): @@ -759,7 +845,8 @@ def tearDown(self): def cases(self): """ Returns a TestSuite for Folium Maps. Testcase are as follows: - 1. FoliumCase: asserts map is of type folium.map and contains expected markers + 1. FoliumCase: asserts map is of type folium.map and contains + expected markers """ return [self.FoliumCase] diff --git a/matplotcheck/folium.py b/matplotcheck/folium.py index b1ab9e10..27e9a302 100644 --- a/matplotcheck/folium.py +++ b/matplotcheck/folium.py @@ -1,9 +1,8 @@ -import numpy as np import folium class FoliumTester(object): - """object to test Folium plots + """Object to test Folium plots Parameters ---------- @@ -16,18 +15,25 @@ def __init__(self, fmap): self.fmap = fmap def assert_map_type_folium(self): - """Asserts fmap is of type folium.folium.Map""" + """ + Asserts fmap is of type folium.folium.Map + """ assert type(self.fmap) == folium.folium.Map def assert_folium_marker_locs( self, markers, m="Markers not shown in appropriate location" ): - """Asserts folium contains markers with locations described in markers_exp and only those markers, with error message m + """ + Asserts folium contains markers with locations described in + markers_exp and only those markers, with error message m Parameters ---------- - markers: set of tuples where each tuple represents the x and y coord of an expected marker - m: string error message if assertion is not met + markers: tuples + Set of tuples where each tuple represents the x and y coord of + an expected marker. + m: string + Error message if assertion is not met. """ marker_locs = set() while self.fmap._children: diff --git a/matplotcheck/notebook.py b/matplotcheck/notebook.py index 00716017..2e62870d 100644 --- a/matplotcheck/notebook.py +++ b/matplotcheck/notebook.py @@ -8,12 +8,16 @@ def convert_axes(plt, which_axes="current"): Parameters --------- - plt: Matplotlib plot to be tested - which_axes: string from the following list ['current', 'last', 'first', 'all'] stating which axes we are saving for testing + plt: matplotlib plot + Matplotlib plot to be tested. + which_axes: string + String from the following list ['current', 'last', 'first', 'all'] + stating which axes we are saving for testing. Returns ------- - ax: Matplotlib axes or list of axes as express by which_axes + ax: Matplotlib axes or list + Matplotlib axes or list of axes as express by which_axes """ fig = plt.gcf() if which_axes == "current": @@ -26,12 +30,13 @@ def convert_axes(plt, which_axes="current"): ax = fig.axes else: raise ValueError( - 'which_axes must be one of the following strings ["current", "last", "first", "all"]' + "which_axes must be one of the following strings " + + '["current", "last", "first", "all"]' ) return ax -### JUPYTER NOTEBOOK TEST HELPER FUNCTIONS! ### +# JUPYTER NOTEBOOK TEST HELPER FUNCTIONS! def error_test(n, n_exp): @@ -39,8 +44,10 @@ def error_test(n, n_exp): Parameters ---------- - n: int of number of cells that that did not produce an error - n_exp: int of number of cell that are checked if producing an error + n: int + Number of cells that that did not produce an error. + n_exp: int + Number of cell that are checked if producing an error. Returns ------- @@ -61,22 +68,29 @@ def remove_comments(input_string): Parameters ---------- input_string: string + String to be modified. Returns ------- - string where all parts commented out by a '#' are removed from input_string + string + Sting where all parts commented out by a '#' are removed from + input_string. """ split_lines = input_string.split("\n") return "".join([line.split("#")[0] for line in split_lines]) def import_test(var_dict, n): - """Tests no import statements are found after the first cell in a Jupyter Notebook + """ + Tests no import statements are found after the first cell in a Jupyter + Notebook Parameters ---------- - vars_dict: dictionary produced by 'locals()' in notebook - n: number of cells to be tested for import statement in Jupyter Notebook + vars_dict: dictionary + Dictionary produced by 'locals()' in notebook. + n: int + number of cells to be tested for import statement in Jupyter Notebook Returns ------- diff --git a/matplotcheck/raster.py b/matplotcheck/raster.py index a8f8e4da..2e870898 100644 --- a/matplotcheck/raster.py +++ b/matplotcheck/raster.py @@ -1,9 +1,8 @@ import numpy as np +from .vector import VectorTester -from .base import PlotTester - -class RasterTester(PlotTester): +class RasterTester(VectorTester): """A PlotTester for spatial raster plots. Parameters @@ -94,9 +93,9 @@ def assert_legend_accuracy_classified_image( all_label_options: list of lists Each internal list represents a class and said list is a list of strings where at least one string is expected to be in the legend - label for this category. Internal lists must be in the same order as - bins in im_expected, e.g. first internal list has the expected label - options for class 0. + label for this category. Internal lists must be in the same order + as bins in im_expected, e.g. first internal list has the expected + label options for class 0. Returns ---------- @@ -106,13 +105,13 @@ def assert_legend_accuracy_classified_image( Notes ---------- First compares all_label_options against the legend labels to find - which element of all_label_options matches that entry. E.g. if the first - legend entry has a match in the first list in all_label_options, then - that legend entry corresponds to the first class (value 0). - Then the plot image array is copied and the values are set to the legend - label that match the values (i.e. the element in all_label_options). - The same is done for the expected image array. Finally those two arrays - of strings are compared. Passes if they match. + which element of all_label_options matches that entry. E.g. if the + first legend entry has a match in the first list in all_label_options, + then that legend entry corresponds to the first class (value 0). + Then the plot image array is copied and the values are set to the + legend label that match the values (i.e. the element in + all_label_options). The same is done for the expected image array. + Finally those two arrays of strings are compared. Passes if they match. """ # Retrieve image array im_data = [] @@ -161,7 +160,43 @@ def assert_legend_accuracy_classified_image( im_data_labels, im_expected_labels ), "Incorrect legend to data relation" - ### IMAGE TESTS/HELPER FUNCTIONS ### + # IMAGE TESTS/HELPER FUNCTIONS + + def get_plot_image(self): + """Returns images stored on the Axes object as a list of numpy arrays. + + Returns + ------- + im_data: List + Numpy array of images stored on Axes object. + """ + im_data = [] + if self.ax.get_images(): + im_data = self.ax.get_images()[0].get_array() + assert list(im_data), "No Image Displayed" + + # If image array has 3 dims (e.g. rgb image), remove alpha channel + if len(im_data.shape) == 3: + im_data = im_data[:, :, :3] + return im_data + + def get_plot_image(self): + """Returns images stored on the Axes object as a list of numpy arrays. + + Returns + ------- + im_data: List + Numpy array of images stored on Axes object. + """ + im_data = [] + if self.ax.get_images(): + im_data = self.ax.get_images()[0].get_array() + assert list(im_data), "No Image Displayed" + + # If image array has 3 dims (e.g. rgb image), remove alpha channel + if len(im_data.shape) == 3: + im_data = im_data[:, :, :3] + return im_data def assert_image( self, im_expected, im_classified=False, m="Incorrect Image Displayed" @@ -170,12 +205,14 @@ def assert_image( Parameters ---------- - im_expected: array containing the expected image data - im_classified: boolean, set to True image has been classified. - Since classified images values can be reversed or shifted and still - produce the same image, setting this to True will allow those - changes. - m: string error message if assertion is not met + im_expected: Numpy Array + Array containing the expected image data. + im_classified: boolean + Set to True image has been classified. Since classified images + values can be reversed or shifted and still produce the same image, + setting this to True will allow those changes. + m: string + String error message if assertion is not met. Returns ---------- diff --git a/matplotcheck/tests/conftest.py b/matplotcheck/tests/conftest.py index 4931fd81..44aae9ad 100644 --- a/matplotcheck/tests/conftest.py +++ b/matplotcheck/tests/conftest.py @@ -1,9 +1,10 @@ """Pytest fixtures for matplotcheck tests""" import pytest -import pandas as pd -import geopandas as gpd import numpy as np import matplotlib.pyplot as plt +import pandas as pd +import geopandas as gpd +from shapely.geometry import Polygon, LineString from matplotcheck.base import PlotTester @@ -31,21 +32,90 @@ def pd_gdf(): """Create a geopandas GeoDataFrame for testing""" df = pd.DataFrame( { - "lat": np.random.randint(-85, 85, size=100), - "lon": np.random.randint(-180, 180, size=100), + "lat": np.random.randint(-85, 85, size=5), + "lon": np.random.randint(-180, 180, size=5), } ) gdf = gpd.GeoDataFrame( - {"A": np.arange(100)}, geometry=gpd.points_from_xy(df.lon, df.lat) + {"A": np.arange(5)}, geometry=gpd.points_from_xy(df.lon, df.lat) ) + gdf["attr"] = ["Tree", "Tree", "Bush", "Bush", "Bush"] + return gdf + + +@pytest.fixture +def basic_polygon(): + """ + A square polygon spanning (2, 2) to (4.25, 4.25) in x and y directions. + Borrowed from rasterio/tests/conftest.py. + Returns + ------- + dict: GeoJSON-style geometry object. + Coordinates are in grid coordinates (Affine.identity()). + """ + return Polygon([(2, 2), (2, 4.25), (4.25, 4.25), (4.25, 2), (2, 2)]) + +@pytest.fixture +def basic_polygon_gdf(basic_polygon): + """ + A GeoDataFrame containing the basic polygon geometry. + Returns + ------- + GeoDataFrame containing the basic_polygon polygon. + """ + gdf = gpd.GeoDataFrame(geometry=[basic_polygon], crs="epsg:4326") + return gdf + + +@pytest.fixture +def two_line_gdf(): + """ Create Line Objects For Testing """ + linea = LineString([(1, 1), (2, 2), (3, 2), (5, 3)]) + lineb = LineString([(3, 4), (5, 7), (12, 2), (10, 5), (9, 7.5)]) + gdf = gpd.GeoDataFrame([1, 2], geometry=[linea, lineb], crs="epsg:4326") + return gdf + + +@pytest.fixture +def basic_polygon(): + """ + A square polygon spanning (2, 2) to (4.25, 4.25) in x and y directions. + Borrowed from rasterio/tests/conftest.py. + Returns + ------- + dict: GeoJSON-style geometry object. + Coordinates are in grid coordinates (Affine.identity()). + """ + return Polygon([(2, 2), (2, 4.25), (4.25, 4.25), (4.25, 2), (2, 2)]) + + +@pytest.fixture +def basic_polygon_gdf(basic_polygon): + """ + A GeoDataFrame containing the basic polygon geometry. + Returns + ------- + GeoDataFrame containing the basic_polygon polygon. + """ + gdf = gpd.GeoDataFrame(geometry=[basic_polygon], crs="epsg:4326") + return gdf + + +@pytest.fixture +def two_line_gdf(): + """ Create Line Objects For Testing """ + linea = LineString([(1, 1), (2, 2), (3, 2), (5, 3)]) + lineb = LineString([(3, 4), (5, 7), (12, 2), (10, 5), (9, 7.5)]) + gdf = gpd.GeoDataFrame([1, 2], geometry=[linea, lineb], crs="epsg:4326") return gdf @pytest.fixture def pd_xlabels(): """Create a DataFrame which uses the column labels as x-data.""" - df = pd.DataFrame({"B": bp.random.randint(0, 100, size=100)}) + df = pd.DataFrame({"B": np.random.randint(0, 100, size=100)}) + return df @pytest.fixture @@ -58,9 +128,7 @@ def pt_scatter_plt(pd_df): ax.set_xlabel("x label") ax.set_ylabel("y label") - axis = plt.gca() - - return PlotTester(axis) + return PlotTester(ax) @pytest.fixture @@ -83,9 +151,7 @@ def pt_line_plt(pd_df): ax_position.ymax - 0.25, ax_position.ymin - 0.075, "Figure Caption" ) - axis = plt.gca() - - return PlotTester(axis) + return PlotTester(ax) @pytest.fixture @@ -96,9 +162,7 @@ def pt_multi_line_plt(pd_df): ax.set_ylim((0, 140)) ax.legend(loc="center left", title="Legend", bbox_to_anchor=(1, 0.5)) - axis = plt.gca() - - return PlotTester(axis) + return PlotTester(ax) @pytest.fixture @@ -111,9 +175,7 @@ def pt_bar_plt(pd_df): ax.set_xlabel("x label") ax.set_ylabel("y label") - axis = plt.gca() - - return PlotTester(axis) + return PlotTester(ax) @pytest.fixture @@ -123,21 +185,22 @@ def pt_time_line_plt(pd_df_timeseries): pd_df_timeseries.plot("time", "A", kind="line", ax=ax) - axis = plt.gca() - - return PlotTester(axis) + return PlotTester(ax) @pytest.fixture def pt_geo_plot(pd_gdf): """Create a geo plot for testing""" fig, ax = plt.subplots() + size = 0 + point_symb = {"Tree": "green", "Bush": "brown"} - pd_gdf.plot(ax=ax) - ax.set_title("My Plot Title", fontsize=30) - ax.set_xlabel("x label") - ax.set_ylabel("y label") + for ctype, points in pd_gdf.groupby("attr"): + color = point_symb[ctype] + label = ctype + size += 100 + points.plot(color=color, ax=ax, label=label, markersize=size) - axis = plt.gca() + ax.legend(title="Legend", loc=(1.1, 0.1)) - return PlotTester(axis) + return PlotTester(ax) diff --git a/matplotcheck/tests/test_autograde.py b/matplotcheck/tests/test_autograde.py new file mode 100644 index 00000000..353ca776 --- /dev/null +++ b/matplotcheck/tests/test_autograde.py @@ -0,0 +1,84 @@ +"""Tests for the autograder module""" + +import matplotcheck.autograde as ag + + +def test_autograde_runs_assert_pass(pt_scatter_plt): + """Test that a normal test is run with autograder and passes""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["Plot"], + ) + assert result["description"] == "assert_title_contains" + assert result["pass"] + assert result["message"] == "default correct" + assert result["points"] == 2 + + +def test_autograde_runs_assert_fail(pt_scatter_plt): + """Test that a when a test fails, an assertion error is returned""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["NotAWord"], + ) + assert result["description"] == "assert_title_contains" + assert not result["pass"] + assert result["message"] == "default error" + assert result["points"] == 0 + assert isinstance(result["traceback"], AssertionError) + + +def test_autograde_runs_assert_pass_custom_message(pt_scatter_plt): + """Test that when a custom message is provided, it is correctly returned + to the user.""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["Plot"], + correct_message="This is correct!", + ) + assert result["message"] == "This is correct!" + + +def test_autograde_runs_assert_fail_custom_message(pt_scatter_plt): + """Test that a custom message for a failed test gets returned""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["NotAWord"], + error_message="This is wrong!", + ) + assert result["message"] == "This is wrong!" + + +def test_output_results_pass(pt_scatter_plt, capsys): + """Test that the correct number of points are returned when a test + passes""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["Plot"], + ) + assert 2 == ag.output_results([result]) + assert ( + "Results for test 'assert_title_contains':\n" + " Pass! default correct (2 points)\n" == capsys.readouterr().out + ) + + +def test_output_results_fail(pt_scatter_plt, capsys): + """Test that the correct points are returned when a test fails""" + result = ag.run_test( + pt_scatter_plt.assert_title_contains, + points=2, + strings_expected=["NotAWord"], + ) + assert 0 == ag.output_results([result]) + assert ( + "Results for test 'assert_title_contains':\n" + " Fail! default error (0 points)\n" + " Traceback: Title does not contain expected string: NotAWord\n" + == capsys.readouterr().out + ) diff --git a/matplotcheck/tests/test_base.py b/matplotcheck/tests/test_base.py index f9f5999b..23b1bf0f 100644 --- a/matplotcheck/tests/test_base.py +++ b/matplotcheck/tests/test_base.py @@ -55,7 +55,8 @@ def test_options(pt_line_plt): def test_assert_string_contains(pt_line_plt): - """Tests that assert_string_contains passes with correct expected strings.""" + """Tests that assert_string_contains passes with correct expected + strings.""" test_string = "this is a test string" string_expected = ["this", "is", "a", "test"] pt_line_plt.assert_string_contains(test_string, string_expected) @@ -63,7 +64,8 @@ def test_assert_string_contains(pt_line_plt): def test_assert_string_contains_fails(pt_line_plt): - """Tests that assert_string_contains fails with incorrect expected strings.""" + """Tests that assert_string_contains fails with incorrect expected + strings.""" test_string = "this is a test string" string_expected = ["this", "is", "not", "a", "test"] with pytest.raises( @@ -74,7 +76,8 @@ def test_assert_string_contains_fails(pt_line_plt): def test_assert_string_contains_or(pt_line_plt): - """Tests that assert_string_contains correctly passes when using OR logic.""" + """Tests that assert_string_contains correctly passes when using OR + logic.""" test_string = "this is a test string" string_expected = ["this", ["is", "not"], "a", "test"] pt_line_plt.assert_string_contains(test_string, string_expected) @@ -82,7 +85,8 @@ def test_assert_string_contains_or(pt_line_plt): def test_assert_string_contains_or_fails(pt_line_plt): - """Tests that assert_string_contains correctly fails when using OR logic.""" + """Tests that assert_string_contains correctly fails when using OR + logic.""" test_string = "this is a test string" string_expected = ["this", "is", ["not", "jambalaya"], "a", "test"] with pytest.raises( @@ -114,7 +118,8 @@ def test_assert_string_contains_handles_short_list_fails(pt_line_plt): def test_assert_string_contains_passes_with_none(pt_line_plt): - """Tests that assert_string_contains passes when strings_expected is None""" + """Tests that assert_string_contains passes when strings_expected is + None""" test_string = "this is a test string" string_expected = None pt_line_plt.assert_string_contains(test_string, string_expected) @@ -122,7 +127,8 @@ def test_assert_string_contains_passes_with_none(pt_line_plt): def test_assert_string_contains_passes_with_empty(pt_line_plt): - """Tests that assert_string_contains passes when strings_expected is empty""" + """Tests that assert_string_contains passes when strings_expected is + empty""" test_string = "this is a test string" string_expected = [] pt_line_plt.assert_string_contains(test_string, string_expected) diff --git a/matplotcheck/tests/test_base_axis.py b/matplotcheck/tests/test_base_axis.py index 83baddbe..dfd70af7 100644 --- a/matplotcheck/tests/test_base_axis.py +++ b/matplotcheck/tests/test_base_axis.py @@ -2,7 +2,6 @@ import pytest import matplotlib.pyplot as plt - """ AXIS TESTS """ @@ -52,7 +51,8 @@ def test_axis_off_one_visible(pt_line_plt): def test_axis_off_empty_ticks(pt_line_plt): - """Check assert_axis_off for case when axis tick labels set to empty lists""" + """Check assert_axis_off for case when axis tick labels set to empty + lists""" pt_line_plt.ax.xaxis.set_ticks([]) pt_line_plt.ax.yaxis.set_ticks([]) pt_line_plt.assert_axis_off() @@ -92,8 +92,25 @@ def test_axis_label_contains_y(pt_line_plt): plt.close() +def test_axis_label_contains_x_spaces(pt_line_plt): + """Checks for assert_axis_label_contains for x axis with spaces""" + pt_line_plt.assert_axis_label_contains( + axis="x", strings_expected=["x label"] + ) + plt.close() + + +def test_axis_label_contains_y_spaces(pt_line_plt): + """Checks for assert_axis_label_contains for y axis with spaces""" + pt_line_plt.assert_axis_label_contains( + axis="y", strings_expected=["y label"] + ) + plt.close() + + def test_axis_label_contains_invalid_axis(pt_line_plt): - """Check that assert_axis_label_contains fails when given unexpected axis""" + """Check that assert_axis_label_contains fails when given unexpected + axis""" # Fails when given an invalid axies with pytest.raises(ValueError, match="axis must be one of the following"): pt_line_plt.assert_axis_label_contains( diff --git a/matplotcheck/tests/test_base_data.py b/matplotcheck/tests/test_base_data.py index fee04b3f..a9cea54d 100644 --- a/matplotcheck/tests/test_base_data.py +++ b/matplotcheck/tests/test_base_data.py @@ -52,11 +52,14 @@ def pt_monthly_data_numeric(pd_df_monthly_data_numeric): @pytest.fixture def pt_hist(): - dataframe_a = pd.DataFrame({"A": np.exp(np.arange(1, 2, 0.01))}) + df_a = pd.DataFrame({"A": np.exp(np.arange(1, 2, 0.01))}) bins = [2, 3, 4, 5, 6, 7, 8] - plt.hist(dataframe_a["A"], bins=bins, alpha=0.5, color="seagreen") - axis = plt.gca() - return PlotTester(axis) + + _, ax = plt.subplots() + + ax.hist(df_a["A"], bins=bins, alpha=0.5, color="seagreen") + + return PlotTester(ax) @pytest.fixture @@ -67,10 +70,12 @@ def pt_hist_overlapping(): ) bins = [2, 3, 4, 5, 6, 7, 8] - plt.hist(dataframe_a["A"], bins=bins, alpha=0.5, color="seagreen") - plt.hist(dataframe_b["B"], bins=bins, alpha=0.5, color="coral") - axis = plt.gca() - return PlotTester(axis) + _, ax = plt.subplots() + + ax.hist(dataframe_a["A"], bins=bins, alpha=0.5, color="seagreen") + ax.hist(dataframe_b["B"], bins=bins, alpha=0.5, color="coral") + + return PlotTester(ax) """DATACHECK TESTS""" @@ -178,7 +183,8 @@ def test_assert_xydata_xlabel_text_fails(pd_df_monthly_data, pt_monthly_data): def test_assert_xydata_xlabel_numeric( pd_df_monthly_data_numeric, pt_monthly_data_numeric ): - """Tests the xlabels flag on xydata works with numeric expected x-labels.""" + """Tests the xlabels flag on xydata works with numeric expected + x-labels.""" pt_monthly_data_numeric.assert_xydata( pd_df_monthly_data_numeric, xcol="months", ycol="data", xlabels=True @@ -369,3 +375,80 @@ def test_assert_bin_values_tolerance_fails(pt_hist_overlapping): pt_hist_overlapping.assert_bin_values(bin_values, tolerance=0.09) plt.close() + + +def test_assert_bin_midpoints_pass(pt_hist): + """Test that bin midpoints are correct""" + bins = [2.5, 3.5, 4.5, 5.5, 6.5, 7.5] + pt_hist.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_fail(pt_hist): + """Test that bin midpoints fail when incorrect""" + bins = [2, 3, 4, 5, 6, 7] + with pytest.raises(AssertionError, match="Did not find expected bin midp"): + pt_hist.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_fails_wrong_type(pt_hist): + """Test that bin midpoints fails when not handed a list""" + bins = (2.5, 3.5, 4.5, 5.5, 6.5, 7.5) + with pytest.raises(ValueError, match="Need to submit a list for expected"): + pt_hist.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_fails_wrong_length(pt_hist): + """Test that bin midpoints fails when not handed a list with the wrong + length""" + bins = [2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8] + with pytest.raises(ValueError, match="Bin midpoints lists lengths do no "): + pt_hist.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_fail_custom_message(pt_hist): + """Test that correct error message is thrown when bin midpoints fail""" + bins = [2, 3, 4, 5, 6, 7] + with pytest.raises(AssertionError, match="Test Message"): + pt_hist.assert_bin_midpoints(bins, message="Test Message") + + plt.close() + + +def test_assert_bin_midpoints_overlap_pass(pt_hist_overlapping): + """Test that bin midpoints are correct with overlapping histograms""" + bins = [2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5] + pt_hist_overlapping.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_overlap_fail(pt_hist_overlapping): + """Test that bin midpoints fail with overlapping histograms when + incorrect""" + bins = [2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7] + with pytest.raises( + AssertionError, match="Did not find expected bin midpo" + ): + pt_hist_overlapping.assert_bin_midpoints(bins) + + plt.close() + + +def test_assert_bin_midpoints_overlap_length_fail(pt_hist_overlapping): + """Test that bin midpoints fail with overlapping histograms when + incorrect length""" + bins = [2.5, 3.5, 4.5, 5.5, 6.5, 7.5] + with pytest.raises( + ValueError, match="Bin midpoints lists lengths do no match" + ): + pt_hist_overlapping.assert_bin_midpoints(bins) + + plt.close() diff --git a/matplotcheck/tests/test_base_legends.py b/matplotcheck/tests/test_base_legends.py index 01803fc2..66884d66 100644 --- a/matplotcheck/tests/test_base_legends.py +++ b/matplotcheck/tests/test_base_legends.py @@ -2,7 +2,6 @@ import pytest import matplotlib.pyplot as plt - """ LEGEND TESTS """ @@ -53,7 +52,8 @@ def test_assert_legend_not_case_sensitive(pt_multi_line_plt): def test_assert_legend_labels_bad_text(pt_multi_line_plt): - """Check that assert_legend_labels raises expected error when given wrong text""" + """Check that assert_legend_labels raises expected error when given + wrong text""" with pytest.raises( AssertionError, match="Legend does not have expected labels" ): @@ -62,7 +62,8 @@ def test_assert_legend_labels_bad_text(pt_multi_line_plt): def test_assert_legend_labels_wrong_num(pt_multi_line_plt): - """Check that assert_legend_labels raises expected error given wrong number of labels""" + """Check that assert_legend_labels raises expected error given wrong + number of labels""" with pytest.raises( AssertionError, match="I was expecting 3 legend entries" ): @@ -91,9 +92,10 @@ def test_assert_no_legend_overlap_single(pt_multi_line_plt): def test_assert_no_legend_overlap_double(pt_multi_line_plt): - """Checks that assert_no_legend_overlap passes when two legends don't overlap""" + """Checks that assert_no_legend_overlap passes when two legends don't + overlap""" leg_1 = plt.legend(loc=[0.8, 0.8]) - leg_2 = plt.legend(loc=[0.1, 0.1]) + plt.legend(loc=[0.1, 0.1]) pt_multi_line_plt.ax.add_artist(leg_1) pt_multi_line_plt.assert_no_legend_overlap() plt.close() @@ -102,7 +104,7 @@ def test_assert_no_legend_overlap_double(pt_multi_line_plt): def test_assert_no_legend_overlap_fail(pt_multi_line_plt): """Checks that assert_no_legend_overlap fails with overlapping legends""" leg_1 = plt.legend(loc=[0.12, 0.12]) - leg_2 = plt.legend(loc=[0.1, 0.1]) + plt.legend(loc=[0.1, 0.1]) pt_multi_line_plt.ax.add_artist(leg_1) with pytest.raises(AssertionError, match="Legends overlap eachother"): pt_multi_line_plt.assert_no_legend_overlap() diff --git a/matplotcheck/tests/test_base_titles_captions.py b/matplotcheck/tests/test_base_titles_captions.py index 303fe9c4..50c2ac96 100644 --- a/matplotcheck/tests/test_base_titles_captions.py +++ b/matplotcheck/tests/test_base_titles_captions.py @@ -22,7 +22,8 @@ def test_get_titles(pt_line_plt): def test_get_titles_suptitle(pt_line_plt): - """Check that the correct suptitle gets grabbed from a figure with 2 subplots""" + """Check that the correct suptitle gets grabbed from a figure with 2 + subplots""" assert "My Figure Title" == pt_line_plt.get_titles()[0] plt.close() @@ -47,6 +48,18 @@ def test_title_contains_axes(pt_line_plt): plt.close() +def test_title_contains_axes_spaces(pt_line_plt): + """Check title_contains for axes title with spaces""" + pt_line_plt.assert_title_contains(["My Plot Title"], title_type="axes") + plt.close() + + +def test_title_contains_axes_string(pt_line_plt): + """Check title_contains for axes title as a string, not a list""" + pt_line_plt.assert_title_contains("My Plot Title", title_type="axes") + plt.close() + + def test_title_contains_axes_badtext(pt_line_plt): """Check title_contains fails when given bad text""" with pytest.raises( @@ -76,7 +89,8 @@ def test_title_contains_figure(pt_line_plt): def test_title_contains_figure_nosuptitle(pt_bar_plt): - """Check title_contains tester for figure title fails when there is no suptitle""" + """Check title_contains tester for figure title fails when there is no + suptitle""" with pytest.raises( AssertionError, match="Expected title is not displayed" ): @@ -95,7 +109,8 @@ def test_title_contains_both_axes_figure(pt_line_plt): def test_title_contains_both_axes_figure_badtext(pt_line_plt): - """Check title_contains tester for combined titles, should fail with bad text""" + """Check title_contains tester for combined titles, should fail with bad + text""" with pytest.raises( AssertionError, match="Title does not contain expected string: foo" ): @@ -120,6 +135,12 @@ def test_assert_caption_contains(pt_line_plt): plt.close() +def test_assert_caption_contains_spaces(pt_line_plt): + """Test that caption contains passes given right text with spaces""" + pt_line_plt.assert_caption_contains([["Figure Caption"]]) + plt.close() + + def test_assert_caption_contains_expect_empty(pt_line_plt): """Test that caption contains passes when expected text list is empty""" pt_line_plt.assert_caption_contains([]) diff --git a/matplotcheck/tests/test_lines.py b/matplotcheck/tests/test_lines.py new file mode 100644 index 00000000..14d68c12 --- /dev/null +++ b/matplotcheck/tests/test_lines.py @@ -0,0 +1,171 @@ +"""Tests for the vector module""" +import matplotlib +import matplotlib.pyplot as plt +import pytest +import geopandas as gpd +from shapely.geometry import LineString + +from matplotcheck.vector import VectorTester + +matplotlib.use("Agg") + + +@pytest.fixture +def multi_line_gdf(two_line_gdf): + """ Create a multi-line GeoDataFrame. + This has one multi line and another regular line. + """ + # Create a single and multi line object + multiline_feat = two_line_gdf.unary_union + linec = LineString([(2, 1), (3, 1), (4, 1), (5, 2)]) + out_df = gpd.GeoDataFrame( + geometry=gpd.GeoSeries([multiline_feat, linec]), crs="epsg:4326", + ) + out_df["attr"] = ["road", "stream"] + return out_df + + +@pytest.fixture +def mixed_type_geo_plot(pd_gdf, multi_line_gdf): + """Create a point plot for testing""" + _, ax = plt.subplots() + + pd_gdf.plot(ax=ax) + multi_line_gdf.plot(ax=ax) + + return VectorTester(ax) + + +@pytest.fixture +def line_geo_plot(two_line_gdf): + """Create a line vector tester object.""" + _, ax = plt.subplots() + + two_line_gdf.plot(ax=ax) + + return VectorTester(ax) + + +@pytest.fixture +def multiline_geo_plot(multi_line_gdf): + """Create a multiline vector tester object.""" + _, ax = plt.subplots() + + multi_line_gdf.plot(ax=ax, column="attr") + + return VectorTester(ax) + + +@pytest.fixture +def multiline_geo_plot_bad(multi_line_gdf): + """Create a multiline vector tester object.""" + _, ax = plt.subplots() + + multi_line_gdf.plot(ax=ax) + + return VectorTester(ax) + + +def test_assert_line_geo(line_geo_plot, two_line_gdf): + """Test that lines are asserted correctly""" + line_geo_plot.assert_lines(two_line_gdf) + plt.close("all") + + +def test_assert_multiline_geo(multiline_geo_plot, multi_line_gdf): + """Test that multi lines are asserted correctly""" + multiline_geo_plot.assert_lines(multi_line_gdf) + plt.close("all") + + +def test_assert_line_geo_fail(line_geo_plot, multi_line_gdf): + """Test that lines fail correctly""" + with pytest.raises(AssertionError, match="Incorrect Line Data"): + line_geo_plot.assert_lines(multi_line_gdf) + plt.close("all") + + +def test_assert_multiline_geo_fail(multiline_geo_plot, two_line_gdf): + """Test that multi lines fail correctly""" + with pytest.raises(AssertionError, match="Incorrect Line Data"): + multiline_geo_plot.assert_lines(two_line_gdf) + plt.close("all") + + +def test_assert_line_fails_list(line_geo_plot): + """Test that assert_lines fails when passed a list""" + linelist = [ + [(1, 1), (2, 2), (3, 2), (5, 3)], + [(3, 4), (5, 7), (12, 2), (10, 5), (9, 7.5)], + ] + with pytest.raises(ValueError, match="lines_expected is not expected ty"): + line_geo_plot.assert_lines(linelist) + plt.close("all") + + +def test_assert_line_geo_passed_nothing(line_geo_plot): + """Test that assertion passes when passed None""" + line_geo_plot.assert_lines(None) + plt.close("all") + + +def test_get_lines_geometry(line_geo_plot): + """Test that get_lines returns the proper values""" + lines = [(LineString(i[0])) for i in line_geo_plot.get_lines().values] + geometries = gpd.GeoDataFrame(geometry=lines) + line_geo_plot.assert_lines(geometries) + plt.close("all") + + +def test_assert_lines_grouped_by_type(multiline_geo_plot, multi_line_gdf): + """Test that assert works for grouped line plots""" + multiline_geo_plot.assert_lines_grouped_by_type(multi_line_gdf, "attr") + plt.close("all") + + +def test_assert_lines_grouped_by_type_fail( + multiline_geo_plot_bad, multi_line_gdf +): + """Test that assert fails for incorrectly grouped line plots""" + with pytest.raises(AssertionError, match="Line attributes not accurate "): + multiline_geo_plot_bad.assert_lines_grouped_by_type( + multi_line_gdf, "attr" + ) + plt.close("all") + + +def test_assert_lines_grouped_by_type_passes_with_none(multiline_geo_plot): + """Test that assert passes if nothing is passed into it""" + multiline_geo_plot.assert_lines_grouped_by_type(None, None) + plt.close("all") + + +def test_assert_lines_grouped_by_type_fails_non_gdf( + multiline_geo_plot, multi_line_gdf +): + """Test that assert fails if a list is passed into it""" + with pytest.raises(ValueError, match="lines_expected is not of expected "): + multiline_geo_plot.assert_lines_grouped_by_type( + multi_line_gdf.to_numpy(), "attr" + ) + plt.close("all") + + +def test_mixed_type_passes(mixed_type_geo_plot, pd_gdf): + """Tests that points passes with a mixed type plot""" + mixed_type_geo_plot.assert_points(pd_gdf) + plt.close("all") + + +def test_get_lines_by_collection(multiline_geo_plot): + """Test that get_lines_by_collection returns the correct values""" + lines_list = [ + [ + [(1, 1), (2, 2), (3, 2), (5, 3)], + [(3, 4), (5, 7), (12, 2), (10, 5), (9, 7.5)], + [(2, 1), (3, 1), (4, 1), (5, 2)], + ] + ] + sorted_lines_list = sorted([sorted(l) for l in lines_list]) + assert sorted_lines_list == multiline_geo_plot.get_lines_by_collection() + plt.close("all") diff --git a/matplotcheck/tests/test_points.py b/matplotcheck/tests/test_points.py new file mode 100644 index 00000000..f2ead5f7 --- /dev/null +++ b/matplotcheck/tests/test_points.py @@ -0,0 +1,161 @@ +"""Tests for the vector module""" +import numpy as np +import matplotlib +import matplotlib.pyplot as plt +import pytest +import geopandas as gpd + +from matplotcheck.vector import VectorTester + +matplotlib.use("Agg") + + +@pytest.fixture +def bad_pd_gdf(pd_gdf): + """Create a point geodataframe with slightly wrong values for testing""" + return gpd.GeoDataFrame( + geometry=gpd.points_from_xy( + pd_gdf.geometry.x + 1, pd_gdf.geometry.y + 1 + ) + ) + + +@pytest.fixture +def origin_pt_gdf(pd_gdf): + """Create a point geodataframe to test assert_points when a point at the + origin of the plot (0, 0) is present in the dataframe. This checks + for a specific bug fix that was added to the assert_points function.""" + origin_pt_gdf = pd_gdf.append( + gpd.GeoDataFrame(geometry=gpd.points_from_xy([0], [0])) + ) + origin_pt_gdf.reset_index(inplace=True, drop=True) + return origin_pt_gdf + + +@pytest.fixture +def pt_geo_plot(pd_gdf): + """Create a geo plot for testing""" + _, ax = plt.subplots() + size = 0 + point_symb = {"Tree": "green", "Bush": "brown"} + + for ctype, points in pd_gdf.groupby("attr"): + color = point_symb[ctype] + label = ctype + size += 100 + points.plot(color=color, ax=ax, label=label, markersize=size) + + ax.legend(title="Legend", loc=(1.1, 0.1)) + + return VectorTester(ax) + + +@pytest.fixture +def pt_geo_plot_bad(pd_gdf): + """Create a geo plot for testing""" + _, ax = plt.subplots() + + pd_gdf.plot(ax=ax) + + return VectorTester(ax) + + +@pytest.fixture +def pt_geo_plot_origin(origin_pt_gdf, two_line_gdf): + """Create a point plot for testing assert_points with a point at the + origin""" + _, ax = plt.subplots() + + origin_pt_gdf.plot(ax=ax) + + two_line_gdf.plot(ax=ax) + + return VectorTester(ax) + + +def test_points_sorted_by_markersize_pass(pt_geo_plot, pd_gdf): + """Tests that points are plotted as different sizes based on an attribute + value passes""" + pt_geo_plot.assert_collection_sorted_by_markersize(pd_gdf, "attr") + plt.close("all") + + +def test_points_sorted_by_markersize_fail(pt_geo_plot_bad, pd_gdf): + """Tests that points are plotted as different sizes based on an attribute + value fails""" + with pytest.raises(AssertionError, match="Markersize not based on"): + pt_geo_plot_bad.assert_collection_sorted_by_markersize(pd_gdf, "attr") + plt.close("all") + + +def test_points_grouped_by_type(pt_geo_plot, pd_gdf): + """Tests that points grouped by type passes""" + pt_geo_plot.assert_points_grouped_by_type(pd_gdf, "attr") + plt.close("all") + + +def test_points_grouped_by_type_fail(pt_geo_plot_bad, pd_gdf): + """Tests that points grouped by type passes""" + with pytest.raises(AssertionError, match="Point attributes not accurate"): + pt_geo_plot_bad.assert_points_grouped_by_type(pd_gdf, "attr") + plt.close("all") + + +def test_point_geometry_pass(pt_geo_plot, pd_gdf): + """Check that the point geometry test recognizes correct points.""" + pt_geo_plot.assert_points(points_expected=pd_gdf) + plt.close("all") + + +def test_point_geometry_fail(pt_geo_plot, bad_pd_gdf): + """Check that the point geometry test recognizes incorrect points.""" + with pytest.raises(AssertionError, match="Incorrect Point Data"): + pt_geo_plot.assert_points(points_expected=bad_pd_gdf) + plt.close("all") + + +def test_assert_point_fails_list(pt_geo_plot, pd_gdf): + """ + Check that the point geometry test fails anything that's not a + GeoDataFrame + """ + list_geo = [list(pd_gdf.geometry.x), list(pd_gdf.geometry.y)] + with pytest.raises(ValueError, match="points_expected is not expected"): + pt_geo_plot.assert_points(points_expected=list_geo) + plt.close("all") + + +def test_get_points(pt_geo_plot, pd_gdf): + """Tests that get_points returns correct values""" + xy_values = pt_geo_plot.get_points() + assert list(sorted(xy_values.x)) == sorted(list(pd_gdf.geometry.x)) + assert list(sorted(xy_values.y)) == sorted(list(pd_gdf.geometry.y)) + plt.close("all") + + +def test_assert_points_custom_message(pt_geo_plot, bad_pd_gdf): + """Tests that a custom error message is passed.""" + message = "Test message" + with pytest.raises(AssertionError, match="Test message"): + pt_geo_plot.assert_points(points_expected=bad_pd_gdf, m=message) + plt.close("all") + + +def test_wrong_length_points_expected(pt_geo_plot, pd_gdf, bad_pd_gdf): + """Tests that error is thrown for incorrect length of a gdf""" + with pytest.raises(AssertionError, match="points_expected's length does "): + pt_geo_plot.assert_points(bad_pd_gdf.append(pd_gdf), "attr") + plt.close("all") + + +def test_convert_length_function_error(pt_geo_plot): + """Test that the convert length function throws an error when given + incorrect inputs""" + with pytest.raises(ValueError, match="Input array length is not: 1 or 9"): + pt_geo_plot._convert_length(np.array([1, 2, 3, 4]), 9) + + +def test_point_gdf_with_point_at_origin(pt_geo_plot_origin, origin_pt_gdf): + """Test that assert_points works when there's a point at the origin in the + gdf""" + pt_geo_plot_origin.assert_points(origin_pt_gdf) diff --git a/matplotcheck/tests/test_polygons.py b/matplotcheck/tests/test_polygons.py new file mode 100644 index 00000000..d626de3a --- /dev/null +++ b/matplotcheck/tests/test_polygons.py @@ -0,0 +1,121 @@ +"""Tests for the vector module""" +import matplotlib +import matplotlib.pyplot as plt +import pytest +import geopandas as gpd +from shapely.geometry import Polygon + +from matplotcheck.vector import VectorTester + +matplotlib.use("Agg") + + +@pytest.fixture +def multi_polygon_gdf(basic_polygon): + """ + A GeoDataFrame containing the basic polygon geometry. + Returns + ------- + GeoDataFrame containing the basic_polygon polygon. + """ + poly_a = Polygon([(3, 5), (2, 3.25), (5.25, 6), (2.25, 2), (2, 2)]) + gdf = gpd.GeoDataFrame( + [1, 2], geometry=[poly_a, basic_polygon], crs="epsg:4326", + ) + multi_gdf = gpd.GeoDataFrame( + geometry=gpd.GeoSeries(gdf.unary_union), crs="epsg:4326" + ) + return multi_gdf + + +@pytest.fixture +def poly_geo_plot(basic_polygon_gdf): + """Create a polygon vector tester object.""" + _, ax = plt.subplots() + + basic_polygon_gdf.plot(ax=ax) + + return VectorTester(ax) + + +@pytest.fixture +def multi_poly_geo_plot(multi_polygon_gdf): + """Create a mutlipolygon vector tester object.""" + _, ax = plt.subplots() + + multi_polygon_gdf.plot(ax=ax) + + return VectorTester(ax) + + +def test_list_of_polygons_check(poly_geo_plot, basic_polygon): + """Check that the polygon assert works with a list of polygons.""" + x, y = basic_polygon.exterior.coords.xy + poly_list = [list(zip(x, y))] + poly_geo_plot.assert_polygons(poly_list) + plt.close("all") + + +def test_polygon_geodataframe_check(poly_geo_plot, basic_polygon_gdf): + """Check that the polygon assert works with a polygon geodataframe""" + poly_geo_plot.assert_polygons(basic_polygon_gdf) + plt.close("all") + + +def test_empty_list_polygon_check(poly_geo_plot): + """Check that the polygon assert fails an empty list.""" + with pytest.raises(ValueError, match="Empty list or GeoDataFrame "): + poly_geo_plot.assert_polygons([]) + plt.close("all") + + +def test_empty_list_entry_polygon_check(poly_geo_plot): + """Check that the polygon assert fails a list with an empty entry.""" + with pytest.raises(ValueError, match="Empty list or GeoDataFrame "): + poly_geo_plot.assert_polygons([[]]) + plt.close("all") + + +def test_empty_gdf_polygon_check(poly_geo_plot): + """Check that the polygon assert fails an empty GeoDataFrame.""" + with pytest.raises(ValueError, match="Empty list or GeoDataFrame "): + poly_geo_plot.assert_polygons(gpd.GeoDataFrame([])) + plt.close("all") + + +def test_polygon_dec_check(poly_geo_plot, basic_polygon): + """ + Check that the polygon assert passes when the polygon is off by less than + the maximum decimal precision. + """ + x, y = basic_polygon.exterior.coords.xy + poly_list = [[(x[0] + 0.1, x[1]) for x in list(zip(x, y))]] + poly_geo_plot.assert_polygons(poly_list, dec=1) + plt.close("all") + + +def test_polygon_dec_check_fail(poly_geo_plot, basic_polygon): + """ + Check that the polygon assert fails when the polygon is off by more than + the maximum decimal precision. + """ + with pytest.raises(AssertionError, match="Incorrect Polygon"): + x, y = basic_polygon.exterior.coords.xy + poly_list = [(x[0] + 0.5, x[1]) for x in list(zip(x, y))] + poly_geo_plot.assert_polygons(poly_list, dec=1) + plt.close("all") + + +def test_polygon_custom_fail_message(poly_geo_plot, basic_polygon): + """Check that the corrct error message is raised when polygons fail""" + with pytest.raises(AssertionError, match="Test Message"): + x, y = basic_polygon.exterior.coords.xy + poly_list = [(x[0] + 0.5, x[1]) for x in list(zip(x, y))] + poly_geo_plot.assert_polygons(poly_list, m="Test Message") + plt.close("all") + + +def test_multi_polygon_pass(multi_poly_geo_plot, multi_polygon_gdf): + """Check a multipolygon passes""" + multi_poly_geo_plot.assert_polygons(multi_polygon_gdf) + plt.close("all") diff --git a/matplotcheck/tests/test_raster.py b/matplotcheck/tests/test_raster.py index ab59e62e..7c4d7fd8 100644 --- a/matplotcheck/tests/test_raster.py +++ b/matplotcheck/tests/test_raster.py @@ -1,6 +1,5 @@ """Tests for the raster module""" import pytest -import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.patches as mpatches @@ -71,7 +70,9 @@ def raster_plt_class(np_ar_discrete): # Create legend colors = [im.cmap(im.norm(val)) for val in values] patches = [ - mpatches.Patch(color=colors[i], label="Level {l}".format(l=values[i])) + mpatches.Patch( + color=colors[i], label="Level {lev}".format(lev=values[i]) + ) for i in range(values.shape[0]) ] plt.legend(handles=patches) @@ -85,7 +86,8 @@ def raster_plt_class(np_ar_discrete): def test_raster_get_colorbars_length(raster_plt): - """Check that get_colorbars correctly retrieves 1 colorbar from raster_plt1""" + """Check that get_colorbars correctly retrieves 1 colorbar from + raster_plt1""" # Should only be 1 object, and should be a colorbar object cb = raster_plt.get_colorbars() assert len(cb) == 1 @@ -156,7 +158,8 @@ def test_raster_assert_colorbar_range_blank(raster_plt_blank, np_ar): def test_raster_assert_legend_accuracy(raster_plt_class, np_ar_discrete): - """Checks that legend matches image, checking both the labels and color patches""" + """Checks that legend matches image, checking both the labels and color + patches""" values = np.sort(np.unique(np_ar_discrete)) label_options = [[str(i)] for i in values] @@ -184,7 +187,8 @@ def test_raster_assert_legend_accuracy_badlabel( def test_raster_assert_legend_accuracy_badvalues( raster_plt_class, np_ar_discrete ): - """Checks that legend matches image, should fail if you swap image values""" + """Checks that legend matches image, should fail if you swap image + values""" values = np.sort(np.unique(np_ar_discrete)) label_options = [[str(i)] for i in values] @@ -276,7 +280,8 @@ def test_raster_assert_image_class(raster_plt_class, np_ar_discrete): def test_raster_assert_image_class_baddata(raster_plt_class, np_ar_discrete): - """Check that assert_image with bad data fails for a discrete, classified image""" + """Check that assert_image with bad data fails for a discrete, classified + image""" bad_ar = np_ar_discrete + 1 with pytest.raises(AssertionError, match="Arrays are not equal"): raster_plt_class.assert_image(bad_ar) @@ -291,7 +296,8 @@ def test_raster_assert_image_fullscreen(raster_plt): def test_raster_assert_image_fullscreen_fail_xlims(raster_plt): """assert fullscreen should fail if we modify the x-axis limits""" - cur_xlim, cur_ylim = raster_plt.ax.get_xlim(), raster_plt.ax.get_ylim() + cur_xlim = raster_plt.ax.get_xlim() + raster_plt.ax.get_ylim() raster_plt.ax.set_xlim([cur_xlim[0], cur_xlim[1] + 5]) with pytest.raises( AssertionError, match="Image is stretched inaccurately" @@ -317,3 +323,9 @@ def test_raster_assert_image_fullscreen_blank(raster_plt_blank): with pytest.raises(AssertionError, match="No image found on axes"): raster_plt_blank.assert_image_full_screen() plt.close() + + +def test_get_plot_images(raster_plt_rgb): + """get_plot_image should get correct image from ax object""" + ax_im = raster_plt_rgb.get_plot_image() + raster_plt_rgb.assert_image(ax_im) diff --git a/matplotcheck/tests/test_timeseries_module.py b/matplotcheck/tests/test_timeseries_module.py index 32aa53f4..b1be1448 100644 --- a/matplotcheck/tests/test_timeseries_module.py +++ b/matplotcheck/tests/test_timeseries_module.py @@ -1,9 +1,9 @@ -import pytest - ''' def test_assert_xydata_timeseries(pt_time_line_plt, pd_df_timeseries): """Commenting this out for now as this requires a time series data object this is failing because the time data needs to be in seconds like how mpl saves it. """ - pt_time_line_plt.assert_xydata(pd_df_timeseries, xcol='time', ycol='A', xtime=True) + pt_time_line_plt.assert_xydata( + pd_df_timeseries, xcol='time', ycol='A', xtime=True + ) ''' diff --git a/matplotcheck/timeseries.py b/matplotcheck/timeseries.py index 7923aaeb..80a0071a 100644 --- a/matplotcheck/timeseries.py +++ b/matplotcheck/timeseries.py @@ -40,8 +40,10 @@ def assert_xticks_reformatted( 'month': if tick should be shown every new month 'week': if tick should be shown every new week 'day': if tick should be shown every new day - None: if no tick format has been specified. This will automatically assert True - m: string error message if assertion is not met + None: if no tick format has been specified. This will automatically + assert True + m: string + string error message if assertion is not met """ if loc_exp: if tick_size == "large": @@ -60,7 +62,8 @@ def assert_xticks_reformatted( ) # September 30, 2013 else: raise ValueError( - "tick_size must be on of the following string ['large', 'small']" + "tick_size must be on of the following string " + + "['large', 'small']" ) if loc_exp == "decade" or loc_exp == "year": accepted_responses = ["2013"] @@ -70,7 +73,7 @@ def assert_xticks_reformatted( accepted_responses = ["sep30", "september30"] else: raise ValueError( - """loc_exp must be one of the following strings ['decade', + """loc_exp must be one of the following strings ['decade', 'year', 'month', 'week', 'day', None]""" ) assert test_date in accepted_responses, m @@ -81,7 +84,8 @@ def assert_xticks_locs( loc_exp=None, m="Incorrect X axis tick locations", ): - """Asserts that Axes ax has xaxis ticks as noted by tick_size and loc_exp + """Asserts that Axes ax has xaxis ticks as noted by tick_size and + loc_exp Parameters ---------- @@ -107,7 +111,7 @@ def assert_xticks_locs( ticks = self.ax.xaxis.get_minorticklocs() else: raise ValueError( - """"Tick_size must be one of the following strings + """"Tick_size must be one of the following strings ['large', 'small']""" ) @@ -123,7 +127,7 @@ def assert_xticks_locs( inc = relativedelta(days=1) else: raise ValueError( - """"loc_exp must be one of the following strings ['decade', + """"loc_exp must be one of the following strings ['decade', 'year', 'month', 'week', 'day'] or None""" ) @@ -162,18 +166,16 @@ def assert_no_data_value(self, nodata=999.99): xtime: boolean does the x-axis contains datetime values? """ - if nodata != None: + if nodata: xy = self.get_xy(xtime=False) - assert ~np.isin( - nodata, xy["x"] - ), "Values of {0} have been found in data. Be sure to remove no data values".format( - nodata - ) - assert ~np.isin( - nodata, xy["y"] - ), "Values of {0} have been found in data. Be sure to remove no data values".format( - nodata - ) + assert ~np.isin(nodata, xy["x"]), ( + "Values of {0} have been found in data. Be sure to remove no " + "data values" + ).format(nodata) + assert ~np.isin(nodata, xy["y"]), ( + "Values of {0} have been found in data. Be sure to remove no " + "data values" + ).format(nodata) def assert_xdata_date( self, x_exp, m="X-axis is not in appropriate date format" diff --git a/matplotcheck/vector.py b/matplotcheck/vector.py index acc44459..ab77c8da 100644 --- a/matplotcheck/vector.py +++ b/matplotcheck/vector.py @@ -11,109 +11,55 @@ class VectorTester(PlotTester): """A PlotTester for spatial vector plots. - Parameters - ---------- - ax: ```matplotlib.axes.Axes``` object + Parameters + ---------- + ax: ```matplotlib.axes.Axes``` object - """ + """ def __init__(self, ax): """Initialize the vector tester""" super(VectorTester, self).__init__(ax) - def assert_legend_no_overlay_content( - self, m="Legend overlays plot contents" - ): - """Asserts that each legend does not overlay plot contents with error message m - - Parameters - --------- - m: string error message if assertion is not met - """ - plot_extent = self.ax.get_window_extent().get_points() - legends = self.get_legends() - for leg in legends: - leg_extent = leg.get_window_extent().get_points() - legend_left = leg_extent[1][0] < plot_extent[0][0] - legend_right = leg_extent[0][0] > plot_extent[1][0] - legend_below = leg_extent[1][1] < plot_extent[0][1] - assert legend_left or legend_right or legend_below, m - - def _legends_overlap(self, b1, b2): - """Helper function for assert_no_legend_overlap - Boolean value if points of window extents for b1 and b2 overlap - - parmeters - --------- - b1: bounding box of window extents - b2: bounding box of window extents - - Returns - ------ - boolean value that says if bounding boxes b1 and b2 overlap - """ - x_overlap = (b1[0][0] <= b2[1][0] and b1[0][0] >= b2[0][0]) or ( - b1[1][0] <= b2[1][0] and b1[1][0] >= b2[0][0] - ) - y_overlap = (b1[0][1] <= b2[1][1] and b1[0][1] >= b2[0][1]) or ( - b1[1][1] <= b2[1][1] and b1[1][1] >= b2[0][1] - ) - return x_overlap and y_overlap - - def assert_no_legend_overlap(self, m="Legends overlap eachother"): - """Asserts there are no two legends in Axes ax that overlap each other with error message m - - Parameters - ---------- - m: string error message if assertion is not met - """ - legends = self.get_legends() - n = len(legends) - for i in range(n - 1): - leg_extent1 = legends[i].get_window_extent().get_points() - for j in range(i + 1, n): - leg_extent2 = legends[j].get_window_extent().get_points() - assert not self._legends_overlap(leg_extent1, leg_extent2), m - - ### VECTOR DATA FUNCTIONS ### - ## MARKER POINTS ### + """ Check Data """ def _convert_length(self, arr, n): - """ helper function for 'get_points_by_attributes' and 'get_lines_by_attributes' - takes an array of either legnth 1 or n. - If array is length 1: array of array's only element repeating n times is returned - If array is length n: original array is returned - Else: function raises value error - - Parameters - ---------- - arr: array of either length 1 or n - n: length of return array - - Returns - ------- - array of length n - """ + """ helper function for 'get_points_by_attributes' and + 'get_lines_by_attributes' + takes an array of either length 1 or n. + If array is length 1: array of array's only element repeating n times + is returned + If array is length n: original array is returned + Else: function raises value error + + Parameters + ---------- + arr: array + A numpy array of either length 1 or n + n: int + length of return array + + Returns + ------- + array of length n + """ if len(arr) == 1: return list(arr) * n elif len(arr) == n: return arr else: - raise ValueError( - "Input array length is not of either expected values:1 or {0}".format( - n - ) - ) + raise ValueError("Input array length is not: 1 or {0}".format(n)) def get_points_by_attributes(self): - """Returns a sorted list of lists where each list contains tuples of xycoords for points of - the same attributes: color, marker, and markersize - - Returns - ------- - sorted list where each list represents all points with the same color. - each point is represented by a tuple with its coordinates. - """ + """Returns a sorted list of lists where each list contains tuples of + xycoords for points of + the same attributes: color, marker, and markersize + + Returns + ------- + sorted list where each list represents all points with the same color. + each point is represented by a tuple with its coordinates. + """ points_dataframe = pd.DataFrame( columns=["offset", "color", "msize", "mstyle"] ) @@ -157,19 +103,22 @@ def get_points_by_attributes(self): return sorted([sorted(p) for p in points_grouped]) def assert_points_grouped_by_type( - self, data_exp, sort_column, m="Point attribtues not accurate by type" + self, data_exp, sort_column, m="Point attributes not accurate by type" ): - """Asserts that the points on Axes ax display attributes based on their type with error message m - attributes tested are: color, marker, and markersize - - Parameters - ---------- - data_exp: Geopandas Dataframe with Point objects in column 'geometry' - an additional column with title sort_column, denotes a category for each point - sort_column: string of column label in dataframe data_exp. - this column contains values expressing which points belong to which group - m: string error message if assertion is not met - """ + """Asserts that the points on Axes ax display attributes based on their + type with error message m + attributes tested are: color, marker, and markersize + + Parameters + ---------- + data_exp: Geopandas Dataframe with Point objects in column 'geometry' + an additional column with title sort_column, denotes a category for + each point + sort_column: string of column label in dataframe data_exp. + this column contains values expressing which points belong to which + group + m: string error message if assertion is not met + """ groups = self.get_points_by_attributes() grouped_exp = [ @@ -183,40 +132,45 @@ def assert_points_grouped_by_type( def sort_collection_by_markersize(self): """ Returns a pandas dataframe of points in collections on Axes ax. - Returns - -------- - pandas dataframe with columns x, y, point_size. Each row reprsents a point on Axes ax with location x,y and markersize pointsize - """ + Returns + -------- + pandas dataframe with columns x, y, point_size. Each row reprsents a + point on Axes ax with location x,y and markersize pointsize + """ df = pd.DataFrame(columns=("x", "y", "markersize")) for c in self.ax.collections: - offsets, markersizes = c.get_offsets(), c.get_sizes() - x_data, y_data = ( - [offset[0] for offset in offsets], - [offset[1] for offset in offsets], - ) - if len(markersizes) == 1: - markersize = [markersizes[0]] * len(offsets) - df2 = pd.DataFrame( - {"x": x_data, "y": y_data, "markersize": markersize} + if isinstance(c, matplotlib.collections.PathCollection): + offsets, markersizes = c.get_offsets(), c.get_sizes() + x_data, y_data = ( + [offset[0] for offset in offsets], + [offset[1] for offset in offsets], ) - df = df.append(df2) - elif len(markersizes) == len(offsets): - df2 = pd.DataFrame( - {"x": x_data, "y": y_data, "markersize": markersizes} - ) - df = df.append(df2) + if len(markersizes) == 1: + markersize = [markersizes[0]] * len(offsets) + df2 = pd.DataFrame( + {"x": x_data, "y": y_data, "markersize": markersize} + ) + df = df.append(df2) + elif len(markersizes) == len(offsets): + df2 = pd.DataFrame( + {"x": x_data, "y": y_data, "markersize": markersizes} + ) + df = df.append(df2) df = df.sort_values(by="markersize").reset_index(drop=True) return df def assert_collection_sorted_by_markersize(self, df_expected, sort_column): - """Asserts a collection of points vary in size by column expresse din sort_column - - Parameters - ---------- - df_expected: geopandas dataframe with geometry column of expected point locations - sort_column: column title from df_expected that points are expected to be sorted by - if None, assertion is passed - """ + """Asserts a collection of points vary in size by column expressed in + sort_column + + Parameters + ---------- + df_expected: geopandas dataframe with geometry column of expected point + locations + sort_column: column title from df_expected that points are expected to + be sorted by + if None, assertion is passed + """ df = self.sort_collection_by_markersize() df_expected = df_expected.sort_values(by=sort_column).reset_index( drop=True @@ -234,24 +188,91 @@ def assert_collection_sorted_by_markersize(self, df_expected, sort_column): err_msg="Markersize not based on {0} values".format(sort_column), ) - ### LINES ### + def get_points(self): + """Returns a Pandas dataframe with all x, y values for points on axis. + + Returns + ------- + output: DataFrame with columns 'x' and 'y'. Each row represents one + points coordinates. + """ + points = self.get_xy(points_only=True).sort_values(by=["x", "y"]) + points.reset_index(inplace=True, drop=True) + return points + + def assert_points(self, points_expected, m="Incorrect Point Data"): + """ + Asserts the point data in Axes ax is equal to points_expected data + with error message m. + If points_expected not a GeoDataFrame, test fails. + + Parameters + ---------- + points_expected : GeoDataFrame + GeoDataFrame with the expected points for the axis. + m : string (default = "Incorrect Point Data") + String error message if assertion is not met. + """ + if isinstance(points_expected, gpd.geodataframe.GeoDataFrame): + points = self.get_points() + xy_expected = pd.DataFrame(columns=["x", "y"]) + xy_expected["x"] = points_expected.geometry.x + xy_expected["y"] = points_expected.geometry.y + xy_expected = xy_expected.sort_values(by=["x", "y"]) + xy_expected.reset_index(inplace=True, drop=True) + # Fix for failure if more than points were plotted in matplotlib + if len(points) != len(xy_expected): + # Checks if there are extra 0, 0 coords in the DataFrame + # returned from self.get_points and removes them. + points_zeros = (points["x"] == 0) & (points["y"] == 0) + if points_zeros.any(): + expected_zeros = (xy_expected["x"] == 0) & ( + xy_expected["y"] == 0 + ) + keep = expected_zeros.sum() + zeros_index_vals = points_zeros.index[ + points_zeros.tolist() + ] + for i in range(keep): + points_zeros.at[zeros_index_vals[i]] = False + points = points[~points_zeros].reset_index(drop=True) + else: + raise AssertionError( + "points_expected's length does not match the stored" + "data's length." + ) + try: + pd.testing.assert_frame_equal(left=points, right=xy_expected) + except AssertionError: + raise AssertionError(m) + else: + raise ValueError( + "points_expected is not expected type: GeoDataFrame" + ) + + # Lines def _convert_multilines(self, df, column_title): """Helper function for get_lines_by_attribute - converts a pandas dataframe containing a column of LineString and MultiLinestring objects - to a pandas dataframe where each row represents a single line. Line segment values are converted - to a list of tuples. - - Parameters - --------- - df: pandas Dataframe containing a column of LineString and MultiLinestring objects - column_title: string of column title which holds LineString and MultLinestring objects - - Returns - ------- - Dataframe where each row repsrents a single line. - Line segments values are converted to a list of tuples in column column_title - """ + converts a pandas dataframe containing a column of LineString and + MultiLinestring objects + to a pandas dataframe where each row represents a single line. Line + segment values are converted + to a list of tuples. + + Parameters + --------- + df: pandas Dataframe containing a column of LineString and + MultiLinestring objects + column_title: string of column title which holds LineString and + MultLinestring objects + + Returns + ------- + Dataframe where each row represents a single line. + Line segments values are converted to a list of tuples in column + column_title + """ dfout = df.copy() for i, row in dfout.iterrows(): seg = row[column_title] @@ -265,35 +286,38 @@ def _convert_multilines(self, df, column_title): dfout = dfout.append(new_row).reset_index(drop=True) else: raise ValueError( - "Segment is not of either expected type: MultiLinestring, LineString" + "Segment is not of either expected type: MultiLinestring, " + "LineString" ) return dfout def _convert_linestyle(self, ls): """helper function for get_lines_by_attributes. - converts linestyle to a tuple of (offset, onoffseq) to get hashable datatypes + converts linestyle to a tuple of (offset, onoffseq) to get hashable + datatypes - Parameters - ---------- - ls: linesytle from a LineCollection retreived by get_linestyle() + Parameters + ---------- + ls: linesytle from a LineCollection retreived by get_linestyle() - Returns - ------- - tuple containing (offset, onoffseq) of linestyle - """ + Returns + ------- + tuple containing (offset, onoffseq) of linestyle + """ onoffseq = ls[1] if onoffseq: onoffseq = tuple(ls[1]) return (ls[0], onoffseq) def get_lines(self): - """Returns a dataframe with all lines on ax - - Returns - ------- - output: DataFrame with column 'lines'. Each row represents one line segment. - Its value in 'lines' is a list of tuples representing the line segement. - """ + """Returns a dataframe with all lines on ax + + Returns + ------- + output: DataFrame with column 'lines'. Each row represents one line + segment. Its value in 'lines' is a list of tuples representing the + line segment. + """ lines = [ [tuple(coords) for coords in seg] for c in self.ax.collections @@ -303,12 +327,14 @@ def get_lines(self): return pd.DataFrame({"lines": lines}) def get_lines_by_collection(self): - """Returns a sorted list of list where each list contains line segments from the same collections - - Returns - ------- - sorted list where each list represents all lines from the same collection - """ + """Returns a sorted list of list where each list contains line segments + from the same collections + + Returns + ------- + sorted list where each list represents all lines from the same + collection + """ lines_grouped = [ [[tuple(coords) for coords in seg] for seg in c.get_segments()] for c in self.ax.collections @@ -317,13 +343,15 @@ def get_lines_by_collection(self): return sorted([sorted(l) for l in lines_grouped]) def get_lines_by_attributes(self): - """Returns a sorted list of lists where each list contains line segments of the same attributes: - color, linewidth, and linestyle - - Returns - ------ - sorted list where each list represents all lines with the same attributes - """ + """Returns a sorted list of lists where each list contains line + segments of the same attributes: + color, linewidth, and linestyle + + Returns + ------ + sorted list where each list represents all lines with the same + attributes + """ lines_dataframe = pd.DataFrame( columns=["seg", "color", "lwidth", "lstyle"] ) @@ -365,17 +393,19 @@ def get_lines_by_attributes(self): return sorted([sorted(l) for l in lines_grouped]) def assert_lines(self, lines_expected, m="Incorrect Line Data"): - """Asserts the line data in Axes ax is equal to lines_expected with error message m. - If line_expected is None or an empty list, assertion is passed - - Parameters - ---------- - lines_expected: Geopandas Dataframe with a geometry column consisting of MultilineString and LineString objects - m: string error message if assertion is not met - """ + """Asserts the line data in Axes ax is equal to lines_expected with + error message m. + If line_expected is None or an empty list, assertion is passed + + Parameters + ---------- + lines_expected: Geopandas Dataframe with a geometry column consisting + of MultilineString and LineString objects + m: string error message if assertion is not met + """ if type(lines_expected) == gpd.geodataframe.GeoDataFrame: lines_expected = lines_expected[ - lines_expected["geometry"].is_empty == False + ~lines_expected["geometry"].is_empty ].reset_index(drop=True) fig, ax_exp = plt.subplots() lines_expected.plot(ax=ax_exp) @@ -397,19 +427,22 @@ def assert_lines_grouped_by_type( sort_column, m="Line attributes not accurate by type", ): - """Asserts that the lines on Axes ax display like attributes based on their type with error message m - attributes tested are: color, linewidth, linestyle - - Parameters - ---------- - lines_expected: Geopandas Dataframe with geometry column consisting of MultiLineString and LineString objects - sort_column: string of column title in lines_expected that contains types lines are expected to be grouped by - m: string error message if assertion is not met - """ + """Asserts that the lines on Axes ax display like attributes based on + their type with error message m + attributes tested are: color, linewidth, linestyle + + Parameters + ---------- + lines_expected: Geopandas Dataframe with geometry column consisting of + MultiLineString and LineString objects + sort_column: string of column title in lines_expected that contains + types lines are expected to be grouped by + m: string error message if assertion is not met + """ if type(lines_expected) == gpd.geodataframe.GeoDataFrame: groups = self.get_lines_by_attributes() lines_expected = lines_expected[ - lines_expected["geometry"].is_empty == False + ~lines_expected["geometry"].is_empty ].reset_index(drop=True) fig, ax_exp = plt.subplots() for typ, data in lines_expected.groupby(sort_column): @@ -422,22 +455,24 @@ def assert_lines_grouped_by_type( grouped_exp = sorted([sorted(l) for l in grouped_exp]) plt.close(fig) np.testing.assert_equal(groups, grouped_exp, m) - elif not lines_expected: + elif lines_expected is None: pass else: raise ValueError( "lines_expected is not of expected type: GeoDataFrame" ) - ### POLYGONS ### + """ Check Polygons """ def get_polygons(self): - """Returns all polygons on Axes ax as a sorted list of polygons where each polygon is a list of coord tuples - - Returns - ------- - output: sorted list of polygons. Each polygon is a list tuples. Ecah tuples is a coordinate. - """ + """Returns all polygons on Axes ax as a sorted list of polygons where + each polygon is a list of coord tuples + + Returns + ------- + output: sorted list of polygons. Each polygon is a list tuples. Each + tuple is a coordinate. + """ output = [ [tuple(coords) for coords in path.vertices] for c in self.ax.collections @@ -448,17 +483,19 @@ def get_polygons(self): def _convert_multipolygons(self, series): """Helper function for assert_polygons - converts a pandas series of Polygon and MultiPolygon objects to a list of lines, - where each line is a list of coord tuples for the exterior - - Parameters - ---------- - series: series where each entry is a Polygon or MultiPolygon - - Returns - ------- - list of lines where each line is a list of coord tuples for the exterior polygon - """ + converts a pandas series of Polygon and MultiPolygon objects to a list + of lines, + where each line is a list of coord tuples for the exterior + + Parameters + ---------- + series: series where each entry is a Polygon or MultiPolygon + + Returns + ------- + list of lines where each line is a list of coord tuples for the + exterior polygon + """ output = [] for entry in series: if type(entry) == shapely.geometry.multipolygon.MultiPolygon: @@ -471,16 +508,32 @@ def _convert_multipolygons(self, series): def assert_polygons( self, polygons_expected, dec=None, m="Incorrect Polygon Data" ): - """Asserts the polygon data in Axes ax is equal to polygons_expected to decimal place dec with error message m - If polygons_expected is am empty list or None, assertion is passed - - Parameters - ---------- - polygons_expected: list of polygons expected to be founds on Axes ax - dec: int stating the desired decimal precision. If None, polygons must be exact - m: string error message if assertion is not met - """ - if polygons_expected: + """Asserts the polygon data in Axes ax is equal to polygons_expected to + decimal place dec with error message m + If polygons_expected is am empty list or None, assertion is passed. + + Parameters + ---------- + polygons_expected : List or GeoDataFrame + List of polygons expected to be founds on Axes ax or a GeoDataFrame + containing the expected polygons. + dec : int (Optional) + Int stating the desired decimal precision. If None, polygons must + be exact. + m : string (default = "Incorrect Polygon Data") + String error message if assertion is not met. + """ + if len(polygons_expected) != 0: + if isinstance(polygons_expected, list): + if len(polygons_expected[0]) == 0: + raise ValueError( + "Empty list or GeoDataFrame passed into assert_" + "polygons." + ) + if isinstance(polygons_expected, gpd.geodataframe.GeoDataFrame): + polygons_expected = self._convert_multipolygons( + polygons_expected["geometry"] + ) polygons = self.get_polygons() if dec: assert len(polygons_expected) == len(polygons), m @@ -494,3 +547,7 @@ def assert_polygons( ) else: np.testing.assert_equal(polygons, sorted(polygons_expected), m) + else: + raise ValueError( + "Empty list or GeoDataFrame passed into assert_polygons." + ) diff --git a/setup.cfg b/setup.cfg index 398d3476..97579d2f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.1 +current_version = 0.1.2 commit = True tag = True diff --git a/setup.py b/setup.py index 4497e4bc..f7eee81b 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type="text/markdown", - version="0.1.1", + version="0.1.2", packages=["matplotcheck"], install_requires=[ "numpy>=1.14.0", diff --git a/tox.ini b/tox.ini index e037d539..4a4951e6 100644 --- a/tox.ini +++ b/tox.ini @@ -20,8 +20,10 @@ commands = deps = pip>=19.0 black + flake8 basepython = python3 commands = black --check --verbose matplotcheck + flake8 matplotcheck [testenv:docs] whitelist_externals = make From 66b82063375cdd3ad2ba53a0a674098aec7a839c Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Tue, 21 Apr 2020 08:39:38 -0600 Subject: [PATCH 29/53] fix copyright on docs (#244) --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 130b0909..ec656914 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,8 +21,8 @@ # -- Project information ----------------------------------------------------- project = "matplotcheck" -copyright = "2019, Leah Wasser, Kristin Curry" -author = "Leah Wasser, Kristin Curry" +copyright = "2020, Leah Wasserr" +author = "Leah Wasser" # The short X.Y version version = "0.1.2" From 63fa4e0d2f2d3c4c02898eabe6eeff3e2fc1d0d4 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 23 Apr 2020 11:01:55 -0600 Subject: [PATCH 30/53] Add pillow (#254) * add pillow as a dev requirement * update for pillow addition --- CHANGELOG.md | 1 + dev-requirements.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b656515..a93aa893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* Add `pillow` as a dev requirement (@lwasser, #253) * Removed the `m2r` package from MatPlotCheck (@nkorinek, #247) * Made `assert_string_contains()` accept a string instead of a list (@nkorinek, #53) * Added functions to get and assert the midpoint values of bins in a histogram (@nkorinek, #184) diff --git a/dev-requirements.txt b/dev-requirements.txt index 131f668d..a400be32 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -10,3 +10,4 @@ setuptools==46.1.3 pre-commit==1.20.0 pip==19.0.3 descartes==1.1.0 +pillow From d8b99e303d065fa0cd555ba40604b748f0b67d9b Mon Sep 17 00:00:00 2001 From: nkorinek Date: Mon, 27 Apr 2020 15:00:06 -0600 Subject: [PATCH 31/53] Fixed issue with new way of checking that the data is covered by the line --- matplotcheck/base.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/matplotcheck/base.py b/matplotcheck/base.py index d9b2bded..295d633d 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -1102,9 +1102,13 @@ def assert_line( ): flag_exist = True line_x_vals = [coord[0] for coord in path_verts] - if math.isclose( - min(line_x_vals), min_val, abs_tol=1e-4 - ) and math.isclose(max(line_x_vals), max_val, abs_tol=1e-4): + if ( + math.isclose(min(line_x_vals), min_val, abs_tol=1e-4) + or min(line_x_vals) <= min_val + ) and ( + math.isclose(max(line_x_vals), max_val, abs_tol=1e-4) + or max(line_x_vals) >= max_val + ): flag_length = True break From 3c90acd8be4e87938638f479f0b820b58a299eb7 Mon Sep 17 00:00:00 2001 From: nkorinek Date: Tue, 28 Apr 2020 14:06:05 -0600 Subject: [PATCH 32/53] small fixes! --- examples/plot_line_testing.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index a87605c5..1821c1f5 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -2,7 +2,7 @@ Testing Line Plots ================== -These are some examples of using the basic functionality of MatPlotCheck +These are some examples of using the basic functionality of Matplotcheck to test line plots (including regression lines) in Python. """ @@ -51,8 +51,8 @@ plt.show() ################################################################################ -# Testing the Line Plot -# --------------------- +# Test Line Plots +# --------------- # Now you can make a PlotTester object and test the line plot. You can test # that the line type is a one to one line or a regression line, and you can # test that the line has the correct y intercept and slope. @@ -61,8 +61,8 @@ line_figure_tests = pt.PlotTester(ax) ################################################################################ -# Testing the Line Types -# ---------------------- +# Test Line Types +# --------------- # As you can see, there are two line types on this plot: a one-to-one line for # reference and a regression line of the data points. You can use the function # ``assert_lines_of_type()`` to test if a one to one or regression line @@ -73,8 +73,8 @@ line_figure_tests.assert_lines_of_type(line_types=['regression', 'onetoone']) ################################################################################ -# Testing the Slope and Y Intercept -# --------------------------------- +# Test Slope and Y Intercept +# -------------------------- # Other aspects of the line plot that you can test are the slope and y # intercept to ensure that the line represents the correct values. If you made # your line from a list of vertices, you can use the @@ -102,7 +102,7 @@ # 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 +# First, import the Notebook module from Matplotcheck import matplotcheck.notebook as nb # Plot the data From 5f6853955c94209bc0e4650494cff7b3df9a69c0 Mon Sep 17 00:00:00 2001 From: nkorinek Date: Wed, 29 Apr 2020 18:03:05 -0600 Subject: [PATCH 33/53] Added seaborn to the dev requirements --- dev-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-requirements.txt b/dev-requirements.txt index 18de722e..7044a55a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -11,3 +11,4 @@ pre-commit==1.20.0 pip==19.0.3 descartes==1.1.0 pillow==7.1.2 +seaborn>=0.10.1 From 978ab543074a3f1ee10f7929ee7286c56264e8cb Mon Sep 17 00:00:00 2001 From: nkorinek Date: Wed, 29 Apr 2020 18:13:29 -0600 Subject: [PATCH 34/53] Slight change to dev requirements --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 7044a55a..d2f68591 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -11,4 +11,4 @@ pre-commit==1.20.0 pip==19.0.3 descartes==1.1.0 pillow==7.1.2 -seaborn>=0.10.1 +seaborn==0.10.1 From d6f70c4e326f3bfca5c43b2238200ad2fcd4846b Mon Sep 17 00:00:00 2001 From: nkorinek Date: Thu, 30 Apr 2020 12:11:40 -0600 Subject: [PATCH 35/53] Getting a valid version number --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index d2f68591..bb0a9ebd 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -11,4 +11,4 @@ pre-commit==1.20.0 pip==19.0.3 descartes==1.1.0 pillow==7.1.2 -seaborn==0.10.1 +seaborn>=0.9.1 From ef9dfc9ee46cfdb41f0d36b2838187c2c5e367fd Mon Sep 17 00:00:00 2001 From: nkorinek Date: Thu, 30 Apr 2020 14:16:30 -0600 Subject: [PATCH 36/53] Small modification to the title --- examples/plot_line_testing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index 1821c1f5..2a15e924 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -1,6 +1,6 @@ """ -Testing Line Plots -================== +Test Line Plots with Matplotcheck +================================= These are some examples of using the basic functionality of Matplotcheck to test line plots (including regression lines) in Python. From 4dd705654ef07d543b44029199f248c834a1f523 Mon Sep 17 00:00:00 2001 From: nkorinek Date: Thu, 30 Apr 2020 17:15:37 -0600 Subject: [PATCH 37/53] Small style update to remove 'we' from the vignette --- examples/plot_line_testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index 2a15e924..c67860d1 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -21,7 +21,7 @@ ################################################################################ # Create Example Data # ------------------- -# Before we create a plot, we need to create some data. For plots with +# Before you create a plot, you need to create some data. For plots with # regression lines, you will need data in which you are looking for trends # over time, such as maximum values from lidar-derived measurements. From 218752d324bff31dd8351459ddfeceee38691c15 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 30 Apr 2020 18:29:31 -0600 Subject: [PATCH 38/53] Update examples/plot_line_testing.py --- examples/plot_line_testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index c67860d1..bb9eb60d 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -51,7 +51,7 @@ plt.show() ################################################################################ -# Test Line Plots +# Test Line Plots Using a basic matplotcheck.PlotTester object # --------------- # Now you can make a PlotTester object and test the line plot. You can test # that the line type is a one to one line or a regression line, and you can From e036b9dfdaefd1edb89d95f23e67aad7eb6180ed Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 30 Apr 2020 18:37:41 -0600 Subject: [PATCH 39/53] minor edits --- examples/plot_line_testing.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index bb9eb60d..2eb15e62 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -51,22 +51,22 @@ plt.show() ################################################################################ -# Test Line Plots Using a basic matplotcheck.PlotTester object -# --------------- -# Now you can make a PlotTester object and test the line plot. You can test -# that the line type is a one to one line or a regression line, and you can -# test that the line has the correct y intercept and slope. +# Test Line Plots Using a matplotcheck.PlotTester Object +# ------------------------------------------------------------ +# Now you can make a ``matplotcheck.PlotTester`` object and test the line plot. +# You can test that the line type is a one to one line or a regression line, +# and you can test that the line has the correct y intercept and slope. -# Convert axes object into a PlotTester object +# Convert matplotlib plot axes object into a matplotcheck PlotTester object line_figure_tests = pt.PlotTester(ax) ################################################################################ # Test Line Types # --------------- -# As you can see, there are two line types on this plot: a one-to-one line for -# reference and a regression line of the data points. You can use the function +# There are two line types on the plot above: a one-to-one line for +# reference and a regression line derived from the data points. You can use the method # ``assert_lines_of_type()`` to test if a one to one or regression line -# (or both types of line) are present in your plot. (These are the only types +# (or both line types) are present in the plot. (NOTE: Regression and 1:1 lines are the only types # of lines you can currently test for with this function.) # Check line types @@ -75,11 +75,11 @@ ################################################################################ # Test Slope and Y Intercept # -------------------------- -# Other aspects of the line plot that you can test are the slope and y -# intercept to ensure that the line represents the correct values. If you made +# 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, -# in order to get the slope and y intercept. However, if you made your line +# 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 @@ -95,8 +95,8 @@ ################################################################################ -# Access the Axes object in a Jupyter Notebook -# -------------------------------------------- +# Access a Matplotlib Axes object in a Jupyter Notebook +# ------------------------------------------------------ # 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 From efab5a275e052ea75560cad7d7433e9c5b6b81fb Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Thu, 30 Apr 2020 18:26:47 -0600 Subject: [PATCH 40/53] Changelog to rst (#267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update setuptools from 42.0.2 to 45.2.0 (#190) * raster inherits from vector rather than base (#76) * Allow geodataframes in assert_polygons (#188) * fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek * update changelog for release * Bump version: 0.1.1 → 0.1.2 * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette * Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek * Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README * Update setuptools from 45.2.0 to 46.0.0 (#215) * Update pytest from 5.3.5 to 5.4.1 * Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe * merge * Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser * Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser * Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser * Update setuptools from 46.1.1 to 46.1.3 (#231) * Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser * M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates * # This is a combination of 2 commits. # This is the 1st commit message: Update setuptools from 42.0.2 to 45.2.0 (#190) raster inherits from vector rather than base (#76) Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek update changelog for release Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Update setuptools from 45.2.0 to 46.0.0 (#215) Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Update pytest from 5.3.5 to 5.4.1 merge Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser Update setuptools from 46.1.1 to 46.1.3 (#231) Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek Get images function (#193) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser Co-authored-by: Leah Wasser Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser # This is the commit message #2: Update setuptools from 46.1.1 to 46.1.3 (#231) * fix copyright on docs (#244) * Add pillow (#254) * add pillow as a dev requirement * update for pillow addition * Changed the changelog to an rst file instead of a markdown file and added it to the docs build. * Put changelog changes into the new changelog lol Co-authored-by: pyup.io bot Co-authored-by: Leah Wasser --- CHANGELOG.md | 50 ----------------------------- CHANGELOG.rst | 79 ++++++++++++++++++++++++++++++++++++++++++++++ docs/changelog.rst | 1 + docs/index.rst | 1 + 4 files changed, 81 insertions(+), 50 deletions(-) delete mode 100644 CHANGELOG.md create mode 100644 CHANGELOG.rst create mode 100644 docs/changelog.rst diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 1c6f1f96..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,50 +0,0 @@ -# MatPlotCheck Release Notes - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] -* Add a vignette for testing vector data plots. (@nkorinek, #208) -* Add `pillow` as a dev requirement (@lwasser, #253) -* Removed the `m2r` package from MatPlotCheck (@nkorinek, #247) -* Made `assert_string_contains()` accept a string instead of a list (@nkorinek, #53) -* Added functions to get and assert the midpoint values of bins in a histogram (@nkorinek, #184) -* Created tests for the autograde module (@nkorinek, #105) -* Created tests for the vector module (@nkorinek, #209) -* Created functions to test point geometries in VectorTester (@nkorinek, #176) -* made `assert_string_contains()` accept correct strings with spaces in them (@nkorinek, #182) -* added contributors file and updated README to remove that information (@nkorinek, #121) - -## [0.1.2] -* Adding flake8 for format and other syntax issues! yay! (@lwasser, #195) -* Created a vignette covering the testing of histograms (@ryla5068, #149) -* Created `get_plot_image()` function for the RasterTester object (@nkorinek, #192) -* Allowed `assert_polygons()` to accept GeoDataFrames and added tests (@nkorinek, #175) -* raster test inherits from vector to allow for multi layer plot testing (@lwasser, #75) - -## [0.1.1] -* Added test for bin heights of histograms (@ryla5068, #124) -* Added support for overlapping histograms in histogram tests (@ryla5068, #123) - -## [0.1.0] -* Created a vignette covering base plot tester functionality (@ryla5068, #122) -* fix pip version to ensure pyproj installs in black tox envt (@lwasser, #144) -* Changed `get_caption()` to return a string (@ryla5068, #125) -* Updated `assert_xlabel_ydata()` to support pulling text from x-labels (@ryla5068, #125) -* Fixed `assert_xydata()` incorrectly failing on some floating point numbers (@ryla5068, #124) -* Updated all string content assertions in base to use the same syntax (@ryla5068, #132) -* Moved tests for titles to `test_base_titles.py` (@ryla5068, #115) -* Created `test_base_data.py` for data tests (@ryla5068, #114) -* Added custom error messages to all assert functions in base module (@ryla5068, #106) -* Added all missing docstrings to base module and updated existing ones (@ryla5068, #102) -* Added significant test coverage to base module (@ryla5068, #101) -* Replaced references to EarthPy in CONTRIBUTING.rst (@ryla5068, #100) -* Add tests for raster module (@kysolvik, #32) -* Added tests for base module -- legend check methods (@kysolvik, #38) -* Added tests for base modules -- axis check methods (@kysolvik, #37) -* Add conftest.py to centralize pytest fixtures (@kysolvik, #35) -* Fix issue with pip 19.1 breaking appveyor build (@kysolvik, #46) -* Fix Python version mismatch in pre-commit hook vs dev environment (@kysolvik, #31) -* Adding cross platform builds to CI diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 00000000..3c68e212 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,79 @@ +MatPlotCheck Release Notes +========================== + +All notable changes to this project will be documented in this file. + +The format is based on `Keep a Changelog `_, and this project adheres to +`Semantic Versioning `_. + +Unreleased +---------- + +- Changed changelog to an rst file. (@nkorinek, #266) +- Add a vignette for testing vector data plots. (@nkorinek, #208) +- Add ``pillow`` as a dev requirement (@lwasser, #253) +- Removed the ``m2r`` package from MatPlotCheck (@nkorinek, #247) +- Made ``assert_string_contains()`` accept a string instead of a list + (@nkorinek, #53) +- Added functions to get and assert the midpoint values of bins in a + histogram (@nkorinek, #184) +- Created tests for the autograde module (@nkorinek, #105) +- Created tests for the vector module (@nkorinek, #209) +- Created functions to test point geometries in VectorTester + (@nkorinek, #176) +- made ``assert_string_contains()`` accept correct strings with spaces + in them (@nkorinek, #182) +- added contributors file and updated README to remove that information + (@nkorinek, #121) + +0.1.2 +----- + +- Adding flake8 for format and other syntax issues! yay! (@lwasser, + #195) +- Created a vignette covering the testing of histograms (@ryla5068, + #149) +- Created ``get_plot_image()`` function for the RasterTester object + (@nkorinek, #192) +- Allowed ``assert_polygons()`` to accept GeoDataFrames and added tests + (@nkorinek, #175) +- raster test inherits from vector to allow for multi layer plot + testing (@lwasser, #75) + +0.1.1 +----- + +- Added test for bin heights of histograms (@ryla5068, #124) +- Added support for overlapping histograms in histogram tests + (@ryla5068, #123) + +0.1.0 +----- + +- Created a vignette covering base plot tester functionality + (@ryla5068, #122) +- fix pip version to ensure pyproj installs in black tox envt + (@lwasser, #144) +- Changed ``get_caption()`` to return a string (@ryla5068, #125) +- Updated ``assert_xlabel_ydata()`` to support pulling text from + x-labels (@ryla5068, #125) +- Fixed ``assert_xydata()`` incorrectly failing on some floating point + numbers (@ryla5068, #124) +- Updated all string content assertions in base to use the same syntax + (@ryla5068, #132) +- Moved tests for titles to ``test_base_titles.py`` (@ryla5068, #115) +- Created ``test_base_data.py`` for data tests (@ryla5068, #114) +- Added custom error messages to all assert functions in base module + (@ryla5068, #106) +- Added all missing docstrings to base module and updated existing ones + (@ryla5068, #102) +- Added significant test coverage to base module (@ryla5068, #101) +- Replaced references to EarthPy in CONTRIBUTING.rst (@ryla5068, #100) +- Add tests for raster module (@kysolvik, #32) +- Added tests for base module – legend check methods (@kysolvik, #38) +- Added tests for base modules – axis check methods (@kysolvik, #37) +- Add conftest.py to centralize pytest fixtures (@kysolvik, #35) +- Fix issue with pip 19.1 breaking appveyor build (@kysolvik, #46) +- Fix Python version mismatch in pre-commit hook vs dev environment + (@kysolvik, #31) +- Adding cross platform builds to CI diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 00000000..565b0521 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1 @@ +.. include:: ../CHANGELOG.rst diff --git a/docs/index.rst b/docs/index.rst index fa3b99f5..710599bd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,7 @@ It currently supports. Examples Gallery contributing code-of-conduct + changelog Indices and tables ================== From a7dfdc77d776dab2f63aaa4e223b5ae444c4d52b Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 30 Apr 2020 18:27:05 -0600 Subject: [PATCH 41/53] setup greetings! (#257) --- .github/workflows/greetings.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/greetings.yml diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 00000000..28ee6b2f --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,13 @@ +name: Greetings + +on: [pull_request, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: 'Message that will be displayed on users'' first issue' + pr-message: 'Message that will be displayed on users'' first pr' From 74c770a5e0142c7767d25fdcc3bff2ab97146d9a Mon Sep 17 00:00:00 2001 From: nkorinek Date: Fri, 1 May 2020 16:00:20 -0600 Subject: [PATCH 42/53] Added some of the changes requested on github --- examples/plot_line_testing.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index c67860d1..68e5b601 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -28,13 +28,13 @@ # Create dataframe of data points col1 = list(np.random.randint(25, size=15)) col2 = list(np.random.randint(25, size=15)) -data = pd.DataFrame(list(zip(col1, col2)), columns=['Data1', 'Data2']) +data = pd.DataFrame(list(zip(col1, col2)), columns=['data1', 'data2']) # Plot data points, regression line, and one-to-one (1:1) line for reference fig, ax = plt.subplots() # Points and regression line -sns.regplot('Data1', 'Data2', +sns.regplot('data1', 'data2', data=data, color='purple', ax=ax) @@ -42,8 +42,8 @@ # 1:1 line ax.plot((0, 1), (0, 1), transform=ax.transAxes, ls='--', c='k') -ax.set(xlabel='Data1', - ylabel='Data2', +ax.set(xlabel='data1', + ylabel='data2', title='Example Data Regression Plot', xlim=(0, 25), ylim=(0, 25)) @@ -88,19 +88,19 @@ # Get slope and y intercept data of regression line for testing slope_data, intercept_data, _, _, _ = stats.linregress( - data.Data1, data.Data2) + data.data1, data.data2) # Check that slope and y intercept are correct (expected) values line_figure_tests.assert_line(slope_exp=slope_data, intercept_exp=intercept_data) ################################################################################ -# Access the Axes object in a Jupyter Notebook -# -------------------------------------------- -# 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. +# +# .. 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 @@ -109,7 +109,7 @@ fig, ax = plt.subplots() # Points and regression line -sns.regplot('Data1', 'Data2', +sns.regplot('data1', 'data2', data=data, color='purple', ax=ax) @@ -117,8 +117,8 @@ # 1:1 line ax.plot((0, 1), (0, 1), transform=ax.transAxes, ls='--', c='k') -ax.set(xlabel='Data1', - ylabel='Data2', +ax.set(xlabel='data1', + ylabel='data2', title='Example Data Regression Plot', xlim=(0, 25), ylim=(0, 25)); From 75e2fdd980a2d0a283fc4589ba1e2f3ebb0ef76c Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Wed, 13 May 2020 17:01:55 -0600 Subject: [PATCH 43/53] small edits --- examples/plot_line_testing.py | 51 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index 9a7e4f50..643b56a1 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -2,14 +2,13 @@ Test Line Plots with Matplotcheck ================================= -These are some examples of using the basic functionality of Matplotcheck -to test line plots (including regression lines) in Python. +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. """ ################################################################################ -# Import Packages -# --------------- -# You will start by importing the required packages and plotting a line plot. +# To begin, import the required Python packages. import matplotlib.pyplot as plt import seaborn as sns @@ -21,11 +20,12 @@ ################################################################################ # Create Example Data # ------------------- -# Before you create a plot, you need to create some data. For plots with -# regression lines, you will need data in which you are looking for trends -# over time, such as maximum values from lidar-derived measurements. +# 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 dataframe of data points +# Create Pandas DataFrame containing data points col1 = list(np.random.randint(25, size=15)) col2 = list(np.random.randint(25, size=15)) data = pd.DataFrame(list(zip(col1, col2)), columns=['data1', 'data2']) @@ -33,13 +33,13 @@ # Plot data points, regression line, and one-to-one (1:1) line for reference fig, ax = plt.subplots() -# Points and regression line +# Use Seaborn to calculate and plot a regression line + associated points sns.regplot('data1', 'data2', data=data, color='purple', ax=ax) -# 1:1 line +# Add 1:1 line to your plot ax.plot((0, 1), (0, 1), transform=ax.transAxes, ls='--', c='k') ax.set(xlabel='data1', @@ -53,24 +53,23 @@ ################################################################################ # Test Line Plots Using a matplotcheck.PlotTester Object # ------------------------------------------------------------ -# Now you can make a ``matplotcheck.PlotTester`` object and test the line plot. -# You can test that the line type is a one to one line or a regression line, -# and you can test that the line has the correct y intercept and slope. +# 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_figure_tests = pt.PlotTester(ax) +line_plot_tester = pt.PlotTester(ax) ################################################################################ -# Test Line Types -# --------------- -# There are two line types on the plot above: a one-to-one line for -# reference and a regression line derived from the data points. You can use the method -# ``assert_lines_of_type()`` to test if a one to one or regression line -# (or both line types) are present in the plot. (NOTE: Regression and 1:1 lines are the only types -# of lines you can currently test for with this function.) +# 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 +# regression line (or both line types) are present in the plot. # Check line types -line_figure_tests.assert_lines_of_type(line_types=['regression', 'onetoone']) +line_plot_tester.assert_lines_of_type(line_types=['regression', 'onetoone']) ################################################################################ # Test Slope and Y Intercept @@ -91,7 +90,7 @@ data.data1, data.data2) # Check that slope and y intercept are correct (expected) values -line_figure_tests.assert_line(slope_exp=slope_data, intercept_exp=intercept_data) +line_plot_tester.assert_line(slope_exp=slope_data, intercept_exp=intercept_data) ################################################################################ @@ -126,9 +125,9 @@ # 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. -plot_test_hold = nb.convert_axes(plt, which_axes="current") +axis_object = nb.convert_axes(plt, which_axes="current") # This object can then be turned into a PlotTester object. -line_figure_tests = pt.PlotTester(plot_test_hold) +line_plot_tester_2 = pt.PlotTester(axis_object) # Now you can run the tests as you did earlier! From 43cdccd41d740013823fd5246a9b7a4f43f4d48f Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Wed, 13 May 2020 17:03:00 -0600 Subject: [PATCH 44/53] title update --- examples/plot_line_testing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index 643b56a1..be161b9b 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -1,6 +1,6 @@ """ -Test Line Plots with Matplotcheck -================================= +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 From d3c1ba6de55bab96a12e119d3312a25ccb0096e2 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Mon, 11 May 2020 18:18:47 -0600 Subject: [PATCH 45/53] Add template for PR --- .github/pull_request_template.md | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..57ad9b47 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,45 @@ +# Earth Lab -- Pull Request Template +Welcome to MatPlotCheck! + +Before submitting a Pull Request please be sure to submit an issue that describes what the contents of the PR addresses. + +**IMPORTANT:** Please do not use this repository as a practice activity to learn how to use GitHub! Only submit a PR after: + +1. Submitting an issue with a valid item that you wish to address in earthpy +2. Getting approval from one of the maintainers to submit a formal PR. + +## Description + +Please include a summary of the change and which issue is fixed. Be sure to reference the ISSUE that this PR addresses which should have been approved by an earthpy maintainer. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update + +## How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration + +- [ ] Test A +- [ ] Test B + + +## Checklist: + +- [ ] I have already submitted an issue and it was approved for a pr by an earthpy maintainer +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules +- [ ] I have checked my code and corrected any misspellings From a850b768b5784fcf609d38ee2ae1c40d724954e2 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Wed, 13 May 2020 16:35:10 -0600 Subject: [PATCH 46/53] Timeseries tests (#273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added tests for timeseries module. * Fixed a bug where assert_xydata() sometimes failed to compare numpy arrays when dtype was object * Changed behavior of assert_xydata() to display standard error message when xy_data and xy_expected do not have the same shape. * remove extensive docs within test file * Update setuptools from 42.0.2 to 45.2.0 (#190) * raster inherits from vector rather than base (#76) * Allow geodataframes in assert_polygons (#188) * fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek * update changelog for release * Bump version: 0.1.1 → 0.1.2 * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette * Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek * Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README * Update setuptools from 45.2.0 to 46.0.0 (#215) * Update pytest from 5.3.5 to 5.4.1 * Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe * merge * Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser * Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser * Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser * Update setuptools from 46.1.1 to 46.1.3 (#231) * Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser * M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates * # This is a combination of 2 commits. # This is the 1st commit message: Update setuptools from 42.0.2 to 45.2.0 (#190) raster inherits from vector rather than base (#76) Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek update changelog for release Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Update setuptools from 45.2.0 to 46.0.0 (#215) Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Update pytest from 5.3.5 to 5.4.1 merge Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser Update setuptools from 46.1.1 to 46.1.3 (#231) Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek Get images function (#193) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser Co-authored-by: Leah Wasser Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser # This is the commit message #2: Update setuptools from 46.1.1 to 46.1.3 (#231) * Change handling of arrays of different shape from AssertionError to ValueError * Updated Changelog * fix copyright on docs (#244) * Add pillow (#254) * add pillow as a dev requirement * update for pillow addition * Update matplotcheck/base.py Co-Authored-By: Nathan Korinek * PEP 8 import order * pep 8 imports fix * Changelog to rst (#267) * Update setuptools from 42.0.2 to 45.2.0 (#190) * raster inherits from vector rather than base (#76) * Allow geodataframes in assert_polygons (#188) * fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek * update changelog for release * Bump version: 0.1.1 → 0.1.2 * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette * Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek * Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README * Update setuptools from 45.2.0 to 46.0.0 (#215) * Update pytest from 5.3.5 to 5.4.1 * Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe * merge * Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser * Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser * Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser * Update setuptools from 46.1.1 to 46.1.3 (#231) * Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser * M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates * # This is a combination of 2 commits. # This is the 1st commit message: Update setuptools from 42.0.2 to 45.2.0 (#190) raster inherits from vector rather than base (#76) Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek update changelog for release Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Update setuptools from 45.2.0 to 46.0.0 (#215) Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Update pytest from 5.3.5 to 5.4.1 merge Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser Update setuptools from 46.1.1 to 46.1.3 (#231) Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek Get images function (#193) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser Co-authored-by: Leah Wasser Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser # This is the commit message #2: Update setuptools from 46.1.1 to 46.1.3 (#231) * fix copyright on docs (#244) * Add pillow (#254) * add pillow as a dev requirement * update for pillow addition * Changed the changelog to an rst file instead of a markdown file and added it to the docs build. * Put changelog changes into the new changelog lol Co-authored-by: pyup.io bot Co-authored-by: Leah Wasser * setup greetings! (#257) * Final changes * delete markdown changelog * Added changes to take away unneccesary parameters * fixed merge error * black * small reword to force rebuild Co-authored-by: Ryan LaRocque Co-authored-by: Leah Wasser Co-authored-by: pyup.io bot --- CHANGELOG.rst | 2 + matplotcheck/base.py | 54 +++++++------ matplotcheck/tests/test_timeseries_module.py | 82 ++++++++++++++++++-- 3 files changed, 106 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 88057a6d..8bbec950 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,6 +27,8 @@ Unreleased (@nkorinek, #121) - Changed tolerance functionality from relative tolerance to absolute tolerance. (@ryla5068, #234) +- Improved handling of datasets with different shapes in base.assert_xy() (@ryla5068, #233) +- Bug fix for handling object datatypes in base.assert_xy() (@ryla5068, #232) 0.1.2 ----- diff --git a/matplotcheck/base.py b/matplotcheck/base.py index 3f896c47..a7c9213e 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -8,14 +8,13 @@ """ import numpy as np -import matplotlib.dates as mdates import matplotlib from matplotlib.backend_bases import RendererBase import math from scipy import stats import pandas as pd -import geopandas as gpd import numbers +import geopandas as gpd class InvalidPlotError(Exception): @@ -766,7 +765,7 @@ def assert_no_legend_overlap(self, message="Legends overlap eachother"): """ BASIC PLOT DATA FUNCTIONS """ - def get_xy(self, points_only=False, xtime=False): + def get_xy(self, points_only=False): """Returns a pandas dataframe with columns "x" and "y" holding the x and y coords on Axes `ax` @@ -777,9 +776,6 @@ def get_xy(self, points_only=False, xtime=False): points_only : boolean Set ``True`` to check only points, set ``False`` to check all data on plot. - xtime : boolean - Set equal to True if the x axis of the plot contains datetime - values Returns ------- @@ -820,9 +816,6 @@ def get_xy(self, points_only=False, xtime=False): xy_data = xy_data[xy_data["x"] >= lims[0]] xy_data = xy_data[xy_data["x"] <= lims[1]].reset_index(drop=True) - # change to datetime dtype if needed - if xtime: - xy_data["x"] = mdates.num2date(xy_data["x"]) return xy_data def assert_xydata( @@ -831,7 +824,6 @@ def assert_xydata( xcol=None, ycol=None, points_only=False, - xtime=False, xlabels=False, tolerance=0, message="Incorrect data values", @@ -856,11 +848,6 @@ def assert_xydata( points_only : boolean, Set ``True`` to check only points, set ``False`` tp check all data on plot. - xtime : boolean - Set ``True`` if the a-axis contains datetime values. Matplotlib - converts datetime objects to seconds? This parameter will ensure - the provided x col values are converted if they are datetime - elements. xlabels : boolean Set ``True`` if using x axis labels rather than x data. Instead of comparing numbers in the x-column to expected, compares numbers or @@ -906,7 +893,7 @@ def assert_xydata( xy_expected, xcol=xcol, ycol=ycol, message=message ) return - xy_data = self.get_xy(points_only=points_only, xtime=xtime) + xy_data = self.get_xy(points_only=points_only) # Make sure the data are sorted the same xy_data, xy_expected = ( @@ -915,8 +902,6 @@ def assert_xydata( ) if tolerance > 0: - if xtime: - raise ValueError("tolerance must be 0 with datetime on x-axis") np.testing.assert_allclose( xy_data["x"], xy_expected[xcol], @@ -934,20 +919,41 @@ def assert_xydata( """We use `assert_array_max_ulp()` to compare the two datasets because it is able to account for small errors in floating point numbers, and it scales nicely between extremely - small or large numbers. We catch this error and throw our own so - that we can use our own message.""" + small or large numbers. Because of the way that matplotlib stores + datetime data, this is essential for comparing high-precision + datetime data (i.e. millisecond or lower). + + We catch this error and raise our own that is more relevant to + the assertion being run.""" try: np.testing.assert_array_max_ulp( - np.array(xy_data["x"]), np.array(xy_expected[xcol]) + xy_data["x"].to_numpy(dtype=np.float64), + xy_expected[xcol].to_numpy(dtype=np.float64), + 5, ) except AssertionError: + # xy_data and xy_expected do not contain the same data raise AssertionError(message) + except ValueError: + # xy_data and xy_expected do not have the same shape + raise ValueError( + "xy_data and xy_expected do not have the same shape" + ) try: np.testing.assert_array_max_ulp( - np.array(xy_data["y"]), np.array(xy_expected[ycol]) + xy_data["y"].to_numpy(dtype=np.float64), + xy_expected[ycol].to_numpy(dtype=np.float64), + 5, ) + except AssertionError: + # xy_data and xy_expected do not contain the same data raise AssertionError(message) + except ValueError: + # xy_data and xy_expected do not have the same shape + raise ValueError( + "xy_data and xy_expected do not have the same shape" + ) def assert_xlabel_ydata( self, xy_expected, xcol, ycol, message="Incorrect Data" @@ -1015,7 +1021,7 @@ def assert_xlabel_ydata( if x_is_numeric: try: np.testing.assert_array_max_ulp( - np.array(xy_data["x"]), np.array(xy_expected[xcol]) + np.array(xy_data["x"]), np.array(xy_expected[xcol]), ) except AssertionError: raise AssertionError(message) @@ -1167,7 +1173,7 @@ def get_num_bins(self): overlapping or stacked histograms in the same `matplotlib.axis.Axis` object, then this returns the number of bins with unique edges. """ - x_data = self.get_xy(xtime=False)["x"] + x_data = self.get_xy()["x"] unique_x_data = list(set(x_data)) num_bins = len(unique_x_data) diff --git a/matplotcheck/tests/test_timeseries_module.py b/matplotcheck/tests/test_timeseries_module.py index b1be1448..e8179bf5 100644 --- a/matplotcheck/tests/test_timeseries_module.py +++ b/matplotcheck/tests/test_timeseries_module.py @@ -1,9 +1,75 @@ -''' -def test_assert_xydata_timeseries(pt_time_line_plt, pd_df_timeseries): - """Commenting this out for now as this requires a time series data object - this is failing because the time data needs to be in seconds like how - mpl saves it. """ - pt_time_line_plt.assert_xydata( - pd_df_timeseries, xcol='time', ycol='A', xtime=True +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import pytest +from matplotcheck.timeseries import TimeSeriesTester + + +@pytest.fixture +def pd_df_timeseries(): + """Create a pandas dataframe for testing, with timeseries in one column""" + return pd.DataFrame( + { + "time": pd.date_range(start="1/1/2018", periods=100), + "A": np.random.randint(0, 100, size=100), + } ) -''' + + +@pytest.fixture +def pt_time_line_plt(pd_df_timeseries): + """Create timeseries line plot for testing""" + fig, ax = plt.subplots() + pd_df_timeseries.plot("time", "A", kind="line", ax=ax) + axis = plt.gca() + + return TimeSeriesTester(axis) + + +def test_assert_xydata_timeseries(pt_time_line_plt): + """Tests that assert_xydata() correctly passes with matching timeseries + data.""" + data = pt_time_line_plt.get_xy() + pt_time_line_plt.assert_xydata(data, xcol="x", ycol="y") + + +def test_assert_xydata_timeseries_fails(pt_time_line_plt): + """Tests that assert_xydata() correctly fails without matching timeseries + data.""" + data = pt_time_line_plt.get_xy() + data.loc[0, "x"] = 100 + with pytest.raises(AssertionError, match="Incorrect data values"): + pt_time_line_plt.assert_xydata(data, xcol="x", ycol="y") + + +def test_assert_xydata_timeseries_truncation_error( + pt_time_line_plt, pd_df_timeseries +): + """Tests that assert_xydata() handles floating-point truncation error + gracefully for timeseries data.""" + + pt1 = pt_time_line_plt + + # Create second plottester object with slightly different time values + # The change in values here should be small enough that it gets truncated + # in matplotlib's conversion of datetime data + for i in range(len(pd_df_timeseries)): + pd_df_timeseries.loc[i, "time"] = pd_df_timeseries.loc[ + i, "time" + ] + pd.Timedelta(1) + fig, ax2 = plt.subplots() + pd_df_timeseries.plot("time", "A", kind="line", ax=ax2) + pt2 = TimeSeriesTester(ax2) + + # Test that the two datasets assert as equal + data1 = pt1.get_xy() + pt2.assert_xydata(data1, xcol="x", ycol="y") + + +def test_assert_xydata_timeseries_roundoff_error(pt_time_line_plt): + """Tests that assert_xydata() handles floating-point roundoff error + gracefully for timeseries data.""" + data = pt_time_line_plt.get_xy() + data.loc[0, "x"] = data.loc[0, "x"] + 0.00000000001 + + pt_time_line_plt.assert_xydata(data, xcol="x", ycol="y") From 43bc9cbcd50e640228d4c49c5cfe59145979c5e3 Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Thu, 14 May 2020 02:09:27 +0200 Subject: [PATCH 47/53] Update pytest from 5.4.1 to 5.4.2 (#275) --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 18de722e..6ef8f581 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -5,7 +5,7 @@ sphinx-autobuild==0.7.1 pytest-vcr==1.0.2 pytest-cov==2.8.1 codecov==2.0.22 -pytest==5.4.1 +pytest==5.4.2 setuptools==46.1.3 pre-commit==1.20.0 pip==19.0.3 From f81f819eda2f12cdfcfdf356141f989c9c0386a5 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 14 May 2020 09:50:49 -0600 Subject: [PATCH 48/53] remove rebase --- .github/workflows/rebase.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .github/workflows/rebase.yml diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml deleted file mode 100644 index 550c4a21..00000000 --- a/.github/workflows/rebase.yml +++ /dev/null @@ -1,12 +0,0 @@ -on: issue_comment -name: Automatic Rebase -jobs: - rebase: - name: Rebase - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: Automatic Rebase - uses: cirrus-actions/rebase@1.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 57646ec0c229689ad8cc98bd7e1d97845955f931 Mon Sep 17 00:00:00 2001 From: Nathan Korinek Date: Thu, 14 May 2020 11:18:44 -0600 Subject: [PATCH 49/53] Test coverage for lines (#274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add line tests for base module, changes to line methods in base module. * Fixed bug with how line limits are checke * Update setuptools from 42.0.2 to 45.2.0 (#190) * raster inherits from vector rather than base (#76) * Allow geodataframes in assert_polygons (#188) * fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek * update changelog for release * Bump version: 0.1.1 → 0.1.2 * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette * Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek * Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README * Update setuptools from 45.2.0 to 46.0.0 (#215) * Update pytest from 5.3.5 to 5.4.1 * Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe * merge * Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser * Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser * Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser * Update setuptools from 46.1.1 to 46.1.3 (#231) * Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser * M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates * # This is a combination of 2 commits. # This is the 1st commit message: Update setuptools from 42.0.2 to 45.2.0 (#190) raster inherits from vector rather than base (#76) Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek update changelog for release Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Update setuptools from 45.2.0 to 46.0.0 (#215) Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Update pytest from 5.3.5 to 5.4.1 merge Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser Update setuptools from 46.1.1 to 46.1.3 (#231) Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek Get images function (#193) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser Co-authored-by: Leah Wasser Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser # This is the commit message #2: Update setuptools from 46.1.1 to 46.1.3 (#231) * Minor change to assert_xy conversion method. * Revert "Minor change to assert_xy conversion method." This reverts commit bfa569fef74938a03362aa04922243a28cf6b28c. * fix copyright on docs (#244) * Add pillow (#254) * add pillow as a dev requirement * update for pillow addition * Improved handline of lines without xy-data (Issue #238) * Added docstrings to line tests. * Added Seaborn to dev requirements * Undo changes regarding x-limits problems (Issue #235) * Fixed issue with new way of checking that the data is covered by the line * Changelog to rst (#267) * Update setuptools from 42.0.2 to 45.2.0 (#190) * raster inherits from vector rather than base (#76) * Allow geodataframes in assert_polygons (#188) * fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek * update changelog for release * Bump version: 0.1.1 → 0.1.2 * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette * Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek * Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README * Update setuptools from 45.2.0 to 46.0.0 (#215) * Update pytest from 5.3.5 to 5.4.1 * Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe * merge * Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser * Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser * Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser * Update setuptools from 46.1.1 to 46.1.3 (#231) * Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser * M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates * # This is a combination of 2 commits. # This is the 1st commit message: Update setuptools from 42.0.2 to 45.2.0 (#190) raster inherits from vector rather than base (#76) Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek update changelog for release Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Update codecov to 2.0.16 (#202) * Update codecov from 2.0.15 to 2.0.16 * update from master (#211) * Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser * Assert string accept spaces (#205) * Made it so that assert_string_contains accepts key words with spaces * black * renaming function * Showed assert_title_contains working with a space in the keyword in vignette Co-authored-by: Nathan Korinek Co-authored-by: Leah Wasser Co-authored-by: Nathan Korinek Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Update setuptools from 45.2.0 to 46.0.0 (#215) Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Update pytest from 5.3.5 to 5.4.1 merge Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser Add tests to the Autograde module (#224) * Added tests for the autograde module * removed unneccesary import * Fixing issues that were failing in base_data * codacy * flake8 * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Update matplotcheck/tests/test_autograde.py Co-Authored-By: Leah Wasser * Updated changelog * flake8 fix Co-authored-by: Leah Wasser Hist bin midpoints (#204) * Add a get and assert function for bin midpoints, as well as tests for those functions * Updated docstrings * Added changelog changes and fixed minor formatting issue * small flake8 fix * Added midpoints functionatlity to the vignette for histogram testing * Adding in changes suggested on GitHub1 * Fixed function description * Took out all instances of in example * update docs * Fixed small bug with section titles * Update matplotcheck/tests/test_base_data.py * reworded changelog Co-authored-by: Leah Wasser Update setuptools from 46.1.1 to 46.1.3 (#231) Title assert accepts strings (#229) * Fixed assert_title_contains to take strings as well as lists * Better implementation * Changelog update Co-authored-by: Leah Wasser M2r remove (#247) * Removed m2r from matplotcheck, and reformated code of conduct * Seeing if rebase works * removed an m2r import * Fixing up the docs a bit * changelog updates Allow geodataframes in assert_polygons (#188) fixing a few syntax errors (#194) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * fixing a few syntax errors * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser * remove tabls Co-authored-by: Nathan Korinek Get images function (#193) * First draft of get_images function * Added a test for get_image * Changelog update * minor formatting change. * black changes * Update matplotcheck/raster.py Co-Authored-By: Leah Wasser Co-authored-by: Leah Wasser Bump version: 0.1.1 → 0.1.2 Add flake8 fix [WIP] (#199) * add flake 8 * yay cleanup * update change log * add flake 8 dep * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * more line length issues * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * fixed test_base_axis.py * fixed text_base_data.py * fixed test_base_legends.py * fixed test_base_titles_captions.py * Format files to be flake8 acceptable (#197) * autograde flake8 fixes * Fixed base to be flake8 compliant (phew) * Made folium.py flake8 compliant * made notebook.py flake8 compliant * Fixed raster.py for flake8 * Fixed timeseries.py * fixed conftest.py * fixed test_base.py * Fixed test_raster.py and issue with legends tests * black * more black changes * Fixing merge conflicts that were found * Changing accepted changes * minor changes * Changing to make CI happy, modified to pass black and make -B docs * Reformatting to make black and flake8 happy. Also updated crs assignment * Fixed formatting issue with timeseries * Better fix for timeseries formatting * Black Co-authored-by: Leah Wasser Contributors update (#213) * Added contributor rst file and moved that information out of the README file * Implemented requested changes to README Add Assert points function & cleanup duplicate methods (#203) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * Taking out redundant tests * Typo * Fixing how vector checks for truth value of a dataframe Additional vector tests (#212) * Added a get_points() and assert_points() function to the vector tester. * Added in get_points() and assert_points() functions with tests * Added proper documentation * Small fix to please codacy * black * Updated changelog * First round of tests for vector * black * Rough draft for bug fix * Fixed bug with multiple geometries plotted alongside bug with identical x values causing failure! * typo * Fixed small bug with markersize * Added comments explaining code * rough drafts of more tests * Added more tests for legends * Added more tests, and took out broken tests * small codacy fix * Fixed test! * Taking out redundant tests * Took out unneccesary tests * Added plt.close * Added more tests * Typo * more tests for uncovered parts of the vector file * black * Fixed issues with vector checking truth value of dataframe, and added tests * Fixing how vector checks for truth value of a dataframe * Added more coverage! * Broke tests up into individual files * black * Added tests for outlier cases * Update matplotcheck/tests/test_points.py typo Co-Authored-By: Leah Wasser * took plt.gca() out of tests * Added changes suggested on GitHub * Update CHANGELOG.md * fix import order * import order fix * import order * import order Co-authored-by: Leah Wasser # This is the commit message #2: Update setuptools from 46.1.1 to 46.1.3 (#231) * fix copyright on docs (#244) * Add pillow (#254) * add pillow as a dev requirement * update for pillow addition * Changed the changelog to an rst file instead of a markdown file and added it to the docs build. * Put changelog changes into the new changelog lol Co-authored-by: pyup.io bot Co-authored-by: Leah Wasser * setup greetings! (#257) * Made checking line coverage optional for base.assert_line() * Fix double requirement in dev-requirements.txt * Change seaborn version * Seaborn requirement version change * Added comment to clarify changes * Merged two PRs and made it so they pass pytest * black * Added more tests to test that the limit checks are correct in the assert_line function * pleasing flake8 issues that came up on the pr * Update matplotcheck/base.py * Update matplotcheck/base.py * small typo fix! Co-authored-by: Ryan (Marty) LaRocque Co-authored-by: pyup.io bot Co-authored-by: Leah Wasser --- CHANGELOG.rst | 4 + dev-requirements.txt | 1 + matplotcheck/base.py | 161 ++++++++++++++-------- matplotcheck/tests/test_base_lines.py | 191 ++++++++++++++++++++++++++ matplotcheck/tests/test_lines.py | 2 +- matplotcheck/timeseries.py | 2 +- matplotcheck/vector.py | 6 +- 7 files changed, 301 insertions(+), 66 deletions(-) create mode 100644 matplotcheck/tests/test_base_lines.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8bbec950..6ba1b78b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ The format is based on `Keep a Changelog ` Unreleased ---------- +- 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) - Add ``pillow`` as a dev requirement (@lwasser, #253) @@ -27,6 +28,9 @@ Unreleased (@nkorinek, #121) - Changed tolerance functionality from relative tolerance to absolute tolerance. (@ryla5068, #234) +- Made checking line coverage optional for `base.assert_line()` + (@ryla5068, #239) +- Fixed bugs involving line tests (@ryla5068, #239) - Improved handling of datasets with different shapes in base.assert_xy() (@ryla5068, #233) - Bug fix for handling object datatypes in base.assert_xy() (@ryla5068, #232) diff --git a/dev-requirements.txt b/dev-requirements.txt index 6ef8f581..36d65867 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -10,4 +10,5 @@ setuptools==46.1.3 pre-commit==1.20.0 pip==19.0.3 descartes==1.1.0 +seaborn>=0.9.1 pillow==7.1.2 diff --git a/matplotcheck/base.py b/matplotcheck/base.py index a7c9213e..37e91e98 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -47,11 +47,11 @@ def _is_line(self): """ if self.ax.lines: - for l in self.ax.lines: + for line in self.ax.lines: if ( - not l.get_linestyle() - or not l.get_linewidth() - or l.get_linewidth() > 0 + not line.get_linestyle() + or not line.get_linewidth() + or line.get_linewidth() > 0 ): return True @@ -68,11 +68,11 @@ def _is_scatter(self): if self.ax.collections: return True elif self.ax.lines: - for l in self.ax.lines: + for line in self.ax.lines: if ( - l.get_linestyle() == "None" - or l.get_linewidth() == "None" - or l.get_linewidth() == 0 + line.get_linestyle() == "None" + or line.get_linewidth() == "None" + or line.get_linewidth() == 0 ): return True return False @@ -482,9 +482,9 @@ def assert_lims( """ # Get axis limit values if axis == "x": - lims = [int(l) for l in self.ax.get_xlim()] + lims = [int(xlim) for xlim in self.ax.get_xlim()] elif axis == "y": - lims = [int(l) for l in self.ax.get_ylim()] + lims = [int(ylim) for ylim in self.ax.get_ylim()] else: raise ValueError( "axis must be one of the following string ['x', 'y']" @@ -673,7 +673,7 @@ def assert_legend_labels( legend_texts = [ t.get_text().lower() for leg in legends for t in leg.get_texts() ] - labels_exp = [l.lower() for l in labels_exp] + labels_exp = [label.lower() for label in labels_exp] num_exp_labs = len(labels_exp) num_actual_labs = len(legend_texts) @@ -786,9 +786,12 @@ def get_xy(self, points_only=False): if points_only: xy_coords = [ val - for l in self.ax.lines - if (l.get_linestyle() == "None" or l.get_linewidth() == "None") - for val in l.get_xydata() + for line in self.ax.lines + if ( + line.get_linestyle() == "None" + or line.get_linewidth() == "None" + ) + for val in line.get_xydata() ] # .plot() xy_coords += [ val @@ -799,7 +802,7 @@ def get_xy(self, points_only=False): else: xy_coords = [ - val for l in self.ax.lines for val in l.get_xydata() + val for line in self.ax.lines for val in line.get_xydata() ] # .plot() xy_coords += [ val for c in self.ax.collections for val in c.get_offsets() @@ -983,8 +986,8 @@ def assert_xlabel_ydata( This is only testing the numbers in x-axis labels. """ x_data = [ - "".join(c for c in l.get_text()) - for l in self.ax.xaxis.get_majorticklabels() + "".join(c for c in label.get_text()) + for label in self.ax.xaxis.get_majorticklabels() ] y_data = self.get_xy()["y"] xy_data = pd.DataFrame(data={"x": x_data, "y": y_data}) @@ -1068,13 +1071,12 @@ def assert_line( self, slope_exp, intercept_exp, - xtime=False, + check_coverage=True, message_no_line="Expected line not displayed", message_data="Line does not cover data set", ): """Asserts that there exists a line on Axes `ax` with slope `slope_exp` - and y-intercept `intercept_exp` and goes at least from x coordinate - `min_val` to x coordinate `max_val` + and y-intercept `intercept_exp` and Parameters ---------- @@ -1082,48 +1084,76 @@ def assert_line( Expected slope of line intercept_exp : float Expeted y intercept of line - xtime : boolean - Set ``True`` if x-axis values are datetime + check_coverage : boolean (default = True) + If `check_coverage` is `True`, function will check that the goes at + least from x coordinate `min_val` to x coordinate `max_val`. If the + line does not cover the entire dataset, and `AssertionError` with + be thrown with message `message_data`. message_no_line : string The error message to be displayed if the line does not exist. message_data : string The error message to be displayed if the line exists but does not - cover the dataset. + cover the dataset, and if `check_coverage` is `True`. Raises ------- AssertionError - with message `m` or `m2` if no line exists that covers the dataset + with message `message_no_line` or `message_data` if no line exists + that covers the dataset. """ - flag_exist, flag_length = False, False - xy = self.get_xy(points_only=True) - min_val, max_val = min(xy["x"]), max(xy["x"]) + flag_exist = False + + if check_coverage: + flag_length = False + xy = self.get_xy(points_only=True) + min_val, max_val = min(xy["x"]), max(xy["x"]) + + for line in self.ax.lines: + # Here we will get the verticies for the line and reformat them in + + # the way that get_slope_yintercept() expects + data = line.get_data() + path_verts = np.column_stack((data[0], data[1])) - for l in self.ax.lines: - path_verts = self.ax.transData.inverted().transform( - l._transformed_path.get_fully_transformed_path().vertices - ) slope, y_intercept = self.get_slope_yintercept(path_verts) if math.isclose(slope, slope_exp, abs_tol=1e-4) and math.isclose( y_intercept, intercept_exp, abs_tol=1e-4 ): flag_exist = True line_x_vals = [coord[0] for coord in path_verts] - if min(line_x_vals) <= min_val and max(line_x_vals) >= max_val: - flag_length = True - break + + # This check ensures that the minimum and maximum values of the + # line are within or very close to the minimum and maximum + # values in the pandas dataframe provided. This accounts for + # small errors sometimes found in matplotlib plots. + if check_coverage: + if ( + math.isclose(min(line_x_vals), min_val, abs_tol=1e-4) + or min(line_x_vals) <= min_val + ) and ( + math.isclose(max(line_x_vals), max_val, abs_tol=1e-4) + or max(line_x_vals) >= max_val + ): + flag_length = True + break assert flag_exist, message_no_line - assert flag_length, message_data + if check_coverage: + assert flag_length, message_data - def assert_lines_of_type(self, line_types): + def assert_lines_of_type(self, line_types, check_coverage=True): """Asserts each line of type in `line_types` exist on `ax` Parameters ---------- - line_types : list of strings + line_types : string or list of strings Acceptable strings in line_types are as follows - ``['regression', 'onetoone']``. + ``['linear-regression', 'onetoone']``. + check_coverage : boolean (default = True) + If `check_coverage` is `True`, function will check that the goes at + least from x coordinate `min_val` to x coordinate `max_val`. If the + line does not cover the entire dataset, and `AssertionError` with + be thrown with message `message_data`. Raises ------- @@ -1134,31 +1164,40 @@ def assert_lines_of_type(self, line_types): ----- If `line_types` is empty, assertion is passed. """ - if line_types: - for line_type in line_types: - if line_type == "regression": - xy = self.get_xy(points_only=True) - slope_exp, intercept_exp, _, _, _ = stats.linregress( - xy.x, xy.y + if isinstance(line_types, str): + line_types = [line_types] + + for line_type in line_types: + if line_type == "linear-regression": + xy = self.get_xy(points_only=True) + # Check that there is xy data for this line. Some one-to-one + # lines do not produce xy data. + if xy.empty: + raise AssertionError( + "linear-regression line not displayed properly" ) - elif line_type == "onetoone": - slope_exp, intercept_exp = 1, 0 - else: - raise ValueError( - "each string in line_types must be from the following " - + '["regression","onetoone"]' - ) - - self.assert_line( - slope_exp, - intercept_exp, - message_no_line="{0} line not displayed properly".format( - line_type - ), - message_data="{0} line does not cover dataset".format( - line_type - ), + slope_exp, intercept_exp, _, _, _ = stats.linregress( + xy.x, xy.y ) + elif line_type == "onetoone": + slope_exp, intercept_exp = 1, 0 + else: + raise ValueError( + "each string in line_types must be from the following " + + '["linear-regression","onetoone"]' + ) + + self.assert_line( + slope_exp, + intercept_exp, + message_no_line="{0} line not displayed properly".format( + line_type + ), + message_data="{0} line does not cover dataset".format( + line_type + ), + check_coverage=check_coverage, + ) # HISTOGRAM FUNCTIONS diff --git a/matplotcheck/tests/test_base_lines.py b/matplotcheck/tests/test_base_lines.py new file mode 100644 index 00000000..e2280ec9 --- /dev/null +++ b/matplotcheck/tests/test_base_lines.py @@ -0,0 +1,191 @@ +import pytest +from matplotcheck.base import PlotTester +import matplotlib.pyplot as plt +import pandas as pd +import seaborn as sns +from scipy import stats + +"""Fixtures""" + + +@pytest.fixture +def pd_df_reg_data(): + """Create a pandas dataframe with points that are roughly along the same + line.""" + data = { + "A": [1.2, 1.9, 3.0, 4.1, 4.6, 6.0, 6.9, 8.4, 9.0], + "B": [2.4, 3.9, 6.1, 7.8, 9.0, 11.5, 15.0, 16.2, 18.6], + } + + return pd.DataFrame(data) + + +@pytest.fixture +def pd_df_reg_one2one_data(): + """Create a pandas dataframe with points that are along a one to one + line.""" + data = { + "A": [0.0, 2.0], + "B": [0.0, 2.0], + } + + return pd.DataFrame(data) + + +@pytest.fixture +def pt_reg_data(pd_df_reg_data): + """Create a PlotTester object with a regression line""" + fig, ax = plt.subplots() + sns.regplot("A", "B", data=pd_df_reg_data, ax=ax) + + return PlotTester(ax) + + +@pytest.fixture +def pt_multiple_reg(pd_df_reg_data): + """Create a PlotTester object with multiple regression line""" + fig, ax = plt.subplots() + sns.regplot("A", "B", data=pd_df_reg_data[:5], ax=ax) + sns.regplot("A", "B", data=pd_df_reg_data[5:], ax=ax) + + return PlotTester(ax) + + +@pytest.fixture +def pt_one2one(): + """Create a PlotTester object a one-to-one line""" + fig, ax = plt.subplots() + ax.plot((0, 1), (0, 1), transform=ax.transAxes, ls="--", c="k") + + return PlotTester(ax) + + +@pytest.fixture +def pt_reg_one2one(pd_df_reg_data): + """Create a PlotTester object with a regression line and a one-to-one + line""" + fig, ax = plt.subplots() + sns.regplot("A", "B", data=pd_df_reg_data, ax=ax) + ax.plot((0, 1), (0, 1), transform=ax.transAxes, ls="--", c="k") + + return PlotTester(ax) + + +@pytest.fixture +def pt_one2one_reg_close(pd_df_reg_one2one_data): + """Create a PlotTester object with a regression line that doesn't cover + all the points in a plot.""" + fig, ax = plt.subplots() + sns.regplot("A", "B", data=pd_df_reg_one2one_data, ax=ax, fit_reg=False) + ax.plot( + (0.0001, 1.9999), + (0.0001, 1.9999), + transform=ax.transAxes, + ls="--", + c="k", + ) + return PlotTester(ax) + + +@pytest.fixture +def pt_one2one_reg(pd_df_reg_one2one_data): + """Create a PlotTester object with a regression line that is plotted so the + points are not covered by the regression line.""" + fig, ax = plt.subplots() + sns.regplot("A", "B", data=pd_df_reg_one2one_data, ax=ax, fit_reg=False) + ax.plot((0, 1), (0, 1), transform=ax.transAxes, ls="--", c="k") + return PlotTester(ax) + + +def test_reg_plot(pd_df_reg_data, pt_reg_data): + """Test that assert_line() correctly passes when given the correct slope + and intercept.""" + # Get the correct slope and intercept for the data + slope_exp, intercept_exp, _, _, _ = stats.linregress( + pd_df_reg_data.A, pd_df_reg_data.B + ) + + pt_reg_data.assert_line(slope_exp, intercept_exp) + + +def test_reg_plot_slope_fails(pd_df_reg_data, pt_reg_data): + """Check that assert_line() correctly falis when given an incorrect + slope.""" + _, intercept_exp, _, _, _ = stats.linregress( + pd_df_reg_data.A, pd_df_reg_data.B + ) + with pytest.raises(AssertionError, match="Expected line not displayed"): + pt_reg_data.assert_line(1, intercept_exp) + + +def test_reg_plot_intercept_fails(pd_df_reg_data, pt_reg_data): + """Check that assert_line() correctly fails when given an incorrect + intercept""" + slope_exp, _, _, _, _ = stats.linregress( + pd_df_reg_data.A, pd_df_reg_data.B + ) + + with pytest.raises(AssertionError, match="Expected line not displayed"): + pt_reg_data.assert_line(slope_exp, 1) + + +def test_line_type_reg(pt_reg_data): + """Check that assert_lines_of_type() correctly passes when checking for a + linear-regression line.""" + pt_reg_data.assert_lines_of_type("linear-regression") + + +def test_line_type_one2one(pt_one2one): + """Check that assert_lines_of_type() correctly passes when checking for a + one-to-one line.""" + pt_one2one.assert_lines_of_type("onetoone", check_coverage=False) + + +def test_line_type_reg_one2one(pt_reg_one2one): + """Check that assert_lines_of_type() correctly passes when checking for + both a linear-regression line and a one-to-one line.""" + pt_reg_one2one.assert_lines_of_type( + ["linear-regression", "onetoone"], check_coverage=False + ) + + +def test_line_type_reg_fails(pt_one2one): + """Check that assert_lines_of_type() correctly fails when checking for a + linear-regression line, but one does not exist.""" + with pytest.raises( + AssertionError, match="linear-regression line not displayed properly" + ): + pt_one2one.assert_lines_of_type("linear-regression") + + +def test_line_type_one2one_fails(pt_reg_data): + """Check that assert_lines_of_type() correctly fails when checking for a + one-to-one line, but one does not exist.""" + with pytest.raises( + AssertionError, match="onetoone line not displayed properly" + ): + pt_reg_data.assert_lines_of_type("onetoone") + + +def test_multi_reg_plot_line_fails(pt_multiple_reg): + """Check that multiple regression lines fails when the not all points are + used to make the regression lines.""" + with pytest.raises( + AssertionError, match="linear-regression line not displayed properly" + ): + pt_multiple_reg.assert_lines_of_type("linear-regression") + + +def test_reg_one2one_fails(pt_one2one_reg): + """Testing that a regression line that doesn't cover all the points in a + plot fails.""" + with pytest.raises( + AssertionError, match="linear-regression line does not cover dataset" + ): + pt_one2one_reg.assert_lines_of_type("linear-regression") + + +def test_reg_one2one_passes_close_lims(pt_one2one_reg_close): + """Testing that a regression line that is slightly out of coverage still + passes.""" + pt_one2one_reg_close.assert_lines_of_type("linear-regression") diff --git a/matplotcheck/tests/test_lines.py b/matplotcheck/tests/test_lines.py index 14d68c12..cd53d1e7 100644 --- a/matplotcheck/tests/test_lines.py +++ b/matplotcheck/tests/test_lines.py @@ -166,6 +166,6 @@ def test_get_lines_by_collection(multiline_geo_plot): [(2, 1), (3, 1), (4, 1), (5, 2)], ] ] - sorted_lines_list = sorted([sorted(l) for l in lines_list]) + sorted_lines_list = sorted([sorted(line) for line in lines_list]) assert sorted_lines_list == multiline_geo_plot.get_lines_by_collection() plt.close("all") diff --git a/matplotcheck/timeseries.py b/matplotcheck/timeseries.py index 80a0071a..7315d219 100644 --- a/matplotcheck/timeseries.py +++ b/matplotcheck/timeseries.py @@ -104,7 +104,7 @@ def assert_xticks_locs( """ if loc_exp: - xlims = [mdates.num2date(l) for l in self.ax.get_xlim()] + xlims = [mdates.num2date(limit) for limit in self.ax.get_xlim()] if tick_size == "large": ticks = self.ax.xaxis.get_majorticklocs() elif tick_size == "small": diff --git a/matplotcheck/vector.py b/matplotcheck/vector.py index ab77c8da..ca8b777b 100644 --- a/matplotcheck/vector.py +++ b/matplotcheck/vector.py @@ -340,7 +340,7 @@ def get_lines_by_collection(self): for c in self.ax.collections if type(c) == matplotlib.collections.LineCollection ] - return sorted([sorted(l) for l in lines_grouped]) + return sorted([sorted(lines) for lines in lines_grouped]) def get_lines_by_attributes(self): """Returns a sorted list of lists where each list contains line @@ -390,7 +390,7 @@ def get_lines_by_attributes(self): ["color", "lwidth", "lstyle"], sort=False ) ] - return sorted([sorted(l) for l in lines_grouped]) + return sorted([sorted(lines) for lines in lines_grouped]) def assert_lines(self, lines_expected, m="Incorrect Line Data"): """Asserts the line data in Axes ax is equal to lines_expected with @@ -452,7 +452,7 @@ def assert_lines_grouped_by_type( for c in ax_exp.collections if type(c) == matplotlib.collections.LineCollection ] - grouped_exp = sorted([sorted(l) for l in grouped_exp]) + grouped_exp = sorted([sorted(lines) for lines in grouped_exp]) plt.close(fig) np.testing.assert_equal(groups, grouped_exp, m) elif lines_expected is None: From 824684f7b50f88ba85d4dcb074bcb93011b8d79e Mon Sep 17 00:00:00 2001 From: nkorinek Date: Thu, 14 May 2020 13:44:49 -0600 Subject: [PATCH 50/53] Draft of section add --- examples/plot_line_testing.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index 9a7e4f50..e1f098b4 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -64,13 +64,14 @@ # Test Line Types # --------------- # There are two line types on the plot above: a one-to-one line for -# reference and a regression line derived from the data points. You can use the method -# ``assert_lines_of_type()`` to test if a one to one or regression line -# (or both line types) are present in the plot. (NOTE: Regression and 1:1 lines are the only types -# of lines you can currently test for with this function.) +# reference and a regression line derived from the data points. You can use the +# method ``assert_lines_of_type()`` to test if a one to one or regression line +# (or both line types) are present in the plot. (NOTE: Linear regression and +# 1:1 lines are the only types of lines you can currently test for with this +# function.) # Check line types -line_figure_tests.assert_lines_of_type(line_types=['regression', 'onetoone']) +line_figure_tests.assert_lines_of_type(line_types=['linear-regression', 'onetoone']) ################################################################################ # Test Slope and Y Intercept @@ -94,6 +95,14 @@ line_figure_tests.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. We can +# test those aspects as well to ensure they are accurate. + ################################################################################ # # .. note:: From 577a59c6c56400355ed23a8b8de05bd61b7091b4 Mon Sep 17 00:00:00 2001 From: nkorinek Date: Thu, 14 May 2020 14:58:11 -0600 Subject: [PATCH 51/53] Added changes requested in github, made a small fix to --- examples/plot_line_testing.py | 52 ++++++++++++++++++++++++++--------- matplotcheck/base.py | 8 +++--- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index 61d084ec..22dc0c48 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -26,9 +26,9 @@ # compare the slope of the regression output to a standard 1:1 fit. # Create Pandas DataFrame containing data points -col1 = list(np.random.randint(25, size=15)) -col2 = list(np.random.randint(25, size=15)) -data = pd.DataFrame(list(zip(col1, col2)), columns=['data1', 'data2']) +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() @@ -37,7 +37,8 @@ sns.regplot('data1', 'data2', data=data, color='purple', - ax=ax) + ax=ax, + scatter=True) # Add 1:1 line to your plot ax.plot((0, 1), (0, 1), transform=ax.transAxes, ls='--', c='k') @@ -66,10 +67,16 @@ # 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 -# regression line (or both line types) are present in the plot. +# 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 line types -line_plot_tester.assert_lines_of_type(line_types=['regression', 'onetoone']) +# 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 @@ -87,7 +94,8 @@ # Get slope and y intercept data of regression line for testing slope_data, intercept_data, _, _, _ = stats.linregress( - data.data1, data.data2) + 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) @@ -98,16 +106,34 @@ # ------------------------------ # 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. We can +# 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. +# 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 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, ) From 85f9afe3b8ce1d0cd1c0821623505c3df3cedf77 Mon Sep 17 00:00:00 2001 From: nkorinek Date: Thu, 14 May 2020 15:13:42 -0600 Subject: [PATCH 52/53] small merge error --- matplotcheck/base.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/matplotcheck/base.py b/matplotcheck/base.py index 938618ac..61ae752c 100644 --- a/matplotcheck/base.py +++ b/matplotcheck/base.py @@ -946,8 +946,6 @@ def assert_xydata( np.testing.assert_array_max_ulp( xy_data["y"].to_numpy(dtype=np.float64).flatten(), xy_expected[ycol].to_numpy(dtype=np.float64).flatten(), - xy_data["y"].to_numpy(dtype=np.float64), - xy_expected[ycol].to_numpy(dtype=np.float64), 5, ) From 3226ba8ac15e5fa071539202a0130e36ba65b660 Mon Sep 17 00:00:00 2001 From: nkorinek Date: Thu, 14 May 2020 15:23:10 -0600 Subject: [PATCH 53/53] took out plt.show() --- examples/plot_line_testing.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/plot_line_testing.py b/examples/plot_line_testing.py index 22dc0c48..6a8cfc34 100644 --- a/examples/plot_line_testing.py +++ b/examples/plot_line_testing.py @@ -47,9 +47,7 @@ ylabel='data2', title='Example Data Regression Plot', xlim=(0, 25), - ylim=(0, 25)) - -plt.show() + ylim=(0, 25)); ################################################################################ # Test Line Plots Using a matplotcheck.PlotTester Object