Initial commit: 首次建仓,建立目录结构
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,579 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
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, strict=True):
|
||||
xpdata = xpl.get_xydata()
|
||||
rsdata = rsl.get_xydata()
|
||||
tm.assert_almost_equal(xpdata, rsdata)
|
||||
|
||||
|
||||
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, strict=True):
|
||||
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, strict=True):
|
||||
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, strict=True):
|
||||
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=None):
|
||||
# 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)
|
||||
|
||||
if kws is None:
|
||||
kws = {}
|
||||
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 assert_is_valid_plot_return_object(objs) -> None:
|
||||
from matplotlib.artist import Artist
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
if isinstance(objs, (Series, np.ndarray)):
|
||||
if isinstance(objs, Series):
|
||||
objs = objs._values
|
||||
for el in objs.reshape(-1):
|
||||
msg = (
|
||||
"one of 'objs' is not a matplotlib Axes instance, "
|
||||
f"type encountered {type(el).__name__!r}"
|
||||
)
|
||||
assert isinstance(el, (Axes, dict)), msg
|
||||
else:
|
||||
msg = (
|
||||
"objs is neither an ndarray of Artist instances nor a single "
|
||||
"ArtistArtist instance, tuple, or dict, 'objs' is a "
|
||||
f"{type(objs).__name__!r}"
|
||||
)
|
||||
assert isinstance(objs, (Artist, tuple, dict)), msg
|
||||
|
||||
|
||||
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
|
||||
fig = kwargs.get("figure", plt.gcf())
|
||||
fig.clf()
|
||||
|
||||
for ret in gen_plots(f, fig, **kwargs):
|
||||
assert_is_valid_plot_return_object(ret)
|
||||
|
||||
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,39 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
to_datetime,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def autouse_mpl_cleanup(mpl_cleanup):
|
||||
pass
|
||||
|
||||
|
||||
@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,734 @@
|
||||
"""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,
|
||||
)
|
||||
|
||||
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", list(range(10)))
|
||||
def test_mpl2_color_cycle_str(self, color):
|
||||
# GH 15516
|
||||
color = f"C{color}"
|
||||
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_color_and_style(self):
|
||||
color = {"g": "black", "h": "brown"}
|
||||
style = {"g": "-", "h": "--"}
|
||||
expected_color = ["black", "brown"]
|
||||
expected_style = ["-", "--"]
|
||||
df = DataFrame({"g": [1, 2], "h": [2, 3]}, index=[1, 2])
|
||||
ax = df.plot.line(color=color, style=style)
|
||||
color = [i.get_color() for i in ax.lines]
|
||||
style = [i.get_linestyle() for i in ax.lines]
|
||||
assert color == expected_color
|
||||
assert style == expected_style
|
||||
|
||||
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 len(np.unique(ax.collections[0].get_facecolor(), axis=0)) == 3 # r/g/b
|
||||
assert (
|
||||
np.unique(ax.collections[0].get_facecolor(), axis=0)
|
||||
== np.array(
|
||||
[[0.0, 0.0, 1.0, 1.0], [0.0, 0.5, 0.0, 1.0], [1.0, 0.0, 0.0, 1.0]]
|
||||
) # r/g/b
|
||||
).all()
|
||||
assert ax.collections[0].colorbar is None
|
||||
|
||||
def test_scatter_with_c_column_name_without_colors(self):
|
||||
# Given
|
||||
colors = ["NY", "MD", "MA", "CA"]
|
||||
color_count = 4 # 4 unique colors
|
||||
|
||||
# When
|
||||
df = DataFrame(
|
||||
{
|
||||
"dataX": range(100),
|
||||
"dataY": range(100),
|
||||
"color": (colors[i % len(colors)] for i in range(100)),
|
||||
}
|
||||
)
|
||||
|
||||
# Then
|
||||
ax = df.plot.scatter("dataX", "dataY", c="color")
|
||||
assert len(np.unique(ax.collections[0].get_facecolor(), axis=0)) == color_count
|
||||
|
||||
# Given
|
||||
colors = ["r", "g", "not-a-color"]
|
||||
color_count = 3
|
||||
# Also, since not all are mpl-colors, points matching 'r' or 'g'
|
||||
# are not necessarily red or green
|
||||
|
||||
# When
|
||||
df = DataFrame(
|
||||
{
|
||||
"dataX": range(100),
|
||||
"dataY": range(100),
|
||||
"color": (colors[i % len(colors)] for i in range(100)),
|
||||
}
|
||||
)
|
||||
|
||||
# Then
|
||||
ax = df.plot.scatter("dataX", "dataY", c="color")
|
||||
assert len(np.unique(ax.collections[0].get_facecolor(), axis=0)) == color_count
|
||||
|
||||
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):
|
||||
ax = df.plot.scatter(x="x", y="y", c="b")
|
||||
assert (
|
||||
len(np.unique(ax.collections[0].get_facecolor(), axis=0)) == 1
|
||||
) # blue
|
||||
assert (
|
||||
np.unique(ax.collections[0].get_facecolor(), axis=0)
|
||||
== np.array([[0.0, 0.0, 1.0, 1.0]])
|
||||
).all() # blue
|
||||
|
||||
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, strict=True):
|
||||
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), strict=True):
|
||||
_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), strict=True):
|
||||
_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, strict=True):
|
||||
_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, strict=True):
|
||||
_check_colors(ax.get_lines(), linecolors=[c])
|
||||
|
||||
def test_area_colors(self):
|
||||
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, mpl.collections.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):
|
||||
df = DataFrame(np.random.default_rng(2).random((5, 5)))
|
||||
ax = df.plot.area(colormap="jet")
|
||||
jet_colors = [mpl.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, mpl.collections.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):
|
||||
df = DataFrame(np.random.default_rng(2).random((5, 5)))
|
||||
jet_colors = [mpl.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=mpl.cm.jet, stacked=False)
|
||||
_check_colors(ax.get_lines(), linecolors=jet_colors)
|
||||
poly = [
|
||||
o
|
||||
for o in ax.get_children()
|
||||
if isinstance(o, mpl.collections.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), strict=True):
|
||||
_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, strict=True):
|
||||
_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, strict=True):
|
||||
_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()
|
||||
handles = legend.legend_handles
|
||||
for legend, line in zip(handles, result.lines, strict=True):
|
||||
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, strict=True):
|
||||
_check_visible(ax.get_yticklabels(), visible=exp)
|
||||
|
||||
def _assert_xtickslabels_visibility(self, axes, expected):
|
||||
for ax, exp in zip(axes, expected, strict=True):
|
||||
_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,262 @@
|
||||
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,
|
||||
)
|
||||
|
||||
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
|
||||
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()
|
||||
result_handles = legend.legend_handles
|
||||
|
||||
assert isinstance(result_handles[0], mpl.collections.LineCollection)
|
||||
assert isinstance(result_handles[1], mpl.lines.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()
|
||||
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,751 @@
|
||||
"""Test cases for DataFrame.plot"""
|
||||
|
||||
import string
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pandas.compat import is_platform_linux
|
||||
|
||||
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, strict=True):
|
||||
_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 layout 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, match="layout keyword is ignored"):
|
||||
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(
|
||||
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(
|
||||
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, match="sharex and sharey"):
|
||||
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, temp_file):
|
||||
# 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 temp_file.open(mode="wb") 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,100 @@
|
||||
import sys
|
||||
import types
|
||||
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
import pandas
|
||||
|
||||
pytestmark = pytest.mark.single_cpu
|
||||
|
||||
|
||||
@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,774 @@
|
||||
"""Test cases for .boxplot method"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
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.util.version import Version
|
||||
|
||||
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()
|
||||
|
||||
|
||||
if Version(mpl.__version__) < Version("3.10"):
|
||||
verts: list[dict[str, bool | str]] = [{"vert": False}, {"vert": True}]
|
||||
else:
|
||||
verts = [{"orientation": "horizontal"}, {"orientation": "vertical"}]
|
||||
|
||||
|
||||
@pytest.fixture(params=verts)
|
||||
def vert(request):
|
||||
return request.param
|
||||
|
||||
|
||||
class TestDataFramePlots:
|
||||
def test_stacked_boxplot_set_axis(self):
|
||||
# GH2980
|
||||
n = 30
|
||||
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, n, 10))
|
||||
plt.draw() # Update changes
|
||||
assert [int(x.get_text()) for x in ax.get_xticklabels()] == list(
|
||||
np.arange(0, n, 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()
|
||||
msg = "the figure containing the passed axes is being cleared"
|
||||
with tm.assert_produces_warning(UserWarning, match=msg):
|
||||
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=10),
|
||||
"b": np.random.default_rng(2).standard_normal(10),
|
||||
"c": np.random.default_rng(2).standard_normal(10) + 2,
|
||||
"d": date_range("2012-01-01", periods=10).astype(str),
|
||||
"e": date_range("2012-01-01", periods=10, tz="UTC"),
|
||||
"f": timedelta_range("1 days", periods=10),
|
||||
}
|
||||
)
|
||||
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)))
|
||||
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.filterwarnings("ignore:set_ticklabels:UserWarning")
|
||||
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", xlabel=xlabel, ylabel=ylabel, **vert)
|
||||
assert ax.get_xlabel() == xlabel
|
||||
assert ax.get_ylabel() == ylabel
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
|
||||
def test_plot_box(self, vert):
|
||||
# GH 54941
|
||||
rng = np.random.default_rng(2)
|
||||
df1 = DataFrame(rng.integers(0, 100, size=(10, 4)), columns=list("ABCD"))
|
||||
df2 = DataFrame(rng.integers(0, 100, size=(10, 4)), columns=list("ABCD"))
|
||||
|
||||
xlabel, ylabel = "x", "y"
|
||||
_, axs = plt.subplots(ncols=2, figsize=(10, 7), sharey=True)
|
||||
df1.plot.box(ax=axs[0], xlabel=xlabel, ylabel=ylabel, **vert)
|
||||
df2.plot.box(ax=axs[1], xlabel=xlabel, ylabel=ylabel, **vert)
|
||||
for ax in axs:
|
||||
assert ax.get_xlabel() == xlabel
|
||||
assert ax.get_ylabel() == ylabel
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
|
||||
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(xlabel=xlabel, ylabel=ylabel, **vert)
|
||||
assert ax.get_xlabel() == xlabel
|
||||
assert ax.get_ylabel() == ylabel
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
|
||||
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", xlabel=xlabel, ylabel=ylabel, **vert)
|
||||
for subplot in ax:
|
||||
assert subplot.get_xlabel() == xlabel
|
||||
assert subplot.get_ylabel() == ylabel
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
|
||||
def test_boxplot_group_no_xlabel_ylabel(self, vert, request):
|
||||
if Version(mpl.__version__) >= Version("3.10") and vert == {
|
||||
"orientation": "horizontal"
|
||||
}:
|
||||
request.applymarker(
|
||||
pytest.mark.xfail(reason=f"{vert} fails starting with matplotlib 3.10")
|
||||
)
|
||||
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)
|
||||
for subplot in ax:
|
||||
target_label = (
|
||||
subplot.get_xlabel()
|
||||
if vert in ({"vert": True}, {"orientation": "vertical"})
|
||||
else subplot.get_ylabel()
|
||||
)
|
||||
assert target_label == pprint_thing(["group"])
|
||||
|
||||
|
||||
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), strict=True)
|
||||
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), strict=True)
|
||||
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))
|
||||
|
||||
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, match="sharex and sharey"):
|
||||
_, 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, match="sharex and sharey"):
|
||||
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, match="sharex and sharey"):
|
||||
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"
|
||||
_, axes = mpl.pyplot.subplots(2, 3)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
# pass different number of axes from required
|
||||
with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
|
||||
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, strict=True))
|
||||
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
|
||||
|
||||
@pytest.mark.parametrize("group", ["X", ["X", "Y"]])
|
||||
def test_boxplot_multi_groupby_groups(self, group):
|
||||
# GH 14701
|
||||
rows = 20
|
||||
df = DataFrame(
|
||||
np.random.default_rng(12).normal(size=(rows, 2)), columns=["Col1", "Col2"]
|
||||
)
|
||||
df["X"] = Series(np.repeat(["A", "B"], int(rows / 2)))
|
||||
df["Y"] = Series(np.tile(["C", "D"], int(rows / 2)))
|
||||
grouped = df.groupby(group)
|
||||
_check_plot_works(df.boxplot, by=group, default_axes=True)
|
||||
_check_plot_works(df.plot.box, by=group, default_axes=True)
|
||||
_check_plot_works(grouped.boxplot, default_axes=True)
|
||||
@ -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,391 @@
|
||||
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,
|
||||
)
|
||||
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
dates = pytest.importorskip("matplotlib.dates")
|
||||
units = pytest.importorskip("matplotlib.units")
|
||||
|
||||
from pandas.plotting._matplotlib import converter
|
||||
|
||||
|
||||
@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):
|
||||
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)
|
||||
|
||||
def test_pandas_plots_register(self):
|
||||
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()
|
||||
|
||||
assert len(w) == 0
|
||||
|
||||
def test_matplotlib_formatters(self):
|
||||
# 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):
|
||||
s = Series(range(12), index=date_range("2017", periods=12))
|
||||
_, ax = plt.subplots()
|
||||
|
||||
# Test without registering first, no warning
|
||||
with cf.option_context("plotting.matplotlib.register_converters", False):
|
||||
ax.plot(s.index, s.values)
|
||||
|
||||
# Now test with registering
|
||||
register_matplotlib_converters()
|
||||
with cf.option_context("plotting.matplotlib.register_converters", False):
|
||||
ax.plot(s.index, s.values)
|
||||
|
||||
def test_registry_resets(self):
|
||||
# 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 = dates.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 = dates.date2num(values)
|
||||
tm.assert_numpy_array_equal(rs, xp)
|
||||
rs = dtc.convert(values[0], None, None)
|
||||
xp = dates.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 = dates.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, exp=9)
|
||||
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,154 @@
|
||||
"""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, strict=True):
|
||||
_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,957 @@
|
||||
"""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")
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
|
||||
from pandas.plotting._matplotlib.hist import _grouped_hist
|
||||
|
||||
|
||||
@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 explicitly 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):
|
||||
x = Series(np.random.default_rng(2).standard_normal(2))
|
||||
y = Series(np.random.default_rng(2).standard_normal(2))
|
||||
plt.subplot(121)
|
||||
x.hist()
|
||||
plt.subplot(122)
|
||||
y.hist()
|
||||
fig = plt.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):
|
||||
fig1 = plt.figure(1)
|
||||
fig2 = plt.figure(2)
|
||||
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 = 5 * ["1"] + 5 * ["2"]
|
||||
s = Series(np.random.default_rng(2).standard_normal(10), 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 = 5 * ["1"] + 5 * ["2"]
|
||||
s = Series(np.random.default_rng(2).standard_normal(10), 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):
|
||||
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, mpl.patches.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((10, 2)))
|
||||
df[2] = to_datetime(
|
||||
np.random.default_rng(2).integers(
|
||||
812419200000000000,
|
||||
819331200000000000,
|
||||
size=10,
|
||||
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=(10, 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(5 * ["1"] + 5 * ["2"], name="c")
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((10, 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, strict=True):
|
||||
_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(5 * ["1"] + 5 * ["2"], name="c")
|
||||
df = DataFrame(
|
||||
np.random.default_rng(2).standard_normal((10, 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((10, 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((10, 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((10, 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
|
||||
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, strict=True))
|
||||
|
||||
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"
|
||||
_, ax2 = mpl.pyplot.subplots()
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
no_nan_df.plot.hist(ax=ax2, weights=idxerror_weights)
|
||||
|
||||
|
||||
class TestDataFrameGroupByPlots:
|
||||
def test_grouped_hist_legacy(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 = _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):
|
||||
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, mpl.patches.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):
|
||||
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):
|
||||
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,866 @@
|
||||
"""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")
|
||||
|
||||
import re
|
||||
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
|
||||
@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):
|
||||
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(plotting.autocorrelation_plot, series=ser)
|
||||
_check_plot_works(plotting.autocorrelation_plot, series=ser.values)
|
||||
|
||||
ax = plotting.autocorrelation_plot(ser, label="Test")
|
||||
_check_legend_labels(ax, labels=["Test"])
|
||||
|
||||
@pytest.mark.parametrize("kwargs", [{}, {"lag": 5}])
|
||||
def test_lag_plot(self, kwargs):
|
||||
ser = Series(
|
||||
np.arange(10, dtype=np.float64),
|
||||
index=date_range("2020-01-01", periods=10),
|
||||
name="ts",
|
||||
)
|
||||
_check_plot_works(plotting.lag_plot, series=ser, **kwargs)
|
||||
|
||||
def test_bootstrap_plot(self):
|
||||
ser = Series(
|
||||
np.arange(10, dtype=np.float64),
|
||||
index=date_range("2020-01-01", periods=10),
|
||||
name="ts",
|
||||
)
|
||||
_check_plot_works(plotting.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((10, 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", "-1", "0"]
|
||||
_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((10, 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.25", "-1.0", "-0.75", "-0.5"]
|
||||
_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):
|
||||
# Ensure no UserWarning when making plot
|
||||
with tm.assert_produces_warning(None):
|
||||
_check_plot_works(plotting.andrews_curves, frame=iris, 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):
|
||||
if isinstance(df, str):
|
||||
df = request.getfixturevalue(df)
|
||||
ax = _check_plot_works(
|
||||
plotting.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):
|
||||
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(
|
||||
plotting.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):
|
||||
colors = ["b", "g", "r"]
|
||||
df = DataFrame({"A": [1, 2, 3], "B": [1, 2, 3], "C": [1, 2, 3], "Name": colors})
|
||||
ax = plotting.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):
|
||||
df = iris
|
||||
|
||||
ax = _check_plot_works(
|
||||
plotting.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):
|
||||
df = iris
|
||||
|
||||
ax = _check_plot_works(
|
||||
plotting.parallel_coordinates,
|
||||
frame=df,
|
||||
class_column="Name",
|
||||
colormap=cm.jet,
|
||||
)
|
||||
cmaps = [mpl.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):
|
||||
df = iris
|
||||
|
||||
ax = _check_plot_works(
|
||||
plotting.parallel_coordinates, frame=df, class_column="Name"
|
||||
)
|
||||
nlines = len(ax.get_lines())
|
||||
nxticks = len(ax.xaxis.get_ticklabels())
|
||||
|
||||
ax = _check_plot_works(
|
||||
plotting.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):
|
||||
df = iris
|
||||
colors = ["b", "g", "r"]
|
||||
df = DataFrame({"A": [1, 2, 3], "B": [1, 2, 3], "C": [1, 2, 3], "Name": colors})
|
||||
ax = plotting.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):
|
||||
# GH 15908
|
||||
df = DataFrame(
|
||||
{
|
||||
"feat": list(range(30)),
|
||||
"class": [2 for _ in range(10)]
|
||||
+ [3 for _ in range(10)]
|
||||
+ [1 for _ in range(10)],
|
||||
}
|
||||
)
|
||||
ax = plotting.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, strict=True
|
||||
)
|
||||
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:]),
|
||||
strict=True,
|
||||
)
|
||||
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):
|
||||
# Ensure no UserWarning when making plot
|
||||
with tm.assert_produces_warning(None):
|
||||
_check_plot_works(plotting.radviz, frame=iris, class_column="Name")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"color",
|
||||
[("#556270", "#4ECDC4", "#C7F464"), ["dodgerblue", "aquamarine", "seagreen"]],
|
||||
)
|
||||
def test_radviz_color(self, iris, color):
|
||||
df = iris
|
||||
ax = _check_plot_works(
|
||||
plotting.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):
|
||||
df = iris
|
||||
ax = _check_plot_works(
|
||||
plotting.radviz, frame=df, class_column="Name", colormap=cm.jet
|
||||
)
|
||||
cmaps = [mpl.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):
|
||||
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 = plotting.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
|
||||
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):
|
||||
# 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.
|
||||
color_before = mpl.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 = mpl.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
|
||||
expected = [mpl.text.Text(0, 0, "0"), mpl.text.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, strict=True)
|
||||
)
|
||||
|
||||
def test_barh_plot_labels_mixed_integer_string(self):
|
||||
# GH39126
|
||||
# Test barh plot with string and integer at the same column
|
||||
df = DataFrame([{"word": 1, "value": 0}, {"word": "knowledge", "value": 2}])
|
||||
plot_barh = df.plot.barh(x="word", legend=None)
|
||||
expected_yticklabels = [
|
||||
mpl.text.Text(0, 0, "1"),
|
||||
mpl.text.Text(0, 1, "knowledge"),
|
||||
]
|
||||
assert all(
|
||||
actual.get_text() == expected.get_text()
|
||||
for actual, expected in zip(
|
||||
plot_barh.get_yticklabels(), expected_yticklabels, strict=True
|
||||
)
|
||||
)
|
||||
|
||||
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(10),
|
||||
"b": np.random.default_rng(2).standard_normal(10),
|
||||
}
|
||||
)
|
||||
|
||||
# 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
|
||||
expected = [mpl.text.Text(0, 0, "([0, 1],)"), mpl.text.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, strict=True)
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def df_bar_data():
|
||||
return np.random.default_rng(3).integers(0, 100, 5)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def df_bar_df(df_bar_data) -> DataFrame:
|
||||
df_bar_df = DataFrame(
|
||||
{
|
||||
"A": df_bar_data,
|
||||
"B": df_bar_data[::-1],
|
||||
"C": df_bar_data[0],
|
||||
"D": df_bar_data[-1],
|
||||
}
|
||||
)
|
||||
return df_bar_df
|
||||
|
||||
|
||||
def _df_bar_xyheight_from_ax_helper(df_bar_data, ax, subplot_division):
|
||||
subplot_data_df_list = []
|
||||
|
||||
# get xy and height of squares representing data, separated by subplots
|
||||
for i in range(len(subplot_division)):
|
||||
subplot_data = np.array(
|
||||
[
|
||||
(x.get_x(), x.get_y(), x.get_height())
|
||||
for x in ax[i].findobj(plt.Rectangle)
|
||||
if x.get_height() in df_bar_data
|
||||
]
|
||||
)
|
||||
subplot_data_df_list.append(
|
||||
DataFrame(data=subplot_data, columns=["x_coord", "y_coord", "height"])
|
||||
)
|
||||
|
||||
return subplot_data_df_list
|
||||
|
||||
|
||||
def _df_bar_subplot_checker(df_bar_data, df_bar_df, subplot_data_df, subplot_columns):
|
||||
subplot_sliced_by_source = [
|
||||
subplot_data_df.iloc[
|
||||
len(df_bar_data) * i : len(df_bar_data) * (i + 1)
|
||||
].reset_index()
|
||||
for i in range(len(subplot_columns))
|
||||
]
|
||||
|
||||
if len(subplot_columns) == 1:
|
||||
expected_total_height = df_bar_df.loc[:, subplot_columns[0]]
|
||||
else:
|
||||
expected_total_height = df_bar_df.loc[:, subplot_columns].sum(axis=1)
|
||||
|
||||
for i in range(len(subplot_columns)):
|
||||
sliced_df = subplot_sliced_by_source[i]
|
||||
if i == 0:
|
||||
# Checks that the bar chart starts y=0
|
||||
assert (sliced_df["y_coord"] == 0).all()
|
||||
height_iter = sliced_df["y_coord"].add(sliced_df["height"])
|
||||
else:
|
||||
height_iter = height_iter + sliced_df["height"]
|
||||
|
||||
if i + 1 == len(subplot_columns):
|
||||
# Checks final height matches what is expected
|
||||
tm.assert_series_equal(
|
||||
height_iter, expected_total_height, check_names=False, check_dtype=False
|
||||
)
|
||||
else:
|
||||
# Checks each preceding bar ends where the next one starts
|
||||
next_start_coord = subplot_sliced_by_source[i + 1]["y_coord"]
|
||||
tm.assert_series_equal(
|
||||
height_iter, next_start_coord, check_names=False, check_dtype=False
|
||||
)
|
||||
|
||||
|
||||
# GH Issue 61018
|
||||
@pytest.mark.parametrize("columns_used", [["A", "B"], ["C", "D"], ["D", "A"]])
|
||||
def test_bar_1_subplot_1_double_stacked(df_bar_data, df_bar_df, columns_used):
|
||||
df_bar_df_trimmed = df_bar_df[columns_used]
|
||||
subplot_division = [columns_used]
|
||||
ax = df_bar_df_trimmed.plot(subplots=subplot_division, kind="bar", stacked=True)
|
||||
subplot_data_df_list = _df_bar_xyheight_from_ax_helper(
|
||||
df_bar_data, ax, subplot_division
|
||||
)
|
||||
for i in range(len(subplot_data_df_list)):
|
||||
_df_bar_subplot_checker(
|
||||
df_bar_data, df_bar_df_trimmed, subplot_data_df_list[i], subplot_division[i]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"columns_used", [["A", "B", "C"], ["A", "C", "B"], ["D", "A", "C"]]
|
||||
)
|
||||
def test_bar_2_subplot_1_double_stacked(df_bar_data, df_bar_df, columns_used):
|
||||
df_bar_df_trimmed = df_bar_df[columns_used]
|
||||
subplot_division = [(columns_used[0], columns_used[1]), (columns_used[2],)]
|
||||
ax = df_bar_df_trimmed.plot(subplots=subplot_division, kind="bar", stacked=True)
|
||||
subplot_data_df_list = _df_bar_xyheight_from_ax_helper(
|
||||
df_bar_data, ax, subplot_division
|
||||
)
|
||||
for i in range(len(subplot_data_df_list)):
|
||||
_df_bar_subplot_checker(
|
||||
df_bar_data, df_bar_df_trimmed, subplot_data_df_list[i], subplot_division[i]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"subplot_division",
|
||||
[
|
||||
[("A", "B"), ("C", "D")],
|
||||
[("A", "D"), ("C", "B")],
|
||||
[("B", "C"), ("D", "A")],
|
||||
[("B", "D"), ("C", "A")],
|
||||
],
|
||||
)
|
||||
def test_bar_2_subplot_2_double_stacked(df_bar_data, df_bar_df, subplot_division):
|
||||
ax = df_bar_df.plot(subplots=subplot_division, kind="bar", stacked=True)
|
||||
subplot_data_df_list = _df_bar_xyheight_from_ax_helper(
|
||||
df_bar_data, ax, subplot_division
|
||||
)
|
||||
for i in range(len(subplot_data_df_list)):
|
||||
_df_bar_subplot_checker(
|
||||
df_bar_data, df_bar_df, subplot_data_df_list[i], subplot_division[i]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"subplot_division",
|
||||
[[("A", "B", "C")], [("A", "D", "B")], [("C", "A", "D")], [("D", "C", "A")]],
|
||||
)
|
||||
def test_bar_2_subplots_1_triple_stacked(df_bar_data, df_bar_df, subplot_division):
|
||||
ax = df_bar_df.plot(subplots=subplot_division, kind="bar", stacked=True)
|
||||
subplot_data_df_list = _df_bar_xyheight_from_ax_helper(
|
||||
df_bar_data, ax, subplot_division
|
||||
)
|
||||
for i in range(len(subplot_data_df_list)):
|
||||
_df_bar_subplot_checker(
|
||||
df_bar_data, df_bar_df, subplot_data_df_list[i], subplot_division[i]
|
||||
)
|
||||
|
||||
|
||||
def test_bar_subplots_stacking_bool(df_bar_data, df_bar_df):
|
||||
subplot_division = [("A"), ("B"), ("C"), ("D")]
|
||||
ax = df_bar_df.plot(subplots=True, kind="bar", stacked=True)
|
||||
subplot_data_df_list = _df_bar_xyheight_from_ax_helper(
|
||||
df_bar_data, ax, subplot_division
|
||||
)
|
||||
for i in range(len(subplot_data_df_list)):
|
||||
_df_bar_subplot_checker(
|
||||
df_bar_data, df_bar_df, subplot_data_df_list[i], subplot_division[i]
|
||||
)
|
||||
|
||||
|
||||
def test_plot_bar_label_count_default():
|
||||
df = DataFrame(
|
||||
[(30, 10, 10, 10), (20, 20, 20, 20), (10, 30, 30, 10)], columns=list("ABCD")
|
||||
)
|
||||
df.plot(subplots=True, kind="bar", title=["A", "B", "C", "D"])
|
||||
|
||||
|
||||
def test_plot_bar_label_count_expected_fail():
|
||||
df = DataFrame(
|
||||
[(30, 10, 10, 10), (20, 20, 20, 20), (10, 30, 30, 10)], columns=list("ABCD")
|
||||
)
|
||||
error_regex = re.escape(
|
||||
"The number of titles (4) must equal the number of subplots (3)."
|
||||
)
|
||||
with pytest.raises(ValueError, match=error_regex):
|
||||
df.plot(
|
||||
subplots=[("A", "B")],
|
||||
kind="bar",
|
||||
title=["A&B", "C", "D", "Extra Title"],
|
||||
)
|
||||
|
||||
|
||||
def test_plot_bar_label_count_expected_success():
|
||||
df = DataFrame(
|
||||
[(30, 10, 10, 10), (20, 20, 20, 20), (10, 30, 30, 10)], columns=list("ABCD")
|
||||
)
|
||||
df.plot(subplots=[("A", "B", "D")], kind="bar", title=["A&B&D", "C"])
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,149 @@
|
||||
import pytest
|
||||
|
||||
from pandas import Series
|
||||
|
||||
mpl = pytest.importorskip("matplotlib")
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
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):
|
||||
mpl_params = {
|
||||
"axes.prop_cycle": plt.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):
|
||||
mpl_params = {
|
||||
"axes.prop_cycle": plt.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):
|
||||
with mpl.rc_context(rc={}):
|
||||
expected = [mpl.colors.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