forked from Alsan/Post_finder
venv
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,563 @@
|
||||
"""
|
||||
Module consolidating common testing functions for checking plotting.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pandas.core.dtypes.api import is_list_like
|
||||
|
||||
import pandas as pd
|
||||
from pandas import Series
|
||||
import pandas._testing as tm
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
|
||||
def _check_legend_labels(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 = _flatten_visible(axes)
|
||||
for ax in axes:
|
||||
if visible:
|
||||
assert ax.get_legend() is not None
|
||||
_check_text_labels(ax.get_legend().get_texts(), labels)
|
||||
else:
|
||||
assert ax.get_legend() is None
|
||||
|
||||
|
||||
def _check_legend_marker(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(xp, rs):
|
||||
"""
|
||||
Check each axes has identical lines
|
||||
|
||||
Parameters
|
||||
----------
|
||||
xp : matplotlib Axes object
|
||||
rs : matplotlib Axes object
|
||||
"""
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
xp_lines = xp.get_lines()
|
||||
rs_lines = rs.get_lines()
|
||||
|
||||
assert len(xp_lines) == len(rs_lines)
|
||||
for xpl, rsl in zip(xp_lines, rs_lines):
|
||||
xpdata = xpl.get_xydata()
|
||||
rsdata = rsl.get_xydata()
|
||||
tm.assert_almost_equal(xpdata, rsdata)
|
||||
|
||||
plt.close("all")
|
||||
|
||||
|
||||
def _check_visible(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 _check_patches_all_filled(axes: Axes | Sequence[Axes], filled: bool = True) -> None:
|
||||
"""
|
||||
Check for each artist whether it is filled or not
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axes : matplotlib Axes object, or its list-like
|
||||
filled : bool
|
||||
expected filling
|
||||
"""
|
||||
|
||||
axes = _flatten_visible(axes)
|
||||
for ax in axes:
|
||||
for patch in ax.patches:
|
||||
assert patch.fill == filled
|
||||
|
||||
|
||||
def _get_colors_mapped(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(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 import colors
|
||||
from matplotlib.collections import (
|
||||
Collection,
|
||||
LineCollection,
|
||||
PolyCollection,
|
||||
)
|
||||
from matplotlib.lines import Line2D
|
||||
|
||||
conv = colors.ColorConverter
|
||||
if linecolors is not None:
|
||||
if mapping is not None:
|
||||
linecolors = _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 = _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(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(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 = _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(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 = _flatten_visible(axes)
|
||||
for ax in axes:
|
||||
assert ax.xaxis.get_scale() == xaxis
|
||||
assert ax.yaxis.get_scale() == yaxis
|
||||
|
||||
|
||||
def _check_axes_shape(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_axes
|
||||
|
||||
if figsize is None:
|
||||
figsize = (6.4, 4.8)
|
||||
visible_axes = _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:
|
||||
x_set = set()
|
||||
y_set = set()
|
||||
for ax in flatten_axes(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])
|
||||
result = (len(y_set), len(x_set))
|
||||
assert result == layout
|
||||
|
||||
tm.assert_numpy_array_equal(
|
||||
visible_axes[0].figure.get_size_inches(),
|
||||
np.array(figsize, dtype=np.float64),
|
||||
)
|
||||
|
||||
|
||||
def _flatten_visible(axes: Axes | Sequence[Axes]) -> Sequence[Axes]:
|
||||
"""
|
||||
Flatten axes, and filter only visible
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axes : matplotlib Axes object, or its list-like
|
||||
|
||||
"""
|
||||
from pandas.plotting._matplotlib.tools import flatten_axes
|
||||
|
||||
axes_ndarray = flatten_axes(axes)
|
||||
axes = [ax for ax in axes_ndarray if ax.get_visible()]
|
||||
return axes
|
||||
|
||||
|
||||
def _check_has_errorbars(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 = _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(
|
||||
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 _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(obj, kinds, kws={}):
|
||||
# Make sure plot defaults to rcParams['axes.grid'] setting, GH 9792
|
||||
|
||||
import matplotlib as mpl
|
||||
|
||||
def is_grid_on():
|
||||
xticks = mpl.pyplot.gca().xaxis.get_major_ticks()
|
||||
yticks = mpl.pyplot.gca().yaxis.get_major_ticks()
|
||||
xoff = all(not g.gridline.get_visible() for g in xticks)
|
||||
yoff = all(not g.gridline.get_visible() for g in yticks)
|
||||
|
||||
return not (xoff and yoff)
|
||||
|
||||
spndx = 1
|
||||
for kind in kinds:
|
||||
mpl.pyplot.subplot(1, 4 * len(kinds), spndx)
|
||||
spndx += 1
|
||||
mpl.rc("axes", grid=False)
|
||||
obj.plot(kind=kind, **kws)
|
||||
assert not is_grid_on()
|
||||
mpl.pyplot.clf()
|
||||
|
||||
mpl.pyplot.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()
|
||||
mpl.pyplot.clf()
|
||||
|
||||
if kind not in ["pie", "hexbin", "scatter"]:
|
||||
mpl.pyplot.subplot(1, 4 * len(kinds), spndx)
|
||||
spndx += 1
|
||||
mpl.rc("axes", grid=True)
|
||||
obj.plot(kind=kind, **kws)
|
||||
assert is_grid_on()
|
||||
mpl.pyplot.clf()
|
||||
|
||||
mpl.pyplot.subplot(1, 4 * len(kinds), spndx)
|
||||
spndx += 1
|
||||
mpl.rc("axes", grid=False)
|
||||
obj.plot(kind=kind, grid=True, **kws)
|
||||
assert is_grid_on()
|
||||
mpl.pyplot.clf()
|
||||
|
||||
|
||||
def _unpack_cycler(rcParams, field="color"):
|
||||
"""
|
||||
Auxiliary function for correctly unpacking cycler after MPL >= 1.5
|
||||
"""
|
||||
return [v[field] for v in rcParams["axes.prop_cycle"]]
|
||||
|
||||
|
||||
def get_x_axis(ax):
|
||||
return ax._shared_axes["x"]
|
||||
|
||||
|
||||
def get_y_axis(ax):
|
||||
return ax._shared_axes["y"]
|
||||
|
||||
|
||||
def _check_plot_works(f, default_axes=False, **kwargs):
|
||||
"""
|
||||
Create plot and ensure that plot return object is valid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
f : func
|
||||
Plotting function.
|
||||
default_axes : bool, optional
|
||||
If False (default):
|
||||
- If `ax` not in `kwargs`, then create subplot(211) and plot there
|
||||
- Create new subplot(212) and plot there as well
|
||||
- Mind special corner case for bootstrap_plot (see `_gen_two_subplots`)
|
||||
If True:
|
||||
- Simply run plotting function with kwargs provided
|
||||
- All required axes instances will be created automatically
|
||||
- It is recommended to use it when the plotting function
|
||||
creates multiple axes itself. It helps avoid warnings like
|
||||
'UserWarning: To output multiple subplots,
|
||||
the figure containing the passed axes is being cleared'
|
||||
**kwargs
|
||||
Keyword arguments passed to the plotting function.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Plot object returned by the last plotting.
|
||||
"""
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
if default_axes:
|
||||
gen_plots = _gen_default_plot
|
||||
else:
|
||||
gen_plots = _gen_two_subplots
|
||||
|
||||
ret = None
|
||||
try:
|
||||
fig = kwargs.get("figure", plt.gcf())
|
||||
plt.clf()
|
||||
|
||||
for ret in gen_plots(f, fig, **kwargs):
|
||||
tm.assert_is_valid_plot_return_object(ret)
|
||||
|
||||
finally:
|
||||
plt.close(fig)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _gen_default_plot(f, fig, **kwargs):
|
||||
"""
|
||||
Create plot in a default way.
|
||||
"""
|
||||
yield f(**kwargs)
|
||||
|
||||
|
||||
def _gen_two_subplots(f, fig, **kwargs):
|
||||
"""
|
||||
Create plot on two subplots forcefully created.
|
||||
"""
|
||||
if "ax" not in kwargs:
|
||||
fig.add_subplot(211)
|
||||
yield f(**kwargs)
|
||||
|
||||
if f is pd.plotting.bootstrap_plot:
|
||||
assert "ax" not in kwargs
|
||||
else:
|
||||
kwargs["ax"] = fig.add_subplot(212)
|
||||
yield f(**kwargs)
|
@ -0,0 +1,56 @@
|
||||
import gc
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
to_datetime,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mpl_cleanup():
|
||||
# matplotlib/testing/decorators.py#L24
|
||||
# 1) Resets units registry
|
||||
# 2) Resets rc_context
|
||||
# 3) Closes all figures
|
||||
mpl = pytest.importorskip("matplotlib")
|
||||
mpl_units = pytest.importorskip("matplotlib.units")
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
orig_units_registry = mpl_units.registry.copy()
|
||||
with mpl.rc_context():
|
||||
mpl.use("template")
|
||||
yield
|
||||
mpl_units.registry.clear()
|
||||
mpl_units.registry.update(orig_units_registry)
|
||||
plt.close("all")
|
||||
# https://matplotlib.org/stable/users/prev_whats_new/whats_new_3.6.0.html#garbage-collection-is-no-longer-run-on-figure-close # noqa: E501
|
||||
gc.collect(1)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hist_df():
|
||||
n = 50
|
||||
rng = np.random.default_rng(10)
|
||||
gender = rng.choice(["Male", "Female"], size=n)
|
||||
classroom = rng.choice(["A", "B", "C"], size=n)
|
||||
|
||||
hist_df = DataFrame(
|
||||
{
|
||||
"gender": gender,
|
||||
"classroom": classroom,
|
||||
"height": rng.normal(66, 4, size=n),
|
||||
"weight": rng.normal(161, 32, size=n),
|
||||
"category": rng.integers(4, size=n),
|
||||
"datetime": to_datetime(
|
||||
rng.integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=n,
|
||||
dtype=np.int64,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
return hist_df
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,670 @@
|
||||
""" Test cases for DataFrame.plot """
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
_check_colors,
|
||||
_check_plot_works,
|
||||
_unpack_cycler,
|
||||
)
|
||||
from pandas.util.version import Version
|
||||
|
||||
mpl = pytest.importorskip("matplotlib")
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
cm = pytest.importorskip("matplotlib.cm")
|
||||
|
||||
|
||||
def _check_colors_box(bp, box_c, whiskers_c, medians_c, caps_c="k", fliers_c=None):
|
||||
if fliers_c is None:
|
||||
fliers_c = "k"
|
||||
_check_colors(bp["boxes"], linecolors=[box_c] * len(bp["boxes"]))
|
||||
_check_colors(bp["whiskers"], linecolors=[whiskers_c] * len(bp["whiskers"]))
|
||||
_check_colors(bp["medians"], linecolors=[medians_c] * len(bp["medians"]))
|
||||
_check_colors(bp["fliers"], linecolors=[fliers_c] * len(bp["fliers"]))
|
||||
_check_colors(bp["caps"], linecolors=[caps_c] * len(bp["caps"]))
|
||||
|
||||
|
||||
class TestDataFrameColor:
|
||||
@pytest.mark.parametrize(
|
||||
"color", ["C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"]
|
||||
)
|
||||
def test_mpl2_color_cycle_str(self, color):
|
||||
# GH 15516
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((10, 3)), columns=["a", "b", "c"]
|
||||
)
|
||||
_check_plot_works(df.plot, color=color)
|
||||
|
||||
def test_color_single_series_list(self):
|
||||
# GH 3486
|
||||
df = DataFrame({"A": [1, 2, 3]})
|
||||
_check_plot_works(df.plot, color=["red"])
|
||||
|
||||
@pytest.mark.parametrize("color", [(1, 0, 0), (1, 0, 0, 0.5)])
|
||||
def test_rgb_tuple_color(self, color):
|
||||
# GH 16695
|
||||
df = DataFrame({"x": [1, 2], "y": [3, 4]})
|
||||
_check_plot_works(df.plot, x="x", y="y", color=color)
|
||||
|
||||
def test_color_empty_string(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
|
||||
with pytest.raises(ValueError, match="Invalid color argument:"):
|
||||
df.plot(color="")
|
||||
|
||||
def test_color_and_style_arguments(self):
|
||||
df = DataFrame({"x": [1, 2], "y": [3, 4]})
|
||||
# passing both 'color' and 'style' arguments should be allowed
|
||||
# if there is no color symbol in the style strings:
|
||||
ax = df.plot(color=["red", "black"], style=["-", "--"])
|
||||
# check that the linestyles are correctly set:
|
||||
linestyle = [line.get_linestyle() for line in ax.lines]
|
||||
assert linestyle == ["-", "--"]
|
||||
# check that the colors are correctly set:
|
||||
color = [line.get_color() for line in ax.lines]
|
||||
assert color == ["red", "black"]
|
||||
# passing both 'color' and 'style' arguments should not be allowed
|
||||
# if there is a color symbol in the style strings:
|
||||
msg = (
|
||||
"Cannot pass 'style' string with a color symbol and 'color' keyword "
|
||||
"argument. Please use one or the other or pass 'style' without a color "
|
||||
"symbol"
|
||||
)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.plot(color=["red", "black"], style=["k-", "r--"])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"color, expected",
|
||||
[
|
||||
("green", ["green"] * 4),
|
||||
(["yellow", "red", "green", "blue"], ["yellow", "red", "green", "blue"]),
|
||||
],
|
||||
)
|
||||
def test_color_and_marker(self, color, expected):
|
||||
# GH 21003
|
||||
df = DataFrame(np.random.default_rng(2).random((7, 4)))
|
||||
ax = df.plot(color=color, style="d--")
|
||||
# check colors
|
||||
result = [i.get_color() for i in ax.lines]
|
||||
assert result == expected
|
||||
# check markers and linestyles
|
||||
assert all(i.get_linestyle() == "--" for i in ax.lines)
|
||||
assert all(i.get_marker() == "d" for i in ax.lines)
|
||||
|
||||
def test_bar_colors(self):
|
||||
default_colors = _unpack_cycler(plt.rcParams)
|
||||
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot.bar()
|
||||
_check_colors(ax.patches[::5], facecolors=default_colors[:5])
|
||||
|
||||
def test_bar_colors_custom(self):
|
||||
custom_colors = "rgcby"
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot.bar(color=custom_colors)
|
||||
_check_colors(ax.patches[::5], facecolors=custom_colors)
|
||||
|
||||
@pytest.mark.parametrize("colormap", ["jet", cm.jet])
|
||||
def test_bar_colors_cmap(self, colormap):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
|
||||
ax = df.plot.bar(colormap=colormap)
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, 5)]
|
||||
_check_colors(ax.patches[::5], facecolors=rgba_colors)
|
||||
|
||||
def test_bar_colors_single_col(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.loc[:, [0]].plot.bar(color="DodgerBlue")
|
||||
_check_colors([ax.patches[0]], facecolors=["DodgerBlue"])
|
||||
|
||||
def test_bar_colors_green(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot(kind="bar", color="green")
|
||||
_check_colors(ax.patches[::5], facecolors=["green"] * 5)
|
||||
|
||||
def test_bar_user_colors(self):
|
||||
df = DataFrame(
|
||||
{"A": range(4), "B": range(1, 5), "color": ["red", "blue", "blue", "red"]}
|
||||
)
|
||||
# This should *only* work when `y` is specified, else
|
||||
# we use one color per column
|
||||
ax = df.plot.bar(y="A", color=df["color"])
|
||||
result = [p.get_facecolor() for p in ax.patches]
|
||||
expected = [
|
||||
(1.0, 0.0, 0.0, 1.0),
|
||||
(0.0, 0.0, 1.0, 1.0),
|
||||
(0.0, 0.0, 1.0, 1.0),
|
||||
(1.0, 0.0, 0.0, 1.0),
|
||||
]
|
||||
assert result == expected
|
||||
|
||||
def test_if_scatterplot_colorbar_affects_xaxis_visibility(self):
|
||||
# addressing issue #10611, to ensure colobar does not
|
||||
# interfere with x-axis label and ticklabels with
|
||||
# ipython inline backend.
|
||||
random_array = np.random.default_rng(2).random((10, 3))
|
||||
df = DataFrame(random_array, columns=["A label", "B label", "C label"])
|
||||
|
||||
ax1 = df.plot.scatter(x="A label", y="B label")
|
||||
ax2 = df.plot.scatter(x="A label", y="B label", c="C label")
|
||||
|
||||
vis1 = [vis.get_visible() for vis in ax1.xaxis.get_minorticklabels()]
|
||||
vis2 = [vis.get_visible() for vis in ax2.xaxis.get_minorticklabels()]
|
||||
assert vis1 == vis2
|
||||
|
||||
vis1 = [vis.get_visible() for vis in ax1.xaxis.get_majorticklabels()]
|
||||
vis2 = [vis.get_visible() for vis in ax2.xaxis.get_majorticklabels()]
|
||||
assert vis1 == vis2
|
||||
|
||||
assert (
|
||||
ax1.xaxis.get_label().get_visible() == ax2.xaxis.get_label().get_visible()
|
||||
)
|
||||
|
||||
def test_if_hexbin_xaxis_label_is_visible(self):
|
||||
# addressing issue #10678, to ensure colobar does not
|
||||
# interfere with x-axis label and ticklabels with
|
||||
# ipython inline backend.
|
||||
random_array = np.random.default_rng(2).random((10, 3))
|
||||
df = DataFrame(random_array, columns=["A label", "B label", "C label"])
|
||||
|
||||
ax = df.plot.hexbin("A label", "B label", gridsize=12)
|
||||
assert all(vis.get_visible() for vis in ax.xaxis.get_minorticklabels())
|
||||
assert all(vis.get_visible() for vis in ax.xaxis.get_majorticklabels())
|
||||
assert ax.xaxis.get_label().get_visible()
|
||||
|
||||
def test_if_scatterplot_colorbars_are_next_to_parent_axes(self):
|
||||
random_array = np.random.default_rng(2).random((10, 3))
|
||||
df = DataFrame(random_array, columns=["A label", "B label", "C label"])
|
||||
|
||||
fig, axes = plt.subplots(1, 2)
|
||||
df.plot.scatter("A label", "B label", c="C label", ax=axes[0])
|
||||
df.plot.scatter("A label", "B label", c="C label", ax=axes[1])
|
||||
plt.tight_layout()
|
||||
|
||||
points = np.array([ax.get_position().get_points() for ax in fig.axes])
|
||||
axes_x_coords = points[:, :, 0]
|
||||
parent_distance = axes_x_coords[1, :] - axes_x_coords[0, :]
|
||||
colorbar_distance = axes_x_coords[3, :] - axes_x_coords[2, :]
|
||||
assert np.isclose(parent_distance, colorbar_distance, atol=1e-7).all()
|
||||
|
||||
@pytest.mark.parametrize("cmap", [None, "Greys"])
|
||||
def test_scatter_with_c_column_name_with_colors(self, cmap):
|
||||
# https://github.com/pandas-dev/pandas/issues/34316
|
||||
|
||||
df = DataFrame(
|
||||
[[5.1, 3.5], [4.9, 3.0], [7.0, 3.2], [6.4, 3.2], [5.9, 3.0]],
|
||||
columns=["length", "width"],
|
||||
)
|
||||
df["species"] = ["r", "r", "g", "g", "b"]
|
||||
if cmap is not None:
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
ax = df.plot.scatter(x=0, y=1, cmap=cmap, c="species")
|
||||
else:
|
||||
ax = df.plot.scatter(x=0, y=1, c="species", cmap=cmap)
|
||||
assert ax.collections[0].colorbar is None
|
||||
|
||||
def test_scatter_colors(self):
|
||||
df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3]})
|
||||
with pytest.raises(TypeError, match="Specify exactly one of `c` and `color`"):
|
||||
df.plot.scatter(x="a", y="b", c="c", color="green")
|
||||
|
||||
def test_scatter_colors_not_raising_warnings(self):
|
||||
# GH-53908. Do not raise UserWarning: No data for colormapping
|
||||
# provided via 'c'. Parameters 'cmap' will be ignored
|
||||
df = DataFrame({"x": [1, 2, 3], "y": [1, 2, 3]})
|
||||
with tm.assert_produces_warning(None):
|
||||
df.plot.scatter(x="x", y="y", c="b")
|
||||
|
||||
def test_scatter_colors_default(self):
|
||||
df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3]})
|
||||
default_colors = _unpack_cycler(mpl.pyplot.rcParams)
|
||||
|
||||
ax = df.plot.scatter(x="a", y="b", c="c")
|
||||
tm.assert_numpy_array_equal(
|
||||
ax.collections[0].get_facecolor()[0],
|
||||
np.array(mpl.colors.ColorConverter.to_rgba(default_colors[0])),
|
||||
)
|
||||
|
||||
def test_scatter_colors_white(self):
|
||||
df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3]})
|
||||
ax = df.plot.scatter(x="a", y="b", color="white")
|
||||
tm.assert_numpy_array_equal(
|
||||
ax.collections[0].get_facecolor()[0],
|
||||
np.array([1, 1, 1, 1], dtype=np.float64),
|
||||
)
|
||||
|
||||
def test_scatter_colorbar_different_cmap(self):
|
||||
# GH 33389
|
||||
df = DataFrame({"x": [1, 2, 3], "y": [1, 3, 2], "c": [1, 2, 3]})
|
||||
df["x2"] = df["x"] + 1
|
||||
|
||||
_, ax = plt.subplots()
|
||||
df.plot("x", "y", c="c", kind="scatter", cmap="cividis", ax=ax)
|
||||
df.plot("x2", "y", c="c", kind="scatter", cmap="magma", ax=ax)
|
||||
|
||||
assert ax.collections[0].cmap.name == "cividis"
|
||||
assert ax.collections[1].cmap.name == "magma"
|
||||
|
||||
def test_line_colors(self):
|
||||
custom_colors = "rgcby"
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
|
||||
ax = df.plot(color=custom_colors)
|
||||
_check_colors(ax.get_lines(), linecolors=custom_colors)
|
||||
|
||||
plt.close("all")
|
||||
|
||||
ax2 = df.plot(color=custom_colors)
|
||||
lines2 = ax2.get_lines()
|
||||
|
||||
for l1, l2 in zip(ax.get_lines(), lines2):
|
||||
assert l1.get_color() == l2.get_color()
|
||||
|
||||
@pytest.mark.parametrize("colormap", ["jet", cm.jet])
|
||||
def test_line_colors_cmap(self, colormap):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot(colormap=colormap)
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
_check_colors(ax.get_lines(), linecolors=rgba_colors)
|
||||
|
||||
def test_line_colors_single_col(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# make color a list if plotting one column frame
|
||||
# handles cases like df.plot(color='DodgerBlue')
|
||||
ax = df.loc[:, [0]].plot(color="DodgerBlue")
|
||||
_check_colors(ax.lines, linecolors=["DodgerBlue"])
|
||||
|
||||
def test_line_colors_single_color(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot(color="red")
|
||||
_check_colors(ax.get_lines(), linecolors=["red"] * 5)
|
||||
|
||||
def test_line_colors_hex(self):
|
||||
# GH 10299
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
custom_colors = ["#FF0000", "#0000FF", "#FFFF00", "#000000", "#FFFFFF"]
|
||||
ax = df.plot(color=custom_colors)
|
||||
_check_colors(ax.get_lines(), linecolors=custom_colors)
|
||||
|
||||
def test_dont_modify_colors(self):
|
||||
colors = ["r", "g", "b"]
|
||||
DataFrame(np.random.default_rng(2).random((10, 2))).plot(color=colors)
|
||||
assert len(colors) == 3
|
||||
|
||||
def test_line_colors_and_styles_subplots(self):
|
||||
# GH 9894
|
||||
default_colors = _unpack_cycler(mpl.pyplot.rcParams)
|
||||
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
|
||||
axes = df.plot(subplots=True)
|
||||
for ax, c in zip(axes, list(default_colors)):
|
||||
_check_colors(ax.get_lines(), linecolors=[c])
|
||||
|
||||
@pytest.mark.parametrize("color", ["k", "green"])
|
||||
def test_line_colors_and_styles_subplots_single_color_str(self, color):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
axes = df.plot(subplots=True, color=color)
|
||||
for ax in axes:
|
||||
_check_colors(ax.get_lines(), linecolors=[color])
|
||||
|
||||
@pytest.mark.parametrize("color", ["rgcby", list("rgcby")])
|
||||
def test_line_colors_and_styles_subplots_custom_colors(self, color):
|
||||
# GH 9894
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
axes = df.plot(color=color, subplots=True)
|
||||
for ax, c in zip(axes, list(color)):
|
||||
_check_colors(ax.get_lines(), linecolors=[c])
|
||||
|
||||
def test_line_colors_and_styles_subplots_colormap_hex(self):
|
||||
# GH 9894
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# GH 10299
|
||||
custom_colors = ["#FF0000", "#0000FF", "#FFFF00", "#000000", "#FFFFFF"]
|
||||
axes = df.plot(color=custom_colors, subplots=True)
|
||||
for ax, c in zip(axes, list(custom_colors)):
|
||||
_check_colors(ax.get_lines(), linecolors=[c])
|
||||
|
||||
@pytest.mark.parametrize("cmap", ["jet", cm.jet])
|
||||
def test_line_colors_and_styles_subplots_colormap_subplot(self, cmap):
|
||||
# GH 9894
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
axes = df.plot(colormap=cmap, subplots=True)
|
||||
for ax, c in zip(axes, rgba_colors):
|
||||
_check_colors(ax.get_lines(), linecolors=[c])
|
||||
|
||||
def test_line_colors_and_styles_subplots_single_col(self):
|
||||
# GH 9894
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# make color a list if plotting one column frame
|
||||
# handles cases like df.plot(color='DodgerBlue')
|
||||
axes = df.loc[:, [0]].plot(color="DodgerBlue", subplots=True)
|
||||
_check_colors(axes[0].lines, linecolors=["DodgerBlue"])
|
||||
|
||||
def test_line_colors_and_styles_subplots_single_char(self):
|
||||
# GH 9894
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# single character style
|
||||
axes = df.plot(style="r", subplots=True)
|
||||
for ax in axes:
|
||||
_check_colors(ax.get_lines(), linecolors=["r"])
|
||||
|
||||
def test_line_colors_and_styles_subplots_list_styles(self):
|
||||
# GH 9894
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# list of styles
|
||||
styles = list("rgcby")
|
||||
axes = df.plot(style=styles, subplots=True)
|
||||
for ax, c in zip(axes, styles):
|
||||
_check_colors(ax.get_lines(), linecolors=[c])
|
||||
|
||||
def test_area_colors(self):
|
||||
from matplotlib.collections import PolyCollection
|
||||
|
||||
custom_colors = "rgcby"
|
||||
df = DataFrame(np.random.default_rng(2).random((5, 5)))
|
||||
|
||||
ax = df.plot.area(color=custom_colors)
|
||||
_check_colors(ax.get_lines(), linecolors=custom_colors)
|
||||
poly = [o for o in ax.get_children() if isinstance(o, PolyCollection)]
|
||||
_check_colors(poly, facecolors=custom_colors)
|
||||
|
||||
handles, _ = ax.get_legend_handles_labels()
|
||||
_check_colors(handles, facecolors=custom_colors)
|
||||
|
||||
for h in handles:
|
||||
assert h.get_alpha() is None
|
||||
|
||||
def test_area_colors_poly(self):
|
||||
from matplotlib import cm
|
||||
from matplotlib.collections import PolyCollection
|
||||
|
||||
df = DataFrame(np.random.default_rng(2).random((5, 5)))
|
||||
ax = df.plot.area(colormap="jet")
|
||||
jet_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
_check_colors(ax.get_lines(), linecolors=jet_colors)
|
||||
poly = [o for o in ax.get_children() if isinstance(o, PolyCollection)]
|
||||
_check_colors(poly, facecolors=jet_colors)
|
||||
|
||||
handles, _ = ax.get_legend_handles_labels()
|
||||
_check_colors(handles, facecolors=jet_colors)
|
||||
for h in handles:
|
||||
assert h.get_alpha() is None
|
||||
|
||||
def test_area_colors_stacked_false(self):
|
||||
from matplotlib import cm
|
||||
from matplotlib.collections import PolyCollection
|
||||
|
||||
df = DataFrame(np.random.default_rng(2).random((5, 5)))
|
||||
jet_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
# When stacked=False, alpha is set to 0.5
|
||||
ax = df.plot.area(colormap=cm.jet, stacked=False)
|
||||
_check_colors(ax.get_lines(), linecolors=jet_colors)
|
||||
poly = [o for o in ax.get_children() if isinstance(o, PolyCollection)]
|
||||
jet_with_alpha = [(c[0], c[1], c[2], 0.5) for c in jet_colors]
|
||||
_check_colors(poly, facecolors=jet_with_alpha)
|
||||
|
||||
handles, _ = ax.get_legend_handles_labels()
|
||||
linecolors = jet_with_alpha
|
||||
_check_colors(handles[: len(jet_colors)], linecolors=linecolors)
|
||||
for h in handles:
|
||||
assert h.get_alpha() == 0.5
|
||||
|
||||
def test_hist_colors(self):
|
||||
default_colors = _unpack_cycler(mpl.pyplot.rcParams)
|
||||
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot.hist()
|
||||
_check_colors(ax.patches[::10], facecolors=default_colors[:5])
|
||||
|
||||
def test_hist_colors_single_custom(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
custom_colors = "rgcby"
|
||||
ax = df.plot.hist(color=custom_colors)
|
||||
_check_colors(ax.patches[::10], facecolors=custom_colors)
|
||||
|
||||
@pytest.mark.parametrize("colormap", ["jet", cm.jet])
|
||||
def test_hist_colors_cmap(self, colormap):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot.hist(colormap=colormap)
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, 5)]
|
||||
_check_colors(ax.patches[::10], facecolors=rgba_colors)
|
||||
|
||||
def test_hist_colors_single_col(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.loc[:, [0]].plot.hist(color="DodgerBlue")
|
||||
_check_colors([ax.patches[0]], facecolors=["DodgerBlue"])
|
||||
|
||||
def test_hist_colors_single_color(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot(kind="hist", color="green")
|
||||
_check_colors(ax.patches[::10], facecolors=["green"] * 5)
|
||||
|
||||
def test_kde_colors(self):
|
||||
pytest.importorskip("scipy")
|
||||
custom_colors = "rgcby"
|
||||
df = DataFrame(np.random.default_rng(2).random((5, 5)))
|
||||
|
||||
ax = df.plot.kde(color=custom_colors)
|
||||
_check_colors(ax.get_lines(), linecolors=custom_colors)
|
||||
|
||||
@pytest.mark.parametrize("colormap", ["jet", cm.jet])
|
||||
def test_kde_colors_cmap(self, colormap):
|
||||
pytest.importorskip("scipy")
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot.kde(colormap=colormap)
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
_check_colors(ax.get_lines(), linecolors=rgba_colors)
|
||||
|
||||
def test_kde_colors_and_styles_subplots(self):
|
||||
pytest.importorskip("scipy")
|
||||
default_colors = _unpack_cycler(mpl.pyplot.rcParams)
|
||||
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
|
||||
axes = df.plot(kind="kde", subplots=True)
|
||||
for ax, c in zip(axes, list(default_colors)):
|
||||
_check_colors(ax.get_lines(), linecolors=[c])
|
||||
|
||||
@pytest.mark.parametrize("colormap", ["k", "red"])
|
||||
def test_kde_colors_and_styles_subplots_single_col_str(self, colormap):
|
||||
pytest.importorskip("scipy")
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
axes = df.plot(kind="kde", color=colormap, subplots=True)
|
||||
for ax in axes:
|
||||
_check_colors(ax.get_lines(), linecolors=[colormap])
|
||||
|
||||
def test_kde_colors_and_styles_subplots_custom_color(self):
|
||||
pytest.importorskip("scipy")
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
custom_colors = "rgcby"
|
||||
axes = df.plot(kind="kde", color=custom_colors, subplots=True)
|
||||
for ax, c in zip(axes, list(custom_colors)):
|
||||
_check_colors(ax.get_lines(), linecolors=[c])
|
||||
|
||||
@pytest.mark.parametrize("colormap", ["jet", cm.jet])
|
||||
def test_kde_colors_and_styles_subplots_cmap(self, colormap):
|
||||
pytest.importorskip("scipy")
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
axes = df.plot(kind="kde", colormap=colormap, subplots=True)
|
||||
for ax, c in zip(axes, rgba_colors):
|
||||
_check_colors(ax.get_lines(), linecolors=[c])
|
||||
|
||||
def test_kde_colors_and_styles_subplots_single_col(self):
|
||||
pytest.importorskip("scipy")
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# make color a list if plotting one column frame
|
||||
# handles cases like df.plot(color='DodgerBlue')
|
||||
axes = df.loc[:, [0]].plot(kind="kde", color="DodgerBlue", subplots=True)
|
||||
_check_colors(axes[0].lines, linecolors=["DodgerBlue"])
|
||||
|
||||
def test_kde_colors_and_styles_subplots_single_char(self):
|
||||
pytest.importorskip("scipy")
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# list of styles
|
||||
# single character style
|
||||
axes = df.plot(kind="kde", style="r", subplots=True)
|
||||
for ax in axes:
|
||||
_check_colors(ax.get_lines(), linecolors=["r"])
|
||||
|
||||
def test_kde_colors_and_styles_subplots_list(self):
|
||||
pytest.importorskip("scipy")
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# list of styles
|
||||
styles = list("rgcby")
|
||||
axes = df.plot(kind="kde", style=styles, subplots=True)
|
||||
for ax, c in zip(axes, styles):
|
||||
_check_colors(ax.get_lines(), linecolors=[c])
|
||||
|
||||
def test_boxplot_colors(self):
|
||||
default_colors = _unpack_cycler(mpl.pyplot.rcParams)
|
||||
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
bp = df.plot.box(return_type="dict")
|
||||
_check_colors_box(
|
||||
bp,
|
||||
default_colors[0],
|
||||
default_colors[0],
|
||||
default_colors[2],
|
||||
default_colors[0],
|
||||
)
|
||||
|
||||
def test_boxplot_colors_dict_colors(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
dict_colors = {
|
||||
"boxes": "#572923",
|
||||
"whiskers": "#982042",
|
||||
"medians": "#804823",
|
||||
"caps": "#123456",
|
||||
}
|
||||
bp = df.plot.box(color=dict_colors, sym="r+", return_type="dict")
|
||||
_check_colors_box(
|
||||
bp,
|
||||
dict_colors["boxes"],
|
||||
dict_colors["whiskers"],
|
||||
dict_colors["medians"],
|
||||
dict_colors["caps"],
|
||||
"r",
|
||||
)
|
||||
|
||||
def test_boxplot_colors_default_color(self):
|
||||
default_colors = _unpack_cycler(mpl.pyplot.rcParams)
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# partial colors
|
||||
dict_colors = {"whiskers": "c", "medians": "m"}
|
||||
bp = df.plot.box(color=dict_colors, return_type="dict")
|
||||
_check_colors_box(bp, default_colors[0], "c", "m", default_colors[0])
|
||||
|
||||
@pytest.mark.parametrize("colormap", ["jet", cm.jet])
|
||||
def test_boxplot_colors_cmap(self, colormap):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
bp = df.plot.box(colormap=colormap, return_type="dict")
|
||||
jet_colors = [cm.jet(n) for n in np.linspace(0, 1, 3)]
|
||||
_check_colors_box(
|
||||
bp, jet_colors[0], jet_colors[0], jet_colors[2], jet_colors[0]
|
||||
)
|
||||
|
||||
def test_boxplot_colors_single(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# string color is applied to all artists except fliers
|
||||
bp = df.plot.box(color="DodgerBlue", return_type="dict")
|
||||
_check_colors_box(bp, "DodgerBlue", "DodgerBlue", "DodgerBlue", "DodgerBlue")
|
||||
|
||||
def test_boxplot_colors_tuple(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# tuple is also applied to all artists except fliers
|
||||
bp = df.plot.box(color=(0, 1, 0), sym="#123456", return_type="dict")
|
||||
_check_colors_box(bp, (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), "#123456")
|
||||
|
||||
def test_boxplot_colors_invalid(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
msg = re.escape(
|
||||
"color dict contains invalid key 'xxxx'. The key must be either "
|
||||
"['boxes', 'whiskers', 'medians', 'caps']"
|
||||
)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
# Color contains invalid key results in ValueError
|
||||
df.plot.box(color={"boxes": "red", "xxxx": "blue"})
|
||||
|
||||
def test_default_color_cycle(self):
|
||||
import cycler
|
||||
|
||||
colors = list("rgbk")
|
||||
plt.rcParams["axes.prop_cycle"] = cycler.cycler("color", colors)
|
||||
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
|
||||
ax = df.plot()
|
||||
|
||||
expected = _unpack_cycler(plt.rcParams)[:3]
|
||||
_check_colors(ax.get_lines(), linecolors=expected)
|
||||
|
||||
def test_no_color_bar(self):
|
||||
df = DataFrame(
|
||||
{
|
||||
"A": np.random.default_rng(2).uniform(size=20),
|
||||
"B": np.random.default_rng(2).uniform(size=20),
|
||||
"C": np.arange(20) + np.random.default_rng(2).uniform(size=20),
|
||||
}
|
||||
)
|
||||
ax = df.plot.hexbin(x="A", y="B", colorbar=None)
|
||||
assert ax.collections[0].colorbar is None
|
||||
|
||||
def test_mixing_cmap_and_colormap_raises(self):
|
||||
df = DataFrame(
|
||||
{
|
||||
"A": np.random.default_rng(2).uniform(size=20),
|
||||
"B": np.random.default_rng(2).uniform(size=20),
|
||||
"C": np.arange(20) + np.random.default_rng(2).uniform(size=20),
|
||||
}
|
||||
)
|
||||
msg = "Only specify one of `cmap` and `colormap`"
|
||||
with pytest.raises(TypeError, match=msg):
|
||||
df.plot.hexbin(x="A", y="B", cmap="YlGn", colormap="BuGn")
|
||||
|
||||
def test_passed_bar_colors(self):
|
||||
color_tuples = [(0.9, 0, 0, 1), (0, 0.9, 0, 1), (0, 0, 0.9, 1)]
|
||||
colormap = mpl.colors.ListedColormap(color_tuples)
|
||||
barplot = DataFrame([[1, 2, 3]]).plot(kind="bar", cmap=colormap)
|
||||
assert color_tuples == [c.get_facecolor() for c in barplot.patches]
|
||||
|
||||
def test_rcParams_bar_colors(self):
|
||||
color_tuples = [(0.9, 0, 0, 1), (0, 0.9, 0, 1), (0, 0, 0.9, 1)]
|
||||
with mpl.rc_context(rc={"axes.prop_cycle": mpl.cycler("color", color_tuples)}):
|
||||
barplot = DataFrame([[1, 2, 3]]).plot(kind="bar")
|
||||
assert color_tuples == [c.get_facecolor() for c in barplot.patches]
|
||||
|
||||
def test_colors_of_columns_with_same_name(self):
|
||||
# ISSUE 11136 -> https://github.com/pandas-dev/pandas/issues/11136
|
||||
# Creating a DataFrame with duplicate column labels and testing colors of them.
|
||||
df = DataFrame({"b": [0, 1, 0], "a": [1, 2, 3]})
|
||||
df1 = DataFrame({"a": [2, 4, 6]})
|
||||
df_concat = pd.concat([df, df1], axis=1)
|
||||
result = df_concat.plot()
|
||||
legend = result.get_legend()
|
||||
if Version(mpl.__version__) < Version("3.7"):
|
||||
handles = legend.legendHandles
|
||||
else:
|
||||
handles = legend.legend_handles
|
||||
for legend, line in zip(handles, result.lines):
|
||||
assert legend.get_color() == line.get_color()
|
||||
|
||||
def test_invalid_colormap(self):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((3, 2)), columns=["A", "B"]
|
||||
)
|
||||
msg = "(is not a valid value)|(is not a known colormap)"
|
||||
with pytest.raises((ValueError, KeyError), match=msg):
|
||||
df.plot(colormap="invalid_colormap")
|
||||
|
||||
def test_dataframe_none_color(self):
|
||||
# GH51953
|
||||
df = DataFrame([[1, 2, 3]])
|
||||
ax = df.plot(color=None)
|
||||
expected = _unpack_cycler(mpl.pyplot.rcParams)[:3]
|
||||
_check_colors(ax.get_lines(), linecolors=expected)
|
@ -0,0 +1,72 @@
|
||||
""" Test cases for DataFrame.plot """
|
||||
|
||||
import pytest
|
||||
|
||||
from pandas import DataFrame
|
||||
from pandas.tests.plotting.common import _check_visible
|
||||
|
||||
pytest.importorskip("matplotlib")
|
||||
|
||||
|
||||
class TestDataFramePlotsGroupby:
|
||||
def _assert_ytickslabels_visibility(self, axes, expected):
|
||||
for ax, exp in zip(axes, expected):
|
||||
_check_visible(ax.get_yticklabels(), visible=exp)
|
||||
|
||||
def _assert_xtickslabels_visibility(self, axes, expected):
|
||||
for ax, exp in zip(axes, expected):
|
||||
_check_visible(ax.get_xticklabels(), visible=exp)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kwargs, expected",
|
||||
[
|
||||
# behavior without keyword
|
||||
({}, [True, False, True, False]),
|
||||
# set sharey=True should be identical
|
||||
({"sharey": True}, [True, False, True, False]),
|
||||
# sharey=False, all yticklabels should be visible
|
||||
({"sharey": False}, [True, True, True, True]),
|
||||
],
|
||||
)
|
||||
def test_groupby_boxplot_sharey(self, kwargs, expected):
|
||||
# https://github.com/pandas-dev/pandas/issues/20968
|
||||
# sharey can now be switched check whether the right
|
||||
# pair of axes is turned on or off
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": [-1.43, -0.15, -3.70, -1.43, -0.14],
|
||||
"b": [0.56, 0.84, 0.29, 0.56, 0.85],
|
||||
"c": [0, 1, 2, 3, 1],
|
||||
},
|
||||
index=[0, 1, 2, 3, 4],
|
||||
)
|
||||
axes = df.groupby("c").boxplot(**kwargs)
|
||||
self._assert_ytickslabels_visibility(axes, expected)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kwargs, expected",
|
||||
[
|
||||
# behavior without keyword
|
||||
({}, [True, True, True, True]),
|
||||
# set sharex=False should be identical
|
||||
({"sharex": False}, [True, True, True, True]),
|
||||
# sharex=True, xticklabels should be visible
|
||||
# only for bottom plots
|
||||
({"sharex": True}, [False, False, True, True]),
|
||||
],
|
||||
)
|
||||
def test_groupby_boxplot_sharex(self, kwargs, expected):
|
||||
# https://github.com/pandas-dev/pandas/issues/20968
|
||||
# sharex can now be switched check whether the right
|
||||
# pair of axes is turned on or off
|
||||
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": [-1.43, -0.15, -3.70, -1.43, -0.14],
|
||||
"b": [0.56, 0.84, 0.29, 0.56, 0.85],
|
||||
"c": [0, 1, 2, 3, 1],
|
||||
},
|
||||
index=[0, 1, 2, 3, 4],
|
||||
)
|
||||
axes = df.groupby("c").boxplot(**kwargs)
|
||||
self._assert_xtickslabels_visibility(axes, expected)
|
@ -0,0 +1,272 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
date_range,
|
||||
)
|
||||
from pandas.tests.plotting.common import (
|
||||
_check_legend_labels,
|
||||
_check_legend_marker,
|
||||
_check_text_labels,
|
||||
)
|
||||
from pandas.util.version import Version
|
||||
|
||||
mpl = pytest.importorskip("matplotlib")
|
||||
|
||||
|
||||
class TestFrameLegend:
|
||||
@pytest.mark.xfail(
|
||||
reason=(
|
||||
"Open bug in matplotlib "
|
||||
"https://github.com/matplotlib/matplotlib/issues/11357"
|
||||
)
|
||||
)
|
||||
def test_mixed_yerr(self):
|
||||
# https://github.com/pandas-dev/pandas/issues/39522
|
||||
from matplotlib.collections import LineCollection
|
||||
from matplotlib.lines import Line2D
|
||||
|
||||
df = DataFrame([{"x": 1, "a": 1, "b": 1}, {"x": 2, "a": 2, "b": 3}])
|
||||
|
||||
ax = df.plot("x", "a", c="orange", yerr=0.1, label="orange")
|
||||
df.plot("x", "b", c="blue", yerr=None, ax=ax, label="blue")
|
||||
|
||||
legend = ax.get_legend()
|
||||
if Version(mpl.__version__) < Version("3.7"):
|
||||
result_handles = legend.legendHandles
|
||||
else:
|
||||
result_handles = legend.legend_handles
|
||||
|
||||
assert isinstance(result_handles[0], LineCollection)
|
||||
assert isinstance(result_handles[1], Line2D)
|
||||
|
||||
def test_legend_false(self):
|
||||
# https://github.com/pandas-dev/pandas/issues/40044
|
||||
df = DataFrame({"a": [1, 1], "b": [2, 3]})
|
||||
df2 = DataFrame({"d": [2.5, 2.5]})
|
||||
|
||||
ax = df.plot(legend=True, color={"a": "blue", "b": "green"}, secondary_y="b")
|
||||
df2.plot(legend=True, color={"d": "red"}, ax=ax)
|
||||
legend = ax.get_legend()
|
||||
if Version(mpl.__version__) < Version("3.7"):
|
||||
handles = legend.legendHandles
|
||||
else:
|
||||
handles = legend.legend_handles
|
||||
result = [handle.get_color() for handle in handles]
|
||||
expected = ["blue", "green", "red"]
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize("kind", ["line", "bar", "barh", "kde", "area", "hist"])
|
||||
def test_df_legend_labels(self, kind):
|
||||
pytest.importorskip("scipy")
|
||||
df = DataFrame(np.random.default_rng(2).random((3, 3)), columns=["a", "b", "c"])
|
||||
df2 = DataFrame(
|
||||
np.random.default_rng(2).random((3, 3)), columns=["d", "e", "f"]
|
||||
)
|
||||
df3 = DataFrame(
|
||||
np.random.default_rng(2).random((3, 3)), columns=["g", "h", "i"]
|
||||
)
|
||||
df4 = DataFrame(
|
||||
np.random.default_rng(2).random((3, 3)), columns=["j", "k", "l"]
|
||||
)
|
||||
|
||||
ax = df.plot(kind=kind, legend=True)
|
||||
_check_legend_labels(ax, labels=df.columns)
|
||||
|
||||
ax = df2.plot(kind=kind, legend=False, ax=ax)
|
||||
_check_legend_labels(ax, labels=df.columns)
|
||||
|
||||
ax = df3.plot(kind=kind, legend=True, ax=ax)
|
||||
_check_legend_labels(ax, labels=df.columns.union(df3.columns))
|
||||
|
||||
ax = df4.plot(kind=kind, legend="reverse", ax=ax)
|
||||
expected = list(df.columns.union(df3.columns)) + list(reversed(df4.columns))
|
||||
_check_legend_labels(ax, labels=expected)
|
||||
|
||||
def test_df_legend_labels_secondary_y(self):
|
||||
pytest.importorskip("scipy")
|
||||
df = DataFrame(np.random.default_rng(2).random((3, 3)), columns=["a", "b", "c"])
|
||||
df2 = DataFrame(
|
||||
np.random.default_rng(2).random((3, 3)), columns=["d", "e", "f"]
|
||||
)
|
||||
df3 = DataFrame(
|
||||
np.random.default_rng(2).random((3, 3)), columns=["g", "h", "i"]
|
||||
)
|
||||
# Secondary Y
|
||||
ax = df.plot(legend=True, secondary_y="b")
|
||||
_check_legend_labels(ax, labels=["a", "b (right)", "c"])
|
||||
ax = df2.plot(legend=False, ax=ax)
|
||||
_check_legend_labels(ax, labels=["a", "b (right)", "c"])
|
||||
ax = df3.plot(kind="bar", legend=True, secondary_y="h", ax=ax)
|
||||
_check_legend_labels(ax, labels=["a", "b (right)", "c", "g", "h (right)", "i"])
|
||||
|
||||
def test_df_legend_labels_time_series(self):
|
||||
# Time Series
|
||||
pytest.importorskip("scipy")
|
||||
ind = date_range("1/1/2014", periods=3)
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((3, 3)),
|
||||
columns=["a", "b", "c"],
|
||||
index=ind,
|
||||
)
|
||||
df2 = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((3, 3)),
|
||||
columns=["d", "e", "f"],
|
||||
index=ind,
|
||||
)
|
||||
df3 = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((3, 3)),
|
||||
columns=["g", "h", "i"],
|
||||
index=ind,
|
||||
)
|
||||
ax = df.plot(legend=True, secondary_y="b")
|
||||
_check_legend_labels(ax, labels=["a", "b (right)", "c"])
|
||||
ax = df2.plot(legend=False, ax=ax)
|
||||
_check_legend_labels(ax, labels=["a", "b (right)", "c"])
|
||||
ax = df3.plot(legend=True, ax=ax)
|
||||
_check_legend_labels(ax, labels=["a", "b (right)", "c", "g", "h", "i"])
|
||||
|
||||
def test_df_legend_labels_time_series_scatter(self):
|
||||
# Time Series
|
||||
pytest.importorskip("scipy")
|
||||
ind = date_range("1/1/2014", periods=3)
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((3, 3)),
|
||||
columns=["a", "b", "c"],
|
||||
index=ind,
|
||||
)
|
||||
df2 = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((3, 3)),
|
||||
columns=["d", "e", "f"],
|
||||
index=ind,
|
||||
)
|
||||
df3 = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((3, 3)),
|
||||
columns=["g", "h", "i"],
|
||||
index=ind,
|
||||
)
|
||||
# scatter
|
||||
ax = df.plot.scatter(x="a", y="b", label="data1")
|
||||
_check_legend_labels(ax, labels=["data1"])
|
||||
ax = df2.plot.scatter(x="d", y="e", legend=False, label="data2", ax=ax)
|
||||
_check_legend_labels(ax, labels=["data1"])
|
||||
ax = df3.plot.scatter(x="g", y="h", label="data3", ax=ax)
|
||||
_check_legend_labels(ax, labels=["data1", "data3"])
|
||||
|
||||
def test_df_legend_labels_time_series_no_mutate(self):
|
||||
pytest.importorskip("scipy")
|
||||
ind = date_range("1/1/2014", periods=3)
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((3, 3)),
|
||||
columns=["a", "b", "c"],
|
||||
index=ind,
|
||||
)
|
||||
# ensure label args pass through and
|
||||
# index name does not mutate
|
||||
# column names don't mutate
|
||||
df5 = df.set_index("a")
|
||||
ax = df5.plot(y="b")
|
||||
_check_legend_labels(ax, labels=["b"])
|
||||
ax = df5.plot(y="b", label="LABEL_b")
|
||||
_check_legend_labels(ax, labels=["LABEL_b"])
|
||||
_check_text_labels(ax.xaxis.get_label(), "a")
|
||||
ax = df5.plot(y="c", label="LABEL_c", ax=ax)
|
||||
_check_legend_labels(ax, labels=["LABEL_b", "LABEL_c"])
|
||||
assert df5.columns.tolist() == ["b", "c"]
|
||||
|
||||
def test_missing_marker_multi_plots_on_same_ax(self):
|
||||
# GH 18222
|
||||
df = DataFrame(data=[[1, 1, 1, 1], [2, 2, 4, 8]], columns=["x", "r", "g", "b"])
|
||||
_, ax = mpl.pyplot.subplots(nrows=1, ncols=3)
|
||||
# Left plot
|
||||
df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[0])
|
||||
df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[0])
|
||||
df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[0])
|
||||
_check_legend_labels(ax[0], labels=["r", "g", "b"])
|
||||
_check_legend_marker(ax[0], expected_markers=["o", "x", "o"])
|
||||
# Center plot
|
||||
df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[1])
|
||||
df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[1])
|
||||
df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[1])
|
||||
_check_legend_labels(ax[1], labels=["b", "r", "g"])
|
||||
_check_legend_marker(ax[1], expected_markers=["o", "o", "x"])
|
||||
# Right plot
|
||||
df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[2])
|
||||
df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[2])
|
||||
df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[2])
|
||||
_check_legend_labels(ax[2], labels=["g", "b", "r"])
|
||||
_check_legend_marker(ax[2], expected_markers=["x", "o", "o"])
|
||||
|
||||
def test_legend_name(self):
|
||||
multi = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((4, 4)),
|
||||
columns=[np.array(["a", "a", "b", "b"]), np.array(["x", "y", "x", "y"])],
|
||||
)
|
||||
multi.columns.names = ["group", "individual"]
|
||||
|
||||
ax = multi.plot()
|
||||
leg_title = ax.legend_.get_title()
|
||||
_check_text_labels(leg_title, "group,individual")
|
||||
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot(legend=True, ax=ax)
|
||||
leg_title = ax.legend_.get_title()
|
||||
_check_text_labels(leg_title, "group,individual")
|
||||
|
||||
df.columns.name = "new"
|
||||
ax = df.plot(legend=False, ax=ax)
|
||||
leg_title = ax.legend_.get_title()
|
||||
_check_text_labels(leg_title, "group,individual")
|
||||
|
||||
ax = df.plot(legend=True, ax=ax)
|
||||
leg_title = ax.legend_.get_title()
|
||||
_check_text_labels(leg_title, "new")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kind",
|
||||
[
|
||||
"line",
|
||||
"bar",
|
||||
"barh",
|
||||
pytest.param("kde", marks=td.skip_if_no("scipy")),
|
||||
"area",
|
||||
"hist",
|
||||
],
|
||||
)
|
||||
def test_no_legend(self, kind):
|
||||
df = DataFrame(np.random.default_rng(2).random((3, 3)), columns=["a", "b", "c"])
|
||||
ax = df.plot(kind=kind, legend=False)
|
||||
_check_legend_labels(ax, visible=False)
|
||||
|
||||
def test_missing_markers_legend(self):
|
||||
# 14958
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((8, 3)), columns=["A", "B", "C"]
|
||||
)
|
||||
ax = df.plot(y=["A"], marker="x", linestyle="solid")
|
||||
df.plot(y=["B"], marker="o", linestyle="dotted", ax=ax)
|
||||
df.plot(y=["C"], marker="<", linestyle="dotted", ax=ax)
|
||||
|
||||
_check_legend_labels(ax, labels=["A", "B", "C"])
|
||||
_check_legend_marker(ax, expected_markers=["x", "o", "<"])
|
||||
|
||||
def test_missing_markers_legend_using_style(self):
|
||||
# 14563
|
||||
df = DataFrame(
|
||||
{
|
||||
"A": [1, 2, 3, 4, 5, 6],
|
||||
"B": [2, 4, 1, 3, 2, 4],
|
||||
"C": [3, 3, 2, 6, 4, 2],
|
||||
"X": [1, 2, 3, 4, 5, 6],
|
||||
}
|
||||
)
|
||||
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
for kind in "ABC":
|
||||
df.plot("X", kind, label=kind, ax=ax, style=".")
|
||||
|
||||
_check_legend_labels(ax, labels=["A", "B", "C"])
|
||||
_check_legend_marker(ax, expected_markers=[".", ".", "."])
|
@ -0,0 +1,752 @@
|
||||
""" Test cases for DataFrame.plot """
|
||||
|
||||
import string
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pandas.compat import is_platform_linux
|
||||
from pandas.compat.numpy import np_version_gte1p24
|
||||
|
||||
import pandas as pd
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Series,
|
||||
date_range,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
_check_axes_shape,
|
||||
_check_box_return_type,
|
||||
_check_legend_labels,
|
||||
_check_ticks_props,
|
||||
_check_visible,
|
||||
_flatten_visible,
|
||||
)
|
||||
|
||||
from pandas.io.formats.printing import pprint_thing
|
||||
|
||||
mpl = pytest.importorskip("matplotlib")
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
|
||||
|
||||
class TestDataFramePlotsSubplots:
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("kind", ["bar", "barh", "line", "area"])
|
||||
def test_subplots(self, kind):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 3)),
|
||||
index=list(string.ascii_letters[:10]),
|
||||
)
|
||||
|
||||
axes = df.plot(kind=kind, subplots=True, sharex=True, legend=True)
|
||||
_check_axes_shape(axes, axes_num=3, layout=(3, 1))
|
||||
assert axes.shape == (3,)
|
||||
|
||||
for ax, column in zip(axes, df.columns):
|
||||
_check_legend_labels(ax, labels=[pprint_thing(column)])
|
||||
|
||||
for ax in axes[:-2]:
|
||||
_check_visible(ax.xaxis) # xaxis must be visible for grid
|
||||
_check_visible(ax.get_xticklabels(), visible=False)
|
||||
if kind != "bar":
|
||||
# change https://github.com/pandas-dev/pandas/issues/26714
|
||||
_check_visible(ax.get_xticklabels(minor=True), visible=False)
|
||||
_check_visible(ax.xaxis.get_label(), visible=False)
|
||||
_check_visible(ax.get_yticklabels())
|
||||
|
||||
_check_visible(axes[-1].xaxis)
|
||||
_check_visible(axes[-1].get_xticklabels())
|
||||
_check_visible(axes[-1].get_xticklabels(minor=True))
|
||||
_check_visible(axes[-1].xaxis.get_label())
|
||||
_check_visible(axes[-1].get_yticklabels())
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("kind", ["bar", "barh", "line", "area"])
|
||||
def test_subplots_no_share_x(self, kind):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 3)),
|
||||
index=list(string.ascii_letters[:10]),
|
||||
)
|
||||
axes = df.plot(kind=kind, subplots=True, sharex=False)
|
||||
for ax in axes:
|
||||
_check_visible(ax.xaxis)
|
||||
_check_visible(ax.get_xticklabels())
|
||||
_check_visible(ax.get_xticklabels(minor=True))
|
||||
_check_visible(ax.xaxis.get_label())
|
||||
_check_visible(ax.get_yticklabels())
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("kind", ["bar", "barh", "line", "area"])
|
||||
def test_subplots_no_legend(self, kind):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 3)),
|
||||
index=list(string.ascii_letters[:10]),
|
||||
)
|
||||
axes = df.plot(kind=kind, subplots=True, legend=False)
|
||||
for ax in axes:
|
||||
assert ax.get_legend() is None
|
||||
|
||||
@pytest.mark.parametrize("kind", ["line", "area"])
|
||||
def test_subplots_timeseries(self, kind):
|
||||
idx = date_range(start="2014-07-01", freq="ME", periods=10)
|
||||
df = DataFrame(np.random.default_rng(2).random((10, 3)), index=idx)
|
||||
|
||||
axes = df.plot(kind=kind, subplots=True, sharex=True)
|
||||
_check_axes_shape(axes, axes_num=3, layout=(3, 1))
|
||||
|
||||
for ax in axes[:-2]:
|
||||
# GH 7801
|
||||
_check_visible(ax.xaxis) # xaxis must be visible for grid
|
||||
_check_visible(ax.get_xticklabels(), visible=False)
|
||||
_check_visible(ax.get_xticklabels(minor=True), visible=False)
|
||||
_check_visible(ax.xaxis.get_label(), visible=False)
|
||||
_check_visible(ax.get_yticklabels())
|
||||
|
||||
_check_visible(axes[-1].xaxis)
|
||||
_check_visible(axes[-1].get_xticklabels())
|
||||
_check_visible(axes[-1].get_xticklabels(minor=True))
|
||||
_check_visible(axes[-1].xaxis.get_label())
|
||||
_check_visible(axes[-1].get_yticklabels())
|
||||
_check_ticks_props(axes, xrot=0)
|
||||
|
||||
@pytest.mark.parametrize("kind", ["line", "area"])
|
||||
def test_subplots_timeseries_rot(self, kind):
|
||||
idx = date_range(start="2014-07-01", freq="ME", periods=10)
|
||||
df = DataFrame(np.random.default_rng(2).random((10, 3)), index=idx)
|
||||
axes = df.plot(kind=kind, subplots=True, sharex=False, rot=45, fontsize=7)
|
||||
for ax in axes:
|
||||
_check_visible(ax.xaxis)
|
||||
_check_visible(ax.get_xticklabels())
|
||||
_check_visible(ax.get_xticklabels(minor=True))
|
||||
_check_visible(ax.xaxis.get_label())
|
||||
_check_visible(ax.get_yticklabels())
|
||||
_check_ticks_props(ax, xlabelsize=7, xrot=45, ylabelsize=7)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"col", ["numeric", "timedelta", "datetime_no_tz", "datetime_all_tz"]
|
||||
)
|
||||
def test_subplots_timeseries_y_axis(self, col):
|
||||
# GH16953
|
||||
data = {
|
||||
"numeric": np.array([1, 2, 5]),
|
||||
"timedelta": [
|
||||
pd.Timedelta(-10, unit="s"),
|
||||
pd.Timedelta(10, unit="m"),
|
||||
pd.Timedelta(10, unit="h"),
|
||||
],
|
||||
"datetime_no_tz": [
|
||||
pd.to_datetime("2017-08-01 00:00:00"),
|
||||
pd.to_datetime("2017-08-01 02:00:00"),
|
||||
pd.to_datetime("2017-08-02 00:00:00"),
|
||||
],
|
||||
"datetime_all_tz": [
|
||||
pd.to_datetime("2017-08-01 00:00:00", utc=True),
|
||||
pd.to_datetime("2017-08-01 02:00:00", utc=True),
|
||||
pd.to_datetime("2017-08-02 00:00:00", utc=True),
|
||||
],
|
||||
"text": ["This", "should", "fail"],
|
||||
}
|
||||
testdata = DataFrame(data)
|
||||
|
||||
ax = testdata.plot(y=col)
|
||||
result = ax.get_lines()[0].get_data()[1]
|
||||
expected = testdata[col].values
|
||||
assert (result == expected).all()
|
||||
|
||||
def test_subplots_timeseries_y_text_error(self):
|
||||
# GH16953
|
||||
data = {
|
||||
"numeric": np.array([1, 2, 5]),
|
||||
"text": ["This", "should", "fail"],
|
||||
}
|
||||
testdata = DataFrame(data)
|
||||
msg = "no numeric data to plot"
|
||||
with pytest.raises(TypeError, match=msg):
|
||||
testdata.plot(y="text")
|
||||
|
||||
@pytest.mark.xfail(reason="not support for period, categorical, datetime_mixed_tz")
|
||||
def test_subplots_timeseries_y_axis_not_supported(self):
|
||||
"""
|
||||
This test will fail for:
|
||||
period:
|
||||
since period isn't yet implemented in ``select_dtypes``
|
||||
and because it will need a custom value converter +
|
||||
tick formatter (as was done for x-axis plots)
|
||||
|
||||
categorical:
|
||||
because it will need a custom value converter +
|
||||
tick formatter (also doesn't work for x-axis, as of now)
|
||||
|
||||
datetime_mixed_tz:
|
||||
because of the way how pandas handles ``Series`` of
|
||||
``datetime`` objects with different timezone,
|
||||
generally converting ``datetime`` objects in a tz-aware
|
||||
form could help with this problem
|
||||
"""
|
||||
data = {
|
||||
"numeric": np.array([1, 2, 5]),
|
||||
"period": [
|
||||
pd.Period("2017-08-01 00:00:00", freq="H"),
|
||||
pd.Period("2017-08-01 02:00", freq="H"),
|
||||
pd.Period("2017-08-02 00:00:00", freq="H"),
|
||||
],
|
||||
"categorical": pd.Categorical(
|
||||
["c", "b", "a"], categories=["a", "b", "c"], ordered=False
|
||||
),
|
||||
"datetime_mixed_tz": [
|
||||
pd.to_datetime("2017-08-01 00:00:00", utc=True),
|
||||
pd.to_datetime("2017-08-01 02:00:00"),
|
||||
pd.to_datetime("2017-08-02 00:00:00"),
|
||||
],
|
||||
}
|
||||
testdata = DataFrame(data)
|
||||
ax_period = testdata.plot(x="numeric", y="period")
|
||||
assert (
|
||||
ax_period.get_lines()[0].get_data()[1] == testdata["period"].values
|
||||
).all()
|
||||
ax_categorical = testdata.plot(x="numeric", y="categorical")
|
||||
assert (
|
||||
ax_categorical.get_lines()[0].get_data()[1]
|
||||
== testdata["categorical"].values
|
||||
).all()
|
||||
ax_datetime_mixed_tz = testdata.plot(x="numeric", y="datetime_mixed_tz")
|
||||
assert (
|
||||
ax_datetime_mixed_tz.get_lines()[0].get_data()[1]
|
||||
== testdata["datetime_mixed_tz"].values
|
||||
).all()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"layout, exp_layout",
|
||||
[
|
||||
[(2, 2), (2, 2)],
|
||||
[(-1, 2), (2, 2)],
|
||||
[(2, -1), (2, 2)],
|
||||
[(1, 4), (1, 4)],
|
||||
[(-1, 4), (1, 4)],
|
||||
[(4, -1), (4, 1)],
|
||||
],
|
||||
)
|
||||
def test_subplots_layout_multi_column(self, layout, exp_layout):
|
||||
# GH 6667
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 3)),
|
||||
index=list(string.ascii_letters[:10]),
|
||||
)
|
||||
|
||||
axes = df.plot(subplots=True, layout=layout)
|
||||
_check_axes_shape(axes, axes_num=3, layout=exp_layout)
|
||||
assert axes.shape == exp_layout
|
||||
|
||||
def test_subplots_layout_multi_column_error(self):
|
||||
# GH 6667
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 3)),
|
||||
index=list(string.ascii_letters[:10]),
|
||||
)
|
||||
msg = "Layout of 1x1 must be larger than required size 3"
|
||||
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.plot(subplots=True, layout=(1, 1))
|
||||
|
||||
msg = "At least one dimension of layout must be positive"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.plot(subplots=True, layout=(-1, -1))
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kwargs, expected_axes_num, expected_layout, expected_shape",
|
||||
[
|
||||
({}, 1, (1, 1), (1,)),
|
||||
({"layout": (3, 3)}, 1, (3, 3), (3, 3)),
|
||||
],
|
||||
)
|
||||
def test_subplots_layout_single_column(
|
||||
self, kwargs, expected_axes_num, expected_layout, expected_shape
|
||||
):
|
||||
# GH 6667
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 1)),
|
||||
index=list(string.ascii_letters[:10]),
|
||||
)
|
||||
axes = df.plot(subplots=True, **kwargs)
|
||||
_check_axes_shape(
|
||||
axes,
|
||||
axes_num=expected_axes_num,
|
||||
layout=expected_layout,
|
||||
)
|
||||
assert axes.shape == expected_shape
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("idx", [range(5), date_range("1/1/2000", periods=5)])
|
||||
def test_subplots_warnings(self, idx):
|
||||
# GH 9464
|
||||
with tm.assert_produces_warning(None):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 4)), index=idx)
|
||||
df.plot(subplots=True, layout=(3, 2))
|
||||
|
||||
def test_subplots_multiple_axes(self):
|
||||
# GH 5353, 6970, GH 7069
|
||||
fig, axes = mpl.pyplot.subplots(2, 3)
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 3)),
|
||||
index=list(string.ascii_letters[:10]),
|
||||
)
|
||||
|
||||
returned = df.plot(subplots=True, ax=axes[0], sharex=False, sharey=False)
|
||||
_check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
assert returned.shape == (3,)
|
||||
assert returned[0].figure is fig
|
||||
# draw on second row
|
||||
returned = df.plot(subplots=True, ax=axes[1], sharex=False, sharey=False)
|
||||
_check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
assert returned.shape == (3,)
|
||||
assert returned[0].figure is fig
|
||||
_check_axes_shape(axes, axes_num=6, layout=(2, 3))
|
||||
|
||||
def test_subplots_multiple_axes_error(self):
|
||||
# GH 5353, 6970, GH 7069
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 3)),
|
||||
index=list(string.ascii_letters[:10]),
|
||||
)
|
||||
msg = "The number of passed axes must be 3, the same as the output plot"
|
||||
_, axes = mpl.pyplot.subplots(2, 3)
|
||||
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
# pass different number of axes from required
|
||||
df.plot(subplots=True, ax=axes)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"layout, exp_layout",
|
||||
[
|
||||
[(2, 1), (2, 2)],
|
||||
[(2, -1), (2, 2)],
|
||||
[(-1, 2), (2, 2)],
|
||||
],
|
||||
)
|
||||
def test_subplots_multiple_axes_2_dim(self, layout, exp_layout):
|
||||
# GH 5353, 6970, GH 7069
|
||||
# pass 2-dim axes and invalid layout
|
||||
# invalid lauout should not affect to input and return value
|
||||
# (show warning is tested in
|
||||
# TestDataFrameGroupByPlots.test_grouped_box_multiple_axes
|
||||
_, axes = mpl.pyplot.subplots(2, 2)
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 4)),
|
||||
index=list(string.ascii_letters[:10]),
|
||||
)
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
returned = df.plot(
|
||||
subplots=True, ax=axes, layout=layout, sharex=False, sharey=False
|
||||
)
|
||||
_check_axes_shape(returned, axes_num=4, layout=exp_layout)
|
||||
assert returned.shape == (4,)
|
||||
|
||||
def test_subplots_multiple_axes_single_col(self):
|
||||
# GH 5353, 6970, GH 7069
|
||||
# single column
|
||||
_, axes = mpl.pyplot.subplots(1, 1)
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 1)),
|
||||
index=list(string.ascii_letters[:10]),
|
||||
)
|
||||
|
||||
axes = df.plot(subplots=True, ax=[axes], sharex=False, sharey=False)
|
||||
_check_axes_shape(axes, axes_num=1, layout=(1, 1))
|
||||
assert axes.shape == (1,)
|
||||
|
||||
def test_subplots_ts_share_axes(self):
|
||||
# GH 3964
|
||||
_, axes = mpl.pyplot.subplots(3, 3, sharex=True, sharey=True)
|
||||
mpl.pyplot.subplots_adjust(left=0.05, right=0.95, hspace=0.3, wspace=0.3)
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((10, 9)),
|
||||
index=date_range(start="2014-07-01", freq="ME", periods=10),
|
||||
)
|
||||
for i, ax in enumerate(axes.ravel()):
|
||||
df[i].plot(ax=ax, fontsize=5)
|
||||
|
||||
# Rows other than bottom should not be visible
|
||||
for ax in axes[0:-1].ravel():
|
||||
_check_visible(ax.get_xticklabels(), visible=False)
|
||||
|
||||
# Bottom row should be visible
|
||||
for ax in axes[-1].ravel():
|
||||
_check_visible(ax.get_xticklabels(), visible=True)
|
||||
|
||||
# First column should be visible
|
||||
for ax in axes[[0, 1, 2], [0]].ravel():
|
||||
_check_visible(ax.get_yticklabels(), visible=True)
|
||||
|
||||
# Other columns should not be visible
|
||||
for ax in axes[[0, 1, 2], [1]].ravel():
|
||||
_check_visible(ax.get_yticklabels(), visible=False)
|
||||
for ax in axes[[0, 1, 2], [2]].ravel():
|
||||
_check_visible(ax.get_yticklabels(), visible=False)
|
||||
|
||||
def test_subplots_sharex_axes_existing_axes(self):
|
||||
# GH 9158
|
||||
d = {"A": [1.0, 2.0, 3.0, 4.0], "B": [4.0, 3.0, 2.0, 1.0], "C": [5, 1, 3, 4]}
|
||||
df = DataFrame(d, index=date_range("2014 10 11", "2014 10 14"))
|
||||
|
||||
axes = df[["A", "B"]].plot(subplots=True)
|
||||
df["C"].plot(ax=axes[0], secondary_y=True)
|
||||
|
||||
_check_visible(axes[0].get_xticklabels(), visible=False)
|
||||
_check_visible(axes[1].get_xticklabels(), visible=True)
|
||||
for ax in axes.ravel():
|
||||
_check_visible(ax.get_yticklabels(), visible=True)
|
||||
|
||||
def test_subplots_dup_columns(self):
|
||||
# GH 10962
|
||||
df = DataFrame(np.random.default_rng(2).random((5, 5)), columns=list("aaaaa"))
|
||||
axes = df.plot(subplots=True)
|
||||
for ax in axes:
|
||||
_check_legend_labels(ax, labels=["a"])
|
||||
assert len(ax.lines) == 1
|
||||
|
||||
def test_subplots_dup_columns_secondary_y(self):
|
||||
# GH 10962
|
||||
df = DataFrame(np.random.default_rng(2).random((5, 5)), columns=list("aaaaa"))
|
||||
axes = df.plot(subplots=True, secondary_y="a")
|
||||
for ax in axes:
|
||||
# (right) is only attached when subplots=False
|
||||
_check_legend_labels(ax, labels=["a"])
|
||||
assert len(ax.lines) == 1
|
||||
|
||||
def test_subplots_dup_columns_secondary_y_no_subplot(self):
|
||||
# GH 10962
|
||||
df = DataFrame(np.random.default_rng(2).random((5, 5)), columns=list("aaaaa"))
|
||||
ax = df.plot(secondary_y="a")
|
||||
_check_legend_labels(ax, labels=["a (right)"] * 5)
|
||||
assert len(ax.lines) == 0
|
||||
assert len(ax.right_ax.lines) == 5
|
||||
|
||||
@pytest.mark.xfail(
|
||||
np_version_gte1p24 and is_platform_linux(),
|
||||
reason="Weird rounding problems",
|
||||
strict=False,
|
||||
)
|
||||
def test_bar_log_no_subplots(self):
|
||||
# GH3254, GH3298 matplotlib/matplotlib#1882, #1892
|
||||
# regressions in 1.2.1
|
||||
expected = np.array([0.1, 1.0, 10.0, 100])
|
||||
|
||||
# no subplots
|
||||
df = DataFrame({"A": [3] * 5, "B": list(range(1, 6))}, index=range(5))
|
||||
ax = df.plot.bar(grid=True, log=True)
|
||||
tm.assert_numpy_array_equal(ax.yaxis.get_ticklocs(), expected)
|
||||
|
||||
@pytest.mark.xfail(
|
||||
np_version_gte1p24 and is_platform_linux(),
|
||||
reason="Weird rounding problems",
|
||||
strict=False,
|
||||
)
|
||||
def test_bar_log_subplots(self):
|
||||
expected = np.array([0.1, 1.0, 10.0, 100.0, 1000.0, 1e4])
|
||||
|
||||
ax = DataFrame([Series([200, 300]), Series([300, 500])]).plot.bar(
|
||||
log=True, subplots=True
|
||||
)
|
||||
|
||||
tm.assert_numpy_array_equal(ax[0].yaxis.get_ticklocs(), expected)
|
||||
tm.assert_numpy_array_equal(ax[1].yaxis.get_ticklocs(), expected)
|
||||
|
||||
def test_boxplot_subplots_return_type_default(self, hist_df):
|
||||
df = hist_df
|
||||
|
||||
# normal style: return_type=None
|
||||
result = df.plot.box(subplots=True)
|
||||
assert isinstance(result, Series)
|
||||
_check_box_return_type(
|
||||
result, None, expected_keys=["height", "weight", "category"]
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("rt", ["dict", "axes", "both"])
|
||||
def test_boxplot_subplots_return_type(self, hist_df, rt):
|
||||
df = hist_df
|
||||
returned = df.plot.box(return_type=rt, subplots=True)
|
||||
_check_box_return_type(
|
||||
returned,
|
||||
rt,
|
||||
expected_keys=["height", "weight", "category"],
|
||||
check_ax_title=False,
|
||||
)
|
||||
|
||||
def test_df_subplots_patterns_minorticks(self):
|
||||
# GH 10657
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((10, 2)),
|
||||
index=date_range("1/1/2000", periods=10),
|
||||
columns=list("AB"),
|
||||
)
|
||||
|
||||
# shared subplots
|
||||
_, axes = plt.subplots(2, 1, sharex=True)
|
||||
axes = df.plot(subplots=True, ax=axes)
|
||||
for ax in axes:
|
||||
assert len(ax.lines) == 1
|
||||
_check_visible(ax.get_yticklabels(), visible=True)
|
||||
# xaxis of 1st ax must be hidden
|
||||
_check_visible(axes[0].get_xticklabels(), visible=False)
|
||||
_check_visible(axes[0].get_xticklabels(minor=True), visible=False)
|
||||
_check_visible(axes[1].get_xticklabels(), visible=True)
|
||||
_check_visible(axes[1].get_xticklabels(minor=True), visible=True)
|
||||
|
||||
def test_df_subplots_patterns_minorticks_1st_ax_hidden(self):
|
||||
# GH 10657
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((10, 2)),
|
||||
index=date_range("1/1/2000", periods=10),
|
||||
columns=list("AB"),
|
||||
)
|
||||
_, axes = plt.subplots(2, 1)
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = df.plot(subplots=True, ax=axes, sharex=True)
|
||||
for ax in axes:
|
||||
assert len(ax.lines) == 1
|
||||
_check_visible(ax.get_yticklabels(), visible=True)
|
||||
# xaxis of 1st ax must be hidden
|
||||
_check_visible(axes[0].get_xticklabels(), visible=False)
|
||||
_check_visible(axes[0].get_xticklabels(minor=True), visible=False)
|
||||
_check_visible(axes[1].get_xticklabels(), visible=True)
|
||||
_check_visible(axes[1].get_xticklabels(minor=True), visible=True)
|
||||
|
||||
def test_df_subplots_patterns_minorticks_not_shared(self):
|
||||
# GH 10657
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((10, 2)),
|
||||
index=date_range("1/1/2000", periods=10),
|
||||
columns=list("AB"),
|
||||
)
|
||||
# not shared
|
||||
_, axes = plt.subplots(2, 1)
|
||||
axes = df.plot(subplots=True, ax=axes)
|
||||
for ax in axes:
|
||||
assert len(ax.lines) == 1
|
||||
_check_visible(ax.get_yticklabels(), visible=True)
|
||||
_check_visible(ax.get_xticklabels(), visible=True)
|
||||
_check_visible(ax.get_xticklabels(minor=True), visible=True)
|
||||
|
||||
def test_subplots_sharex_false(self):
|
||||
# test when sharex is set to False, two plots should have different
|
||||
# labels, GH 25160
|
||||
df = DataFrame(np.random.default_rng(2).random((10, 2)))
|
||||
df.iloc[5:, 1] = np.nan
|
||||
df.iloc[:5, 0] = np.nan
|
||||
|
||||
_, axs = mpl.pyplot.subplots(2, 1)
|
||||
df.plot.line(ax=axs, subplots=True, sharex=False)
|
||||
|
||||
expected_ax1 = np.arange(4.5, 10, 0.5)
|
||||
expected_ax2 = np.arange(-0.5, 5, 0.5)
|
||||
|
||||
tm.assert_numpy_array_equal(axs[0].get_xticks(), expected_ax1)
|
||||
tm.assert_numpy_array_equal(axs[1].get_xticks(), expected_ax2)
|
||||
|
||||
def test_subplots_constrained_layout(self):
|
||||
# GH 25261
|
||||
idx = date_range(start="now", periods=10)
|
||||
df = DataFrame(np.random.default_rng(2).random((10, 3)), index=idx)
|
||||
kwargs = {}
|
||||
if hasattr(mpl.pyplot.Figure, "get_constrained_layout"):
|
||||
kwargs["constrained_layout"] = True
|
||||
_, axes = mpl.pyplot.subplots(2, **kwargs)
|
||||
with tm.assert_produces_warning(None):
|
||||
df.plot(ax=axes[0])
|
||||
with tm.ensure_clean(return_filelike=True) as path:
|
||||
mpl.pyplot.savefig(path)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index_name, old_label, new_label",
|
||||
[
|
||||
(None, "", "new"),
|
||||
("old", "old", "new"),
|
||||
(None, "", ""),
|
||||
(None, "", 1),
|
||||
(None, "", [1, 2]),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("kind", ["line", "area", "bar"])
|
||||
def test_xlabel_ylabel_dataframe_subplots(
|
||||
self, kind, index_name, old_label, new_label
|
||||
):
|
||||
# GH 9093
|
||||
df = DataFrame([[1, 2], [2, 5]], columns=["Type A", "Type B"])
|
||||
df.index.name = index_name
|
||||
|
||||
# default is the ylabel is not shown and xlabel is index name
|
||||
axes = df.plot(kind=kind, subplots=True)
|
||||
assert all(ax.get_ylabel() == "" for ax in axes)
|
||||
assert all(ax.get_xlabel() == old_label for ax in axes)
|
||||
|
||||
# old xlabel will be overridden and assigned ylabel will be used as ylabel
|
||||
axes = df.plot(kind=kind, ylabel=new_label, xlabel=new_label, subplots=True)
|
||||
assert all(ax.get_ylabel() == str(new_label) for ax in axes)
|
||||
assert all(ax.get_xlabel() == str(new_label) for ax in axes)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kwargs",
|
||||
[
|
||||
# stacked center
|
||||
{"kind": "bar", "stacked": True},
|
||||
{"kind": "bar", "stacked": True, "width": 0.9},
|
||||
{"kind": "barh", "stacked": True},
|
||||
{"kind": "barh", "stacked": True, "width": 0.9},
|
||||
# center
|
||||
{"kind": "bar", "stacked": False},
|
||||
{"kind": "bar", "stacked": False, "width": 0.9},
|
||||
{"kind": "barh", "stacked": False},
|
||||
{"kind": "barh", "stacked": False, "width": 0.9},
|
||||
# subplots center
|
||||
{"kind": "bar", "subplots": True},
|
||||
{"kind": "bar", "subplots": True, "width": 0.9},
|
||||
{"kind": "barh", "subplots": True},
|
||||
{"kind": "barh", "subplots": True, "width": 0.9},
|
||||
# align edge
|
||||
{"kind": "bar", "stacked": True, "align": "edge"},
|
||||
{"kind": "bar", "stacked": True, "width": 0.9, "align": "edge"},
|
||||
{"kind": "barh", "stacked": True, "align": "edge"},
|
||||
{"kind": "barh", "stacked": True, "width": 0.9, "align": "edge"},
|
||||
{"kind": "bar", "stacked": False, "align": "edge"},
|
||||
{"kind": "bar", "stacked": False, "width": 0.9, "align": "edge"},
|
||||
{"kind": "barh", "stacked": False, "align": "edge"},
|
||||
{"kind": "barh", "stacked": False, "width": 0.9, "align": "edge"},
|
||||
{"kind": "bar", "subplots": True, "align": "edge"},
|
||||
{"kind": "bar", "subplots": True, "width": 0.9, "align": "edge"},
|
||||
{"kind": "barh", "subplots": True, "align": "edge"},
|
||||
{"kind": "barh", "subplots": True, "width": 0.9, "align": "edge"},
|
||||
],
|
||||
)
|
||||
def test_bar_align_multiple_columns(self, kwargs):
|
||||
# GH2157
|
||||
df = DataFrame({"A": [3] * 5, "B": list(range(5))}, index=range(5))
|
||||
self._check_bar_alignment(df, **kwargs)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kwargs",
|
||||
[
|
||||
{"kind": "bar", "stacked": False},
|
||||
{"kind": "bar", "stacked": True},
|
||||
{"kind": "barh", "stacked": False},
|
||||
{"kind": "barh", "stacked": True},
|
||||
{"kind": "bar", "subplots": True},
|
||||
{"kind": "barh", "subplots": True},
|
||||
],
|
||||
)
|
||||
def test_bar_align_single_column(self, kwargs):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal(5))
|
||||
self._check_bar_alignment(df, **kwargs)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kwargs",
|
||||
[
|
||||
{"kind": "bar", "stacked": False},
|
||||
{"kind": "bar", "stacked": True},
|
||||
{"kind": "barh", "stacked": False},
|
||||
{"kind": "barh", "stacked": True},
|
||||
{"kind": "bar", "subplots": True},
|
||||
{"kind": "barh", "subplots": True},
|
||||
],
|
||||
)
|
||||
def test_bar_barwidth_position(self, kwargs):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
self._check_bar_alignment(df, width=0.9, position=0.2, **kwargs)
|
||||
|
||||
@pytest.mark.parametrize("w", [1, 1.0])
|
||||
def test_bar_barwidth_position_int(self, w):
|
||||
# GH 12979
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
ax = df.plot.bar(stacked=True, width=w)
|
||||
ticks = ax.xaxis.get_ticklocs()
|
||||
tm.assert_numpy_array_equal(ticks, np.array([0, 1, 2, 3, 4]))
|
||||
assert ax.get_xlim() == (-0.75, 4.75)
|
||||
# check left-edge of bars
|
||||
assert ax.patches[0].get_x() == -0.5
|
||||
assert ax.patches[-1].get_x() == 3.5
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kind, kwargs",
|
||||
[
|
||||
["bar", {"stacked": True}],
|
||||
["barh", {"stacked": False}],
|
||||
["barh", {"stacked": True}],
|
||||
["bar", {"subplots": True}],
|
||||
["barh", {"subplots": True}],
|
||||
],
|
||||
)
|
||||
def test_bar_barwidth_position_int_width_1(self, kind, kwargs):
|
||||
# GH 12979
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
self._check_bar_alignment(df, kind=kind, width=1, **kwargs)
|
||||
|
||||
def _check_bar_alignment(
|
||||
self,
|
||||
df,
|
||||
kind="bar",
|
||||
stacked=False,
|
||||
subplots=False,
|
||||
align="center",
|
||||
width=0.5,
|
||||
position=0.5,
|
||||
):
|
||||
axes = df.plot(
|
||||
kind=kind,
|
||||
stacked=stacked,
|
||||
subplots=subplots,
|
||||
align=align,
|
||||
width=width,
|
||||
position=position,
|
||||
grid=True,
|
||||
)
|
||||
|
||||
axes = _flatten_visible(axes)
|
||||
|
||||
for ax in axes:
|
||||
if kind == "bar":
|
||||
axis = ax.xaxis
|
||||
ax_min, ax_max = ax.get_xlim()
|
||||
min_edge = min(p.get_x() for p in ax.patches)
|
||||
max_edge = max(p.get_x() + p.get_width() for p in ax.patches)
|
||||
elif kind == "barh":
|
||||
axis = ax.yaxis
|
||||
ax_min, ax_max = ax.get_ylim()
|
||||
min_edge = min(p.get_y() for p in ax.patches)
|
||||
max_edge = max(p.get_y() + p.get_height() for p in ax.patches)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
# GH 7498
|
||||
# compare margins between lim and bar edges
|
||||
tm.assert_almost_equal(ax_min, min_edge - 0.25)
|
||||
tm.assert_almost_equal(ax_max, max_edge + 0.25)
|
||||
|
||||
p = ax.patches[0]
|
||||
if kind == "bar" and (stacked is True or subplots is True):
|
||||
edge = p.get_x()
|
||||
center = edge + p.get_width() * position
|
||||
elif kind == "bar" and stacked is False:
|
||||
center = p.get_x() + p.get_width() * len(df.columns) * position
|
||||
edge = p.get_x()
|
||||
elif kind == "barh" and (stacked is True or subplots is True):
|
||||
center = p.get_y() + p.get_height() * position
|
||||
edge = p.get_y()
|
||||
elif kind == "barh" and stacked is False:
|
||||
center = p.get_y() + p.get_height() * len(df.columns) * position
|
||||
edge = p.get_y()
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
# Check the ticks locates on integer
|
||||
assert (axis.get_ticklocs() == np.arange(len(df))).all()
|
||||
|
||||
if align == "center":
|
||||
# Check whether the bar locates on center
|
||||
tm.assert_almost_equal(axis.get_ticklocs()[0], center)
|
||||
elif align == "edge":
|
||||
# Check whether the bar's edge starts from the tick
|
||||
tm.assert_almost_equal(axis.get_ticklocs()[0], edge)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
return axes
|
@ -0,0 +1,342 @@
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pandas import DataFrame
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
_check_axes_shape,
|
||||
_check_plot_works,
|
||||
get_x_axis,
|
||||
get_y_axis,
|
||||
)
|
||||
|
||||
pytest.importorskip("matplotlib")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hist_df():
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 2)), columns=["A", "B"]
|
||||
)
|
||||
df["C"] = np.random.default_rng(2).choice(["a", "b", "c"], 30)
|
||||
df["D"] = np.random.default_rng(2).choice(["a", "b", "c"], 30)
|
||||
return df
|
||||
|
||||
|
||||
class TestHistWithBy:
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"by, column, titles, legends",
|
||||
[
|
||||
("C", "A", ["a", "b", "c"], [["A"]] * 3),
|
||||
("C", ["A", "B"], ["a", "b", "c"], [["A", "B"]] * 3),
|
||||
("C", None, ["a", "b", "c"], [["A", "B"]] * 3),
|
||||
(
|
||||
["C", "D"],
|
||||
"A",
|
||||
[
|
||||
"(a, a)",
|
||||
"(b, b)",
|
||||
"(c, c)",
|
||||
],
|
||||
[["A"]] * 3,
|
||||
),
|
||||
(
|
||||
["C", "D"],
|
||||
["A", "B"],
|
||||
[
|
||||
"(a, a)",
|
||||
"(b, b)",
|
||||
"(c, c)",
|
||||
],
|
||||
[["A", "B"]] * 3,
|
||||
),
|
||||
(
|
||||
["C", "D"],
|
||||
None,
|
||||
[
|
||||
"(a, a)",
|
||||
"(b, b)",
|
||||
"(c, c)",
|
||||
],
|
||||
[["A", "B"]] * 3,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_hist_plot_by_argument(self, by, column, titles, legends, hist_df):
|
||||
# GH 15079
|
||||
axes = _check_plot_works(
|
||||
hist_df.plot.hist, column=column, by=by, default_axes=True
|
||||
)
|
||||
result_titles = [ax.get_title() for ax in axes]
|
||||
result_legends = [
|
||||
[legend.get_text() for legend in ax.get_legend().texts] for ax in axes
|
||||
]
|
||||
|
||||
assert result_legends == legends
|
||||
assert result_titles == titles
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"by, column, titles, legends",
|
||||
[
|
||||
(0, "A", ["a", "b", "c"], [["A"]] * 3),
|
||||
(0, None, ["a", "b", "c"], [["A", "B"]] * 3),
|
||||
(
|
||||
[0, "D"],
|
||||
"A",
|
||||
[
|
||||
"(a, a)",
|
||||
"(b, b)",
|
||||
"(c, c)",
|
||||
],
|
||||
[["A"]] * 3,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_hist_plot_by_0(self, by, column, titles, legends, hist_df):
|
||||
# GH 15079
|
||||
df = hist_df.copy()
|
||||
df = df.rename(columns={"C": 0})
|
||||
|
||||
axes = _check_plot_works(df.plot.hist, default_axes=True, column=column, by=by)
|
||||
result_titles = [ax.get_title() for ax in axes]
|
||||
result_legends = [
|
||||
[legend.get_text() for legend in ax.get_legend().texts] for ax in axes
|
||||
]
|
||||
|
||||
assert result_legends == legends
|
||||
assert result_titles == titles
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"by, column",
|
||||
[
|
||||
([], ["A"]),
|
||||
([], ["A", "B"]),
|
||||
((), None),
|
||||
((), ["A", "B"]),
|
||||
],
|
||||
)
|
||||
def test_hist_plot_empty_list_string_tuple_by(self, by, column, hist_df):
|
||||
# GH 15079
|
||||
msg = "No group keys passed"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
_check_plot_works(
|
||||
hist_df.plot.hist, default_axes=True, column=column, by=by
|
||||
)
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"by, column, layout, axes_num",
|
||||
[
|
||||
(["C"], "A", (2, 2), 3),
|
||||
("C", "A", (2, 2), 3),
|
||||
(["C"], ["A"], (1, 3), 3),
|
||||
("C", None, (3, 1), 3),
|
||||
("C", ["A", "B"], (3, 1), 3),
|
||||
(["C", "D"], "A", (9, 1), 3),
|
||||
(["C", "D"], "A", (3, 3), 3),
|
||||
(["C", "D"], ["A"], (5, 2), 3),
|
||||
(["C", "D"], ["A", "B"], (9, 1), 3),
|
||||
(["C", "D"], None, (9, 1), 3),
|
||||
(["C", "D"], ["A", "B"], (5, 2), 3),
|
||||
],
|
||||
)
|
||||
def test_hist_plot_layout_with_by(self, by, column, layout, axes_num, hist_df):
|
||||
# GH 15079
|
||||
# _check_plot_works adds an ax so catch warning. see GH #13188
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
axes = _check_plot_works(
|
||||
hist_df.plot.hist, column=column, by=by, layout=layout
|
||||
)
|
||||
_check_axes_shape(axes, axes_num=axes_num, layout=layout)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"msg, by, layout",
|
||||
[
|
||||
("larger than required size", ["C", "D"], (1, 1)),
|
||||
(re.escape("Layout must be a tuple of (rows, columns)"), "C", (1,)),
|
||||
("At least one dimension of layout must be positive", "C", (-1, -1)),
|
||||
],
|
||||
)
|
||||
def test_hist_plot_invalid_layout_with_by_raises(self, msg, by, layout, hist_df):
|
||||
# GH 15079, test if error is raised when invalid layout is given
|
||||
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
hist_df.plot.hist(column=["A", "B"], by=by, layout=layout)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_axis_share_x_with_by(self, hist_df):
|
||||
# GH 15079
|
||||
ax1, ax2, ax3 = hist_df.plot.hist(column="A", by="C", sharex=True)
|
||||
|
||||
# share x
|
||||
assert get_x_axis(ax1).joined(ax1, ax2)
|
||||
assert get_x_axis(ax2).joined(ax1, ax2)
|
||||
assert get_x_axis(ax3).joined(ax1, ax3)
|
||||
assert get_x_axis(ax3).joined(ax2, ax3)
|
||||
|
||||
# don't share y
|
||||
assert not get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert not get_y_axis(ax2).joined(ax1, ax2)
|
||||
assert not get_y_axis(ax3).joined(ax1, ax3)
|
||||
assert not get_y_axis(ax3).joined(ax2, ax3)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_axis_share_y_with_by(self, hist_df):
|
||||
# GH 15079
|
||||
ax1, ax2, ax3 = hist_df.plot.hist(column="A", by="C", sharey=True)
|
||||
|
||||
# share y
|
||||
assert get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert get_y_axis(ax2).joined(ax1, ax2)
|
||||
assert get_y_axis(ax3).joined(ax1, ax3)
|
||||
assert get_y_axis(ax3).joined(ax2, ax3)
|
||||
|
||||
# don't share x
|
||||
assert not get_x_axis(ax1).joined(ax1, ax2)
|
||||
assert not get_x_axis(ax2).joined(ax1, ax2)
|
||||
assert not get_x_axis(ax3).joined(ax1, ax3)
|
||||
assert not get_x_axis(ax3).joined(ax2, ax3)
|
||||
|
||||
@pytest.mark.parametrize("figsize", [(12, 8), (20, 10)])
|
||||
def test_figure_shape_hist_with_by(self, figsize, hist_df):
|
||||
# GH 15079
|
||||
axes = hist_df.plot.hist(column="A", by="C", figsize=figsize)
|
||||
_check_axes_shape(axes, axes_num=3, figsize=figsize)
|
||||
|
||||
|
||||
class TestBoxWithBy:
|
||||
@pytest.mark.parametrize(
|
||||
"by, column, titles, xticklabels",
|
||||
[
|
||||
("C", "A", ["A"], [["a", "b", "c"]]),
|
||||
(
|
||||
["C", "D"],
|
||||
"A",
|
||||
["A"],
|
||||
[
|
||||
[
|
||||
"(a, a)",
|
||||
"(b, b)",
|
||||
"(c, c)",
|
||||
]
|
||||
],
|
||||
),
|
||||
("C", ["A", "B"], ["A", "B"], [["a", "b", "c"]] * 2),
|
||||
(
|
||||
["C", "D"],
|
||||
["A", "B"],
|
||||
["A", "B"],
|
||||
[
|
||||
[
|
||||
"(a, a)",
|
||||
"(b, b)",
|
||||
"(c, c)",
|
||||
]
|
||||
]
|
||||
* 2,
|
||||
),
|
||||
(["C"], None, ["A", "B"], [["a", "b", "c"]] * 2),
|
||||
],
|
||||
)
|
||||
def test_box_plot_by_argument(self, by, column, titles, xticklabels, hist_df):
|
||||
# GH 15079
|
||||
axes = _check_plot_works(
|
||||
hist_df.plot.box, default_axes=True, column=column, by=by
|
||||
)
|
||||
result_titles = [ax.get_title() for ax in axes]
|
||||
result_xticklabels = [
|
||||
[label.get_text() for label in ax.get_xticklabels()] for ax in axes
|
||||
]
|
||||
|
||||
assert result_xticklabels == xticklabels
|
||||
assert result_titles == titles
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"by, column, titles, xticklabels",
|
||||
[
|
||||
(0, "A", ["A"], [["a", "b", "c"]]),
|
||||
(
|
||||
[0, "D"],
|
||||
"A",
|
||||
["A"],
|
||||
[
|
||||
[
|
||||
"(a, a)",
|
||||
"(b, b)",
|
||||
"(c, c)",
|
||||
]
|
||||
],
|
||||
),
|
||||
(0, None, ["A", "B"], [["a", "b", "c"]] * 2),
|
||||
],
|
||||
)
|
||||
def test_box_plot_by_0(self, by, column, titles, xticklabels, hist_df):
|
||||
# GH 15079
|
||||
df = hist_df.copy()
|
||||
df = df.rename(columns={"C": 0})
|
||||
|
||||
axes = _check_plot_works(df.plot.box, default_axes=True, column=column, by=by)
|
||||
result_titles = [ax.get_title() for ax in axes]
|
||||
result_xticklabels = [
|
||||
[label.get_text() for label in ax.get_xticklabels()] for ax in axes
|
||||
]
|
||||
|
||||
assert result_xticklabels == xticklabels
|
||||
assert result_titles == titles
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"by, column",
|
||||
[
|
||||
([], ["A"]),
|
||||
((), "A"),
|
||||
([], None),
|
||||
((), ["A", "B"]),
|
||||
],
|
||||
)
|
||||
def test_box_plot_with_none_empty_list_by(self, by, column, hist_df):
|
||||
# GH 15079
|
||||
msg = "No group keys passed"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
_check_plot_works(hist_df.plot.box, default_axes=True, column=column, by=by)
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"by, column, layout, axes_num",
|
||||
[
|
||||
(["C"], "A", (1, 1), 1),
|
||||
("C", "A", (1, 1), 1),
|
||||
("C", None, (2, 1), 2),
|
||||
("C", ["A", "B"], (1, 2), 2),
|
||||
(["C", "D"], "A", (1, 1), 1),
|
||||
(["C", "D"], None, (1, 2), 2),
|
||||
],
|
||||
)
|
||||
def test_box_plot_layout_with_by(self, by, column, layout, axes_num, hist_df):
|
||||
# GH 15079
|
||||
axes = _check_plot_works(
|
||||
hist_df.plot.box, default_axes=True, column=column, by=by, layout=layout
|
||||
)
|
||||
_check_axes_shape(axes, axes_num=axes_num, layout=layout)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"msg, by, layout",
|
||||
[
|
||||
("larger than required size", ["C", "D"], (1, 1)),
|
||||
(re.escape("Layout must be a tuple of (rows, columns)"), "C", (1,)),
|
||||
("At least one dimension of layout must be positive", "C", (-1, -1)),
|
||||
],
|
||||
)
|
||||
def test_box_plot_invalid_layout_with_by_raises(self, msg, by, layout, hist_df):
|
||||
# GH 15079, test if error is raised when invalid layout is given
|
||||
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
hist_df.plot.box(column=["A", "B"], by=by, layout=layout)
|
||||
|
||||
@pytest.mark.parametrize("figsize", [(12, 8), (20, 10)])
|
||||
def test_figure_shape_hist_with_by(self, figsize, hist_df):
|
||||
# GH 15079
|
||||
axes = hist_df.plot.box(column="A", by="C", figsize=figsize)
|
||||
_check_axes_shape(axes, axes_num=1, figsize=figsize)
|
@ -0,0 +1,98 @@
|
||||
import sys
|
||||
import types
|
||||
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
import pandas
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dummy_backend():
|
||||
db = types.ModuleType("pandas_dummy_backend")
|
||||
setattr(db, "plot", lambda *args, **kwargs: "used_dummy")
|
||||
return db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def restore_backend():
|
||||
"""Restore the plotting backend to matplotlib"""
|
||||
with pandas.option_context("plotting.backend", "matplotlib"):
|
||||
yield
|
||||
|
||||
|
||||
def test_backend_is_not_module():
|
||||
msg = "Could not find plotting backend 'not_an_existing_module'."
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
pandas.set_option("plotting.backend", "not_an_existing_module")
|
||||
|
||||
assert pandas.options.plotting.backend == "matplotlib"
|
||||
|
||||
|
||||
def test_backend_is_correct(monkeypatch, restore_backend, dummy_backend):
|
||||
monkeypatch.setitem(sys.modules, "pandas_dummy_backend", dummy_backend)
|
||||
|
||||
pandas.set_option("plotting.backend", "pandas_dummy_backend")
|
||||
assert pandas.get_option("plotting.backend") == "pandas_dummy_backend"
|
||||
assert (
|
||||
pandas.plotting._core._get_plot_backend("pandas_dummy_backend") is dummy_backend
|
||||
)
|
||||
|
||||
|
||||
def test_backend_can_be_set_in_plot_call(monkeypatch, restore_backend, dummy_backend):
|
||||
monkeypatch.setitem(sys.modules, "pandas_dummy_backend", dummy_backend)
|
||||
df = pandas.DataFrame([1, 2, 3])
|
||||
|
||||
assert pandas.get_option("plotting.backend") == "matplotlib"
|
||||
assert df.plot(backend="pandas_dummy_backend") == "used_dummy"
|
||||
|
||||
|
||||
def test_register_entrypoint(restore_backend, tmp_path, monkeypatch, dummy_backend):
|
||||
monkeypatch.syspath_prepend(tmp_path)
|
||||
monkeypatch.setitem(sys.modules, "pandas_dummy_backend", dummy_backend)
|
||||
|
||||
dist_info = tmp_path / "my_backend-0.0.0.dist-info"
|
||||
dist_info.mkdir()
|
||||
# entry_point name should not match module name - otherwise pandas will
|
||||
# fall back to backend lookup by module name
|
||||
(dist_info / "entry_points.txt").write_bytes(
|
||||
b"[pandas_plotting_backends]\nmy_ep_backend = pandas_dummy_backend\n"
|
||||
)
|
||||
|
||||
assert pandas.plotting._core._get_plot_backend("my_ep_backend") is dummy_backend
|
||||
|
||||
with pandas.option_context("plotting.backend", "my_ep_backend"):
|
||||
assert pandas.plotting._core._get_plot_backend() is dummy_backend
|
||||
|
||||
|
||||
def test_setting_backend_without_plot_raises(monkeypatch):
|
||||
# GH-28163
|
||||
module = types.ModuleType("pandas_plot_backend")
|
||||
monkeypatch.setitem(sys.modules, "pandas_plot_backend", module)
|
||||
|
||||
assert pandas.options.plotting.backend == "matplotlib"
|
||||
with pytest.raises(
|
||||
ValueError, match="Could not find plotting backend 'pandas_plot_backend'."
|
||||
):
|
||||
pandas.set_option("plotting.backend", "pandas_plot_backend")
|
||||
|
||||
assert pandas.options.plotting.backend == "matplotlib"
|
||||
|
||||
|
||||
@td.skip_if_installed("matplotlib")
|
||||
def test_no_matplotlib_ok():
|
||||
msg = (
|
||||
'matplotlib is required for plotting when the default backend "matplotlib" is '
|
||||
"selected."
|
||||
)
|
||||
with pytest.raises(ImportError, match=msg):
|
||||
pandas.plotting._core._get_plot_backend("matplotlib")
|
||||
|
||||
|
||||
def test_extra_kinds_ok(monkeypatch, restore_backend, dummy_backend):
|
||||
# https://github.com/pandas-dev/pandas/pull/28647
|
||||
monkeypatch.setitem(sys.modules, "pandas_dummy_backend", dummy_backend)
|
||||
pandas.set_option("plotting.backend", "pandas_dummy_backend")
|
||||
df = pandas.DataFrame({"A": [1, 2, 3]})
|
||||
df.plot(kind="not a real kind")
|
@ -0,0 +1,761 @@
|
||||
""" Test cases for .boxplot method """
|
||||
|
||||
import itertools
|
||||
import string
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
MultiIndex,
|
||||
Series,
|
||||
date_range,
|
||||
plotting,
|
||||
timedelta_range,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
_check_axes_shape,
|
||||
_check_box_return_type,
|
||||
_check_plot_works,
|
||||
_check_ticks_props,
|
||||
_check_visible,
|
||||
)
|
||||
|
||||
from pandas.io.formats.printing import pprint_thing
|
||||
|
||||
mpl = pytest.importorskip("matplotlib")
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
|
||||
|
||||
def _check_ax_limits(col, ax):
|
||||
y_min, y_max = ax.get_ylim()
|
||||
assert y_min <= col.min()
|
||||
assert y_max >= col.max()
|
||||
|
||||
|
||||
class TestDataFramePlots:
|
||||
def test_stacked_boxplot_set_axis(self):
|
||||
# GH2980
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
n = 80
|
||||
df = DataFrame(
|
||||
{
|
||||
"Clinical": np.random.default_rng(2).choice([0, 1, 2, 3], n),
|
||||
"Confirmed": np.random.default_rng(2).choice([0, 1, 2, 3], n),
|
||||
"Discarded": np.random.default_rng(2).choice([0, 1, 2, 3], n),
|
||||
},
|
||||
index=np.arange(0, n),
|
||||
)
|
||||
ax = df.plot(kind="bar", stacked=True)
|
||||
assert [int(x.get_text()) for x in ax.get_xticklabels()] == df.index.to_list()
|
||||
ax.set_xticks(np.arange(0, 80, 10))
|
||||
plt.draw() # Update changes
|
||||
assert [int(x.get_text()) for x in ax.get_xticklabels()] == list(
|
||||
np.arange(0, 80, 10)
|
||||
)
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"kwargs, warn",
|
||||
[
|
||||
[{"return_type": "dict"}, None],
|
||||
[{"column": ["one", "two"]}, None],
|
||||
[{"column": ["one", "two"], "by": "indic"}, UserWarning],
|
||||
[{"column": ["one"], "by": ["indic", "indic2"]}, None],
|
||||
[{"by": "indic"}, UserWarning],
|
||||
[{"by": ["indic", "indic2"]}, UserWarning],
|
||||
[{"notch": 1}, None],
|
||||
[{"by": "indic", "notch": 1}, UserWarning],
|
||||
],
|
||||
)
|
||||
def test_boxplot_legacy1(self, kwargs, warn):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((6, 4)),
|
||||
index=list(string.ascii_letters[:6]),
|
||||
columns=["one", "two", "three", "four"],
|
||||
)
|
||||
df["indic"] = ["foo", "bar"] * 3
|
||||
df["indic2"] = ["foo", "bar", "foo"] * 2
|
||||
|
||||
# _check_plot_works can add an ax so catch warning. see GH #13188
|
||||
with tm.assert_produces_warning(warn, check_stacklevel=False):
|
||||
_check_plot_works(df.boxplot, **kwargs)
|
||||
|
||||
def test_boxplot_legacy1_series(self):
|
||||
ser = Series(np.random.default_rng(2).standard_normal(6))
|
||||
_check_plot_works(plotting._core.boxplot, data=ser, return_type="dict")
|
||||
|
||||
def test_boxplot_legacy2(self):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 2)), columns=["Col1", "Col2"]
|
||||
)
|
||||
df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"])
|
||||
df["Y"] = Series(["A"] * 10)
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
_check_plot_works(df.boxplot, by="X")
|
||||
|
||||
def test_boxplot_legacy2_with_ax(self):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 2)), columns=["Col1", "Col2"]
|
||||
)
|
||||
df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"])
|
||||
df["Y"] = Series(["A"] * 10)
|
||||
# When ax is supplied and required number of axes is 1,
|
||||
# passed ax should be used:
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
axes = df.boxplot("Col1", by="X", ax=ax)
|
||||
ax_axes = ax.axes
|
||||
assert ax_axes is axes
|
||||
|
||||
def test_boxplot_legacy2_with_ax_return_type(self):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 2)), columns=["Col1", "Col2"]
|
||||
)
|
||||
df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"])
|
||||
df["Y"] = Series(["A"] * 10)
|
||||
fig, ax = mpl.pyplot.subplots()
|
||||
axes = df.groupby("Y").boxplot(ax=ax, return_type="axes")
|
||||
ax_axes = ax.axes
|
||||
assert ax_axes is axes["A"]
|
||||
|
||||
def test_boxplot_legacy2_with_multi_col(self):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 2)), columns=["Col1", "Col2"]
|
||||
)
|
||||
df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"])
|
||||
df["Y"] = Series(["A"] * 10)
|
||||
# Multiple columns with an ax argument should use same figure
|
||||
fig, ax = mpl.pyplot.subplots()
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = df.boxplot(
|
||||
column=["Col1", "Col2"], by="X", ax=ax, return_type="axes"
|
||||
)
|
||||
assert axes["Col1"].get_figure() is fig
|
||||
|
||||
def test_boxplot_legacy2_by_none(self):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 2)), columns=["Col1", "Col2"]
|
||||
)
|
||||
df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"])
|
||||
df["Y"] = Series(["A"] * 10)
|
||||
# When by is None, check that all relevant lines are present in the
|
||||
# dict
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
d = df.boxplot(ax=ax, return_type="dict")
|
||||
lines = list(itertools.chain.from_iterable(d.values()))
|
||||
assert len(ax.get_lines()) == len(lines)
|
||||
|
||||
def test_boxplot_return_type_none(self, hist_df):
|
||||
# GH 12216; return_type=None & by=None -> axes
|
||||
result = hist_df.boxplot()
|
||||
assert isinstance(result, mpl.pyplot.Axes)
|
||||
|
||||
def test_boxplot_return_type_legacy(self):
|
||||
# API change in https://github.com/pandas-dev/pandas/pull/7096
|
||||
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((6, 4)),
|
||||
index=list(string.ascii_letters[:6]),
|
||||
columns=["one", "two", "three", "four"],
|
||||
)
|
||||
msg = "return_type must be {'axes', 'dict', 'both'}"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.boxplot(return_type="NOT_A_TYPE")
|
||||
|
||||
result = df.boxplot()
|
||||
_check_box_return_type(result, "axes")
|
||||
|
||||
@pytest.mark.parametrize("return_type", ["dict", "axes", "both"])
|
||||
def test_boxplot_return_type_legacy_return_type(self, return_type):
|
||||
# API change in https://github.com/pandas-dev/pandas/pull/7096
|
||||
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((6, 4)),
|
||||
index=list(string.ascii_letters[:6]),
|
||||
columns=["one", "two", "three", "four"],
|
||||
)
|
||||
with tm.assert_produces_warning(False):
|
||||
result = df.boxplot(return_type=return_type)
|
||||
_check_box_return_type(result, return_type)
|
||||
|
||||
def test_boxplot_axis_limits(self, hist_df):
|
||||
df = hist_df.copy()
|
||||
df["age"] = np.random.default_rng(2).integers(1, 20, df.shape[0])
|
||||
# One full row
|
||||
height_ax, weight_ax = df.boxplot(["height", "weight"], by="category")
|
||||
_check_ax_limits(df["height"], height_ax)
|
||||
_check_ax_limits(df["weight"], weight_ax)
|
||||
assert weight_ax._sharey == height_ax
|
||||
|
||||
def test_boxplot_axis_limits_two_rows(self, hist_df):
|
||||
df = hist_df.copy()
|
||||
df["age"] = np.random.default_rng(2).integers(1, 20, df.shape[0])
|
||||
# Two rows, one partial
|
||||
p = df.boxplot(["height", "weight", "age"], by="category")
|
||||
height_ax, weight_ax, age_ax = p[0, 0], p[0, 1], p[1, 0]
|
||||
dummy_ax = p[1, 1]
|
||||
|
||||
_check_ax_limits(df["height"], height_ax)
|
||||
_check_ax_limits(df["weight"], weight_ax)
|
||||
_check_ax_limits(df["age"], age_ax)
|
||||
assert weight_ax._sharey == height_ax
|
||||
assert age_ax._sharey == height_ax
|
||||
assert dummy_ax._sharey is None
|
||||
|
||||
def test_boxplot_empty_column(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((20, 4)))
|
||||
df.loc[:, 0] = np.nan
|
||||
_check_plot_works(df.boxplot, return_type="axes")
|
||||
|
||||
def test_figsize(self):
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 5)), columns=["A", "B", "C", "D", "E"]
|
||||
)
|
||||
result = df.boxplot(return_type="axes", figsize=(12, 8))
|
||||
assert result.figure.bbox_inches.width == 12
|
||||
assert result.figure.bbox_inches.height == 8
|
||||
|
||||
def test_fontsize(self):
|
||||
df = DataFrame({"a": [1, 2, 3, 4, 5, 6]})
|
||||
_check_ticks_props(df.boxplot("a", fontsize=16), xlabelsize=16, ylabelsize=16)
|
||||
|
||||
def test_boxplot_numeric_data(self):
|
||||
# GH 22799
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": date_range("2012-01-01", periods=100),
|
||||
"b": np.random.default_rng(2).standard_normal(100),
|
||||
"c": np.random.default_rng(2).standard_normal(100) + 2,
|
||||
"d": date_range("2012-01-01", periods=100).astype(str),
|
||||
"e": date_range("2012-01-01", periods=100, tz="UTC"),
|
||||
"f": timedelta_range("1 days", periods=100),
|
||||
}
|
||||
)
|
||||
ax = df.plot(kind="box")
|
||||
assert [x.get_text() for x in ax.get_xticklabels()] == ["b", "c"]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"colors_kwd, expected",
|
||||
[
|
||||
(
|
||||
{"boxes": "r", "whiskers": "b", "medians": "g", "caps": "c"},
|
||||
{"boxes": "r", "whiskers": "b", "medians": "g", "caps": "c"},
|
||||
),
|
||||
({"boxes": "r"}, {"boxes": "r"}),
|
||||
("r", {"boxes": "r", "whiskers": "r", "medians": "r", "caps": "r"}),
|
||||
],
|
||||
)
|
||||
def test_color_kwd(self, colors_kwd, expected):
|
||||
# GH: 26214
|
||||
df = DataFrame(np.random.default_rng(2).random((10, 2)))
|
||||
result = df.boxplot(color=colors_kwd, return_type="dict")
|
||||
for k, v in expected.items():
|
||||
assert result[k][0].get_color() == v
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scheme,expected",
|
||||
[
|
||||
(
|
||||
"dark_background",
|
||||
{
|
||||
"boxes": "#8dd3c7",
|
||||
"whiskers": "#8dd3c7",
|
||||
"medians": "#bfbbd9",
|
||||
"caps": "#8dd3c7",
|
||||
},
|
||||
),
|
||||
(
|
||||
"default",
|
||||
{
|
||||
"boxes": "#1f77b4",
|
||||
"whiskers": "#1f77b4",
|
||||
"medians": "#2ca02c",
|
||||
"caps": "#1f77b4",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_colors_in_theme(self, scheme, expected):
|
||||
# GH: 40769
|
||||
df = DataFrame(np.random.default_rng(2).random((10, 2)))
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
plt.style.use(scheme)
|
||||
result = df.plot.box(return_type="dict")
|
||||
for k, v in expected.items():
|
||||
assert result[k][0].get_color() == v
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_colors, msg",
|
||||
[({"boxes": "r", "invalid_key": "r"}, "invalid key 'invalid_key'")],
|
||||
)
|
||||
def test_color_kwd_errors(self, dict_colors, msg):
|
||||
# GH: 26214
|
||||
df = DataFrame(np.random.default_rng(2).random((10, 2)))
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.boxplot(color=dict_colors, return_type="dict")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"props, expected",
|
||||
[
|
||||
("boxprops", "boxes"),
|
||||
("whiskerprops", "whiskers"),
|
||||
("capprops", "caps"),
|
||||
("medianprops", "medians"),
|
||||
],
|
||||
)
|
||||
def test_specified_props_kwd(self, props, expected):
|
||||
# GH 30346
|
||||
df = DataFrame({k: np.random.default_rng(2).random(10) for k in "ABC"})
|
||||
kwd = {props: {"color": "C1"}}
|
||||
result = df.boxplot(return_type="dict", **kwd)
|
||||
|
||||
assert result[expected][0].get_color() == "C1"
|
||||
|
||||
@pytest.mark.parametrize("vert", [True, False])
|
||||
def test_plot_xlabel_ylabel(self, vert):
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": np.random.default_rng(2).standard_normal(10),
|
||||
"b": np.random.default_rng(2).standard_normal(10),
|
||||
"group": np.random.default_rng(2).choice(["group1", "group2"], 10),
|
||||
}
|
||||
)
|
||||
xlabel, ylabel = "x", "y"
|
||||
ax = df.plot(kind="box", vert=vert, xlabel=xlabel, ylabel=ylabel)
|
||||
assert ax.get_xlabel() == xlabel
|
||||
assert ax.get_ylabel() == ylabel
|
||||
|
||||
@pytest.mark.parametrize("vert", [True, False])
|
||||
def test_plot_box(self, vert):
|
||||
# GH 54941
|
||||
rng = np.random.default_rng(2)
|
||||
df1 = DataFrame(rng.integers(0, 100, size=(100, 4)), columns=list("ABCD"))
|
||||
df2 = DataFrame(rng.integers(0, 100, size=(100, 4)), columns=list("ABCD"))
|
||||
|
||||
xlabel, ylabel = "x", "y"
|
||||
_, axs = plt.subplots(ncols=2, figsize=(10, 7), sharey=True)
|
||||
df1.plot.box(ax=axs[0], vert=vert, xlabel=xlabel, ylabel=ylabel)
|
||||
df2.plot.box(ax=axs[1], vert=vert, xlabel=xlabel, ylabel=ylabel)
|
||||
for ax in axs:
|
||||
assert ax.get_xlabel() == xlabel
|
||||
assert ax.get_ylabel() == ylabel
|
||||
mpl.pyplot.close()
|
||||
|
||||
@pytest.mark.parametrize("vert", [True, False])
|
||||
def test_boxplot_xlabel_ylabel(self, vert):
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": np.random.default_rng(2).standard_normal(10),
|
||||
"b": np.random.default_rng(2).standard_normal(10),
|
||||
"group": np.random.default_rng(2).choice(["group1", "group2"], 10),
|
||||
}
|
||||
)
|
||||
xlabel, ylabel = "x", "y"
|
||||
ax = df.boxplot(vert=vert, xlabel=xlabel, ylabel=ylabel)
|
||||
assert ax.get_xlabel() == xlabel
|
||||
assert ax.get_ylabel() == ylabel
|
||||
|
||||
@pytest.mark.parametrize("vert", [True, False])
|
||||
def test_boxplot_group_xlabel_ylabel(self, vert):
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": np.random.default_rng(2).standard_normal(10),
|
||||
"b": np.random.default_rng(2).standard_normal(10),
|
||||
"group": np.random.default_rng(2).choice(["group1", "group2"], 10),
|
||||
}
|
||||
)
|
||||
xlabel, ylabel = "x", "y"
|
||||
ax = df.boxplot(by="group", vert=vert, xlabel=xlabel, ylabel=ylabel)
|
||||
for subplot in ax:
|
||||
assert subplot.get_xlabel() == xlabel
|
||||
assert subplot.get_ylabel() == ylabel
|
||||
mpl.pyplot.close()
|
||||
|
||||
@pytest.mark.parametrize("vert", [True, False])
|
||||
def test_boxplot_group_no_xlabel_ylabel(self, vert):
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": np.random.default_rng(2).standard_normal(10),
|
||||
"b": np.random.default_rng(2).standard_normal(10),
|
||||
"group": np.random.default_rng(2).choice(["group1", "group2"], 10),
|
||||
}
|
||||
)
|
||||
ax = df.boxplot(by="group", vert=vert)
|
||||
for subplot in ax:
|
||||
target_label = subplot.get_xlabel() if vert else subplot.get_ylabel()
|
||||
assert target_label == pprint_thing(["group"])
|
||||
mpl.pyplot.close()
|
||||
|
||||
|
||||
class TestDataFrameGroupByPlots:
|
||||
def test_boxplot_legacy1(self, hist_df):
|
||||
grouped = hist_df.groupby(by="gender")
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
axes = _check_plot_works(grouped.boxplot, return_type="axes")
|
||||
_check_axes_shape(list(axes.values), axes_num=2, layout=(1, 2))
|
||||
|
||||
def test_boxplot_legacy1_return_type(self, hist_df):
|
||||
grouped = hist_df.groupby(by="gender")
|
||||
axes = _check_plot_works(grouped.boxplot, subplots=False, return_type="axes")
|
||||
_check_axes_shape(axes, axes_num=1, layout=(1, 1))
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_boxplot_legacy2(self):
|
||||
tuples = zip(string.ascii_letters[:10], range(10))
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 3)),
|
||||
index=MultiIndex.from_tuples(tuples),
|
||||
)
|
||||
grouped = df.groupby(level=1)
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
axes = _check_plot_works(grouped.boxplot, return_type="axes")
|
||||
_check_axes_shape(list(axes.values), axes_num=10, layout=(4, 3))
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_boxplot_legacy2_return_type(self):
|
||||
tuples = zip(string.ascii_letters[:10], range(10))
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 3)),
|
||||
index=MultiIndex.from_tuples(tuples),
|
||||
)
|
||||
grouped = df.groupby(level=1)
|
||||
axes = _check_plot_works(grouped.boxplot, subplots=False, return_type="axes")
|
||||
_check_axes_shape(axes, axes_num=1, layout=(1, 1))
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"subplots, warn, axes_num, layout",
|
||||
[[True, UserWarning, 3, (2, 2)], [False, None, 1, (1, 1)]],
|
||||
)
|
||||
def test_boxplot_legacy3(self, subplots, warn, axes_num, layout):
|
||||
tuples = zip(string.ascii_letters[:10], range(10))
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).random((10, 3)),
|
||||
index=MultiIndex.from_tuples(tuples),
|
||||
)
|
||||
msg = "DataFrame.groupby with axis=1 is deprecated"
|
||||
with tm.assert_produces_warning(FutureWarning, match=msg):
|
||||
grouped = df.unstack(level=1).groupby(level=0, axis=1)
|
||||
with tm.assert_produces_warning(warn, check_stacklevel=False):
|
||||
axes = _check_plot_works(
|
||||
grouped.boxplot, subplots=subplots, return_type="axes"
|
||||
)
|
||||
_check_axes_shape(axes, axes_num=axes_num, layout=layout)
|
||||
|
||||
def test_grouped_plot_fignums(self):
|
||||
n = 10
|
||||
weight = Series(np.random.default_rng(2).normal(166, 20, size=n))
|
||||
height = Series(np.random.default_rng(2).normal(60, 10, size=n))
|
||||
gender = np.random.default_rng(2).choice(["male", "female"], size=n)
|
||||
df = DataFrame({"height": height, "weight": weight, "gender": gender})
|
||||
gb = df.groupby("gender")
|
||||
|
||||
res = gb.plot()
|
||||
assert len(mpl.pyplot.get_fignums()) == 2
|
||||
assert len(res) == 2
|
||||
plt.close("all")
|
||||
|
||||
res = gb.boxplot(return_type="axes")
|
||||
assert len(mpl.pyplot.get_fignums()) == 1
|
||||
assert len(res) == 2
|
||||
|
||||
def test_grouped_plot_fignums_excluded_col(self):
|
||||
n = 10
|
||||
weight = Series(np.random.default_rng(2).normal(166, 20, size=n))
|
||||
height = Series(np.random.default_rng(2).normal(60, 10, size=n))
|
||||
gender = np.random.default_rng(2).choice(["male", "female"], size=n)
|
||||
df = DataFrame({"height": height, "weight": weight, "gender": gender})
|
||||
# now works with GH 5610 as gender is excluded
|
||||
df.groupby("gender").hist()
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_grouped_box_return_type(self, hist_df):
|
||||
df = hist_df
|
||||
|
||||
# old style: return_type=None
|
||||
result = df.boxplot(by="gender")
|
||||
assert isinstance(result, np.ndarray)
|
||||
_check_box_return_type(
|
||||
result, None, expected_keys=["height", "weight", "category"]
|
||||
)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_grouped_box_return_type_groupby(self, hist_df):
|
||||
df = hist_df
|
||||
# now for groupby
|
||||
result = df.groupby("gender").boxplot(return_type="dict")
|
||||
_check_box_return_type(result, "dict", expected_keys=["Male", "Female"])
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("return_type", ["dict", "axes", "both"])
|
||||
def test_grouped_box_return_type_arg(self, hist_df, return_type):
|
||||
df = hist_df
|
||||
|
||||
returned = df.groupby("classroom").boxplot(return_type=return_type)
|
||||
_check_box_return_type(returned, return_type, expected_keys=["A", "B", "C"])
|
||||
|
||||
returned = df.boxplot(by="classroom", return_type=return_type)
|
||||
_check_box_return_type(
|
||||
returned, return_type, expected_keys=["height", "weight", "category"]
|
||||
)
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("return_type", ["dict", "axes", "both"])
|
||||
def test_grouped_box_return_type_arg_duplcate_cats(self, return_type):
|
||||
columns2 = "X B C D A".split()
|
||||
df2 = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((6, 5)), columns=columns2
|
||||
)
|
||||
categories2 = "A B".split()
|
||||
df2["category"] = categories2 * 3
|
||||
|
||||
returned = df2.groupby("category").boxplot(return_type=return_type)
|
||||
_check_box_return_type(returned, return_type, expected_keys=categories2)
|
||||
|
||||
returned = df2.boxplot(by="category", return_type=return_type)
|
||||
_check_box_return_type(returned, return_type, expected_keys=columns2)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_grouped_box_layout_too_small(self, hist_df):
|
||||
df = hist_df
|
||||
|
||||
msg = "Layout of 1x1 must be larger than required size 2"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.boxplot(column=["weight", "height"], by=df.gender, layout=(1, 1))
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_grouped_box_layout_needs_by(self, hist_df):
|
||||
df = hist_df
|
||||
msg = "The 'layout' keyword is not supported when 'by' is None"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.boxplot(
|
||||
column=["height", "weight", "category"],
|
||||
layout=(2, 1),
|
||||
return_type="dict",
|
||||
)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_grouped_box_layout_positive_layout(self, hist_df):
|
||||
df = hist_df
|
||||
msg = "At least one dimension of layout must be positive"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.boxplot(column=["weight", "height"], by=df.gender, layout=(-1, -1))
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"gb_key, axes_num, rows",
|
||||
[["gender", 2, 1], ["category", 4, 2], ["classroom", 3, 2]],
|
||||
)
|
||||
def test_grouped_box_layout_positive_layout_axes(
|
||||
self, hist_df, gb_key, axes_num, rows
|
||||
):
|
||||
df = hist_df
|
||||
# _check_plot_works adds an ax so catch warning. see GH #13188 GH 6769
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
_check_plot_works(
|
||||
df.groupby(gb_key).boxplot, column="height", return_type="dict"
|
||||
)
|
||||
_check_axes_shape(mpl.pyplot.gcf().axes, axes_num=axes_num, layout=(rows, 2))
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"col, visible", [["height", False], ["weight", True], ["category", True]]
|
||||
)
|
||||
def test_grouped_box_layout_visible(self, hist_df, col, visible):
|
||||
df = hist_df
|
||||
# GH 5897
|
||||
axes = df.boxplot(
|
||||
column=["height", "weight", "category"], by="gender", return_type="axes"
|
||||
)
|
||||
_check_axes_shape(mpl.pyplot.gcf().axes, axes_num=3, layout=(2, 2))
|
||||
ax = axes[col]
|
||||
_check_visible(ax.get_xticklabels(), visible=visible)
|
||||
_check_visible([ax.xaxis.get_label()], visible=visible)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_grouped_box_layout_shape(self, hist_df):
|
||||
df = hist_df
|
||||
df.groupby("classroom").boxplot(
|
||||
column=["height", "weight", "category"], return_type="dict"
|
||||
)
|
||||
_check_axes_shape(mpl.pyplot.gcf().axes, axes_num=3, layout=(2, 2))
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("cols", [2, -1])
|
||||
def test_grouped_box_layout_works(self, hist_df, cols):
|
||||
df = hist_df
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
_check_plot_works(
|
||||
df.groupby("category").boxplot,
|
||||
column="height",
|
||||
layout=(3, cols),
|
||||
return_type="dict",
|
||||
)
|
||||
_check_axes_shape(mpl.pyplot.gcf().axes, axes_num=4, layout=(3, 2))
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("rows, res", [[4, 4], [-1, 3]])
|
||||
def test_grouped_box_layout_axes_shape_rows(self, hist_df, rows, res):
|
||||
df = hist_df
|
||||
df.boxplot(
|
||||
column=["height", "weight", "category"], by="gender", layout=(rows, 1)
|
||||
)
|
||||
_check_axes_shape(mpl.pyplot.gcf().axes, axes_num=3, layout=(res, 1))
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("cols, res", [[4, 4], [-1, 3]])
|
||||
def test_grouped_box_layout_axes_shape_cols_groupby(self, hist_df, cols, res):
|
||||
df = hist_df
|
||||
df.groupby("classroom").boxplot(
|
||||
column=["height", "weight", "category"],
|
||||
layout=(1, cols),
|
||||
return_type="dict",
|
||||
)
|
||||
_check_axes_shape(mpl.pyplot.gcf().axes, axes_num=3, layout=(1, res))
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_grouped_box_multiple_axes(self, hist_df):
|
||||
# GH 6970, GH 7069
|
||||
df = hist_df
|
||||
|
||||
# check warning to ignore sharex / sharey
|
||||
# this check should be done in the first function which
|
||||
# passes multiple axes to plot, hist or boxplot
|
||||
# location should be changed if other test is added
|
||||
# which has earlier alphabetical order
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_, axes = mpl.pyplot.subplots(2, 2)
|
||||
df.groupby("category").boxplot(column="height", return_type="axes", ax=axes)
|
||||
_check_axes_shape(mpl.pyplot.gcf().axes, axes_num=4, layout=(2, 2))
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_grouped_box_multiple_axes_on_fig(self, hist_df):
|
||||
# GH 6970, GH 7069
|
||||
df = hist_df
|
||||
fig, axes = mpl.pyplot.subplots(2, 3)
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
returned = df.boxplot(
|
||||
column=["height", "weight", "category"],
|
||||
by="gender",
|
||||
return_type="axes",
|
||||
ax=axes[0],
|
||||
)
|
||||
returned = np.array(list(returned.values))
|
||||
_check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
tm.assert_numpy_array_equal(returned, axes[0])
|
||||
assert returned[0].figure is fig
|
||||
|
||||
# draw on second row
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
returned = df.groupby("classroom").boxplot(
|
||||
column=["height", "weight", "category"], return_type="axes", ax=axes[1]
|
||||
)
|
||||
returned = np.array(list(returned.values))
|
||||
_check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
tm.assert_numpy_array_equal(returned, axes[1])
|
||||
assert returned[0].figure is fig
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_grouped_box_multiple_axes_ax_error(self, hist_df):
|
||||
# GH 6970, GH 7069
|
||||
df = hist_df
|
||||
msg = "The number of passed axes must be 3, the same as the output plot"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
fig, axes = mpl.pyplot.subplots(2, 3)
|
||||
# pass different number of axes from required
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = df.groupby("classroom").boxplot(ax=axes)
|
||||
|
||||
def test_fontsize(self):
|
||||
df = DataFrame({"a": [1, 2, 3, 4, 5, 6], "b": [0, 0, 0, 1, 1, 1]})
|
||||
_check_ticks_props(
|
||||
df.boxplot("a", by="b", fontsize=16), xlabelsize=16, ylabelsize=16
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"col, expected_xticklabel",
|
||||
[
|
||||
("v", ["(a, v)", "(b, v)", "(c, v)", "(d, v)", "(e, v)"]),
|
||||
(["v"], ["(a, v)", "(b, v)", "(c, v)", "(d, v)", "(e, v)"]),
|
||||
("v1", ["(a, v1)", "(b, v1)", "(c, v1)", "(d, v1)", "(e, v1)"]),
|
||||
(
|
||||
["v", "v1"],
|
||||
[
|
||||
"(a, v)",
|
||||
"(a, v1)",
|
||||
"(b, v)",
|
||||
"(b, v1)",
|
||||
"(c, v)",
|
||||
"(c, v1)",
|
||||
"(d, v)",
|
||||
"(d, v1)",
|
||||
"(e, v)",
|
||||
"(e, v1)",
|
||||
],
|
||||
),
|
||||
(
|
||||
None,
|
||||
[
|
||||
"(a, v)",
|
||||
"(a, v1)",
|
||||
"(b, v)",
|
||||
"(b, v1)",
|
||||
"(c, v)",
|
||||
"(c, v1)",
|
||||
"(d, v)",
|
||||
"(d, v1)",
|
||||
"(e, v)",
|
||||
"(e, v1)",
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_groupby_boxplot_subplots_false(self, col, expected_xticklabel):
|
||||
# GH 16748
|
||||
df = DataFrame(
|
||||
{
|
||||
"cat": np.random.default_rng(2).choice(list("abcde"), 100),
|
||||
"v": np.random.default_rng(2).random(100),
|
||||
"v1": np.random.default_rng(2).random(100),
|
||||
}
|
||||
)
|
||||
grouped = df.groupby("cat")
|
||||
|
||||
axes = _check_plot_works(
|
||||
grouped.boxplot, subplots=False, column=col, return_type="axes"
|
||||
)
|
||||
|
||||
result_xticklabel = [x.get_text() for x in axes.get_xticklabels()]
|
||||
assert expected_xticklabel == result_xticklabel
|
||||
|
||||
def test_groupby_boxplot_object(self, hist_df):
|
||||
# GH 43480
|
||||
df = hist_df.astype("object")
|
||||
grouped = df.groupby("gender")
|
||||
msg = "boxplot method requires numerical columns, nothing to plot"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
_check_plot_works(grouped.boxplot, subplots=False)
|
||||
|
||||
def test_boxplot_multiindex_column(self):
|
||||
# GH 16748
|
||||
arrays = [
|
||||
["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
|
||||
["one", "two", "one", "two", "one", "two", "one", "two"],
|
||||
]
|
||||
tuples = list(zip(*arrays))
|
||||
index = MultiIndex.from_tuples(tuples, names=["first", "second"])
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((3, 8)),
|
||||
index=["A", "B", "C"],
|
||||
columns=index,
|
||||
)
|
||||
|
||||
col = [("bar", "one"), ("bar", "two")]
|
||||
axes = _check_plot_works(df.boxplot, column=col, return_type="axes")
|
||||
|
||||
expected_xticklabel = ["(bar, one)", "(bar, two)"]
|
||||
result_xticklabel = [x.get_text() for x in axes.get_xticklabels()]
|
||||
assert expected_xticklabel == result_xticklabel
|
@ -0,0 +1,60 @@
|
||||
import pytest
|
||||
|
||||
from pandas import DataFrame
|
||||
from pandas.tests.plotting.common import (
|
||||
_check_plot_works,
|
||||
_check_ticks_props,
|
||||
_gen_two_subplots,
|
||||
)
|
||||
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
|
||||
|
||||
class TestCommon:
|
||||
def test__check_ticks_props(self):
|
||||
# GH 34768
|
||||
df = DataFrame({"b": [0, 1, 0], "a": [1, 2, 3]})
|
||||
ax = _check_plot_works(df.plot, rot=30)
|
||||
ax.yaxis.set_tick_params(rotation=30)
|
||||
msg = "expected 0.00000 but got "
|
||||
with pytest.raises(AssertionError, match=msg):
|
||||
_check_ticks_props(ax, xrot=0)
|
||||
with pytest.raises(AssertionError, match=msg):
|
||||
_check_ticks_props(ax, xlabelsize=0)
|
||||
with pytest.raises(AssertionError, match=msg):
|
||||
_check_ticks_props(ax, yrot=0)
|
||||
with pytest.raises(AssertionError, match=msg):
|
||||
_check_ticks_props(ax, ylabelsize=0)
|
||||
|
||||
def test__gen_two_subplots_with_ax(self):
|
||||
fig = plt.gcf()
|
||||
gen = _gen_two_subplots(f=lambda **kwargs: None, fig=fig, ax="test")
|
||||
# On the first yield, no subplot should be added since ax was passed
|
||||
next(gen)
|
||||
assert fig.get_axes() == []
|
||||
# On the second, the one axis should match fig.subplot(2, 1, 2)
|
||||
next(gen)
|
||||
axes = fig.get_axes()
|
||||
assert len(axes) == 1
|
||||
subplot_geometry = list(axes[0].get_subplotspec().get_geometry()[:-1])
|
||||
subplot_geometry[-1] += 1
|
||||
assert subplot_geometry == [2, 1, 2]
|
||||
|
||||
def test_colorbar_layout(self):
|
||||
fig = plt.figure()
|
||||
|
||||
axes = fig.subplot_mosaic(
|
||||
"""
|
||||
AB
|
||||
CC
|
||||
"""
|
||||
)
|
||||
|
||||
x = [1, 2, 3]
|
||||
y = [1, 2, 3]
|
||||
|
||||
cs0 = axes["A"].scatter(x, y)
|
||||
axes["B"].scatter(x, y)
|
||||
|
||||
fig.colorbar(cs0, ax=[axes["A"], axes["B"]], location="right")
|
||||
DataFrame(x).plot(ax=axes["C"])
|
@ -0,0 +1,410 @@
|
||||
from datetime import (
|
||||
date,
|
||||
datetime,
|
||||
)
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas._config.config as cf
|
||||
|
||||
from pandas._libs.tslibs import to_offset
|
||||
|
||||
from pandas import (
|
||||
Index,
|
||||
Period,
|
||||
PeriodIndex,
|
||||
Series,
|
||||
Timestamp,
|
||||
arrays,
|
||||
date_range,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
|
||||
from pandas.plotting import (
|
||||
deregister_matplotlib_converters,
|
||||
register_matplotlib_converters,
|
||||
)
|
||||
from pandas.tseries.offsets import (
|
||||
Day,
|
||||
Micro,
|
||||
Milli,
|
||||
Second,
|
||||
)
|
||||
|
||||
try:
|
||||
from pandas.plotting._matplotlib import converter
|
||||
except ImportError:
|
||||
# try / except, rather than skip, to avoid internal refactoring
|
||||
# causing an improper skip
|
||||
pass
|
||||
|
||||
pytest.importorskip("matplotlib.pyplot")
|
||||
dates = pytest.importorskip("matplotlib.dates")
|
||||
|
||||
|
||||
@pytest.mark.single_cpu
|
||||
def test_registry_mpl_resets():
|
||||
# Check that Matplotlib converters are properly reset (see issue #27481)
|
||||
code = (
|
||||
"import matplotlib.units as units; "
|
||||
"import matplotlib.dates as mdates; "
|
||||
"n_conv = len(units.registry); "
|
||||
"import pandas as pd; "
|
||||
"pd.plotting.register_matplotlib_converters(); "
|
||||
"pd.plotting.deregister_matplotlib_converters(); "
|
||||
"assert len(units.registry) == n_conv"
|
||||
)
|
||||
call = [sys.executable, "-c", code]
|
||||
subprocess.check_output(call)
|
||||
|
||||
|
||||
def test_timtetonum_accepts_unicode():
|
||||
assert converter.time2num("00:01") == converter.time2num("00:01")
|
||||
|
||||
|
||||
class TestRegistration:
|
||||
@pytest.mark.single_cpu
|
||||
def test_dont_register_by_default(self):
|
||||
# Run in subprocess to ensure a clean state
|
||||
code = (
|
||||
"import matplotlib.units; "
|
||||
"import pandas as pd; "
|
||||
"units = dict(matplotlib.units.registry); "
|
||||
"assert pd.Timestamp not in units"
|
||||
)
|
||||
call = [sys.executable, "-c", code]
|
||||
assert subprocess.check_call(call) == 0
|
||||
|
||||
def test_registering_no_warning(self):
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
s = Series(range(12), index=date_range("2017", periods=12))
|
||||
_, ax = plt.subplots()
|
||||
|
||||
# Set to the "warn" state, in case this isn't the first test run
|
||||
register_matplotlib_converters()
|
||||
ax.plot(s.index, s.values)
|
||||
plt.close()
|
||||
|
||||
def test_pandas_plots_register(self):
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
s = Series(range(12), index=date_range("2017", periods=12))
|
||||
# Set to the "warn" state, in case this isn't the first test run
|
||||
with tm.assert_produces_warning(None) as w:
|
||||
s.plot()
|
||||
|
||||
try:
|
||||
assert len(w) == 0
|
||||
finally:
|
||||
plt.close()
|
||||
|
||||
def test_matplotlib_formatters(self):
|
||||
units = pytest.importorskip("matplotlib.units")
|
||||
|
||||
# Can't make any assertion about the start state.
|
||||
# We we check that toggling converters off removes it, and toggling it
|
||||
# on restores it.
|
||||
|
||||
with cf.option_context("plotting.matplotlib.register_converters", True):
|
||||
with cf.option_context("plotting.matplotlib.register_converters", False):
|
||||
assert Timestamp not in units.registry
|
||||
assert Timestamp in units.registry
|
||||
|
||||
def test_option_no_warning(self):
|
||||
pytest.importorskip("matplotlib.pyplot")
|
||||
ctx = cf.option_context("plotting.matplotlib.register_converters", False)
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
s = Series(range(12), index=date_range("2017", periods=12))
|
||||
_, ax = plt.subplots()
|
||||
|
||||
# Test without registering first, no warning
|
||||
with ctx:
|
||||
ax.plot(s.index, s.values)
|
||||
|
||||
# Now test with registering
|
||||
register_matplotlib_converters()
|
||||
with ctx:
|
||||
ax.plot(s.index, s.values)
|
||||
plt.close()
|
||||
|
||||
def test_registry_resets(self):
|
||||
units = pytest.importorskip("matplotlib.units")
|
||||
dates = pytest.importorskip("matplotlib.dates")
|
||||
|
||||
# make a copy, to reset to
|
||||
original = dict(units.registry)
|
||||
|
||||
try:
|
||||
# get to a known state
|
||||
units.registry.clear()
|
||||
date_converter = dates.DateConverter()
|
||||
units.registry[datetime] = date_converter
|
||||
units.registry[date] = date_converter
|
||||
|
||||
register_matplotlib_converters()
|
||||
assert units.registry[date] is not date_converter
|
||||
deregister_matplotlib_converters()
|
||||
assert units.registry[date] is date_converter
|
||||
|
||||
finally:
|
||||
# restore original stater
|
||||
units.registry.clear()
|
||||
for k, v in original.items():
|
||||
units.registry[k] = v
|
||||
|
||||
|
||||
class TestDateTimeConverter:
|
||||
@pytest.fixture
|
||||
def dtc(self):
|
||||
return converter.DatetimeConverter()
|
||||
|
||||
def test_convert_accepts_unicode(self, dtc):
|
||||
r1 = dtc.convert("2000-01-01 12:22", None, None)
|
||||
r2 = dtc.convert("2000-01-01 12:22", None, None)
|
||||
assert r1 == r2, "DatetimeConverter.convert should accept unicode"
|
||||
|
||||
def test_conversion(self, dtc):
|
||||
rs = dtc.convert(["2012-1-1"], None, None)[0]
|
||||
xp = dates.date2num(datetime(2012, 1, 1))
|
||||
assert rs == xp
|
||||
|
||||
rs = dtc.convert("2012-1-1", None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = dtc.convert(date(2012, 1, 1), None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = dtc.convert("2012-1-1", None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = dtc.convert(Timestamp("2012-1-1"), None, None)
|
||||
assert rs == xp
|
||||
|
||||
# also testing datetime64 dtype (GH8614)
|
||||
rs = dtc.convert("2012-01-01", None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = dtc.convert("2012-01-01 00:00:00+0000", None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = dtc.convert(
|
||||
np.array(["2012-01-01 00:00:00+0000", "2012-01-02 00:00:00+0000"]),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
assert rs[0] == xp
|
||||
|
||||
# we have a tz-aware date (constructed to that when we turn to utc it
|
||||
# is the same as our sample)
|
||||
ts = Timestamp("2012-01-01").tz_localize("UTC").tz_convert("US/Eastern")
|
||||
rs = dtc.convert(ts, None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = dtc.convert(ts.to_pydatetime(), None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = dtc.convert(Index([ts - Day(1), ts]), None, None)
|
||||
assert rs[1] == xp
|
||||
|
||||
rs = dtc.convert(Index([ts - Day(1), ts]).to_pydatetime(), None, None)
|
||||
assert rs[1] == xp
|
||||
|
||||
def test_conversion_float(self, dtc):
|
||||
rtol = 0.5 * 10**-9
|
||||
|
||||
rs = dtc.convert(Timestamp("2012-1-1 01:02:03", tz="UTC"), None, None)
|
||||
xp = converter.mdates.date2num(Timestamp("2012-1-1 01:02:03", tz="UTC"))
|
||||
tm.assert_almost_equal(rs, xp, rtol=rtol)
|
||||
|
||||
rs = dtc.convert(
|
||||
Timestamp("2012-1-1 09:02:03", tz="Asia/Hong_Kong"), None, None
|
||||
)
|
||||
tm.assert_almost_equal(rs, xp, rtol=rtol)
|
||||
|
||||
rs = dtc.convert(datetime(2012, 1, 1, 1, 2, 3), None, None)
|
||||
tm.assert_almost_equal(rs, xp, rtol=rtol)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"values",
|
||||
[
|
||||
[date(1677, 1, 1), date(1677, 1, 2)],
|
||||
[datetime(1677, 1, 1, 12), datetime(1677, 1, 2, 12)],
|
||||
],
|
||||
)
|
||||
def test_conversion_outofbounds_datetime(self, dtc, values):
|
||||
# 2579
|
||||
rs = dtc.convert(values, None, None)
|
||||
xp = converter.mdates.date2num(values)
|
||||
tm.assert_numpy_array_equal(rs, xp)
|
||||
rs = dtc.convert(values[0], None, None)
|
||||
xp = converter.mdates.date2num(values[0])
|
||||
assert rs == xp
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"time,format_expected",
|
||||
[
|
||||
(0, "00:00"), # time2num(datetime.time.min)
|
||||
(86399.999999, "23:59:59.999999"), # time2num(datetime.time.max)
|
||||
(90000, "01:00"),
|
||||
(3723, "01:02:03"),
|
||||
(39723.2, "11:02:03.200"),
|
||||
],
|
||||
)
|
||||
def test_time_formatter(self, time, format_expected):
|
||||
# issue 18478
|
||||
result = converter.TimeFormatter(None)(time)
|
||||
assert result == format_expected
|
||||
|
||||
@pytest.mark.parametrize("freq", ("B", "ms", "s"))
|
||||
def test_dateindex_conversion(self, freq, dtc):
|
||||
rtol = 10**-9
|
||||
dateindex = date_range("2020-01-01", periods=10, freq=freq)
|
||||
rs = dtc.convert(dateindex, None, None)
|
||||
xp = converter.mdates.date2num(dateindex._mpl_repr())
|
||||
tm.assert_almost_equal(rs, xp, rtol=rtol)
|
||||
|
||||
@pytest.mark.parametrize("offset", [Second(), Milli(), Micro(50)])
|
||||
def test_resolution(self, offset, dtc):
|
||||
# Matplotlib's time representation using floats cannot distinguish
|
||||
# intervals smaller than ~10 microsecond in the common range of years.
|
||||
ts1 = Timestamp("2012-1-1")
|
||||
ts2 = ts1 + offset
|
||||
val1 = dtc.convert(ts1, None, None)
|
||||
val2 = dtc.convert(ts2, None, None)
|
||||
if not val1 < val2:
|
||||
raise AssertionError(f"{val1} is not less than {val2}.")
|
||||
|
||||
def test_convert_nested(self, dtc):
|
||||
inner = [Timestamp("2017-01-01"), Timestamp("2017-01-02")]
|
||||
data = [inner, inner]
|
||||
result = dtc.convert(data, None, None)
|
||||
expected = [dtc.convert(x, None, None) for x in data]
|
||||
assert (np.array(result) == expected).all()
|
||||
|
||||
|
||||
class TestPeriodConverter:
|
||||
@pytest.fixture
|
||||
def pc(self):
|
||||
return converter.PeriodConverter()
|
||||
|
||||
@pytest.fixture
|
||||
def axis(self):
|
||||
class Axis:
|
||||
pass
|
||||
|
||||
axis = Axis()
|
||||
axis.freq = "D"
|
||||
return axis
|
||||
|
||||
def test_convert_accepts_unicode(self, pc, axis):
|
||||
r1 = pc.convert("2012-1-1", None, axis)
|
||||
r2 = pc.convert("2012-1-1", None, axis)
|
||||
assert r1 == r2
|
||||
|
||||
def test_conversion(self, pc, axis):
|
||||
rs = pc.convert(["2012-1-1"], None, axis)[0]
|
||||
xp = Period("2012-1-1").ordinal
|
||||
assert rs == xp
|
||||
|
||||
rs = pc.convert("2012-1-1", None, axis)
|
||||
assert rs == xp
|
||||
|
||||
rs = pc.convert([date(2012, 1, 1)], None, axis)[0]
|
||||
assert rs == xp
|
||||
|
||||
rs = pc.convert(date(2012, 1, 1), None, axis)
|
||||
assert rs == xp
|
||||
|
||||
rs = pc.convert([Timestamp("2012-1-1")], None, axis)[0]
|
||||
assert rs == xp
|
||||
|
||||
rs = pc.convert(Timestamp("2012-1-1"), None, axis)
|
||||
assert rs == xp
|
||||
|
||||
rs = pc.convert("2012-01-01", None, axis)
|
||||
assert rs == xp
|
||||
|
||||
rs = pc.convert("2012-01-01 00:00:00+0000", None, axis)
|
||||
assert rs == xp
|
||||
|
||||
rs = pc.convert(
|
||||
np.array(
|
||||
["2012-01-01 00:00:00", "2012-01-02 00:00:00"],
|
||||
dtype="datetime64[ns]",
|
||||
),
|
||||
None,
|
||||
axis,
|
||||
)
|
||||
assert rs[0] == xp
|
||||
|
||||
def test_integer_passthrough(self, pc, axis):
|
||||
# GH9012
|
||||
rs = pc.convert([0, 1], None, axis)
|
||||
xp = [0, 1]
|
||||
assert rs == xp
|
||||
|
||||
def test_convert_nested(self, pc, axis):
|
||||
data = ["2012-1-1", "2012-1-2"]
|
||||
r1 = pc.convert([data, data], None, axis)
|
||||
r2 = [pc.convert(data, None, axis) for _ in range(2)]
|
||||
assert r1 == r2
|
||||
|
||||
|
||||
class TestTimeDeltaConverter:
|
||||
"""Test timedelta converter"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"x, decimal, format_expected",
|
||||
[
|
||||
(0.0, 0, "00:00:00"),
|
||||
(3972320000000, 1, "01:06:12.3"),
|
||||
(713233432000000, 2, "8 days 06:07:13.43"),
|
||||
(32423432000000, 4, "09:00:23.4320"),
|
||||
],
|
||||
)
|
||||
def test_format_timedelta_ticks(self, x, decimal, format_expected):
|
||||
tdc = converter.TimeSeries_TimedeltaFormatter
|
||||
result = tdc.format_timedelta_ticks(x, pos=None, n_decimals=decimal)
|
||||
assert result == format_expected
|
||||
|
||||
@pytest.mark.parametrize("view_interval", [(1, 2), (2, 1)])
|
||||
def test_call_w_different_view_intervals(self, view_interval, monkeypatch):
|
||||
# previously broke on reversed xlmits; see GH37454
|
||||
class mock_axis:
|
||||
def get_view_interval(self):
|
||||
return view_interval
|
||||
|
||||
tdc = converter.TimeSeries_TimedeltaFormatter()
|
||||
monkeypatch.setattr(tdc, "axis", mock_axis())
|
||||
tdc(0.0, 0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("year_span", [11.25, 30, 80, 150, 400, 800, 1500, 2500, 3500])
|
||||
# The range is limited to 11.25 at the bottom by if statements in
|
||||
# the _quarterly_finder() function
|
||||
def test_quarterly_finder(year_span):
|
||||
vmin = -1000
|
||||
vmax = vmin + year_span * 4
|
||||
span = vmax - vmin + 1
|
||||
if span < 45:
|
||||
pytest.skip("the quarterly finder is only invoked if the span is >= 45")
|
||||
nyears = span / 4
|
||||
(min_anndef, maj_anndef) = converter._get_default_annual_spacing(nyears)
|
||||
result = converter._quarterly_finder(vmin, vmax, to_offset("QE"))
|
||||
quarters = PeriodIndex(
|
||||
arrays.PeriodArray(np.array([x[0] for x in result]), dtype="period[Q]")
|
||||
)
|
||||
majors = np.array([x[1] for x in result])
|
||||
minors = np.array([x[2] for x in result])
|
||||
major_quarters = quarters[majors]
|
||||
minor_quarters = quarters[minors]
|
||||
check_major_years = major_quarters.year % maj_anndef == 0
|
||||
check_minor_years = minor_quarters.year % min_anndef == 0
|
||||
check_major_quarters = major_quarters.quarter == 1
|
||||
check_minor_quarters = minor_quarters.quarter == 1
|
||||
assert np.all(check_major_years)
|
||||
assert np.all(check_minor_years)
|
||||
assert np.all(check_major_quarters)
|
||||
assert np.all(check_minor_quarters)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,155 @@
|
||||
""" Test cases for GroupBy.plot """
|
||||
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Index,
|
||||
Series,
|
||||
)
|
||||
from pandas.tests.plotting.common import (
|
||||
_check_axes_shape,
|
||||
_check_legend_labels,
|
||||
)
|
||||
|
||||
pytest.importorskip("matplotlib")
|
||||
|
||||
|
||||
class TestDataFrameGroupByPlots:
|
||||
def test_series_groupby_plotting_nominally_works(self):
|
||||
n = 10
|
||||
weight = Series(np.random.default_rng(2).normal(166, 20, size=n))
|
||||
gender = np.random.default_rng(2).choice(["male", "female"], size=n)
|
||||
|
||||
weight.groupby(gender).plot()
|
||||
|
||||
def test_series_groupby_plotting_nominally_works_hist(self):
|
||||
n = 10
|
||||
height = Series(np.random.default_rng(2).normal(60, 10, size=n))
|
||||
gender = np.random.default_rng(2).choice(["male", "female"], size=n)
|
||||
height.groupby(gender).hist()
|
||||
|
||||
def test_series_groupby_plotting_nominally_works_alpha(self):
|
||||
n = 10
|
||||
height = Series(np.random.default_rng(2).normal(60, 10, size=n))
|
||||
gender = np.random.default_rng(2).choice(["male", "female"], size=n)
|
||||
# Regression test for GH8733
|
||||
height.groupby(gender).plot(alpha=0.5)
|
||||
|
||||
def test_plotting_with_float_index_works(self):
|
||||
# GH 7025
|
||||
df = DataFrame(
|
||||
{
|
||||
"def": [1, 1, 1, 2, 2, 2, 3, 3, 3],
|
||||
"val": np.random.default_rng(2).standard_normal(9),
|
||||
},
|
||||
index=[1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0],
|
||||
)
|
||||
|
||||
df.groupby("def")["val"].plot()
|
||||
|
||||
def test_plotting_with_float_index_works_apply(self):
|
||||
# GH 7025
|
||||
df = DataFrame(
|
||||
{
|
||||
"def": [1, 1, 1, 2, 2, 2, 3, 3, 3],
|
||||
"val": np.random.default_rng(2).standard_normal(9),
|
||||
},
|
||||
index=[1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0],
|
||||
)
|
||||
df.groupby("def")["val"].apply(lambda x: x.plot())
|
||||
|
||||
def test_hist_single_row(self):
|
||||
# GH10214
|
||||
bins = np.arange(80, 100 + 2, 1)
|
||||
df = DataFrame({"Name": ["AAA", "BBB"], "ByCol": [1, 2], "Mark": [85, 89]})
|
||||
df["Mark"].hist(by=df["ByCol"], bins=bins)
|
||||
|
||||
def test_hist_single_row_single_bycol(self):
|
||||
# GH10214
|
||||
bins = np.arange(80, 100 + 2, 1)
|
||||
df = DataFrame({"Name": ["AAA"], "ByCol": [1], "Mark": [85]})
|
||||
df["Mark"].hist(by=df["ByCol"], bins=bins)
|
||||
|
||||
def test_plot_submethod_works(self):
|
||||
df = DataFrame({"x": [1, 2, 3, 4, 5], "y": [1, 2, 3, 2, 1], "z": list("ababa")})
|
||||
df.groupby("z").plot.scatter("x", "y")
|
||||
|
||||
def test_plot_submethod_works_line(self):
|
||||
df = DataFrame({"x": [1, 2, 3, 4, 5], "y": [1, 2, 3, 2, 1], "z": list("ababa")})
|
||||
df.groupby("z")["x"].plot.line()
|
||||
|
||||
def test_plot_kwargs(self):
|
||||
df = DataFrame({"x": [1, 2, 3, 4, 5], "y": [1, 2, 3, 2, 1], "z": list("ababa")})
|
||||
|
||||
res = df.groupby("z").plot(kind="scatter", x="x", y="y")
|
||||
# check that a scatter plot is effectively plotted: the axes should
|
||||
# contain a PathCollection from the scatter plot (GH11805)
|
||||
assert len(res["a"].collections) == 1
|
||||
|
||||
def test_plot_kwargs_scatter(self):
|
||||
df = DataFrame({"x": [1, 2, 3, 4, 5], "y": [1, 2, 3, 2, 1], "z": list("ababa")})
|
||||
res = df.groupby("z").plot.scatter(x="x", y="y")
|
||||
assert len(res["a"].collections) == 1
|
||||
|
||||
@pytest.mark.parametrize("column, expected_axes_num", [(None, 2), ("b", 1)])
|
||||
def test_groupby_hist_frame_with_legend(self, column, expected_axes_num):
|
||||
# GH 6279 - DataFrameGroupBy histogram can have a legend
|
||||
expected_layout = (1, expected_axes_num)
|
||||
expected_labels = column or [["a"], ["b"]]
|
||||
|
||||
index = Index(15 * ["1"] + 15 * ["2"], name="c")
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 2)),
|
||||
index=index,
|
||||
columns=["a", "b"],
|
||||
)
|
||||
g = df.groupby("c")
|
||||
|
||||
for axes in g.hist(legend=True, column=column):
|
||||
_check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout)
|
||||
for ax, expected_label in zip(axes[0], expected_labels):
|
||||
_check_legend_labels(ax, expected_label)
|
||||
|
||||
@pytest.mark.parametrize("column", [None, "b"])
|
||||
def test_groupby_hist_frame_with_legend_raises(self, column):
|
||||
# GH 6279 - DataFrameGroupBy histogram with legend and label raises
|
||||
index = Index(15 * ["1"] + 15 * ["2"], name="c")
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 2)),
|
||||
index=index,
|
||||
columns=["a", "b"],
|
||||
)
|
||||
g = df.groupby("c")
|
||||
|
||||
with pytest.raises(ValueError, match="Cannot use both legend and label"):
|
||||
g.hist(legend=True, column=column, label="d")
|
||||
|
||||
def test_groupby_hist_series_with_legend(self):
|
||||
# GH 6279 - SeriesGroupBy histogram can have a legend
|
||||
index = Index(15 * ["1"] + 15 * ["2"], name="c")
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 2)),
|
||||
index=index,
|
||||
columns=["a", "b"],
|
||||
)
|
||||
g = df.groupby("c")
|
||||
|
||||
for ax in g["a"].hist(legend=True):
|
||||
_check_axes_shape(ax, axes_num=1, layout=(1, 1))
|
||||
_check_legend_labels(ax, ["1", "2"])
|
||||
|
||||
def test_groupby_hist_series_with_legend_raises(self):
|
||||
# GH 6279 - SeriesGroupBy histogram with legend and label raises
|
||||
index = Index(15 * ["1"] + 15 * ["2"], name="c")
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 2)),
|
||||
index=index,
|
||||
columns=["a", "b"],
|
||||
)
|
||||
g = df.groupby("c")
|
||||
|
||||
with pytest.raises(ValueError, match="Cannot use both legend and label"):
|
||||
g.hist(legend=True, label="d")
|
@ -0,0 +1,971 @@
|
||||
""" Test cases for .hist method """
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Index,
|
||||
Series,
|
||||
date_range,
|
||||
to_datetime,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
_check_ax_scales,
|
||||
_check_axes_shape,
|
||||
_check_colors,
|
||||
_check_legend_labels,
|
||||
_check_patches_all_filled,
|
||||
_check_plot_works,
|
||||
_check_text_labels,
|
||||
_check_ticks_props,
|
||||
get_x_axis,
|
||||
get_y_axis,
|
||||
)
|
||||
|
||||
mpl = pytest.importorskip("matplotlib")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ts():
|
||||
return Series(
|
||||
np.arange(30, dtype=np.float64),
|
||||
index=date_range("2020-01-01", periods=30, freq="B"),
|
||||
name="ts",
|
||||
)
|
||||
|
||||
|
||||
class TestSeriesPlots:
|
||||
@pytest.mark.parametrize("kwargs", [{}, {"grid": False}, {"figsize": (8, 10)}])
|
||||
def test_hist_legacy_kwargs(self, ts, kwargs):
|
||||
_check_plot_works(ts.hist, **kwargs)
|
||||
|
||||
@pytest.mark.parametrize("kwargs", [{}, {"bins": 5}])
|
||||
def test_hist_legacy_kwargs_warning(self, ts, kwargs):
|
||||
# _check_plot_works adds an ax so catch warning. see GH #13188
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
_check_plot_works(ts.hist, by=ts.index.month, **kwargs)
|
||||
|
||||
def test_hist_legacy_ax(self, ts):
|
||||
fig, ax = mpl.pyplot.subplots(1, 1)
|
||||
_check_plot_works(ts.hist, ax=ax, default_axes=True)
|
||||
|
||||
def test_hist_legacy_ax_and_fig(self, ts):
|
||||
fig, ax = mpl.pyplot.subplots(1, 1)
|
||||
_check_plot_works(ts.hist, ax=ax, figure=fig, default_axes=True)
|
||||
|
||||
def test_hist_legacy_fig(self, ts):
|
||||
fig, _ = mpl.pyplot.subplots(1, 1)
|
||||
_check_plot_works(ts.hist, figure=fig, default_axes=True)
|
||||
|
||||
def test_hist_legacy_multi_ax(self, ts):
|
||||
fig, (ax1, ax2) = mpl.pyplot.subplots(1, 2)
|
||||
_check_plot_works(ts.hist, figure=fig, ax=ax1, default_axes=True)
|
||||
_check_plot_works(ts.hist, figure=fig, ax=ax2, default_axes=True)
|
||||
|
||||
def test_hist_legacy_by_fig_error(self, ts):
|
||||
fig, _ = mpl.pyplot.subplots(1, 1)
|
||||
msg = (
|
||||
"Cannot pass 'figure' when using the 'by' argument, since a new 'Figure' "
|
||||
"instance will be created"
|
||||
)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
ts.hist(by=ts.index, figure=fig)
|
||||
|
||||
def test_hist_bins_legacy(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
|
||||
ax = df.hist(bins=2)[0][0]
|
||||
assert len(ax.patches) == 2
|
||||
|
||||
def test_hist_layout(self, hist_df):
|
||||
df = hist_df
|
||||
msg = "The 'layout' keyword is not supported when 'by' is None"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.height.hist(layout=(1, 1))
|
||||
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.height.hist(layout=[1, 1])
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"by, layout, axes_num, res_layout",
|
||||
[
|
||||
["gender", (2, 1), 2, (2, 1)],
|
||||
["gender", (3, -1), 2, (3, 1)],
|
||||
["category", (4, 1), 4, (4, 1)],
|
||||
["category", (2, -1), 4, (2, 2)],
|
||||
["category", (3, -1), 4, (3, 2)],
|
||||
["category", (-1, 4), 4, (1, 4)],
|
||||
["classroom", (2, 2), 3, (2, 2)],
|
||||
],
|
||||
)
|
||||
def test_hist_layout_with_by(self, hist_df, by, layout, axes_num, res_layout):
|
||||
df = hist_df
|
||||
|
||||
# _check_plot_works adds an `ax` kwarg to the method call
|
||||
# so we get a warning about an axis being cleared, even
|
||||
# though we don't explicing pass one, see GH #13188
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
axes = _check_plot_works(df.height.hist, by=getattr(df, by), layout=layout)
|
||||
_check_axes_shape(axes, axes_num=axes_num, layout=res_layout)
|
||||
|
||||
def test_hist_layout_with_by_shape(self, hist_df):
|
||||
df = hist_df
|
||||
|
||||
axes = df.height.hist(by=df.category, layout=(4, 2), figsize=(12, 7))
|
||||
_check_axes_shape(axes, axes_num=4, layout=(4, 2), figsize=(12, 7))
|
||||
|
||||
def test_hist_no_overlap(self):
|
||||
from matplotlib.pyplot import (
|
||||
gcf,
|
||||
subplot,
|
||||
)
|
||||
|
||||
x = Series(np.random.default_rng(2).standard_normal(2))
|
||||
y = Series(np.random.default_rng(2).standard_normal(2))
|
||||
subplot(121)
|
||||
x.hist()
|
||||
subplot(122)
|
||||
y.hist()
|
||||
fig = gcf()
|
||||
axes = fig.axes
|
||||
assert len(axes) == 2
|
||||
|
||||
def test_hist_by_no_extra_plots(self, hist_df):
|
||||
df = hist_df
|
||||
df.height.hist(by=df.gender)
|
||||
assert len(mpl.pyplot.get_fignums()) == 1
|
||||
|
||||
def test_plot_fails_when_ax_differs_from_figure(self, ts):
|
||||
from pylab import figure
|
||||
|
||||
fig1 = figure()
|
||||
fig2 = figure()
|
||||
ax1 = fig1.add_subplot(111)
|
||||
msg = "passed axis not bound to passed figure"
|
||||
with pytest.raises(AssertionError, match=msg):
|
||||
ts.hist(ax=ax1, figure=fig2)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"histtype, expected",
|
||||
[
|
||||
("bar", True),
|
||||
("barstacked", True),
|
||||
("step", False),
|
||||
("stepfilled", True),
|
||||
],
|
||||
)
|
||||
def test_histtype_argument(self, histtype, expected):
|
||||
# GH23992 Verify functioning of histtype argument
|
||||
ser = Series(np.random.default_rng(2).integers(1, 10))
|
||||
ax = ser.hist(histtype=histtype)
|
||||
_check_patches_all_filled(ax, filled=expected)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"by, expected_axes_num, expected_layout", [(None, 1, (1, 1)), ("b", 2, (1, 2))]
|
||||
)
|
||||
def test_hist_with_legend(self, by, expected_axes_num, expected_layout):
|
||||
# GH 6279 - Series histogram can have a legend
|
||||
index = 15 * ["1"] + 15 * ["2"]
|
||||
s = Series(np.random.default_rng(2).standard_normal(30), index=index, name="a")
|
||||
s.index.name = "b"
|
||||
|
||||
# Use default_axes=True when plotting method generate subplots itself
|
||||
axes = _check_plot_works(s.hist, default_axes=True, legend=True, by=by)
|
||||
_check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout)
|
||||
_check_legend_labels(axes, "a")
|
||||
|
||||
@pytest.mark.parametrize("by", [None, "b"])
|
||||
def test_hist_with_legend_raises(self, by):
|
||||
# GH 6279 - Series histogram with legend and label raises
|
||||
index = 15 * ["1"] + 15 * ["2"]
|
||||
s = Series(np.random.default_rng(2).standard_normal(30), index=index, name="a")
|
||||
s.index.name = "b"
|
||||
|
||||
with pytest.raises(ValueError, match="Cannot use both legend and label"):
|
||||
s.hist(legend=True, by=by, label="c")
|
||||
|
||||
def test_hist_kwargs(self, ts):
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot.hist(bins=5, ax=ax)
|
||||
assert len(ax.patches) == 5
|
||||
_check_text_labels(ax.yaxis.get_label(), "Frequency")
|
||||
|
||||
def test_hist_kwargs_horizontal(self, ts):
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot.hist(bins=5, ax=ax)
|
||||
ax = ts.plot.hist(orientation="horizontal", ax=ax)
|
||||
_check_text_labels(ax.xaxis.get_label(), "Frequency")
|
||||
|
||||
def test_hist_kwargs_align(self, ts):
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot.hist(bins=5, ax=ax)
|
||||
ax = ts.plot.hist(align="left", stacked=True, ax=ax)
|
||||
|
||||
@pytest.mark.xfail(reason="Api changed in 3.6.0")
|
||||
def test_hist_kde(self, ts):
|
||||
pytest.importorskip("scipy")
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot.hist(logy=True, ax=ax)
|
||||
_check_ax_scales(ax, yaxis="log")
|
||||
xlabels = ax.get_xticklabels()
|
||||
# ticks are values, thus ticklabels are blank
|
||||
_check_text_labels(xlabels, [""] * len(xlabels))
|
||||
ylabels = ax.get_yticklabels()
|
||||
_check_text_labels(ylabels, [""] * len(ylabels))
|
||||
|
||||
def test_hist_kde_plot_works(self, ts):
|
||||
pytest.importorskip("scipy")
|
||||
_check_plot_works(ts.plot.kde)
|
||||
|
||||
def test_hist_kde_density_works(self, ts):
|
||||
pytest.importorskip("scipy")
|
||||
_check_plot_works(ts.plot.density)
|
||||
|
||||
@pytest.mark.xfail(reason="Api changed in 3.6.0")
|
||||
def test_hist_kde_logy(self, ts):
|
||||
pytest.importorskip("scipy")
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot.kde(logy=True, ax=ax)
|
||||
_check_ax_scales(ax, yaxis="log")
|
||||
xlabels = ax.get_xticklabels()
|
||||
_check_text_labels(xlabels, [""] * len(xlabels))
|
||||
ylabels = ax.get_yticklabels()
|
||||
_check_text_labels(ylabels, [""] * len(ylabels))
|
||||
|
||||
def test_hist_kde_color_bins(self, ts):
|
||||
pytest.importorskip("scipy")
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot.hist(logy=True, bins=10, color="b", ax=ax)
|
||||
_check_ax_scales(ax, yaxis="log")
|
||||
assert len(ax.patches) == 10
|
||||
_check_colors(ax.patches, facecolors=["b"] * 10)
|
||||
|
||||
def test_hist_kde_color(self, ts):
|
||||
pytest.importorskip("scipy")
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot.kde(logy=True, color="r", ax=ax)
|
||||
_check_ax_scales(ax, yaxis="log")
|
||||
lines = ax.get_lines()
|
||||
assert len(lines) == 1
|
||||
_check_colors(lines, ["r"])
|
||||
|
||||
|
||||
class TestDataFramePlots:
|
||||
@pytest.mark.slow
|
||||
def test_hist_df_legacy(self, hist_df):
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
_check_plot_works(hist_df.hist)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_hist_df_legacy_layout(self):
|
||||
# make sure layout is handled
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
|
||||
df[2] = to_datetime(
|
||||
np.random.default_rng(2).integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
axes = _check_plot_works(df.hist, grid=False)
|
||||
_check_axes_shape(axes, axes_num=3, layout=(2, 2))
|
||||
assert not axes[1, 1].get_visible()
|
||||
|
||||
_check_plot_works(df[[2]].hist)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_hist_df_legacy_layout2(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((10, 1)))
|
||||
_check_plot_works(df.hist)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_hist_df_legacy_layout3(self):
|
||||
# make sure layout is handled
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((10, 5)))
|
||||
df[5] = to_datetime(
|
||||
np.random.default_rng(2).integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
axes = _check_plot_works(df.hist, layout=(4, 2))
|
||||
_check_axes_shape(axes, axes_num=6, layout=(4, 2))
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"kwargs", [{"sharex": True, "sharey": True}, {"figsize": (8, 10)}, {"bins": 5}]
|
||||
)
|
||||
def test_hist_df_legacy_layout_kwargs(self, kwargs):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((10, 5)))
|
||||
df[5] = to_datetime(
|
||||
np.random.default_rng(2).integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
# make sure sharex, sharey is handled
|
||||
# handle figsize arg
|
||||
# check bins argument
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
_check_plot_works(df.hist, **kwargs)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_hist_df_legacy_layout_labelsize_rot(self, frame_or_series):
|
||||
# make sure xlabelsize and xrot are handled
|
||||
obj = frame_or_series(range(10))
|
||||
xf, yf = 20, 18
|
||||
xrot, yrot = 30, 40
|
||||
axes = obj.hist(xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot)
|
||||
_check_ticks_props(axes, xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_hist_df_legacy_rectangles(self):
|
||||
from matplotlib.patches import Rectangle
|
||||
|
||||
ser = Series(range(10))
|
||||
ax = ser.hist(cumulative=True, bins=4, density=True)
|
||||
# height of last bin (index 5) must be 1.0
|
||||
rects = [x for x in ax.get_children() if isinstance(x, Rectangle)]
|
||||
tm.assert_almost_equal(rects[-1].get_height(), 1.0)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_hist_df_legacy_scale(self):
|
||||
ser = Series(range(10))
|
||||
ax = ser.hist(log=True)
|
||||
# scale of y must be 'log'
|
||||
_check_ax_scales(ax, yaxis="log")
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_hist_df_legacy_external_error(self):
|
||||
ser = Series(range(10))
|
||||
# propagate attr exception from matplotlib.Axes.hist
|
||||
with tm.external_error_raised(AttributeError):
|
||||
ser.hist(foo="bar")
|
||||
|
||||
def test_hist_non_numerical_or_datetime_raises(self):
|
||||
# gh-10444, GH32590
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": np.random.default_rng(2).random(10),
|
||||
"b": np.random.default_rng(2).integers(0, 10, 10),
|
||||
"c": to_datetime(
|
||||
np.random.default_rng(2).integers(
|
||||
1582800000000000000, 1583500000000000000, 10, dtype=np.int64
|
||||
)
|
||||
),
|
||||
"d": to_datetime(
|
||||
np.random.default_rng(2).integers(
|
||||
1582800000000000000, 1583500000000000000, 10, dtype=np.int64
|
||||
),
|
||||
utc=True,
|
||||
),
|
||||
}
|
||||
)
|
||||
df_o = df.astype(object)
|
||||
|
||||
msg = "hist method requires numerical or datetime columns, nothing to plot."
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df_o.hist()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"layout_test",
|
||||
(
|
||||
{"layout": None, "expected_size": (2, 2)}, # default is 2x2
|
||||
{"layout": (2, 2), "expected_size": (2, 2)},
|
||||
{"layout": (4, 1), "expected_size": (4, 1)},
|
||||
{"layout": (1, 4), "expected_size": (1, 4)},
|
||||
{"layout": (3, 3), "expected_size": (3, 3)},
|
||||
{"layout": (-1, 4), "expected_size": (1, 4)},
|
||||
{"layout": (4, -1), "expected_size": (4, 1)},
|
||||
{"layout": (-1, 2), "expected_size": (2, 2)},
|
||||
{"layout": (2, -1), "expected_size": (2, 2)},
|
||||
),
|
||||
)
|
||||
def test_hist_layout(self, layout_test):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
|
||||
df[2] = to_datetime(
|
||||
np.random.default_rng(2).integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
axes = df.hist(layout=layout_test["layout"])
|
||||
expected = layout_test["expected_size"]
|
||||
_check_axes_shape(axes, axes_num=3, layout=expected)
|
||||
|
||||
def test_hist_layout_error(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
|
||||
df[2] = to_datetime(
|
||||
np.random.default_rng(2).integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
# layout too small for all 4 plots
|
||||
msg = "Layout of 1x1 must be larger than required size 3"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.hist(layout=(1, 1))
|
||||
|
||||
# invalid format for layout
|
||||
msg = re.escape("Layout must be a tuple of (rows, columns)")
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.hist(layout=(1,))
|
||||
msg = "At least one dimension of layout must be positive"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.hist(layout=(-1, -1))
|
||||
|
||||
# GH 9351
|
||||
def test_tight_layout(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((100, 2)))
|
||||
df[2] = to_datetime(
|
||||
np.random.default_rng(2).integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=100,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
# Use default_axes=True when plotting method generate subplots itself
|
||||
_check_plot_works(df.hist, default_axes=True)
|
||||
mpl.pyplot.tight_layout()
|
||||
|
||||
def test_hist_subplot_xrot(self):
|
||||
# GH 30288
|
||||
df = DataFrame(
|
||||
{
|
||||
"length": [1.5, 0.5, 1.2, 0.9, 3],
|
||||
"animal": ["pig", "rabbit", "pig", "pig", "rabbit"],
|
||||
}
|
||||
)
|
||||
# Use default_axes=True when plotting method generate subplots itself
|
||||
axes = _check_plot_works(
|
||||
df.hist,
|
||||
default_axes=True,
|
||||
column="length",
|
||||
by="animal",
|
||||
bins=5,
|
||||
xrot=0,
|
||||
)
|
||||
_check_ticks_props(axes, xrot=0)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"column, expected",
|
||||
[
|
||||
(None, ["width", "length", "height"]),
|
||||
(["length", "width", "height"], ["length", "width", "height"]),
|
||||
],
|
||||
)
|
||||
def test_hist_column_order_unchanged(self, column, expected):
|
||||
# GH29235
|
||||
|
||||
df = DataFrame(
|
||||
{
|
||||
"width": [0.7, 0.2, 0.15, 0.2, 1.1],
|
||||
"length": [1.5, 0.5, 1.2, 0.9, 3],
|
||||
"height": [3, 0.5, 3.4, 2, 1],
|
||||
},
|
||||
index=["pig", "rabbit", "duck", "chicken", "horse"],
|
||||
)
|
||||
|
||||
# Use default_axes=True when plotting method generate subplots itself
|
||||
axes = _check_plot_works(
|
||||
df.hist,
|
||||
default_axes=True,
|
||||
column=column,
|
||||
layout=(1, 3),
|
||||
)
|
||||
result = [axes[0, i].get_title() for i in range(3)]
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"histtype, expected",
|
||||
[
|
||||
("bar", True),
|
||||
("barstacked", True),
|
||||
("step", False),
|
||||
("stepfilled", True),
|
||||
],
|
||||
)
|
||||
def test_histtype_argument(self, histtype, expected):
|
||||
# GH23992 Verify functioning of histtype argument
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).integers(1, 10, size=(100, 2)), columns=["a", "b"]
|
||||
)
|
||||
ax = df.hist(histtype=histtype)
|
||||
_check_patches_all_filled(ax, filled=expected)
|
||||
|
||||
@pytest.mark.parametrize("by", [None, "c"])
|
||||
@pytest.mark.parametrize("column", [None, "b"])
|
||||
def test_hist_with_legend(self, by, column):
|
||||
# GH 6279 - DataFrame histogram can have a legend
|
||||
expected_axes_num = 1 if by is None and column is not None else 2
|
||||
expected_layout = (1, expected_axes_num)
|
||||
expected_labels = column or ["a", "b"]
|
||||
if by is not None:
|
||||
expected_labels = [expected_labels] * 2
|
||||
|
||||
index = Index(15 * ["1"] + 15 * ["2"], name="c")
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 2)),
|
||||
index=index,
|
||||
columns=["a", "b"],
|
||||
)
|
||||
|
||||
# Use default_axes=True when plotting method generate subplots itself
|
||||
axes = _check_plot_works(
|
||||
df.hist,
|
||||
default_axes=True,
|
||||
legend=True,
|
||||
by=by,
|
||||
column=column,
|
||||
)
|
||||
|
||||
_check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout)
|
||||
if by is None and column is None:
|
||||
axes = axes[0]
|
||||
for expected_label, ax in zip(expected_labels, axes):
|
||||
_check_legend_labels(ax, expected_label)
|
||||
|
||||
@pytest.mark.parametrize("by", [None, "c"])
|
||||
@pytest.mark.parametrize("column", [None, "b"])
|
||||
def test_hist_with_legend_raises(self, by, column):
|
||||
# GH 6279 - DataFrame histogram with legend and label raises
|
||||
index = Index(15 * ["1"] + 15 * ["2"], name="c")
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 2)),
|
||||
index=index,
|
||||
columns=["a", "b"],
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="Cannot use both legend and label"):
|
||||
df.hist(legend=True, by=by, column=column, label="d")
|
||||
|
||||
def test_hist_df_kwargs(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df.plot.hist(bins=5, ax=ax)
|
||||
assert len(ax.patches) == 10
|
||||
|
||||
def test_hist_df_with_nonnumerics(self):
|
||||
# GH 9853
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((10, 4)),
|
||||
columns=["A", "B", "C", "D"],
|
||||
)
|
||||
df["E"] = ["x", "y"] * 5
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df.plot.hist(bins=5, ax=ax)
|
||||
assert len(ax.patches) == 20
|
||||
|
||||
def test_hist_df_with_nonnumerics_no_bins(self):
|
||||
# GH 9853
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((10, 4)),
|
||||
columns=["A", "B", "C", "D"],
|
||||
)
|
||||
df["E"] = ["x", "y"] * 5
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df.plot.hist(ax=ax) # bins=10
|
||||
assert len(ax.patches) == 40
|
||||
|
||||
def test_hist_secondary_legend(self):
|
||||
# GH 9610
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 4)), columns=list("abcd")
|
||||
)
|
||||
|
||||
# primary -> secondary
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df["a"].plot.hist(legend=True, ax=ax)
|
||||
df["b"].plot.hist(ax=ax, legend=True, secondary_y=True)
|
||||
# both legends are drawn on left ax
|
||||
# left and right axis must be visible
|
||||
_check_legend_labels(ax, labels=["a", "b (right)"])
|
||||
assert ax.get_yaxis().get_visible()
|
||||
assert ax.right_ax.get_yaxis().get_visible()
|
||||
|
||||
def test_hist_secondary_secondary(self):
|
||||
# GH 9610
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 4)), columns=list("abcd")
|
||||
)
|
||||
# secondary -> secondary
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df["a"].plot.hist(legend=True, secondary_y=True, ax=ax)
|
||||
df["b"].plot.hist(ax=ax, legend=True, secondary_y=True)
|
||||
# both legends are draw on left ax
|
||||
# left axis must be invisible, right axis must be visible
|
||||
_check_legend_labels(ax.left_ax, labels=["a (right)", "b (right)"])
|
||||
assert not ax.left_ax.get_yaxis().get_visible()
|
||||
assert ax.get_yaxis().get_visible()
|
||||
|
||||
def test_hist_secondary_primary(self):
|
||||
# GH 9610
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 4)), columns=list("abcd")
|
||||
)
|
||||
# secondary -> primary
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df["a"].plot.hist(legend=True, secondary_y=True, ax=ax)
|
||||
# right axes is returned
|
||||
df["b"].plot.hist(ax=ax, legend=True)
|
||||
# both legends are draw on left ax
|
||||
# left and right axis must be visible
|
||||
_check_legend_labels(ax.left_ax, labels=["a (right)", "b"])
|
||||
assert ax.left_ax.get_yaxis().get_visible()
|
||||
assert ax.get_yaxis().get_visible()
|
||||
|
||||
def test_hist_with_nans_and_weights(self):
|
||||
# GH 48884
|
||||
mpl_patches = pytest.importorskip("matplotlib.patches")
|
||||
df = DataFrame(
|
||||
[[np.nan, 0.2, 0.3], [0.4, np.nan, np.nan], [0.7, 0.8, 0.9]],
|
||||
columns=list("abc"),
|
||||
)
|
||||
weights = np.array([0.25, 0.3, 0.45])
|
||||
no_nan_df = DataFrame([[0.4, 0.2, 0.3], [0.7, 0.8, 0.9]], columns=list("abc"))
|
||||
no_nan_weights = np.array([[0.3, 0.25, 0.25], [0.45, 0.45, 0.45]])
|
||||
|
||||
_, ax0 = mpl.pyplot.subplots()
|
||||
df.plot.hist(ax=ax0, weights=weights)
|
||||
rects = [x for x in ax0.get_children() if isinstance(x, mpl_patches.Rectangle)]
|
||||
heights = [rect.get_height() for rect in rects]
|
||||
_, ax1 = mpl.pyplot.subplots()
|
||||
no_nan_df.plot.hist(ax=ax1, weights=no_nan_weights)
|
||||
no_nan_rects = [
|
||||
x for x in ax1.get_children() if isinstance(x, mpl_patches.Rectangle)
|
||||
]
|
||||
no_nan_heights = [rect.get_height() for rect in no_nan_rects]
|
||||
assert all(h0 == h1 for h0, h1 in zip(heights, no_nan_heights))
|
||||
|
||||
idxerror_weights = np.array([[0.3, 0.25], [0.45, 0.45]])
|
||||
|
||||
msg = "weights must have the same shape as data, or be a single column"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
_, ax2 = mpl.pyplot.subplots()
|
||||
no_nan_df.plot.hist(ax=ax2, weights=idxerror_weights)
|
||||
|
||||
|
||||
class TestDataFrameGroupByPlots:
|
||||
def test_grouped_hist_legacy(self):
|
||||
from pandas.plotting._matplotlib.hist import _grouped_hist
|
||||
|
||||
rs = np.random.default_rng(10)
|
||||
df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
|
||||
df["B"] = to_datetime(
|
||||
rs.integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
df["C"] = rs.integers(0, 4, 10)
|
||||
df["D"] = ["X"] * 10
|
||||
|
||||
axes = _grouped_hist(df.A, by=df.C)
|
||||
_check_axes_shape(axes, axes_num=4, layout=(2, 2))
|
||||
|
||||
def test_grouped_hist_legacy_axes_shape_no_col(self):
|
||||
rs = np.random.default_rng(10)
|
||||
df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
|
||||
df["B"] = to_datetime(
|
||||
rs.integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
df["C"] = rs.integers(0, 4, 10)
|
||||
df["D"] = ["X"] * 10
|
||||
axes = df.hist(by=df.C)
|
||||
_check_axes_shape(axes, axes_num=4, layout=(2, 2))
|
||||
|
||||
def test_grouped_hist_legacy_single_key(self):
|
||||
rs = np.random.default_rng(2)
|
||||
df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
|
||||
df["B"] = to_datetime(
|
||||
rs.integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
df["C"] = rs.integers(0, 4, 10)
|
||||
df["D"] = ["X"] * 10
|
||||
# group by a key with single value
|
||||
axes = df.hist(by="D", rot=30)
|
||||
_check_axes_shape(axes, axes_num=1, layout=(1, 1))
|
||||
_check_ticks_props(axes, xrot=30)
|
||||
|
||||
def test_grouped_hist_legacy_grouped_hist_kwargs(self):
|
||||
from matplotlib.patches import Rectangle
|
||||
|
||||
from pandas.plotting._matplotlib.hist import _grouped_hist
|
||||
|
||||
rs = np.random.default_rng(2)
|
||||
df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
|
||||
df["B"] = to_datetime(
|
||||
rs.integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
df["C"] = rs.integers(0, 4, 10)
|
||||
# make sure kwargs to hist are handled
|
||||
xf, yf = 20, 18
|
||||
xrot, yrot = 30, 40
|
||||
|
||||
axes = _grouped_hist(
|
||||
df.A,
|
||||
by=df.C,
|
||||
cumulative=True,
|
||||
bins=4,
|
||||
xlabelsize=xf,
|
||||
xrot=xrot,
|
||||
ylabelsize=yf,
|
||||
yrot=yrot,
|
||||
density=True,
|
||||
)
|
||||
# height of last bin (index 5) must be 1.0
|
||||
for ax in axes.ravel():
|
||||
rects = [x for x in ax.get_children() if isinstance(x, Rectangle)]
|
||||
height = rects[-1].get_height()
|
||||
tm.assert_almost_equal(height, 1.0)
|
||||
_check_ticks_props(axes, xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot)
|
||||
|
||||
def test_grouped_hist_legacy_grouped_hist(self):
|
||||
from pandas.plotting._matplotlib.hist import _grouped_hist
|
||||
|
||||
rs = np.random.default_rng(2)
|
||||
df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
|
||||
df["B"] = to_datetime(
|
||||
rs.integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
df["C"] = rs.integers(0, 4, 10)
|
||||
df["D"] = ["X"] * 10
|
||||
axes = _grouped_hist(df.A, by=df.C, log=True)
|
||||
# scale of y must be 'log'
|
||||
_check_ax_scales(axes, yaxis="log")
|
||||
|
||||
def test_grouped_hist_legacy_external_err(self):
|
||||
from pandas.plotting._matplotlib.hist import _grouped_hist
|
||||
|
||||
rs = np.random.default_rng(2)
|
||||
df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
|
||||
df["B"] = to_datetime(
|
||||
rs.integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
df["C"] = rs.integers(0, 4, 10)
|
||||
df["D"] = ["X"] * 10
|
||||
# propagate attr exception from matplotlib.Axes.hist
|
||||
with tm.external_error_raised(AttributeError):
|
||||
_grouped_hist(df.A, by=df.C, foo="bar")
|
||||
|
||||
def test_grouped_hist_legacy_figsize_err(self):
|
||||
rs = np.random.default_rng(2)
|
||||
df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
|
||||
df["B"] = to_datetime(
|
||||
rs.integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
df["C"] = rs.integers(0, 4, 10)
|
||||
df["D"] = ["X"] * 10
|
||||
msg = "Specify figure size by tuple instead"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.hist(by="C", figsize="default")
|
||||
|
||||
def test_grouped_hist_legacy2(self):
|
||||
n = 10
|
||||
weight = Series(np.random.default_rng(2).normal(166, 20, size=n))
|
||||
height = Series(np.random.default_rng(2).normal(60, 10, size=n))
|
||||
gender_int = np.random.default_rng(2).choice([0, 1], size=n)
|
||||
df_int = DataFrame({"height": height, "weight": weight, "gender": gender_int})
|
||||
gb = df_int.groupby("gender")
|
||||
axes = gb.hist()
|
||||
assert len(axes) == 2
|
||||
assert len(mpl.pyplot.get_fignums()) == 2
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"msg, plot_col, by_col, layout",
|
||||
[
|
||||
[
|
||||
"Layout of 1x1 must be larger than required size 2",
|
||||
"weight",
|
||||
"gender",
|
||||
(1, 1),
|
||||
],
|
||||
[
|
||||
"Layout of 1x3 must be larger than required size 4",
|
||||
"height",
|
||||
"category",
|
||||
(1, 3),
|
||||
],
|
||||
[
|
||||
"At least one dimension of layout must be positive",
|
||||
"height",
|
||||
"category",
|
||||
(-1, -1),
|
||||
],
|
||||
],
|
||||
)
|
||||
def test_grouped_hist_layout_error(self, hist_df, msg, plot_col, by_col, layout):
|
||||
df = hist_df
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.hist(column=plot_col, by=getattr(df, by_col), layout=layout)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_grouped_hist_layout_warning(self, hist_df):
|
||||
df = hist_df
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
axes = _check_plot_works(
|
||||
df.hist, column="height", by=df.gender, layout=(2, 1)
|
||||
)
|
||||
_check_axes_shape(axes, axes_num=2, layout=(2, 1))
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"layout, check_layout, figsize",
|
||||
[[(4, 1), (4, 1), None], [(-1, 1), (4, 1), None], [(4, 2), (4, 2), (12, 8)]],
|
||||
)
|
||||
def test_grouped_hist_layout_figsize(self, hist_df, layout, check_layout, figsize):
|
||||
df = hist_df
|
||||
axes = df.hist(column="height", by=df.category, layout=layout, figsize=figsize)
|
||||
_check_axes_shape(axes, axes_num=4, layout=check_layout, figsize=figsize)
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("kwargs", [{}, {"column": "height", "layout": (2, 2)}])
|
||||
def test_grouped_hist_layout_by_warning(self, hist_df, kwargs):
|
||||
df = hist_df
|
||||
# GH 6769
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
axes = _check_plot_works(df.hist, by="classroom", **kwargs)
|
||||
_check_axes_shape(axes, axes_num=3, layout=(2, 2))
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"kwargs, axes_num, layout",
|
||||
[
|
||||
[{"by": "gender", "layout": (3, 5)}, 2, (3, 5)],
|
||||
[{"column": ["height", "weight", "category"]}, 3, (2, 2)],
|
||||
],
|
||||
)
|
||||
def test_grouped_hist_layout_axes(self, hist_df, kwargs, axes_num, layout):
|
||||
df = hist_df
|
||||
axes = df.hist(**kwargs)
|
||||
_check_axes_shape(axes, axes_num=axes_num, layout=layout)
|
||||
|
||||
def test_grouped_hist_multiple_axes(self, hist_df):
|
||||
# GH 6970, GH 7069
|
||||
df = hist_df
|
||||
|
||||
fig, axes = mpl.pyplot.subplots(2, 3)
|
||||
returned = df.hist(column=["height", "weight", "category"], ax=axes[0])
|
||||
_check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
tm.assert_numpy_array_equal(returned, axes[0])
|
||||
assert returned[0].figure is fig
|
||||
|
||||
def test_grouped_hist_multiple_axes_no_cols(self, hist_df):
|
||||
# GH 6970, GH 7069
|
||||
df = hist_df
|
||||
|
||||
fig, axes = mpl.pyplot.subplots(2, 3)
|
||||
returned = df.hist(by="classroom", ax=axes[1])
|
||||
_check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
tm.assert_numpy_array_equal(returned, axes[1])
|
||||
assert returned[0].figure is fig
|
||||
|
||||
def test_grouped_hist_multiple_axes_error(self, hist_df):
|
||||
# GH 6970, GH 7069
|
||||
df = hist_df
|
||||
fig, axes = mpl.pyplot.subplots(2, 3)
|
||||
# pass different number of axes from required
|
||||
msg = "The number of passed axes must be 1, the same as the output plot"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
axes = df.hist(column="height", ax=axes)
|
||||
|
||||
def test_axis_share_x(self, hist_df):
|
||||
df = hist_df
|
||||
# GH4089
|
||||
ax1, ax2 = df.hist(column="height", by=df.gender, sharex=True)
|
||||
|
||||
# share x
|
||||
assert get_x_axis(ax1).joined(ax1, ax2)
|
||||
assert get_x_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
# don't share y
|
||||
assert not get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert not get_y_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
def test_axis_share_y(self, hist_df):
|
||||
df = hist_df
|
||||
ax1, ax2 = df.hist(column="height", by=df.gender, sharey=True)
|
||||
|
||||
# share y
|
||||
assert get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert get_y_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
# don't share x
|
||||
assert not get_x_axis(ax1).joined(ax1, ax2)
|
||||
assert not get_x_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
def test_axis_share_xy(self, hist_df):
|
||||
df = hist_df
|
||||
ax1, ax2 = df.hist(column="height", by=df.gender, sharex=True, sharey=True)
|
||||
|
||||
# share both x and y
|
||||
assert get_x_axis(ax1).joined(ax1, ax2)
|
||||
assert get_x_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
assert get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert get_y_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"histtype, expected",
|
||||
[
|
||||
("bar", True),
|
||||
("barstacked", True),
|
||||
("step", False),
|
||||
("stepfilled", True),
|
||||
],
|
||||
)
|
||||
def test_histtype_argument(self, histtype, expected):
|
||||
# GH23992 Verify functioning of histtype argument
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).integers(1, 10, size=(10, 2)), columns=["a", "b"]
|
||||
)
|
||||
ax = df.hist(by="a", histtype=histtype)
|
||||
_check_patches_all_filled(ax, filled=expected)
|
@ -0,0 +1,720 @@
|
||||
""" Test cases for misc plot functions """
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Index,
|
||||
Series,
|
||||
Timestamp,
|
||||
date_range,
|
||||
interval_range,
|
||||
period_range,
|
||||
plotting,
|
||||
read_csv,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
_check_colors,
|
||||
_check_legend_labels,
|
||||
_check_plot_works,
|
||||
_check_text_labels,
|
||||
_check_ticks_props,
|
||||
)
|
||||
|
||||
mpl = pytest.importorskip("matplotlib")
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
cm = pytest.importorskip("matplotlib.cm")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def iris(datapath) -> DataFrame:
|
||||
"""
|
||||
The iris dataset as a DataFrame.
|
||||
"""
|
||||
return read_csv(datapath("io", "data", "csv", "iris.csv"))
|
||||
|
||||
|
||||
@td.skip_if_installed("matplotlib")
|
||||
def test_import_error_message():
|
||||
# GH-19810
|
||||
df = DataFrame({"A": [1, 2]})
|
||||
|
||||
with pytest.raises(ImportError, match="matplotlib is required for plotting"):
|
||||
df.plot()
|
||||
|
||||
|
||||
def test_get_accessor_args():
|
||||
func = plotting._core.PlotAccessor._get_call_args
|
||||
|
||||
msg = "Called plot accessor for type list, expected Series or DataFrame"
|
||||
with pytest.raises(TypeError, match=msg):
|
||||
func(backend_name="", data=[], args=[], kwargs={})
|
||||
|
||||
msg = "should not be called with positional arguments"
|
||||
with pytest.raises(TypeError, match=msg):
|
||||
func(backend_name="", data=Series(dtype=object), args=["line", None], kwargs={})
|
||||
|
||||
x, y, kind, kwargs = func(
|
||||
backend_name="",
|
||||
data=DataFrame(),
|
||||
args=["x"],
|
||||
kwargs={"y": "y", "kind": "bar", "grid": False},
|
||||
)
|
||||
assert x == "x"
|
||||
assert y == "y"
|
||||
assert kind == "bar"
|
||||
assert kwargs == {"grid": False}
|
||||
|
||||
x, y, kind, kwargs = func(
|
||||
backend_name="pandas.plotting._matplotlib",
|
||||
data=Series(dtype=object),
|
||||
args=[],
|
||||
kwargs={},
|
||||
)
|
||||
assert x is None
|
||||
assert y is None
|
||||
assert kind == "line"
|
||||
assert len(kwargs) == 24
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kind", plotting.PlotAccessor._all_kinds)
|
||||
@pytest.mark.parametrize(
|
||||
"data", [DataFrame(np.arange(15).reshape(5, 3)), Series(range(5))]
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index",
|
||||
[
|
||||
Index(range(5)),
|
||||
date_range("2020-01-01", periods=5),
|
||||
period_range("2020-01-01", periods=5),
|
||||
],
|
||||
)
|
||||
def test_savefig(kind, data, index):
|
||||
fig, ax = plt.subplots()
|
||||
data.index = index
|
||||
kwargs = {}
|
||||
if kind in ["hexbin", "scatter", "pie"]:
|
||||
if isinstance(data, Series):
|
||||
pytest.skip(f"{kind} not supported with Series")
|
||||
kwargs = {"x": 0, "y": 1}
|
||||
data.plot(kind=kind, ax=ax, **kwargs)
|
||||
fig.savefig(os.devnull)
|
||||
|
||||
|
||||
class TestSeriesPlots:
|
||||
def test_autocorrelation_plot(self):
|
||||
from pandas.plotting import autocorrelation_plot
|
||||
|
||||
ser = Series(
|
||||
np.arange(10, dtype=np.float64),
|
||||
index=date_range("2020-01-01", periods=10),
|
||||
name="ts",
|
||||
)
|
||||
# Ensure no UserWarning when making plot
|
||||
with tm.assert_produces_warning(None):
|
||||
_check_plot_works(autocorrelation_plot, series=ser)
|
||||
_check_plot_works(autocorrelation_plot, series=ser.values)
|
||||
|
||||
ax = autocorrelation_plot(ser, label="Test")
|
||||
_check_legend_labels(ax, labels=["Test"])
|
||||
|
||||
@pytest.mark.parametrize("kwargs", [{}, {"lag": 5}])
|
||||
def test_lag_plot(self, kwargs):
|
||||
from pandas.plotting import lag_plot
|
||||
|
||||
ser = Series(
|
||||
np.arange(10, dtype=np.float64),
|
||||
index=date_range("2020-01-01", periods=10),
|
||||
name="ts",
|
||||
)
|
||||
_check_plot_works(lag_plot, series=ser, **kwargs)
|
||||
|
||||
def test_bootstrap_plot(self):
|
||||
from pandas.plotting import bootstrap_plot
|
||||
|
||||
ser = Series(
|
||||
np.arange(10, dtype=np.float64),
|
||||
index=date_range("2020-01-01", periods=10),
|
||||
name="ts",
|
||||
)
|
||||
_check_plot_works(bootstrap_plot, series=ser, size=10)
|
||||
|
||||
|
||||
class TestDataFramePlots:
|
||||
@pytest.mark.parametrize("pass_axis", [False, True])
|
||||
def test_scatter_matrix_axis(self, pass_axis):
|
||||
pytest.importorskip("scipy")
|
||||
scatter_matrix = plotting.scatter_matrix
|
||||
|
||||
ax = None
|
||||
if pass_axis:
|
||||
_, ax = mpl.pyplot.subplots(3, 3)
|
||||
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((100, 3)))
|
||||
|
||||
# we are plotting multiples on a sub-plot
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
axes = _check_plot_works(
|
||||
scatter_matrix,
|
||||
frame=df,
|
||||
range_padding=0.1,
|
||||
ax=ax,
|
||||
)
|
||||
axes0_labels = axes[0][0].yaxis.get_majorticklabels()
|
||||
# GH 5662
|
||||
expected = ["-2", "0", "2"]
|
||||
_check_text_labels(axes0_labels, expected)
|
||||
_check_ticks_props(axes, xlabelsize=8, xrot=90, ylabelsize=8, yrot=0)
|
||||
|
||||
@pytest.mark.parametrize("pass_axis", [False, True])
|
||||
def test_scatter_matrix_axis_smaller(self, pass_axis):
|
||||
pytest.importorskip("scipy")
|
||||
scatter_matrix = plotting.scatter_matrix
|
||||
|
||||
ax = None
|
||||
if pass_axis:
|
||||
_, ax = mpl.pyplot.subplots(3, 3)
|
||||
|
||||
df = DataFrame(np.random.default_rng(11).standard_normal((100, 3)))
|
||||
df[0] = (df[0] - 2) / 3
|
||||
|
||||
# we are plotting multiples on a sub-plot
|
||||
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
|
||||
axes = _check_plot_works(
|
||||
scatter_matrix,
|
||||
frame=df,
|
||||
range_padding=0.1,
|
||||
ax=ax,
|
||||
)
|
||||
axes0_labels = axes[0][0].yaxis.get_majorticklabels()
|
||||
expected = ["-1.0", "-0.5", "0.0"]
|
||||
_check_text_labels(axes0_labels, expected)
|
||||
_check_ticks_props(axes, xlabelsize=8, xrot=90, ylabelsize=8, yrot=0)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_andrews_curves_no_warning(self, iris):
|
||||
from pandas.plotting import andrews_curves
|
||||
|
||||
df = iris
|
||||
# Ensure no UserWarning when making plot
|
||||
with tm.assert_produces_warning(None):
|
||||
_check_plot_works(andrews_curves, frame=df, class_column="Name")
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"linecolors",
|
||||
[
|
||||
("#556270", "#4ECDC4", "#C7F464"),
|
||||
["dodgerblue", "aquamarine", "seagreen"],
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"df",
|
||||
[
|
||||
"iris",
|
||||
DataFrame(
|
||||
{
|
||||
"A": np.random.default_rng(2).standard_normal(10),
|
||||
"B": np.random.default_rng(2).standard_normal(10),
|
||||
"C": np.random.default_rng(2).standard_normal(10),
|
||||
"Name": ["A"] * 10,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_andrews_curves_linecolors(self, request, df, linecolors):
|
||||
from pandas.plotting import andrews_curves
|
||||
|
||||
if isinstance(df, str):
|
||||
df = request.getfixturevalue(df)
|
||||
ax = _check_plot_works(
|
||||
andrews_curves, frame=df, class_column="Name", color=linecolors
|
||||
)
|
||||
_check_colors(
|
||||
ax.get_lines()[:10], linecolors=linecolors, mapping=df["Name"][:10]
|
||||
)
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"df",
|
||||
[
|
||||
"iris",
|
||||
DataFrame(
|
||||
{
|
||||
"A": np.random.default_rng(2).standard_normal(10),
|
||||
"B": np.random.default_rng(2).standard_normal(10),
|
||||
"C": np.random.default_rng(2).standard_normal(10),
|
||||
"Name": ["A"] * 10,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_andrews_curves_cmap(self, request, df):
|
||||
from pandas.plotting import andrews_curves
|
||||
|
||||
if isinstance(df, str):
|
||||
df = request.getfixturevalue(df)
|
||||
cmaps = [cm.jet(n) for n in np.linspace(0, 1, df["Name"].nunique())]
|
||||
ax = _check_plot_works(
|
||||
andrews_curves, frame=df, class_column="Name", color=cmaps
|
||||
)
|
||||
_check_colors(ax.get_lines()[:10], linecolors=cmaps, mapping=df["Name"][:10])
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_andrews_curves_handle(self):
|
||||
from pandas.plotting import andrews_curves
|
||||
|
||||
colors = ["b", "g", "r"]
|
||||
df = DataFrame({"A": [1, 2, 3], "B": [1, 2, 3], "C": [1, 2, 3], "Name": colors})
|
||||
ax = andrews_curves(df, "Name", color=colors)
|
||||
handles, _ = ax.get_legend_handles_labels()
|
||||
_check_colors(handles, linecolors=colors)
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"color",
|
||||
[("#556270", "#4ECDC4", "#C7F464"), ["dodgerblue", "aquamarine", "seagreen"]],
|
||||
)
|
||||
def test_parallel_coordinates_colors(self, iris, color):
|
||||
from pandas.plotting import parallel_coordinates
|
||||
|
||||
df = iris
|
||||
|
||||
ax = _check_plot_works(
|
||||
parallel_coordinates, frame=df, class_column="Name", color=color
|
||||
)
|
||||
_check_colors(ax.get_lines()[:10], linecolors=color, mapping=df["Name"][:10])
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_parallel_coordinates_cmap(self, iris):
|
||||
from matplotlib import cm
|
||||
|
||||
from pandas.plotting import parallel_coordinates
|
||||
|
||||
df = iris
|
||||
|
||||
ax = _check_plot_works(
|
||||
parallel_coordinates, frame=df, class_column="Name", colormap=cm.jet
|
||||
)
|
||||
cmaps = [cm.jet(n) for n in np.linspace(0, 1, df["Name"].nunique())]
|
||||
_check_colors(ax.get_lines()[:10], linecolors=cmaps, mapping=df["Name"][:10])
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_parallel_coordinates_line_diff(self, iris):
|
||||
from pandas.plotting import parallel_coordinates
|
||||
|
||||
df = iris
|
||||
|
||||
ax = _check_plot_works(parallel_coordinates, frame=df, class_column="Name")
|
||||
nlines = len(ax.get_lines())
|
||||
nxticks = len(ax.xaxis.get_ticklabels())
|
||||
|
||||
ax = _check_plot_works(
|
||||
parallel_coordinates, frame=df, class_column="Name", axvlines=False
|
||||
)
|
||||
assert len(ax.get_lines()) == (nlines - nxticks)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_parallel_coordinates_handles(self, iris):
|
||||
from pandas.plotting import parallel_coordinates
|
||||
|
||||
df = iris
|
||||
colors = ["b", "g", "r"]
|
||||
df = DataFrame({"A": [1, 2, 3], "B": [1, 2, 3], "C": [1, 2, 3], "Name": colors})
|
||||
ax = parallel_coordinates(df, "Name", color=colors)
|
||||
handles, _ = ax.get_legend_handles_labels()
|
||||
_check_colors(handles, linecolors=colors)
|
||||
|
||||
# not sure if this is indicative of a problem
|
||||
@pytest.mark.filterwarnings("ignore:Attempting to set:UserWarning")
|
||||
def test_parallel_coordinates_with_sorted_labels(self):
|
||||
"""For #15908"""
|
||||
from pandas.plotting import parallel_coordinates
|
||||
|
||||
df = DataFrame(
|
||||
{
|
||||
"feat": list(range(30)),
|
||||
"class": [2 for _ in range(10)]
|
||||
+ [3 for _ in range(10)]
|
||||
+ [1 for _ in range(10)],
|
||||
}
|
||||
)
|
||||
ax = parallel_coordinates(df, "class", sort_labels=True)
|
||||
polylines, labels = ax.get_legend_handles_labels()
|
||||
color_label_tuples = zip(
|
||||
[polyline.get_color() for polyline in polylines], labels
|
||||
)
|
||||
ordered_color_label_tuples = sorted(color_label_tuples, key=lambda x: x[1])
|
||||
prev_next_tupels = zip(
|
||||
list(ordered_color_label_tuples[0:-1]), list(ordered_color_label_tuples[1:])
|
||||
)
|
||||
for prev, nxt in prev_next_tupels:
|
||||
# labels and colors are ordered strictly increasing
|
||||
assert prev[1] < nxt[1] and prev[0] < nxt[0]
|
||||
|
||||
def test_radviz_no_warning(self, iris):
|
||||
from pandas.plotting import radviz
|
||||
|
||||
df = iris
|
||||
# Ensure no UserWarning when making plot
|
||||
with tm.assert_produces_warning(None):
|
||||
_check_plot_works(radviz, frame=df, class_column="Name")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"color",
|
||||
[("#556270", "#4ECDC4", "#C7F464"), ["dodgerblue", "aquamarine", "seagreen"]],
|
||||
)
|
||||
def test_radviz_color(self, iris, color):
|
||||
from pandas.plotting import radviz
|
||||
|
||||
df = iris
|
||||
ax = _check_plot_works(radviz, frame=df, class_column="Name", color=color)
|
||||
# skip Circle drawn as ticks
|
||||
patches = [p for p in ax.patches[:20] if p.get_label() != ""]
|
||||
_check_colors(patches[:10], facecolors=color, mapping=df["Name"][:10])
|
||||
|
||||
def test_radviz_color_cmap(self, iris):
|
||||
from matplotlib import cm
|
||||
|
||||
from pandas.plotting import radviz
|
||||
|
||||
df = iris
|
||||
ax = _check_plot_works(radviz, frame=df, class_column="Name", colormap=cm.jet)
|
||||
cmaps = [cm.jet(n) for n in np.linspace(0, 1, df["Name"].nunique())]
|
||||
patches = [p for p in ax.patches[:20] if p.get_label() != ""]
|
||||
_check_colors(patches, facecolors=cmaps, mapping=df["Name"][:10])
|
||||
|
||||
def test_radviz_colors_handles(self):
|
||||
from pandas.plotting import radviz
|
||||
|
||||
colors = [[0.0, 0.0, 1.0, 1.0], [0.0, 0.5, 1.0, 1.0], [1.0, 0.0, 0.0, 1.0]]
|
||||
df = DataFrame(
|
||||
{"A": [1, 2, 3], "B": [2, 1, 3], "C": [3, 2, 1], "Name": ["b", "g", "r"]}
|
||||
)
|
||||
ax = radviz(df, "Name", color=colors)
|
||||
handles, _ = ax.get_legend_handles_labels()
|
||||
_check_colors(handles, facecolors=colors)
|
||||
|
||||
def test_subplot_titles(self, iris):
|
||||
df = iris.drop("Name", axis=1).head()
|
||||
# Use the column names as the subplot titles
|
||||
title = list(df.columns)
|
||||
|
||||
# Case len(title) == len(df)
|
||||
plot = df.plot(subplots=True, title=title)
|
||||
assert [p.get_title() for p in plot] == title
|
||||
|
||||
def test_subplot_titles_too_much(self, iris):
|
||||
df = iris.drop("Name", axis=1).head()
|
||||
# Use the column names as the subplot titles
|
||||
title = list(df.columns)
|
||||
# Case len(title) > len(df)
|
||||
msg = (
|
||||
"The length of `title` must equal the number of columns if "
|
||||
"using `title` of type `list` and `subplots=True`"
|
||||
)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.plot(subplots=True, title=title + ["kittens > puppies"])
|
||||
|
||||
def test_subplot_titles_too_little(self, iris):
|
||||
df = iris.drop("Name", axis=1).head()
|
||||
# Use the column names as the subplot titles
|
||||
title = list(df.columns)
|
||||
msg = (
|
||||
"The length of `title` must equal the number of columns if "
|
||||
"using `title` of type `list` and `subplots=True`"
|
||||
)
|
||||
# Case len(title) < len(df)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.plot(subplots=True, title=title[:2])
|
||||
|
||||
def test_subplot_titles_subplots_false(self, iris):
|
||||
df = iris.drop("Name", axis=1).head()
|
||||
# Use the column names as the subplot titles
|
||||
title = list(df.columns)
|
||||
# Case subplots=False and title is of type list
|
||||
msg = (
|
||||
"Using `title` of type `list` is not supported unless "
|
||||
"`subplots=True` is passed"
|
||||
)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.plot(subplots=False, title=title)
|
||||
|
||||
def test_subplot_titles_numeric_square_layout(self, iris):
|
||||
df = iris.drop("Name", axis=1).head()
|
||||
# Use the column names as the subplot titles
|
||||
title = list(df.columns)
|
||||
# Case df with 3 numeric columns but layout of (2,2)
|
||||
plot = df.drop("SepalWidth", axis=1).plot(
|
||||
subplots=True, layout=(2, 2), title=title[:-1]
|
||||
)
|
||||
title_list = [ax.get_title() for sublist in plot for ax in sublist]
|
||||
assert title_list == title[:3] + [""]
|
||||
|
||||
def test_get_standard_colors_random_seed(self):
|
||||
# GH17525
|
||||
df = DataFrame(np.zeros((10, 10)))
|
||||
|
||||
# Make sure that the random seed isn't reset by get_standard_colors
|
||||
plotting.parallel_coordinates(df, 0)
|
||||
rand1 = np.random.default_rng(None).random()
|
||||
plotting.parallel_coordinates(df, 0)
|
||||
rand2 = np.random.default_rng(None).random()
|
||||
assert rand1 != rand2
|
||||
|
||||
def test_get_standard_colors_consistency(self):
|
||||
# GH17525
|
||||
# Make sure it produces the same colors every time it's called
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
color1 = get_standard_colors(1, color_type="random")
|
||||
color2 = get_standard_colors(1, color_type="random")
|
||||
assert color1 == color2
|
||||
|
||||
def test_get_standard_colors_default_num_colors(self):
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
# Make sure the default color_types returns the specified amount
|
||||
color1 = get_standard_colors(1, color_type="default")
|
||||
color2 = get_standard_colors(9, color_type="default")
|
||||
color3 = get_standard_colors(20, color_type="default")
|
||||
assert len(color1) == 1
|
||||
assert len(color2) == 9
|
||||
assert len(color3) == 20
|
||||
|
||||
def test_plot_single_color(self):
|
||||
# Example from #20585. All 3 bars should have the same color
|
||||
df = DataFrame(
|
||||
{
|
||||
"account-start": ["2017-02-03", "2017-03-03", "2017-01-01"],
|
||||
"client": ["Alice Anders", "Bob Baker", "Charlie Chaplin"],
|
||||
"balance": [-1432.32, 10.43, 30000.00],
|
||||
"db-id": [1234, 2424, 251],
|
||||
"proxy-id": [525, 1525, 2542],
|
||||
"rank": [52, 525, 32],
|
||||
}
|
||||
)
|
||||
ax = df.client.value_counts().plot.bar()
|
||||
colors = [rect.get_facecolor() for rect in ax.get_children()[0:3]]
|
||||
assert all(color == colors[0] for color in colors)
|
||||
|
||||
def test_get_standard_colors_no_appending(self):
|
||||
# GH20726
|
||||
|
||||
# Make sure not to add more colors so that matplotlib can cycle
|
||||
# correctly.
|
||||
from matplotlib import cm
|
||||
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
color_before = cm.gnuplot(range(5))
|
||||
color_after = get_standard_colors(1, color=color_before)
|
||||
assert len(color_after) == len(color_before)
|
||||
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((48, 4)), columns=list("ABCD")
|
||||
)
|
||||
|
||||
color_list = cm.gnuplot(np.linspace(0, 1, 16))
|
||||
p = df.A.plot.bar(figsize=(16, 7), color=color_list)
|
||||
assert p.patches[1].get_facecolor() == p.patches[17].get_facecolor()
|
||||
|
||||
@pytest.mark.parametrize("kind", ["bar", "line"])
|
||||
def test_dictionary_color(self, kind):
|
||||
# issue-8193
|
||||
# Test plot color dictionary format
|
||||
data_files = ["a", "b"]
|
||||
|
||||
expected = [(0.5, 0.24, 0.6), (0.3, 0.7, 0.7)]
|
||||
|
||||
df1 = DataFrame(np.random.default_rng(2).random((2, 2)), columns=data_files)
|
||||
dic_color = {"b": (0.3, 0.7, 0.7), "a": (0.5, 0.24, 0.6)}
|
||||
|
||||
ax = df1.plot(kind=kind, color=dic_color)
|
||||
if kind == "bar":
|
||||
colors = [rect.get_facecolor()[0:-1] for rect in ax.get_children()[0:3:2]]
|
||||
else:
|
||||
colors = [rect.get_color() for rect in ax.get_lines()[0:2]]
|
||||
assert all(color == expected[index] for index, color in enumerate(colors))
|
||||
|
||||
def test_bar_plot(self):
|
||||
# GH38947
|
||||
# Test bar plot with string and int index
|
||||
from matplotlib.text import Text
|
||||
|
||||
expected = [Text(0, 0, "0"), Text(1, 0, "Total")]
|
||||
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": [1, 2],
|
||||
},
|
||||
index=Index([0, "Total"]),
|
||||
)
|
||||
plot_bar = df.plot.bar()
|
||||
assert all(
|
||||
(a.get_text() == b.get_text())
|
||||
for a, b in zip(plot_bar.get_xticklabels(), expected)
|
||||
)
|
||||
|
||||
def test_barh_plot_labels_mixed_integer_string(self):
|
||||
# GH39126
|
||||
# Test barh plot with string and integer at the same column
|
||||
from matplotlib.text import Text
|
||||
|
||||
df = DataFrame([{"word": 1, "value": 0}, {"word": "knowledge", "value": 2}])
|
||||
plot_barh = df.plot.barh(x="word", legend=None)
|
||||
expected_yticklabels = [Text(0, 0, "1"), Text(0, 1, "knowledge")]
|
||||
assert all(
|
||||
actual.get_text() == expected.get_text()
|
||||
for actual, expected in zip(
|
||||
plot_barh.get_yticklabels(), expected_yticklabels
|
||||
)
|
||||
)
|
||||
|
||||
def test_has_externally_shared_axis_x_axis(self):
|
||||
# GH33819
|
||||
# Test _has_externally_shared_axis() works for x-axis
|
||||
func = plotting._matplotlib.tools._has_externally_shared_axis
|
||||
|
||||
fig = mpl.pyplot.figure()
|
||||
plots = fig.subplots(2, 4)
|
||||
|
||||
# Create *externally* shared axes for first and third columns
|
||||
plots[0][0] = fig.add_subplot(231, sharex=plots[1][0])
|
||||
plots[0][2] = fig.add_subplot(233, sharex=plots[1][2])
|
||||
|
||||
# Create *internally* shared axes for second and third columns
|
||||
plots[0][1].twinx()
|
||||
plots[0][2].twinx()
|
||||
|
||||
# First column is only externally shared
|
||||
# Second column is only internally shared
|
||||
# Third column is both
|
||||
# Fourth column is neither
|
||||
assert func(plots[0][0], "x")
|
||||
assert not func(plots[0][1], "x")
|
||||
assert func(plots[0][2], "x")
|
||||
assert not func(plots[0][3], "x")
|
||||
|
||||
def test_has_externally_shared_axis_y_axis(self):
|
||||
# GH33819
|
||||
# Test _has_externally_shared_axis() works for y-axis
|
||||
func = plotting._matplotlib.tools._has_externally_shared_axis
|
||||
|
||||
fig = mpl.pyplot.figure()
|
||||
plots = fig.subplots(4, 2)
|
||||
|
||||
# Create *externally* shared axes for first and third rows
|
||||
plots[0][0] = fig.add_subplot(321, sharey=plots[0][1])
|
||||
plots[2][0] = fig.add_subplot(325, sharey=plots[2][1])
|
||||
|
||||
# Create *internally* shared axes for second and third rows
|
||||
plots[1][0].twiny()
|
||||
plots[2][0].twiny()
|
||||
|
||||
# First row is only externally shared
|
||||
# Second row is only internally shared
|
||||
# Third row is both
|
||||
# Fourth row is neither
|
||||
assert func(plots[0][0], "y")
|
||||
assert not func(plots[1][0], "y")
|
||||
assert func(plots[2][0], "y")
|
||||
assert not func(plots[3][0], "y")
|
||||
|
||||
def test_has_externally_shared_axis_invalid_compare_axis(self):
|
||||
# GH33819
|
||||
# Test _has_externally_shared_axis() raises an exception when
|
||||
# passed an invalid value as compare_axis parameter
|
||||
func = plotting._matplotlib.tools._has_externally_shared_axis
|
||||
|
||||
fig = mpl.pyplot.figure()
|
||||
plots = fig.subplots(4, 2)
|
||||
|
||||
# Create arbitrary axes
|
||||
plots[0][0] = fig.add_subplot(321, sharey=plots[0][1])
|
||||
|
||||
# Check that an invalid compare_axis value triggers the expected exception
|
||||
msg = "needs 'x' or 'y' as a second parameter"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
func(plots[0][0], "z")
|
||||
|
||||
def test_externally_shared_axes(self):
|
||||
# Example from GH33819
|
||||
# Create data
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": np.random.default_rng(2).standard_normal(1000),
|
||||
"b": np.random.default_rng(2).standard_normal(1000),
|
||||
}
|
||||
)
|
||||
|
||||
# Create figure
|
||||
fig = mpl.pyplot.figure()
|
||||
plots = fig.subplots(2, 3)
|
||||
|
||||
# Create *externally* shared axes
|
||||
plots[0][0] = fig.add_subplot(231, sharex=plots[1][0])
|
||||
# note: no plots[0][1] that's the twin only case
|
||||
plots[0][2] = fig.add_subplot(233, sharex=plots[1][2])
|
||||
|
||||
# Create *internally* shared axes
|
||||
# note: no plots[0][0] that's the external only case
|
||||
twin_ax1 = plots[0][1].twinx()
|
||||
twin_ax2 = plots[0][2].twinx()
|
||||
|
||||
# Plot data to primary axes
|
||||
df["a"].plot(ax=plots[0][0], title="External share only").set_xlabel(
|
||||
"this label should never be visible"
|
||||
)
|
||||
df["a"].plot(ax=plots[1][0])
|
||||
|
||||
df["a"].plot(ax=plots[0][1], title="Internal share (twin) only").set_xlabel(
|
||||
"this label should always be visible"
|
||||
)
|
||||
df["a"].plot(ax=plots[1][1])
|
||||
|
||||
df["a"].plot(ax=plots[0][2], title="Both").set_xlabel(
|
||||
"this label should never be visible"
|
||||
)
|
||||
df["a"].plot(ax=plots[1][2])
|
||||
|
||||
# Plot data to twinned axes
|
||||
df["b"].plot(ax=twin_ax1, color="green")
|
||||
df["b"].plot(ax=twin_ax2, color="yellow")
|
||||
|
||||
assert not plots[0][0].xaxis.get_label().get_visible()
|
||||
assert plots[0][1].xaxis.get_label().get_visible()
|
||||
assert not plots[0][2].xaxis.get_label().get_visible()
|
||||
|
||||
def test_plot_bar_axis_units_timestamp_conversion(self):
|
||||
# GH 38736
|
||||
# Ensure string x-axis from the second plot will not be converted to datetime
|
||||
# due to axis data from first plot
|
||||
df = DataFrame(
|
||||
[1.0],
|
||||
index=[Timestamp("2022-02-22 22:22:22")],
|
||||
)
|
||||
_check_plot_works(df.plot)
|
||||
s = Series({"A": 1.0})
|
||||
_check_plot_works(s.plot.bar)
|
||||
|
||||
def test_bar_plt_xaxis_intervalrange(self):
|
||||
# GH 38969
|
||||
# Ensure IntervalIndex x-axis produces a bar plot as expected
|
||||
from matplotlib.text import Text
|
||||
|
||||
expected = [Text(0, 0, "([0, 1],)"), Text(1, 0, "([1, 2],)")]
|
||||
s = Series(
|
||||
[1, 2],
|
||||
index=[interval_range(0, 2, closed="both")],
|
||||
)
|
||||
_check_plot_works(s.plot.bar)
|
||||
assert all(
|
||||
(a.get_text() == b.get_text())
|
||||
for a, b in zip(s.plot.bar().get_xticklabels(), expected)
|
||||
)
|
@ -0,0 +1,985 @@
|
||||
""" Test cases for Series.plot """
|
||||
from datetime import datetime
|
||||
from itertools import chain
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pandas.compat import is_platform_linux
|
||||
from pandas.compat.numpy import np_version_gte1p24
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
import pandas as pd
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Series,
|
||||
date_range,
|
||||
period_range,
|
||||
plotting,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
_check_ax_scales,
|
||||
_check_axes_shape,
|
||||
_check_colors,
|
||||
_check_grid_settings,
|
||||
_check_has_errorbars,
|
||||
_check_legend_labels,
|
||||
_check_plot_works,
|
||||
_check_text_labels,
|
||||
_check_ticks_props,
|
||||
_unpack_cycler,
|
||||
get_y_axis,
|
||||
)
|
||||
|
||||
mpl = pytest.importorskip("matplotlib")
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ts():
|
||||
return Series(
|
||||
np.arange(10, dtype=np.float64),
|
||||
index=date_range("2020-01-01", periods=10),
|
||||
name="ts",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def series():
|
||||
return Series(
|
||||
range(20), dtype=np.float64, name="series", index=[f"i_{i}" for i in range(20)]
|
||||
)
|
||||
|
||||
|
||||
class TestSeriesPlots:
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("kwargs", [{"label": "foo"}, {"use_index": False}])
|
||||
def test_plot(self, ts, kwargs):
|
||||
_check_plot_works(ts.plot, **kwargs)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_plot_tick_props(self, ts):
|
||||
axes = _check_plot_works(ts.plot, rot=0)
|
||||
_check_ticks_props(axes, xrot=0)
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"scale, exp_scale",
|
||||
[
|
||||
[{"logy": True}, {"yaxis": "log"}],
|
||||
[{"logx": True}, {"xaxis": "log"}],
|
||||
[{"loglog": True}, {"xaxis": "log", "yaxis": "log"}],
|
||||
],
|
||||
)
|
||||
def test_plot_scales(self, ts, scale, exp_scale):
|
||||
ax = _check_plot_works(ts.plot, style=".", **scale)
|
||||
_check_ax_scales(ax, **exp_scale)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_plot_ts_bar(self, ts):
|
||||
_check_plot_works(ts[:10].plot.bar)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_plot_ts_area_stacked(self, ts):
|
||||
_check_plot_works(ts.plot.area, stacked=False)
|
||||
|
||||
def test_plot_iseries(self):
|
||||
ser = Series(range(5), period_range("2020-01-01", periods=5))
|
||||
_check_plot_works(ser.plot)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kind",
|
||||
[
|
||||
"line",
|
||||
"bar",
|
||||
"barh",
|
||||
pytest.param("kde", marks=td.skip_if_no("scipy")),
|
||||
"hist",
|
||||
"box",
|
||||
],
|
||||
)
|
||||
def test_plot_series_kinds(self, series, kind):
|
||||
_check_plot_works(series[:5].plot, kind=kind)
|
||||
|
||||
def test_plot_series_barh(self, series):
|
||||
_check_plot_works(series[:10].plot.barh)
|
||||
|
||||
def test_plot_series_bar_ax(self):
|
||||
ax = _check_plot_works(
|
||||
Series(np.random.default_rng(2).standard_normal(10)).plot.bar, color="black"
|
||||
)
|
||||
_check_colors([ax.patches[0]], facecolors=["black"])
|
||||
|
||||
@pytest.mark.parametrize("kwargs", [{}, {"layout": (-1, 1)}, {"layout": (1, -1)}])
|
||||
def test_plot_6951(self, ts, kwargs):
|
||||
# GH 6951
|
||||
ax = _check_plot_works(ts.plot, subplots=True, **kwargs)
|
||||
_check_axes_shape(ax, axes_num=1, layout=(1, 1))
|
||||
|
||||
def test_plot_figsize_and_title(self, series):
|
||||
# figsize and title
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = series.plot(title="Test", figsize=(16, 8), ax=ax)
|
||||
_check_text_labels(ax.title, "Test")
|
||||
_check_axes_shape(ax, axes_num=1, layout=(1, 1), figsize=(16, 8))
|
||||
|
||||
def test_dont_modify_rcParams(self):
|
||||
# GH 8242
|
||||
key = "axes.prop_cycle"
|
||||
colors = mpl.pyplot.rcParams[key]
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
Series([1, 2, 3]).plot(ax=ax)
|
||||
assert colors == mpl.pyplot.rcParams[key]
|
||||
|
||||
@pytest.mark.parametrize("kwargs", [{}, {"secondary_y": True}])
|
||||
def test_ts_line_lim(self, ts, kwargs):
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot(ax=ax, **kwargs)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
lines = ax.get_lines()
|
||||
assert xmin <= lines[0].get_data(orig=False)[0][0]
|
||||
assert xmax >= lines[0].get_data(orig=False)[0][-1]
|
||||
|
||||
def test_ts_area_lim(self, ts):
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot.area(stacked=False, ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
line = ax.get_lines()[0].get_data(orig=False)[0]
|
||||
assert xmin <= line[0]
|
||||
assert xmax >= line[-1]
|
||||
_check_ticks_props(ax, xrot=0)
|
||||
|
||||
def test_ts_area_lim_xcompat(self, ts):
|
||||
# GH 7471
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot.area(stacked=False, x_compat=True, ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
line = ax.get_lines()[0].get_data(orig=False)[0]
|
||||
assert xmin <= line[0]
|
||||
assert xmax >= line[-1]
|
||||
_check_ticks_props(ax, xrot=30)
|
||||
|
||||
def test_ts_tz_area_lim_xcompat(self, ts):
|
||||
tz_ts = ts.copy()
|
||||
tz_ts.index = tz_ts.tz_localize("GMT").tz_convert("CET")
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = tz_ts.plot.area(stacked=False, x_compat=True, ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
line = ax.get_lines()[0].get_data(orig=False)[0]
|
||||
assert xmin <= line[0]
|
||||
assert xmax >= line[-1]
|
||||
_check_ticks_props(ax, xrot=0)
|
||||
|
||||
def test_ts_tz_area_lim_xcompat_secondary_y(self, ts):
|
||||
tz_ts = ts.copy()
|
||||
tz_ts.index = tz_ts.tz_localize("GMT").tz_convert("CET")
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = tz_ts.plot.area(stacked=False, secondary_y=True, ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
line = ax.get_lines()[0].get_data(orig=False)[0]
|
||||
assert xmin <= line[0]
|
||||
assert xmax >= line[-1]
|
||||
_check_ticks_props(ax, xrot=0)
|
||||
|
||||
def test_area_sharey_dont_overwrite(self, ts):
|
||||
# GH37942
|
||||
fig, (ax1, ax2) = mpl.pyplot.subplots(1, 2, sharey=True)
|
||||
|
||||
abs(ts).plot(ax=ax1, kind="area")
|
||||
abs(ts).plot(ax=ax2, kind="area")
|
||||
|
||||
assert get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert get_y_axis(ax2).joined(ax1, ax2)
|
||||
plt.close(fig)
|
||||
|
||||
def test_label(self):
|
||||
s = Series([1, 2])
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = s.plot(label="LABEL", legend=True, ax=ax)
|
||||
_check_legend_labels(ax, labels=["LABEL"])
|
||||
mpl.pyplot.close("all")
|
||||
|
||||
def test_label_none(self):
|
||||
s = Series([1, 2])
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = s.plot(legend=True, ax=ax)
|
||||
_check_legend_labels(ax, labels=[""])
|
||||
mpl.pyplot.close("all")
|
||||
|
||||
def test_label_ser_name(self):
|
||||
s = Series([1, 2], name="NAME")
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = s.plot(legend=True, ax=ax)
|
||||
_check_legend_labels(ax, labels=["NAME"])
|
||||
mpl.pyplot.close("all")
|
||||
|
||||
def test_label_ser_name_override(self):
|
||||
s = Series([1, 2], name="NAME")
|
||||
# override the default
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = s.plot(legend=True, label="LABEL", ax=ax)
|
||||
_check_legend_labels(ax, labels=["LABEL"])
|
||||
mpl.pyplot.close("all")
|
||||
|
||||
def test_label_ser_name_override_dont_draw(self):
|
||||
s = Series([1, 2], name="NAME")
|
||||
# Add lebel info, but don't draw
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = s.plot(legend=False, label="LABEL", ax=ax)
|
||||
assert ax.get_legend() is None # Hasn't been drawn
|
||||
ax.legend() # draw it
|
||||
_check_legend_labels(ax, labels=["LABEL"])
|
||||
mpl.pyplot.close("all")
|
||||
|
||||
def test_boolean(self):
|
||||
# GH 23719
|
||||
s = Series([False, False, True])
|
||||
_check_plot_works(s.plot, include_bool=True)
|
||||
|
||||
msg = "no numeric data to plot"
|
||||
with pytest.raises(TypeError, match=msg):
|
||||
_check_plot_works(s.plot)
|
||||
|
||||
@pytest.mark.parametrize("index", [None, date_range("2020-01-01", periods=4)])
|
||||
def test_line_area_nan_series(self, index):
|
||||
values = [1, 2, np.nan, 3]
|
||||
d = Series(values, index=index)
|
||||
ax = _check_plot_works(d.plot)
|
||||
masked = ax.lines[0].get_ydata()
|
||||
# remove nan for comparison purpose
|
||||
exp = np.array([1, 2, 3], dtype=np.float64)
|
||||
tm.assert_numpy_array_equal(np.delete(masked.data, 2), exp)
|
||||
tm.assert_numpy_array_equal(masked.mask, np.array([False, False, True, False]))
|
||||
|
||||
expected = np.array([1, 2, 0, 3], dtype=np.float64)
|
||||
ax = _check_plot_works(d.plot, stacked=True)
|
||||
tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
|
||||
ax = _check_plot_works(d.plot.area)
|
||||
tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
|
||||
ax = _check_plot_works(d.plot.area, stacked=False)
|
||||
tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
|
||||
|
||||
def test_line_use_index_false(self):
|
||||
s = Series([1, 2, 3], index=["a", "b", "c"])
|
||||
s.index.name = "The Index"
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = s.plot(use_index=False, ax=ax)
|
||||
label = ax.get_xlabel()
|
||||
assert label == ""
|
||||
|
||||
def test_line_use_index_false_diff_var(self):
|
||||
s = Series([1, 2, 3], index=["a", "b", "c"])
|
||||
s.index.name = "The Index"
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax2 = s.plot.bar(use_index=False, ax=ax)
|
||||
label2 = ax2.get_xlabel()
|
||||
assert label2 == ""
|
||||
|
||||
@pytest.mark.xfail(
|
||||
np_version_gte1p24 and is_platform_linux(),
|
||||
reason="Weird rounding problems",
|
||||
strict=False,
|
||||
)
|
||||
@pytest.mark.parametrize("axis, meth", [("yaxis", "bar"), ("xaxis", "barh")])
|
||||
def test_bar_log(self, axis, meth):
|
||||
expected = np.array([1e-1, 1e0, 1e1, 1e2, 1e3, 1e4])
|
||||
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = getattr(Series([200, 500]).plot, meth)(log=True, ax=ax)
|
||||
tm.assert_numpy_array_equal(getattr(ax, axis).get_ticklocs(), expected)
|
||||
|
||||
@pytest.mark.xfail(
|
||||
np_version_gte1p24 and is_platform_linux(),
|
||||
reason="Weird rounding problems",
|
||||
strict=False,
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"axis, kind, res_meth",
|
||||
[["yaxis", "bar", "get_ylim"], ["xaxis", "barh", "get_xlim"]],
|
||||
)
|
||||
def test_bar_log_kind_bar(self, axis, kind, res_meth):
|
||||
# GH 9905
|
||||
expected = np.array([1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e0, 1e1])
|
||||
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = Series([0.1, 0.01, 0.001]).plot(log=True, kind=kind, ax=ax)
|
||||
ymin = 0.0007943282347242822
|
||||
ymax = 0.12589254117941673
|
||||
res = getattr(ax, res_meth)()
|
||||
tm.assert_almost_equal(res[0], ymin)
|
||||
tm.assert_almost_equal(res[1], ymax)
|
||||
tm.assert_numpy_array_equal(getattr(ax, axis).get_ticklocs(), expected)
|
||||
|
||||
def test_bar_ignore_index(self):
|
||||
df = Series([1, 2, 3, 4], index=["a", "b", "c", "d"])
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df.plot.bar(use_index=False, ax=ax)
|
||||
_check_text_labels(ax.get_xticklabels(), ["0", "1", "2", "3"])
|
||||
|
||||
def test_bar_user_colors(self):
|
||||
s = Series([1, 2, 3, 4])
|
||||
ax = s.plot.bar(color=["red", "blue", "blue", "red"])
|
||||
result = [p.get_facecolor() for p in ax.patches]
|
||||
expected = [
|
||||
(1.0, 0.0, 0.0, 1.0),
|
||||
(0.0, 0.0, 1.0, 1.0),
|
||||
(0.0, 0.0, 1.0, 1.0),
|
||||
(1.0, 0.0, 0.0, 1.0),
|
||||
]
|
||||
assert result == expected
|
||||
|
||||
def test_rotation_default(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
# Default rot 0
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
axes = df.plot(ax=ax)
|
||||
_check_ticks_props(axes, xrot=0)
|
||||
|
||||
def test_rotation_30(self):
|
||||
df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
axes = df.plot(rot=30, ax=ax)
|
||||
_check_ticks_props(axes, xrot=30)
|
||||
|
||||
def test_irregular_datetime(self):
|
||||
from pandas.plotting._matplotlib.converter import DatetimeConverter
|
||||
|
||||
rng = date_range("1/1/2000", "3/1/2000")
|
||||
rng = rng[[0, 1, 2, 3, 5, 9, 10, 11, 12]]
|
||||
ser = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ser.plot(ax=ax)
|
||||
xp = DatetimeConverter.convert(datetime(1999, 1, 1), "", ax)
|
||||
ax.set_xlim("1/1/1999", "1/1/2001")
|
||||
assert xp == ax.get_xlim()[0]
|
||||
_check_ticks_props(ax, xrot=30)
|
||||
|
||||
def test_unsorted_index_xlim(self):
|
||||
ser = Series(
|
||||
[0.0, 1.0, np.nan, 3.0, 4.0, 5.0, 6.0],
|
||||
index=[1.0, 0.0, 3.0, 2.0, np.nan, 3.0, 2.0],
|
||||
)
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ser.plot(ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
lines = ax.get_lines()
|
||||
assert xmin <= np.nanmin(lines[0].get_data(orig=False)[0])
|
||||
assert xmax >= np.nanmax(lines[0].get_data(orig=False)[0])
|
||||
|
||||
def test_pie_series(self):
|
||||
# if sum of values is less than 1.0, pie handle them as rate and draw
|
||||
# semicircle.
|
||||
series = Series(
|
||||
np.random.default_rng(2).integers(1, 5),
|
||||
index=["a", "b", "c", "d", "e"],
|
||||
name="YLABEL",
|
||||
)
|
||||
ax = _check_plot_works(series.plot.pie)
|
||||
_check_text_labels(ax.texts, series.index)
|
||||
assert ax.get_ylabel() == "YLABEL"
|
||||
|
||||
def test_pie_series_no_label(self):
|
||||
series = Series(
|
||||
np.random.default_rng(2).integers(1, 5),
|
||||
index=["a", "b", "c", "d", "e"],
|
||||
name="YLABEL",
|
||||
)
|
||||
ax = _check_plot_works(series.plot.pie, labels=None)
|
||||
_check_text_labels(ax.texts, [""] * 5)
|
||||
|
||||
def test_pie_series_less_colors_than_elements(self):
|
||||
series = Series(
|
||||
np.random.default_rng(2).integers(1, 5),
|
||||
index=["a", "b", "c", "d", "e"],
|
||||
name="YLABEL",
|
||||
)
|
||||
color_args = ["r", "g", "b"]
|
||||
ax = _check_plot_works(series.plot.pie, colors=color_args)
|
||||
|
||||
color_expected = ["r", "g", "b", "r", "g"]
|
||||
_check_colors(ax.patches, facecolors=color_expected)
|
||||
|
||||
def test_pie_series_labels_and_colors(self):
|
||||
series = Series(
|
||||
np.random.default_rng(2).integers(1, 5),
|
||||
index=["a", "b", "c", "d", "e"],
|
||||
name="YLABEL",
|
||||
)
|
||||
# with labels and colors
|
||||
labels = ["A", "B", "C", "D", "E"]
|
||||
color_args = ["r", "g", "b", "c", "m"]
|
||||
ax = _check_plot_works(series.plot.pie, labels=labels, colors=color_args)
|
||||
_check_text_labels(ax.texts, labels)
|
||||
_check_colors(ax.patches, facecolors=color_args)
|
||||
|
||||
def test_pie_series_autopct_and_fontsize(self):
|
||||
series = Series(
|
||||
np.random.default_rng(2).integers(1, 5),
|
||||
index=["a", "b", "c", "d", "e"],
|
||||
name="YLABEL",
|
||||
)
|
||||
color_args = ["r", "g", "b", "c", "m"]
|
||||
ax = _check_plot_works(
|
||||
series.plot.pie, colors=color_args, autopct="%.2f", fontsize=7
|
||||
)
|
||||
pcts = [f"{s*100:.2f}" for s in series.values / series.sum()]
|
||||
expected_texts = list(chain.from_iterable(zip(series.index, pcts)))
|
||||
_check_text_labels(ax.texts, expected_texts)
|
||||
for t in ax.texts:
|
||||
assert t.get_fontsize() == 7
|
||||
|
||||
def test_pie_series_negative_raises(self):
|
||||
# includes negative value
|
||||
series = Series([1, 2, 0, 4, -1], index=["a", "b", "c", "d", "e"])
|
||||
with pytest.raises(ValueError, match="pie plot doesn't allow negative values"):
|
||||
series.plot.pie()
|
||||
|
||||
def test_pie_series_nan(self):
|
||||
# includes nan
|
||||
series = Series([1, 2, np.nan, 4], index=["a", "b", "c", "d"], name="YLABEL")
|
||||
ax = _check_plot_works(series.plot.pie)
|
||||
_check_text_labels(ax.texts, ["a", "b", "", "d"])
|
||||
|
||||
def test_pie_nan(self):
|
||||
s = Series([1, np.nan, 1, 1])
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = s.plot.pie(legend=True, ax=ax)
|
||||
expected = ["0", "", "2", "3"]
|
||||
result = [x.get_text() for x in ax.texts]
|
||||
assert result == expected
|
||||
|
||||
def test_df_series_secondary_legend(self):
|
||||
# GH 9779
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 3)), columns=list("abc")
|
||||
)
|
||||
s = Series(np.random.default_rng(2).standard_normal(30), name="x")
|
||||
|
||||
# primary -> secondary (without passing ax)
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df.plot(ax=ax)
|
||||
s.plot(legend=True, secondary_y=True, ax=ax)
|
||||
# both legends are drawn on left ax
|
||||
# left and right axis must be visible
|
||||
_check_legend_labels(ax, labels=["a", "b", "c", "x (right)"])
|
||||
assert ax.get_yaxis().get_visible()
|
||||
assert ax.right_ax.get_yaxis().get_visible()
|
||||
|
||||
def test_df_series_secondary_legend_with_axes(self):
|
||||
# GH 9779
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 3)), columns=list("abc")
|
||||
)
|
||||
s = Series(np.random.default_rng(2).standard_normal(30), name="x")
|
||||
# primary -> secondary (with passing ax)
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df.plot(ax=ax)
|
||||
s.plot(ax=ax, legend=True, secondary_y=True)
|
||||
# both legends are drawn on left ax
|
||||
# left and right axis must be visible
|
||||
_check_legend_labels(ax, labels=["a", "b", "c", "x (right)"])
|
||||
assert ax.get_yaxis().get_visible()
|
||||
assert ax.right_ax.get_yaxis().get_visible()
|
||||
|
||||
def test_df_series_secondary_legend_both(self):
|
||||
# GH 9779
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 3)), columns=list("abc")
|
||||
)
|
||||
s = Series(np.random.default_rng(2).standard_normal(30), name="x")
|
||||
# secondary -> secondary (without passing ax)
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df.plot(secondary_y=True, ax=ax)
|
||||
s.plot(legend=True, secondary_y=True, ax=ax)
|
||||
# both legends are drawn on left ax
|
||||
# left axis must be invisible and right axis must be visible
|
||||
expected = ["a (right)", "b (right)", "c (right)", "x (right)"]
|
||||
_check_legend_labels(ax.left_ax, labels=expected)
|
||||
assert not ax.left_ax.get_yaxis().get_visible()
|
||||
assert ax.get_yaxis().get_visible()
|
||||
|
||||
def test_df_series_secondary_legend_both_with_axis(self):
|
||||
# GH 9779
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 3)), columns=list("abc")
|
||||
)
|
||||
s = Series(np.random.default_rng(2).standard_normal(30), name="x")
|
||||
# secondary -> secondary (with passing ax)
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df.plot(secondary_y=True, ax=ax)
|
||||
s.plot(ax=ax, legend=True, secondary_y=True)
|
||||
# both legends are drawn on left ax
|
||||
# left axis must be invisible and right axis must be visible
|
||||
expected = ["a (right)", "b (right)", "c (right)", "x (right)"]
|
||||
_check_legend_labels(ax.left_ax, expected)
|
||||
assert not ax.left_ax.get_yaxis().get_visible()
|
||||
assert ax.get_yaxis().get_visible()
|
||||
|
||||
def test_df_series_secondary_legend_both_with_axis_2(self):
|
||||
# GH 9779
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((30, 3)), columns=list("abc")
|
||||
)
|
||||
s = Series(np.random.default_rng(2).standard_normal(30), name="x")
|
||||
# secondary -> secondary (with passing ax)
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = df.plot(secondary_y=True, mark_right=False, ax=ax)
|
||||
s.plot(ax=ax, legend=True, secondary_y=True)
|
||||
# both legends are drawn on left ax
|
||||
# left axis must be invisible and right axis must be visible
|
||||
expected = ["a", "b", "c", "x (right)"]
|
||||
_check_legend_labels(ax.left_ax, expected)
|
||||
assert not ax.left_ax.get_yaxis().get_visible()
|
||||
assert ax.get_yaxis().get_visible()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_logy, expected_scale", [(True, "log"), ("sym", "symlog")]
|
||||
)
|
||||
def test_secondary_logy(self, input_logy, expected_scale):
|
||||
# GH 25545
|
||||
s1 = Series(np.random.default_rng(2).standard_normal(100))
|
||||
s2 = Series(np.random.default_rng(2).standard_normal(100))
|
||||
|
||||
# GH 24980
|
||||
ax1 = s1.plot(logy=input_logy)
|
||||
ax2 = s2.plot(secondary_y=True, logy=input_logy)
|
||||
|
||||
assert ax1.get_yscale() == expected_scale
|
||||
assert ax2.get_yscale() == expected_scale
|
||||
|
||||
def test_plot_fails_with_dupe_color_and_style(self):
|
||||
x = Series(np.random.default_rng(2).standard_normal(2))
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
msg = (
|
||||
"Cannot pass 'style' string with a color symbol and 'color' keyword "
|
||||
"argument. Please use one or the other or pass 'style' without a color "
|
||||
"symbol"
|
||||
)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
x.plot(style="k--", color="k", ax=ax)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bw_method, ind",
|
||||
[
|
||||
["scott", 20],
|
||||
[None, 20],
|
||||
[None, np.int_(20)],
|
||||
[0.5, np.linspace(-100, 100, 20)],
|
||||
],
|
||||
)
|
||||
def test_kde_kwargs(self, ts, bw_method, ind):
|
||||
pytest.importorskip("scipy")
|
||||
_check_plot_works(ts.plot.kde, bw_method=bw_method, ind=ind)
|
||||
|
||||
def test_density_kwargs(self, ts):
|
||||
pytest.importorskip("scipy")
|
||||
sample_points = np.linspace(-100, 100, 20)
|
||||
_check_plot_works(ts.plot.density, bw_method=0.5, ind=sample_points)
|
||||
|
||||
def test_kde_kwargs_check_axes(self, ts):
|
||||
pytest.importorskip("scipy")
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
sample_points = np.linspace(-100, 100, 20)
|
||||
ax = ts.plot.kde(logy=True, bw_method=0.5, ind=sample_points, ax=ax)
|
||||
_check_ax_scales(ax, yaxis="log")
|
||||
_check_text_labels(ax.yaxis.get_label(), "Density")
|
||||
|
||||
def test_kde_missing_vals(self):
|
||||
pytest.importorskip("scipy")
|
||||
s = Series(np.random.default_rng(2).uniform(size=50))
|
||||
s[0] = np.nan
|
||||
axes = _check_plot_works(s.plot.kde)
|
||||
|
||||
# gh-14821: check if the values have any missing values
|
||||
assert any(~np.isnan(axes.lines[0].get_xdata()))
|
||||
|
||||
@pytest.mark.xfail(reason="Api changed in 3.6.0")
|
||||
def test_boxplot_series(self, ts):
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ts.plot.box(logy=True, ax=ax)
|
||||
_check_ax_scales(ax, yaxis="log")
|
||||
xlabels = ax.get_xticklabels()
|
||||
_check_text_labels(xlabels, [ts.name])
|
||||
ylabels = ax.get_yticklabels()
|
||||
_check_text_labels(ylabels, [""] * len(ylabels))
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kind",
|
||||
plotting.PlotAccessor._common_kinds + plotting.PlotAccessor._series_kinds,
|
||||
)
|
||||
def test_kind_kwarg(self, kind):
|
||||
pytest.importorskip("scipy")
|
||||
s = Series(range(3))
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
s.plot(kind=kind, ax=ax)
|
||||
mpl.pyplot.close()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"kind",
|
||||
plotting.PlotAccessor._common_kinds + plotting.PlotAccessor._series_kinds,
|
||||
)
|
||||
def test_kind_attr(self, kind):
|
||||
pytest.importorskip("scipy")
|
||||
s = Series(range(3))
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
getattr(s.plot, kind)()
|
||||
mpl.pyplot.close()
|
||||
|
||||
@pytest.mark.parametrize("kind", plotting.PlotAccessor._common_kinds)
|
||||
def test_invalid_plot_data(self, kind):
|
||||
s = Series(list("abcd"))
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
msg = "no numeric data to plot"
|
||||
with pytest.raises(TypeError, match=msg):
|
||||
s.plot(kind=kind, ax=ax)
|
||||
|
||||
@pytest.mark.parametrize("kind", plotting.PlotAccessor._common_kinds)
|
||||
def test_valid_object_plot(self, kind):
|
||||
pytest.importorskip("scipy")
|
||||
s = Series(range(10), dtype=object)
|
||||
_check_plot_works(s.plot, kind=kind)
|
||||
|
||||
@pytest.mark.parametrize("kind", plotting.PlotAccessor._common_kinds)
|
||||
def test_partially_invalid_plot_data(self, kind):
|
||||
s = Series(["a", "b", 1.0, 2])
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
msg = "no numeric data to plot"
|
||||
with pytest.raises(TypeError, match=msg):
|
||||
s.plot(kind=kind, ax=ax)
|
||||
|
||||
def test_invalid_kind(self):
|
||||
s = Series([1, 2])
|
||||
with pytest.raises(ValueError, match="invalid_kind is not a valid plot kind"):
|
||||
s.plot(kind="invalid_kind")
|
||||
|
||||
def test_dup_datetime_index_plot(self):
|
||||
dr1 = date_range("1/1/2009", periods=4)
|
||||
dr2 = date_range("1/2/2009", periods=4)
|
||||
index = dr1.append(dr2)
|
||||
values = np.random.default_rng(2).standard_normal(index.size)
|
||||
s = Series(values, index=index)
|
||||
_check_plot_works(s.plot)
|
||||
|
||||
def test_errorbar_asymmetrical(self):
|
||||
# GH9536
|
||||
s = Series(np.arange(10), name="x")
|
||||
err = np.random.default_rng(2).random((2, 10))
|
||||
|
||||
ax = s.plot(yerr=err, xerr=err)
|
||||
|
||||
result = np.vstack([i.vertices[:, 1] for i in ax.collections[1].get_paths()])
|
||||
expected = (err.T * np.array([-1, 1])) + s.to_numpy().reshape(-1, 1)
|
||||
tm.assert_numpy_array_equal(result, expected)
|
||||
|
||||
msg = (
|
||||
"Asymmetrical error bars should be provided "
|
||||
f"with the shape \\(2, {len(s)}\\)"
|
||||
)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
s.plot(yerr=np.random.default_rng(2).random((2, 11)))
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("kind", ["line", "bar"])
|
||||
@pytest.mark.parametrize(
|
||||
"yerr",
|
||||
[
|
||||
Series(np.abs(np.random.default_rng(2).standard_normal(10))),
|
||||
np.abs(np.random.default_rng(2).standard_normal(10)),
|
||||
list(np.abs(np.random.default_rng(2).standard_normal(10))),
|
||||
DataFrame(
|
||||
np.abs(np.random.default_rng(2).standard_normal((10, 2))),
|
||||
columns=["x", "y"],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_errorbar_plot(self, kind, yerr):
|
||||
s = Series(np.arange(10), name="x")
|
||||
ax = _check_plot_works(s.plot, yerr=yerr, kind=kind)
|
||||
_check_has_errorbars(ax, xerr=0, yerr=1)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_errorbar_plot_yerr_0(self):
|
||||
s = Series(np.arange(10), name="x")
|
||||
s_err = np.abs(np.random.default_rng(2).standard_normal(10))
|
||||
ax = _check_plot_works(s.plot, xerr=s_err)
|
||||
_check_has_errorbars(ax, xerr=1, yerr=0)
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize(
|
||||
"yerr",
|
||||
[
|
||||
Series(np.abs(np.random.default_rng(2).standard_normal(12))),
|
||||
DataFrame(
|
||||
np.abs(np.random.default_rng(2).standard_normal((12, 2))),
|
||||
columns=["x", "y"],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_errorbar_plot_ts(self, yerr):
|
||||
# test time series plotting
|
||||
ix = date_range("1/1/2000", "1/1/2001", freq="ME")
|
||||
ts = Series(np.arange(12), index=ix, name="x")
|
||||
yerr.index = ix
|
||||
|
||||
ax = _check_plot_works(ts.plot, yerr=yerr)
|
||||
_check_has_errorbars(ax, xerr=0, yerr=1)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_errorbar_plot_invalid_yerr_shape(self):
|
||||
s = Series(np.arange(10), name="x")
|
||||
# check incorrect lengths and types
|
||||
with tm.external_error_raised(ValueError):
|
||||
s.plot(yerr=np.arange(11))
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_errorbar_plot_invalid_yerr(self):
|
||||
s = Series(np.arange(10), name="x")
|
||||
s_err = ["zzz"] * 10
|
||||
with tm.external_error_raised(TypeError):
|
||||
s.plot(yerr=s_err)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_table_true(self, series):
|
||||
_check_plot_works(series.plot, table=True)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_table_self(self, series):
|
||||
_check_plot_works(series.plot, table=series)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_series_grid_settings(self):
|
||||
# Make sure plot defaults to rcParams['axes.grid'] setting, GH 9792
|
||||
pytest.importorskip("scipy")
|
||||
_check_grid_settings(
|
||||
Series([1, 2, 3]),
|
||||
plotting.PlotAccessor._series_kinds + plotting.PlotAccessor._common_kinds,
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("c", ["r", "red", "green", "#FF0000"])
|
||||
def test_standard_colors(self, c):
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
result = get_standard_colors(1, color=c)
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(1, color=[c])
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(3, color=c)
|
||||
assert result == [c] * 3
|
||||
|
||||
result = get_standard_colors(3, color=[c])
|
||||
assert result == [c] * 3
|
||||
|
||||
def test_standard_colors_all(self):
|
||||
from matplotlib import colors
|
||||
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
# multiple colors like mediumaquamarine
|
||||
for c in colors.cnames:
|
||||
result = get_standard_colors(num_colors=1, color=c)
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(num_colors=1, color=[c])
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(num_colors=3, color=c)
|
||||
assert result == [c] * 3
|
||||
|
||||
result = get_standard_colors(num_colors=3, color=[c])
|
||||
assert result == [c] * 3
|
||||
|
||||
# single letter colors like k
|
||||
for c in colors.ColorConverter.colors:
|
||||
result = get_standard_colors(num_colors=1, color=c)
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(num_colors=1, color=[c])
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(num_colors=3, color=c)
|
||||
assert result == [c] * 3
|
||||
|
||||
result = get_standard_colors(num_colors=3, color=[c])
|
||||
assert result == [c] * 3
|
||||
|
||||
def test_series_plot_color_kwargs(self):
|
||||
# GH1890
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = Series(np.arange(12) + 1).plot(color="green", ax=ax)
|
||||
_check_colors(ax.get_lines(), linecolors=["green"])
|
||||
|
||||
def test_time_series_plot_color_kwargs(self):
|
||||
# #1890
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = Series(np.arange(12) + 1, index=date_range("1/1/2000", periods=12)).plot(
|
||||
color="green", ax=ax
|
||||
)
|
||||
_check_colors(ax.get_lines(), linecolors=["green"])
|
||||
|
||||
def test_time_series_plot_color_with_empty_kwargs(self):
|
||||
import matplotlib as mpl
|
||||
|
||||
def_colors = _unpack_cycler(mpl.rcParams)
|
||||
index = date_range("1/1/2000", periods=12)
|
||||
s = Series(np.arange(1, 13), index=index)
|
||||
|
||||
ncolors = 3
|
||||
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
for i in range(ncolors):
|
||||
ax = s.plot(ax=ax)
|
||||
_check_colors(ax.get_lines(), linecolors=def_colors[:ncolors])
|
||||
|
||||
def test_xticklabels(self):
|
||||
# GH11529
|
||||
s = Series(np.arange(10), index=[f"P{i:02d}" for i in range(10)])
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = s.plot(xticks=[0, 3, 5, 9], ax=ax)
|
||||
exp = [f"P{i:02d}" for i in [0, 3, 5, 9]]
|
||||
_check_text_labels(ax.get_xticklabels(), exp)
|
||||
|
||||
def test_xtick_barPlot(self):
|
||||
# GH28172
|
||||
s = Series(range(10), index=[f"P{i:02d}" for i in range(10)])
|
||||
ax = s.plot.bar(xticks=range(0, 11, 2))
|
||||
exp = np.array(list(range(0, 11, 2)))
|
||||
tm.assert_numpy_array_equal(exp, ax.get_xticks())
|
||||
|
||||
def test_custom_business_day_freq(self):
|
||||
# GH7222
|
||||
from pandas.tseries.offsets import CustomBusinessDay
|
||||
|
||||
s = Series(
|
||||
range(100, 121),
|
||||
index=pd.bdate_range(
|
||||
start="2014-05-01",
|
||||
end="2014-06-01",
|
||||
freq=CustomBusinessDay(holidays=["2014-05-26"]),
|
||||
),
|
||||
)
|
||||
|
||||
_check_plot_works(s.plot)
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="GH#24426, see also "
|
||||
"github.com/pandas-dev/pandas/commit/"
|
||||
"ef1bd69fa42bbed5d09dd17f08c44fc8bfc2b685#r61470674"
|
||||
)
|
||||
def test_plot_accessor_updates_on_inplace(self):
|
||||
ser = Series([1, 2, 3, 4])
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
ax = ser.plot(ax=ax)
|
||||
before = ax.xaxis.get_ticklocs()
|
||||
|
||||
ser.drop([0, 1], inplace=True)
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
after = ax.xaxis.get_ticklocs()
|
||||
tm.assert_numpy_array_equal(before, after)
|
||||
|
||||
@pytest.mark.parametrize("kind", ["line", "area"])
|
||||
def test_plot_xlim_for_series(self, kind):
|
||||
# test if xlim is also correctly plotted in Series for line and area
|
||||
# GH 27686
|
||||
s = Series([2, 3])
|
||||
_, ax = mpl.pyplot.subplots()
|
||||
s.plot(kind=kind, ax=ax)
|
||||
xlims = ax.get_xlim()
|
||||
|
||||
assert xlims[0] < 0
|
||||
assert xlims[1] > 1
|
||||
|
||||
def test_plot_no_rows(self):
|
||||
# GH 27758
|
||||
df = Series(dtype=int)
|
||||
assert df.empty
|
||||
ax = df.plot()
|
||||
assert len(ax.get_lines()) == 1
|
||||
line = ax.get_lines()[0]
|
||||
assert len(line.get_xdata()) == 0
|
||||
assert len(line.get_ydata()) == 0
|
||||
|
||||
def test_plot_no_numeric_data(self):
|
||||
df = Series(["a", "b", "c"])
|
||||
with pytest.raises(TypeError, match="no numeric data to plot"):
|
||||
df.plot()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data, index",
|
||||
[
|
||||
([1, 2, 3, 4], [3, 2, 1, 0]),
|
||||
([10, 50, 20, 30], [1910, 1920, 1980, 1950]),
|
||||
],
|
||||
)
|
||||
def test_plot_order(self, data, index):
|
||||
# GH38865 Verify plot order of a Series
|
||||
ser = Series(data=data, index=index)
|
||||
ax = ser.plot(kind="bar")
|
||||
|
||||
expected = ser.tolist()
|
||||
result = [
|
||||
patch.get_bbox().ymax
|
||||
for patch in sorted(ax.patches, key=lambda patch: patch.get_bbox().xmax)
|
||||
]
|
||||
assert expected == result
|
||||
|
||||
def test_style_single_ok(self):
|
||||
s = Series([1, 2])
|
||||
ax = s.plot(style="s", color="C3")
|
||||
assert ax.lines[0].get_color() == "C3"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index_name, old_label, new_label",
|
||||
[(None, "", "new"), ("old", "old", "new"), (None, "", "")],
|
||||
)
|
||||
@pytest.mark.parametrize("kind", ["line", "area", "bar", "barh", "hist"])
|
||||
def test_xlabel_ylabel_series(self, kind, index_name, old_label, new_label):
|
||||
# GH 9093
|
||||
ser = Series([1, 2, 3, 4])
|
||||
ser.index.name = index_name
|
||||
|
||||
# default is the ylabel is not shown and xlabel is index name (reverse for barh)
|
||||
ax = ser.plot(kind=kind)
|
||||
if kind == "barh":
|
||||
assert ax.get_xlabel() == ""
|
||||
assert ax.get_ylabel() == old_label
|
||||
elif kind == "hist":
|
||||
assert ax.get_xlabel() == ""
|
||||
assert ax.get_ylabel() == "Frequency"
|
||||
else:
|
||||
assert ax.get_ylabel() == ""
|
||||
assert ax.get_xlabel() == old_label
|
||||
|
||||
# old xlabel will be overridden and assigned ylabel will be used as ylabel
|
||||
ax = ser.plot(kind=kind, ylabel=new_label, xlabel=new_label)
|
||||
assert ax.get_ylabel() == new_label
|
||||
assert ax.get_xlabel() == new_label
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index",
|
||||
[
|
||||
pd.timedelta_range(start=0, periods=2, freq="D"),
|
||||
[pd.Timedelta(days=1), pd.Timedelta(days=2)],
|
||||
],
|
||||
)
|
||||
def test_timedelta_index(self, index):
|
||||
# GH37454
|
||||
xlims = (3, 1)
|
||||
ax = Series([1, 2], index=index).plot(xlim=(xlims))
|
||||
assert ax.get_xlim() == (3, 1)
|
||||
|
||||
def test_series_none_color(self):
|
||||
# GH51953
|
||||
series = Series([1, 2, 3])
|
||||
ax = series.plot(color=None)
|
||||
expected = _unpack_cycler(mpl.pyplot.rcParams)[:1]
|
||||
_check_colors(ax.get_lines(), linecolors=expected)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_plot_no_warning(self, ts):
|
||||
# GH 55138
|
||||
# TODO(3.0): this can be removed once Period[B] deprecation is enforced
|
||||
with tm.assert_produces_warning(False):
|
||||
_ = ts.plot()
|
@ -0,0 +1,157 @@
|
||||
import pytest
|
||||
|
||||
from pandas import Series
|
||||
|
||||
pytest.importorskip("matplotlib")
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
|
||||
class TestGetStandardColors:
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected",
|
||||
[
|
||||
(3, ["red", "green", "blue"]),
|
||||
(5, ["red", "green", "blue", "red", "green"]),
|
||||
(7, ["red", "green", "blue", "red", "green", "blue", "red"]),
|
||||
(2, ["red", "green"]),
|
||||
(1, ["red"]),
|
||||
],
|
||||
)
|
||||
def test_default_colors_named_from_prop_cycle(self, num_colors, expected):
|
||||
import matplotlib as mpl
|
||||
from matplotlib.pyplot import cycler
|
||||
|
||||
mpl_params = {
|
||||
"axes.prop_cycle": cycler(color=["red", "green", "blue"]),
|
||||
}
|
||||
with mpl.rc_context(rc=mpl_params):
|
||||
result = get_standard_colors(num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected",
|
||||
[
|
||||
(1, ["b"]),
|
||||
(3, ["b", "g", "r"]),
|
||||
(4, ["b", "g", "r", "y"]),
|
||||
(5, ["b", "g", "r", "y", "b"]),
|
||||
(7, ["b", "g", "r", "y", "b", "g", "r"]),
|
||||
],
|
||||
)
|
||||
def test_default_colors_named_from_prop_cycle_string(self, num_colors, expected):
|
||||
import matplotlib as mpl
|
||||
from matplotlib.pyplot import cycler
|
||||
|
||||
mpl_params = {
|
||||
"axes.prop_cycle": cycler(color="bgry"),
|
||||
}
|
||||
with mpl.rc_context(rc=mpl_params):
|
||||
result = get_standard_colors(num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected_name",
|
||||
[
|
||||
(1, ["C0"]),
|
||||
(3, ["C0", "C1", "C2"]),
|
||||
(
|
||||
12,
|
||||
[
|
||||
"C0",
|
||||
"C1",
|
||||
"C2",
|
||||
"C3",
|
||||
"C4",
|
||||
"C5",
|
||||
"C6",
|
||||
"C7",
|
||||
"C8",
|
||||
"C9",
|
||||
"C0",
|
||||
"C1",
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_default_colors_named_undefined_prop_cycle(self, num_colors, expected_name):
|
||||
import matplotlib as mpl
|
||||
import matplotlib.colors as mcolors
|
||||
|
||||
with mpl.rc_context(rc={}):
|
||||
expected = [mcolors.to_hex(x) for x in expected_name]
|
||||
result = get_standard_colors(num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected",
|
||||
[
|
||||
(1, ["red", "green", (0.1, 0.2, 0.3)]),
|
||||
(2, ["red", "green", (0.1, 0.2, 0.3)]),
|
||||
(3, ["red", "green", (0.1, 0.2, 0.3)]),
|
||||
(4, ["red", "green", (0.1, 0.2, 0.3), "red"]),
|
||||
],
|
||||
)
|
||||
def test_user_input_color_sequence(self, num_colors, expected):
|
||||
color = ["red", "green", (0.1, 0.2, 0.3)]
|
||||
result = get_standard_colors(color=color, num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected",
|
||||
[
|
||||
(1, ["r", "g", "b", "k"]),
|
||||
(2, ["r", "g", "b", "k"]),
|
||||
(3, ["r", "g", "b", "k"]),
|
||||
(4, ["r", "g", "b", "k"]),
|
||||
(5, ["r", "g", "b", "k", "r"]),
|
||||
(6, ["r", "g", "b", "k", "r", "g"]),
|
||||
],
|
||||
)
|
||||
def test_user_input_color_string(self, num_colors, expected):
|
||||
color = "rgbk"
|
||||
result = get_standard_colors(color=color, num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected",
|
||||
[
|
||||
(1, [(0.1, 0.2, 0.3)]),
|
||||
(2, [(0.1, 0.2, 0.3), (0.1, 0.2, 0.3)]),
|
||||
(3, [(0.1, 0.2, 0.3), (0.1, 0.2, 0.3), (0.1, 0.2, 0.3)]),
|
||||
],
|
||||
)
|
||||
def test_user_input_color_floats(self, num_colors, expected):
|
||||
color = (0.1, 0.2, 0.3)
|
||||
result = get_standard_colors(color=color, num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"color, num_colors, expected",
|
||||
[
|
||||
("Crimson", 1, ["Crimson"]),
|
||||
("DodgerBlue", 2, ["DodgerBlue", "DodgerBlue"]),
|
||||
("firebrick", 3, ["firebrick", "firebrick", "firebrick"]),
|
||||
],
|
||||
)
|
||||
def test_user_input_named_color_string(self, color, num_colors, expected):
|
||||
result = get_standard_colors(color=color, num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize("color", ["", [], (), Series([], dtype="object")])
|
||||
def test_empty_color_raises(self, color):
|
||||
with pytest.raises(ValueError, match="Invalid color argument"):
|
||||
get_standard_colors(color=color, num_colors=1)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"color",
|
||||
[
|
||||
"bad_color",
|
||||
("red", "green", "bad_color"),
|
||||
(0.1,),
|
||||
(0.1, 0.2),
|
||||
(0.1, 0.2, 0.3, 0.4, 0.5), # must be either 3 or 4 floats
|
||||
],
|
||||
)
|
||||
def test_bad_color_raises(self, color):
|
||||
with pytest.raises(ValueError, match="Invalid color"):
|
||||
get_standard_colors(color=color, num_colors=5)
|
Reference in New Issue
Block a user