Skip to content
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

ENH: Expose symlog scaling in plotting API #24968

Merged
merged 14 commits into from
Apr 12, 2019
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v0.25.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ including other versions of pandas.

Other Enhancements
^^^^^^^^^^^^^^^^^^

- :func:`DataFrame.plot` keywords ``logy``, ``logx`` and ``loglog`` can now accept the value ``'sym'`` for symlog scaling. (:issue:`24867`)
- Added support for ISO week year format ('%G-%V-%u') when parsing datetimes using :meth: `to_datetime` (:issue:`16607`)
- Indexing of ``DataFrame`` and ``Series`` now accepts zerodim ``np.ndarray`` (:issue:`24919`)
- :meth:`Timestamp.replace` now supports the ``fold`` argument to disambiguate DST transition times (:issue:`25017`)
Expand Down
40 changes: 31 additions & 9 deletions pandas/plotting/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,10 @@ def _maybe_right_yaxis(self, ax, axes_num):
if not self._has_plotted_object(orig_ax): # no data on left y
orig_ax.get_yaxis().set_visible(False)

if self.logy or self.loglog:
if self.logy is True or self.loglog is True:
new_ax.set_yscale('log')
elif self.logy == 'sym' or self.loglog == 'sym':
new_ax.set_yscale('symlog')
return new_ax

def _setup_subplots(self):
Expand All @@ -311,10 +313,24 @@ def _setup_subplots(self):

axes = _flatten(axes)

if self.logx or self.loglog:
valid_log = {False, True, 'sym', None}
jreback marked this conversation as resolved.
Show resolved Hide resolved
input_log = {self.logx, self.logy, self.loglog}
if input_log - valid_log:
invalid_log = next(iter((input_log - valid_log)))
raise ValueError(
"Boolean, None and 'sym' are valid options,"
" '{}' is given.".format(invalid_log)
)

if self.logx is True or self.loglog is True:
[a.set_xscale('log') for a in axes]
if self.logy or self.loglog:
elif self.logx == 'sym' or self.loglog == 'sym':
[a.set_xscale('symlog') for a in axes]

if self.logy is True or self.loglog is True:
[a.set_yscale('log') for a in axes]
elif self.logy == 'sym' or self.loglog == 'sym':
[a.set_yscale('symlog') for a in axes]

self.fig = fig
self.axes = axes
Expand Down Expand Up @@ -1909,12 +1925,18 @@ def _plot(data, x=None, y=None, subplots=False,
Place legend on axis subplots
style : list or dict
matplotlib line style per column
logx : bool, default False
Use log scaling on x axis
logy : bool, default False
Use log scaling on y axis
loglog : bool, default False
Use log scaling on both x and y axes
logx : bool or 'sym', default False
Use log scaling or symlog scaling on x axis
.. versionchanged:: 0.25.0

logy : bool or 'sym' default False
Copy link
Contributor

Choose a reason for hiding this comment

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

need a blank line after the versionadded (for each of these); make these versionchanged

Use log scaling or symlog scaling on y axis
.. versionchanged:: 0.25.0

loglog : bool or 'sym', default False
Use log scaling or symlog scaling on both x and y axes
.. versionchanged:: 0.25.0

xticks : sequence
Values to use for the xticks
yticks : sequence
Expand Down
32 changes: 25 additions & 7 deletions pandas/tests/plotting/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,16 +248,34 @@ def test_plot_xy(self):
# TODO add MultiIndex test

@pytest.mark.slow
def test_logscales(self):
@pytest.mark.parametrize("input_log, expected_log", [
(True, 'log'),
('sym', 'symlog')
])
def test_logscales(self, input_log, expected_log):
df = DataFrame({'a': np.arange(100)}, index=np.arange(100))
ax = df.plot(logy=True)
self._check_ax_scales(ax, yaxis='log')

ax = df.plot(logx=True)
self._check_ax_scales(ax, xaxis='log')
ax = df.plot(logy=input_log)
self._check_ax_scales(ax, yaxis=expected_log)
assert ax.get_yscale() == expected_log

ax = df.plot(logx=input_log)
self._check_ax_scales(ax, xaxis=expected_log)
assert ax.get_xscale() == expected_log

ax = df.plot(loglog=input_log)
self._check_ax_scales(ax, xaxis=expected_log, yaxis=expected_log)
assert ax.get_xscale() == expected_log
assert ax.get_yscale() == expected_log

@pytest.mark.parametrize("input_param", ["logx", "logy", "loglog"])
def test_invalid_logscale(self, input_param):
# GH: 24867
df = DataFrame({'a': np.arange(100)}, index=np.arange(100))

ax = df.plot(loglog=True)
self._check_ax_scales(ax, xaxis='log', yaxis='log')
msg = "Boolean, None and 'sym' are valid options, 'sm' is given."
jreback marked this conversation as resolved.
Show resolved Hide resolved
with pytest.raises(ValueError, match=msg):
df.plot(**{input_param: "sm"})
WillAyd marked this conversation as resolved.
Show resolved Hide resolved

@pytest.mark.slow
def test_xcompat(self):
Expand Down
15 changes: 10 additions & 5 deletions pandas/tests/plotting/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,16 +571,21 @@ def test_df_series_secondary_legend(self):
tm.close()

@pytest.mark.slow
def test_secondary_logy(self):
@pytest.mark.parametrize("input_logy, expected_scale", [
(True, 'log'),
('sym', 'symlog')
])
def test_secondary_logy(self, input_logy, expected_scale):
# GH 25545
s1 = Series(np.random.randn(30))
s2 = Series(np.random.randn(30))

ax1 = s1.plot(logy=True)
ax2 = s2.plot(secondary_y=True, logy=True)
# GH 24980
ax1 = s1.plot(logy=input_logy)
ax2 = s2.plot(secondary_y=True, logy=input_logy)

assert ax1.get_yscale() == 'log'
assert ax2.get_yscale() == 'log'
assert ax1.get_yscale() == expected_scale
assert ax2.get_yscale() == expected_scale

@pytest.mark.slow
def test_plot_fails_with_dupe_color_and_style(self):
Expand Down