Skip to content

Commit 03a0948

Browse files
charlesdong1991jreback
authored andcommitted
ENH: Expose symlog scaling in plotting API (#24968)
1 parent d062858 commit 03a0948

File tree

4 files changed

+67
-22
lines changed

4 files changed

+67
-22
lines changed

doc/source/whatsnew/v0.25.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ including other versions of pandas.
2323

2424
Other Enhancements
2525
^^^^^^^^^^^^^^^^^^
26-
26+
- :func:`DataFrame.plot` keywords ``logy``, ``logx`` and ``loglog`` can now accept the value ``'sym'`` for symlog scaling. (:issue:`24867`)
2727
- Added support for ISO week year format ('%G-%V-%u') when parsing datetimes using :meth: `to_datetime` (:issue:`16607`)
2828
- Indexing of ``DataFrame`` and ``Series`` now accepts zerodim ``np.ndarray`` (:issue:`24919`)
2929
- :meth:`Timestamp.replace` now supports the ``fold`` argument to disambiguate DST transition times (:issue:`25017`)

pandas/plotting/_core.py

+31-9
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,10 @@ def _maybe_right_yaxis(self, ax, axes_num):
287287
if not self._has_plotted_object(orig_ax): # no data on left y
288288
orig_ax.get_yaxis().set_visible(False)
289289

290-
if self.logy or self.loglog:
290+
if self.logy is True or self.loglog is True:
291291
new_ax.set_yscale('log')
292+
elif self.logy == 'sym' or self.loglog == 'sym':
293+
new_ax.set_yscale('symlog')
292294
return new_ax
293295

294296
def _setup_subplots(self):
@@ -310,10 +312,24 @@ def _setup_subplots(self):
310312

311313
axes = _flatten(axes)
312314

313-
if self.logx or self.loglog:
315+
valid_log = {False, True, 'sym', None}
316+
input_log = {self.logx, self.logy, self.loglog}
317+
if input_log - valid_log:
318+
invalid_log = next(iter((input_log - valid_log)))
319+
raise ValueError(
320+
"Boolean, None and 'sym' are valid options,"
321+
" '{}' is given.".format(invalid_log)
322+
)
323+
324+
if self.logx is True or self.loglog is True:
314325
[a.set_xscale('log') for a in axes]
315-
if self.logy or self.loglog:
326+
elif self.logx == 'sym' or self.loglog == 'sym':
327+
[a.set_xscale('symlog') for a in axes]
328+
329+
if self.logy is True or self.loglog is True:
316330
[a.set_yscale('log') for a in axes]
331+
elif self.logy == 'sym' or self.loglog == 'sym':
332+
[a.set_yscale('symlog') for a in axes]
317333

318334
self.fig = fig
319335
self.axes = axes
@@ -1900,12 +1916,18 @@ def _plot(data, x=None, y=None, subplots=False,
19001916
Place legend on axis subplots
19011917
style : list or dict
19021918
matplotlib line style per column
1903-
logx : bool, default False
1904-
Use log scaling on x axis
1905-
logy : bool, default False
1906-
Use log scaling on y axis
1907-
loglog : bool, default False
1908-
Use log scaling on both x and y axes
1919+
logx : bool or 'sym', default False
1920+
Use log scaling or symlog scaling on x axis
1921+
.. versionchanged:: 0.25.0
1922+
1923+
logy : bool or 'sym' default False
1924+
Use log scaling or symlog scaling on y axis
1925+
.. versionchanged:: 0.25.0
1926+
1927+
loglog : bool or 'sym', default False
1928+
Use log scaling or symlog scaling on both x and y axes
1929+
.. versionchanged:: 0.25.0
1930+
19091931
xticks : sequence
19101932
Values to use for the xticks
19111933
yticks : sequence

pandas/tests/plotting/test_frame.py

+25-7
Original file line numberDiff line numberDiff line change
@@ -245,16 +245,34 @@ def test_plot_xy(self):
245245
# TODO add MultiIndex test
246246

247247
@pytest.mark.slow
248-
def test_logscales(self):
248+
@pytest.mark.parametrize("input_log, expected_log", [
249+
(True, 'log'),
250+
('sym', 'symlog')
251+
])
252+
def test_logscales(self, input_log, expected_log):
249253
df = DataFrame({'a': np.arange(100)}, index=np.arange(100))
250-
ax = df.plot(logy=True)
251-
self._check_ax_scales(ax, yaxis='log')
252254

253-
ax = df.plot(logx=True)
254-
self._check_ax_scales(ax, xaxis='log')
255+
ax = df.plot(logy=input_log)
256+
self._check_ax_scales(ax, yaxis=expected_log)
257+
assert ax.get_yscale() == expected_log
258+
259+
ax = df.plot(logx=input_log)
260+
self._check_ax_scales(ax, xaxis=expected_log)
261+
assert ax.get_xscale() == expected_log
262+
263+
ax = df.plot(loglog=input_log)
264+
self._check_ax_scales(ax, xaxis=expected_log, yaxis=expected_log)
265+
assert ax.get_xscale() == expected_log
266+
assert ax.get_yscale() == expected_log
267+
268+
@pytest.mark.parametrize("input_param", ["logx", "logy", "loglog"])
269+
def test_invalid_logscale(self, input_param):
270+
# GH: 24867
271+
df = DataFrame({'a': np.arange(100)}, index=np.arange(100))
255272

256-
ax = df.plot(loglog=True)
257-
self._check_ax_scales(ax, xaxis='log', yaxis='log')
273+
msg = "Boolean, None and 'sym' are valid options, 'sm' is given."
274+
with pytest.raises(ValueError, match=msg):
275+
df.plot(**{input_param: "sm"})
258276

259277
@pytest.mark.slow
260278
def test_xcompat(self):

pandas/tests/plotting/test_series.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -567,16 +567,21 @@ def test_df_series_secondary_legend(self):
567567
tm.close()
568568

569569
@pytest.mark.slow
570-
def test_secondary_logy(self):
570+
@pytest.mark.parametrize("input_logy, expected_scale", [
571+
(True, 'log'),
572+
('sym', 'symlog')
573+
])
574+
def test_secondary_logy(self, input_logy, expected_scale):
571575
# GH 25545
572576
s1 = Series(np.random.randn(30))
573577
s2 = Series(np.random.randn(30))
574578

575-
ax1 = s1.plot(logy=True)
576-
ax2 = s2.plot(secondary_y=True, logy=True)
579+
# GH 24980
580+
ax1 = s1.plot(logy=input_logy)
581+
ax2 = s2.plot(secondary_y=True, logy=input_logy)
577582

578-
assert ax1.get_yscale() == 'log'
579-
assert ax2.get_yscale() == 'log'
583+
assert ax1.get_yscale() == expected_scale
584+
assert ax2.get_yscale() == expected_scale
580585

581586
@pytest.mark.slow
582587
def test_plot_fails_with_dupe_color_and_style(self):

0 commit comments

Comments
 (0)