Skip to content

BUG: repair 'style' kwd handling in DataFrame.plot (#21003) #33821

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Sep 5, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/source/whatsnew/v1.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1061,11 +1061,12 @@ I/O
Plotting
^^^^^^^^

- :func:`.plot` for line/bar now accepts color by dictonary (:issue:`8193`).
- :func:`.plot` for line/bar now accepts color by dictionary (:issue:`8193`).
- Bug in :meth:`DataFrame.plot.hist` where weights are not working for multiple columns (:issue:`33173`)
- Bug in :meth:`DataFrame.boxplot` and :meth:`DataFrame.plot.boxplot` lost color attributes of ``medianprops``, ``whiskerprops``, ``capprops`` and ``medianprops`` (:issue:`30346`)
- Bug in :meth:`DataFrame.hist` where the order of ``column`` argument was ignored (:issue:`29235`)
- Bug in :meth:`DataFrame.plot.scatter` that when adding multiple plots with different ``cmap``, colorbars alway use the first ``cmap`` (:issue:`33389`)
- Bug in :meth:`DataFrame.plot` where a marker letter in the ``style`` keyword sometimes causes a ``ValueError`` (:issue:`21003`)
- Bug in :meth:`DataFrame.plot.scatter` was adding a colorbar to the plot even if the argument `c` was assigned to a column containing color names (:issue:`34316`)
- Bug in :meth:`pandas.plotting.bootstrap_plot` was causing cluttered axes and overlapping labels (:issue:`34905`)

Expand Down
26 changes: 16 additions & 10 deletions pandas/plotting/_matplotlib/core.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import re
from typing import List, Optional
import warnings

Expand Down Expand Up @@ -44,6 +43,15 @@
)


def _color_in_style(style: str) -> bool:
"""
Check if there is a color letter in the style string.
"""
from matplotlib.colors import BASE_COLORS

return not set(BASE_COLORS).isdisjoint(style)


class MPLPlot:
"""
Base class for assembling a pandas plot using matplotlib
Expand Down Expand Up @@ -201,7 +209,6 @@ def __init__(
self._validate_color_args()

def _validate_color_args(self):
import matplotlib.colors

if (
"color" in self.kwds
Expand Down Expand Up @@ -234,13 +241,12 @@ def _validate_color_args(self):
styles = [self.style]
# need only a single match
for s in styles:
for char in s:
if char in matplotlib.colors.BASE_COLORS:
raise ValueError(
"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"
)
if _color_in_style(s):
raise ValueError(
"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"
)

def _iter_data(self, data=None, keep_index=False, fillna=None):
if data is None:
Expand Down Expand Up @@ -740,7 +746,7 @@ def _apply_style_colors(self, colors, kwds, col_num, label):
style = self.style

has_color = "color" in kwds or self.colormap is not None
nocolor_style = style is None or re.match("[a-z]+", style) is None
nocolor_style = style is None or not _color_in_style(style)
if (has_color or self.subplots) and nocolor_style:
if isinstance(colors, dict):
kwds["color"] = colors[label]
Expand Down
11 changes: 11 additions & 0 deletions pandas/tests/plotting/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,17 @@ def test_color_and_style_arguments(self):
# if there is a color symbol in the style strings:
with pytest.raises(ValueError):
df.plot(color=["red", "black"], style=["k-", "r--"])
# k for black, r for red

@pytest.mark.parametrize("color", ["green", ["yellow", "red", "green", "blue"]])
def test_color_and_marker(self, color):
# GH 21003
df = DataFrame(np.random.random((7, 4)))
ax = df.plot(color=color, style="d--") # d for diamond
green_line = ax.lines[2]
assert green_line.get_color() == "green"
assert green_line.get_marker() == "d"
assert green_line.get_linestyle() == "--"
Copy link
Member

Choose a reason for hiding this comment

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

since you might need another rebase, just one minor comment, as mentioned in #33821 (comment)

I still prefer to not test only green line but all lines using parametrization, i had an example there

Copy link
Contributor Author

Choose a reason for hiding this comment

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

since you might need another rebase, just one minor comment, as mentioned in #33821 (comment)

Why do I need another rebase? I just merged upstream/master 6 days ago which unfortunately left one CI issue. I don't think another rebase will solve that?

I still prefer to not test only green line but all lines using parametrization, i had an example there

Well the function is parametrized I'm testing the inputs color="green" and color=["yellow", "red", "green", "blue"] . The only thing is, to simplify the code, I'm only checking the third line, the one that is green for both inputs in the parametrize. I'm confident that this is enough. Checking all four lines would be overkill. After all, checking all four lines (as opposed to the third) would only guard against absurd bugs such as if we suddenly had code that ignored the color kwarg and made every line green.

Copy link
Member

Choose a reason for hiding this comment

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

I just merged upstream/master 6 days ago which unfortunately left one CI issue. I don't think another rebase will solve that?

Might do if upstream/master has changed

Regarding the test - unfortunately, absurd bugs do sometimes happen. FWIW, I think the suggestion to check all lines' colours is sensible


def test_nonnumeric_exclude(self):
df = DataFrame({"A": ["x", "y", "z"], "B": [1, 2, 3]})
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/plotting/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,7 @@ def test_plot_no_numeric_data(self):
def test_style_single_ok(self):
s = pd.Series([1, 2])
ax = s.plot(style="s", color="C3")
assert ax.lines[0].get_color() == ["C3"]
assert ax.lines[0].get_color() == "C3"

@pytest.mark.parametrize(
"index_name, old_label, new_label",
Expand Down