Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7bd5d64

Browse filesBrowse files
authored
Merge pull request #9477 from anntzer/clip-log-further
API: In LogTransform, clip after log, not before.
2 parents 73da078 + e3b777f commit 7bd5d64
Copy full SHA for 7bd5d64

File tree

Expand file treeCollapse file tree

11 files changed

+86
-37
lines changed
Filter options
Expand file treeCollapse file tree

11 files changed

+86
-37
lines changed

‎doc/api/api_changes.rst

Copy file name to clipboardExpand all lines: doc/api/api_changes.rst
+15Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_axes.py
-7Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,11 +1555,9 @@ def loglog(self, *args, **kwargs):
15551555

15561556
dx = {'basex': kwargs.pop('basex', 10),
15571557
'subsx': kwargs.pop('subsx', None),
1558-
'nonposx': kwargs.pop('nonposx', 'mask'),
15591558
}
15601559
dy = {'basey': kwargs.pop('basey', 10),
15611560
'subsy': kwargs.pop('subsy', None),
1562-
'nonposy': kwargs.pop('nonposy', 'mask'),
15631561
}
15641562

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

‎lib/matplotlib/axes/_base.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.py
-10Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2971,11 +2971,6 @@ def set_xscale(self, value, **kwargs):
29712971
29722972
matplotlib.scale.LogisticTransform : logit transform
29732973
"""
2974-
# If the scale is being set to log, mask nonposx to prevent headaches
2975-
# around zero
2976-
if value.lower() == 'log' and 'nonposx' not in kwargs:
2977-
kwargs['nonposx'] = 'mask'
2978-
29792974
g = self.get_shared_x_axes()
29802975
for ax in g.get_siblings(self):
29812976
ax.xaxis._set_scale(value, **kwargs)
@@ -3293,11 +3288,6 @@ def set_yscale(self, value, **kwargs):
32933288
32943289
matplotlib.scale.LogisticTransform : logit transform
32953290
"""
3296-
# If the scale is being set to log, mask nonposy to prevent headaches
3297-
# around zero
3298-
if value.lower() == 'log' and 'nonposy' not in kwargs:
3299-
kwargs['nonposy'] = 'mask'
3300-
33013291
g = self.get_shared_y_axes()
33023292
for ax in g.get_siblings(self):
33033293
ax.yaxis._set_scale(value, **kwargs)

‎lib/matplotlib/scale.py

Copy file name to clipboardExpand all lines: lib/matplotlib/scale.py
+31-17Lines 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)
Loading

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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/baseline_images/test_axes/log_scales.svg
+2-1Lines changed: 2 additions & 1 deletion
Loading
Loading
Loading
Loading

‎lib/matplotlib/tests/test_path.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_path.py
+4-2Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_scale.py
+34Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.