Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b4f011c

Browse files
committed
ENH: remove arbitrary long args if used with data
Arbitrary long args, i.e., plot("x","y","r","x2","y2","b") were problematic if used with a data kwarg and the color spec ("r", "b") was included in data: this made it impossible in some cases to determine what the user wanted to plot: plot("x","y","r","x2","y2","b", data={..., "r":..., "b":...) could be interpreted as plot(x, y, "r") # all points red plot(x2, y2, "b") # all points black or plot(x,y) plot(r,x2) plot(y2,b) This could lead to hard to debug problems if both styles result in a similar plot (e.g. if all are values in the same range). Therefore it was decided to remove this possibility so that the usere gets a proper error message instead: #4829 (comment) There is still a case of ambiguity (plot("y", "ro", data={"y":..., "ro":...), which is now detected and a warning is issued. This detection could theoretically be used to detect the above case as well, but there were so many corner cases, that the checks became too horrible and I took that out again. Note that passing in data directly (without a data kwarg) is unaffected, it still accepts arbitrary long args.
1 parent 25b1d43 commit b4f011c

File tree

2 files changed

+55
-47
lines changed

2 files changed

+55
-47
lines changed

lib/matplotlib/axes/_axes.py

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -49,42 +49,48 @@
4949

5050

5151
def _plot_args_replacer(args, data):
52-
_replacer = []
53-
remaining = args
54-
while 1:
55-
if len(remaining) == 1:
56-
57-
msg = ("Missing argument: this can happen if a color spec "
58-
"('c') is in `data`")
59-
warnings.warn(msg, RuntimeWarning, stacklevel=3)
60-
_replacer += ["x"]
61-
elif len(remaining) == 2:
62-
_replacer += ["x", "y"]
63-
elif len(remaining) == 3:
64-
if remaining[2] in data:
52+
if len(args) == 1:
53+
return ["y"]
54+
elif len(args) == 2:
55+
# this can be two cases: x,y or y,c
56+
if not args[1] in data:
57+
# this is not in data, so just assume that it is something which
58+
# will not get replaced (color spec or array like).
59+
return ["y", "c"]
60+
# it's data, but could be a color code like 'ro' or 'b--'
61+
# -> warn the user in that case...
62+
arg2 = args[1]
63+
if is_string_like(arg2) and len(arg2) <= 3:
64+
# all possible linestyles and color codes -> see doc of plot
65+
reserved_ls = ["-", "--", "-.", ":", ".", ",", "o", "v", "^", "<",
66+
">", "1", "2", "3", "4", "s", "p", "*", "h", "H",
67+
"+", "x", "D", "d", "|", "_"]
68+
reserved_cc = ["b", "r", "c", "m", "y", "k", "w"]
69+
# remove the line style part
70+
for ls in reserved_ls:
71+
if ls in arg2:
72+
arg2 = arg2.replace(ls, '')
73+
continue
74+
# can now only be a color code...
75+
if arg2 in reserved_cc:
6576
import warnings
66-
67-
msg = "Found a color spec ('c') in data."
77+
msg = "Second argument is ambiguous: could be a color spec " \
78+
"but is in data. Using as data.\nEither rename the " \
79+
"entry in data or use three arguments to plot."
6880
warnings.warn(msg, RuntimeWarning, stacklevel=3)
69-
_replacer += ["x", "y", "c"]
70-
71-
# if less than 3, the above code handled it so just return
72-
if len(remaining) <= 3:
73-
return _replacer
74-
75-
# More than 3 -> split off the beginning and continue
76-
if remaining[2] not in data:
77-
_replacer += ["x", "y", "c"]
78-
isplit = 3
79-
else:
80-
_replacer += ["x", "y"]
81-
isplit = 2
82-
remaining = remaining[isplit:]
81+
return ["x", "y"]
82+
elif len(args) == 3:
83+
return ["x", "y", "c"]
84+
else:
85+
raise ValueError("Using arbitrary long args with data is not "
86+
"supported due to ambiguity of arguments.\nUse "
87+
"multiple plotting calls instead.")
8388

8489

8590
# The axes module contains all the wrappers to plotting functions.
8691
# All the other methods should go in the _AxesBase class.
8792

93+
8894
class Axes(_AxesBase):
8995
"""
9096
The :class:`Axes` contains most of the figure elements:
@@ -1305,8 +1311,14 @@ def plot(self, *args, **kwargs):
13051311
If *x* and/or *y* is 2-dimensional, then the corresponding columns
13061312
will be plotted.
13071313
1308-
An arbitrary number of *x*, *y*, *fmt* groups can be
1309-
specified, as in::
1314+
If used with labeled data, make sure that the color spec is not
1315+
included as an element in data, as otherwise the last case
1316+
``plot("v","r", data={"v":..., "r":...)``
1317+
can be interpreted as the first case which would do ``plot(v, r)``
1318+
using the default line style and color.
1319+
1320+
If not used with labeled data (i.e., without a data argument),
1321+
an arbitrary number of *x*, *y*, *fmt* groups can be specified, as in::
13101322
13111323
a.plot(x1, y1, 'g^', x2, y2, 'g-')
13121324

lib/matplotlib/tests/test_labeled_data_unpacking.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -467,31 +467,27 @@ def test_positional_parameter_names_as_function():
467467
# Also test the _plot_arg_replacer for plot...
468468
from matplotlib.axes._axes import _plot_args_replacer
469469

470-
# this is a replace for plot, which can take args as
471-
# x,y,c,x,y,c or x,y,x,y,c,x,y or any other way... :-/
472470
@unpack_labeled_data(replace_names=["x", "y"],
473471
positional_parameter_names=_plot_args_replacer)
474472
def funcy(ax, *args, **kwargs):
475473
return "{args} | {kwargs}".format(args=args, kwargs=kwargs)
476474

475+
# the normal case...
477476
data = {"x": "X", "y": "Y"}
478477
assert_equal(funcy(None, "x", "y", data=data),
479478
"('X', 'Y') | {}")
480479
assert_equal(funcy(None, "x", "y", "c", data=data),
481480
"('X', 'Y', 'c') | {}")
482-
assert_equal(funcy(None, "x", "y", "c", "x", "y", "x", "y", data=data),
483-
"('X', 'Y', 'c', 'X', 'Y', 'X', 'Y') | {}")
484481

485-
# the color spec should not be in data...
486-
data = {"x": "X", "y": "Y", "c": "!!"}
487-
with assert_produces_warning(RuntimeWarning):
482+
# no arbitrary long args with data
483+
def f():
488484
assert_equal(funcy(None, "x", "y", "c", "x", "y", "x", "y", data=data),
489-
"('X', 'Y', '!!', 'X', 'Y', 'X', 'y') | {}")
490-
491-
# And this is the case which we screw up, as we can't distinguish
492-
# between a color spec and data...
493-
# -> This test should actually produce a warning, if someone finds a way
494-
# to distinguish between data and color spec...
495-
with assert_produces_warning(False):
496-
assert_equal(funcy(None, "x", "y", "c", "x", "y", "c", data=data),
497-
"('X', 'Y', '!!', 'X', 'Y', '!!') | {}")
485+
"('X', 'Y', 'c', 'X', 'Y', 'X', 'Y') | {}")
486+
assert_raises(ValueError, f)
487+
488+
# In the two arg case, if a valid color spec is in data, we warn but use
489+
# it as data...
490+
data = {"x": "X", "y": "Y", "ro": "!!"}
491+
with assert_produces_warning(RuntimeWarning):
492+
assert_equal(funcy(None, "y", "ro", data=data),
493+
"('Y', '!!') | {}")

0 commit comments

Comments
 (0)