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

Skip to content

Commit a37f8e7

Browse files
committed
Add log scale option to clip non-positive values instead of masking
svn path=/trunk/matplotlib/; revision=7023
1 parent 382b7ec commit a37f8e7

5 files changed

Lines changed: 82 additions & 24 deletions

File tree

CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2009-04-04 Allow log axis scale to clip non-positive values to
2+
small positive value; this is useful for errorbars. - EF
3+
14
2009-03-28 Make images handle nan in their array argument.
25
A helper, cbook.safe_masked_invalid() was added. - EF
36

doc/api/api_changes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ list may help describe what changes may be necessary in your code.
1919

2020
Changes for 0.98.x
2121
==================
22+
* Added new keyword parameters *nonposx*, *nonposy* to
23+
:class:`matplotlib.axes.Axes` methods that set log scale
24+
parameters. The default is still to mask out non-positive
25+
values, but the kwargs accept 'clip', which causes non-positive
26+
values to be replaced with a very small positive value.
27+
2228
* Added new :func:`matplotlib.pyplot.fignum_exists` and
2329
:func:`matplotlib.pyplot.get_fignums`; they merely expose
2430
information that had been hidden in :mod:`matplotlib._pylab_helpers`.

examples/pylab_examples/log_demo.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,33 @@
66
t = np.arange(0.01, 20.0, 0.01)
77

88
# log y axis
9-
plt.subplot(311)
9+
plt.subplot(221)
1010
plt.semilogy(t, np.exp(-t/5.0))
11-
plt.ylabel('semilogy')
11+
plt.title('semilogy')
1212
plt.grid(True)
1313

1414
# log x axis
15-
plt.subplot(312)
15+
plt.subplot(222)
1616
plt.semilogx(t, np.sin(2*np.pi*t))
17-
plt.ylabel('semilogx')
17+
plt.title('semilogx')
1818
plt.grid(True)
1919

2020
# log x and y axis
21-
plt.subplot(313)
21+
plt.subplot(223)
2222
plt.loglog(t, 20*np.exp(-t/10.0), basex=4)
2323
plt.grid(True)
24-
plt.ylabel('loglog base 4 on x')
24+
plt.title('loglog base 4 on x')
25+
26+
# with errorbars: clip non-positive values
27+
ax = plt.subplot(224)
28+
ax.set_xscale("log", nonposx='clip')
29+
ax.set_yscale("log", nonposy='clip')
30+
31+
x = 10.0**np.linspace(0.0, 2.0, 20)
32+
y = x**2.0
33+
plt.errorbar(x, y, xerr=0.1*x, yerr=5.0+0.75*y)
34+
ax.set_ylim(ymin=0.1)
35+
ax.set_title('Errorbars go negative')
36+
2537

2638
plt.show()

