Skip to content

ENH: Implement xlabel and ylabel options in Series.plot and DataFrame.plot #34223

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 32 commits into from
Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7e461a1
remove \n from docstring
charlesdong1991 Dec 3, 2018
1314059
fix conflicts
charlesdong1991 Jan 19, 2019
8bcb313
Merge remote-tracking branch 'upstream/master'
charlesdong1991 Jul 30, 2019
24c3ede
Merge remote-tracking branch 'upstream/master'
charlesdong1991 Jan 14, 2020
dea38f2
fix issue 17038
charlesdong1991 Jan 14, 2020
cd9e7ac
revert change
charlesdong1991 Jan 14, 2020
e5e912b
revert change
charlesdong1991 Jan 14, 2020
045a76f
Merge remote-tracking branch 'upstream/master'
charlesdong1991 Apr 6, 2020
e69c080
Merge remote-tracking branch 'upstream/master' into fix_issue_9093
charlesdong1991 May 17, 2020
bc7bad4
allow xlabel ylabel
charlesdong1991 May 17, 2020
4ae4f17
fixup
charlesdong1991 May 17, 2020
0d32836
add more tests and fix docs
charlesdong1991 May 17, 2020
4d7216b
annotation
charlesdong1991 May 17, 2020
cc1b6d6
add whatsnew
charlesdong1991 May 17, 2020
dbf8f1a
linting
charlesdong1991 May 17, 2020
3fb1faa
fix annotation
charlesdong1991 May 17, 2020
f8ad37a
fix conflict
charlesdong1991 May 18, 2020
3f34099
allow number as labels
charlesdong1991 May 25, 2020
4246f6e
Merge remote-tracking branch 'upstream/master' into fix_issue_9093
charlesdong1991 Jun 14, 2020
408d08f
simplify test
charlesdong1991 Jun 14, 2020
54824ab
fix linting
charlesdong1991 Jun 14, 2020
8a24a40
fix linting
charlesdong1991 Jun 14, 2020
6bb571a
Merge remote-tracking branch 'upstream/master' into fix_issue_9093
charlesdong1991 Jun 14, 2020
66cf42f
Merge remote-tracking branch 'upstream/master' into fix_issue_9093
charlesdong1991 Jun 17, 2020
7e2a7c9
Merge remote-tracking branch 'upstream/master' into fix_issue_9093
charlesdong1991 Jun 20, 2020
31c8ccf
add user guide
charlesdong1991 Jun 20, 2020
9d1d942
fix test
charlesdong1991 Jun 20, 2020
5335de8
use current df
charlesdong1991 Jun 20, 2020
2a8406d
Merge remote-tracking branch 'upstream/master' into fix_issue_9093
charlesdong1991 Jun 20, 2020
8f12150
fix typo
charlesdong1991 Jun 20, 2020
e1ade53
code change on reviews
charlesdong1991 Jun 25, 2020
26bfa6e
Merge remote-tracking branch 'upstream/master' into fix_issue_9093
charlesdong1991 Jun 25, 2020
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
28 changes: 28 additions & 0 deletions doc/source/user_guide/visualization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,34 @@ shown by default.

plt.close('all')


Controlling the labels
~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 1.1.0

You may set the ``xlabel`` and ``ylabel`` arguments to give the plot custom labels
for x and y axis. By default, Pandas will pick up index name as xlabel, while leaving
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
for x and y axis. By default, Pandas will pick up index name as xlabel, while leaving
for x and y axis. By default, pandas will pick up index name as xlabel, while leaving

We use pandas in lowercase.

Copy link
Member Author

Choose a reason for hiding this comment

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

changed!

it empty for ylabel.

.. ipython:: python
:suppress:

plt.figure()

.. ipython:: python

df.plot()

@savefig plot_xlabel_ylabel.png
df.plot(xlabel="new x", ylabel="new y")

.. ipython:: python
:suppress:

plt.close('all')


