Skip to content

Commit 268fcf6

Browse files
masongallojavadnoorb
authored andcommitted
API & BUG: allow list-like y argument to df.plot & fix integer arg to x,y (pandas-dev#20000)
* Add support for list-like y argument * update whatsnew * add doc change for y * Add test cases and fix position args * don't copy save cols ahead of time and update whatsnew * address fdbck
1 parent e2053c9 commit 268fcf6

File tree

3 files changed

+60
-18
lines changed

3 files changed

+60
-18
lines changed

doc/source/whatsnew/v0.23.0.txt

+2
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,7 @@ Plotting
987987
^^^^^^^^
988988

989989
- :func:`DataFrame.plot` now raises a ``ValueError`` when the ``x`` or ``y`` argument is improperly formed (:issue:`18671`)
990+
- Bug in :func:`DataFrame.plot` when ``x`` and ``y`` arguments given as positions caused incorrect referenced columns for line, bar and area plots (:issue:`20056`)
990991
- Bug in formatting tick labels with ``datetime.time()`` and fractional seconds (:issue:`18478`).
991992
- :meth:`Series.plot.kde` has exposed the args ``ind`` and ``bw_method`` in the docstring (:issue:`18461`). The argument ``ind`` may now also be an integer (number of sample points).
992993

@@ -1042,3 +1043,4 @@ Other
10421043

10431044
- Improved error message when attempting to use a Python keyword as an identifier in a ``numexpr`` backed query (:issue:`18221`)
10441045
- Bug in accessing a :func:`pandas.get_option`, which raised ``KeyError`` rather than ``OptionError`` when looking up a non-existant option key in some cases (:issue:`19789`)
1046+
- :func:`DataFrame.plot` now supports multiple columns to the ``y`` argument (:issue:`19699`)

pandas/plotting/_core.py

+25-10
Original file line numberDiff line numberDiff line change
@@ -1764,22 +1764,22 @@ def _plot(data, x=None, y=None, subplots=False,
17641764
plot_obj = klass(data, subplots=subplots, ax=ax, kind=kind, **kwds)
17651765
else:
17661766
if isinstance(data, ABCDataFrame):
1767+
data_cols = data.columns
17671768
if x is not None:
17681769
if is_integer(x) and not data.columns.holds_integer():
1769-
x = data.columns[x]
1770+
x = data_cols[x]
17701771
elif not isinstance(data[x], ABCSeries):
17711772
raise ValueError("x must be a label or position")
17721773
data = data.set_index(x)
17731774

17741775
if y is not None:
1775-
if is_integer(y) and not data.columns.holds_integer():
1776-
y = data.columns[y]
1777-
elif not isinstance(data[y], ABCSeries):
1778-
raise ValueError("y must be a label or position")
1779-
label = kwds['label'] if 'label' in kwds else y
1780-
series = data[y].copy() # Don't modify
1781-
series.name = label
1776+
# check if we have y as int or list of ints
1777+
int_ylist = is_list_like(y) and all(is_integer(c) for c in y)
1778+
int_y_arg = is_integer(y) or int_ylist
1779+
if int_y_arg and not data.columns.holds_integer():
1780+
y = data_cols[y]
17821781

1782+
label_kw = kwds['label'] if 'label' in kwds else False
17831783
for kw in ['xerr', 'yerr']:
17841784
if (kw in kwds) and \
17851785
(isinstance(kwds[kw], string_types) or
@@ -1788,7 +1788,22 @@ def _plot(data, x=None, y=None, subplots=False,
17881788
kwds[kw] = data[kwds[kw]]
17891789
except (IndexError, KeyError, TypeError):
17901790
pass
1791-
data = series
1791+
1792+
# don't overwrite
1793+
data = data[y].copy()
1794+
1795+
if isinstance(data, ABCSeries):
1796+
label_name = label_kw or y
1797+
data.name = label_name
1798+
else:
1799+
match = is_list_like(label_kw) and len(label_kw) == len(y)
1800+
if label_kw and not match:
1801+
raise ValueError(
1802+
"label should be list-like and same length as y"
1803+
)
1804+
label_name = label_kw or data.columns
1805+
data.columns = label_name
1806+
17921807
plot_obj = klass(data, subplots=subplots, ax=ax, kind=kind, **kwds)
17931808

17941809
plot_obj.generate()
@@ -1801,7 +1816,7 @@ def _plot(data, x=None, y=None, subplots=False,
18011816
series_kind = ""
18021817

18031818
df_coord = """x : label or position, default None
1804-
y : label or position, default None
1819+
y : label, position or list of label, positions, default None
18051820
Allows plotting of one column versus another"""
18061821
series_coord = ""
18071822

pandas/tests/plotting/test_frame.py

+33-8
Original file line numberDiff line numberDiff line change
@@ -2207,26 +2207,51 @@ def test_invalid_kind(self):
22072207
with pytest.raises(ValueError):
22082208
df.plot(kind='aasdf')
22092209

2210-
@pytest.mark.parametrize("x,y", [
2211-
(['B', 'C'], 'A'),
2212-
('A', ['B', 'C'])
2210+
@pytest.mark.parametrize("x,y,lbl", [
2211+
(['B', 'C'], 'A', 'a'),
2212+
(['A'], ['B', 'C'], ['b', 'c']),
2213+
('A', ['B', 'C'], 'badlabel')
22132214
])
2214-
def test_invalid_xy_args(self, x, y):
2215-
# GH 18671
2215+
def test_invalid_xy_args(self, x, y, lbl):
2216+
# GH 18671, 19699 allows y to be list-like but not x
22162217
df = DataFrame({"A": [1, 2], 'B': [3, 4], 'C': [5, 6]})
22172218
with pytest.raises(ValueError):
2218-
df.plot(x=x, y=y)
2219+
df.plot(x=x, y=y, label=lbl)
22192220

22202221
@pytest.mark.parametrize("x,y", [
22212222
('A', 'B'),
2222-
('B', 'A')
2223+
(['A'], 'B')
22232224
])
22242225
def test_invalid_xy_args_dup_cols(self, x, y):
2225-
# GH 18671
2226+
# GH 18671, 19699 allows y to be list-like but not x
22262227
df = DataFrame([[1, 3, 5], [2, 4, 6]], columns=list('AAB'))
22272228
with pytest.raises(ValueError):
22282229
df.plot(x=x, y=y)
22292230

2231+
@pytest.mark.parametrize("x,y,lbl,colors", [
2232+
('A', ['B'], ['b'], ['red']),
2233+
('A', ['B', 'C'], ['b', 'c'], ['red', 'blue']),
2234+
(0, [1, 2], ['bokeh', 'cython'], ['green', 'yellow'])
2235+
])
2236+
def test_y_listlike(self, x, y, lbl, colors):
2237+
# GH 19699: tests list-like y and verifies lbls & colors
2238+
df = DataFrame({"A": [1, 2], 'B': [3, 4], 'C': [5, 6]})
2239+
_check_plot_works(df.plot, x='A', y=y, label=lbl)
2240+
2241+
ax = df.plot(x=x, y=y, label=lbl, color=colors)
2242+
assert len(ax.lines) == len(y)
2243+
self._check_colors(ax.get_lines(), linecolors=colors)
2244+
2245+
@pytest.mark.parametrize("x,y,colnames", [
2246+
(0, 1, ['A', 'B']),
2247+
(1, 0, [0, 1])
2248+
])
2249+
def test_xy_args_integer(self, x, y, colnames):
2250+
# GH 20056: tests integer args for xy and checks col names
2251+
df = DataFrame({"A": [1, 2], 'B': [3, 4]})
2252+
df.columns = colnames
2253+
_check_plot_works(df.plot, x=x, y=y)
2254+
22302255
@pytest.mark.slow
22312256
def test_hexbin_basic(self):
22322257
df = self.hexbin_df

0 commit comments

Comments
 (0)