lib/matplotlib/axes.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3455,6 +3455,10 @@ def loglog(self, *args, **kwargs):
34553455
plot; see :meth:`matplotlib.axes.Axes.set_xscale` /
34563456
:meth:`matplotlib.axes.Axes.set_yscale` for details
34573457
3458+
*nonposx*/*nonposy*: ['mask' | 'clip' ]
3459+
non-positive values in *x* or *y* can be masked as
3460+
invalid, or clipped to a very small positive number
3461+
34583462
The remaining valid kwargs are
34593463
:class:`~matplotlib.lines.Line2D` properties:
34603464
@@ -3469,9 +3473,11 @@ def loglog(self, *args, **kwargs):
34693473

34703474
dx = {'basex': kwargs.pop('basex', 10),
34713475
'subsx': kwargs.pop('subsx', None),
3476+
'nonposx': kwargs.pop('nonposx', 'mask'),
34723477
}
34733478
dy = {'basey': kwargs.pop('basey', 10),
34743479
'subsy': kwargs.pop('subsy', None),
3480+
'nonposy': kwargs.pop('nonposy', 'mask'),
34753481
}
34763482

34773483
self.set_xscale('log', **dx)
@@ -3508,6 +3514,10 @@ def semilogx(self, *args, **kwargs):
35083514
plot; see :meth:`~matplotlib.axes.Axes.set_xscale` for
35093515
details.
35103516
3517+
*nonposx*: ['mask' | 'clip' ]
3518+
non-positive values in *x* can be masked as
3519+
invalid, or clipped to a very small positive number
3520+
35113521
The remaining valid kwargs are
35123522
:class:`~matplotlib.lines.Line2D` properties:
35133523
@@ -3521,6 +3531,7 @@ def semilogx(self, *args, **kwargs):
35213531
if not self._hold: self.cla()
35223532
d = {'basex': kwargs.pop( 'basex', 10),
35233533
'subsx': kwargs.pop( 'subsx', None),
3534+
'nonposx': kwargs.pop('nonposx', 'mask'),
35243535
}
35253536

35263537
self.set_xscale('log', **d)
@@ -3554,6 +3565,10 @@ def semilogy(self, *args, **kwargs):
35543565
plot; see :meth:`~matplotlib.axes.Axes.set_yscale` for
35553566
details.
35563567
3568+
*nonposy*: ['mask' | 'clip' ]
3569+
non-positive values in *y* can be masked as
3570+
invalid, or clipped to a very small positive number
3571+
35573572
The remaining valid kwargs are
35583573
:class:`~matplotlib.lines.Line2D` properties:
35593574
@@ -3567,6 +3582,7 @@ def semilogy(self, *args, **kwargs):
35673582
if not self._hold: self.cla()
35683583
d = {'basey': kwargs.pop('basey', 10),
35693584
'subsy': kwargs.pop('subsy', None),
3585+
'nonposy': kwargs.pop('nonposy', 'mask'),
35703586
}
35713587
self.set_yscale('log', **d)
35723588
b = self._hold

lib/matplotlib/scale.py

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ def _mask_non_positives(a):
8787
return ma.MaskedArray(a, mask=mask)
8888
return a
8989

90+
def _clip_non_positives(a):
91+
a[a <= 0.0] = 1e-300
92+
return a
93+
9094
class LogScale(ScaleBase):
9195
"""
9296
A standard logarithmic scale. Care is taken so non-positive
@@ -104,14 +108,24 @@ class LogScale(ScaleBase):
104108

105109
name = 'log'
106110

107-
class Log10Transform(Transform):
111+
class LogTransformBase(Transform):
108112
input_dims = 1
109113
output_dims = 1
110114
is_separable = True
115+
116+
def __init__(self, nonpos):
117+
Transform.__init__(self)
118+
if nonpos == 'mask':
119+
self._handle_nonpos = _mask_non_positives
120+
else:
121+
self._handle_nonpos = _clip_non_positives
122+
123+
124+
class Log10Transform(LogTransformBase):
111125
base = 10.0
112126

113127
def transform(self, a):
114-
a = _mask_non_positives(a * 10.0)
128+
a = self._handle_nonpos(a * 10.0)
115129
if isinstance(a, MaskedArray):
116130
return ma.log10(a)
117131
return np.log10(a)
@@ -131,14 +145,11 @@ def transform(self, a):
131145
def inverted(self):
132146
return LogScale.Log10Transform()
133147

134-
class Log2Transform(Transform):
135-
input_dims = 1
136-
output_dims = 1
137-
is_separable = True
148+
class Log2Transform(LogTransformBase):
138149
base = 2.0
139150

140151
def transform(self, a):
141-
a = _mask_non_positives(a * 2.0)
152+
a = self._handle_nonpos(a * 2.0)
142153
if isinstance(a, MaskedArray):
143154
return ma.log(a) / np.log(2)
144155
return np.log2(a)
@@ -158,14 +169,11 @@ def transform(self, a):
158169
def inverted(self):
159170
return LogScale.Log2Transform()
160171

161-
class NaturalLogTransform(Transform):
162-
input_dims = 1
163-
output_dims = 1
164-
is_separable = True
172+
class NaturalLogTransform(LogTransformBase):
165173
base = np.e
166174

167175
def transform(self, a):
168-
a = _mask_non_positives(a * np.e)
176+
a = self._handle_nonpos(a * np.e)
169177
if isinstance(a, MaskedArray):
170178
return ma.log(a)
171179
return np.log(a)
@@ -190,12 +198,16 @@ class LogTransform(Transform):
190198
output_dims = 1
191199
is_separable = True
192200

193-
def __init__(self, base):
201+
def __init__(self, base, nonpos):
194202
Transform.__init__(self)
195203
self.base = base
204+
if nonpos == 'mask':
205+
self._handle_nonpos = _mask_non_positives
206+
else:
207+
self._handle_nonpos = _clip_non_positives
196208

197209
def transform(self, a):
198-
a = _mask_non_positives(a * self.base)
210+
a = self._handle_nonpos(a * self.base)
199211
if isinstance(a, MaskedArray):
200212
return ma.log(a) / np.log(self.base)
201213
return np.log(a) / np.log(self.base)
@@ -224,6 +236,10 @@ def __init__(self, axis, **kwargs):
224236
*basex*/*basey*:
225237
The base of the logarithm
226238
239+
*nonposx*/*nonposy*: ['mask' | 'clip' ]
240+
non-positive values in *x* or *y* can be masked as
241+
invalid, or clipped to a very small positive number
242+
227243
*subsx*/*subsy*:
228244
Where to place the subticks between each major tick.
229245
Should be a sequence of integers. For example, in a log10
@@ -235,18 +251,23 @@ def __init__(self, axis, **kwargs):
235251
if axis.axis_name == 'x':
236252
base = kwargs.pop('basex', 10.0)
237253
subs = kwargs.pop('subsx', None)
254+
nonpos = kwargs.pop('nonposx', 'mask')
238255
else:
239256
base = kwargs.pop('basey', 10.0)
240257
subs = kwargs.pop('subsy', None)
258+
nonpos = kwargs.pop('nonposy', 'mask')
259+
260+
if nonpos not in ['mask', 'clip']:
261+
raise ValueError("nonposx, nonposy kwarg must be 'mask' or 'clip'")
241262

242263
if base == 10.0:
243-
self._transform = self.Log10Transform()
264+
self._transform = self.Log10Transform(nonpos)
244265
elif base == 2.0:
245-
self._transform = self.Log2Transform()
266+
self._transform = self.Log2Transform(nonpos)
246267
elif base == np.e:
247-
self._transform = self.NaturalLogTransform()
268+
self._transform = self.NaturalLogTransform(nonpos)
248269
else:
249-
self._transform = self.LogTransform(base)
270+
self._transform = self.LogTransform(base, nonpos)
250271

251272
self.base = base
252273
self.subs = subs

0 commit comments

Comments
 (0)