Scales
~~~~~~

Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ Other enhancements
:class:`~pandas.io.stata.StataWriter`, :class:`~pandas.io.stata.StataWriter117`,
and :class:`~pandas.io.stata.StataWriterUTF8` (:issue:`26599`).
- :meth:`HDFStore.put` now accepts `track_times` parameter. Parameter is passed to ``create_table`` method of ``PyTables`` (:issue:`32682`).
- :meth:`Series.plot` and :meth:`DataFrame.plot` now accepts `xlabel` and `ylabel` parameters to present labels on x and y axis (:issue:`9093`).
- Make :class:`pandas.core.window.Rolling` and :class:`pandas.core.window.Expanding` iterable(:issue:`11704`)
- Make ``option_context`` a :class:`contextlib.ContextDecorator`, which allows it to be used as a decorator over an entire function (:issue:`34253`).
- :meth:`DataFrame.to_csv` and :meth:`Series.to_csv` now accept an ``errors`` argument (:issue:`22610`)
Expand Down
14 changes: 14 additions & 0 deletions pandas/plotting/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,16 @@ class PlotAccessor(PandasObject):
Set the x limits of the current axes.
ylim : 2-tuple/list
Set the y limits of the current axes.
xlabel : label, default None
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
xlabel : label, default None
xlabel : label, optional

We usually use default None when the None is being used (imagine you have something like fill_with=None). When None means that the value is simply not provided, and the parameter won't be use, we use optional. A bit complex, but we've been somehow consistent with that.

Copy link
Member Author

Choose a reason for hiding this comment

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

yep, thats fair! changed

Name to use for the xlabel on x-axis. Default uses index name as xlabel.

.. versionadded:: 1.1.0

ylabel : label, default None
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ylabel : label, default None
ylabel : label, optional

Name to use for the ylabel on y-axis. Default will show no ylabel.

.. versionadded:: 1.1.0

