From 668ff586c12a3c577ddd985a375030381e80539c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 25 Oct 2017 13:57:47 -0400 Subject: [PATCH] Backport PR #9477: In LogTransform, clip after log, not before. --- doc/api/api_changes.rst | 15 ++++++ lib/matplotlib/axes/_axes.py | 7 --- lib/matplotlib/axes/_base.py | 10 ---- lib/matplotlib/scale.py | 48 +++++++++++------- .../baseline_images/test_axes/log_scales.png | Bin 13712 -> 10312 bytes .../baseline_images/test_axes/log_scales.svg | 3 +- .../test_path/semi_log_with_zero.png | Bin 29487 -> 24929 bytes .../test_scale/logscale_mask.png | Bin 12163 -> 9503 bytes .../test_scale/logscale_nonpos_values.png | Bin 0 -> 17034 bytes lib/matplotlib/tests/test_path.py | 6 ++- lib/matplotlib/tests/test_scale.py | 34 +++++++++++++ 11 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_scale/logscale_nonpos_values.png diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index 790a56cb63a6..9e1c267f9c77 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -10,9 +10,24 @@ out what caused the breakage and how to fix it by updating your code. For new features that were added to Matplotlib, please see :ref:`whats-new`. +API Changes in 2.1.1 +==================== + +Default behavior of log scales reverted to clip <= 0 values +----------------------------------------------------------- + +The change it 2.1.0 to mask in logscale by default had more disruptive +changes than anticipated and has been reverted, however the clipping is now +done in a way that fixes the issues that motivated changing the default behavior +to ``'mask'``. + +As a side effect of this change, error bars which go negative now work as expected +on log scales. + API Changes in 2.1.0 ==================== + Default behavior of log scales changed to mask <= 0 values ---------------------------------------------------------- diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index c49327a58723..4064b339016f 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1556,11 +1556,9 @@ def loglog(self, *args, **kwargs): dx = {'basex': kwargs.pop('basex', 10), 'subsx': kwargs.pop('subsx', None), - 'nonposx': kwargs.pop('nonposx', 'mask'), } dy = {'basey': kwargs.pop('basey', 10), 'subsy': kwargs.pop('subsy', None), - 'nonposy': kwargs.pop('nonposy', 'mask'), } self.set_xscale('log', **dx) @@ -2851,11 +2849,6 @@ def errorbar(self, x, y, yerr=None, xerr=None, Valid kwargs for the marker properties are %(Line2D)s - - Notes - ----- - Error bars with negative values will not be shown when plotted on a - logarithmic axis. """ kwargs = cbook.normalize_kwargs(kwargs, _alias_map) # anything that comes in as 'None', drop so the default thing diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 87a9b08f08d1..5c3ca1ceec1d 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2970,11 +2970,6 @@ def set_xscale(self, value, **kwargs): matplotlib.scale.LogisticTransform : logit transform """ - # If the scale is being set to log, mask nonposx to prevent headaches - # around zero - if value.lower() == 'log' and 'nonposx' not in kwargs: - kwargs['nonposx'] = 'mask' - g = self.get_shared_x_axes() for ax in g.get_siblings(self): ax.xaxis._set_scale(value, **kwargs) @@ -3292,11 +3287,6 @@ def set_yscale(self, value, **kwargs): matplotlib.scale.LogisticTransform : logit transform """ - # If the scale is being set to log, mask nonposy to prevent headaches - # around zero - if value.lower() == 'log' and 'nonposy' not in kwargs: - kwargs['nonposy'] = 'mask' - g = self.get_shared_y_axes() for ax in g.get_siblings(self): ax.yaxis._set_scale(value, **kwargs) diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index 7b8f224ee221..459c1c0326c5 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -92,15 +92,24 @@ class LogTransformBase(Transform): def __init__(self, nonpos): Transform.__init__(self) - if nonpos == 'mask': - self._fill_value = np.nan - else: - self._fill_value = 1e-300 + self._clip = {"clip": True, "mask": False}[nonpos] def transform_non_affine(self, a): - with np.errstate(invalid="ignore"): - a = np.where(a <= 0, self._fill_value, a) - return np.divide(np.log(a, out=a), np.log(self.base), out=a) + with np.errstate(divide="ignore", invalid="ignore"): + out = np.log(a) + out /= np.log(self.base) + if self._clip: + # SVG spec says that conforming viewers must support values up + # to 3.4e38 (C float); however experiments suggest that Inkscape + # (which uses cairo for rendering) runs into cairo's 24-bit limit + # (which is apparently shared by Agg). + # Ghostscript (used for pdf rendering appears to overflow even + # earlier, with the max value around 2 ** 15 for the tests to pass. + # On the other hand, in practice, we want to clip beyond + # np.log10(np.nextafter(0, 1)) ~ -323 + # so 1000 seems safe. + out[a <= 0] = -1000 + return out class InvertedLogTransformBase(Transform): @@ -220,11 +229,17 @@ def __init__(self, axis, **kwargs): if axis.axis_name == 'x': base = kwargs.pop('basex', 10.0) subs = kwargs.pop('subsx', None) - nonpos = kwargs.pop('nonposx', 'mask') + nonpos = kwargs.pop('nonposx', 'clip') else: base = kwargs.pop('basey', 10.0) subs = kwargs.pop('subsy', None) - nonpos = kwargs.pop('nonposy', 'mask') + nonpos = kwargs.pop('nonposy', 'clip') + + if len(kwargs): + raise ValueError(("provided too many kwargs, can only pass " + "{'basex', 'subsx', nonposx'} or " + "{'basey', 'subsy', nonposy'}. You passed ") + + "{!r}".format(kwargs)) if nonpos not in ['mask', 'clip']: raise ValueError("nonposx, nonposy kwarg must be 'mask' or 'clip'") @@ -432,18 +447,17 @@ class LogitTransform(Transform): def __init__(self, nonpos): Transform.__init__(self) - if nonpos == 'mask': - self._fill_value = np.nan - else: - self._fill_value = 1e-300 self._nonpos = nonpos + self._clip = {"clip": True, "mask": False}[nonpos] def transform_non_affine(self, a): """logit transform (base 10), masked or clipped""" - with np.errstate(invalid="ignore"): - a = np.select( - [a <= 0, a >= 1], [self._fill_value, 1 - self._fill_value], a) - return np.log10(a / (1 - a)) + with np.errstate(divide="ignore", invalid="ignore"): + out = np.log10(a / (1 - a)) + if self._clip: # See LogTransform for choice of clip value. + out[a <= 0] = -1000 + out[1 <= a] = 1000 + return out def inverted(self): return LogisticTransform(self._nonpos) diff --git a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.png b/lib/matplotlib/tests/baseline_images/test_axes/log_scales.png index 9e68e62fd75dd87bffee3f4ab5d97f63fcaa2f09..e305de1d9ac7b55b59b28fa5f8d7d36e191619bb 100644 GIT binary patch literal 10312 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 literal 13712 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 -