Skip to content

Commit c0f12a1

Browse files
authored
REF: avoid statefulness in 'color' args (#55904)
* REF: avoid altering state with 'color' * de-dup
1 parent ccc04c4 commit c0f12a1

File tree

3 files changed

+70
-67
lines changed

3 files changed

+70
-67
lines changed

pandas/plotting/_matplotlib/boxplot.py

Lines changed: 36 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from matplotlib.artist import setp
1111
import numpy as np
1212

13+
from pandas._libs import lib
1314
from pandas.util._decorators import cache_readonly
1415
from pandas.util._exceptions import find_stack_level
1516

@@ -113,26 +114,26 @@ def _plot( # type: ignore[override]
113114
else:
114115
return ax, bp
115116

116-
def _validate_color_args(self):
117-
if "color" in self.kwds:
118-
if self.colormap is not None:
119-
warnings.warn(
120-
"'color' and 'colormap' cannot be used "
121-
"simultaneously. Using 'color'",
122-
stacklevel=find_stack_level(),
123-
)
124-
self.color = self.kwds.pop("color")
117+
def _validate_color_args(self, color, colormap):
118+
if color is lib.no_default:
119+
return None
125120

126-
if isinstance(self.color, dict):
127-
valid_keys = ["boxes", "whiskers", "medians", "caps"]
128-
for key in self.color:
129-
if key not in valid_keys:
130-
raise ValueError(
131-
f"color dict contains invalid key '{key}'. "
132-
f"The key must be either {valid_keys}"
133-
)
134-
else:
135-
self.color = None
121+
if colormap is not None:
122+
warnings.warn(
123+
"'color' and 'colormap' cannot be used "
124+
"simultaneously. Using 'color'",
125+
stacklevel=find_stack_level(),
126+
)
127+
128+
if isinstance(color, dict):
129+
valid_keys = ["boxes", "whiskers", "medians", "caps"]
130+
for key in color:
131+
if key not in valid_keys:
132+
raise ValueError(
133+
f"color dict contains invalid key '{key}'. "
134+
f"The key must be either {valid_keys}"
135+
)
136+
return color
136137

137138
@cache_readonly
138139
def _color_attrs(self):
@@ -182,16 +183,8 @@ def maybe_color_bp(self, bp) -> None:
182183
medians = self.color or self._medians_c
183184
caps = self.color or self._caps_c
184185

185-
# GH 30346, when users specifying those arguments explicitly, our defaults
186-
# for these four kwargs should be overridden; if not, use Pandas settings
187-
if not self.kwds.get("boxprops"):
188-
setp(bp["boxes"], color=boxes, alpha=1)
189-
if not self.kwds.get("whiskerprops"):
190-
setp(bp["whiskers"], color=whiskers, alpha=1)
191-
if not self.kwds.get("medianprops"):
192-
setp(bp["medians"], color=medians, alpha=1)
193-
if not self.kwds.get("capprops"):
194-
setp(bp["caps"], color=caps, alpha=1)
186+
color_tup = (boxes, whiskers, medians, caps)
187+
maybe_color_bp(bp, color_tup=color_tup, **self.kwds)
195188

196189
def _make_plot(self, fig: Figure) -> None:
197190
if self.subplots:
@@ -276,6 +269,19 @@ def result(self):
276269
return self._return_obj
277270

278271

272+
def maybe_color_bp(bp, color_tup, **kwds) -> None:
273+
# GH#30346, when users specifying those arguments explicitly, our defaults
274+
# for these four kwargs should be overridden; if not, use Pandas settings
275+
if not kwds.get("boxprops"):
276+
setp(bp["boxes"], color=color_tup[0], alpha=1)
277+
if not kwds.get("whiskerprops"):
278+
setp(bp["whiskers"], color=color_tup[1], alpha=1)
279+
if not kwds.get("medianprops"):
280+
setp(bp["medians"], color=color_tup[2], alpha=1)
281+
if not kwds.get("capprops"):
282+
setp(bp["caps"], color=color_tup[3], alpha=1)
283+
284+
279285
def _grouped_plot_by_column(
280286
plotf,
281287
data,
@@ -389,18 +395,6 @@ def _get_colors():
389395

390396
return result
391397

392-
def maybe_color_bp(bp, **kwds) -> None:
393-
# GH 30346, when users specifying those arguments explicitly, our defaults
394-
# for these four kwargs should be overridden; if not, use Pandas settings
395-
if not kwds.get("boxprops"):
396-
setp(bp["boxes"], color=colors[0], alpha=1)
397-
if not kwds.get("whiskerprops"):
398-
setp(bp["whiskers"], color=colors[1], alpha=1)
399-
if not kwds.get("medianprops"):
400-
setp(bp["medians"], color=colors[2], alpha=1)
401-
if not kwds.get("capprops"):
402-
setp(bp["caps"], color=colors[3], alpha=1)
403-
404398
def plot_group(keys, values, ax: Axes, **kwds):
405399
# GH 45465: xlabel/ylabel need to be popped out before plotting happens
406400
xlabel, ylabel = kwds.pop("xlabel", None), kwds.pop("ylabel", None)
@@ -419,7 +413,7 @@ def plot_group(keys, values, ax: Axes, **kwds):
419413
_set_ticklabels(
420414
ax=ax, labels=keys, is_vertical=kwds.get("vert", True), rotation=rot
421415
)
422-
maybe_color_bp(bp, **kwds)
416+
maybe_color_bp(bp, color_tup=colors, **kwds)
423417

424418
# Return axes in multiplot case, maybe revisit later # 985
425419
if return_type == "dict":

pandas/plotting/_matplotlib/core.py

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,10 @@ def __init__(
271271

272272
self.kwds = kwds
273273

274-
self._validate_color_args()
274+
color = kwds.pop("color", lib.no_default)
275+
self.color = self._validate_color_args(color, self.colormap)
276+
assert "color" not in self.kwds
277+
275278
self.data = self._ensure_frame(self.data)
276279

277280
@final
@@ -396,34 +399,31 @@ def _validate_subplots_kwarg(
396399
out.append((idx_loc,))
397400
return out
398401

399-
def _validate_color_args(self):
400-
if (
401-
"color" in self.kwds
402-
and self.nseries == 1
403-
and self.kwds["color"] is not None
404-
and not is_list_like(self.kwds["color"])
405-
):
402+
def _validate_color_args(self, color, colormap):
403+
if color is lib.no_default:
404+
# It was not provided by the user
405+
if "colors" in self.kwds and colormap is not None:
406+
warnings.warn(
407+
"'color' and 'colormap' cannot be used simultaneously. "
408+
"Using 'color'",
409+
stacklevel=find_stack_level(),
410+
)
411+
return None
412+
if self.nseries == 1 and color is not None and not is_list_like(color):
406413
# support series.plot(color='green')
407-
self.kwds["color"] = [self.kwds["color"]]
414+
color = [color]
408415

409-
if (
410-
"color" in self.kwds
411-
and isinstance(self.kwds["color"], tuple)
412-
and self.nseries == 1
413-
and len(self.kwds["color"]) in (3, 4)
414-
):
416+
if isinstance(color, tuple) and self.nseries == 1 and len(color) in (3, 4):
415417
# support RGB and RGBA tuples in series plot
416-
self.kwds["color"] = [self.kwds["color"]]
418+
color = [color]
417419

418-
if (
419-
"color" in self.kwds or "colors" in self.kwds
420-
) and self.colormap is not None:
420+
if colormap is not None:
421421
warnings.warn(
422422
"'color' and 'colormap' cannot be used simultaneously. Using 'color'",
423423
stacklevel=find_stack_level(),
424424
)
425425

426-
if "color" in self.kwds and self.style is not None:
426+
if self.style is not None:
427427
if is_list_like(self.style):
428428
styles = self.style
429429
else:
@@ -436,6 +436,7 @@ def _validate_color_args(self):
436436
"'color' keyword argument. Please use one or the "
437437
"other or pass 'style' without a color symbol"
438438
)
439+
return color
439440

440441
@final
441442
@staticmethod
@@ -1058,11 +1059,14 @@ def _get_colors(
10581059
):
10591060
if num_colors is None:
10601061
num_colors = self.nseries
1061-
1062+
if color_kwds == "color":
1063+
color = self.color
1064+
else:
1065+
color = self.kwds.get(color_kwds)
10621066
return get_standard_colors(
10631067
num_colors=num_colors,
10641068
colormap=self.colormap,
1065-
color=self.kwds.get(color_kwds),
1069+
color=color,
10661070
)
10671071

10681072
# TODO: tighter typing for first return?
@@ -1302,7 +1306,7 @@ def _make_plot(self, fig: Figure):
13021306
self.data[c].dtype, CategoricalDtype
13031307
)
13041308

1305-
color = self.kwds.pop("color", None)
1309+
color = self.color
13061310
c_values = self._get_c_values(color, color_by_categorical, c_is_column)
13071311
norm, cmap = self._get_norm_and_cmap(c_values, color_by_categorical)
13081312
cb = self._get_colorbar(c_values, c_is_column)
@@ -1487,6 +1491,8 @@ def _make_plot(self, fig: Figure) -> None:
14871491
for i, (label, y) in enumerate(it):
14881492
ax = self._get_ax(i)
14891493
kwds = self.kwds.copy()
1494+
if self.color is not None:
1495+
kwds["color"] = self.color
14901496
style, kwds = self._apply_style_colors(
14911497
colors,
14921498
kwds,
@@ -1998,8 +2004,9 @@ def __init__(self, data, kind=None, **kwargs) -> None:
19982004
self.logx = False
19992005
self.loglog = False
20002006

2001-
def _validate_color_args(self) -> None:
2002-
pass
2007+
def _validate_color_args(self, color, colormap) -> None:
2008+
# TODO: warn if color is passed and ignored?
2009+
return None
20032010

20042011
def _make_plot(self, fig: Figure) -> None:
20052012
colors = self._get_colors(num_colors=len(self.data), color_kwds="colors")

pandas/plotting/_matplotlib/hist.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ def _make_plot(self, fig: Figure) -> None:
140140
ax = self._get_ax(i)
141141

142142
kwds = self.kwds.copy()
143+
if self.color is not None:
144+
kwds["color"] = self.color
143145

144146
label = pprint_thing(label)
145147
label = self._mark_right_label(label, index=i)

0 commit comments

Comments
 (0)