Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 187dd92

Browse files
authored
Merge pull request #9576 from matplotlib/auto-backport-of-pr-9477
Backport PR #9477 on branch v2.1.x
2 parents d5f0b56 + 668ff58 commit 187dd92

File tree

11 files changed

+86
-37
lines changed

11 files changed

+86
-37
lines changed

doc/api/api_changes.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,24 @@ out what caused the breakage and how to fix it by updating your code.
1010
For new features that were added to Matplotlib, please see
1111
:ref:`whats-new`.
1212

13+
API Changes in 2.1.1
14+
====================
15+
16+
Default behavior of log scales reverted to clip <= 0 values
17+
-----------------------------------------------------------
18+
19+
The change it 2.1.0 to mask in logscale by default had more disruptive
20+
changes than anticipated and has been reverted, however the clipping is now
21+
done in a way that fixes the issues that motivated changing the default behavior
22+
to ``'mask'``.
23+
24+
As a side effect of this change, error bars which go negative now work as expected
25+
on log scales.
26+
1327
API Changes in 2.1.0
1428
====================
1529

30+
1631
Default behavior of log scales changed to mask <= 0 values
1732
----------------------------------------------------------
1833

lib/matplotlib/axes/_axes.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,11 +1556,9 @@ def loglog(self, *args, **kwargs):
15561556

15571557
dx = {'basex': kwargs.pop('basex', 10),
15581558
'subsx': kwargs.pop('subsx', None),
1559-
'nonposx': kwargs.pop('nonposx', 'mask'),
15601559
}
15611560
dy = {'basey': kwargs.pop('basey', 10),
15621561
'subsy': kwargs.pop('subsy', None),
1563-
'nonposy': kwargs.pop('nonposy', 'mask'),
15641562
}
15651563

