Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Unreleased
in them (@nkorinek, #182)
- added contributors file and updated README to remove that information
(@nkorinek, #121)
- Made checking line coverage optional for `base.assert_line()`
(@ryla5068, #239)
- Fixed bugs involving line tests (@ryla5068, #239)

0.1.2
-----
Expand Down
1 change: 1 addition & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
105 changes: 63 additions & 42 deletions matplotcheck/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1061,60 +1061,73 @@ def assert_line(
self,
slope_exp,
intercept_exp,
xtime=False,
check_coverage=False,
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
----------
slope_exp : float
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
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 = not check_coverage
xy = self.get_xy(points_only=True)
min_val, max_val = min(xy["x"]), max(xy["x"])

for l in self.ax.lines:
path_verts = self.ax.transData.inverted().transform(
l._transformed_path.get_fully_transformed_path().vertices
)
# Here we will get the verticies for the line and reformat them in
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets:

  1. Remove xtime as a parameter
  2. Let's make the data extent (boolean) parameter to make this test OPTIONAL

# the way that get_slope_yintercept() expects
data = l.get_data()
path_verts = np.column_stack((data[0], data[1]))

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
if check_coverage:
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

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):
"""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']``.

Expand All @@ -1127,32 +1140,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
)
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"]'
if isinstance(line_types, str):
line_types = [line_types]

for line_type in line_types:
if line_type == "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(
"regression line not displayed properly"
)

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 "
+ '["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
),
)

# HISTOGRAM FUNCTIONS

def get_num_bins(self):
Expand Down
117 changes: 117 additions & 0 deletions matplotcheck/tests/test_base_lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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 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_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)


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
regression line."""
pt_reg_data.assert_lines_of_type("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")


def test_line_type_reg_one2one(pt_reg_one2one):
"""Check that assert_lines_of_type() correctly passes when checking for
both a regression line and a one-to-one line."""
pt_reg_one2one.assert_lines_of_type(["regression", "onetoone"])


def test_line_type_reg_fails(pt_one2one):
"""Check that assert_lines_of_type() correctly fails when checking for a
regression line, but one does not exist."""
with pytest.raises(
AssertionError, match="regression line not displayed properly"
):
pt_one2one.assert_lines_of_type("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")