From 2c03e251bcc40623ed4643ed6e340b9a4fda1bf2 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 24 Jul 2019 15:12:33 +0200 Subject: [PATCH 1/3] Make kwargs names in scale.py not include the axis direction. - Make names of kwargs to Scale subclasses independent of whether the underlying axis is x or y (so that after the deprecation period, one can just have a normal signature -- `def __init__(self, *, base=10, ...)`. We could possibly also change semilogx and semilogy to use the unsuffixed names (with deprecation), leaving only loglog with the suffixed names, or even also make loglog use unsuffixed names, in which case it would become impossible to set separate x and y bases directly in loglog -- one should then do `gca().set_xscale("log", base=...)` which doesn't seem too onerous. (loglog is the only case where the suffixed names has *some* utility.) In general, note that having kwargs that depend on the axis direction doesn't really scale -- for example, if we were to finally add log-scale support to mplot3d, should scale.py know about it and add `basez`? Should we have `baser` for log-scale polar plots? (at least in the radial direction, they make sense) - Move argument validation up the the Transform subclasses. - Make SymmetricalLogScale.{base,linthresh,linscale} properties mapping to the underlying transform so that they can't go out of sync. --- doc/api/next_api_changes/deprecations.rst | 10 ++ examples/scales/log_demo.py | 7 +- examples/scales/scales.py | 2 +- examples/scales/symlog_demo.py | 2 +- lib/matplotlib/axes/_axes.py | 31 +++--- lib/matplotlib/scale.py | 119 ++++++++++------------ lib/matplotlib/tests/test_axes.py | 8 +- lib/matplotlib/tests/test_scale.py | 12 +-- tutorials/introductory/pyplot.py | 2 +- 9 files changed, 93 insertions(+), 100 deletions(-) diff --git a/doc/api/next_api_changes/deprecations.rst b/doc/api/next_api_changes/deprecations.rst index 9dbf1691ad7d..0c8f9c0cc8b8 100644 --- a/doc/api/next_api_changes/deprecations.rst +++ b/doc/api/next_api_changes/deprecations.rst @@ -184,3 +184,13 @@ Debian 8 (2015, EOL 06/2020) and Ubuntu 14.04 (EOL 04/2019) were the last versions of Debian and Ubuntu to ship avconv. It remains possible to force the use of avconv by using the ffmpeg-based writers with :rc:`animation.ffmpeg_path` set to "avconv". + +Signature of `.LogScale` and `.SymmetricalLogScale` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +These classes used to take keyword arguments that depends on the axis +orientation ("basex" vs "basey", "nonposx" vs "nonposy"); these parameter +names are now deprecated in favor of "base", "nonpos", etc. This deprecation +also affects e.g. ``ax.set_yscale("log", basey=...)`` which must now be +spelled ``ax.set_yscale("log", base=...)``. The only place where "basex", etc. +remain in use is in the helper functions `~.Axes.loglog`, `~.Axes.semilogx`, +and `~.Axes.semilogy` (because `~.Axes.loglog` takes both "basex" and "basey"). diff --git a/examples/scales/log_demo.py b/examples/scales/log_demo.py index 19bfb858983e..2a04cb0beb73 100644 --- a/examples/scales/log_demo.py +++ b/examples/scales/log_demo.py @@ -26,7 +26,8 @@ ax2.grid() # log x and y axis -ax3.loglog(t, 20 * np.exp(-t / 10.0), basex=2) +ax3.loglog(t, 20 * np.exp(-t / 10.0)) +ax3.set_xscale('log', base=2) ax3.set(title='loglog base 2 on x') ax3.grid() @@ -35,8 +36,8 @@ x = 10.0**np.linspace(0.0, 2.0, 20) y = x**2.0 -ax4.set_xscale("log", nonposx='clip') -ax4.set_yscale("log", nonposy='clip') +ax4.set_xscale("log", nonpos='clip') +ax4.set_yscale("log", nonpos='clip') ax4.set(title='Errorbars go negative') ax4.errorbar(x, y, xerr=0.1 * x, yerr=5.0 + 0.75 * y) # ylim must be set after errorbar to allow errorbar to autoscale limits diff --git a/examples/scales/scales.py b/examples/scales/scales.py index fc3b3a7daf41..0716d90c7414 100644 --- a/examples/scales/scales.py +++ b/examples/scales/scales.py @@ -45,7 +45,7 @@ # symmetric log ax = axs[1, 1] ax.plot(x, y - y.mean()) -ax.set_yscale('symlog', linthreshy=0.02) +ax.set_yscale('symlog', linthresh=0.02) ax.set_title('symlog') ax.grid(True) diff --git a/examples/scales/symlog_demo.py b/examples/scales/symlog_demo.py index 0c27ba35e693..69266be29065 100644 --- a/examples/scales/symlog_demo.py +++ b/examples/scales/symlog_demo.py @@ -27,7 +27,7 @@ plt.subplot(313) plt.plot(x, np.sin(x / 3.0)) plt.xscale('symlog') -plt.yscale('symlog', linthreshy=0.015) +plt.yscale('symlog', linthresh=0.015) plt.grid(True) plt.ylabel('symlog both') diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index e788e17d9d1d..18395b373b86 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1795,16 +1795,13 @@ def loglog(self, *args, **kwargs): **kwargs All parameters supported by `.plot`. """ - dx = {k: kwargs.pop(k) for k in ['basex', 'subsx', 'nonposx'] + dx = {k[:-1]: kwargs.pop(k) for k in ['basex', 'subsx', 'nonposx'] if k in kwargs} - dy = {k: kwargs.pop(k) for k in ['basey', 'subsy', 'nonposy'] - if k in kwargs} - self.set_xscale('log', **dx) + dy = {k[:-1]: kwargs.pop(k) for k in ['basey', 'subsy', 'nonposy'] + if k in kwargs} self.set_yscale('log', **dy) - - l = self.plot(*args, **kwargs) - return l + return self.plot(*args, **kwargs) # @_preprocess_data() # let 'plot' do the unpacking.. @docstring.dedent_interpd @@ -1848,12 +1845,10 @@ def semilogx(self, *args, **kwargs): **kwargs All parameters supported by `.plot`. """ - d = {k: kwargs.pop(k) for k in ['basex', 'subsx', 'nonposx'] + d = {k[:-1]: kwargs.pop(k) for k in ['basex', 'subsx', 'nonposx'] if k in kwargs} - self.set_xscale('log', **d) - l = self.plot(*args, **kwargs) - return l + return self.plot(*args, **kwargs) # @_preprocess_data() # let 'plot' do the unpacking.. @docstring.dedent_interpd @@ -1897,12 +1892,10 @@ def semilogy(self, *args, **kwargs): **kwargs All parameters supported by `.plot`. """ - d = {k: kwargs.pop(k) for k in ['basey', 'subsy', 'nonposy'] + d = {k[:-1]: kwargs.pop(k) for k in ['basey', 'subsy', 'nonposy'] if k in kwargs} self.set_yscale('log', **d) - l = self.plot(*args, **kwargs) - - return l + return self.plot(*args, **kwargs) @_preprocess_data(replace_names=["x"], label_namer="x") def acorr(self, x, **kwargs): @@ -2343,11 +2336,11 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center", if orientation == 'vertical': self._process_unit_info(xdata=x, ydata=height, kwargs=kwargs) if log: - self.set_yscale('log', nonposy='clip') + self.set_yscale('log', nonpos='clip') elif orientation == 'horizontal': self._process_unit_info(xdata=width, ydata=y, kwargs=kwargs) if log: - self.set_xscale('log', nonposx='clip') + self.set_xscale('log', nonpos='clip') # lets do some conversions now since some types cannot be # subtracted uniformly @@ -6715,9 +6708,9 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, if log: if orientation == 'horizontal': - self.set_xscale('log', nonposx='clip') + self.set_xscale('log', nonpos='clip') else: # orientation == 'vertical' - self.set_yscale('log', nonposy='clip') + self.set_yscale('log', nonpos='clip') if align == 'left': x -= 0.5*(bins[1]-bins[0]) diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index f61ca49993e7..116c3cc94a05 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -281,8 +281,11 @@ class LogTransform(Transform): def __init__(self, base, nonpos='clip'): Transform.__init__(self) + if base <= 0 or base == 1: + raise ValueError('The log base cannot be <= 0 or == 1') self.base = base - self._clip = {"clip": True, "mask": False}[nonpos] + self._clip = cbook._check_getitem( + {"clip": True, "mask": False}, nonpos=nonpos) def __str__(self): return "{}(base={}, nonpos={!r})".format( @@ -362,41 +365,32 @@ def __init__(self, axis, **kwargs): ---------- axis : `~matplotlib.axis.Axis` The axis for the scale. - basex, basey : float, default: 10 + base : float, default: 10 The base of the logarithm. - nonposx, nonposy : {'clip', 'mask'}, default: 'clip' + nonpos : {'clip', 'mask'}, default: 'clip' Determines the behavior for non-positive values. They can either be masked as invalid, or clipped to a very small positive number. - subsx, subsy : sequence of int, default: None - Where to place the subticks between each major tick. - For example, in a log10 scale: ``[2, 3, 4, 5, 6, 7, 8, 9]`` - will place 8 logarithmically spaced minor ticks between - each major tick. + subs : sequence of int, default: None + Where to place the subticks between each major tick. For example, + in a log10 scale, ``[2, 3, 4, 5, 6, 7, 8, 9]`` will place 8 + logarithmically spaced minor ticks between each major tick. """ - if axis.axis_name == 'x': - base = kwargs.pop('basex', 10.0) - subs = kwargs.pop('subsx', None) - nonpos = kwargs.pop('nonposx', 'clip') - cbook._check_in_list(['mask', 'clip'], nonposx=nonpos) - else: - base = kwargs.pop('basey', 10.0) - subs = kwargs.pop('subsy', None) - nonpos = kwargs.pop('nonposy', 'clip') - cbook._check_in_list(['mask', 'clip'], nonposy=nonpos) - - if kwargs: - raise TypeError(f"LogScale got an unexpected keyword " - f"argument {next(iter(kwargs))!r}") - - if base <= 0 or base == 1: - raise ValueError('The log base cannot be <= 0 or == 1') - + # After the deprecation, the whole (outer) __init__ can be replaced by + # def __init__(self, axis, *, base=10, subs=None, nonpos="clip"): ... + # The following is to emit the right warnings depending on the axis + # used, as the *old* kwarg names depended on the axis. + axis_name = getattr(axis, "axis_name", "x") + @cbook._rename_parameter("3.3", f"base{axis_name}", "base") + @cbook._rename_parameter("3.3", f"subs{axis_name}", "subs") + @cbook._rename_parameter("3.3", f"nonpos{axis_name}", "nonpos") + def __init__(*, base=10, subs=None, nonpos="clip"): + return base, subs, nonpos + + base, subs, nonpos = __init__(**kwargs) self._transform = LogTransform(base, nonpos) self.subs = subs - @property - def base(self): - return self._transform.base + base = property(lambda self: self._transform.base) def set_default_locators_and_formatters(self, axis): # docstring inherited @@ -463,6 +457,12 @@ class SymmetricalLogTransform(Transform): def __init__(self, base, linthresh, linscale): Transform.__init__(self) + if base <= 1.0: + raise ValueError("'base' must be larger than 1") + if linthresh <= 0.0: + raise ValueError("'linthresh' must be positive") + if linscale <= 0.0: + raise ValueError("'linscale' must be positive") self.base = base self.linthresh = linthresh self.linscale = linscale @@ -523,19 +523,19 @@ class SymmetricalLogScale(ScaleBase): Parameters ---------- - basex, basey : float, default: 10 + base : float, default: 10 The base of the logarithm. - linthreshx, linthreshy : float, default: 2 + linthresh : float, default: 2 Defines the range ``(-x, x)``, within which the plot is linear. This avoids having the plot go to infinity around zero. - subsx, subsy : sequence of int + subs : sequence of int Where to place the subticks between each major tick. For example, in a log10 scale: ``[2, 3, 4, 5, 6, 7, 8, 9]`` will place 8 logarithmically spaced minor ticks between each major tick. - linscalex, linscaley : float, optional + linscale : float, optional This allows the linear range ``(-linthresh, linthresh)`` to be stretched relative to the logarithmic range. Its value is the number of decades to use for each half of the linear range. For example, when @@ -557,40 +557,31 @@ def InvertedSymmetricalLogTransform(self): return InvertedSymmetricalLogTransform def __init__(self, axis, **kwargs): - if axis.axis_name == 'x': - base = kwargs.pop('basex', 10.0) - linthresh = kwargs.pop('linthreshx', 2.0) - subs = kwargs.pop('subsx', None) - linscale = kwargs.pop('linscalex', 1.0) - else: - base = kwargs.pop('basey', 10.0) - linthresh = kwargs.pop('linthreshy', 2.0) - subs = kwargs.pop('subsy', None) - linscale = kwargs.pop('linscaley', 1.0) - if kwargs: - warn_deprecated( - '3.2.0', - message=( - f"SymmetricalLogScale got an unexpected keyword " - f"argument {next(iter(kwargs))!r}. " - 'In the future this will raise TypeError') - ) - # raise TypeError(f"SymmetricalLogScale got an unexpected keyword " - # f"argument {next(iter(kwargs))!r}") - - if base <= 1.0: - raise ValueError("'basex/basey' must be larger than 1") - if linthresh <= 0.0: - raise ValueError("'linthreshx/linthreshy' must be positive") - if linscale <= 0.0: - raise ValueError("'linscalex/linthreshy' must be positive") - + axis_name = getattr(axis, "axis_name", "x") + # See explanation in LogScale.__init__. + @cbook._rename_parameter("3.3", f"base{axis_name}", "base") + @cbook._rename_parameter("3.3", f"linthresh{axis_name}", "linthresh") + @cbook._rename_parameter("3.3", f"subs{axis_name}", "subs") + @cbook._rename_parameter("3.3", f"linscale{axis_name}", "linscale") + def __init__(*, base=10, linthresh=2, subs=None, linscale=1, **kwargs): + if kwargs: + warn_deprecated( + "3.2.0", + message=( + f"SymmetricalLogScale got an unexpected keyword " + f"argument {next(iter(kwargs))!r}; in the future, " + f"this will raise a TypeError") + ) + return base, linthresh, subs, linscale + + base, linthresh, subs, linscale = __init__(**kwargs) self._transform = SymmetricalLogTransform(base, linthresh, linscale) - self.base = base - self.linthresh = linthresh - self.linscale = linscale self.subs = subs + base = property(lambda self: self._transform.base) + linthresh = property(lambda self: self._transform.linthresh) + linscale = property(lambda self: self._transform.linscale) + def set_default_locators_and_formatters(self, axis): # docstring inherited axis.set_major_locator(SymmetricalLogLocator(self.get_transform())) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 8e8729751290..f2c014671f74 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1150,9 +1150,9 @@ def test_symlog2(): x = np.arange(-50, 50, 0.001) fig, axs = plt.subplots(5, 1) - for ax, linthreshx in zip(axs, [20., 2., 1., 0.1, 0.01]): + for ax, linthresh in zip(axs, [20., 2., 1., 0.1, 0.01]): ax.plot(x, x) - ax.set_xscale('symlog', linthreshx=linthreshx) + ax.set_xscale('symlog', linthresh=linthresh) ax.grid(True) axs[-1].set_ylim(-0.1, 0.1) @@ -2241,9 +2241,9 @@ def test_log_scales(): fig = plt.figure() ax = fig.add_subplot(1, 1, 1) ax.plot(np.log(np.linspace(0.1, 100))) - ax.set_yscale('log', basey=5.5) + ax.set_yscale('log', base=5.5) ax.invert_yaxis() - ax.set_xscale('log', basex=9.0) + ax.set_xscale('log', base=9.0) def test_log_scales_no_data(): diff --git a/lib/matplotlib/tests/test_scale.py b/lib/matplotlib/tests/test_scale.py index 0acc89e8b1aa..d838fcd04049 100644 --- a/lib/matplotlib/tests/test_scale.py +++ b/lib/matplotlib/tests/test_scale.py @@ -88,7 +88,7 @@ def test_log_scatter(): def test_logscale_subs(): fig, ax = plt.subplots() - ax.set_yscale('log', subsy=np.array([2, 3, 4])) + ax.set_yscale('log', subs=np.array([2, 3, 4])) # force draw fig.canvas.draw() @@ -108,16 +108,14 @@ def test_logscale_mask(): def test_extra_kwargs_raise_or_warn(): fig, ax = plt.subplots() - # with pytest.raises(TypeError): with pytest.warns(MatplotlibDeprecationWarning): - ax.set_yscale('linear', nonpos='mask') + ax.set_yscale('linear', foo='mask') with pytest.raises(TypeError): - ax.set_yscale('log', nonpos='mask') + ax.set_yscale('log', foo='mask') - # with pytest.raises(TypeError): with pytest.warns(MatplotlibDeprecationWarning): - ax.set_yscale('symlog', nonpos='mask') + ax.set_yscale('symlog', foo='mask') def test_logscale_invert_transform(): @@ -150,7 +148,7 @@ def test_logscale_nonpos_values(): ax1.hist(xs, range=(-5, 5), bins=10) ax1.set_yscale('log') ax2.hist(xs, range=(-5, 5), bins=10) - ax2.set_yscale('log', nonposy='mask') + ax2.set_yscale('log', nonpos='mask') xdata = np.arange(0, 10, 0.01) ydata = np.exp(-xdata) diff --git a/tutorials/introductory/pyplot.py b/tutorials/introductory/pyplot.py index c1c31abc4b18..d9880e83ea8f 100644 --- a/tutorials/introductory/pyplot.py +++ b/tutorials/introductory/pyplot.py @@ -443,7 +443,7 @@ def f(t): # symmetric log plt.subplot(223) plt.plot(x, y - y.mean()) -plt.yscale('symlog', linthreshy=0.01) +plt.yscale('symlog', linthresh=0.01) plt.title('symlog') plt.grid(True) From a00c27c1eb309118be2fc7dd362a643d24c9d56e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 4 Feb 2020 08:27:22 +0100 Subject: [PATCH 2/3] Also change the kwarg names on semilogx/y and loglog. --- doc/api/next_api_changes/deprecations.rst | 20 ++++--- lib/matplotlib/axes/_axes.py | 68 ++++++++++++----------- lib/matplotlib/tests/test_axes.py | 38 ++++++++++--- 3 files changed, 76 insertions(+), 50 deletions(-) diff --git a/doc/api/next_api_changes/deprecations.rst b/doc/api/next_api_changes/deprecations.rst index 0c8f9c0cc8b8..deccd2527e6b 100644 --- a/doc/api/next_api_changes/deprecations.rst +++ b/doc/api/next_api_changes/deprecations.rst @@ -185,12 +185,14 @@ last versions of Debian and Ubuntu to ship avconv. It remains possible to force the use of avconv by using the ffmpeg-based writers with :rc:`animation.ffmpeg_path` set to "avconv". -Signature of `.LogScale` and `.SymmetricalLogScale` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -These classes used to take keyword arguments that depends on the axis -orientation ("basex" vs "basey", "nonposx" vs "nonposy"); these parameter -names are now deprecated in favor of "base", "nonpos", etc. This deprecation -also affects e.g. ``ax.set_yscale("log", basey=...)`` which must now be -spelled ``ax.set_yscale("log", base=...)``. The only place where "basex", etc. -remain in use is in the helper functions `~.Axes.loglog`, `~.Axes.semilogx`, -and `~.Axes.semilogy` (because `~.Axes.loglog` takes both "basex" and "basey"). +log/symlog scale base, ticks, and nonpos specification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`~.Axes.semilogx`, `~.Axes.semilogy`, `~.Axes.loglog`, `.LogScale`, and +`.SymmetricalLogScale` used to take keyword arguments that depends on the axis +orientation ("basex" vs "basey", "nonposx" vs "nonposy"); these parameter names +are now deprecated in favor of "base", "nonpos", etc. This deprecation also +affects e.g. ``ax.set_yscale("log", basey=...)`` which must now be spelled +``ax.set_yscale("log", base=...)``. + +To use *different* bases for the x-axis and y-axis of a `~.Axes.loglog` plot, +use e.g. ``ax.set_xscale("log", base=10); ax.set_yscale("log", base=2)``. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 18395b373b86..4679437c858c 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1766,24 +1766,25 @@ def loglog(self, *args, **kwargs): both the x-axis and the y-axis to log scaling. All of the concepts and parameters of plot can be used here as well. - The additional parameters *basex/y*, *subsx/y* and *nonposx/y* control - the x/y-axis properties. They are just forwarded to `.Axes.set_xscale` - and `.Axes.set_yscale`. + The additional parameters *base*, *subs* and *nonpos* control the + x/y-axis properties. They are just forwarded to `.Axes.set_xscale` and + `.Axes.set_yscale`. To use different properties on the x-axis and the + y-axis, use e.g. + ``ax.set_xscale("log", base=10); ax.set_yscale("log", base=2)``. Parameters ---------- - basex, basey : float, default: 10 - Base of the x/y logarithm. + base : float, default: 10 + Base of the logarithm. - subsx, subsy : sequence, optional - The location of the minor x/y ticks. If *None*, reasonable - locations are automatically chosen depending on the number of - decades in the plot. - See `.Axes.set_xscale` / `.Axes.set_yscale` for details. + subs : sequence, optional + The location of the minor ticks. If *None*, reasonable locations + are automatically chosen depending on the number of decades in the + plot. See `.Axes.set_xscale`/`.Axes.set_yscale` for details. - nonposx, nonposy : {'mask', 'clip'}, default: 'mask' - Non-positive values in x or y can be masked as invalid, or clipped - to a very small positive number. + nonpos : {'mask', 'clip'}, default: 'mask' + Non-positive values can be masked as invalid, or clipped to a very + small positive number. Returns ------- @@ -1795,13 +1796,14 @@ def loglog(self, *args, **kwargs): **kwargs All parameters supported by `.plot`. """ - dx = {k[:-1]: kwargs.pop(k) for k in ['basex', 'subsx', 'nonposx'] - if k in kwargs} + dx = {k: v for k, v in kwargs.items() + if k in ['base', 'subs', 'nonpos', 'basex', 'subsx', 'nonposx']} self.set_xscale('log', **dx) - dy = {k[:-1]: kwargs.pop(k) for k in ['basey', 'subsy', 'nonposy'] - if k in kwargs} + dy = {k: v for k, v in kwargs.items() + if k in ['base', 'subs', 'nonpos', 'basey', 'subsy', 'nonposy']} self.set_yscale('log', **dy) - return self.plot(*args, **kwargs) + return self.plot( + *args, **{k: v for k, v in kwargs.items() if k not in {*dx, *dy}}) # @_preprocess_data() # let 'plot' do the unpacking.. @docstring.dedent_interpd @@ -1818,20 +1820,20 @@ def semilogx(self, *args, **kwargs): the x-axis to log scaling. All of the concepts and parameters of plot can be used here as well. - The additional parameters *basex*, *subsx* and *nonposx* control the + The additional parameters *base*, *subs*, and *nonpos* control the x-axis properties. They are just forwarded to `.Axes.set_xscale`. Parameters ---------- - basex : float, default: 10 + base : float, default: 10 Base of the x logarithm. - subsx : array-like, optional + subs : array-like, optional The location of the minor xticks. If *None*, reasonable locations are automatically chosen depending on the number of decades in the plot. See `.Axes.set_xscale` for details. - nonposx : {'mask', 'clip'}, default: 'mask' + nonpos : {'mask', 'clip'}, default: 'mask' Non-positive values in x can be masked as invalid, or clipped to a very small positive number. @@ -1845,10 +1847,11 @@ def semilogx(self, *args, **kwargs): **kwargs All parameters supported by `.plot`. """ - d = {k[:-1]: kwargs.pop(k) for k in ['basex', 'subsx', 'nonposx'] - if k in kwargs} + d = {k: v for k, v in kwargs.items() + if k in ['base', 'subs', 'nonpos', 'basex', 'subsx', 'nonposx']} self.set_xscale('log', **d) - return self.plot(*args, **kwargs) + return self.plot( + *args, **{k: v for k, v in kwargs.items() if k not in d}) # @_preprocess_data() # let 'plot' do the unpacking.. @docstring.dedent_interpd @@ -1865,20 +1868,20 @@ def semilogy(self, *args, **kwargs): the y-axis to log scaling. All of the concepts and parameters of plot can be used here as well. - The additional parameters *basey*, *subsy* and *nonposy* control the + The additional parameters *base*, *subs*, and *nonpos* control the y-axis properties. They are just forwarded to `.Axes.set_yscale`. Parameters ---------- - basey : float, default: 10 + base : float, default: 10 Base of the y logarithm. - subsy : array-like, optional + subs : array-like, optional The location of the minor yticks. If *None*, reasonable locations are automatically chosen depending on the number of decades in the plot. See `.Axes.set_yscale` for details. - nonposy : {'mask', 'clip'}, default: 'mask' + nonpos : {'mask', 'clip'}, default: 'mask' Non-positive values in y can be masked as invalid, or clipped to a very small positive number. @@ -1892,10 +1895,11 @@ def semilogy(self, *args, **kwargs): **kwargs All parameters supported by `.plot`. """ - d = {k[:-1]: kwargs.pop(k) for k in ['basey', 'subsy', 'nonposy'] - if k in kwargs} + d = {k: v for k, v in kwargs.items() + if k in ['base', 'subs', 'nonpos', 'basey', 'subsy', 'nonposy']} self.set_yscale('log', **d) - return self.plot(*args, **kwargs) + return self.plot( + *args, **{k: v for k, v in kwargs.items() if k not in d}) @_preprocess_data(replace_names=["x"], label_namer="x") def acorr(self, x, **kwargs): diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f2c014671f74..ace2767be6a6 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1,16 +1,19 @@ from collections import namedtuple -from itertools import product +import datetime +from decimal import Decimal import io +from itertools import product import platform - -import datetime +try: + from contextlib import nullcontext +except ImportError: + from contextlib import ExitStack as nullcontext # Py3.6. import dateutil.tz as dutz import numpy as np from numpy import ma from cycler import cycler -from decimal import Decimal import pytest import matplotlib @@ -5666,21 +5669,38 @@ def test_loglog(): ax.tick_params(length=15, width=2, which='minor') +@pytest.mark.parametrize("new_api", [False, True]) @image_comparison(["test_loglog_nonpos.png"], remove_text=True, style='mpl20') -def test_loglog_nonpos(): - fig, ax = plt.subplots(3, 3) +def test_loglog_nonpos(new_api): + fig, axs = plt.subplots(3, 3) x = np.arange(1, 11) y = x**3 y[7] = -3. x[4] = -10 - for nn, mcx in enumerate(['mask', 'clip', '']): - for mm, mcy in enumerate(['mask', 'clip', '']): + for (i, j), ax in np.ndenumerate(axs): + mcx = ['mask', 'clip', ''][j] + mcy = ['mask', 'clip', ''][i] + if new_api: + if mcx == mcy: + if mcx: + ax.loglog(x, y**3, lw=2, nonpos=mcx) + else: + ax.loglog(x, y**3, lw=2) + else: + ax.loglog(x, y**3, lw=2) + if mcx: + ax.set_xscale("log", nonpos=mcx) + if mcy: + ax.set_yscale("log", nonpos=mcy) + else: kws = {} if mcx: kws['nonposx'] = mcx if mcy: kws['nonposy'] = mcy - ax[mm, nn].loglog(x, y**3, lw=2, **kws) + with (pytest.warns(MatplotlibDeprecationWarning) if kws + else nullcontext()): + ax.loglog(x, y**3, lw=2, **kws) @pytest.mark.style('default') From ea57339d07b66b87cd84a087e5a175c44792bd51 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 9 Feb 2020 11:46:17 +0100 Subject: [PATCH 3/3] Also rename nonpos->nonpositive. --- doc/api/next_api_changes/deprecations.rst | 11 +++-- examples/scales/log_demo.py | 4 +- lib/matplotlib/axes/_axes.py | 32 +++++++------ lib/matplotlib/scale.py | 58 +++++++++++------------ lib/matplotlib/tests/test_axes.py | 6 +-- lib/matplotlib/tests/test_scale.py | 14 +++--- 6 files changed, 64 insertions(+), 61 deletions(-) diff --git a/doc/api/next_api_changes/deprecations.rst b/doc/api/next_api_changes/deprecations.rst index deccd2527e6b..7e9e32c39444 100644 --- a/doc/api/next_api_changes/deprecations.rst +++ b/doc/api/next_api_changes/deprecations.rst @@ -189,10 +189,13 @@ log/symlog scale base, ticks, and nonpos specification ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ `~.Axes.semilogx`, `~.Axes.semilogy`, `~.Axes.loglog`, `.LogScale`, and `.SymmetricalLogScale` used to take keyword arguments that depends on the axis -orientation ("basex" vs "basey", "nonposx" vs "nonposy"); these parameter names -are now deprecated in favor of "base", "nonpos", etc. This deprecation also -affects e.g. ``ax.set_yscale("log", basey=...)`` which must now be spelled -``ax.set_yscale("log", base=...)``. +orientation ("basex" vs "basey", "subsx" vs "subsy", "nonposx" vs "nonposy"); +these parameter names are now deprecated in favor of "base", "subs", +"nonpositive". This deprecation also affects e.g. ``ax.set_yscale("log", +basey=...)`` which must now be spelled ``ax.set_yscale("log", base=...)``. + +The change from "nonpos" to "nonpositive" also affects `~.scale.LogTransform`, +`~.scale.InvertedLogTransform`, `~.scale.SymmetricalLogTransform`, etc. To use *different* bases for the x-axis and y-axis of a `~.Axes.loglog` plot, use e.g. ``ax.set_xscale("log", base=10); ax.set_yscale("log", base=2)``. diff --git a/examples/scales/log_demo.py b/examples/scales/log_demo.py index 2a04cb0beb73..0db3a3e74b2b 100644 --- a/examples/scales/log_demo.py +++ b/examples/scales/log_demo.py @@ -36,8 +36,8 @@ x = 10.0**np.linspace(0.0, 2.0, 20) y = x**2.0 -ax4.set_xscale("log", nonpos='clip') -ax4.set_yscale("log", nonpos='clip') +ax4.set_xscale("log", nonpositive='clip') +ax4.set_yscale("log", nonpositive='clip') ax4.set(title='Errorbars go negative') ax4.errorbar(x, y, xerr=0.1 * x, yerr=5.0 + 0.75 * y) # ylim must be set after errorbar to allow errorbar to autoscale limits diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4679437c858c..7a7105eee40b 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1766,7 +1766,7 @@ def loglog(self, *args, **kwargs): both the x-axis and the y-axis to log scaling. All of the concepts and parameters of plot can be used here as well. - The additional parameters *base*, *subs* and *nonpos* control the + The additional parameters *base*, *subs* and *nonpositive* control the x/y-axis properties. They are just forwarded to `.Axes.set_xscale` and `.Axes.set_yscale`. To use different properties on the x-axis and the y-axis, use e.g. @@ -1782,7 +1782,7 @@ def loglog(self, *args, **kwargs): are automatically chosen depending on the number of decades in the plot. See `.Axes.set_xscale`/`.Axes.set_yscale` for details. - nonpos : {'mask', 'clip'}, default: 'mask' + nonpositive : {'mask', 'clip'}, default: 'mask' Non-positive values can be masked as invalid, or clipped to a very small positive number. @@ -1797,10 +1797,12 @@ def loglog(self, *args, **kwargs): All parameters supported by `.plot`. """ dx = {k: v for k, v in kwargs.items() - if k in ['base', 'subs', 'nonpos', 'basex', 'subsx', 'nonposx']} + if k in ['base', 'subs', 'nonpositive', + 'basex', 'subsx', 'nonposx']} self.set_xscale('log', **dx) dy = {k: v for k, v in kwargs.items() - if k in ['base', 'subs', 'nonpos', 'basey', 'subsy', 'nonposy']} + if k in ['base', 'subs', 'nonpositive', + 'basey', 'subsy', 'nonposy']} self.set_yscale('log', **dy) return self.plot( *args, **{k: v for k, v in kwargs.items() if k not in {*dx, *dy}}) @@ -1820,7 +1822,7 @@ def semilogx(self, *args, **kwargs): the x-axis to log scaling. All of the concepts and parameters of plot can be used here as well. - The additional parameters *base*, *subs*, and *nonpos* control the + The additional parameters *base*, *subs*, and *nonpositive* control the x-axis properties. They are just forwarded to `.Axes.set_xscale`. Parameters @@ -1833,7 +1835,7 @@ def semilogx(self, *args, **kwargs): are automatically chosen depending on the number of decades in the plot. See `.Axes.set_xscale` for details. - nonpos : {'mask', 'clip'}, default: 'mask' + nonpositive : {'mask', 'clip'}, default: 'mask' Non-positive values in x can be masked as invalid, or clipped to a very small positive number. @@ -1848,7 +1850,8 @@ def semilogx(self, *args, **kwargs): All parameters supported by `.plot`. """ d = {k: v for k, v in kwargs.items() - if k in ['base', 'subs', 'nonpos', 'basex', 'subsx', 'nonposx']} + if k in ['base', 'subs', 'nonpositive', + 'basex', 'subsx', 'nonposx']} self.set_xscale('log', **d) return self.plot( *args, **{k: v for k, v in kwargs.items() if k not in d}) @@ -1868,7 +1871,7 @@ def semilogy(self, *args, **kwargs): the y-axis to log scaling. All of the concepts and parameters of plot can be used here as well. - The additional parameters *base*, *subs*, and *nonpos* control the + The additional parameters *base*, *subs*, and *nonpositive* control the y-axis properties. They are just forwarded to `.Axes.set_yscale`. Parameters @@ -1881,7 +1884,7 @@ def semilogy(self, *args, **kwargs): are automatically chosen depending on the number of decades in the plot. See `.Axes.set_yscale` for details. - nonpos : {'mask', 'clip'}, default: 'mask' + nonpositive : {'mask', 'clip'}, default: 'mask' Non-positive values in y can be masked as invalid, or clipped to a very small positive number. @@ -1896,7 +1899,8 @@ def semilogy(self, *args, **kwargs): All parameters supported by `.plot`. """ d = {k: v for k, v in kwargs.items() - if k in ['base', 'subs', 'nonpos', 'basey', 'subsy', 'nonposy']} + if k in ['base', 'subs', 'nonpositive', + 'basey', 'subsy', 'nonposy']} self.set_yscale('log', **d) return self.plot( *args, **{k: v for k, v in kwargs.items() if k not in d}) @@ -2340,11 +2344,11 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center", if orientation == 'vertical': self._process_unit_info(xdata=x, ydata=height, kwargs=kwargs) if log: - self.set_yscale('log', nonpos='clip') + self.set_yscale('log', nonpositive='clip') elif orientation == 'horizontal': self._process_unit_info(xdata=width, ydata=y, kwargs=kwargs) if log: - self.set_xscale('log', nonpos='clip') + self.set_xscale('log', nonpositive='clip') # lets do some conversions now since some types cannot be # subtracted uniformly @@ -6712,9 +6716,9 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, if log: if orientation == 'horizontal': - self.set_xscale('log', nonpos='clip') + self.set_xscale('log', nonpositive='clip') else: # orientation == 'vertical' - self.set_yscale('log', nonpos='clip') + self.set_yscale('log', nonpositive='clip') if align == 'left': x -= 0.5*(bins[1]-bins[0]) diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index 116c3cc94a05..2f127587626e 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -279,16 +279,17 @@ def inverted(self): class LogTransform(Transform): input_dims = output_dims = 1 - def __init__(self, base, nonpos='clip'): + @cbook._rename_parameter("3.3", "nonpos", "nonpositive") + def __init__(self, base, nonpositive='clip'): Transform.__init__(self) if base <= 0 or base == 1: raise ValueError('The log base cannot be <= 0 or == 1') self.base = base self._clip = cbook._check_getitem( - {"clip": True, "mask": False}, nonpos=nonpos) + {"clip": True, "mask": False}, nonpositive=nonpositive) def __str__(self): - return "{}(base={}, nonpos={!r})".format( + return "{}(base={}, nonpositive={!r})".format( type(self).__name__, self.base, "clip" if self._clip else "mask") def transform_non_affine(self, a): @@ -367,7 +368,7 @@ def __init__(self, axis, **kwargs): The axis for the scale. base : float, default: 10 The base of the logarithm. - nonpos : {'clip', 'mask'}, default: 'clip' + nonpositive : {'clip', 'mask'}, default: 'clip' Determines the behavior for non-positive values. They can either be masked as invalid, or clipped to a very small positive number. subs : sequence of int, default: None @@ -376,18 +377,18 @@ def __init__(self, axis, **kwargs): logarithmically spaced minor ticks between each major tick. """ # After the deprecation, the whole (outer) __init__ can be replaced by - # def __init__(self, axis, *, base=10, subs=None, nonpos="clip"): ... + # def __init__(self, axis, *, base=10, subs=None, nonpositive="clip") # The following is to emit the right warnings depending on the axis # used, as the *old* kwarg names depended on the axis. axis_name = getattr(axis, "axis_name", "x") @cbook._rename_parameter("3.3", f"base{axis_name}", "base") @cbook._rename_parameter("3.3", f"subs{axis_name}", "subs") - @cbook._rename_parameter("3.3", f"nonpos{axis_name}", "nonpos") - def __init__(*, base=10, subs=None, nonpos="clip"): - return base, subs, nonpos + @cbook._rename_parameter("3.3", f"nonpos{axis_name}", "nonpositive") + def __init__(*, base=10, subs=None, nonpositive="clip"): + return base, subs, nonpositive - base, subs, nonpos = __init__(**kwargs) - self._transform = LogTransform(base, nonpos) + base, subs, nonpositive = __init__(**kwargs) + self._transform = LogTransform(base, nonpositive) self.subs = subs base = property(lambda self: self._transform.base) @@ -598,11 +599,12 @@ def get_transform(self): class LogitTransform(Transform): input_dims = output_dims = 1 - def __init__(self, nonpos='mask'): + @cbook._rename_parameter("3.3", "nonpos", "nonpositive") + def __init__(self, nonpositive='mask'): Transform.__init__(self) - cbook._check_in_list(['mask', 'clip'], nonpos=nonpos) - self._nonpos = nonpos - self._clip = {"clip": True, "mask": False}[nonpos] + cbook._check_in_list(['mask', 'clip'], nonpositive=nonpositive) + self._nonpositive = nonpositive + self._clip = {"clip": True, "mask": False}[nonpositive] def transform_non_affine(self, a): """logit transform (base 10), masked or clipped""" @@ -614,28 +616,29 @@ def transform_non_affine(self, a): return out def inverted(self): - return LogisticTransform(self._nonpos) + return LogisticTransform(self._nonpositive) def __str__(self): - return "{}({!r})".format(type(self).__name__, self._nonpos) + return "{}({!r})".format(type(self).__name__, self._nonpositive) class LogisticTransform(Transform): input_dims = output_dims = 1 - def __init__(self, nonpos='mask'): + @cbook._rename_parameter("3.3", "nonpos", "nonpositive") + def __init__(self, nonpositive='mask'): Transform.__init__(self) - self._nonpos = nonpos + self._nonpositive = nonpositive def transform_non_affine(self, a): """logistic transform (base 10)""" return 1.0 / (1 + 10**(-a)) def inverted(self): - return LogitTransform(self._nonpos) + return LogitTransform(self._nonpositive) def __str__(self): - return "{}({!r})".format(type(self).__name__, self._nonpos) + return "{}({!r})".format(type(self).__name__, self._nonpositive) class LogitScale(ScaleBase): @@ -647,20 +650,15 @@ class LogitScale(ScaleBase): """ name = 'logit' - def __init__( - self, - axis, - nonpos='mask', - *, - one_half=r"\frac{1}{2}", - use_overline=False, - ): + @cbook._rename_parameter("3.3", "nonpos", "nonpositive") + def __init__(self, axis, nonpositive='mask', *, + one_half=r"\frac{1}{2}", use_overline=False): r""" Parameters ---------- axis : `matplotlib.axis.Axis` Currently unused. - nonpos : {'mask', 'clip'} + nonpositive : {'mask', 'clip'} Determines the behavior for values beyond the open interval ]0, 1[. They can either be masked as invalid, or clipped to a number very close to 0 or 1. @@ -670,7 +668,7 @@ def __init__( one_half : str, default: r"\frac{1}{2}" The string used for ticks formatter to represent 1/2. """ - self._transform = LogitTransform(nonpos) + self._transform = LogitTransform(nonpositive) self._use_overline = use_overline self._one_half = one_half diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index ace2767be6a6..c87128d63616 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5683,15 +5683,15 @@ def test_loglog_nonpos(new_api): if new_api: if mcx == mcy: if mcx: - ax.loglog(x, y**3, lw=2, nonpos=mcx) + ax.loglog(x, y**3, lw=2, nonpositive=mcx) else: ax.loglog(x, y**3, lw=2) else: ax.loglog(x, y**3, lw=2) if mcx: - ax.set_xscale("log", nonpos=mcx) + ax.set_xscale("log", nonpositive=mcx) if mcy: - ax.set_yscale("log", nonpos=mcy) + ax.set_yscale("log", nonpositive=mcy) else: kws = {} if mcx: diff --git a/lib/matplotlib/tests/test_scale.py b/lib/matplotlib/tests/test_scale.py index d838fcd04049..d119940a13a3 100644 --- a/lib/matplotlib/tests/test_scale.py +++ b/lib/matplotlib/tests/test_scale.py @@ -1,7 +1,8 @@ from matplotlib.cbook import MatplotlibDeprecationWarning import matplotlib.pyplot as plt -from matplotlib.scale import (Log10Transform, InvertedLog10Transform, - SymmetricalLogTransform) +from matplotlib.scale import ( + LogTransform, Log10Transform, InvertedLog10Transform, + SymmetricalLogTransform) from matplotlib.testing.decorators import check_figures_equal, image_comparison import numpy as np @@ -132,11 +133,8 @@ def test_logscale_invert_transform(): def test_logscale_transform_repr(): fig, ax = plt.subplots() ax.set_yscale('log') - repr(ax.transData) # check that repr of log transform succeeds - - # check that repr of log transform succeeds - with pytest.warns(MatplotlibDeprecationWarning): - repr(Log10Transform(nonpos='clip')) + repr(ax.transData) + repr(LogTransform(10, nonpositive='clip')) @image_comparison(['logscale_nonpos_values.png'], @@ -148,7 +146,7 @@ def test_logscale_nonpos_values(): ax1.hist(xs, range=(-5, 5), bins=10) ax1.set_yscale('log') ax2.hist(xs, range=(-5, 5), bins=10) - ax2.set_yscale('log', nonpos='mask') + ax2.set_yscale('log', nonpositive='mask') xdata = np.arange(0, 10, 0.01) ydata = np.exp(-xdata)