Skip to content

Commit 998e2ab

Browse files
ENH: Implement xlabel and ylabel options in Series.plot and DataFrame.plot (#34223)
1 parent 6a9ceac commit 998e2ab

File tree

7 files changed

+138
-4
lines changed

7 files changed

+138
-4
lines changed

doc/source/user_guide/visualization.rst

+28
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,34 @@ shown by default.
11081108
11091109
plt.close('all')
11101110
1111+
1112+
Controlling the labels
1113+
~~~~~~~~~~~~~~~~~~~~~~
1114+
1115+
.. versionadded:: 1.1.0
1116+
1117+
You may set the ``xlabel`` and ``ylabel`` arguments to give the plot custom labels
1118+
for x and y axis. By default, pandas will pick up index name as xlabel, while leaving
1119+
it empty for ylabel.
1120+
1121+
.. ipython:: python
1122+
:suppress:
1123+
1124+
plt.figure()
1125+
1126+
.. ipython:: python
1127+
1128+
df.plot()
1129+
1130+
@savefig plot_xlabel_ylabel.png
1131+
df.plot(xlabel="new x", ylabel="new y")
1132+
1133+
.. ipython:: python
1134+
:suppress:
1135+
1136+
plt.close('all')
1137+
1138+
11111139
Scales
11121140
~~~~~~
11131141

doc/source/whatsnew/v1.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ Other enhancements
303303
:class:`~pandas.io.stata.StataWriter`, :class:`~pandas.io.stata.StataWriter117`,
304304
and :class:`~pandas.io.stata.StataWriterUTF8` (:issue:`26599`).
305305
- :meth:`HDFStore.put` now accepts `track_times` parameter. Parameter is passed to ``create_table`` method of ``PyTables`` (:issue:`32682`).
306+
- :meth:`Series.plot` and :meth:`DataFrame.plot` now accepts `xlabel` and `ylabel` parameters to present labels on x and y axis (:issue:`9093`).
306307
- Make :class:`pandas.core.window.Rolling` and :class:`pandas.core.window.Expanding` iterable(:issue:`11704`)
307308
- Make ``option_context`` a :class:`contextlib.ContextDecorator`, which allows it to be used as a decorator over an entire function (:issue:`34253`).
308309
- :meth:`DataFrame.to_csv` and :meth:`Series.to_csv` now accept an ``errors`` argument (:issue:`22610`)

pandas/plotting/_core.py

+14
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,16 @@ class PlotAccessor(PandasObject):
673673
Set the x limits of the current axes.
674674
ylim : 2-tuple/list
675675
Set the y limits of the current axes.
676+
xlabel : label, optional
677+
Name to use for the xlabel on x-axis. Default uses index name as xlabel.
678+
679+
.. versionadded:: 1.1.0
680+
681+
ylabel : label, optional
682+
Name to use for the ylabel on y-axis. Default will show no ylabel.
683+
684+
.. versionadded:: 1.1.0
685+
676686
rot : int, default None
677687
Rotation for ticks (xticks for vertical, yticks for horizontal
678688
plots).
@@ -779,6 +789,8 @@ def _get_call_args(backend_name, data, args, kwargs):
779789
("xerr", None),
780790
("label", None),
781791
("secondary_y", False),
792+
("xlabel", None),
793+
("ylabel", None),
782794
]
783795
elif isinstance(data, ABCDataFrame):
784796
arg_def = [
@@ -811,6 +823,8 @@ def _get_call_args(backend_name, data, args, kwargs):
811823
("xerr", None),
812824
("secondary_y", False),
813825
("sort_columns", False),
826+
("xlabel", None),
827+
("ylabel", None),
814828
]
815829
else:
816830
raise TypeError(

pandas/plotting/_matplotlib/core.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import re
2-
from typing import Optional
2+
from typing import List, Optional
33
import warnings
44

5+
from matplotlib.artist import Artist
56
import numpy as np
67

8+
from pandas._typing import Label
79
from pandas.errors import AbstractMethodError
810
from pandas.util._decorators import cache_readonly
911

@@ -97,6 +99,8 @@ def __init__(
9799
ylim=None,
98100
xticks=None,
99101
yticks=None,
102+
xlabel: Optional[Label] = None,
103+
ylabel: Optional[Label] = None,
100104
sort_columns=False,
101105
fontsize=None,
102106
secondary_y=False,
@@ -138,6 +142,8 @@ def __init__(
138142
self.ylim = ylim
139143
self.title = title
140144
self.use_index = use_index
145+
self.xlabel = xlabel
146+
self.ylabel = ylabel
141147

142148
self.fontsize = fontsize
143149

@@ -155,8 +161,8 @@ def __init__(
155161

156162
self.grid = grid
157163
self.legend = legend
158-
self.legend_handles = []
159-
self.legend_labels = []
164+
self.legend_handles: List[Artist] = []
165+
self.legend_labels: List[Label] = []
160166

161167
for attr in self._pop_attributes:
162168
value = kwds.pop(attr, self._attr_defaults.get(attr, None))
@@ -482,6 +488,11 @@ def _adorn_subplots(self):
482488
if self.xlim is not None:
483489
ax.set_xlim(self.xlim)
484490

491+
# GH9093, currently Pandas does not show ylabel, so if users provide
492+
# ylabel will set it as ylabel in the plot.
493+
if self.ylabel is not None:
494+
ax.set_ylabel(pprint_thing(self.ylabel))
495+
485496
ax.grid(self.grid)
486497

487498
if self.title:
@@ -668,6 +679,10 @@ def _get_index_name(self):
668679
if name is not None:
669680
name = pprint_thing(name)
670681

682+
# GH 9093, override the default xlabel if xlabel is provided.
683+
if self.xlabel is not None:
684+
name = pprint_thing(self.xlabel)
685+
671686
return name
672687

673688
@classmethod

pandas/tests/plotting/test_frame.py

+56
Original file line numberDiff line numberDiff line change
@@ -3363,6 +3363,62 @@ def test_colors_of_columns_with_same_name(self):
33633363
for legend, line in zip(result.get_legend().legendHandles, result.lines):
33643364
assert legend.get_color() == line.get_color()
33653365

3366+
@pytest.mark.parametrize(
3367+
"index_name, old_label, new_label",
3368+
[
3369+
(None, "", "new"),
3370+
("old", "old", "new"),
3371+
(None, "", ""),
3372+
(None, "", 1),
3373+
(None, "", [1, 2]),
3374+
],
3375+
)
3376+
@pytest.mark.parametrize("kind", ["line", "area", "bar"])
3377+
def test_xlabel_ylabel_dataframe_single_plot(
3378+
self, kind, index_name, old_label, new_label
3379+
):
3380+
# GH 9093
3381+
df = pd.DataFrame([[1, 2], [2, 5]], columns=["Type A", "Type B"])
3382+
df.index.name = index_name
3383+
3384+
# default is the ylabel is not shown and xlabel is index name
3385+
ax = df.plot(kind=kind)
3386+
assert ax.get_xlabel() == old_label
3387+
assert ax.get_ylabel() == ""
3388+
3389+
# old xlabel will be overriden and assigned ylabel will be used as ylabel
3390+
ax = df.plot(kind=kind, ylabel=new_label, xlabel=new_label)
3391+
assert ax.get_ylabel() == str(new_label)
3392+
assert ax.get_xlabel() == str(new_label)
3393+
3394+
@pytest.mark.parametrize(
3395+
"index_name, old_label, new_label",
3396+
[
3397+
(None, "", "new"),
3398+
("old", "old", "new"),
3399+
(None, "", ""),
3400+
(None, "", 1),
3401+
(None, "", [1, 2]),
3402+
],
3403+
)
3404+
@pytest.mark.parametrize("kind", ["line", "area", "bar"])
3405+
def test_xlabel_ylabel_dataframe_subplots(
3406+
self, kind, index_name, old_label, new_label
3407+
):
3408+
# GH 9093
3409+
df = pd.DataFrame([[1, 2], [2, 5]], columns=["Type A", "Type B"])
3410+
df.index.name = index_name
3411+
3412+
# default is the ylabel is not shown and xlabel is index name
3413+
axes = df.plot(kind=kind, subplots=True)
3414+
assert all(ax.get_ylabel() == "" for ax in axes)
3415+
assert all(ax.get_xlabel() == old_label for ax in axes)
3416+
3417+
# old xlabel will be overriden and assigned ylabel will be used as ylabel
3418+
axes = df.plot(kind=kind, ylabel=new_label, xlabel=new_label, subplots=True)
3419+
assert all(ax.get_ylabel() == str(new_label) for ax in axes)
3420+
assert all(ax.get_xlabel() == str(new_label) for ax in axes)
3421+
33663422

33673423
def _generate_4_axes_via_gridspec():
33683424
import matplotlib.pyplot as plt

pandas/tests/plotting/test_misc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def test_get_accessor_args():
5454
assert x is None
5555
assert y is None
5656
assert kind == "line"
57-
assert len(kwargs) == 22
57+
assert len(kwargs) == 24
5858

5959

6060
@td.skip_if_no_mpl

pandas/tests/plotting/test_series.py

+20
Original file line numberDiff line numberDiff line change
@@ -934,3 +934,23 @@ def test_style_single_ok(self):
934934
s = pd.Series([1, 2])
935935
ax = s.plot(style="s", color="C3")
936936
assert ax.lines[0].get_color() == ["C3"]
937+
938+
@pytest.mark.parametrize(
939+
"index_name, old_label, new_label",
940+
[(None, "", "new"), ("old", "old", "new"), (None, "", "")],
941+
)
942+
@pytest.mark.parametrize("kind", ["line", "area", "bar"])
943+
def test_xlabel_ylabel_series(self, kind, index_name, old_label, new_label):
944+
# GH 9093
945+
ser = pd.Series([1, 2, 3, 4])
946+
ser.index.name = index_name
947+
948+
# default is the ylabel is not shown and xlabel is index name
949+
ax = ser.plot(kind=kind)
950+
assert ax.get_ylabel() == ""
951+
assert ax.get_xlabel() == old_label
952+
953+
# old xlabel will be overriden and assigned ylabel will be used as ylabel
954+
ax = ser.plot(kind=kind, ylabel=new_label, xlabel=new_label)
955+
assert ax.get_ylabel() == new_label
956+
assert ax.get_xlabel() == new_label

0 commit comments

Comments
 (0)