15661564
self.set_xscale('log', **dx)
@@ -2851,11 +2849,6 @@ def errorbar(self, x, y, yerr=None, xerr=None,
28512849
Valid kwargs for the marker properties are
28522850
28532851
%(Line2D)s
2854-
2855-
Notes
2856-
-----
2857-
Error bars with negative values will not be shown when plotted on a
2858-
logarithmic axis.
28592852
"""
28602853
kwargs = cbook.normalize_kwargs(kwargs, _alias_map)
28612854
# anything that comes in as 'None', drop so the default thing

lib/matplotlib/axes/_base.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2970,11 +2970,6 @@ def set_xscale(self, value, **kwargs):
29702970
29712971
matplotlib.scale.LogisticTransform : logit transform
29722972
"""
2973-
# If the scale is being set to log, mask nonposx to prevent headaches
2974-
# around zero
2975-
if value.lower() == 'log' and 'nonposx' not in kwargs:
2976-
kwargs['nonposx'] = 'mask'
2977-
29782973
g = self.get_shared_x_axes()
29792974
for ax in g.get_siblings(self):
29802975
ax.xaxis._set_scale(value, **kwargs)
@@ -3292,11 +3287,6 @@ def set_yscale(self, value, **kwargs):
32923287
32933288
matplotlib.scale.LogisticTransform : logit transform
32943289
"""
3295-
# If the scale is being set to log, mask nonposy to prevent headaches
3296-
# around zero
3297-
if value.lower() == 'log' and 'nonposy' not in kwargs:
3298-
kwargs['nonposy'] = 'mask'
3299-
33003290
g = self.get_shared_y_axes()
33013291
for ax in g.get_siblings(self):
33023292
ax.yaxis._set_scale(value, **kwargs)

lib/matplotlib/scale.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,24 @@ class LogTransformBase(Transform):
9292

9393
def __init__(self, nonpos):
9494
Transform.__init__(self)
95-
if nonpos == 'mask':
96-
self._fill_value = np.nan
97-
else:
98-
self._fill_value = 1e-300
95+
self._clip = {"clip": True, "mask": False}[nonpos]
9996

10097
def transform_non_affine(self, a):
101-
with np.errstate(invalid="ignore"):
102-
a = np.where(a <= 0, self._fill_value, a)
103-
return np.divide(np.log(a, out=a), np.log(self.base), out=a)
98+
with np.errstate(divide="ignore", invalid="ignore"):
99+
out = np.log(a)
100+
out /= np.log(self.base)
101+
if self._clip:
102+
# SVG spec says that conforming viewers must support values up
103+
# to 3.4e38 (C float); however experiments suggest that Inkscape
104+
# (which uses cairo for rendering) runs into cairo's 24-bit limit
105+
# (which is apparently shared by Agg).
106+
# Ghostscript (used for pdf rendering appears to overflow even
107+
# earlier, with the max value around 2 ** 15 for the tests to pass.
108+
# On the other hand, in practice, we want to clip beyond
109+
# np.log10(np.nextafter(0, 1)) ~ -323
110+
# so 1000 seems safe.
111+
out[a <= 0] = -1000
112+
return out
104113

105114

106115
class InvertedLogTransformBase(Transform):
@@ -220,11 +229,17 @@ def __init__(self, axis, **kwargs):
220229
if axis.axis_name == 'x':
221230
base = kwargs.pop('basex', 10.0)
222231
subs = kwargs.pop('subsx', None)
223-
nonpos = kwargs.pop('nonposx', 'mask')
232+
nonpos = kwargs.pop('nonposx', 'clip')
224233
else:
225234
base = kwargs.pop('basey', 10.0)
226235
subs = kwargs.pop('subsy', None)
227-
nonpos = kwargs.pop('nonposy', 'mask')
236+
nonpos = kwargs.pop('nonposy', 'clip')
237+
238+
if len(kwargs):
239+
raise ValueError(("provided too many kwargs, can only pass "
240+
"{'basex', 'subsx', nonposx'} or "
241+
"{'basey', 'subsy', nonposy'}. You passed ") +
242+
"{!r}".format(kwargs))
228243

229244
if nonpos not in ['mask', 'clip']:
230245
raise ValueError("nonposx, nonposy kwarg must be 'mask' or 'clip'")
@@ -432,18 +447,17 @@ class LogitTransform(Transform):
432447

433448
def __init__(self, nonpos):
434449
Transform.__init__(self)
435-
if nonpos == 'mask':
436-
self._fill_value = np.nan
437-
else:
438-
self._fill_value = 1e-300
439450
self._nonpos = nonpos
451+
self._clip = {"clip": True, "mask": False}[nonpos]
440452

441453
def transform_non_affine(self, a):
442454
"""logit transform (base 10), masked or clipped"""
443-
with np.errstate(invalid="ignore"):
444-
a = np.select(
445-
[a <= 0, a >= 1], [self._fill_value, 1 - self._fill_value], a)
446-
return np.log10(a / (1 - a))
455+
with np.errstate(divide="ignore", invalid="ignore"):
456+
out = np.log10(a / (1 - a))
457+
if self._clip: # See LogTransform for choice of clip value.
458+
out[a <= 0] = -1000
459+
out[1 <= a] = 1000
460+
return out
447461

448462
def inverted(self):
449463
return LogisticTransform(self._nonpos)

lib/matplotlib/tests/baseline_images/test_axes/log_scales.svg

Lines changed: 2 additions & 1 deletion
Loading

lib/matplotlib/tests/test_path.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,15 @@ def test_path_clipping():
8787
xy, facecolor='none', edgecolor='red', closed=True))
8888

8989

90-
@image_comparison(baseline_images=['semi_log_with_zero'], extensions=['png'])
90+
@image_comparison(baseline_images=['semi_log_with_zero'], extensions=['png'],
91+
style='mpl20')
9192
def test_log_transform_with_zero():
9293
x = np.arange(-10, 10)
9394
y = (1.0 - 1.0/(x**2+1))**20
9495

9596
fig, ax = plt.subplots()
96-
ax.semilogy(x, y, "-o", lw=15)
97+
ax.semilogy(x, y, "-o", lw=15, markeredgecolor='k')
98+
ax.set_ylim(1e-7, 1)
9799
ax.grid(True)
98100

99101

lib/matplotlib/tests/test_scale.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import matplotlib.pyplot as plt
55
import numpy as np
66
import io
7+
import pytest
78

89

910
@image_comparison(baseline_images=['log_scales'], remove_text=True)
@@ -65,3 +66,36 @@ def test_logscale_mask():
6566
ax.plot(np.exp(-xs**2))
6667
fig.canvas.draw()
6768
ax.set(yscale="log")
69+
70+
71+
def test_extra_kwargs_raise():
72+
fig, ax = plt.subplots()
73+
with pytest.raises(ValueError):
74+
ax.set_yscale('log', nonpos='mask')
75+
76+
77+
@image_comparison(baseline_images=['logscale_nonpos_values'], remove_text=True,
78+
extensions=['png'], style='mpl20')
79+
def test_logscale_nonpos_values():
80+
np.random.seed(19680801)
81+
xs = np.random.normal(size=int(1e3))
82+
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
83+
ax1.hist(xs, range=(-5, 5), bins=10)
84+
ax1.set_yscale('log')
85+
ax2.hist(xs, range=(-5, 5), bins=10)
86+
ax2.set_yscale('log', nonposy='mask')
87+
88+
xdata = np.arange(0, 10, 0.01)
89+
ydata = np.exp(-xdata)
90+
edata = 0.2*(10-xdata)*np.cos(5*xdata)*np.exp(-xdata)
91+
92+
ax3.fill_between(xdata, ydata - edata, ydata + edata)
93+
ax3.set_yscale('log')
94+
95+
x = np.logspace(-1, 1)
96+
y = x ** 3
97+
yerr = x**2
98+
ax4.errorbar(x, y, yerr=yerr)
99+
100+
ax4.set_yscale('log')
101+
ax4.set_xscale('log')

0 commit comments

Comments
 (0)