craftbeerpi4-pione/venv3/lib/python3.7/site-packages/pandas/tests/plotting/common.py
2021-03-03 23:49:41 +01:00

558 lines
18 KiB
Python

import os
import warnings
import numpy as np
from numpy import random
from pandas.util._decorators import cache_readonly
import pandas.util._test_decorators as td
from pandas.core.dtypes.api import is_list_like
import pandas as pd
from pandas import DataFrame, Series
import pandas._testing as tm
"""
This is a common base class used for various plotting tests
"""
@td.skip_if_no_mpl
class TestPlotBase:
def setup_method(self, method):
import matplotlib as mpl
from pandas.plotting._matplotlib import compat
mpl.rcdefaults()
self.mpl_ge_2_2_3 = compat._mpl_ge_2_2_3()
self.mpl_ge_3_0_0 = compat._mpl_ge_3_0_0()
self.mpl_ge_3_1_0 = compat._mpl_ge_3_1_0()
self.mpl_ge_3_2_0 = compat._mpl_ge_3_2_0()
self.bp_n_objects = 7
self.polycollection_factor = 2
self.default_figsize = (6.4, 4.8)
self.default_tick_position = "left"
n = 100
with tm.RNGContext(42):
gender = np.random.choice(["Male", "Female"], size=n)
classroom = np.random.choice(["A", "B", "C"], size=n)
self.hist_df = DataFrame(
{
"gender": gender,
"classroom": classroom,
"height": random.normal(66, 4, size=n),
"weight": random.normal(161, 32, size=n),
"category": random.randint(4, size=n),
}
)
self.tdf = tm.makeTimeDataFrame()
self.hexbin_df = DataFrame(
{
"A": np.random.uniform(size=20),
"B": np.random.uniform(size=20),
"C": np.arange(20) + np.random.uniform(size=20),
}
)
def teardown_method(self, method):
tm.close()
@cache_readonly
def plt(self):
import matplotlib.pyplot as plt
return plt
@cache_readonly
def colorconverter(self):
import matplotlib.colors as colors
return colors.colorConverter
def _check_legend_labels(self, axes, labels=None, visible=True):
"""
Check each axes has expected legend labels
Parameters
----------
axes : matplotlib Axes object, or its list-like
labels : list-like
expected legend labels
visible : bool
expected legend visibility. labels are checked only when visible is
True
"""
if visible and (labels is None):
raise ValueError("labels must be specified when visible is True")
axes = self._flatten_visible(axes)
for ax in axes:
if visible:
assert ax.get_legend() is not None
self._check_text_labels(ax.get_legend().get_texts(), labels)
else:
assert ax.get_legend() is None
def _check_legend_marker(self, ax, expected_markers=None, visible=True):
"""
Check ax has expected legend markers
Parameters
----------
ax : matplotlib Axes object
expected_markers : list-like
expected legend markers
visible : bool
expected legend visibility. labels are checked only when visible is
True
"""
if visible and (expected_markers is None):
raise ValueError("Markers must be specified when visible is True")
if visible:
handles, _ = ax.get_legend_handles_labels()
markers = [handle.get_marker() for handle in handles]
assert markers == expected_markers
else:
assert ax.get_legend() is None
def _check_data(self, xp, rs):
"""
Check each axes has identical lines
Parameters
----------
xp : matplotlib Axes object
rs : matplotlib Axes object
"""
xp_lines = xp.get_lines()
rs_lines = rs.get_lines()
def check_line(xpl, rsl):
xpdata = xpl.get_xydata()
rsdata = rsl.get_xydata()
tm.assert_almost_equal(xpdata, rsdata)
assert len(xp_lines) == len(rs_lines)
[check_line(xpl, rsl) for xpl, rsl in zip(xp_lines, rs_lines)]
tm.close()
def _check_visible(self, collections, visible=True):
"""
Check each artist is visible or not
Parameters
----------
collections : matplotlib Artist or its list-like
target Artist or its list or collection
visible : bool
expected visibility
"""
from matplotlib.collections import Collection
if not isinstance(collections, Collection) and not is_list_like(collections):
collections = [collections]
for patch in collections:
assert patch.get_visible() == visible
def _get_colors_mapped(self, series, colors):
unique = series.unique()
# unique and colors length can be differed
# depending on slice value
mapped = dict(zip(unique, colors))
return [mapped[v] for v in series.values]
def _check_colors(
self, collections, linecolors=None, facecolors=None, mapping=None
):
"""
Check each artist has expected line colors and face colors
Parameters
----------
collections : list-like
list or collection of target artist
linecolors : list-like which has the same length as collections
list of expected line colors
facecolors : list-like which has the same length as collections
list of expected face colors
mapping : Series
Series used for color grouping key
used for andrew_curves, parallel_coordinates, radviz test
"""
from matplotlib.collections import Collection, LineCollection, PolyCollection
from matplotlib.lines import Line2D
conv = self.colorconverter
if linecolors is not None:
if mapping is not None:
linecolors = self._get_colors_mapped(mapping, linecolors)
linecolors = linecolors[: len(collections)]
assert len(collections) == len(linecolors)
for patch, color in zip(collections, linecolors):
if isinstance(patch, Line2D):
result = patch.get_color()
# Line2D may contains string color expression
result = conv.to_rgba(result)
elif isinstance(patch, (PolyCollection, LineCollection)):
result = tuple(patch.get_edgecolor()[0])
else:
result = patch.get_edgecolor()
expected = conv.to_rgba(color)
assert result == expected
if facecolors is not None:
if mapping is not None:
facecolors = self._get_colors_mapped(mapping, facecolors)
facecolors = facecolors[: len(collections)]
assert len(collections) == len(facecolors)
for patch, color in zip(collections, facecolors):
if isinstance(patch, Collection):
# returned as list of np.array
result = patch.get_facecolor()[0]
else:
result = patch.get_facecolor()
if isinstance(result, np.ndarray):
result = tuple(result)
expected = conv.to_rgba(color)
assert result == expected
def _check_text_labels(self, texts, expected):
"""
Check each text has expected labels
Parameters
----------
texts : matplotlib Text object, or its list-like
target text, or its list
expected : str or list-like which has the same length as texts
expected text label, or its list
"""
if not is_list_like(texts):
assert texts.get_text() == expected
else:
labels = [t.get_text() for t in texts]
assert len(labels) == len(expected)
for label, e in zip(labels, expected):
assert label == e
def _check_ticks_props(
self, axes, xlabelsize=None, xrot=None, ylabelsize=None, yrot=None
):
"""
Check each axes has expected tick properties
Parameters
----------
axes : matplotlib Axes object, or its list-like
xlabelsize : number
expected xticks font size
xrot : number
expected xticks rotation
ylabelsize : number
expected yticks font size
yrot : number
expected yticks rotation
"""
from matplotlib.ticker import NullFormatter
axes = self._flatten_visible(axes)
for ax in axes:
if xlabelsize is not None or xrot is not None:
if isinstance(ax.xaxis.get_minor_formatter(), NullFormatter):
# If minor ticks has NullFormatter, rot / fontsize are not
# retained
labels = ax.get_xticklabels()
else:
labels = ax.get_xticklabels() + ax.get_xticklabels(minor=True)
for label in labels:
if xlabelsize is not None:
tm.assert_almost_equal(label.get_fontsize(), xlabelsize)
if xrot is not None:
tm.assert_almost_equal(label.get_rotation(), xrot)
if ylabelsize is not None or yrot is not None:
if isinstance(ax.yaxis.get_minor_formatter(), NullFormatter):
labels = ax.get_yticklabels()
else:
labels = ax.get_yticklabels() + ax.get_yticklabels(minor=True)
for label in labels:
if ylabelsize is not None:
tm.assert_almost_equal(label.get_fontsize(), ylabelsize)
if yrot is not None:
tm.assert_almost_equal(label.get_rotation(), yrot)
def _check_ax_scales(self, axes, xaxis="linear", yaxis="linear"):
"""
Check each axes has expected scales
Parameters
----------
axes : matplotlib Axes object, or its list-like
xaxis : {'linear', 'log'}
expected xaxis scale
yaxis : {'linear', 'log'}
expected yaxis scale
"""
axes = self._flatten_visible(axes)
for ax in axes:
assert ax.xaxis.get_scale() == xaxis
assert ax.yaxis.get_scale() == yaxis
def _check_axes_shape(self, axes, axes_num=None, layout=None, figsize=None):
"""
Check expected number of axes is drawn in expected layout
Parameters
----------
axes : matplotlib Axes object, or its list-like
axes_num : number
expected number of axes. Unnecessary axes should be set to
invisible.
layout : tuple
expected layout, (expected number of rows , columns)
figsize : tuple
expected figsize. default is matplotlib default
"""
from pandas.plotting._matplotlib.tools import _flatten
if figsize is None:
figsize = self.default_figsize
visible_axes = self._flatten_visible(axes)
if axes_num is not None:
assert len(visible_axes) == axes_num
for ax in visible_axes:
# check something drawn on visible axes
assert len(ax.get_children()) > 0
if layout is not None:
result = self._get_axes_layout(_flatten(axes))
assert result == layout
tm.assert_numpy_array_equal(
visible_axes[0].figure.get_size_inches(),
np.array(figsize, dtype=np.float64),
)
def _get_axes_layout(self, axes):
x_set = set()
y_set = set()
for ax in axes:
# check axes coordinates to estimate layout
points = ax.get_position().get_points()
x_set.add(points[0][0])
y_set.add(points[0][1])
return (len(y_set), len(x_set))
def _flatten_visible(self, axes):
"""
Flatten axes, and filter only visible
Parameters
----------
axes : matplotlib Axes object, or its list-like
"""
from pandas.plotting._matplotlib.tools import _flatten
axes = _flatten(axes)
axes = [ax for ax in axes if ax.get_visible()]
return axes
def _check_has_errorbars(self, axes, xerr=0, yerr=0):
"""
Check axes has expected number of errorbars
Parameters
----------
axes : matplotlib Axes object, or its list-like
xerr : number
expected number of x errorbar
yerr : number
expected number of y errorbar
"""
axes = self._flatten_visible(axes)
for ax in axes:
containers = ax.containers
xerr_count = 0
yerr_count = 0
for c in containers:
has_xerr = getattr(c, "has_xerr", False)
has_yerr = getattr(c, "has_yerr", False)
if has_xerr:
xerr_count += 1
if has_yerr:
yerr_count += 1
assert xerr == xerr_count
assert yerr == yerr_count
def _check_box_return_type(
self, returned, return_type, expected_keys=None, check_ax_title=True
):
"""
Check box returned type is correct
Parameters
----------
returned : object to be tested, returned from boxplot
return_type : str
return_type passed to boxplot
expected_keys : list-like, optional
group labels in subplot case. If not passed,
the function checks assuming boxplot uses single ax
check_ax_title : bool
Whether to check the ax.title is the same as expected_key
Intended to be checked by calling from ``boxplot``.
Normal ``plot`` doesn't attach ``ax.title``, it must be disabled.
"""
from matplotlib.axes import Axes
types = {"dict": dict, "axes": Axes, "both": tuple}
if expected_keys is None:
# should be fixed when the returning default is changed
if return_type is None:
return_type = "dict"
assert isinstance(returned, types[return_type])
if return_type == "both":
assert isinstance(returned.ax, Axes)
assert isinstance(returned.lines, dict)
else:
# should be fixed when the returning default is changed
if return_type is None:
for r in self._flatten_visible(returned):
assert isinstance(r, Axes)
return
assert isinstance(returned, Series)
assert sorted(returned.keys()) == sorted(expected_keys)
for key, value in returned.items():
assert isinstance(value, types[return_type])
# check returned dict has correct mapping
if return_type == "axes":
if check_ax_title:
assert value.get_title() == key
elif return_type == "both":
if check_ax_title:
assert value.ax.get_title() == key
assert isinstance(value.ax, Axes)
assert isinstance(value.lines, dict)
elif return_type == "dict":
line = value["medians"][0]
axes = line.axes
if check_ax_title:
assert axes.get_title() == key
else:
raise AssertionError
def _check_grid_settings(self, obj, kinds, kws={}):
# Make sure plot defaults to rcParams['axes.grid'] setting, GH 9792
import matplotlib as mpl
def is_grid_on():
xticks = self.plt.gca().xaxis.get_major_ticks()
yticks = self.plt.gca().yaxis.get_major_ticks()
# for mpl 2.2.2, gridOn and gridline.get_visible disagree.
# for new MPL, they are the same.
if self.mpl_ge_3_1_0:
xoff = all(not g.gridline.get_visible() for g in xticks)
yoff = all(not g.gridline.get_visible() for g in yticks)
else:
xoff = all(not g.gridOn for g in xticks)
yoff = all(not g.gridOn for g in yticks)
return not (xoff and yoff)
spndx = 1
for kind in kinds:
self.plt.subplot(1, 4 * len(kinds), spndx)
spndx += 1
mpl.rc("axes", grid=False)
obj.plot(kind=kind, **kws)
assert not is_grid_on()
self.plt.subplot(1, 4 * len(kinds), spndx)
spndx += 1
mpl.rc("axes", grid=True)
obj.plot(kind=kind, grid=False, **kws)
assert not is_grid_on()
if kind != "pie":
self.plt.subplot(1, 4 * len(kinds), spndx)
spndx += 1
mpl.rc("axes", grid=True)
obj.plot(kind=kind, **kws)
assert is_grid_on()
self.plt.subplot(1, 4 * len(kinds), spndx)
spndx += 1
mpl.rc("axes", grid=False)
obj.plot(kind=kind, grid=True, **kws)
assert is_grid_on()
def _unpack_cycler(self, rcParams, field="color"):
"""
Auxiliary function for correctly unpacking cycler after MPL >= 1.5
"""
return [v[field] for v in rcParams["axes.prop_cycle"]]
def _check_plot_works(f, filterwarnings="always", **kwargs):
import matplotlib.pyplot as plt
ret = None
with warnings.catch_warnings():
warnings.simplefilter(filterwarnings)
try:
try:
fig = kwargs["figure"]
except KeyError:
fig = plt.gcf()
plt.clf()
kwargs.get("ax", fig.add_subplot(211))
ret = f(**kwargs)
tm.assert_is_valid_plot_return_object(ret)
if f is pd.plotting.bootstrap_plot:
assert "ax" not in kwargs
else:
kwargs["ax"] = fig.add_subplot(212)
ret = f(**kwargs)
tm.assert_is_valid_plot_return_object(ret)
with tm.ensure_clean(return_filelike=True) as path:
plt.savefig(path)
finally:
tm.close(fig)
return ret
def curpath():
pth, _ = os.path.split(os.path.abspath(__file__))
return pth