rot : int, default None
Rotation for ticks (xticks for vertical, yticks for horizontal
plots).
Expand Down Expand Up @@ -759,6 +769,8 @@ def _get_call_args(backend_name, data, args, kwargs):
("xerr", None),
("label", None),
("secondary_y", False),
("xlabel", None),
("ylabel", None),
]
elif isinstance(data, ABCDataFrame):
arg_def = [
Expand Down Expand Up @@ -791,6 +803,8 @@ def _get_call_args(backend_name, data, args, kwargs):
("xerr", None),
("secondary_y", False),
("sort_columns", False),
("xlabel", None),
("ylabel", None),
]
else:
raise TypeError(
Expand Down
21 changes: 18 additions & 3 deletions pandas/plotting/_matplotlib/core.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import re
from typing import Optional
from typing import List, Optional
import warnings

from matplotlib.artist import Artist
import numpy as np

from pandas._typing import Label
from pandas.errors import AbstractMethodError
from pandas.util._decorators import cache_readonly

Expand Down Expand Up @@ -97,6 +99,8 @@ def __init__(
ylim=None,
xticks=None,
yticks=None,
xlabel: Optional[Label] = None,
ylabel: Optional[Label] = None,
sort_columns=False,
fontsize=None,
secondary_y=False,
Expand Down Expand Up @@ -138,6 +142,8 @@ def __init__(
self.ylim = ylim
self.title = title
self.use_index = use_index
self.xlabel = xlabel
self.ylabel = ylabel

self.fontsize = fontsize

Expand All @@ -155,8 +161,8 @@ def __init__(

self.grid = grid
self.legend = legend
self.legend_handles = []
self.legend_labels = []
self.legend_handles: List[Artist] = []
self.legend_labels: List[Label] = []

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

# GH9093, currently Pandas does not show ylabel, so if users provide
# ylabel will set it as ylabel in the plot.
if self.ylabel is not None:
ax.set_ylabel(self.ylabel)
Copy link
Member

Choose a reason for hiding this comment

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

Is this consistent with xlabel, which seems to pprint the value?

May be I'm wrong, but feels like df.plot(xlabel=a_complex_structure) will make a_complex_structure (may be a dict of lists) look "nice". While df.plot(ylabel=a_complex_structure) may fail, or just convert to string with str instead of pprint_thing.

Would be probably good to add a test in the parametrization to make sure this works as expected.

Copy link
Member Author

Choose a reason for hiding this comment

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

good call! i think matplotlib's ax.set_ylabel already could make the complex structure case look nice by converting to string automatically without using pprint_thing, e.g. inputs like [1, 2, 3] will be shown as '[1, 2, 3]' same in matplotlib plot.

But i think it might be indeed nicer to be consistent with xlabel so I added pprint_thing for ylabel as well, and add the test case in parametrization! thanks!


ax.grid(self.grid)

if self.title:
Expand Down Expand Up @@ -668,6 +679,10 @@ def _get_index_name(self):
if name is not None:
name = pprint_thing(name)

# GH 9093, override the default xlabel if xlabel is provided.
if self.xlabel is not None:
name = pprint_thing(self.xlabel)

return name

@classmethod
Expand Down
44 changes: 44 additions & 0 deletions pandas/tests/plotting/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -3363,6 +3363,50 @@ def test_colors_of_columns_with_same_name(self):
for legend, line in zip(result.get_legend().legendHandles, result.lines):
assert legend.get_color() == line.get_color()

@pytest.mark.parametrize(
"index_name, old_label, new_label",
[(None, "", "new"), ("old", "old", "new"), (None, "", ""), (None, "", 1)],
)
@pytest.mark.parametrize("kind", ["line", "area", "bar"])
def test_xlabel_ylabel_dataframe_single_plot(
self, kind, index_name, old_label, new_label
):
# GH 9093
df = pd.DataFrame([[1, 2], [2, 5]], columns=["Type A", "Type B"])
df.index.name = index_name

# default is the ylabel is not shown and xlabel is index name
ax = df.plot(kind=kind)
assert ax.get_xlabel() == old_label
assert ax.get_ylabel() == ""

# old xlabel will be overriden and assigned ylabel will be used as ylabel
ax = df.plot(kind=kind, ylabel=new_label, xlabel=new_label)
assert ax.get_ylabel() == str(new_label)
assert ax.get_xlabel() == str(new_label)

@pytest.mark.parametrize(
"index_name, old_label, new_label",
[(None, "", "new"), ("old", "old", "new"), (None, "", ""), (None, "", 1)],
)
@pytest.mark.parametrize("kind", ["line", "area", "bar"])
def test_xlabel_ylabel_dataframe_subplots(
self, kind, index_name, old_label, new_label
):
# GH 9093
df = pd.DataFrame([[1, 2], [2, 5]], columns=["Type A", "Type B"])
df.index.name = index_name

# default is the ylabel is not shown and xlabel is index name
axes = df.plot(kind=kind, subplots=True)
assert all(ax.get_ylabel() == "" for ax in axes)
assert all(ax.get_xlabel() == old_label for ax in axes)

# old xlabel will be overriden and assigned ylabel will be used as ylabel
axes = df.plot(kind=kind, ylabel=new_label, xlabel=new_label, subplots=True)
assert all(ax.get_ylabel() == str(new_label) for ax in axes)
assert all(ax.get_xlabel() == str(new_label) for ax in axes)


def _generate_4_axes_via_gridspec():
import matplotlib.pyplot as plt
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/plotting/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_get_accessor_args():
assert x is None
assert y is None
assert kind == "line"
assert len(kwargs) == 22
assert len(kwargs) == 24


@td.skip_if_no_mpl
Expand Down
20 changes: 20 additions & 0 deletions pandas/tests/plotting/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -934,3 +934,23 @@ 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"]

@pytest.mark.parametrize(
"index_name, old_label, new_label",
[(None, "", "new"), ("old", "old", "new"), (None, "", "")],
)
@pytest.mark.parametrize("kind", ["line", "area", "bar"])
def test_xlabel_ylabel_series(self, kind, index_name, old_label, new_label):
# GH 9093
ser = pd.Series([1, 2, 3, 4])
ser.index.name = index_name

# default is the ylabel is not shown and xlabel is index name
ax = ser.plot(kind=kind)
assert ax.get_ylabel() == ""
assert ax.get_xlabel() == old_label

# old xlabel will be overriden and assigned ylabel will be used as ylabel
ax = ser.plot(kind=kind, ylabel=new_label, xlabel=new_label)
assert ax.get_ylabel() == new_label
assert ax.get_xlabel() == new_label