From 88705a54fd6a49efa9e7733a8968a86c81c22d3f Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Tue, 5 Mar 2019 18:04:30 +0100 Subject: [PATCH 01/13] merge master --- doc/source/whatsnew/v0.25.0.rst | 2 +- pandas/plotting/_core.py | 14 ++++++++++++-- pandas/tests/plotting/test_frame.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 124ec8f4ab92c..61df60bb46f7e 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -18,7 +18,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`) - 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`) - :meth:`DataFrame.at_time` and :meth:`Series.at_time` now support :meth:`datetime.time` objects with timezones (:issue:`24043`) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 48d870bfc2e03..c508c2aec9b8e 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -308,10 +308,20 @@ 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: + raise ValueError("Valid inputs are boolean, None and 'sym'.") + + 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 diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index 98b241f5c8206..c44802c4a5639 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -231,14 +231,42 @@ def test_plot_xy(self): @pytest.mark.slow def test_logscales(self): df = DataFrame({'a': np.arange(100)}, index=np.arange(100)) + ax = df.plot(logy=True) self._check_ax_scales(ax, yaxis='log') + assert ax.get_yscale() == 'log' + + ax = df.plot(logy='sym') + self._check_ax_scales(ax, yaxis='symlog') + assert ax.get_yscale() == 'symlog' ax = df.plot(logx=True) self._check_ax_scales(ax, xaxis='log') + assert ax.get_xscale() == 'log' + + ax = df.plot(logx='sym') + self._check_ax_scales(ax, xaxis='symlog') + assert ax.get_xscale() == 'symlog' ax = df.plot(loglog=True) self._check_ax_scales(ax, xaxis='log', yaxis='log') + assert ax.get_xscale() == 'log' + assert ax.get_yscale() == 'log' + + ax = df.plot(loglog='sym') + self._check_ax_scales(ax, xaxis='symlog', yaxis='symlog') + assert ax.get_xscale() == 'symlog' + assert ax.get_yscale() == 'symlog' + + @pytest.mark.parametrize("wrong_input", ["sm", "symlog"]) + @pytest.mark.parametrize("input_param", ["logx", "logy", "loglog"]) + def test_invalid_logscale(self, wrong_input, input_param): + # GH: 24867 + df = DataFrame({'a': np.arange(100)}, index=np.arange(100)) + + msg = "Valid inputs are boolean, None and 'sym'." + with pytest.raises(ValueError, match=msg): + df.plot(**{input_param: wrong_input}) @pytest.mark.slow def test_xcompat(self): From 7bf16e49693489ed00c0df79301dd01c42870cbb Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Tue, 5 Mar 2019 18:05:26 +0100 Subject: [PATCH 02/13] add quote --- doc/source/whatsnew/v0.25.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 61df60bb46f7e..f0646363595fb 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -18,7 +18,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`) +- :func:`DataFrame.plot` keywords ``logy``, ``logx`` and ``loglog`` can now accept the value ``'sym'`` for symlog scaling. (:issue:`24867`) - 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`) - :meth:`DataFrame.at_time` and :meth:`Series.at_time` now support :meth:`datetime.time` objects with timezones (:issue:`24043`) From 446bcc01406f53e4a59f44fb925808cd74e8e258 Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Tue, 5 Mar 2019 18:06:55 +0100 Subject: [PATCH 03/13] change error message --- pandas/plotting/_core.py | 3 ++- pandas/tests/plotting/test_frame.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index c508c2aec9b8e..194f18b7602de 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -311,7 +311,8 @@ def _setup_subplots(self): valid_log = {False, True, 'sym', None} input_log = {self.logx, self.logy, self.loglog} if input_log - valid_log: - raise ValueError("Valid inputs are boolean, None and 'sym'.") + raise ValueError(f"Valid inputs are boolean, None and 'sym'" + f", {i} is given.") if self.logx is True or self.loglog is True: [a.set_xscale('log') for a in axes] diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index c44802c4a5639..0bebff604b09b 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -264,7 +264,7 @@ def test_invalid_logscale(self, wrong_input, input_param): # GH: 24867 df = DataFrame({'a': np.arange(100)}, index=np.arange(100)) - msg = "Valid inputs are boolean, None and 'sym'." + msg = "Valid inputs are boolean, None and 'sym'" with pytest.raises(ValueError, match=msg): df.plot(**{input_param: wrong_input}) From 238d1f449b78b0c531342d6826e2b1bf76fed206 Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Tue, 5 Mar 2019 18:16:50 +0100 Subject: [PATCH 04/13] change test --- pandas/plotting/_core.py | 3 ++- pandas/tests/plotting/test_frame.py | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 194f18b7602de..a7265c3689ae5 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -311,8 +311,9 @@ def _setup_subplots(self): valid_log = {False, True, 'sym', None} input_log = {self.logx, self.logy, self.loglog} if input_log - valid_log: + invalid_log = list(input_log - valid_log)[0] raise ValueError(f"Valid inputs are boolean, None and 'sym'" - f", {i} is given.") + f", '{invalid_log}' is given.") if self.logx is True or self.loglog is True: [a.set_xscale('log') for a in axes] diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index 0bebff604b09b..4ffc4f0c5f26d 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -258,15 +258,14 @@ def test_logscales(self): assert ax.get_xscale() == 'symlog' assert ax.get_yscale() == 'symlog' - @pytest.mark.parametrize("wrong_input", ["sm", "symlog"]) @pytest.mark.parametrize("input_param", ["logx", "logy", "loglog"]) - def test_invalid_logscale(self, wrong_input, input_param): + def test_invalid_logscale(self, input_param): # GH: 24867 df = DataFrame({'a': np.arange(100)}, index=np.arange(100)) - msg = "Valid inputs are boolean, None and 'sym'" + msg = "Valid inputs are boolean, None and 'sym', 'sm' is given." with pytest.raises(ValueError, match=msg): - df.plot(**{input_param: wrong_input}) + df.plot(**{input_param: "sm"}) @pytest.mark.slow def test_xcompat(self): From 8941bdf5f94ed8d4e09568d944e8c0a5fdd71b47 Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Tue, 5 Mar 2019 18:19:16 +0100 Subject: [PATCH 05/13] optimize --- pandas/plotting/_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index a7265c3689ae5..15dac3478627e 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -311,7 +311,7 @@ def _setup_subplots(self): valid_log = {False, True, 'sym', None} input_log = {self.logx, self.logy, self.loglog} if input_log - valid_log: - invalid_log = list(input_log - valid_log)[0] + invalid_log = next(iter((input_log - valid_log))) raise ValueError(f"Valid inputs are boolean, None and 'sym'" f", '{invalid_log}' is given.") From f8b124ac24c7d69f24691795675e666ebfe913ed Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Tue, 5 Mar 2019 19:58:58 +0100 Subject: [PATCH 06/13] push change --- pandas/plotting/_core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 15dac3478627e..11af8acd032fb 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -312,8 +312,9 @@ def _setup_subplots(self): input_log = {self.logx, self.logy, self.loglog} if input_log - valid_log: invalid_log = next(iter((input_log - valid_log))) - raise ValueError(f"Valid inputs are boolean, None and 'sym'" - f", '{invalid_log}' is given.") + raise ValueError( + f"Boolean, None and 'sym' are valid, '{invalid_log}' is given." + ) if self.logx is True or self.loglog is True: [a.set_xscale('log') for a in axes] From 549dd4d3e97391987c9bb13f02d17a35d0dcba11 Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Tue, 5 Mar 2019 20:18:19 +0100 Subject: [PATCH 07/13] fix test error --- pandas/plotting/_core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 11af8acd032fb..372e1e2abdc24 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -313,7 +313,8 @@ def _setup_subplots(self): if input_log - valid_log: invalid_log = next(iter((input_log - valid_log))) raise ValueError( - f"Boolean, None and 'sym' are valid, '{invalid_log}' is given." + "Boolean, None and 'sym' are valid," + " '{}' is given.".format(invalid_log) ) if self.logx is True or self.loglog is True: From 0ea6653392b298cf53a5c12219e6f5c1dcdfc50e Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Tue, 5 Mar 2019 20:20:52 +0100 Subject: [PATCH 08/13] fix tests --- pandas/plotting/_core.py | 2 +- pandas/tests/plotting/test_frame.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 372e1e2abdc24..e49a066725147 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -313,7 +313,7 @@ def _setup_subplots(self): if input_log - valid_log: invalid_log = next(iter((input_log - valid_log))) raise ValueError( - "Boolean, None and 'sym' are valid," + "Boolean, None and 'sym' are valid options," " '{}' is given.".format(invalid_log) ) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index 4ffc4f0c5f26d..15e4b911d0568 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -263,7 +263,7 @@ def test_invalid_logscale(self, input_param): # GH: 24867 df = DataFrame({'a': np.arange(100)}, index=np.arange(100)) - msg = "Valid inputs are boolean, None and 'sym', 'sm' is given." + msg = "Boolean, None and 'sym' are valid options, 'sm' is given." with pytest.raises(ValueError, match=msg): df.plot(**{input_param: "sm"}) From 3a265e9496e51051ac5498d70fa305719697c75c Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Sun, 24 Mar 2019 16:01:10 +0100 Subject: [PATCH 09/13] add docstring --- pandas/plotting/_core.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 2ecb9b945de75..c94d6fadb5077 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -1923,12 +1923,15 @@ 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 + .. versionadded:: 0.25.0 + logy : bool or 'sym' default False + Use log scaling or symlog scaling on y axis + .. versionadded:: 0.25.0 + loglog : bool or 'sym', default False + Use log scaling or symlog scaling on both x and y axes + .. versionadded:: 0.25.0 xticks : sequence Values to use for the xticks yticks : sequence From a3e71c9c6fc4dc33f44e3b3cfa07612167ec932d Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Sun, 24 Mar 2019 16:06:10 +0100 Subject: [PATCH 10/13] parametrize tests --- pandas/tests/plotting/test_frame.py | 39 +++++++++++------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index a8a0b6c4ab5ab..768fb7519c1ce 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -248,34 +248,25 @@ 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') - assert ax.get_yscale() == 'log' - - ax = df.plot(logy='sym') - self._check_ax_scales(ax, yaxis='symlog') - assert ax.get_yscale() == 'symlog' - - ax = df.plot(logx=True) - self._check_ax_scales(ax, xaxis='log') - assert ax.get_xscale() == 'log' - - ax = df.plot(logx='sym') - self._check_ax_scales(ax, xaxis='symlog') - assert ax.get_xscale() == 'symlog' + ax = df.plot(logy=input_log) + self._check_ax_scales(ax, yaxis=expected_log) + assert ax.get_yscale() == expected_log - ax = df.plot(loglog=True) - self._check_ax_scales(ax, xaxis='log', yaxis='log') - assert ax.get_xscale() == 'log' - assert ax.get_yscale() == '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='sym') - self._check_ax_scales(ax, xaxis='symlog', yaxis='symlog') - assert ax.get_xscale() == 'symlog' - assert ax.get_yscale() == 'symlog' + 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): From 9c205f5ac6ef2cd75b68c36d8eada7199bd7db98 Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Sun, 24 Mar 2019 16:13:26 +0100 Subject: [PATCH 11/13] xref 25586 --- pandas/plotting/_core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index c94d6fadb5077..5a10e867b1718 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -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): From d0e5c41d1301eef1a30bc07e0cab5dc4c9dc414d Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Sun, 24 Mar 2019 16:16:49 +0100 Subject: [PATCH 12/13] add testing --- pandas/tests/plotting/test_series.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index e384c578aa446..daa17c173ca08 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -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): From 517000030bc7a8aa9e3b888c4c5143b6b63da0b3 Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Mon, 25 Mar 2019 19:00:02 +0100 Subject: [PATCH 13/13] add blank line after version change --- pandas/plotting/_core.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 5a10e867b1718..e75e8bb4f8821 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -1927,13 +1927,16 @@ def _plot(data, x=None, y=None, subplots=False, matplotlib line style per column logx : bool or 'sym', default False Use log scaling or symlog scaling on x axis - .. versionadded:: 0.25.0 + .. versionchanged:: 0.25.0 + logy : bool or 'sym' default False Use log scaling or symlog scaling on y axis - .. versionadded:: 0.25.0 + .. versionchanged:: 0.25.0 + loglog : bool or 'sym', default False Use log scaling or symlog scaling on both x and y axes - .. versionadded:: 0.25.0 + .. versionchanged:: 0.25.0 + xticks : sequence Values to use for the xticks yticks : sequence