first 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.
@@ -0,0 +1,663 @@
|
||||
"""
|
||||
Module consolidating common testing functions for checking plotting.
|
||||
|
||||
Currently all plotting tests are marked as slow via
|
||||
``pytestmark = pytest.mark.slow`` at the module level.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Sequence,
|
||||
)
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pandas.util._decorators import cache_readonly
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas.core.dtypes.api import is_list_like
|
||||
|
||||
import pandas as pd
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Series,
|
||||
to_datetime,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestPlotBase:
|
||||
"""
|
||||
This is a common base class used for various plotting tests
|
||||
"""
|
||||
|
||||
def setup_method(self, method):
|
||||
|
||||
import matplotlib as mpl
|
||||
|
||||
from pandas.plotting._matplotlib import compat
|
||||
|
||||
self.compat = compat
|
||||
|
||||
mpl.rcdefaults()
|
||||
|
||||
self.start_date_to_int64 = 812419200000000000
|
||||
self.end_date_to_int64 = 819331200000000000
|
||||
|
||||
self.mpl_ge_2_2_3 = compat.mpl_ge_2_2_3()
|
||||
self.mpl_ge_3_0_0 = compat.mpl_ge_3_0_0()
|
||||
self.mpl_ge_3_1_0 = compat.mpl_ge_3_1_0()
|
||||
self.mpl_ge_3_2_0 = compat.mpl_ge_3_2_0()
|
||||
|
||||
self.bp_n_objects = 7
|
||||
self.polycollection_factor = 2
|
||||
self.default_figsize = (6.4, 4.8)
|
||||
self.default_tick_position = "left"
|
||||
|
||||
n = 100
|
||||
with tm.RNGContext(42):
|
||||
gender = np.random.choice(["Male", "Female"], size=n)
|
||||
classroom = np.random.choice(["A", "B", "C"], size=n)
|
||||
|
||||
self.hist_df = DataFrame(
|
||||
{
|
||||
"gender": gender,
|
||||
"classroom": classroom,
|
||||
"height": np.random.normal(66, 4, size=n),
|
||||
"weight": np.random.normal(161, 32, size=n),
|
||||
"category": np.random.randint(4, size=n),
|
||||
"datetime": to_datetime(
|
||||
np.random.randint(
|
||||
self.start_date_to_int64,
|
||||
self.end_date_to_int64,
|
||||
size=n,
|
||||
dtype=np.int64,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
self.tdf = tm.makeTimeDataFrame()
|
||||
self.hexbin_df = DataFrame(
|
||||
{
|
||||
"A": np.random.uniform(size=20),
|
||||
"B": np.random.uniform(size=20),
|
||||
"C": np.arange(20) + np.random.uniform(size=20),
|
||||
}
|
||||
)
|
||||
|
||||
def teardown_method(self, method):
|
||||
tm.close()
|
||||
|
||||
@cache_readonly
|
||||
def plt(self):
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
return plt
|
||||
|
||||
@cache_readonly
|
||||
def colorconverter(self):
|
||||
import matplotlib.colors as colors
|
||||
|
||||
return colors.colorConverter
|
||||
|
||||
def _check_legend_labels(self, axes, labels=None, visible=True):
|
||||
"""
|
||||
Check each axes has expected legend labels
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axes : matplotlib Axes object, or its list-like
|
||||
labels : list-like
|
||||
expected legend labels
|
||||
visible : bool
|
||||
expected legend visibility. labels are checked only when visible is
|
||||
True
|
||||
"""
|
||||
if visible and (labels is None):
|
||||
raise ValueError("labels must be specified when visible is True")
|
||||
axes = self._flatten_visible(axes)
|
||||
for ax in axes:
|
||||
if visible:
|
||||
assert ax.get_legend() is not None
|
||||
self._check_text_labels(ax.get_legend().get_texts(), labels)
|
||||
else:
|
||||
assert ax.get_legend() is None
|
||||
|
||||
def _check_legend_marker(self, ax, expected_markers=None, visible=True):
|
||||
"""
|
||||
Check ax has expected legend markers
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ax : matplotlib Axes object
|
||||
expected_markers : list-like
|
||||
expected legend markers
|
||||
visible : bool
|
||||
expected legend visibility. labels are checked only when visible is
|
||||
True
|
||||
"""
|
||||
if visible and (expected_markers is None):
|
||||
raise ValueError("Markers must be specified when visible is True")
|
||||
if visible:
|
||||
handles, _ = ax.get_legend_handles_labels()
|
||||
markers = [handle.get_marker() for handle in handles]
|
||||
assert markers == expected_markers
|
||||
else:
|
||||
assert ax.get_legend() is None
|
||||
|
||||
def _check_data(self, xp, rs):
|
||||
"""
|
||||
Check each axes has identical lines
|
||||
|
||||
Parameters
|
||||
----------
|
||||
xp : matplotlib Axes object
|
||||
rs : matplotlib Axes object
|
||||
"""
|
||||
xp_lines = xp.get_lines()
|
||||
rs_lines = rs.get_lines()
|
||||
|
||||
def check_line(xpl, rsl):
|
||||
xpdata = xpl.get_xydata()
|
||||
rsdata = rsl.get_xydata()
|
||||
tm.assert_almost_equal(xpdata, rsdata)
|
||||
|
||||
assert len(xp_lines) == len(rs_lines)
|
||||
[check_line(xpl, rsl) for xpl, rsl in zip(xp_lines, rs_lines)]
|
||||
tm.close()
|
||||
|
||||
def _check_visible(self, collections, visible=True):
|
||||
"""
|
||||
Check each artist is visible or not
|
||||
|
||||
Parameters
|
||||
----------
|
||||
collections : matplotlib Artist or its list-like
|
||||
target Artist or its list or collection
|
||||
visible : bool
|
||||
expected visibility
|
||||
"""
|
||||
from matplotlib.collections import Collection
|
||||
|
||||
if not isinstance(collections, Collection) and not is_list_like(collections):
|
||||
collections = [collections]
|
||||
|
||||
for patch in collections:
|
||||
assert patch.get_visible() == visible
|
||||
|
||||
def _check_patches_all_filled(
|
||||
self, 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 = self._flatten_visible(axes)
|
||||
for ax in axes:
|
||||
for patch in ax.patches:
|
||||
assert patch.fill == filled
|
||||
|
||||
def _get_colors_mapped(self, series, colors):
|
||||
unique = series.unique()
|
||||
# unique and colors length can be differed
|
||||
# depending on slice value
|
||||
mapped = dict(zip(unique, colors))
|
||||
return [mapped[v] for v in series.values]
|
||||
|
||||
def _check_colors(
|
||||
self, collections, linecolors=None, facecolors=None, mapping=None
|
||||
):
|
||||
"""
|
||||
Check each artist has expected line colors and face colors
|
||||
|
||||
Parameters
|
||||
----------
|
||||
collections : list-like
|
||||
list or collection of target artist
|
||||
linecolors : list-like which has the same length as collections
|
||||
list of expected line colors
|
||||
facecolors : list-like which has the same length as collections
|
||||
list of expected face colors
|
||||
mapping : Series
|
||||
Series used for color grouping key
|
||||
used for andrew_curves, parallel_coordinates, radviz test
|
||||
"""
|
||||
from matplotlib.collections import (
|
||||
Collection,
|
||||
LineCollection,
|
||||
PolyCollection,
|
||||
)
|
||||
from matplotlib.lines import Line2D
|
||||
|
||||
conv = self.colorconverter
|
||||
if linecolors is not None:
|
||||
|
||||
if mapping is not None:
|
||||
linecolors = self._get_colors_mapped(mapping, linecolors)
|
||||
linecolors = linecolors[: len(collections)]
|
||||
|
||||
assert len(collections) == len(linecolors)
|
||||
for patch, color in zip(collections, linecolors):
|
||||
if isinstance(patch, Line2D):
|
||||
result = patch.get_color()
|
||||
# Line2D may contains string color expression
|
||||
result = conv.to_rgba(result)
|
||||
elif isinstance(patch, (PolyCollection, LineCollection)):
|
||||
result = tuple(patch.get_edgecolor()[0])
|
||||
else:
|
||||
result = patch.get_edgecolor()
|
||||
|
||||
expected = conv.to_rgba(color)
|
||||
assert result == expected
|
||||
|
||||
if facecolors is not None:
|
||||
|
||||
if mapping is not None:
|
||||
facecolors = self._get_colors_mapped(mapping, facecolors)
|
||||
facecolors = facecolors[: len(collections)]
|
||||
|
||||
assert len(collections) == len(facecolors)
|
||||
for patch, color in zip(collections, facecolors):
|
||||
if isinstance(patch, Collection):
|
||||
# returned as list of np.array
|
||||
result = patch.get_facecolor()[0]
|
||||
else:
|
||||
result = patch.get_facecolor()
|
||||
|
||||
if isinstance(result, np.ndarray):
|
||||
result = tuple(result)
|
||||
|
||||
expected = conv.to_rgba(color)
|
||||
assert result == expected
|
||||
|
||||
def _check_text_labels(self, texts, expected):
|
||||
"""
|
||||
Check each text has expected labels
|
||||
|
||||
Parameters
|
||||
----------
|
||||
texts : matplotlib Text object, or its list-like
|
||||
target text, or its list
|
||||
expected : str or list-like which has the same length as texts
|
||||
expected text label, or its list
|
||||
"""
|
||||
if not is_list_like(texts):
|
||||
assert texts.get_text() == expected
|
||||
else:
|
||||
labels = [t.get_text() for t in texts]
|
||||
assert len(labels) == len(expected)
|
||||
for label, e in zip(labels, expected):
|
||||
assert label == e
|
||||
|
||||
def _check_ticks_props(
|
||||
self, axes, xlabelsize=None, xrot=None, ylabelsize=None, yrot=None
|
||||
):
|
||||
"""
|
||||
Check each axes has expected tick properties
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axes : matplotlib Axes object, or its list-like
|
||||
xlabelsize : number
|
||||
expected xticks font size
|
||||
xrot : number
|
||||
expected xticks rotation
|
||||
ylabelsize : number
|
||||
expected yticks font size
|
||||
yrot : number
|
||||
expected yticks rotation
|
||||
"""
|
||||
from matplotlib.ticker import NullFormatter
|
||||
|
||||
axes = self._flatten_visible(axes)
|
||||
for ax in axes:
|
||||
if xlabelsize is not None or xrot is not None:
|
||||
if isinstance(ax.xaxis.get_minor_formatter(), NullFormatter):
|
||||
# If minor ticks has NullFormatter, rot / fontsize are not
|
||||
# retained
|
||||
labels = ax.get_xticklabels()
|
||||
else:
|
||||
labels = ax.get_xticklabels() + ax.get_xticklabels(minor=True)
|
||||
|
||||
for label in labels:
|
||||
if xlabelsize is not None:
|
||||
tm.assert_almost_equal(label.get_fontsize(), xlabelsize)
|
||||
if xrot is not None:
|
||||
tm.assert_almost_equal(label.get_rotation(), xrot)
|
||||
|
||||
if ylabelsize is not None or yrot is not None:
|
||||
if isinstance(ax.yaxis.get_minor_formatter(), NullFormatter):
|
||||
labels = ax.get_yticklabels()
|
||||
else:
|
||||
labels = ax.get_yticklabels() + ax.get_yticklabels(minor=True)
|
||||
|
||||
for label in labels:
|
||||
if ylabelsize is not None:
|
||||
tm.assert_almost_equal(label.get_fontsize(), ylabelsize)
|
||||
if yrot is not None:
|
||||
tm.assert_almost_equal(label.get_rotation(), yrot)
|
||||
|
||||
def _check_ax_scales(self, axes, xaxis="linear", yaxis="linear"):
|
||||
"""
|
||||
Check each axes has expected scales
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axes : matplotlib Axes object, or its list-like
|
||||
xaxis : {'linear', 'log'}
|
||||
expected xaxis scale
|
||||
yaxis : {'linear', 'log'}
|
||||
expected yaxis scale
|
||||
"""
|
||||
axes = self._flatten_visible(axes)
|
||||
for ax in axes:
|
||||
assert ax.xaxis.get_scale() == xaxis
|
||||
assert ax.yaxis.get_scale() == yaxis
|
||||
|
||||
def _check_axes_shape(self, axes, axes_num=None, layout=None, figsize=None):
|
||||
"""
|
||||
Check expected number of axes is drawn in expected layout
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axes : matplotlib Axes object, or its list-like
|
||||
axes_num : number
|
||||
expected number of axes. Unnecessary axes should be set to
|
||||
invisible.
|
||||
layout : tuple
|
||||
expected layout, (expected number of rows , columns)
|
||||
figsize : tuple
|
||||
expected figsize. default is matplotlib default
|
||||
"""
|
||||
from pandas.plotting._matplotlib.tools import flatten_axes
|
||||
|
||||
if figsize is None:
|
||||
figsize = self.default_figsize
|
||||
visible_axes = self._flatten_visible(axes)
|
||||
|
||||
if axes_num is not None:
|
||||
assert len(visible_axes) == axes_num
|
||||
for ax in visible_axes:
|
||||
# check something drawn on visible axes
|
||||
assert len(ax.get_children()) > 0
|
||||
|
||||
if layout is not None:
|
||||
result = self._get_axes_layout(flatten_axes(axes))
|
||||
assert result == layout
|
||||
|
||||
tm.assert_numpy_array_equal(
|
||||
visible_axes[0].figure.get_size_inches(),
|
||||
np.array(figsize, dtype=np.float64),
|
||||
)
|
||||
|
||||
def _get_axes_layout(self, axes):
|
||||
x_set = set()
|
||||
y_set = set()
|
||||
for ax in axes:
|
||||
# check axes coordinates to estimate layout
|
||||
points = ax.get_position().get_points()
|
||||
x_set.add(points[0][0])
|
||||
y_set.add(points[0][1])
|
||||
return (len(y_set), len(x_set))
|
||||
|
||||
def _flatten_visible(self, axes):
|
||||
"""
|
||||
Flatten axes, and filter only visible
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axes : matplotlib Axes object, or its list-like
|
||||
|
||||
"""
|
||||
from pandas.plotting._matplotlib.tools import flatten_axes
|
||||
|
||||
axes = flatten_axes(axes)
|
||||
axes = [ax for ax in axes if ax.get_visible()]
|
||||
return axes
|
||||
|
||||
def _check_has_errorbars(self, axes, xerr=0, yerr=0):
|
||||
"""
|
||||
Check axes has expected number of errorbars
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axes : matplotlib Axes object, or its list-like
|
||||
xerr : number
|
||||
expected number of x errorbar
|
||||
yerr : number
|
||||
expected number of y errorbar
|
||||
"""
|
||||
axes = self._flatten_visible(axes)
|
||||
for ax in axes:
|
||||
containers = ax.containers
|
||||
xerr_count = 0
|
||||
yerr_count = 0
|
||||
for c in containers:
|
||||
has_xerr = getattr(c, "has_xerr", False)
|
||||
has_yerr = getattr(c, "has_yerr", False)
|
||||
if has_xerr:
|
||||
xerr_count += 1
|
||||
if has_yerr:
|
||||
yerr_count += 1
|
||||
assert xerr == xerr_count
|
||||
assert yerr == yerr_count
|
||||
|
||||
def _check_box_return_type(
|
||||
self, returned, return_type, expected_keys=None, check_ax_title=True
|
||||
):
|
||||
"""
|
||||
Check box returned type is correct
|
||||
|
||||
Parameters
|
||||
----------
|
||||
returned : object to be tested, returned from boxplot
|
||||
return_type : str
|
||||
return_type passed to boxplot
|
||||
expected_keys : list-like, optional
|
||||
group labels in subplot case. If not passed,
|
||||
the function checks assuming boxplot uses single ax
|
||||
check_ax_title : bool
|
||||
Whether to check the ax.title is the same as expected_key
|
||||
Intended to be checked by calling from ``boxplot``.
|
||||
Normal ``plot`` doesn't attach ``ax.title``, it must be disabled.
|
||||
"""
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
types = {"dict": dict, "axes": Axes, "both": tuple}
|
||||
if expected_keys is None:
|
||||
# should be fixed when the returning default is changed
|
||||
if return_type is None:
|
||||
return_type = "dict"
|
||||
|
||||
assert isinstance(returned, types[return_type])
|
||||
if return_type == "both":
|
||||
assert isinstance(returned.ax, Axes)
|
||||
assert isinstance(returned.lines, dict)
|
||||
else:
|
||||
# should be fixed when the returning default is changed
|
||||
if return_type is None:
|
||||
for r in self._flatten_visible(returned):
|
||||
assert isinstance(r, Axes)
|
||||
return
|
||||
|
||||
assert isinstance(returned, Series)
|
||||
|
||||
assert sorted(returned.keys()) == sorted(expected_keys)
|
||||
for key, value in returned.items():
|
||||
assert isinstance(value, types[return_type])
|
||||
# check returned dict has correct mapping
|
||||
if return_type == "axes":
|
||||
if check_ax_title:
|
||||
assert value.get_title() == key
|
||||
elif return_type == "both":
|
||||
if check_ax_title:
|
||||
assert value.ax.get_title() == key
|
||||
assert isinstance(value.ax, Axes)
|
||||
assert isinstance(value.lines, dict)
|
||||
elif return_type == "dict":
|
||||
line = value["medians"][0]
|
||||
axes = line.axes
|
||||
if check_ax_title:
|
||||
assert axes.get_title() == key
|
||||
else:
|
||||
raise AssertionError
|
||||
|
||||
def _check_grid_settings(self, obj, kinds, kws={}):
|
||||
# Make sure plot defaults to rcParams['axes.grid'] setting, GH 9792
|
||||
|
||||
import matplotlib as mpl
|
||||
|
||||
def is_grid_on():
|
||||
xticks = self.plt.gca().xaxis.get_major_ticks()
|
||||
yticks = self.plt.gca().yaxis.get_major_ticks()
|
||||
# for mpl 2.2.2, gridOn and gridline.get_visible disagree.
|
||||
# for new MPL, they are the same.
|
||||
|
||||
if self.mpl_ge_3_1_0:
|
||||
xoff = all(not g.gridline.get_visible() for g in xticks)
|
||||
yoff = all(not g.gridline.get_visible() for g in yticks)
|
||||
else:
|
||||
xoff = all(not g.gridOn for g in xticks)
|
||||
yoff = all(not g.gridOn for g in yticks)
|
||||
|
||||
return not (xoff and yoff)
|
||||
|
||||
spndx = 1
|
||||
for kind in kinds:
|
||||
|
||||
self.plt.subplot(1, 4 * len(kinds), spndx)
|
||||
spndx += 1
|
||||
mpl.rc("axes", grid=False)
|
||||
obj.plot(kind=kind, **kws)
|
||||
assert not is_grid_on()
|
||||
|
||||
self.plt.subplot(1, 4 * len(kinds), spndx)
|
||||
spndx += 1
|
||||
mpl.rc("axes", grid=True)
|
||||
obj.plot(kind=kind, grid=False, **kws)
|
||||
assert not is_grid_on()
|
||||
|
||||
if kind not in ["pie", "hexbin", "scatter"]:
|
||||
self.plt.subplot(1, 4 * len(kinds), spndx)
|
||||
spndx += 1
|
||||
mpl.rc("axes", grid=True)
|
||||
obj.plot(kind=kind, **kws)
|
||||
assert is_grid_on()
|
||||
|
||||
self.plt.subplot(1, 4 * len(kinds), spndx)
|
||||
spndx += 1
|
||||
mpl.rc("axes", grid=False)
|
||||
obj.plot(kind=kind, grid=True, **kws)
|
||||
assert is_grid_on()
|
||||
|
||||
def _unpack_cycler(self, rcParams, field="color"):
|
||||
"""
|
||||
Auxiliary function for correctly unpacking cycler after MPL >= 1.5
|
||||
"""
|
||||
return [v[field] for v in rcParams["axes.prop_cycle"]]
|
||||
|
||||
def get_x_axis(self, ax):
|
||||
return ax._shared_axes["x"] if self.compat.mpl_ge_3_5_0() else ax._shared_x_axes
|
||||
|
||||
def get_y_axis(self, ax):
|
||||
return ax._shared_axes["y"] if self.compat.mpl_ge_3_5_0() else ax._shared_y_axes
|
||||
|
||||
|
||||
def _check_plot_works(f, filterwarnings="always", default_axes=False, **kwargs):
|
||||
"""
|
||||
Create plot and ensure that plot return object is valid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
f : func
|
||||
Plotting function.
|
||||
filterwarnings : str
|
||||
Warnings filter.
|
||||
See https://docs.python.org/3/library/warnings.html#warning-filter
|
||||
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
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter(filterwarnings)
|
||||
try:
|
||||
fig = kwargs.get("figure", plt.gcf())
|
||||
plt.clf()
|
||||
|
||||
for ret in gen_plots(f, fig, **kwargs):
|
||||
tm.assert_is_valid_plot_return_object(ret)
|
||||
|
||||
with tm.ensure_clean(return_filelike=True) as path:
|
||||
plt.savefig(path)
|
||||
|
||||
except Exception as err:
|
||||
raise err
|
||||
finally:
|
||||
tm.close(fig)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _gen_default_plot(f, fig, **kwargs):
|
||||
"""
|
||||
Create plot in a default way.
|
||||
"""
|
||||
yield f(**kwargs)
|
||||
|
||||
|
||||
def _gen_two_subplots(f, fig, **kwargs):
|
||||
"""
|
||||
Create plot on two subplots forcefully created.
|
||||
"""
|
||||
if "ax" not in kwargs:
|
||||
fig.add_subplot(211)
|
||||
yield f(**kwargs)
|
||||
|
||||
if f is pd.plotting.bootstrap_plot:
|
||||
assert "ax" not in kwargs
|
||||
else:
|
||||
kwargs["ax"] = fig.add_subplot(212)
|
||||
yield f(**kwargs)
|
||||
|
||||
|
||||
def curpath():
|
||||
pth, _ = os.path.split(os.path.abspath(__file__))
|
||||
return pth
|
||||
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,664 @@
|
||||
""" Test cases for DataFrame.plot """
|
||||
import re
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
TestPlotBase,
|
||||
_check_plot_works,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestDataFrameColor(TestPlotBase):
|
||||
def setup_method(self, method):
|
||||
TestPlotBase.setup_method(self, method)
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.rcdefaults()
|
||||
|
||||
self.tdf = tm.makeTimeDataFrame()
|
||||
self.hexbin_df = DataFrame(
|
||||
{
|
||||
"A": np.random.uniform(size=20),
|
||||
"B": np.random.uniform(size=20),
|
||||
"C": np.arange(20) + np.random.uniform(size=20),
|
||||
}
|
||||
)
|
||||
|
||||
def test_mpl2_color_cycle_str(self):
|
||||
# GH 15516
|
||||
df = DataFrame(np.random.randn(10, 3), columns=["a", "b", "c"])
|
||||
colors = ["C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"]
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always", "MatplotlibDeprecationWarning")
|
||||
|
||||
for color in colors:
|
||||
_check_plot_works(df.plot, color=color)
|
||||
|
||||
# if warning is raised, check that it is the exact problematic one
|
||||
# GH 36972
|
||||
if w:
|
||||
match = "Support for uppercase single-letter colors is deprecated"
|
||||
warning_message = str(w[0].message)
|
||||
msg = "MatplotlibDeprecationWarning related to CN colors was raised"
|
||||
assert match not in warning_message, msg
|
||||
|
||||
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.randn(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.random((7, 4)))
|
||||
ax = df.plot(color=color, style="d--")
|
||||
# check colors
|
||||
result = [i.get_color() for i in ax.lines]
|
||||
assert result == expected
|
||||
# check markers and linestyles
|
||||
assert all(i.get_linestyle() == "--" for i in ax.lines)
|
||||
assert all(i.get_marker() == "d" for i in ax.lines)
|
||||
|
||||
def test_bar_colors(self):
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
default_colors = self._unpack_cycler(plt.rcParams)
|
||||
|
||||
df = DataFrame(np.random.randn(5, 5))
|
||||
ax = df.plot.bar()
|
||||
self._check_colors(ax.patches[::5], facecolors=default_colors[:5])
|
||||
tm.close()
|
||||
|
||||
custom_colors = "rgcby"
|
||||
ax = df.plot.bar(color=custom_colors)
|
||||
self._check_colors(ax.patches[::5], facecolors=custom_colors)
|
||||
tm.close()
|
||||
|
||||
from matplotlib import cm
|
||||
|
||||
# Test str -> colormap functionality
|
||||
ax = df.plot.bar(colormap="jet")
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, 5)]
|
||||
self._check_colors(ax.patches[::5], facecolors=rgba_colors)
|
||||
tm.close()
|
||||
|
||||
# Test colormap functionality
|
||||
ax = df.plot.bar(colormap=cm.jet)
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, 5)]
|
||||
self._check_colors(ax.patches[::5], facecolors=rgba_colors)
|
||||
tm.close()
|
||||
|
||||
ax = df.loc[:, [0]].plot.bar(color="DodgerBlue")
|
||||
self._check_colors([ax.patches[0]], facecolors=["DodgerBlue"])
|
||||
tm.close()
|
||||
|
||||
ax = df.plot(kind="bar", color="green")
|
||||
self._check_colors(ax.patches[::5], facecolors=["green"] * 5)
|
||||
tm.close()
|
||||
|
||||
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.random((1000, 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.random((1000, 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):
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
random_array = np.random.random((1000, 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"]
|
||||
ax = df.plot.scatter(x=0, y=1, c="species", cmap=cmap)
|
||||
assert ax.collections[0].colorbar is None
|
||||
|
||||
def test_scatter_colors(self):
|
||||
df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3]})
|
||||
with pytest.raises(TypeError, match="Specify exactly one of `c` and `color`"):
|
||||
df.plot.scatter(x="a", y="b", c="c", color="green")
|
||||
|
||||
default_colors = self._unpack_cycler(self.plt.rcParams)
|
||||
|
||||
ax = df.plot.scatter(x="a", y="b", c="c")
|
||||
tm.assert_numpy_array_equal(
|
||||
ax.collections[0].get_facecolor()[0],
|
||||
np.array(self.colorconverter.to_rgba(default_colors[0])),
|
||||
)
|
||||
|
||||
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
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
df = DataFrame({"x": [1, 2, 3], "y": [1, 3, 2], "c": [1, 2, 3]})
|
||||
df["x2"] = df["x"] + 1
|
||||
|
||||
fig, 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):
|
||||
from matplotlib import cm
|
||||
|
||||
custom_colors = "rgcby"
|
||||
df = DataFrame(np.random.randn(5, 5))
|
||||
|
||||
ax = df.plot(color=custom_colors)
|
||||
self._check_colors(ax.get_lines(), linecolors=custom_colors)
|
||||
|
||||
tm.close()
|
||||
|
||||
ax2 = df.plot(color=custom_colors)
|
||||
lines2 = ax2.get_lines()
|
||||
|
||||
for l1, l2 in zip(ax.get_lines(), lines2):
|
||||
assert l1.get_color() == l2.get_color()
|
||||
|
||||
tm.close()
|
||||
|
||||
ax = df.plot(colormap="jet")
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
self._check_colors(ax.get_lines(), linecolors=rgba_colors)
|
||||
tm.close()
|
||||
|
||||
ax = df.plot(colormap=cm.jet)
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
self._check_colors(ax.get_lines(), linecolors=rgba_colors)
|
||||
tm.close()
|
||||
|
||||
# make color a list if plotting one column frame
|
||||
# handles cases like df.plot(color='DodgerBlue')
|
||||
ax = df.loc[:, [0]].plot(color="DodgerBlue")
|
||||
self._check_colors(ax.lines, linecolors=["DodgerBlue"])
|
||||
|
||||
ax = df.plot(color="red")
|
||||
self._check_colors(ax.get_lines(), linecolors=["red"] * 5)
|
||||
tm.close()
|
||||
|
||||
# GH 10299
|
||||
custom_colors = ["#FF0000", "#0000FF", "#FFFF00", "#000000", "#FFFFFF"]
|
||||
ax = df.plot(color=custom_colors)
|
||||
self._check_colors(ax.get_lines(), linecolors=custom_colors)
|
||||
tm.close()
|
||||
|
||||
def test_dont_modify_colors(self):
|
||||
colors = ["r", "g", "b"]
|
||||
DataFrame(np.random.rand(10, 2)).plot(color=colors)
|
||||
assert len(colors) == 3
|
||||
|
||||
def test_line_colors_and_styles_subplots(self):
|
||||
# GH 9894
|
||||
from matplotlib import cm
|
||||
|
||||
default_colors = self._unpack_cycler(self.plt.rcParams)
|
||||
|
||||
df = DataFrame(np.random.randn(5, 5))
|
||||
|
||||
axes = df.plot(subplots=True)
|
||||
for ax, c in zip(axes, list(default_colors)):
|
||||
self._check_colors(ax.get_lines(), linecolors=[c])
|
||||
tm.close()
|
||||
|
||||
# single color char
|
||||
axes = df.plot(subplots=True, color="k")
|
||||
for ax in axes:
|
||||
self._check_colors(ax.get_lines(), linecolors=["k"])
|
||||
tm.close()
|
||||
|
||||
# single color str
|
||||
axes = df.plot(subplots=True, color="green")
|
||||
for ax in axes:
|
||||
self._check_colors(ax.get_lines(), linecolors=["green"])
|
||||
tm.close()
|
||||
|
||||
custom_colors = "rgcby"
|
||||
axes = df.plot(color=custom_colors, subplots=True)
|
||||
for ax, c in zip(axes, list(custom_colors)):
|
||||
self._check_colors(ax.get_lines(), linecolors=[c])
|
||||
tm.close()
|
||||
|
||||
axes = df.plot(color=list(custom_colors), subplots=True)
|
||||
for ax, c in zip(axes, list(custom_colors)):
|
||||
self._check_colors(ax.get_lines(), linecolors=[c])
|
||||
tm.close()
|
||||
|
||||
# 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)):
|
||||
self._check_colors(ax.get_lines(), linecolors=[c])
|
||||
tm.close()
|
||||
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
for cmap in ["jet", cm.jet]:
|
||||
axes = df.plot(colormap=cmap, subplots=True)
|
||||
for ax, c in zip(axes, rgba_colors):
|
||||
self._check_colors(ax.get_lines(), linecolors=[c])
|
||||
tm.close()
|
||||
|
||||
# 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)
|
||||
self._check_colors(axes[0].lines, linecolors=["DodgerBlue"])
|
||||
|
||||
# single character style
|
||||
axes = df.plot(style="r", subplots=True)
|
||||
for ax in axes:
|
||||
self._check_colors(ax.get_lines(), linecolors=["r"])
|
||||
tm.close()
|
||||
|
||||
# list of styles
|
||||
styles = list("rgcby")
|
||||
axes = df.plot(style=styles, subplots=True)
|
||||
for ax, c in zip(axes, styles):
|
||||
self._check_colors(ax.get_lines(), linecolors=[c])
|
||||
tm.close()
|
||||
|
||||
def test_area_colors(self):
|
||||
from matplotlib import cm
|
||||
from matplotlib.collections import PolyCollection
|
||||
|
||||
custom_colors = "rgcby"
|
||||
df = DataFrame(np.random.rand(5, 5))
|
||||
|
||||
ax = df.plot.area(color=custom_colors)
|
||||
self._check_colors(ax.get_lines(), linecolors=custom_colors)
|
||||
poly = [o for o in ax.get_children() if isinstance(o, PolyCollection)]
|
||||
self._check_colors(poly, facecolors=custom_colors)
|
||||
|
||||
handles, labels = ax.get_legend_handles_labels()
|
||||
self._check_colors(handles, facecolors=custom_colors)
|
||||
|
||||
for h in handles:
|
||||
assert h.get_alpha() is None
|
||||
tm.close()
|
||||
|
||||
ax = df.plot.area(colormap="jet")
|
||||
jet_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
self._check_colors(ax.get_lines(), linecolors=jet_colors)
|
||||
poly = [o for o in ax.get_children() if isinstance(o, PolyCollection)]
|
||||
self._check_colors(poly, facecolors=jet_colors)
|
||||
|
||||
handles, labels = ax.get_legend_handles_labels()
|
||||
self._check_colors(handles, facecolors=jet_colors)
|
||||
for h in handles:
|
||||
assert h.get_alpha() is None
|
||||
tm.close()
|
||||
|
||||
# When stacked=False, alpha is set to 0.5
|
||||
ax = df.plot.area(colormap=cm.jet, stacked=False)
|
||||
self._check_colors(ax.get_lines(), linecolors=jet_colors)
|
||||
poly = [o for o in ax.get_children() if isinstance(o, PolyCollection)]
|
||||
jet_with_alpha = [(c[0], c[1], c[2], 0.5) for c in jet_colors]
|
||||
self._check_colors(poly, facecolors=jet_with_alpha)
|
||||
|
||||
handles, labels = ax.get_legend_handles_labels()
|
||||
linecolors = jet_with_alpha
|
||||
self._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 = self._unpack_cycler(self.plt.rcParams)
|
||||
|
||||
df = DataFrame(np.random.randn(5, 5))
|
||||
ax = df.plot.hist()
|
||||
self._check_colors(ax.patches[::10], facecolors=default_colors[:5])
|
||||
tm.close()
|
||||
|
||||
custom_colors = "rgcby"
|
||||
ax = df.plot.hist(color=custom_colors)
|
||||
self._check_colors(ax.patches[::10], facecolors=custom_colors)
|
||||
tm.close()
|
||||
|
||||
from matplotlib import cm
|
||||
|
||||
# Test str -> colormap functionality
|
||||
ax = df.plot.hist(colormap="jet")
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, 5)]
|
||||
self._check_colors(ax.patches[::10], facecolors=rgba_colors)
|
||||
tm.close()
|
||||
|
||||
# Test colormap functionality
|
||||
ax = df.plot.hist(colormap=cm.jet)
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, 5)]
|
||||
self._check_colors(ax.patches[::10], facecolors=rgba_colors)
|
||||
tm.close()
|
||||
|
||||
ax = df.loc[:, [0]].plot.hist(color="DodgerBlue")
|
||||
self._check_colors([ax.patches[0]], facecolors=["DodgerBlue"])
|
||||
|
||||
ax = df.plot(kind="hist", color="green")
|
||||
self._check_colors(ax.patches[::10], facecolors=["green"] * 5)
|
||||
tm.close()
|
||||
|
||||
@td.skip_if_no_scipy
|
||||
def test_kde_colors(self):
|
||||
from matplotlib import cm
|
||||
|
||||
custom_colors = "rgcby"
|
||||
df = DataFrame(np.random.rand(5, 5))
|
||||
|
||||
ax = df.plot.kde(color=custom_colors)
|
||||
self._check_colors(ax.get_lines(), linecolors=custom_colors)
|
||||
tm.close()
|
||||
|
||||
ax = df.plot.kde(colormap="jet")
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
self._check_colors(ax.get_lines(), linecolors=rgba_colors)
|
||||
tm.close()
|
||||
|
||||
ax = df.plot.kde(colormap=cm.jet)
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
self._check_colors(ax.get_lines(), linecolors=rgba_colors)
|
||||
|
||||
@td.skip_if_no_scipy
|
||||
def test_kde_colors_and_styles_subplots(self):
|
||||
from matplotlib import cm
|
||||
|
||||
default_colors = self._unpack_cycler(self.plt.rcParams)
|
||||
|
||||
df = DataFrame(np.random.randn(5, 5))
|
||||
|
||||
axes = df.plot(kind="kde", subplots=True)
|
||||
for ax, c in zip(axes, list(default_colors)):
|
||||
self._check_colors(ax.get_lines(), linecolors=[c])
|
||||
tm.close()
|
||||
|
||||
# single color char
|
||||
axes = df.plot(kind="kde", color="k", subplots=True)
|
||||
for ax in axes:
|
||||
self._check_colors(ax.get_lines(), linecolors=["k"])
|
||||
tm.close()
|
||||
|
||||
# single color str
|
||||
axes = df.plot(kind="kde", color="red", subplots=True)
|
||||
for ax in axes:
|
||||
self._check_colors(ax.get_lines(), linecolors=["red"])
|
||||
tm.close()
|
||||
|
||||
custom_colors = "rgcby"
|
||||
axes = df.plot(kind="kde", color=custom_colors, subplots=True)
|
||||
for ax, c in zip(axes, list(custom_colors)):
|
||||
self._check_colors(ax.get_lines(), linecolors=[c])
|
||||
tm.close()
|
||||
|
||||
rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
|
||||
for cmap in ["jet", cm.jet]:
|
||||
axes = df.plot(kind="kde", colormap=cmap, subplots=True)
|
||||
for ax, c in zip(axes, rgba_colors):
|
||||
self._check_colors(ax.get_lines(), linecolors=[c])
|
||||
tm.close()
|
||||
|
||||
# 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)
|
||||
self._check_colors(axes[0].lines, linecolors=["DodgerBlue"])
|
||||
|
||||
# single character style
|
||||
axes = df.plot(kind="kde", style="r", subplots=True)
|
||||
for ax in axes:
|
||||
self._check_colors(ax.get_lines(), linecolors=["r"])
|
||||
tm.close()
|
||||
|
||||
# list of styles
|
||||
styles = list("rgcby")
|
||||
axes = df.plot(kind="kde", style=styles, subplots=True)
|
||||
for ax, c in zip(axes, styles):
|
||||
self._check_colors(ax.get_lines(), linecolors=[c])
|
||||
tm.close()
|
||||
|
||||
def test_boxplot_colors(self):
|
||||
def _check_colors(bp, box_c, whiskers_c, medians_c, caps_c="k", fliers_c=None):
|
||||
# TODO: outside this func?
|
||||
if fliers_c is None:
|
||||
fliers_c = "k"
|
||||
self._check_colors(bp["boxes"], linecolors=[box_c] * len(bp["boxes"]))
|
||||
self._check_colors(
|
||||
bp["whiskers"], linecolors=[whiskers_c] * len(bp["whiskers"])
|
||||
)
|
||||
self._check_colors(
|
||||
bp["medians"], linecolors=[medians_c] * len(bp["medians"])
|
||||
)
|
||||
self._check_colors(bp["fliers"], linecolors=[fliers_c] * len(bp["fliers"]))
|
||||
self._check_colors(bp["caps"], linecolors=[caps_c] * len(bp["caps"]))
|
||||
|
||||
default_colors = self._unpack_cycler(self.plt.rcParams)
|
||||
|
||||
df = DataFrame(np.random.randn(5, 5))
|
||||
bp = df.plot.box(return_type="dict")
|
||||
_check_colors(
|
||||
bp,
|
||||
default_colors[0],
|
||||
default_colors[0],
|
||||
default_colors[2],
|
||||
default_colors[0],
|
||||
)
|
||||
tm.close()
|
||||
|
||||
dict_colors = {
|
||||
"boxes": "#572923",
|
||||
"whiskers": "#982042",
|
||||
"medians": "#804823",
|
||||
"caps": "#123456",
|
||||
}
|
||||
bp = df.plot.box(color=dict_colors, sym="r+", return_type="dict")
|
||||
_check_colors(
|
||||
bp,
|
||||
dict_colors["boxes"],
|
||||
dict_colors["whiskers"],
|
||||
dict_colors["medians"],
|
||||
dict_colors["caps"],
|
||||
"r",
|
||||
)
|
||||
tm.close()
|
||||
|
||||
# partial colors
|
||||
dict_colors = {"whiskers": "c", "medians": "m"}
|
||||
bp = df.plot.box(color=dict_colors, return_type="dict")
|
||||
_check_colors(bp, default_colors[0], "c", "m", default_colors[0])
|
||||
tm.close()
|
||||
|
||||
from matplotlib import cm
|
||||
|
||||
# Test str -> colormap functionality
|
||||
bp = df.plot.box(colormap="jet", return_type="dict")
|
||||
jet_colors = [cm.jet(n) for n in np.linspace(0, 1, 3)]
|
||||
_check_colors(bp, jet_colors[0], jet_colors[0], jet_colors[2], jet_colors[0])
|
||||
tm.close()
|
||||
|
||||
# Test colormap functionality
|
||||
bp = df.plot.box(colormap=cm.jet, return_type="dict")
|
||||
_check_colors(bp, jet_colors[0], jet_colors[0], jet_colors[2], jet_colors[0])
|
||||
tm.close()
|
||||
|
||||
# string color is applied to all artists except fliers
|
||||
bp = df.plot.box(color="DodgerBlue", return_type="dict")
|
||||
_check_colors(bp, "DodgerBlue", "DodgerBlue", "DodgerBlue", "DodgerBlue")
|
||||
|
||||
# tuple is also applied to all artists except fliers
|
||||
bp = df.plot.box(color=(0, 1, 0), sym="#123456", return_type="dict")
|
||||
_check_colors(bp, (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), "#123456")
|
||||
|
||||
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
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
colors = list("rgbk")
|
||||
plt.rcParams["axes.prop_cycle"] = cycler.cycler("color", colors)
|
||||
|
||||
df = DataFrame(np.random.randn(5, 3))
|
||||
ax = df.plot()
|
||||
|
||||
expected = self._unpack_cycler(plt.rcParams)[:3]
|
||||
self._check_colors(ax.get_lines(), linecolors=expected)
|
||||
|
||||
def test_no_color_bar(self):
|
||||
df = self.hexbin_df
|
||||
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 = self.hexbin_df
|
||||
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):
|
||||
import matplotlib as mpl
|
||||
|
||||
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):
|
||||
import matplotlib as mpl
|
||||
|
||||
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()
|
||||
for legend, line in zip(result.get_legend().legendHandles, result.lines):
|
||||
assert legend.get_color() == line.get_color()
|
||||
|
||||
def test_invalid_colormap(self):
|
||||
df = DataFrame(np.random.randn(3, 2), columns=["A", "B"])
|
||||
msg = "'invalid_colormap' is not a valid value for name; supported values are "
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.plot(colormap="invalid_colormap")
|
||||
@@ -0,0 +1,92 @@
|
||||
""" Test cases for DataFrame.plot """
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas import DataFrame
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import TestPlotBase
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestDataFramePlotsGroupby(TestPlotBase):
|
||||
def setup_method(self, method):
|
||||
TestPlotBase.setup_method(self, method)
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.rcdefaults()
|
||||
|
||||
self.tdf = tm.makeTimeDataFrame()
|
||||
self.hexbin_df = DataFrame(
|
||||
{
|
||||
"A": np.random.uniform(size=20),
|
||||
"B": np.random.uniform(size=20),
|
||||
"C": np.arange(20) + np.random.uniform(size=20),
|
||||
}
|
||||
)
|
||||
|
||||
def _assert_ytickslabels_visibility(self, axes, expected):
|
||||
for ax, exp in zip(axes, expected):
|
||||
self._check_visible(ax.get_yticklabels(), visible=exp)
|
||||
|
||||
def _assert_xtickslabels_visibility(self, axes, expected):
|
||||
for ax, exp in zip(axes, expected):
|
||||
self._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,195 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
date_range,
|
||||
)
|
||||
from pandas.tests.plotting.common import TestPlotBase
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
class TestFrameLegend(TestPlotBase):
|
||||
@pytest.mark.xfail(
|
||||
reason=(
|
||||
"Open bug in matplotlib "
|
||||
"https://github.com/matplotlib/matplotlib/issues/11357"
|
||||
)
|
||||
)
|
||||
def test_mixed_yerr(self):
|
||||
# https://github.com/pandas-dev/pandas/issues/39522
|
||||
from matplotlib.collections import LineCollection
|
||||
from matplotlib.lines import Line2D
|
||||
|
||||
df = DataFrame([{"x": 1, "a": 1, "b": 1}, {"x": 2, "a": 2, "b": 3}])
|
||||
|
||||
ax = df.plot("x", "a", c="orange", yerr=0.1, label="orange")
|
||||
df.plot("x", "b", c="blue", yerr=None, ax=ax, label="blue")
|
||||
|
||||
legend = ax.get_legend()
|
||||
result_handles = legend.legendHandles
|
||||
|
||||
assert isinstance(result_handles[0], LineCollection)
|
||||
assert isinstance(result_handles[1], Line2D)
|
||||
|
||||
def test_legend_false(self):
|
||||
# https://github.com/pandas-dev/pandas/issues/40044
|
||||
df = DataFrame({"a": [1, 1], "b": [2, 3]})
|
||||
df2 = DataFrame({"d": [2.5, 2.5]})
|
||||
|
||||
ax = df.plot(legend=True, color={"a": "blue", "b": "green"}, secondary_y="b")
|
||||
df2.plot(legend=True, color={"d": "red"}, ax=ax)
|
||||
legend = ax.get_legend()
|
||||
result = [handle.get_color() for handle in legend.legendHandles]
|
||||
expected = ["blue", "green", "red"]
|
||||
assert result == expected
|
||||
|
||||
def test_df_legend_labels(self):
|
||||
kinds = ["line", "bar", "barh", "kde", "area", "hist"]
|
||||
df = DataFrame(np.random.rand(3, 3), columns=["a", "b", "c"])
|
||||
df2 = DataFrame(np.random.rand(3, 3), columns=["d", "e", "f"])
|
||||
df3 = DataFrame(np.random.rand(3, 3), columns=["g", "h", "i"])
|
||||
df4 = DataFrame(np.random.rand(3, 3), columns=["j", "k", "l"])
|
||||
|
||||
for kind in kinds:
|
||||
|
||||
ax = df.plot(kind=kind, legend=True)
|
||||
self._check_legend_labels(ax, labels=df.columns)
|
||||
|
||||
ax = df2.plot(kind=kind, legend=False, ax=ax)
|
||||
self._check_legend_labels(ax, labels=df.columns)
|
||||
|
||||
ax = df3.plot(kind=kind, legend=True, ax=ax)
|
||||
self._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))
|
||||
self._check_legend_labels(ax, labels=expected)
|
||||
|
||||
# Secondary Y
|
||||
ax = df.plot(legend=True, secondary_y="b")
|
||||
self._check_legend_labels(ax, labels=["a", "b (right)", "c"])
|
||||
ax = df2.plot(legend=False, ax=ax)
|
||||
self._check_legend_labels(ax, labels=["a", "b (right)", "c"])
|
||||
ax = df3.plot(kind="bar", legend=True, secondary_y="h", ax=ax)
|
||||
self._check_legend_labels(
|
||||
ax, labels=["a", "b (right)", "c", "g", "h (right)", "i"]
|
||||
)
|
||||
|
||||
# Time Series
|
||||
ind = date_range("1/1/2014", periods=3)
|
||||
df = DataFrame(np.random.randn(3, 3), columns=["a", "b", "c"], index=ind)
|
||||
df2 = DataFrame(np.random.randn(3, 3), columns=["d", "e", "f"], index=ind)
|
||||
df3 = DataFrame(np.random.randn(3, 3), columns=["g", "h", "i"], index=ind)
|
||||
ax = df.plot(legend=True, secondary_y="b")
|
||||
self._check_legend_labels(ax, labels=["a", "b (right)", "c"])
|
||||
ax = df2.plot(legend=False, ax=ax)
|
||||
self._check_legend_labels(ax, labels=["a", "b (right)", "c"])
|
||||
ax = df3.plot(legend=True, ax=ax)
|
||||
self._check_legend_labels(ax, labels=["a", "b (right)", "c", "g", "h", "i"])
|
||||
|
||||
# scatter
|
||||
ax = df.plot.scatter(x="a", y="b", label="data1")
|
||||
self._check_legend_labels(ax, labels=["data1"])
|
||||
ax = df2.plot.scatter(x="d", y="e", legend=False, label="data2", ax=ax)
|
||||
self._check_legend_labels(ax, labels=["data1"])
|
||||
ax = df3.plot.scatter(x="g", y="h", label="data3", ax=ax)
|
||||
self._check_legend_labels(ax, labels=["data1", "data3"])
|
||||
|
||||
# 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")
|
||||
self._check_legend_labels(ax, labels=["b"])
|
||||
ax = df5.plot(y="b", label="LABEL_b")
|
||||
self._check_legend_labels(ax, labels=["LABEL_b"])
|
||||
self._check_text_labels(ax.xaxis.get_label(), "a")
|
||||
ax = df5.plot(y="c", label="LABEL_c", ax=ax)
|
||||
self._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"])
|
||||
fig, ax = self.plt.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])
|
||||
self._check_legend_labels(ax[0], labels=["r", "g", "b"])
|
||||
self._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])
|
||||
self._check_legend_labels(ax[1], labels=["b", "r", "g"])
|
||||
self._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])
|
||||
self._check_legend_labels(ax[2], labels=["g", "b", "r"])
|
||||
self._check_legend_marker(ax[2], expected_markers=["x", "o", "o"])
|
||||
|
||||
def test_legend_name(self):
|
||||
multi = DataFrame(
|
||||
np.random.randn(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()
|
||||
self._check_text_labels(leg_title, "group,individual")
|
||||
|
||||
df = DataFrame(np.random.randn(5, 5))
|
||||
ax = df.plot(legend=True, ax=ax)
|
||||
leg_title = ax.legend_.get_title()
|
||||
self._check_text_labels(leg_title, "group,individual")
|
||||
|
||||
df.columns.name = "new"
|
||||
ax = df.plot(legend=False, ax=ax)
|
||||
leg_title = ax.legend_.get_title()
|
||||
self._check_text_labels(leg_title, "group,individual")
|
||||
|
||||
ax = df.plot(legend=True, ax=ax)
|
||||
leg_title = ax.legend_.get_title()
|
||||
self._check_text_labels(leg_title, "new")
|
||||
|
||||
def test_no_legend(self):
|
||||
kinds = ["line", "bar", "barh", "kde", "area", "hist"]
|
||||
df = DataFrame(np.random.rand(3, 3), columns=["a", "b", "c"])
|
||||
|
||||
for kind in kinds:
|
||||
ax = df.plot(kind=kind, legend=False)
|
||||
self._check_legend_labels(ax, visible=False)
|
||||
|
||||
def test_missing_markers_legend(self):
|
||||
# 14958
|
||||
df = DataFrame(np.random.randn(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)
|
||||
|
||||
self._check_legend_labels(ax, labels=["A", "B", "C"])
|
||||
self._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],
|
||||
}
|
||||
)
|
||||
|
||||
fig, ax = self.plt.subplots()
|
||||
for kind in "ABC":
|
||||
df.plot("X", kind, label=kind, ax=ax, style=".")
|
||||
|
||||
self._check_legend_labels(ax, labels=["A", "B", "C"])
|
||||
self._check_legend_marker(ax, expected_markers=[".", ".", "."])
|
||||
@@ -0,0 +1,687 @@
|
||||
""" Test cases for DataFrame.plot """
|
||||
|
||||
import string
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
import pandas as pd
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Series,
|
||||
date_range,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import TestPlotBase
|
||||
|
||||
from pandas.io.formats.printing import pprint_thing
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestDataFramePlotsSubplots(TestPlotBase):
|
||||
def setup_method(self, method):
|
||||
TestPlotBase.setup_method(self, method)
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.rcdefaults()
|
||||
|
||||
self.tdf = tm.makeTimeDataFrame()
|
||||
self.hexbin_df = DataFrame(
|
||||
{
|
||||
"A": np.random.uniform(size=20),
|
||||
"B": np.random.uniform(size=20),
|
||||
"C": np.arange(20) + np.random.uniform(size=20),
|
||||
}
|
||||
)
|
||||
|
||||
def test_subplots(self):
|
||||
df = DataFrame(np.random.rand(10, 3), index=list(string.ascii_letters[:10]))
|
||||
|
||||
for kind in ["bar", "barh", "line", "area"]:
|
||||
axes = df.plot(kind=kind, subplots=True, sharex=True, legend=True)
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(3, 1))
|
||||
assert axes.shape == (3,)
|
||||
|
||||
for ax, column in zip(axes, df.columns):
|
||||
self._check_legend_labels(ax, labels=[pprint_thing(column)])
|
||||
|
||||
for ax in axes[:-2]:
|
||||
self._check_visible(ax.xaxis) # xaxis must be visible for grid
|
||||
self._check_visible(ax.get_xticklabels(), visible=False)
|
||||
if not (kind == "bar" and self.mpl_ge_3_1_0):
|
||||
# change https://github.com/pandas-dev/pandas/issues/26714
|
||||
self._check_visible(ax.get_xticklabels(minor=True), visible=False)
|
||||
self._check_visible(ax.xaxis.get_label(), visible=False)
|
||||
self._check_visible(ax.get_yticklabels())
|
||||
|
||||
self._check_visible(axes[-1].xaxis)
|
||||
self._check_visible(axes[-1].get_xticklabels())
|
||||
self._check_visible(axes[-1].get_xticklabels(minor=True))
|
||||
self._check_visible(axes[-1].xaxis.get_label())
|
||||
self._check_visible(axes[-1].get_yticklabels())
|
||||
|
||||
axes = df.plot(kind=kind, subplots=True, sharex=False)
|
||||
for ax in axes:
|
||||
self._check_visible(ax.xaxis)
|
||||
self._check_visible(ax.get_xticklabels())
|
||||
self._check_visible(ax.get_xticklabels(minor=True))
|
||||
self._check_visible(ax.xaxis.get_label())
|
||||
self._check_visible(ax.get_yticklabels())
|
||||
|
||||
axes = df.plot(kind=kind, subplots=True, legend=False)
|
||||
for ax in axes:
|
||||
assert ax.get_legend() is None
|
||||
|
||||
def test_subplots_timeseries(self):
|
||||
idx = date_range(start="2014-07-01", freq="M", periods=10)
|
||||
df = DataFrame(np.random.rand(10, 3), index=idx)
|
||||
|
||||
for kind in ["line", "area"]:
|
||||
axes = df.plot(kind=kind, subplots=True, sharex=True)
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(3, 1))
|
||||
|
||||
for ax in axes[:-2]:
|
||||
# GH 7801
|
||||
self._check_visible(ax.xaxis) # xaxis must be visible for grid
|
||||
self._check_visible(ax.get_xticklabels(), visible=False)
|
||||
self._check_visible(ax.get_xticklabels(minor=True), visible=False)
|
||||
self._check_visible(ax.xaxis.get_label(), visible=False)
|
||||
self._check_visible(ax.get_yticklabels())
|
||||
|
||||
self._check_visible(axes[-1].xaxis)
|
||||
self._check_visible(axes[-1].get_xticklabels())
|
||||
self._check_visible(axes[-1].get_xticklabels(minor=True))
|
||||
self._check_visible(axes[-1].xaxis.get_label())
|
||||
self._check_visible(axes[-1].get_yticklabels())
|
||||
self._check_ticks_props(axes, xrot=0)
|
||||
|
||||
axes = df.plot(kind=kind, subplots=True, sharex=False, rot=45, fontsize=7)
|
||||
for ax in axes:
|
||||
self._check_visible(ax.xaxis)
|
||||
self._check_visible(ax.get_xticklabels())
|
||||
self._check_visible(ax.get_xticklabels(minor=True))
|
||||
self._check_visible(ax.xaxis.get_label())
|
||||
self._check_visible(ax.get_yticklabels())
|
||||
self._check_ticks_props(ax, xlabelsize=7, xrot=45, ylabelsize=7)
|
||||
|
||||
def test_subplots_timeseries_y_axis(self):
|
||||
# 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)
|
||||
|
||||
y_cols = ["numeric", "timedelta", "datetime_no_tz", "datetime_all_tz"]
|
||||
for col in y_cols:
|
||||
ax = testdata.plot(y=col)
|
||||
result = ax.get_lines()[0].get_data()[1]
|
||||
expected = testdata[col].values
|
||||
assert (result == expected).all()
|
||||
|
||||
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()
|
||||
|
||||
def test_subplots_layout_multi_column(self):
|
||||
# GH 6667
|
||||
df = DataFrame(np.random.rand(10, 3), index=list(string.ascii_letters[:10]))
|
||||
|
||||
axes = df.plot(subplots=True, layout=(2, 2))
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))
|
||||
assert axes.shape == (2, 2)
|
||||
|
||||
axes = df.plot(subplots=True, layout=(-1, 2))
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))
|
||||
assert axes.shape == (2, 2)
|
||||
|
||||
axes = df.plot(subplots=True, layout=(2, -1))
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))
|
||||
assert axes.shape == (2, 2)
|
||||
|
||||
axes = df.plot(subplots=True, layout=(1, 4))
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(1, 4))
|
||||
assert axes.shape == (1, 4)
|
||||
|
||||
axes = df.plot(subplots=True, layout=(-1, 4))
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(1, 4))
|
||||
assert axes.shape == (1, 4)
|
||||
|
||||
axes = df.plot(subplots=True, layout=(4, -1))
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(4, 1))
|
||||
assert axes.shape == (4, 1)
|
||||
|
||||
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.rand(10, 1), index=list(string.ascii_letters[:10]))
|
||||
axes = df.plot(subplots=True, **kwargs)
|
||||
self._check_axes_shape(
|
||||
axes,
|
||||
axes_num=expected_axes_num,
|
||||
layout=expected_layout,
|
||||
)
|
||||
assert axes.shape == expected_shape
|
||||
|
||||
def test_subplots_warnings(self):
|
||||
# GH 9464
|
||||
with tm.assert_produces_warning(None):
|
||||
df = DataFrame(np.random.randn(100, 4))
|
||||
df.plot(subplots=True, layout=(3, 2))
|
||||
|
||||
df = DataFrame(
|
||||
np.random.randn(100, 4), index=date_range("1/1/2000", periods=100)
|
||||
)
|
||||
df.plot(subplots=True, layout=(3, 2))
|
||||
|
||||
def test_subplots_multiple_axes(self):
|
||||
# GH 5353, 6970, GH 7069
|
||||
fig, axes = self.plt.subplots(2, 3)
|
||||
df = DataFrame(np.random.rand(10, 3), index=list(string.ascii_letters[:10]))
|
||||
|
||||
returned = df.plot(subplots=True, ax=axes[0], sharex=False, sharey=False)
|
||||
self._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)
|
||||
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
assert returned.shape == (3,)
|
||||
assert returned[0].figure is fig
|
||||
self._check_axes_shape(axes, axes_num=6, layout=(2, 3))
|
||||
tm.close()
|
||||
|
||||
msg = "The number of passed axes must be 3, the same as the output plot"
|
||||
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
fig, axes = self.plt.subplots(2, 3)
|
||||
# pass different number of axes from required
|
||||
df.plot(subplots=True, ax=axes)
|
||||
|
||||
# pass 2-dim axes and invalid layout
|
||||
# invalid lauout should not affect to input and return value
|
||||
# (show warning is tested in
|
||||
# TestDataFrameGroupByPlots.test_grouped_box_multiple_axes
|
||||
fig, axes = self.plt.subplots(2, 2)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", UserWarning)
|
||||
df = DataFrame(np.random.rand(10, 4), index=list(string.ascii_letters[:10]))
|
||||
|
||||
returned = df.plot(
|
||||
subplots=True, ax=axes, layout=(2, 1), sharex=False, sharey=False
|
||||
)
|
||||
self._check_axes_shape(returned, axes_num=4, layout=(2, 2))
|
||||
assert returned.shape == (4,)
|
||||
|
||||
returned = df.plot(
|
||||
subplots=True, ax=axes, layout=(2, -1), sharex=False, sharey=False
|
||||
)
|
||||
self._check_axes_shape(returned, axes_num=4, layout=(2, 2))
|
||||
assert returned.shape == (4,)
|
||||
|
||||
returned = df.plot(
|
||||
subplots=True, ax=axes, layout=(-1, 2), sharex=False, sharey=False
|
||||
)
|
||||
self._check_axes_shape(returned, axes_num=4, layout=(2, 2))
|
||||
assert returned.shape == (4,)
|
||||
|
||||
# single column
|
||||
fig, axes = self.plt.subplots(1, 1)
|
||||
df = DataFrame(np.random.rand(10, 1), index=list(string.ascii_letters[:10]))
|
||||
|
||||
axes = df.plot(subplots=True, ax=[axes], sharex=False, sharey=False)
|
||||
self._check_axes_shape(axes, axes_num=1, layout=(1, 1))
|
||||
assert axes.shape == (1,)
|
||||
|
||||
def test_subplots_ts_share_axes(self):
|
||||
# GH 3964
|
||||
fig, axes = self.plt.subplots(3, 3, sharex=True, sharey=True)
|
||||
self.plt.subplots_adjust(left=0.05, right=0.95, hspace=0.3, wspace=0.3)
|
||||
df = DataFrame(
|
||||
np.random.randn(10, 9),
|
||||
index=date_range(start="2014-07-01", freq="M", 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():
|
||||
self._check_visible(ax.get_xticklabels(), visible=False)
|
||||
|
||||
# Bottom row should be visible
|
||||
for ax in axes[-1].ravel():
|
||||
self._check_visible(ax.get_xticklabels(), visible=True)
|
||||
|
||||
# First column should be visible
|
||||
for ax in axes[[0, 1, 2], [0]].ravel():
|
||||
self._check_visible(ax.get_yticklabels(), visible=True)
|
||||
|
||||
# Other columns should not be visible
|
||||
for ax in axes[[0, 1, 2], [1]].ravel():
|
||||
self._check_visible(ax.get_yticklabels(), visible=False)
|
||||
for ax in axes[[0, 1, 2], [2]].ravel():
|
||||
self._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)
|
||||
|
||||
self._check_visible(axes[0].get_xticklabels(), visible=False)
|
||||
self._check_visible(axes[1].get_xticklabels(), visible=True)
|
||||
for ax in axes.ravel():
|
||||
self._check_visible(ax.get_yticklabels(), visible=True)
|
||||
|
||||
def test_subplots_dup_columns(self):
|
||||
# GH 10962
|
||||
df = DataFrame(np.random.rand(5, 5), columns=list("aaaaa"))
|
||||
axes = df.plot(subplots=True)
|
||||
for ax in axes:
|
||||
self._check_legend_labels(ax, labels=["a"])
|
||||
assert len(ax.lines) == 1
|
||||
tm.close()
|
||||
|
||||
axes = df.plot(subplots=True, secondary_y="a")
|
||||
for ax in axes:
|
||||
# (right) is only attached when subplots=False
|
||||
self._check_legend_labels(ax, labels=["a"])
|
||||
assert len(ax.lines) == 1
|
||||
tm.close()
|
||||
|
||||
ax = df.plot(secondary_y="a")
|
||||
self._check_legend_labels(ax, labels=["a (right)"] * 5)
|
||||
assert len(ax.lines) == 0
|
||||
assert len(ax.right_ax.lines) == 5
|
||||
|
||||
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)
|
||||
|
||||
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(self):
|
||||
df = self.hist_df
|
||||
|
||||
# normal style: return_type=None
|
||||
result = df.plot.box(subplots=True)
|
||||
assert isinstance(result, Series)
|
||||
self._check_box_return_type(
|
||||
result, None, expected_keys=["height", "weight", "category"]
|
||||
)
|
||||
|
||||
for t in ["dict", "axes", "both"]:
|
||||
returned = df.plot.box(return_type=t, subplots=True)
|
||||
self._check_box_return_type(
|
||||
returned,
|
||||
t,
|
||||
expected_keys=["height", "weight", "category"],
|
||||
check_ax_title=False,
|
||||
)
|
||||
|
||||
def test_df_subplots_patterns_minorticks(self):
|
||||
# GH 10657
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
df = DataFrame(
|
||||
np.random.randn(10, 2),
|
||||
index=date_range("1/1/2000", periods=10),
|
||||
columns=list("AB"),
|
||||
)
|
||||
|
||||
# shared subplots
|
||||
fig, axes = plt.subplots(2, 1, sharex=True)
|
||||
axes = df.plot(subplots=True, ax=axes)
|
||||
for ax in axes:
|
||||
assert len(ax.lines) == 1
|
||||
self._check_visible(ax.get_yticklabels(), visible=True)
|
||||
# xaxis of 1st ax must be hidden
|
||||
self._check_visible(axes[0].get_xticklabels(), visible=False)
|
||||
self._check_visible(axes[0].get_xticklabels(minor=True), visible=False)
|
||||
self._check_visible(axes[1].get_xticklabels(), visible=True)
|
||||
self._check_visible(axes[1].get_xticklabels(minor=True), visible=True)
|
||||
tm.close()
|
||||
|
||||
fig, axes = plt.subplots(2, 1)
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = df.plot(subplots=True, ax=axes, sharex=True)
|
||||
for ax in axes:
|
||||
assert len(ax.lines) == 1
|
||||
self._check_visible(ax.get_yticklabels(), visible=True)
|
||||
# xaxis of 1st ax must be hidden
|
||||
self._check_visible(axes[0].get_xticklabels(), visible=False)
|
||||
self._check_visible(axes[0].get_xticklabels(minor=True), visible=False)
|
||||
self._check_visible(axes[1].get_xticklabels(), visible=True)
|
||||
self._check_visible(axes[1].get_xticklabels(minor=True), visible=True)
|
||||
tm.close()
|
||||
|
||||
# not shared
|
||||
fig, axes = plt.subplots(2, 1)
|
||||
axes = df.plot(subplots=True, ax=axes)
|
||||
for ax in axes:
|
||||
assert len(ax.lines) == 1
|
||||
self._check_visible(ax.get_yticklabels(), visible=True)
|
||||
self._check_visible(ax.get_xticklabels(), visible=True)
|
||||
self._check_visible(ax.get_xticklabels(minor=True), visible=True)
|
||||
tm.close()
|
||||
|
||||
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.rand(10, 2))
|
||||
df.iloc[5:, 1] = np.nan
|
||||
df.iloc[:5, 0] = np.nan
|
||||
|
||||
figs, axs = self.plt.subplots(2, 1)
|
||||
df.plot.line(ax=axs, subplots=True, sharex=False)
|
||||
|
||||
expected_ax1 = np.arange(4.5, 10, 0.5)
|
||||
expected_ax2 = np.arange(-0.5, 5, 0.5)
|
||||
|
||||
tm.assert_numpy_array_equal(axs[0].get_xticks(), expected_ax1)
|
||||
tm.assert_numpy_array_equal(axs[1].get_xticks(), expected_ax2)
|
||||
|
||||
def test_subplots_constrained_layout(self):
|
||||
# GH 25261
|
||||
idx = date_range(start="now", periods=10)
|
||||
df = DataFrame(np.random.rand(10, 3), index=idx)
|
||||
kwargs = {}
|
||||
if hasattr(self.plt.Figure, "get_constrained_layout"):
|
||||
kwargs["constrained_layout"] = True
|
||||
fig, axes = self.plt.subplots(2, **kwargs)
|
||||
with tm.assert_produces_warning(None):
|
||||
df.plot(ax=axes[0])
|
||||
with tm.ensure_clean(return_filelike=True) as path:
|
||||
self.plt.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.randn(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.randn(5, 5))
|
||||
self._check_bar_alignment(df, width=0.9, position=0.2, **kwargs)
|
||||
|
||||
def test_bar_barwidth_position_int(self):
|
||||
# GH 12979
|
||||
df = DataFrame(np.random.randn(5, 5))
|
||||
|
||||
for w in [1, 1.0]:
|
||||
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
|
||||
|
||||
self._check_bar_alignment(df, kind="bar", stacked=True, width=1)
|
||||
self._check_bar_alignment(df, kind="barh", stacked=False, width=1)
|
||||
self._check_bar_alignment(df, kind="barh", stacked=True, width=1)
|
||||
self._check_bar_alignment(df, kind="bar", subplots=True, width=1)
|
||||
self._check_bar_alignment(df, kind="barh", subplots=True, width=1)
|
||||
|
||||
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 = self._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,389 @@
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas import DataFrame
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
TestPlotBase,
|
||||
_check_plot_works,
|
||||
)
|
||||
|
||||
|
||||
def _create_hist_box_with_by_df():
|
||||
np.random.seed(0)
|
||||
df = DataFrame(np.random.randn(30, 2), columns=["A", "B"])
|
||||
df["C"] = np.random.choice(["a", "b", "c"], 30)
|
||||
df["D"] = np.random.choice(["a", "b", "c"], 30)
|
||||
return df
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestHistWithBy(TestPlotBase):
|
||||
def setup_method(self, method):
|
||||
TestPlotBase.setup_method(self, method)
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.rcdefaults()
|
||||
self.hist_df = _create_hist_box_with_by_df()
|
||||
|
||||
@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)",
|
||||
"(a, b)",
|
||||
"(a, c)",
|
||||
"(b, a)",
|
||||
"(b, b)",
|
||||
"(b, c)",
|
||||
"(c, a)",
|
||||
"(c, b)",
|
||||
"(c, c)",
|
||||
],
|
||||
[["A"]] * 9,
|
||||
),
|
||||
(
|
||||
["C", "D"],
|
||||
["A", "B"],
|
||||
[
|
||||
"(a, a)",
|
||||
"(a, b)",
|
||||
"(a, c)",
|
||||
"(b, a)",
|
||||
"(b, b)",
|
||||
"(b, c)",
|
||||
"(c, a)",
|
||||
"(c, b)",
|
||||
"(c, c)",
|
||||
],
|
||||
[["A", "B"]] * 9,
|
||||
),
|
||||
(
|
||||
["C", "D"],
|
||||
None,
|
||||
[
|
||||
"(a, a)",
|
||||
"(a, b)",
|
||||
"(a, c)",
|
||||
"(b, a)",
|
||||
"(b, b)",
|
||||
"(b, c)",
|
||||
"(c, a)",
|
||||
"(c, b)",
|
||||
"(c, c)",
|
||||
],
|
||||
[["A", "B"]] * 9,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_hist_plot_by_argument(self, by, column, titles, legends):
|
||||
# GH 15079
|
||||
axes = _check_plot_works(self.hist_df.plot.hist, 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, titles, legends",
|
||||
[
|
||||
(0, "A", ["a", "b", "c"], [["A"]] * 3),
|
||||
(0, None, ["a", "b", "c"], [["A", "B"]] * 3),
|
||||
(
|
||||
[0, "D"],
|
||||
"A",
|
||||
[
|
||||
"(a, a)",
|
||||
"(a, b)",
|
||||
"(a, c)",
|
||||
"(b, a)",
|
||||
"(b, b)",
|
||||
"(b, c)",
|
||||
"(c, a)",
|
||||
"(c, b)",
|
||||
"(c, c)",
|
||||
],
|
||||
[["A"]] * 9,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_hist_plot_by_0(self, by, column, titles, legends):
|
||||
# GH 15079
|
||||
df = self.hist_df.copy()
|
||||
df = df.rename(columns={"C": 0})
|
||||
|
||||
axes = _check_plot_works(df.plot.hist, 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):
|
||||
# GH 15079
|
||||
msg = "No group keys passed"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
_check_plot_works(self.hist_df.plot.hist, 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), 9),
|
||||
(["C", "D"], "A", (3, 3), 9),
|
||||
(["C", "D"], ["A"], (5, 2), 9),
|
||||
(["C", "D"], ["A", "B"], (9, 1), 9),
|
||||
(["C", "D"], None, (9, 1), 9),
|
||||
(["C", "D"], ["A", "B"], (5, 2), 9),
|
||||
],
|
||||
)
|
||||
def test_hist_plot_layout_with_by(self, by, column, layout, axes_num):
|
||||
# GH 15079
|
||||
# _check_plot_works adds an ax so catch warning. see GH #13188
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(
|
||||
self.hist_df.plot.hist, column=column, by=by, layout=layout
|
||||
)
|
||||
self._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):
|
||||
# GH 15079, test if error is raised when invalid layout is given
|
||||
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
self.hist_df.plot.hist(column=["A", "B"], by=by, layout=layout)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_axis_share_x_with_by(self):
|
||||
# GH 15079
|
||||
ax1, ax2, ax3 = self.hist_df.plot.hist(column="A", by="C", sharex=True)
|
||||
|
||||
# share x
|
||||
assert self.get_x_axis(ax1).joined(ax1, ax2)
|
||||
assert self.get_x_axis(ax2).joined(ax1, ax2)
|
||||
assert self.get_x_axis(ax3).joined(ax1, ax3)
|
||||
assert self.get_x_axis(ax3).joined(ax2, ax3)
|
||||
|
||||
# don't share y
|
||||
assert not self.get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert not self.get_y_axis(ax2).joined(ax1, ax2)
|
||||
assert not self.get_y_axis(ax3).joined(ax1, ax3)
|
||||
assert not self.get_y_axis(ax3).joined(ax2, ax3)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_axis_share_y_with_by(self):
|
||||
# GH 15079
|
||||
ax1, ax2, ax3 = self.hist_df.plot.hist(column="A", by="C", sharey=True)
|
||||
|
||||
# share y
|
||||
assert self.get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert self.get_y_axis(ax2).joined(ax1, ax2)
|
||||
assert self.get_y_axis(ax3).joined(ax1, ax3)
|
||||
assert self.get_y_axis(ax3).joined(ax2, ax3)
|
||||
|
||||
# don't share x
|
||||
assert not self.get_x_axis(ax1).joined(ax1, ax2)
|
||||
assert not self.get_x_axis(ax2).joined(ax1, ax2)
|
||||
assert not self.get_x_axis(ax3).joined(ax1, ax3)
|
||||
assert not self.get_x_axis(ax3).joined(ax2, ax3)
|
||||
|
||||
@pytest.mark.parametrize("figsize", [(12, 8), (20, 10)])
|
||||
def test_figure_shape_hist_with_by(self, figsize):
|
||||
# GH 15079
|
||||
axes = self.hist_df.plot.hist(column="A", by="C", figsize=figsize)
|
||||
self._check_axes_shape(axes, axes_num=3, figsize=figsize)
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestBoxWithBy(TestPlotBase):
|
||||
def setup_method(self, method):
|
||||
TestPlotBase.setup_method(self, method)
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.rcdefaults()
|
||||
self.box_df = _create_hist_box_with_by_df()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"by, column, titles, xticklabels",
|
||||
[
|
||||
("C", "A", ["A"], [["a", "b", "c"]]),
|
||||
(
|
||||
["C", "D"],
|
||||
"A",
|
||||
["A"],
|
||||
[
|
||||
[
|
||||
"(a, a)",
|
||||
"(a, b)",
|
||||
"(a, c)",
|
||||
"(b, a)",
|
||||
"(b, b)",
|
||||
"(b, c)",
|
||||
"(c, a)",
|
||||
"(c, b)",
|
||||
"(c, c)",
|
||||
]
|
||||
],
|
||||
),
|
||||
("C", ["A", "B"], ["A", "B"], [["a", "b", "c"]] * 2),
|
||||
(
|
||||
["C", "D"],
|
||||
["A", "B"],
|
||||
["A", "B"],
|
||||
[
|
||||
[
|
||||
"(a, a)",
|
||||
"(a, b)",
|
||||
"(a, c)",
|
||||
"(b, a)",
|
||||
"(b, b)",
|
||||
"(b, c)",
|
||||
"(c, a)",
|
||||
"(c, b)",
|
||||
"(c, c)",
|
||||
]
|
||||
]
|
||||
* 2,
|
||||
),
|
||||
(["C"], None, ["A", "B"], [["a", "b", "c"]] * 2),
|
||||
],
|
||||
)
|
||||
def test_box_plot_by_argument(self, by, column, titles, xticklabels):
|
||||
# GH 15079
|
||||
axes = _check_plot_works(self.box_df.plot.box, 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)",
|
||||
"(a, b)",
|
||||
"(a, c)",
|
||||
"(b, a)",
|
||||
"(b, b)",
|
||||
"(b, c)",
|
||||
"(c, a)",
|
||||
"(c, b)",
|
||||
"(c, c)",
|
||||
]
|
||||
],
|
||||
),
|
||||
(0, None, ["A", "B"], [["a", "b", "c"]] * 2),
|
||||
],
|
||||
)
|
||||
def test_box_plot_by_0(self, by, column, titles, xticklabels):
|
||||
# GH 15079
|
||||
df = self.box_df.copy()
|
||||
df = df.rename(columns={"C": 0})
|
||||
|
||||
axes = _check_plot_works(df.plot.box, 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):
|
||||
# GH 15079
|
||||
msg = "No group keys passed"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
_check_plot_works(self.box_df.plot.box, 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):
|
||||
# GH 15079
|
||||
axes = _check_plot_works(
|
||||
self.box_df.plot.box, column=column, by=by, layout=layout
|
||||
)
|
||||
self._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):
|
||||
# GH 15079, test if error is raised when invalid layout is given
|
||||
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
self.box_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):
|
||||
# GH 15079
|
||||
axes = self.box_df.plot.box(column="A", by="C", figsize=figsize)
|
||||
self._check_axes_shape(axes, axes_num=1, figsize=figsize)
|
||||
@@ -0,0 +1,111 @@
|
||||
import sys
|
||||
import types
|
||||
|
||||
import pkg_resources
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
import pandas
|
||||
|
||||
dummy_backend = types.ModuleType("pandas_dummy_backend")
|
||||
setattr(dummy_backend, "plot", lambda *args, **kwargs: "used_dummy")
|
||||
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def restore_backend():
|
||||
"""Restore the plotting backend to matplotlib"""
|
||||
pandas.set_option("plotting.backend", "matplotlib")
|
||||
yield
|
||||
pandas.set_option("plotting.backend", "matplotlib")
|
||||
|
||||
|
||||
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):
|
||||
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):
|
||||
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"
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
def test_register_entrypoint(restore_backend):
|
||||
|
||||
dist = pkg_resources.get_distribution("pandas")
|
||||
if dist.module_path not in pandas.__file__:
|
||||
# We are running from a non-installed pandas, and this test is invalid
|
||||
pytest.skip("Testing a non-installed pandas")
|
||||
|
||||
mod = types.ModuleType("my_backend")
|
||||
mod.plot = lambda *args, **kwargs: 1
|
||||
|
||||
backends = pkg_resources.get_entry_map("pandas")
|
||||
my_entrypoint = pkg_resources.EntryPoint(
|
||||
"pandas_plotting_backend", mod.__name__, dist=dist
|
||||
)
|
||||
backends["pandas_plotting_backends"]["my_backend"] = my_entrypoint
|
||||
# TODO: the docs recommend importlib.util.module_from_spec. But this works for now.
|
||||
sys.modules["my_backend"] = mod
|
||||
|
||||
result = pandas.plotting._core._get_plot_backend("my_backend")
|
||||
assert result is mod
|
||||
|
||||
# TODO(GH#27517): https://github.com/pandas-dev/pandas/issues/27517
|
||||
# Remove the td.skip_if_no_mpl
|
||||
with pandas.option_context("plotting.backend", "my_backend"):
|
||||
result = pandas.plotting._core._get_plot_backend()
|
||||
|
||||
assert result is mod
|
||||
|
||||
|
||||
def test_setting_backend_without_plot_raises():
|
||||
# GH-28163
|
||||
module = types.ModuleType("pandas_plot_backend")
|
||||
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_mpl
|
||||
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):
|
||||
# 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,568 @@
|
||||
""" Test cases for .boxplot method """
|
||||
|
||||
import itertools
|
||||
import string
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
MultiIndex,
|
||||
Series,
|
||||
date_range,
|
||||
timedelta_range,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
TestPlotBase,
|
||||
_check_plot_works,
|
||||
)
|
||||
|
||||
import pandas.plotting as plotting
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestDataFramePlots(TestPlotBase):
|
||||
def test_boxplot_legacy1(self):
|
||||
df = DataFrame(
|
||||
np.random.randn(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(df.boxplot, return_type="dict")
|
||||
_check_plot_works(df.boxplot, column=["one", "two"], return_type="dict")
|
||||
# _check_plot_works adds an ax so catch warning. see GH #13188
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_check_plot_works(df.boxplot, column=["one", "two"], by="indic")
|
||||
_check_plot_works(df.boxplot, column="one", by=["indic", "indic2"])
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_check_plot_works(df.boxplot, by="indic")
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_check_plot_works(df.boxplot, by=["indic", "indic2"])
|
||||
_check_plot_works(plotting._core.boxplot, data=df["one"], return_type="dict")
|
||||
_check_plot_works(df.boxplot, notch=1, return_type="dict")
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_check_plot_works(df.boxplot, by="indic", notch=1)
|
||||
|
||||
def test_boxplot_legacy2(self):
|
||||
df = DataFrame(np.random.rand(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_plot_works(df.boxplot, by="X")
|
||||
|
||||
# When ax is supplied and required number of axes is 1,
|
||||
# passed ax should be used:
|
||||
fig, ax = self.plt.subplots()
|
||||
axes = df.boxplot("Col1", by="X", ax=ax)
|
||||
ax_axes = ax.axes
|
||||
assert ax_axes is axes
|
||||
|
||||
fig, ax = self.plt.subplots()
|
||||
axes = df.groupby("Y").boxplot(ax=ax, return_type="axes")
|
||||
ax_axes = ax.axes
|
||||
assert ax_axes is axes["A"]
|
||||
|
||||
# Multiple columns with an ax argument should use same figure
|
||||
fig, ax = self.plt.subplots()
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = df.boxplot(
|
||||
column=["Col1", "Col2"], by="X", ax=ax, return_type="axes"
|
||||
)
|
||||
assert axes["Col1"].get_figure() is fig
|
||||
|
||||
# When by is None, check that all relevant lines are present in the
|
||||
# dict
|
||||
fig, ax = self.plt.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):
|
||||
# GH 12216; return_type=None & by=None -> axes
|
||||
result = self.hist_df.boxplot()
|
||||
assert isinstance(result, self.plt.Axes)
|
||||
|
||||
def test_boxplot_return_type_legacy(self):
|
||||
# API change in https://github.com/pandas-dev/pandas/pull/7096
|
||||
|
||||
df = DataFrame(
|
||||
np.random.randn(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()
|
||||
self._check_box_return_type(result, "axes")
|
||||
|
||||
with tm.assert_produces_warning(False):
|
||||
result = df.boxplot(return_type="dict")
|
||||
self._check_box_return_type(result, "dict")
|
||||
|
||||
with tm.assert_produces_warning(False):
|
||||
result = df.boxplot(return_type="axes")
|
||||
self._check_box_return_type(result, "axes")
|
||||
|
||||
with tm.assert_produces_warning(False):
|
||||
result = df.boxplot(return_type="both")
|
||||
self._check_box_return_type(result, "both")
|
||||
|
||||
def test_boxplot_axis_limits(self):
|
||||
def _check_ax_limits(col, ax):
|
||||
y_min, y_max = ax.get_ylim()
|
||||
assert y_min <= col.min()
|
||||
assert y_max >= col.max()
|
||||
|
||||
df = self.hist_df.copy()
|
||||
df["age"] = np.random.randint(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
|
||||
|
||||
# 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.randn(20, 4))
|
||||
df.loc[:, 0] = np.nan
|
||||
_check_plot_works(df.boxplot, return_type="axes")
|
||||
|
||||
def test_figsize(self):
|
||||
df = DataFrame(np.random.rand(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]})
|
||||
self._check_ticks_props(
|
||||
df.boxplot("a", fontsize=16), xlabelsize=16, ylabelsize=16
|
||||
)
|
||||
|
||||
def test_boxplot_numeric_data(self):
|
||||
# GH 22799
|
||||
df = DataFrame(
|
||||
{
|
||||
"a": date_range("2012-01-01", periods=100),
|
||||
"b": np.random.randn(100),
|
||||
"c": np.random.randn(100) + 2,
|
||||
"d": date_range("2012-01-01", periods=100).astype(str),
|
||||
"e": date_range("2012-01-01", periods=100, tz="UTC"),
|
||||
"f": timedelta_range("1 days", periods=100),
|
||||
}
|
||||
)
|
||||
ax = df.plot(kind="box")
|
||||
assert [x.get_text() for x in ax.get_xticklabels()] == ["b", "c"]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"colors_kwd, expected",
|
||||
[
|
||||
(
|
||||
{"boxes": "r", "whiskers": "b", "medians": "g", "caps": "c"},
|
||||
{"boxes": "r", "whiskers": "b", "medians": "g", "caps": "c"},
|
||||
),
|
||||
({"boxes": "r"}, {"boxes": "r"}),
|
||||
("r", {"boxes": "r", "whiskers": "r", "medians": "r", "caps": "r"}),
|
||||
],
|
||||
)
|
||||
def test_color_kwd(self, colors_kwd, expected):
|
||||
# GH: 26214
|
||||
df = DataFrame(np.random.rand(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.rand(10, 2))
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
plt.style.use(scheme)
|
||||
result = df.plot.box(return_type="dict")
|
||||
for k, v in expected.items():
|
||||
assert result[k][0].get_color() == v
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_colors, msg",
|
||||
[({"boxes": "r", "invalid_key": "r"}, "invalid key 'invalid_key'")],
|
||||
)
|
||||
def test_color_kwd_errors(self, dict_colors, msg):
|
||||
# GH: 26214
|
||||
df = DataFrame(np.random.rand(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.random(100) for k in "ABC"})
|
||||
kwd = {props: {"color": "C1"}}
|
||||
result = df.boxplot(return_type="dict", **kwd)
|
||||
|
||||
assert result[expected][0].get_color() == "C1"
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestDataFrameGroupByPlots(TestPlotBase):
|
||||
def test_boxplot_legacy1(self):
|
||||
grouped = self.hist_df.groupby(by="gender")
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(grouped.boxplot, return_type="axes")
|
||||
self._check_axes_shape(list(axes.values), axes_num=2, layout=(1, 2))
|
||||
axes = _check_plot_works(grouped.boxplot, subplots=False, return_type="axes")
|
||||
self._check_axes_shape(axes, axes_num=1, layout=(1, 1))
|
||||
|
||||
def test_boxplot_legacy2(self):
|
||||
tuples = zip(string.ascii_letters[:10], range(10))
|
||||
df = DataFrame(np.random.rand(10, 3), index=MultiIndex.from_tuples(tuples))
|
||||
grouped = df.groupby(level=1)
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(grouped.boxplot, return_type="axes")
|
||||
self._check_axes_shape(list(axes.values), axes_num=10, layout=(4, 3))
|
||||
|
||||
axes = _check_plot_works(grouped.boxplot, subplots=False, return_type="axes")
|
||||
self._check_axes_shape(axes, axes_num=1, layout=(1, 1))
|
||||
|
||||
def test_boxplot_legacy3(self):
|
||||
tuples = zip(string.ascii_letters[:10], range(10))
|
||||
df = DataFrame(np.random.rand(10, 3), index=MultiIndex.from_tuples(tuples))
|
||||
grouped = df.unstack(level=1).groupby(level=0, axis=1)
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(grouped.boxplot, return_type="axes")
|
||||
self._check_axes_shape(list(axes.values), axes_num=3, layout=(2, 2))
|
||||
axes = _check_plot_works(grouped.boxplot, subplots=False, return_type="axes")
|
||||
self._check_axes_shape(axes, axes_num=1, layout=(1, 1))
|
||||
|
||||
def test_grouped_plot_fignums(self):
|
||||
n = 10
|
||||
weight = Series(np.random.normal(166, 20, size=n))
|
||||
height = Series(np.random.normal(60, 10, size=n))
|
||||
with tm.RNGContext(42):
|
||||
gender = np.random.choice(["male", "female"], size=n)
|
||||
df = DataFrame({"height": height, "weight": weight, "gender": gender})
|
||||
gb = df.groupby("gender")
|
||||
|
||||
res = gb.plot()
|
||||
assert len(self.plt.get_fignums()) == 2
|
||||
assert len(res) == 2
|
||||
tm.close()
|
||||
|
||||
res = gb.boxplot(return_type="axes")
|
||||
assert len(self.plt.get_fignums()) == 1
|
||||
assert len(res) == 2
|
||||
tm.close()
|
||||
|
||||
# now works with GH 5610 as gender is excluded
|
||||
res = df.groupby("gender").hist()
|
||||
tm.close()
|
||||
|
||||
def test_grouped_box_return_type(self):
|
||||
df = self.hist_df
|
||||
|
||||
# old style: return_type=None
|
||||
result = df.boxplot(by="gender")
|
||||
assert isinstance(result, np.ndarray)
|
||||
self._check_box_return_type(
|
||||
result, None, expected_keys=["height", "weight", "category"]
|
||||
)
|
||||
|
||||
# now for groupby
|
||||
result = df.groupby("gender").boxplot(return_type="dict")
|
||||
self._check_box_return_type(result, "dict", expected_keys=["Male", "Female"])
|
||||
|
||||
columns2 = "X B C D A G Y N Q O".split()
|
||||
df2 = DataFrame(np.random.randn(50, 10), columns=columns2)
|
||||
categories2 = "A B C D E F G H I J".split()
|
||||
df2["category"] = categories2 * 5
|
||||
|
||||
for t in ["dict", "axes", "both"]:
|
||||
returned = df.groupby("classroom").boxplot(return_type=t)
|
||||
self._check_box_return_type(returned, t, expected_keys=["A", "B", "C"])
|
||||
|
||||
returned = df.boxplot(by="classroom", return_type=t)
|
||||
self._check_box_return_type(
|
||||
returned, t, expected_keys=["height", "weight", "category"]
|
||||
)
|
||||
|
||||
returned = df2.groupby("category").boxplot(return_type=t)
|
||||
self._check_box_return_type(returned, t, expected_keys=categories2)
|
||||
|
||||
returned = df2.boxplot(by="category", return_type=t)
|
||||
self._check_box_return_type(returned, t, expected_keys=columns2)
|
||||
|
||||
def test_grouped_box_layout(self):
|
||||
df = self.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))
|
||||
|
||||
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",
|
||||
)
|
||||
|
||||
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))
|
||||
|
||||
# _check_plot_works adds an ax so catch warning. see GH #13188
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
box = _check_plot_works(
|
||||
df.groupby("gender").boxplot, column="height", return_type="dict"
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=2, layout=(1, 2))
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
box = _check_plot_works(
|
||||
df.groupby("category").boxplot, column="height", return_type="dict"
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=4, layout=(2, 2))
|
||||
|
||||
# GH 6769
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
box = _check_plot_works(
|
||||
df.groupby("classroom").boxplot, column="height", return_type="dict"
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=3, layout=(2, 2))
|
||||
|
||||
# GH 5897
|
||||
axes = df.boxplot(
|
||||
column=["height", "weight", "category"], by="gender", return_type="axes"
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=3, layout=(2, 2))
|
||||
for ax in [axes["height"]]:
|
||||
self._check_visible(ax.get_xticklabels(), visible=False)
|
||||
self._check_visible([ax.xaxis.get_label()], visible=False)
|
||||
for ax in [axes["weight"], axes["category"]]:
|
||||
self._check_visible(ax.get_xticklabels())
|
||||
self._check_visible([ax.xaxis.get_label()])
|
||||
|
||||
box = df.groupby("classroom").boxplot(
|
||||
column=["height", "weight", "category"], return_type="dict"
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=3, layout=(2, 2))
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
box = _check_plot_works(
|
||||
df.groupby("category").boxplot,
|
||||
column="height",
|
||||
layout=(3, 2),
|
||||
return_type="dict",
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=4, layout=(3, 2))
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
box = _check_plot_works(
|
||||
df.groupby("category").boxplot,
|
||||
column="height",
|
||||
layout=(3, -1),
|
||||
return_type="dict",
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=4, layout=(3, 2))
|
||||
|
||||
box = df.boxplot(
|
||||
column=["height", "weight", "category"], by="gender", layout=(4, 1)
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=3, layout=(4, 1))
|
||||
|
||||
box = df.boxplot(
|
||||
column=["height", "weight", "category"], by="gender", layout=(-1, 1)
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=3, layout=(3, 1))
|
||||
|
||||
box = df.groupby("classroom").boxplot(
|
||||
column=["height", "weight", "category"], layout=(1, 4), return_type="dict"
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=3, layout=(1, 4))
|
||||
|
||||
box = df.groupby("classroom").boxplot( # noqa
|
||||
column=["height", "weight", "category"], layout=(1, -1), return_type="dict"
|
||||
)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=3, layout=(1, 3))
|
||||
|
||||
def test_grouped_box_multiple_axes(self):
|
||||
# GH 6970, GH 7069
|
||||
df = self.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):
|
||||
fig, axes = self.plt.subplots(2, 2)
|
||||
df.groupby("category").boxplot(column="height", return_type="axes", ax=axes)
|
||||
self._check_axes_shape(self.plt.gcf().axes, axes_num=4, layout=(2, 2))
|
||||
|
||||
fig, axes = self.plt.subplots(2, 3)
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
returned = df.boxplot(
|
||||
column=["height", "weight", "category"],
|
||||
by="gender",
|
||||
return_type="axes",
|
||||
ax=axes[0],
|
||||
)
|
||||
returned = np.array(list(returned.values))
|
||||
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
tm.assert_numpy_array_equal(returned, axes[0])
|
||||
assert returned[0].figure is fig
|
||||
|
||||
# draw on second row
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
returned = df.groupby("classroom").boxplot(
|
||||
column=["height", "weight", "category"], return_type="axes", ax=axes[1]
|
||||
)
|
||||
returned = np.array(list(returned.values))
|
||||
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
tm.assert_numpy_array_equal(returned, axes[1])
|
||||
assert returned[0].figure is fig
|
||||
|
||||
msg = "The number of passed axes must be 3, the same as the output plot"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
fig, axes = self.plt.subplots(2, 3)
|
||||
# pass different number of axes from required
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = df.groupby("classroom").boxplot(ax=axes)
|
||||
|
||||
def test_fontsize(self):
|
||||
df = DataFrame({"a": [1, 2, 3, 4, 5, 6], "b": [0, 0, 0, 1, 1, 1]})
|
||||
self._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.choice(list("abcde"), 100),
|
||||
"v": np.random.rand(100),
|
||||
"v1": np.random.rand(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):
|
||||
# GH 43480
|
||||
df = self.hist_df.astype("object")
|
||||
grouped = df.groupby("gender")
|
||||
msg = "boxplot method requires numerical columns, nothing to plot"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
_check_plot_works(grouped.boxplot, subplots=False)
|
||||
|
||||
def test_boxplot_multiindex_column(self):
|
||||
# GH 16748
|
||||
arrays = [
|
||||
["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
|
||||
["one", "two", "one", "two", "one", "two", "one", "two"],
|
||||
]
|
||||
tuples = list(zip(*arrays))
|
||||
index = MultiIndex.from_tuples(tuples, names=["first", "second"])
|
||||
df = DataFrame(np.random.randn(3, 8), index=["A", "B", "C"], columns=index)
|
||||
|
||||
col = [("bar", "one"), ("bar", "two")]
|
||||
axes = _check_plot_works(df.boxplot, column=col, return_type="axes")
|
||||
|
||||
expected_xticklabel = ["(bar, one)", "(bar, two)"]
|
||||
result_xticklabel = [x.get_text() for x in axes.get_xticklabels()]
|
||||
assert expected_xticklabel == result_xticklabel
|
||||
@@ -0,0 +1,44 @@
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas import DataFrame
|
||||
from pandas.tests.plotting.common import (
|
||||
TestPlotBase,
|
||||
_check_plot_works,
|
||||
_gen_two_subplots,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestCommon(TestPlotBase):
|
||||
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):
|
||||
self._check_ticks_props(ax, xrot=0)
|
||||
with pytest.raises(AssertionError, match=msg):
|
||||
self._check_ticks_props(ax, xlabelsize=0)
|
||||
with pytest.raises(AssertionError, match=msg):
|
||||
self._check_ticks_props(ax, yrot=0)
|
||||
with pytest.raises(AssertionError, match=msg):
|
||||
self._check_ticks_props(ax, ylabelsize=0)
|
||||
|
||||
def test__gen_two_subplots_with_ax(self):
|
||||
fig = self.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]
|
||||
@@ -0,0 +1,384 @@
|
||||
from datetime import (
|
||||
date,
|
||||
datetime,
|
||||
)
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas._config.config as cf
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas import (
|
||||
Index,
|
||||
Period,
|
||||
Series,
|
||||
Timestamp,
|
||||
date_range,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
|
||||
from pandas.plotting import (
|
||||
deregister_matplotlib_converters,
|
||||
register_matplotlib_converters,
|
||||
)
|
||||
from pandas.tseries.offsets import (
|
||||
Day,
|
||||
Micro,
|
||||
Milli,
|
||||
Second,
|
||||
)
|
||||
|
||||
try:
|
||||
from pandas.plotting._matplotlib import converter
|
||||
except ImportError:
|
||||
# try / except, rather than skip, to avoid internal refactoring
|
||||
# causing an improper skip
|
||||
pass
|
||||
|
||||
pytest.importorskip("matplotlib.pyplot")
|
||||
dates = pytest.importorskip("matplotlib.dates")
|
||||
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
@td.skip_if_no("matplotlib", min_version="3.1.3")
|
||||
def test_registering_no_warning(self):
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
s = Series(range(12), index=date_range("2017", periods=12))
|
||||
_, ax = plt.subplots()
|
||||
|
||||
# Set to the "warn" state, in case this isn't the first test run
|
||||
register_matplotlib_converters()
|
||||
ax.plot(s.index, s.values)
|
||||
plt.close()
|
||||
|
||||
def test_pandas_plots_register(self):
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
s = Series(range(12), index=date_range("2017", periods=12))
|
||||
# Set to the "warn" state, in case this isn't the first test run
|
||||
with tm.assert_produces_warning(None) as w:
|
||||
s.plot()
|
||||
|
||||
try:
|
||||
assert len(w) == 0
|
||||
finally:
|
||||
plt.close()
|
||||
|
||||
def test_matplotlib_formatters(self):
|
||||
units = pytest.importorskip("matplotlib.units")
|
||||
|
||||
# Can't make any assertion about the start state.
|
||||
# We we check that toggling converters off removes it, and toggling it
|
||||
# on restores it.
|
||||
|
||||
with cf.option_context("plotting.matplotlib.register_converters", True):
|
||||
with cf.option_context("plotting.matplotlib.register_converters", False):
|
||||
assert Timestamp not in units.registry
|
||||
assert Timestamp in units.registry
|
||||
|
||||
@td.skip_if_no("matplotlib", min_version="3.1.3")
|
||||
def test_option_no_warning(self):
|
||||
pytest.importorskip("matplotlib.pyplot")
|
||||
ctx = cf.option_context("plotting.matplotlib.register_converters", False)
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
s = Series(range(12), index=date_range("2017", periods=12))
|
||||
_, ax = plt.subplots()
|
||||
|
||||
# Test without registering first, no warning
|
||||
with ctx:
|
||||
ax.plot(s.index, s.values)
|
||||
|
||||
# Now test with registering
|
||||
register_matplotlib_converters()
|
||||
with ctx:
|
||||
ax.plot(s.index, s.values)
|
||||
plt.close()
|
||||
|
||||
def test_registry_resets(self):
|
||||
units = pytest.importorskip("matplotlib.units")
|
||||
dates = pytest.importorskip("matplotlib.dates")
|
||||
|
||||
# make a copy, to reset to
|
||||
original = dict(units.registry)
|
||||
|
||||
try:
|
||||
# get to a known state
|
||||
units.registry.clear()
|
||||
date_converter = dates.DateConverter()
|
||||
units.registry[datetime] = date_converter
|
||||
units.registry[date] = date_converter
|
||||
|
||||
register_matplotlib_converters()
|
||||
assert units.registry[date] is not date_converter
|
||||
deregister_matplotlib_converters()
|
||||
assert units.registry[date] is date_converter
|
||||
|
||||
finally:
|
||||
# restore original stater
|
||||
units.registry.clear()
|
||||
for k, v in original.items():
|
||||
units.registry[k] = v
|
||||
|
||||
|
||||
class TestDateTimeConverter:
|
||||
def setup_method(self, method):
|
||||
self.dtc = converter.DatetimeConverter()
|
||||
self.tc = converter.TimeFormatter(None)
|
||||
|
||||
def test_convert_accepts_unicode(self):
|
||||
r1 = self.dtc.convert("12:22", None, None)
|
||||
r2 = self.dtc.convert("12:22", None, None)
|
||||
assert r1 == r2, "DatetimeConverter.convert should accept unicode"
|
||||
|
||||
def test_conversion(self):
|
||||
rs = self.dtc.convert(["2012-1-1"], None, None)[0]
|
||||
xp = dates.date2num(datetime(2012, 1, 1))
|
||||
assert rs == xp
|
||||
|
||||
rs = self.dtc.convert("2012-1-1", None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.dtc.convert(date(2012, 1, 1), None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.dtc.convert("2012-1-1", None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.dtc.convert(Timestamp("2012-1-1"), None, None)
|
||||
assert rs == xp
|
||||
|
||||
# also testing datetime64 dtype (GH8614)
|
||||
rs = self.dtc.convert("2012-01-01", None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.dtc.convert("2012-01-01 00:00:00+0000", None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.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 = self.dtc.convert(ts, None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.dtc.convert(ts.to_pydatetime(), None, None)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.dtc.convert(Index([ts - Day(1), ts]), None, None)
|
||||
assert rs[1] == xp
|
||||
|
||||
rs = self.dtc.convert(Index([ts - Day(1), ts]).to_pydatetime(), None, None)
|
||||
assert rs[1] == xp
|
||||
|
||||
def test_conversion_float(self):
|
||||
rtol = 0.5 * 10**-9
|
||||
|
||||
rs = self.dtc.convert(Timestamp("2012-1-1 01:02:03", tz="UTC"), None, None)
|
||||
xp = converter.dates.date2num(Timestamp("2012-1-1 01:02:03", tz="UTC"))
|
||||
tm.assert_almost_equal(rs, xp, rtol=rtol)
|
||||
|
||||
rs = self.dtc.convert(
|
||||
Timestamp("2012-1-1 09:02:03", tz="Asia/Hong_Kong"), None, None
|
||||
)
|
||||
tm.assert_almost_equal(rs, xp, rtol=rtol)
|
||||
|
||||
rs = self.dtc.convert(datetime(2012, 1, 1, 1, 2, 3), None, None)
|
||||
tm.assert_almost_equal(rs, xp, rtol=rtol)
|
||||
|
||||
def test_conversion_outofbounds_datetime(self):
|
||||
# 2579
|
||||
values = [date(1677, 1, 1), date(1677, 1, 2)]
|
||||
rs = self.dtc.convert(values, None, None)
|
||||
xp = converter.dates.date2num(values)
|
||||
tm.assert_numpy_array_equal(rs, xp)
|
||||
rs = self.dtc.convert(values[0], None, None)
|
||||
xp = converter.dates.date2num(values[0])
|
||||
assert rs == xp
|
||||
|
||||
values = [datetime(1677, 1, 1, 12), datetime(1677, 1, 2, 12)]
|
||||
rs = self.dtc.convert(values, None, None)
|
||||
xp = converter.dates.date2num(values)
|
||||
tm.assert_numpy_array_equal(rs, xp)
|
||||
rs = self.dtc.convert(values[0], None, None)
|
||||
xp = converter.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 = self.tc(time)
|
||||
assert result == format_expected
|
||||
|
||||
def test_dateindex_conversion(self):
|
||||
rtol = 10**-9
|
||||
|
||||
for freq in ("B", "L", "S"):
|
||||
dateindex = tm.makeDateIndex(k=10, freq=freq)
|
||||
rs = self.dtc.convert(dateindex, None, None)
|
||||
xp = converter.dates.date2num(dateindex._mpl_repr())
|
||||
tm.assert_almost_equal(rs, xp, rtol=rtol)
|
||||
|
||||
def test_resolution(self):
|
||||
def _assert_less(ts1, ts2):
|
||||
val1 = self.dtc.convert(ts1, None, None)
|
||||
val2 = self.dtc.convert(ts2, None, None)
|
||||
if not val1 < val2:
|
||||
raise AssertionError(f"{val1} is not less than {val2}.")
|
||||
|
||||
# Matplotlib's time representation using floats cannot distinguish
|
||||
# intervals smaller than ~10 microsecond in the common range of years.
|
||||
ts = Timestamp("2012-1-1")
|
||||
_assert_less(ts, ts + Second())
|
||||
_assert_less(ts, ts + Milli())
|
||||
_assert_less(ts, ts + Micro(50))
|
||||
|
||||
def test_convert_nested(self):
|
||||
inner = [Timestamp("2017-01-01"), Timestamp("2017-01-02")]
|
||||
data = [inner, inner]
|
||||
result = self.dtc.convert(data, None, None)
|
||||
expected = [self.dtc.convert(x, None, None) for x in data]
|
||||
assert (np.array(result) == expected).all()
|
||||
|
||||
|
||||
class TestPeriodConverter:
|
||||
def setup_method(self, method):
|
||||
self.pc = converter.PeriodConverter()
|
||||
|
||||
class Axis:
|
||||
pass
|
||||
|
||||
self.axis = Axis()
|
||||
self.axis.freq = "D"
|
||||
|
||||
def test_convert_accepts_unicode(self):
|
||||
r1 = self.pc.convert("2012-1-1", None, self.axis)
|
||||
r2 = self.pc.convert("2012-1-1", None, self.axis)
|
||||
assert r1 == r2
|
||||
|
||||
def test_conversion(self):
|
||||
rs = self.pc.convert(["2012-1-1"], None, self.axis)[0]
|
||||
xp = Period("2012-1-1").ordinal
|
||||
assert rs == xp
|
||||
|
||||
rs = self.pc.convert("2012-1-1", None, self.axis)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.pc.convert([date(2012, 1, 1)], None, self.axis)[0]
|
||||
assert rs == xp
|
||||
|
||||
rs = self.pc.convert(date(2012, 1, 1), None, self.axis)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.pc.convert([Timestamp("2012-1-1")], None, self.axis)[0]
|
||||
assert rs == xp
|
||||
|
||||
rs = self.pc.convert(Timestamp("2012-1-1"), None, self.axis)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.pc.convert("2012-01-01", None, self.axis)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.pc.convert("2012-01-01 00:00:00+0000", None, self.axis)
|
||||
assert rs == xp
|
||||
|
||||
rs = self.pc.convert(
|
||||
np.array(
|
||||
["2012-01-01 00:00:00", "2012-01-02 00:00:00"],
|
||||
dtype="datetime64[ns]",
|
||||
),
|
||||
None,
|
||||
self.axis,
|
||||
)
|
||||
assert rs[0] == xp
|
||||
|
||||
def test_integer_passthrough(self):
|
||||
# GH9012
|
||||
rs = self.pc.convert([0, 1], None, self.axis)
|
||||
xp = [0, 1]
|
||||
assert rs == xp
|
||||
|
||||
def test_convert_nested(self):
|
||||
data = ["2012-1-1", "2012-1-2"]
|
||||
r1 = self.pc.convert([data, data], None, self.axis)
|
||||
r2 = [self.pc.convert(data, None, self.axis) for _ in range(2)]
|
||||
assert r1 == r2
|
||||
|
||||
|
||||
class TestTimeDeltaConverter:
|
||||
"""Test timedelta converter"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"x, decimal, format_expected",
|
||||
[
|
||||
(0.0, 0, "00:00:00"),
|
||||
(3972320000000, 1, "01:06:12.3"),
|
||||
(713233432000000, 2, "8 days 06:07:13.43"),
|
||||
(32423432000000, 4, "09:00:23.4320"),
|
||||
],
|
||||
)
|
||||
def test_format_timedelta_ticks(self, x, decimal, format_expected):
|
||||
tdc = converter.TimeSeries_TimedeltaFormatter
|
||||
result = tdc.format_timedelta_ticks(x, pos=None, n_decimals=decimal)
|
||||
assert result == format_expected
|
||||
|
||||
@pytest.mark.parametrize("view_interval", [(1, 2), (2, 1)])
|
||||
def test_call_w_different_view_intervals(self, view_interval, monkeypatch):
|
||||
# previously broke on reversed xlmits; see GH37454
|
||||
class mock_axis:
|
||||
def get_view_interval(self):
|
||||
return view_interval
|
||||
|
||||
tdc = converter.TimeSeries_TimedeltaFormatter()
|
||||
monkeypatch.setattr(tdc, "axis", mock_axis())
|
||||
tdc(0.0, 0)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,120 @@
|
||||
""" Test cases for GroupBy.plot """
|
||||
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Index,
|
||||
Series,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import TestPlotBase
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestDataFrameGroupByPlots(TestPlotBase):
|
||||
def test_series_groupby_plotting_nominally_works(self):
|
||||
n = 10
|
||||
weight = Series(np.random.normal(166, 20, size=n))
|
||||
height = Series(np.random.normal(60, 10, size=n))
|
||||
with tm.RNGContext(42):
|
||||
gender = np.random.choice(["male", "female"], size=n)
|
||||
|
||||
weight.groupby(gender).plot()
|
||||
tm.close()
|
||||
height.groupby(gender).hist()
|
||||
tm.close()
|
||||
# Regression test for GH8733
|
||||
height.groupby(gender).plot(alpha=0.5)
|
||||
tm.close()
|
||||
|
||||
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.randn(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()
|
||||
tm.close()
|
||||
df.groupby("def")["val"].apply(lambda x: x.plot())
|
||||
tm.close()
|
||||
|
||||
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)
|
||||
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")
|
||||
tm.close()
|
||||
df.groupby("z")["x"].plot.line()
|
||||
tm.close()
|
||||
|
||||
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
|
||||
|
||||
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.randn(30, 2), index=index, columns=["a", "b"])
|
||||
g = df.groupby("c")
|
||||
|
||||
for axes in g.hist(legend=True, column=column):
|
||||
self._check_axes_shape(
|
||||
axes, axes_num=expected_axes_num, layout=expected_layout
|
||||
)
|
||||
for ax, expected_label in zip(axes[0], expected_labels):
|
||||
self._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.randn(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.randn(30, 2), index=index, columns=["a", "b"])
|
||||
g = df.groupby("c")
|
||||
|
||||
for ax in g["a"].hist(legend=True):
|
||||
self._check_axes_shape(ax, axes_num=1, layout=(1, 1))
|
||||
self._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.randn(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,774 @@
|
||||
""" Test cases for .hist method """
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Index,
|
||||
Series,
|
||||
to_datetime,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
TestPlotBase,
|
||||
_check_plot_works,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestSeriesPlots(TestPlotBase):
|
||||
def setup_method(self, method):
|
||||
TestPlotBase.setup_method(self, method)
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.rcdefaults()
|
||||
|
||||
self.ts = tm.makeTimeSeries()
|
||||
self.ts.name = "ts"
|
||||
|
||||
def test_hist_legacy(self):
|
||||
_check_plot_works(self.ts.hist)
|
||||
_check_plot_works(self.ts.hist, grid=False)
|
||||
_check_plot_works(self.ts.hist, figsize=(8, 10))
|
||||
# _check_plot_works adds an ax so catch warning. see GH #13188
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_check_plot_works(self.ts.hist, by=self.ts.index.month)
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_check_plot_works(self.ts.hist, by=self.ts.index.month, bins=5)
|
||||
|
||||
fig, ax = self.plt.subplots(1, 1)
|
||||
_check_plot_works(self.ts.hist, ax=ax, default_axes=True)
|
||||
_check_plot_works(self.ts.hist, ax=ax, figure=fig, default_axes=True)
|
||||
_check_plot_works(self.ts.hist, figure=fig, default_axes=True)
|
||||
tm.close()
|
||||
|
||||
fig, (ax1, ax2) = self.plt.subplots(1, 2)
|
||||
_check_plot_works(self.ts.hist, figure=fig, ax=ax1, default_axes=True)
|
||||
_check_plot_works(self.ts.hist, figure=fig, ax=ax2, default_axes=True)
|
||||
|
||||
msg = (
|
||||
"Cannot pass 'figure' when using the 'by' argument, since a new 'Figure' "
|
||||
"instance will be created"
|
||||
)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
self.ts.hist(by=self.ts.index, figure=fig)
|
||||
|
||||
def test_hist_bins_legacy(self):
|
||||
df = DataFrame(np.random.randn(10, 2))
|
||||
ax = df.hist(bins=2)[0][0]
|
||||
assert len(ax.patches) == 2
|
||||
|
||||
def test_hist_layout(self):
|
||||
df = self.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])
|
||||
|
||||
def test_hist_layout_with_by(self):
|
||||
df = self.hist_df
|
||||
|
||||
# _check_plot_works adds an `ax` kwarg to the method call
|
||||
# so we get a warning about an axis being cleared, even
|
||||
# though we don't explicing pass one, see GH #13188
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(df.height.hist, by=df.gender, layout=(2, 1))
|
||||
self._check_axes_shape(axes, axes_num=2, layout=(2, 1))
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(df.height.hist, by=df.gender, layout=(3, -1))
|
||||
self._check_axes_shape(axes, axes_num=2, layout=(3, 1))
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(df.height.hist, by=df.category, layout=(4, 1))
|
||||
self._check_axes_shape(axes, axes_num=4, layout=(4, 1))
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(df.height.hist, by=df.category, layout=(2, -1))
|
||||
self._check_axes_shape(axes, axes_num=4, layout=(2, 2))
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(df.height.hist, by=df.category, layout=(3, -1))
|
||||
self._check_axes_shape(axes, axes_num=4, layout=(3, 2))
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(df.height.hist, by=df.category, layout=(-1, 4))
|
||||
self._check_axes_shape(axes, axes_num=4, layout=(1, 4))
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(df.height.hist, by=df.classroom, layout=(2, 2))
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))
|
||||
|
||||
axes = df.height.hist(by=df.category, layout=(4, 2), figsize=(12, 7))
|
||||
self._check_axes_shape(axes, axes_num=4, layout=(4, 2), figsize=(12, 7))
|
||||
|
||||
def test_hist_no_overlap(self):
|
||||
from matplotlib.pyplot import (
|
||||
gcf,
|
||||
subplot,
|
||||
)
|
||||
|
||||
x = Series(np.random.randn(2))
|
||||
y = Series(np.random.randn(2))
|
||||
subplot(121)
|
||||
x.hist()
|
||||
subplot(122)
|
||||
y.hist()
|
||||
fig = gcf()
|
||||
axes = fig.axes
|
||||
assert len(axes) == 2
|
||||
|
||||
def test_hist_by_no_extra_plots(self):
|
||||
df = self.hist_df
|
||||
axes = df.height.hist(by=df.gender) # noqa
|
||||
assert len(self.plt.get_fignums()) == 1
|
||||
|
||||
def test_plot_fails_when_ax_differs_from_figure(self):
|
||||
from pylab import figure
|
||||
|
||||
fig1 = figure()
|
||||
fig2 = figure()
|
||||
ax1 = fig1.add_subplot(111)
|
||||
msg = "passed axis not bound to passed figure"
|
||||
with pytest.raises(AssertionError, match=msg):
|
||||
self.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.randint(1, 10))
|
||||
ax = ser.hist(histtype=histtype)
|
||||
self._check_patches_all_filled(ax, filled=expected)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"by, expected_axes_num, expected_layout", [(None, 1, (1, 1)), ("b", 2, (1, 2))]
|
||||
)
|
||||
def test_hist_with_legend(self, by, expected_axes_num, expected_layout):
|
||||
# GH 6279 - Series histogram can have a legend
|
||||
index = 15 * ["1"] + 15 * ["2"]
|
||||
s = Series(np.random.randn(30), index=index, name="a")
|
||||
s.index.name = "b"
|
||||
|
||||
# Use default_axes=True when plotting method generate subplots itself
|
||||
axes = _check_plot_works(s.hist, default_axes=True, legend=True, by=by)
|
||||
self._check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout)
|
||||
self._check_legend_labels(axes, "a")
|
||||
|
||||
@pytest.mark.parametrize("by", [None, "b"])
|
||||
def test_hist_with_legend_raises(self, by):
|
||||
# GH 6279 - Series histogram with legend and label raises
|
||||
index = 15 * ["1"] + 15 * ["2"]
|
||||
s = Series(np.random.randn(30), index=index, name="a")
|
||||
s.index.name = "b"
|
||||
|
||||
with pytest.raises(ValueError, match="Cannot use both legend and label"):
|
||||
s.hist(legend=True, by=by, label="c")
|
||||
|
||||
def test_hist_kwargs(self):
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.hist(bins=5, ax=ax)
|
||||
assert len(ax.patches) == 5
|
||||
self._check_text_labels(ax.yaxis.get_label(), "Frequency")
|
||||
tm.close()
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.hist(orientation="horizontal", ax=ax)
|
||||
self._check_text_labels(ax.xaxis.get_label(), "Frequency")
|
||||
tm.close()
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.hist(align="left", stacked=True, ax=ax)
|
||||
tm.close()
|
||||
|
||||
@td.skip_if_no_scipy
|
||||
def test_hist_kde(self):
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.hist(logy=True, ax=ax)
|
||||
self._check_ax_scales(ax, yaxis="log")
|
||||
xlabels = ax.get_xticklabels()
|
||||
# ticks are values, thus ticklabels are blank
|
||||
self._check_text_labels(xlabels, [""] * len(xlabels))
|
||||
ylabels = ax.get_yticklabels()
|
||||
self._check_text_labels(ylabels, [""] * len(ylabels))
|
||||
|
||||
_check_plot_works(self.ts.plot.kde)
|
||||
_check_plot_works(self.ts.plot.density)
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.kde(logy=True, ax=ax)
|
||||
self._check_ax_scales(ax, yaxis="log")
|
||||
xlabels = ax.get_xticklabels()
|
||||
self._check_text_labels(xlabels, [""] * len(xlabels))
|
||||
ylabels = ax.get_yticklabels()
|
||||
self._check_text_labels(ylabels, [""] * len(ylabels))
|
||||
|
||||
@td.skip_if_no_scipy
|
||||
def test_hist_kde_color(self):
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.hist(logy=True, bins=10, color="b", ax=ax)
|
||||
self._check_ax_scales(ax, yaxis="log")
|
||||
assert len(ax.patches) == 10
|
||||
self._check_colors(ax.patches, facecolors=["b"] * 10)
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.kde(logy=True, color="r", ax=ax)
|
||||
self._check_ax_scales(ax, yaxis="log")
|
||||
lines = ax.get_lines()
|
||||
assert len(lines) == 1
|
||||
self._check_colors(lines, ["r"])
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestDataFramePlots(TestPlotBase):
|
||||
def test_hist_df_legacy(self):
|
||||
from matplotlib.patches import Rectangle
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_check_plot_works(self.hist_df.hist)
|
||||
|
||||
# make sure layout is handled
|
||||
df = DataFrame(np.random.randn(100, 2))
|
||||
df[2] = to_datetime(
|
||||
np.random.randint(
|
||||
self.start_date_to_int64,
|
||||
self.end_date_to_int64,
|
||||
size=100,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(df.hist, grid=False)
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))
|
||||
assert not axes[1, 1].get_visible()
|
||||
|
||||
_check_plot_works(df[[2]].hist)
|
||||
df = DataFrame(np.random.randn(100, 1))
|
||||
_check_plot_works(df.hist)
|
||||
|
||||
# make sure layout is handled
|
||||
df = DataFrame(np.random.randn(100, 5))
|
||||
df[5] = to_datetime(
|
||||
np.random.randint(
|
||||
self.start_date_to_int64,
|
||||
self.end_date_to_int64,
|
||||
size=100,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(df.hist, layout=(4, 2))
|
||||
self._check_axes_shape(axes, axes_num=6, layout=(4, 2))
|
||||
|
||||
# make sure sharex, sharey is handled
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_check_plot_works(df.hist, sharex=True, sharey=True)
|
||||
|
||||
# handle figsize arg
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_check_plot_works(df.hist, figsize=(8, 10))
|
||||
|
||||
# check bins argument
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
_check_plot_works(df.hist, bins=5)
|
||||
|
||||
# make sure xlabelsize and xrot are handled
|
||||
ser = df[0]
|
||||
xf, yf = 20, 18
|
||||
xrot, yrot = 30, 40
|
||||
axes = ser.hist(xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot)
|
||||
self._check_ticks_props(
|
||||
axes, xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot
|
||||
)
|
||||
|
||||
xf, yf = 20, 18
|
||||
xrot, yrot = 30, 40
|
||||
axes = df.hist(xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot)
|
||||
self._check_ticks_props(
|
||||
axes, xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot
|
||||
)
|
||||
|
||||
tm.close()
|
||||
|
||||
ax = ser.hist(cumulative=True, bins=4, density=True)
|
||||
# height of last bin (index 5) must be 1.0
|
||||
rects = [x for x in ax.get_children() if isinstance(x, Rectangle)]
|
||||
tm.assert_almost_equal(rects[-1].get_height(), 1.0)
|
||||
|
||||
tm.close()
|
||||
ax = ser.hist(log=True)
|
||||
# scale of y must be 'log'
|
||||
self._check_ax_scales(ax, yaxis="log")
|
||||
|
||||
tm.close()
|
||||
|
||||
# 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.rand(10),
|
||||
"b": np.random.randint(0, 10, 10),
|
||||
"c": to_datetime(
|
||||
np.random.randint(
|
||||
1582800000000000000, 1583500000000000000, 10, dtype=np.int64
|
||||
)
|
||||
),
|
||||
"d": to_datetime(
|
||||
np.random.randint(
|
||||
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()
|
||||
|
||||
def test_hist_layout(self):
|
||||
df = DataFrame(np.random.randn(100, 2))
|
||||
df[2] = to_datetime(
|
||||
np.random.randint(
|
||||
self.start_date_to_int64,
|
||||
self.end_date_to_int64,
|
||||
size=100,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
|
||||
layout_to_expected_size = (
|
||||
{"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)},
|
||||
)
|
||||
|
||||
for layout_test in layout_to_expected_size:
|
||||
axes = df.hist(layout=layout_test["layout"])
|
||||
expected = layout_test["expected_size"]
|
||||
self._check_axes_shape(axes, axes_num=3, layout=expected)
|
||||
|
||||
# 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.randn(100, 2))
|
||||
df[2] = to_datetime(
|
||||
np.random.randint(
|
||||
self.start_date_to_int64,
|
||||
self.end_date_to_int64,
|
||||
size=100,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
# Use default_axes=True when plotting method generate subplots itself
|
||||
_check_plot_works(df.hist, default_axes=True)
|
||||
self.plt.tight_layout()
|
||||
|
||||
tm.close()
|
||||
|
||||
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,
|
||||
filterwarnings="always",
|
||||
column="length",
|
||||
by="animal",
|
||||
bins=5,
|
||||
xrot=0,
|
||||
)
|
||||
self._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.randint(1, 10, size=(100, 2)), columns=["a", "b"])
|
||||
ax = df.hist(histtype=histtype)
|
||||
self._check_patches_all_filled(ax, filled=expected)
|
||||
|
||||
@pytest.mark.parametrize("by", [None, "c"])
|
||||
@pytest.mark.parametrize("column", [None, "b"])
|
||||
def test_hist_with_legend(self, by, column):
|
||||
# GH 6279 - DataFrame histogram can have a legend
|
||||
expected_axes_num = 1 if by is None and column is not None else 2
|
||||
expected_layout = (1, expected_axes_num)
|
||||
expected_labels = column or ["a", "b"]
|
||||
if by is not None:
|
||||
expected_labels = [expected_labels] * 2
|
||||
|
||||
index = Index(15 * ["1"] + 15 * ["2"], name="c")
|
||||
df = DataFrame(np.random.randn(30, 2), index=index, columns=["a", "b"])
|
||||
|
||||
# Use default_axes=True when plotting method generate subplots itself
|
||||
axes = _check_plot_works(
|
||||
df.hist,
|
||||
default_axes=True,
|
||||
legend=True,
|
||||
by=by,
|
||||
column=column,
|
||||
)
|
||||
|
||||
self._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):
|
||||
self._check_legend_labels(ax, expected_label)
|
||||
|
||||
@pytest.mark.parametrize("by", [None, "c"])
|
||||
@pytest.mark.parametrize("column", [None, "b"])
|
||||
def test_hist_with_legend_raises(self, by, column):
|
||||
# GH 6279 - DataFrame histogram with legend and label raises
|
||||
index = Index(15 * ["1"] + 15 * ["2"], name="c")
|
||||
df = DataFrame(np.random.randn(30, 2), index=index, columns=["a", "b"])
|
||||
|
||||
with pytest.raises(ValueError, match="Cannot use both legend and label"):
|
||||
df.hist(legend=True, by=by, column=column, label="d")
|
||||
|
||||
def test_hist_df_kwargs(self):
|
||||
df = DataFrame(np.random.randn(10, 2))
|
||||
_, ax = self.plt.subplots()
|
||||
ax = df.plot.hist(bins=5, ax=ax)
|
||||
assert len(ax.patches) == 10
|
||||
|
||||
def test_hist_df_with_nonnumerics(self):
|
||||
# GH 9853
|
||||
with tm.RNGContext(1):
|
||||
df = DataFrame(np.random.randn(10, 4), columns=["A", "B", "C", "D"])
|
||||
df["E"] = ["x", "y"] * 5
|
||||
_, ax = self.plt.subplots()
|
||||
ax = df.plot.hist(bins=5, ax=ax)
|
||||
assert len(ax.patches) == 20
|
||||
|
||||
_, ax = self.plt.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.randn(30, 4), columns=list("abcd"))
|
||||
|
||||
# primary -> secondary
|
||||
_, ax = self.plt.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
|
||||
self._check_legend_labels(ax, labels=["a", "b (right)"])
|
||||
assert ax.get_yaxis().get_visible()
|
||||
assert ax.right_ax.get_yaxis().get_visible()
|
||||
tm.close()
|
||||
|
||||
# secondary -> secondary
|
||||
_, ax = self.plt.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
|
||||
self._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()
|
||||
tm.close()
|
||||
|
||||
# secondary -> primary
|
||||
_, ax = self.plt.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
|
||||
self._check_legend_labels(ax.left_ax, labels=["a (right)", "b"])
|
||||
assert ax.left_ax.get_yaxis().get_visible()
|
||||
assert ax.get_yaxis().get_visible()
|
||||
tm.close()
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestDataFrameGroupByPlots(TestPlotBase):
|
||||
def test_grouped_hist_legacy(self):
|
||||
from matplotlib.patches import Rectangle
|
||||
|
||||
from pandas.plotting._matplotlib.hist import _grouped_hist
|
||||
|
||||
df = DataFrame(np.random.randn(500, 1), columns=["A"])
|
||||
df["B"] = to_datetime(
|
||||
np.random.randint(
|
||||
self.start_date_to_int64,
|
||||
self.end_date_to_int64,
|
||||
size=500,
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
df["C"] = np.random.randint(0, 4, 500)
|
||||
df["D"] = ["X"] * 500
|
||||
|
||||
axes = _grouped_hist(df.A, by=df.C)
|
||||
self._check_axes_shape(axes, axes_num=4, layout=(2, 2))
|
||||
|
||||
tm.close()
|
||||
axes = df.hist(by=df.C)
|
||||
self._check_axes_shape(axes, axes_num=4, layout=(2, 2))
|
||||
|
||||
tm.close()
|
||||
# group by a key with single value
|
||||
axes = df.hist(by="D", rot=30)
|
||||
self._check_axes_shape(axes, axes_num=1, layout=(1, 1))
|
||||
self._check_ticks_props(axes, xrot=30)
|
||||
|
||||
tm.close()
|
||||
# make sure kwargs to hist are handled
|
||||
xf, yf = 20, 18
|
||||
xrot, yrot = 30, 40
|
||||
|
||||
axes = _grouped_hist(
|
||||
df.A,
|
||||
by=df.C,
|
||||
cumulative=True,
|
||||
bins=4,
|
||||
xlabelsize=xf,
|
||||
xrot=xrot,
|
||||
ylabelsize=yf,
|
||||
yrot=yrot,
|
||||
density=True,
|
||||
)
|
||||
# height of last bin (index 5) must be 1.0
|
||||
for ax in axes.ravel():
|
||||
rects = [x for x in ax.get_children() if isinstance(x, Rectangle)]
|
||||
height = rects[-1].get_height()
|
||||
tm.assert_almost_equal(height, 1.0)
|
||||
self._check_ticks_props(
|
||||
axes, xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot
|
||||
)
|
||||
|
||||
tm.close()
|
||||
axes = _grouped_hist(df.A, by=df.C, log=True)
|
||||
# scale of y must be 'log'
|
||||
self._check_ax_scales(axes, yaxis="log")
|
||||
|
||||
tm.close()
|
||||
# propagate attr exception from matplotlib.Axes.hist
|
||||
with tm.external_error_raised(AttributeError):
|
||||
_grouped_hist(df.A, by=df.C, foo="bar")
|
||||
|
||||
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.normal(166, 20, size=n))
|
||||
height = Series(np.random.normal(60, 10, size=n))
|
||||
with tm.RNGContext(42):
|
||||
gender_int = np.random.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(self.plt.get_fignums()) == 2
|
||||
tm.close()
|
||||
|
||||
def test_grouped_hist_layout(self):
|
||||
df = self.hist_df
|
||||
msg = "Layout of 1x1 must be larger than required size 2"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.hist(column="weight", by=df.gender, layout=(1, 1))
|
||||
|
||||
msg = "Layout of 1x3 must be larger than required size 4"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.hist(column="height", by=df.category, layout=(1, 3))
|
||||
|
||||
msg = "At least one dimension of layout must be positive"
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.hist(column="height", by=df.category, layout=(-1, -1))
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(
|
||||
df.hist, column="height", by=df.gender, layout=(2, 1)
|
||||
)
|
||||
self._check_axes_shape(axes, axes_num=2, layout=(2, 1))
|
||||
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(
|
||||
df.hist, column="height", by=df.gender, layout=(2, -1)
|
||||
)
|
||||
self._check_axes_shape(axes, axes_num=2, layout=(2, 1))
|
||||
|
||||
axes = df.hist(column="height", by=df.category, layout=(4, 1))
|
||||
self._check_axes_shape(axes, axes_num=4, layout=(4, 1))
|
||||
|
||||
axes = df.hist(column="height", by=df.category, layout=(-1, 1))
|
||||
self._check_axes_shape(axes, axes_num=4, layout=(4, 1))
|
||||
|
||||
axes = df.hist(column="height", by=df.category, layout=(4, 2), figsize=(12, 8))
|
||||
self._check_axes_shape(axes, axes_num=4, layout=(4, 2), figsize=(12, 8))
|
||||
tm.close()
|
||||
|
||||
# GH 6769
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(
|
||||
df.hist, column="height", by="classroom", layout=(2, 2)
|
||||
)
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))
|
||||
|
||||
# without column
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(df.hist, by="classroom")
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))
|
||||
|
||||
axes = df.hist(by="gender", layout=(3, 5))
|
||||
self._check_axes_shape(axes, axes_num=2, layout=(3, 5))
|
||||
|
||||
axes = df.hist(column=["height", "weight", "category"])
|
||||
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))
|
||||
|
||||
def test_grouped_hist_multiple_axes(self):
|
||||
# GH 6970, GH 7069
|
||||
df = self.hist_df
|
||||
|
||||
fig, axes = self.plt.subplots(2, 3)
|
||||
returned = df.hist(column=["height", "weight", "category"], ax=axes[0])
|
||||
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
tm.assert_numpy_array_equal(returned, axes[0])
|
||||
assert returned[0].figure is fig
|
||||
returned = df.hist(by="classroom", ax=axes[1])
|
||||
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
|
||||
tm.assert_numpy_array_equal(returned, axes[1])
|
||||
assert returned[0].figure is fig
|
||||
|
||||
fig, axes = self.plt.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):
|
||||
df = self.hist_df
|
||||
# GH4089
|
||||
ax1, ax2 = df.hist(column="height", by=df.gender, sharex=True)
|
||||
|
||||
# share x
|
||||
assert self.get_x_axis(ax1).joined(ax1, ax2)
|
||||
assert self.get_x_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
# don't share y
|
||||
assert not self.get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert not self.get_y_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
def test_axis_share_y(self):
|
||||
df = self.hist_df
|
||||
ax1, ax2 = df.hist(column="height", by=df.gender, sharey=True)
|
||||
|
||||
# share y
|
||||
assert self.get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert self.get_y_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
# don't share x
|
||||
assert not self.get_x_axis(ax1).joined(ax1, ax2)
|
||||
assert not self.get_x_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
def test_axis_share_xy(self):
|
||||
df = self.hist_df
|
||||
ax1, ax2 = df.hist(column="height", by=df.gender, sharex=True, sharey=True)
|
||||
|
||||
# share both x and y
|
||||
assert self.get_x_axis(ax1).joined(ax1, ax2)
|
||||
assert self.get_x_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
assert self.get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert self.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.randint(1, 10, size=(100, 2)), columns=["a", "b"])
|
||||
ax = df.hist(by="a", histtype=histtype)
|
||||
self._check_patches_all_filled(ax, filled=expected)
|
||||
@@ -0,0 +1,561 @@
|
||||
""" Test cases for misc plot functions """
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Series,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
TestPlotBase,
|
||||
_check_plot_works,
|
||||
)
|
||||
|
||||
import pandas.plotting as plotting
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@td.skip_if_mpl
|
||||
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
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestSeriesPlots(TestPlotBase):
|
||||
def setup_method(self, method):
|
||||
TestPlotBase.setup_method(self, method)
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.rcdefaults()
|
||||
|
||||
self.ts = tm.makeTimeSeries()
|
||||
self.ts.name = "ts"
|
||||
|
||||
def test_autocorrelation_plot(self):
|
||||
from pandas.plotting import autocorrelation_plot
|
||||
|
||||
# Ensure no UserWarning when making plot
|
||||
with tm.assert_produces_warning(None):
|
||||
_check_plot_works(autocorrelation_plot, series=self.ts)
|
||||
_check_plot_works(autocorrelation_plot, series=self.ts.values)
|
||||
|
||||
ax = autocorrelation_plot(self.ts, label="Test")
|
||||
self._check_legend_labels(ax, labels=["Test"])
|
||||
|
||||
def test_lag_plot(self):
|
||||
from pandas.plotting import lag_plot
|
||||
|
||||
_check_plot_works(lag_plot, series=self.ts)
|
||||
_check_plot_works(lag_plot, series=self.ts, lag=5)
|
||||
|
||||
def test_bootstrap_plot(self):
|
||||
from pandas.plotting import bootstrap_plot
|
||||
|
||||
_check_plot_works(bootstrap_plot, series=self.ts, size=10)
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestDataFramePlots(TestPlotBase):
|
||||
@td.skip_if_no_scipy
|
||||
@pytest.mark.parametrize("pass_axis", [False, True])
|
||||
def test_scatter_matrix_axis(self, pass_axis):
|
||||
from pandas.plotting._matplotlib.compat import mpl_ge_3_0_0
|
||||
|
||||
scatter_matrix = plotting.scatter_matrix
|
||||
|
||||
ax = None
|
||||
if pass_axis:
|
||||
_, ax = self.plt.subplots(3, 3)
|
||||
|
||||
with tm.RNGContext(42):
|
||||
df = DataFrame(np.random.randn(100, 3))
|
||||
|
||||
# we are plotting multiples on a sub-plot
|
||||
with tm.assert_produces_warning(
|
||||
UserWarning, raise_on_extra_warnings=mpl_ge_3_0_0()
|
||||
):
|
||||
axes = _check_plot_works(
|
||||
scatter_matrix,
|
||||
filterwarnings="always",
|
||||
frame=df,
|
||||
range_padding=0.1,
|
||||
ax=ax,
|
||||
)
|
||||
axes0_labels = axes[0][0].yaxis.get_majorticklabels()
|
||||
|
||||
# GH 5662
|
||||
expected = ["-2", "0", "2"]
|
||||
self._check_text_labels(axes0_labels, expected)
|
||||
self._check_ticks_props(axes, xlabelsize=8, xrot=90, ylabelsize=8, yrot=0)
|
||||
|
||||
df[0] = (df[0] - 2) / 3
|
||||
|
||||
# we are plotting multiples on a sub-plot
|
||||
with tm.assert_produces_warning(UserWarning):
|
||||
axes = _check_plot_works(
|
||||
scatter_matrix,
|
||||
filterwarnings="always",
|
||||
frame=df,
|
||||
range_padding=0.1,
|
||||
ax=ax,
|
||||
)
|
||||
axes0_labels = axes[0][0].yaxis.get_majorticklabels()
|
||||
expected = ["-1.0", "-0.5", "0.0"]
|
||||
self._check_text_labels(axes0_labels, expected)
|
||||
self._check_ticks_props(axes, xlabelsize=8, xrot=90, ylabelsize=8, yrot=0)
|
||||
|
||||
def test_andrews_curves(self, iris):
|
||||
from matplotlib import cm
|
||||
|
||||
from pandas.plotting import andrews_curves
|
||||
|
||||
df = iris
|
||||
# Ensure no UserWarning when making plot
|
||||
with tm.assert_produces_warning(None):
|
||||
_check_plot_works(andrews_curves, frame=df, class_column="Name")
|
||||
|
||||
rgba = ("#556270", "#4ECDC4", "#C7F464")
|
||||
ax = _check_plot_works(
|
||||
andrews_curves, frame=df, class_column="Name", color=rgba
|
||||
)
|
||||
self._check_colors(
|
||||
ax.get_lines()[:10], linecolors=rgba, mapping=df["Name"][:10]
|
||||
)
|
||||
|
||||
cnames = ["dodgerblue", "aquamarine", "seagreen"]
|
||||
ax = _check_plot_works(
|
||||
andrews_curves, frame=df, class_column="Name", color=cnames
|
||||
)
|
||||
self._check_colors(
|
||||
ax.get_lines()[:10], linecolors=cnames, mapping=df["Name"][:10]
|
||||
)
|
||||
|
||||
ax = _check_plot_works(
|
||||
andrews_curves, frame=df, class_column="Name", colormap=cm.jet
|
||||
)
|
||||
cmaps = [cm.jet(n) for n in np.linspace(0, 1, df["Name"].nunique())]
|
||||
self._check_colors(
|
||||
ax.get_lines()[:10], linecolors=cmaps, mapping=df["Name"][:10]
|
||||
)
|
||||
|
||||
length = 10
|
||||
df = DataFrame(
|
||||
{
|
||||
"A": np.random.rand(length),
|
||||
"B": np.random.rand(length),
|
||||
"C": np.random.rand(length),
|
||||
"Name": ["A"] * length,
|
||||
}
|
||||
)
|
||||
|
||||
_check_plot_works(andrews_curves, frame=df, class_column="Name")
|
||||
|
||||
rgba = ("#556270", "#4ECDC4", "#C7F464")
|
||||
ax = _check_plot_works(
|
||||
andrews_curves, frame=df, class_column="Name", color=rgba
|
||||
)
|
||||
self._check_colors(
|
||||
ax.get_lines()[:10], linecolors=rgba, mapping=df["Name"][:10]
|
||||
)
|
||||
|
||||
cnames = ["dodgerblue", "aquamarine", "seagreen"]
|
||||
ax = _check_plot_works(
|
||||
andrews_curves, frame=df, class_column="Name", color=cnames
|
||||
)
|
||||
self._check_colors(
|
||||
ax.get_lines()[:10], linecolors=cnames, mapping=df["Name"][:10]
|
||||
)
|
||||
|
||||
ax = _check_plot_works(
|
||||
andrews_curves, frame=df, class_column="Name", colormap=cm.jet
|
||||
)
|
||||
cmaps = [cm.jet(n) for n in np.linspace(0, 1, df["Name"].nunique())]
|
||||
self._check_colors(
|
||||
ax.get_lines()[:10], linecolors=cmaps, mapping=df["Name"][:10]
|
||||
)
|
||||
|
||||
colors = ["b", "g", "r"]
|
||||
df = DataFrame({"A": [1, 2, 3], "B": [1, 2, 3], "C": [1, 2, 3], "Name": colors})
|
||||
ax = andrews_curves(df, "Name", color=colors)
|
||||
handles, labels = ax.get_legend_handles_labels()
|
||||
self._check_colors(handles, linecolors=colors)
|
||||
|
||||
def test_parallel_coordinates(self, iris):
|
||||
from matplotlib import cm
|
||||
|
||||
from pandas.plotting import parallel_coordinates
|
||||
|
||||
df = iris
|
||||
|
||||
ax = _check_plot_works(parallel_coordinates, frame=df, class_column="Name")
|
||||
nlines = len(ax.get_lines())
|
||||
nxticks = len(ax.xaxis.get_ticklabels())
|
||||
|
||||
rgba = ("#556270", "#4ECDC4", "#C7F464")
|
||||
ax = _check_plot_works(
|
||||
parallel_coordinates, frame=df, class_column="Name", color=rgba
|
||||
)
|
||||
self._check_colors(
|
||||
ax.get_lines()[:10], linecolors=rgba, mapping=df["Name"][:10]
|
||||
)
|
||||
|
||||
cnames = ["dodgerblue", "aquamarine", "seagreen"]
|
||||
ax = _check_plot_works(
|
||||
parallel_coordinates, frame=df, class_column="Name", color=cnames
|
||||
)
|
||||
self._check_colors(
|
||||
ax.get_lines()[:10], linecolors=cnames, mapping=df["Name"][:10]
|
||||
)
|
||||
|
||||
ax = _check_plot_works(
|
||||
parallel_coordinates, frame=df, class_column="Name", colormap=cm.jet
|
||||
)
|
||||
cmaps = [cm.jet(n) for n in np.linspace(0, 1, df["Name"].nunique())]
|
||||
self._check_colors(
|
||||
ax.get_lines()[:10], linecolors=cmaps, mapping=df["Name"][:10]
|
||||
)
|
||||
|
||||
ax = _check_plot_works(
|
||||
parallel_coordinates, frame=df, class_column="Name", axvlines=False
|
||||
)
|
||||
assert len(ax.get_lines()) == (nlines - nxticks)
|
||||
|
||||
colors = ["b", "g", "r"]
|
||||
df = DataFrame({"A": [1, 2, 3], "B": [1, 2, 3], "C": [1, 2, 3], "Name": colors})
|
||||
ax = parallel_coordinates(df, "Name", color=colors)
|
||||
handles, labels = ax.get_legend_handles_labels()
|
||||
self._check_colors(handles, linecolors=colors)
|
||||
|
||||
# not sure if this is indicative of a problem
|
||||
@pytest.mark.filterwarnings("ignore:Attempting to set:UserWarning")
|
||||
def test_parallel_coordinates_with_sorted_labels(self):
|
||||
"""For #15908"""
|
||||
from pandas.plotting import parallel_coordinates
|
||||
|
||||
df = DataFrame(
|
||||
{
|
||||
"feat": list(range(30)),
|
||||
"class": [2 for _ in range(10)]
|
||||
+ [3 for _ in range(10)]
|
||||
+ [1 for _ in range(10)],
|
||||
}
|
||||
)
|
||||
ax = parallel_coordinates(df, "class", sort_labels=True)
|
||||
polylines, labels = ax.get_legend_handles_labels()
|
||||
color_label_tuples = zip(
|
||||
[polyline.get_color() for polyline in polylines], labels
|
||||
)
|
||||
ordered_color_label_tuples = sorted(color_label_tuples, key=lambda x: x[1])
|
||||
prev_next_tupels = zip(
|
||||
list(ordered_color_label_tuples[0:-1]), list(ordered_color_label_tuples[1:])
|
||||
)
|
||||
for prev, nxt in prev_next_tupels:
|
||||
# labels and colors are ordered strictly increasing
|
||||
assert prev[1] < nxt[1] and prev[0] < nxt[0]
|
||||
|
||||
def test_radviz(self, iris):
|
||||
from matplotlib import cm
|
||||
|
||||
from pandas.plotting import radviz
|
||||
|
||||
df = iris
|
||||
# Ensure no UserWarning when making plot
|
||||
with tm.assert_produces_warning(None):
|
||||
_check_plot_works(radviz, frame=df, class_column="Name")
|
||||
|
||||
rgba = ("#556270", "#4ECDC4", "#C7F464")
|
||||
ax = _check_plot_works(radviz, frame=df, class_column="Name", color=rgba)
|
||||
# skip Circle drawn as ticks
|
||||
patches = [p for p in ax.patches[:20] if p.get_label() != ""]
|
||||
self._check_colors(patches[:10], facecolors=rgba, mapping=df["Name"][:10])
|
||||
|
||||
cnames = ["dodgerblue", "aquamarine", "seagreen"]
|
||||
_check_plot_works(radviz, frame=df, class_column="Name", color=cnames)
|
||||
patches = [p for p in ax.patches[:20] if p.get_label() != ""]
|
||||
self._check_colors(patches, facecolors=cnames, mapping=df["Name"][:10])
|
||||
|
||||
_check_plot_works(radviz, frame=df, class_column="Name", colormap=cm.jet)
|
||||
cmaps = [cm.jet(n) for n in np.linspace(0, 1, df["Name"].nunique())]
|
||||
patches = [p for p in ax.patches[:20] if p.get_label() != ""]
|
||||
self._check_colors(patches, facecolors=cmaps, mapping=df["Name"][:10])
|
||||
|
||||
colors = [[0.0, 0.0, 1.0, 1.0], [0.0, 0.5, 1.0, 1.0], [1.0, 0.0, 0.0, 1.0]]
|
||||
df = DataFrame(
|
||||
{"A": [1, 2, 3], "B": [2, 1, 3], "C": [3, 2, 1], "Name": ["b", "g", "r"]}
|
||||
)
|
||||
ax = radviz(df, "Name", color=colors)
|
||||
handles, labels = ax.get_legend_handles_labels()
|
||||
self._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
|
||||
|
||||
# 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"])
|
||||
|
||||
# Case len(title) < len(df)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
df.plot(subplots=True, title=title[:2])
|
||||
|
||||
# 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)
|
||||
|
||||
# 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 np.random.seed isn't reset by get_standard_colors
|
||||
plotting.parallel_coordinates(df, 0)
|
||||
rand1 = np.random.random()
|
||||
plotting.parallel_coordinates(df, 0)
|
||||
rand2 = np.random.random()
|
||||
assert rand1 != rand2
|
||||
|
||||
# Make sure it produces the same colors every time it's called
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
color1 = get_standard_colors(1, color_type="random")
|
||||
color2 = get_standard_colors(1, color_type="random")
|
||||
assert color1 == color2
|
||||
|
||||
def test_get_standard_colors_default_num_colors(self):
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
# Make sure the default color_types returns the specified amount
|
||||
color1 = get_standard_colors(1, color_type="default")
|
||||
color2 = get_standard_colors(9, color_type="default")
|
||||
color3 = get_standard_colors(20, color_type="default")
|
||||
assert len(color1) == 1
|
||||
assert len(color2) == 9
|
||||
assert len(color3) == 20
|
||||
|
||||
def test_plot_single_color(self):
|
||||
# Example from #20585. All 3 bars should have the same color
|
||||
df = DataFrame(
|
||||
{
|
||||
"account-start": ["2017-02-03", "2017-03-03", "2017-01-01"],
|
||||
"client": ["Alice Anders", "Bob Baker", "Charlie Chaplin"],
|
||||
"balance": [-1432.32, 10.43, 30000.00],
|
||||
"db-id": [1234, 2424, 251],
|
||||
"proxy-id": [525, 1525, 2542],
|
||||
"rank": [52, 525, 32],
|
||||
}
|
||||
)
|
||||
ax = df.client.value_counts().plot.bar()
|
||||
colors = [rect.get_facecolor() for rect in ax.get_children()[0:3]]
|
||||
assert all(color == colors[0] for color in colors)
|
||||
|
||||
def test_get_standard_colors_no_appending(self):
|
||||
# GH20726
|
||||
|
||||
# Make sure not to add more colors so that matplotlib can cycle
|
||||
# correctly.
|
||||
from matplotlib import cm
|
||||
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
color_before = cm.gnuplot(range(5))
|
||||
color_after = get_standard_colors(1, color=color_before)
|
||||
assert len(color_after) == len(color_before)
|
||||
|
||||
df = DataFrame(np.random.randn(48, 4), columns=list("ABCD"))
|
||||
|
||||
color_list = cm.gnuplot(np.linspace(0, 1, 16))
|
||||
p = df.A.plot.bar(figsize=(16, 7), color=color_list)
|
||||
assert p.patches[1].get_facecolor() == p.patches[17].get_facecolor()
|
||||
|
||||
def test_dictionary_color(self):
|
||||
# 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.rand(2, 2), columns=data_files)
|
||||
dic_color = {"b": (0.3, 0.7, 0.7), "a": (0.5, 0.24, 0.6)}
|
||||
|
||||
# Bar color test
|
||||
ax = df1.plot(kind="bar", color=dic_color)
|
||||
colors = [rect.get_facecolor()[0:-1] for rect in ax.get_children()[0:3:2]]
|
||||
assert all(color == expected[index] for index, color in enumerate(colors))
|
||||
|
||||
# Line color test
|
||||
ax = df1.plot(kind="line", color=dic_color)
|
||||
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_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 = self.plt.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 = self.plt.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 = self.plt.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.randn(1000), "b": np.random.randn(1000)})
|
||||
|
||||
# Create figure
|
||||
fig = self.plt.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()
|
||||
@@ -0,0 +1,817 @@
|
||||
""" Test cases for Series.plot """
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
from itertools import chain
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import pandas.util._test_decorators as td
|
||||
|
||||
import pandas as pd
|
||||
from pandas import (
|
||||
DataFrame,
|
||||
Series,
|
||||
date_range,
|
||||
)
|
||||
import pandas._testing as tm
|
||||
from pandas.tests.plotting.common import (
|
||||
TestPlotBase,
|
||||
_check_plot_works,
|
||||
)
|
||||
|
||||
import pandas.plotting as plotting
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
@td.skip_if_no_mpl
|
||||
class TestSeriesPlots(TestPlotBase):
|
||||
def setup_method(self, method):
|
||||
TestPlotBase.setup_method(self, method)
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.rcdefaults()
|
||||
|
||||
self.ts = tm.makeTimeSeries()
|
||||
self.ts.name = "ts"
|
||||
|
||||
self.series = tm.makeStringSeries()
|
||||
self.series.name = "series"
|
||||
|
||||
self.iseries = tm.makePeriodSeries()
|
||||
self.iseries.name = "iseries"
|
||||
|
||||
def test_plot(self):
|
||||
_check_plot_works(self.ts.plot, label="foo")
|
||||
_check_plot_works(self.ts.plot, use_index=False)
|
||||
axes = _check_plot_works(self.ts.plot, rot=0)
|
||||
self._check_ticks_props(axes, xrot=0)
|
||||
|
||||
ax = _check_plot_works(self.ts.plot, style=".", logy=True)
|
||||
self._check_ax_scales(ax, yaxis="log")
|
||||
|
||||
ax = _check_plot_works(self.ts.plot, style=".", logx=True)
|
||||
self._check_ax_scales(ax, xaxis="log")
|
||||
|
||||
ax = _check_plot_works(self.ts.plot, style=".", loglog=True)
|
||||
self._check_ax_scales(ax, xaxis="log", yaxis="log")
|
||||
|
||||
_check_plot_works(self.ts[:10].plot.bar)
|
||||
_check_plot_works(self.ts.plot.area, stacked=False)
|
||||
_check_plot_works(self.iseries.plot)
|
||||
|
||||
for kind in ["line", "bar", "barh", "kde", "hist", "box"]:
|
||||
_check_plot_works(self.series[:5].plot, kind=kind)
|
||||
|
||||
_check_plot_works(self.series[:10].plot.barh)
|
||||
ax = _check_plot_works(Series(np.random.randn(10)).plot.bar, color="black")
|
||||
self._check_colors([ax.patches[0]], facecolors=["black"])
|
||||
|
||||
# GH 6951
|
||||
ax = _check_plot_works(self.ts.plot, subplots=True)
|
||||
self._check_axes_shape(ax, axes_num=1, layout=(1, 1))
|
||||
|
||||
ax = _check_plot_works(self.ts.plot, subplots=True, layout=(-1, 1))
|
||||
self._check_axes_shape(ax, axes_num=1, layout=(1, 1))
|
||||
ax = _check_plot_works(self.ts.plot, subplots=True, layout=(1, -1))
|
||||
self._check_axes_shape(ax, axes_num=1, layout=(1, 1))
|
||||
|
||||
def test_plot_figsize_and_title(self):
|
||||
# figsize and title
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.series.plot(title="Test", figsize=(16, 8), ax=ax)
|
||||
self._check_text_labels(ax.title, "Test")
|
||||
self._check_axes_shape(ax, axes_num=1, layout=(1, 1), figsize=(16, 8))
|
||||
|
||||
def test_dont_modify_rcParams(self):
|
||||
# GH 8242
|
||||
key = "axes.prop_cycle"
|
||||
colors = self.plt.rcParams[key]
|
||||
_, ax = self.plt.subplots()
|
||||
Series([1, 2, 3]).plot(ax=ax)
|
||||
assert colors == self.plt.rcParams[key]
|
||||
|
||||
def test_ts_line_lim(self):
|
||||
fig, ax = self.plt.subplots()
|
||||
ax = self.ts.plot(ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
lines = ax.get_lines()
|
||||
assert xmin <= lines[0].get_data(orig=False)[0][0]
|
||||
assert xmax >= lines[0].get_data(orig=False)[0][-1]
|
||||
tm.close()
|
||||
|
||||
ax = self.ts.plot(secondary_y=True, ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
lines = ax.get_lines()
|
||||
assert xmin <= lines[0].get_data(orig=False)[0][0]
|
||||
assert xmax >= lines[0].get_data(orig=False)[0][-1]
|
||||
|
||||
def test_ts_area_lim(self):
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.area(stacked=False, ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
line = ax.get_lines()[0].get_data(orig=False)[0]
|
||||
assert xmin <= line[0]
|
||||
assert xmax >= line[-1]
|
||||
self._check_ticks_props(ax, xrot=0)
|
||||
tm.close()
|
||||
|
||||
# GH 7471
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.area(stacked=False, x_compat=True, ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
line = ax.get_lines()[0].get_data(orig=False)[0]
|
||||
assert xmin <= line[0]
|
||||
assert xmax >= line[-1]
|
||||
self._check_ticks_props(ax, xrot=30)
|
||||
tm.close()
|
||||
|
||||
tz_ts = self.ts.copy()
|
||||
tz_ts.index = tz_ts.tz_localize("GMT").tz_convert("CET")
|
||||
_, ax = self.plt.subplots()
|
||||
ax = tz_ts.plot.area(stacked=False, x_compat=True, ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
line = ax.get_lines()[0].get_data(orig=False)[0]
|
||||
assert xmin <= line[0]
|
||||
assert xmax >= line[-1]
|
||||
self._check_ticks_props(ax, xrot=0)
|
||||
tm.close()
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
ax = tz_ts.plot.area(stacked=False, secondary_y=True, ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
line = ax.get_lines()[0].get_data(orig=False)[0]
|
||||
assert xmin <= line[0]
|
||||
assert xmax >= line[-1]
|
||||
self._check_ticks_props(ax, xrot=0)
|
||||
|
||||
def test_area_sharey_dont_overwrite(self):
|
||||
# GH37942
|
||||
fig, (ax1, ax2) = self.plt.subplots(1, 2, sharey=True)
|
||||
|
||||
abs(self.ts).plot(ax=ax1, kind="area")
|
||||
abs(self.ts).plot(ax=ax2, kind="area")
|
||||
|
||||
assert self.get_y_axis(ax1).joined(ax1, ax2)
|
||||
assert self.get_y_axis(ax2).joined(ax1, ax2)
|
||||
|
||||
def test_label(self):
|
||||
s = Series([1, 2])
|
||||
_, ax = self.plt.subplots()
|
||||
ax = s.plot(label="LABEL", legend=True, ax=ax)
|
||||
self._check_legend_labels(ax, labels=["LABEL"])
|
||||
self.plt.close()
|
||||
_, ax = self.plt.subplots()
|
||||
ax = s.plot(legend=True, ax=ax)
|
||||
self._check_legend_labels(ax, labels=["None"])
|
||||
self.plt.close()
|
||||
# get name from index
|
||||
s.name = "NAME"
|
||||
_, ax = self.plt.subplots()
|
||||
ax = s.plot(legend=True, ax=ax)
|
||||
self._check_legend_labels(ax, labels=["NAME"])
|
||||
self.plt.close()
|
||||
# override the default
|
||||
_, ax = self.plt.subplots()
|
||||
ax = s.plot(legend=True, label="LABEL", ax=ax)
|
||||
self._check_legend_labels(ax, labels=["LABEL"])
|
||||
self.plt.close()
|
||||
# Add lebel info, but don't draw
|
||||
_, ax = self.plt.subplots()
|
||||
ax = s.plot(legend=False, label="LABEL", ax=ax)
|
||||
assert ax.get_legend() is None # Hasn't been drawn
|
||||
ax.legend() # draw it
|
||||
self._check_legend_labels(ax, labels=["LABEL"])
|
||||
|
||||
def test_boolean(self):
|
||||
# GH 23719
|
||||
s = Series([False, False, True])
|
||||
_check_plot_works(s.plot, include_bool=True)
|
||||
|
||||
msg = "no numeric data to plot"
|
||||
with pytest.raises(TypeError, match=msg):
|
||||
_check_plot_works(s.plot)
|
||||
|
||||
def test_line_area_nan_series(self):
|
||||
values = [1, 2, np.nan, 3]
|
||||
s = Series(values)
|
||||
ts = Series(values, index=tm.makeDateIndex(k=4))
|
||||
|
||||
for d in [s, ts]:
|
||||
ax = _check_plot_works(d.plot)
|
||||
masked = ax.lines[0].get_ydata()
|
||||
# remove nan for comparison purpose
|
||||
exp = np.array([1, 2, 3], dtype=np.float64)
|
||||
tm.assert_numpy_array_equal(np.delete(masked.data, 2), exp)
|
||||
tm.assert_numpy_array_equal(
|
||||
masked.mask, np.array([False, False, True, False])
|
||||
)
|
||||
|
||||
expected = np.array([1, 2, 0, 3], dtype=np.float64)
|
||||
ax = _check_plot_works(d.plot, stacked=True)
|
||||
tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
|
||||
ax = _check_plot_works(d.plot.area)
|
||||
tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
|
||||
ax = _check_plot_works(d.plot.area, stacked=False)
|
||||
tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
|
||||
|
||||
def test_line_use_index_false(self):
|
||||
s = Series([1, 2, 3], index=["a", "b", "c"])
|
||||
s.index.name = "The Index"
|
||||
_, ax = self.plt.subplots()
|
||||
ax = s.plot(use_index=False, ax=ax)
|
||||
label = ax.get_xlabel()
|
||||
assert label == ""
|
||||
_, ax = self.plt.subplots()
|
||||
ax2 = s.plot.bar(use_index=False, ax=ax)
|
||||
label2 = ax2.get_xlabel()
|
||||
assert label2 == ""
|
||||
|
||||
def test_bar_log(self):
|
||||
expected = np.array([1e-1, 1e0, 1e1, 1e2, 1e3, 1e4])
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
ax = Series([200, 500]).plot.bar(log=True, ax=ax)
|
||||
tm.assert_numpy_array_equal(ax.yaxis.get_ticklocs(), expected)
|
||||
tm.close()
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
ax = Series([200, 500]).plot.barh(log=True, ax=ax)
|
||||
tm.assert_numpy_array_equal(ax.xaxis.get_ticklocs(), expected)
|
||||
tm.close()
|
||||
|
||||
# GH 9905
|
||||
expected = np.array([1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e0, 1e1])
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
ax = Series([0.1, 0.01, 0.001]).plot(log=True, kind="bar", ax=ax)
|
||||
ymin = 0.0007943282347242822
|
||||
ymax = 0.12589254117941673
|
||||
res = ax.get_ylim()
|
||||
tm.assert_almost_equal(res[0], ymin)
|
||||
tm.assert_almost_equal(res[1], ymax)
|
||||
tm.assert_numpy_array_equal(ax.yaxis.get_ticklocs(), expected)
|
||||
tm.close()
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
ax = Series([0.1, 0.01, 0.001]).plot(log=True, kind="barh", ax=ax)
|
||||
res = ax.get_xlim()
|
||||
tm.assert_almost_equal(res[0], ymin)
|
||||
tm.assert_almost_equal(res[1], ymax)
|
||||
tm.assert_numpy_array_equal(ax.xaxis.get_ticklocs(), expected)
|
||||
|
||||
def test_bar_ignore_index(self):
|
||||
df = Series([1, 2, 3, 4], index=["a", "b", "c", "d"])
|
||||
_, ax = self.plt.subplots()
|
||||
ax = df.plot.bar(use_index=False, ax=ax)
|
||||
self._check_text_labels(ax.get_xticklabels(), ["0", "1", "2", "3"])
|
||||
|
||||
def test_bar_user_colors(self):
|
||||
s = Series([1, 2, 3, 4])
|
||||
ax = s.plot.bar(color=["red", "blue", "blue", "red"])
|
||||
result = [p.get_facecolor() for p in ax.patches]
|
||||
expected = [
|
||||
(1.0, 0.0, 0.0, 1.0),
|
||||
(0.0, 0.0, 1.0, 1.0),
|
||||
(0.0, 0.0, 1.0, 1.0),
|
||||
(1.0, 0.0, 0.0, 1.0),
|
||||
]
|
||||
assert result == expected
|
||||
|
||||
def test_rotation(self):
|
||||
df = DataFrame(np.random.randn(5, 5))
|
||||
# Default rot 0
|
||||
_, ax = self.plt.subplots()
|
||||
axes = df.plot(ax=ax)
|
||||
self._check_ticks_props(axes, xrot=0)
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
axes = df.plot(rot=30, ax=ax)
|
||||
self._check_ticks_props(axes, xrot=30)
|
||||
|
||||
def test_irregular_datetime(self):
|
||||
from pandas.plotting._matplotlib.converter import DatetimeConverter
|
||||
|
||||
rng = date_range("1/1/2000", "3/1/2000")
|
||||
rng = rng[[0, 1, 2, 3, 5, 9, 10, 11, 12]]
|
||||
ser = Series(np.random.randn(len(rng)), rng)
|
||||
_, ax = self.plt.subplots()
|
||||
ax = ser.plot(ax=ax)
|
||||
xp = DatetimeConverter.convert(datetime(1999, 1, 1), "", ax)
|
||||
ax.set_xlim("1/1/1999", "1/1/2001")
|
||||
assert xp == ax.get_xlim()[0]
|
||||
self._check_ticks_props(ax, xrot=30)
|
||||
|
||||
def test_unsorted_index_xlim(self):
|
||||
ser = Series(
|
||||
[0.0, 1.0, np.nan, 3.0, 4.0, 5.0, 6.0],
|
||||
index=[1.0, 0.0, 3.0, 2.0, np.nan, 3.0, 2.0],
|
||||
)
|
||||
_, ax = self.plt.subplots()
|
||||
ax = ser.plot(ax=ax)
|
||||
xmin, xmax = ax.get_xlim()
|
||||
lines = ax.get_lines()
|
||||
assert xmin <= np.nanmin(lines[0].get_data(orig=False)[0])
|
||||
assert xmax >= np.nanmax(lines[0].get_data(orig=False)[0])
|
||||
|
||||
def test_pie_series(self):
|
||||
# if sum of values is less than 1.0, pie handle them as rate and draw
|
||||
# semicircle.
|
||||
series = Series(
|
||||
np.random.randint(1, 5), index=["a", "b", "c", "d", "e"], name="YLABEL"
|
||||
)
|
||||
ax = _check_plot_works(series.plot.pie)
|
||||
self._check_text_labels(ax.texts, series.index)
|
||||
assert ax.get_ylabel() == "YLABEL"
|
||||
|
||||
# without wedge labels
|
||||
ax = _check_plot_works(series.plot.pie, labels=None)
|
||||
self._check_text_labels(ax.texts, [""] * 5)
|
||||
|
||||
# with less colors than elements
|
||||
color_args = ["r", "g", "b"]
|
||||
ax = _check_plot_works(series.plot.pie, colors=color_args)
|
||||
|
||||
color_expected = ["r", "g", "b", "r", "g"]
|
||||
self._check_colors(ax.patches, facecolors=color_expected)
|
||||
|
||||
# with labels and colors
|
||||
labels = ["A", "B", "C", "D", "E"]
|
||||
color_args = ["r", "g", "b", "c", "m"]
|
||||
ax = _check_plot_works(series.plot.pie, labels=labels, colors=color_args)
|
||||
self._check_text_labels(ax.texts, labels)
|
||||
self._check_colors(ax.patches, facecolors=color_args)
|
||||
|
||||
# with autopct and fontsize
|
||||
ax = _check_plot_works(
|
||||
series.plot.pie, colors=color_args, autopct="%.2f", fontsize=7
|
||||
)
|
||||
pcts = [f"{s*100:.2f}" for s in series.values / series.sum()]
|
||||
expected_texts = list(chain.from_iterable(zip(series.index, pcts)))
|
||||
self._check_text_labels(ax.texts, expected_texts)
|
||||
for t in ax.texts:
|
||||
assert t.get_fontsize() == 7
|
||||
|
||||
# includes negative value
|
||||
series = Series([1, 2, 0, 4, -1], index=["a", "b", "c", "d", "e"])
|
||||
with pytest.raises(ValueError, match="pie plot doesn't allow negative values"):
|
||||
series.plot.pie()
|
||||
|
||||
# includes nan
|
||||
series = Series([1, 2, np.nan, 4], index=["a", "b", "c", "d"], name="YLABEL")
|
||||
ax = _check_plot_works(series.plot.pie)
|
||||
self._check_text_labels(ax.texts, ["a", "b", "", "d"])
|
||||
|
||||
def test_pie_nan(self):
|
||||
s = Series([1, np.nan, 1, 1])
|
||||
_, ax = self.plt.subplots()
|
||||
ax = s.plot.pie(legend=True, ax=ax)
|
||||
expected = ["0", "", "2", "3"]
|
||||
result = [x.get_text() for x in ax.texts]
|
||||
assert result == expected
|
||||
|
||||
def test_df_series_secondary_legend(self):
|
||||
# GH 9779
|
||||
df = DataFrame(np.random.randn(30, 3), columns=list("abc"))
|
||||
s = Series(np.random.randn(30), name="x")
|
||||
|
||||
# primary -> secondary (without passing ax)
|
||||
_, ax = self.plt.subplots()
|
||||
ax = df.plot(ax=ax)
|
||||
s.plot(legend=True, secondary_y=True, ax=ax)
|
||||
# both legends are drawn on left ax
|
||||
# left and right axis must be visible
|
||||
self._check_legend_labels(ax, labels=["a", "b", "c", "x (right)"])
|
||||
assert ax.get_yaxis().get_visible()
|
||||
assert ax.right_ax.get_yaxis().get_visible()
|
||||
tm.close()
|
||||
|
||||
# primary -> secondary (with passing ax)
|
||||
_, ax = self.plt.subplots()
|
||||
ax = df.plot(ax=ax)
|
||||
s.plot(ax=ax, legend=True, secondary_y=True)
|
||||
# both legends are drawn on left ax
|
||||
# left and right axis must be visible
|
||||
self._check_legend_labels(ax, labels=["a", "b", "c", "x (right)"])
|
||||
assert ax.get_yaxis().get_visible()
|
||||
assert ax.right_ax.get_yaxis().get_visible()
|
||||
tm.close()
|
||||
|
||||
# secondary -> secondary (without passing ax)
|
||||
_, ax = self.plt.subplots()
|
||||
ax = df.plot(secondary_y=True, ax=ax)
|
||||
s.plot(legend=True, secondary_y=True, ax=ax)
|
||||
# both legends are drawn on left ax
|
||||
# left axis must be invisible and right axis must be visible
|
||||
expected = ["a (right)", "b (right)", "c (right)", "x (right)"]
|
||||
self._check_legend_labels(ax.left_ax, labels=expected)
|
||||
assert not ax.left_ax.get_yaxis().get_visible()
|
||||
assert ax.get_yaxis().get_visible()
|
||||
tm.close()
|
||||
|
||||
# secondary -> secondary (with passing ax)
|
||||
_, ax = self.plt.subplots()
|
||||
ax = df.plot(secondary_y=True, ax=ax)
|
||||
s.plot(ax=ax, legend=True, secondary_y=True)
|
||||
# both legends are drawn on left ax
|
||||
# left axis must be invisible and right axis must be visible
|
||||
expected = ["a (right)", "b (right)", "c (right)", "x (right)"]
|
||||
self._check_legend_labels(ax.left_ax, expected)
|
||||
assert not ax.left_ax.get_yaxis().get_visible()
|
||||
assert ax.get_yaxis().get_visible()
|
||||
tm.close()
|
||||
|
||||
# secondary -> secondary (with passing ax)
|
||||
_, ax = self.plt.subplots()
|
||||
ax = df.plot(secondary_y=True, mark_right=False, ax=ax)
|
||||
s.plot(ax=ax, legend=True, secondary_y=True)
|
||||
# both legends are drawn on left ax
|
||||
# left axis must be invisible and right axis must be visible
|
||||
expected = ["a", "b", "c", "x (right)"]
|
||||
self._check_legend_labels(ax.left_ax, expected)
|
||||
assert not ax.left_ax.get_yaxis().get_visible()
|
||||
assert ax.get_yaxis().get_visible()
|
||||
tm.close()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_logy, expected_scale", [(True, "log"), ("sym", "symlog")]
|
||||
)
|
||||
def test_secondary_logy(self, input_logy, expected_scale):
|
||||
# GH 25545
|
||||
s1 = Series(np.random.randn(30))
|
||||
s2 = Series(np.random.randn(30))
|
||||
|
||||
# GH 24980
|
||||
ax1 = s1.plot(logy=input_logy)
|
||||
ax2 = s2.plot(secondary_y=True, logy=input_logy)
|
||||
|
||||
assert ax1.get_yscale() == expected_scale
|
||||
assert ax2.get_yscale() == expected_scale
|
||||
|
||||
def test_plot_fails_with_dupe_color_and_style(self):
|
||||
x = Series(np.random.randn(2))
|
||||
_, ax = self.plt.subplots()
|
||||
msg = (
|
||||
"Cannot pass 'style' string with a color symbol and 'color' keyword "
|
||||
"argument. Please use one or the other or pass 'style' without a color "
|
||||
"symbol"
|
||||
)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
x.plot(style="k--", color="k", ax=ax)
|
||||
|
||||
@td.skip_if_no_scipy
|
||||
def test_kde_kwargs(self):
|
||||
sample_points = np.linspace(-100, 100, 20)
|
||||
_check_plot_works(self.ts.plot.kde, bw_method="scott", ind=20)
|
||||
_check_plot_works(self.ts.plot.kde, bw_method=None, ind=20)
|
||||
_check_plot_works(self.ts.plot.kde, bw_method=None, ind=np.int_(20))
|
||||
_check_plot_works(self.ts.plot.kde, bw_method=0.5, ind=sample_points)
|
||||
_check_plot_works(self.ts.plot.density, bw_method=0.5, ind=sample_points)
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.kde(logy=True, bw_method=0.5, ind=sample_points, ax=ax)
|
||||
self._check_ax_scales(ax, yaxis="log")
|
||||
self._check_text_labels(ax.yaxis.get_label(), "Density")
|
||||
|
||||
@td.skip_if_no_scipy
|
||||
def test_kde_missing_vals(self):
|
||||
s = Series(np.random.uniform(size=50))
|
||||
s[0] = np.nan
|
||||
axes = _check_plot_works(s.plot.kde)
|
||||
|
||||
# gh-14821: check if the values have any missing values
|
||||
assert any(~np.isnan(axes.lines[0].get_xdata()))
|
||||
|
||||
def test_boxplot_series(self):
|
||||
_, ax = self.plt.subplots()
|
||||
ax = self.ts.plot.box(logy=True, ax=ax)
|
||||
self._check_ax_scales(ax, yaxis="log")
|
||||
xlabels = ax.get_xticklabels()
|
||||
self._check_text_labels(xlabels, [self.ts.name])
|
||||
ylabels = ax.get_yticklabels()
|
||||
self._check_text_labels(ylabels, [""] * len(ylabels))
|
||||
|
||||
def test_kind_both_ways(self):
|
||||
s = Series(range(3))
|
||||
kinds = (
|
||||
plotting.PlotAccessor._common_kinds + plotting.PlotAccessor._series_kinds
|
||||
)
|
||||
for kind in kinds:
|
||||
_, ax = self.plt.subplots()
|
||||
s.plot(kind=kind, ax=ax)
|
||||
self.plt.close()
|
||||
_, ax = self.plt.subplots()
|
||||
getattr(s.plot, kind)()
|
||||
self.plt.close()
|
||||
|
||||
def test_invalid_plot_data(self):
|
||||
s = Series(list("abcd"))
|
||||
_, ax = self.plt.subplots()
|
||||
for kind in plotting.PlotAccessor._common_kinds:
|
||||
|
||||
msg = "no numeric data to plot"
|
||||
with pytest.raises(TypeError, match=msg):
|
||||
s.plot(kind=kind, ax=ax)
|
||||
|
||||
def test_valid_object_plot(self):
|
||||
s = Series(range(10), dtype=object)
|
||||
for kind in plotting.PlotAccessor._common_kinds:
|
||||
_check_plot_works(s.plot, kind=kind)
|
||||
|
||||
def test_partially_invalid_plot_data(self):
|
||||
s = Series(["a", "b", 1.0, 2])
|
||||
_, ax = self.plt.subplots()
|
||||
for kind in plotting.PlotAccessor._common_kinds:
|
||||
|
||||
msg = "no numeric data to plot"
|
||||
with pytest.raises(TypeError, match=msg):
|
||||
s.plot(kind=kind, ax=ax)
|
||||
|
||||
def test_invalid_kind(self):
|
||||
s = Series([1, 2])
|
||||
with pytest.raises(ValueError, match="invalid_kind is not a valid plot kind"):
|
||||
s.plot(kind="invalid_kind")
|
||||
|
||||
def test_dup_datetime_index_plot(self):
|
||||
dr1 = date_range("1/1/2009", periods=4)
|
||||
dr2 = date_range("1/2/2009", periods=4)
|
||||
index = dr1.append(dr2)
|
||||
values = np.random.randn(index.size)
|
||||
s = Series(values, index=index)
|
||||
_check_plot_works(s.plot)
|
||||
|
||||
def test_errorbar_asymmetrical(self):
|
||||
# GH9536
|
||||
s = Series(np.arange(10), name="x")
|
||||
err = np.random.rand(2, 10)
|
||||
|
||||
ax = s.plot(yerr=err, xerr=err)
|
||||
|
||||
result = np.vstack([i.vertices[:, 1] for i in ax.collections[1].get_paths()])
|
||||
expected = (err.T * np.array([-1, 1])) + s.to_numpy().reshape(-1, 1)
|
||||
tm.assert_numpy_array_equal(result, expected)
|
||||
|
||||
msg = (
|
||||
"Asymmetrical error bars should be provided "
|
||||
f"with the shape \\(2, {len(s)}\\)"
|
||||
)
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
s.plot(yerr=np.random.rand(2, 11))
|
||||
|
||||
tm.close()
|
||||
|
||||
def test_errorbar_plot(self):
|
||||
|
||||
s = Series(np.arange(10), name="x")
|
||||
s_err = np.random.randn(10)
|
||||
d_err = DataFrame(np.random.randn(10, 2), index=s.index, columns=["x", "y"])
|
||||
# test line and bar plots
|
||||
kinds = ["line", "bar"]
|
||||
for kind in kinds:
|
||||
ax = _check_plot_works(s.plot, yerr=Series(s_err), kind=kind)
|
||||
self._check_has_errorbars(ax, xerr=0, yerr=1)
|
||||
ax = _check_plot_works(s.plot, yerr=s_err, kind=kind)
|
||||
self._check_has_errorbars(ax, xerr=0, yerr=1)
|
||||
ax = _check_plot_works(s.plot, yerr=s_err.tolist(), kind=kind)
|
||||
self._check_has_errorbars(ax, xerr=0, yerr=1)
|
||||
ax = _check_plot_works(s.plot, yerr=d_err, kind=kind)
|
||||
self._check_has_errorbars(ax, xerr=0, yerr=1)
|
||||
ax = _check_plot_works(s.plot, xerr=0.2, yerr=0.2, kind=kind)
|
||||
self._check_has_errorbars(ax, xerr=1, yerr=1)
|
||||
|
||||
ax = _check_plot_works(s.plot, xerr=s_err)
|
||||
self._check_has_errorbars(ax, xerr=1, yerr=0)
|
||||
|
||||
# test time series plotting
|
||||
ix = date_range("1/1/2000", "1/1/2001", freq="M")
|
||||
ts = Series(np.arange(12), index=ix, name="x")
|
||||
ts_err = Series(np.random.randn(12), index=ix)
|
||||
td_err = DataFrame(np.random.randn(12, 2), index=ix, columns=["x", "y"])
|
||||
|
||||
ax = _check_plot_works(ts.plot, yerr=ts_err)
|
||||
self._check_has_errorbars(ax, xerr=0, yerr=1)
|
||||
ax = _check_plot_works(ts.plot, yerr=td_err)
|
||||
self._check_has_errorbars(ax, xerr=0, yerr=1)
|
||||
|
||||
# check incorrect lengths and types
|
||||
with tm.external_error_raised(ValueError):
|
||||
s.plot(yerr=np.arange(11))
|
||||
|
||||
s_err = ["zzz"] * 10
|
||||
with tm.external_error_raised(TypeError):
|
||||
s.plot(yerr=s_err)
|
||||
|
||||
def test_table(self):
|
||||
_check_plot_works(self.series.plot, table=True)
|
||||
_check_plot_works(self.series.plot, table=self.series)
|
||||
|
||||
def test_series_grid_settings(self):
|
||||
# Make sure plot defaults to rcParams['axes.grid'] setting, GH 9792
|
||||
self._check_grid_settings(
|
||||
Series([1, 2, 3]),
|
||||
plotting.PlotAccessor._series_kinds + plotting.PlotAccessor._common_kinds,
|
||||
)
|
||||
|
||||
def test_standard_colors(self):
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
for c in ["r", "red", "green", "#FF0000"]:
|
||||
result = get_standard_colors(1, color=c)
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(1, color=[c])
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(3, color=c)
|
||||
assert result == [c] * 3
|
||||
|
||||
result = get_standard_colors(3, color=[c])
|
||||
assert result == [c] * 3
|
||||
|
||||
def test_standard_colors_all(self):
|
||||
import matplotlib.colors as colors
|
||||
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
# multiple colors like mediumaquamarine
|
||||
for c in colors.cnames:
|
||||
result = get_standard_colors(num_colors=1, color=c)
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(num_colors=1, color=[c])
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(num_colors=3, color=c)
|
||||
assert result == [c] * 3
|
||||
|
||||
result = get_standard_colors(num_colors=3, color=[c])
|
||||
assert result == [c] * 3
|
||||
|
||||
# single letter colors like k
|
||||
for c in colors.ColorConverter.colors:
|
||||
result = get_standard_colors(num_colors=1, color=c)
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(num_colors=1, color=[c])
|
||||
assert result == [c]
|
||||
|
||||
result = get_standard_colors(num_colors=3, color=c)
|
||||
assert result == [c] * 3
|
||||
|
||||
result = get_standard_colors(num_colors=3, color=[c])
|
||||
assert result == [c] * 3
|
||||
|
||||
def test_series_plot_color_kwargs(self):
|
||||
# GH1890
|
||||
_, ax = self.plt.subplots()
|
||||
ax = Series(np.arange(12) + 1).plot(color="green", ax=ax)
|
||||
self._check_colors(ax.get_lines(), linecolors=["green"])
|
||||
|
||||
def test_time_series_plot_color_kwargs(self):
|
||||
# #1890
|
||||
_, ax = self.plt.subplots()
|
||||
ax = Series(np.arange(12) + 1, index=date_range("1/1/2000", periods=12)).plot(
|
||||
color="green", ax=ax
|
||||
)
|
||||
self._check_colors(ax.get_lines(), linecolors=["green"])
|
||||
|
||||
def test_time_series_plot_color_with_empty_kwargs(self):
|
||||
import matplotlib as mpl
|
||||
|
||||
def_colors = self._unpack_cycler(mpl.rcParams)
|
||||
index = date_range("1/1/2000", periods=12)
|
||||
s = Series(np.arange(1, 13), index=index)
|
||||
|
||||
ncolors = 3
|
||||
|
||||
_, ax = self.plt.subplots()
|
||||
for i in range(ncolors):
|
||||
ax = s.plot(ax=ax)
|
||||
self._check_colors(ax.get_lines(), linecolors=def_colors[:ncolors])
|
||||
|
||||
def test_xticklabels(self):
|
||||
# GH11529
|
||||
s = Series(np.arange(10), index=[f"P{i:02d}" for i in range(10)])
|
||||
_, ax = self.plt.subplots()
|
||||
ax = s.plot(xticks=[0, 3, 5, 9], ax=ax)
|
||||
exp = [f"P{i:02d}" for i in [0, 3, 5, 9]]
|
||||
self._check_text_labels(ax.get_xticklabels(), exp)
|
||||
|
||||
def test_xtick_barPlot(self):
|
||||
# GH28172
|
||||
s = Series(range(10), index=[f"P{i:02d}" for i in range(10)])
|
||||
ax = s.plot.bar(xticks=range(0, 11, 2))
|
||||
exp = np.array(list(range(0, 11, 2)))
|
||||
tm.assert_numpy_array_equal(exp, ax.get_xticks())
|
||||
|
||||
def test_custom_business_day_freq(self):
|
||||
# GH7222
|
||||
from pandas.tseries.offsets import CustomBusinessDay
|
||||
|
||||
s = Series(
|
||||
range(100, 121),
|
||||
index=pd.bdate_range(
|
||||
start="2014-05-01",
|
||||
end="2014-06-01",
|
||||
freq=CustomBusinessDay(holidays=["2014-05-26"]),
|
||||
),
|
||||
)
|
||||
|
||||
_check_plot_works(s.plot)
|
||||
|
||||
@pytest.mark.xfail(reason="GH#24426")
|
||||
def test_plot_accessor_updates_on_inplace(self):
|
||||
ser = Series([1, 2, 3, 4])
|
||||
_, ax = self.plt.subplots()
|
||||
ax = ser.plot(ax=ax)
|
||||
before = ax.xaxis.get_ticklocs()
|
||||
|
||||
ser.drop([0, 1], inplace=True)
|
||||
_, ax = self.plt.subplots()
|
||||
after = ax.xaxis.get_ticklocs()
|
||||
tm.assert_numpy_array_equal(before, after)
|
||||
|
||||
@pytest.mark.parametrize("kind", ["line", "area"])
|
||||
def test_plot_xlim_for_series(self, kind):
|
||||
# test if xlim is also correctly plotted in Series for line and area
|
||||
# GH 27686
|
||||
s = Series([2, 3])
|
||||
_, ax = self.plt.subplots()
|
||||
s.plot(kind=kind, ax=ax)
|
||||
xlims = ax.get_xlim()
|
||||
|
||||
assert xlims[0] < 0
|
||||
assert xlims[1] > 1
|
||||
|
||||
def test_plot_no_rows(self):
|
||||
# GH 27758
|
||||
df = Series(dtype=int)
|
||||
assert df.empty
|
||||
ax = df.plot()
|
||||
assert len(ax.get_lines()) == 1
|
||||
line = ax.get_lines()[0]
|
||||
assert len(line.get_xdata()) == 0
|
||||
assert len(line.get_ydata()) == 0
|
||||
|
||||
def test_plot_no_numeric_data(self):
|
||||
df = Series(["a", "b", "c"])
|
||||
with pytest.raises(TypeError, match="no numeric data to plot"):
|
||||
df.plot()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data, index",
|
||||
[
|
||||
([1, 2, 3, 4], [3, 2, 1, 0]),
|
||||
([10, 50, 20, 30], [1910, 1920, 1980, 1950]),
|
||||
],
|
||||
)
|
||||
def test_plot_order(self, data, index):
|
||||
# GH38865 Verify plot order of a Series
|
||||
ser = Series(data=data, index=index)
|
||||
ax = ser.plot(kind="bar")
|
||||
|
||||
expected = ser.tolist()
|
||||
result = [
|
||||
patch.get_bbox().ymax
|
||||
for patch in sorted(ax.patches, key=lambda patch: patch.get_bbox().xmax)
|
||||
]
|
||||
assert expected == result
|
||||
|
||||
def test_style_single_ok(self):
|
||||
s = Series([1, 2])
|
||||
ax = s.plot(style="s", color="C3")
|
||||
assert ax.lines[0].get_color() == "C3"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index_name, old_label, new_label",
|
||||
[(None, "", "new"), ("old", "old", "new"), (None, "", "")],
|
||||
)
|
||||
@pytest.mark.parametrize("kind", ["line", "area", "bar"])
|
||||
def test_xlabel_ylabel_series(self, kind, index_name, old_label, new_label):
|
||||
# GH 9093
|
||||
ser = Series([1, 2, 3, 4])
|
||||
ser.index.name = index_name
|
||||
|
||||
# default is the ylabel is not shown and xlabel is index name
|
||||
ax = ser.plot(kind=kind)
|
||||
assert ax.get_ylabel() == ""
|
||||
assert ax.get_xlabel() == old_label
|
||||
|
||||
# old xlabel will be overridden and assigned ylabel will be used as ylabel
|
||||
ax = ser.plot(kind=kind, ylabel=new_label, xlabel=new_label)
|
||||
assert ax.get_ylabel() == new_label
|
||||
assert ax.get_xlabel() == new_label
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index",
|
||||
[
|
||||
pd.timedelta_range(start=0, periods=2, freq="D"),
|
||||
[pd.Timedelta(days=1), pd.Timedelta(days=2)],
|
||||
],
|
||||
)
|
||||
def test_timedelta_index(self, index):
|
||||
# GH37454
|
||||
xlims = (3, 1)
|
||||
ax = Series([1, 2], index=index).plot(xlim=(xlims))
|
||||
assert ax.get_xlim() == (3, 1)
|
||||
@@ -0,0 +1,159 @@
|
||||
import pytest
|
||||
|
||||
from pandas import Series
|
||||
|
||||
pytest.importorskip("matplotlib")
|
||||
from pandas.plotting._matplotlib.style import get_standard_colors
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
class TestGetStandardColors:
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected",
|
||||
[
|
||||
(3, ["red", "green", "blue"]),
|
||||
(5, ["red", "green", "blue", "red", "green"]),
|
||||
(7, ["red", "green", "blue", "red", "green", "blue", "red"]),
|
||||
(2, ["red", "green"]),
|
||||
(1, ["red"]),
|
||||
],
|
||||
)
|
||||
def test_default_colors_named_from_prop_cycle(self, num_colors, expected):
|
||||
import matplotlib as mpl
|
||||
from matplotlib.pyplot import cycler
|
||||
|
||||
mpl_params = {
|
||||
"axes.prop_cycle": cycler(color=["red", "green", "blue"]),
|
||||
}
|
||||
with mpl.rc_context(rc=mpl_params):
|
||||
result = get_standard_colors(num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected",
|
||||
[
|
||||
(1, ["b"]),
|
||||
(3, ["b", "g", "r"]),
|
||||
(4, ["b", "g", "r", "y"]),
|
||||
(5, ["b", "g", "r", "y", "b"]),
|
||||
(7, ["b", "g", "r", "y", "b", "g", "r"]),
|
||||
],
|
||||
)
|
||||
def test_default_colors_named_from_prop_cycle_string(self, num_colors, expected):
|
||||
import matplotlib as mpl
|
||||
from matplotlib.pyplot import cycler
|
||||
|
||||
mpl_params = {
|
||||
"axes.prop_cycle": cycler(color="bgry"),
|
||||
}
|
||||
with mpl.rc_context(rc=mpl_params):
|
||||
result = get_standard_colors(num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected_name",
|
||||
[
|
||||
(1, ["C0"]),
|
||||
(3, ["C0", "C1", "C2"]),
|
||||
(
|
||||
12,
|
||||
[
|
||||
"C0",
|
||||
"C1",
|
||||
"C2",
|
||||
"C3",
|
||||
"C4",
|
||||
"C5",
|
||||
"C6",
|
||||
"C7",
|
||||
"C8",
|
||||
"C9",
|
||||
"C0",
|
||||
"C1",
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_default_colors_named_undefined_prop_cycle(self, num_colors, expected_name):
|
||||
import matplotlib as mpl
|
||||
import matplotlib.colors as mcolors
|
||||
|
||||
with mpl.rc_context(rc={}):
|
||||
expected = [mcolors.to_hex(x) for x in expected_name]
|
||||
result = get_standard_colors(num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected",
|
||||
[
|
||||
(1, ["red", "green", (0.1, 0.2, 0.3)]),
|
||||
(2, ["red", "green", (0.1, 0.2, 0.3)]),
|
||||
(3, ["red", "green", (0.1, 0.2, 0.3)]),
|
||||
(4, ["red", "green", (0.1, 0.2, 0.3), "red"]),
|
||||
],
|
||||
)
|
||||
def test_user_input_color_sequence(self, num_colors, expected):
|
||||
color = ["red", "green", (0.1, 0.2, 0.3)]
|
||||
result = get_standard_colors(color=color, num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected",
|
||||
[
|
||||
(1, ["r", "g", "b", "k"]),
|
||||
(2, ["r", "g", "b", "k"]),
|
||||
(3, ["r", "g", "b", "k"]),
|
||||
(4, ["r", "g", "b", "k"]),
|
||||
(5, ["r", "g", "b", "k", "r"]),
|
||||
(6, ["r", "g", "b", "k", "r", "g"]),
|
||||
],
|
||||
)
|
||||
def test_user_input_color_string(self, num_colors, expected):
|
||||
color = "rgbk"
|
||||
result = get_standard_colors(color=color, num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_colors, expected",
|
||||
[
|
||||
(1, [(0.1, 0.2, 0.3)]),
|
||||
(2, [(0.1, 0.2, 0.3), (0.1, 0.2, 0.3)]),
|
||||
(3, [(0.1, 0.2, 0.3), (0.1, 0.2, 0.3), (0.1, 0.2, 0.3)]),
|
||||
],
|
||||
)
|
||||
def test_user_input_color_floats(self, num_colors, expected):
|
||||
color = (0.1, 0.2, 0.3)
|
||||
result = get_standard_colors(color=color, num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"color, num_colors, expected",
|
||||
[
|
||||
("Crimson", 1, ["Crimson"]),
|
||||
("DodgerBlue", 2, ["DodgerBlue", "DodgerBlue"]),
|
||||
("firebrick", 3, ["firebrick", "firebrick", "firebrick"]),
|
||||
],
|
||||
)
|
||||
def test_user_input_named_color_string(self, color, num_colors, expected):
|
||||
result = get_standard_colors(color=color, num_colors=num_colors)
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.parametrize("color", ["", [], (), Series([], dtype="object")])
|
||||
def test_empty_color_raises(self, color):
|
||||
with pytest.raises(ValueError, match="Invalid color argument"):
|
||||
get_standard_colors(color=color, num_colors=1)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"color",
|
||||
[
|
||||
"bad_color",
|
||||
("red", "green", "bad_color"),
|
||||
(0.1,),
|
||||
(0.1, 0.2),
|
||||
(0.1, 0.2, 0.3, 0.4, 0.5), # must be either 3 or 4 floats
|
||||
],
|
||||
)
|
||||
def test_bad_color_raises(self, color):
|
||||
with pytest.raises(ValueError, match="Invalid color"):
|
||||
get_standard_colors(color=color, num_colors=5)
|
||||
Reference in New Issue
Block a user