Skip to content

Commit

Permalink
ENH: Expose symlog scaling in plotting API (pandas-dev#24968)
Browse files Browse the repository at this point in the history
  • Loading branch information
charlesdong1991 authored and yhaque1213 committed Apr 22, 2019
1 parent fad68eb commit 5ed351c
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 22 deletions.
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 @@ -287,8 +287,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 @@ -310,10 +312,24 @@ def _setup_subplots(self):

axes = _flatten(axes)

if self.logx or self.loglog:
valid_log = {False, True, 'sym', None}
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 @@ -1900,12 +1916,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
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 @@ -245,16 +245,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."
with pytest.raises(ValueError, match=msg):
df.plot(**{input_param: "sm"})

@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 @@ -567,16 +567,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

0 comments on commit 5ed351c

Please sign in to comment.