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 2275361aa17c..69d21b1ec732 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 00cc9ee7aa4a..ab07179ba0f2 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 9e68e62fd75d..e305de1d9ac7 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.png and b/lib/matplotlib/tests/baseline_images/test_axes/log_scales.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.svg b/lib/matplotlib/tests/baseline_images/test_axes/log_scales.svg index 596ba00f9b67..0a29c9d0af21 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/log_scales.svg @@ -27,7 +27,8 @@ z " style="fill:#ffffff;"/> -