7
7
NullLocator ,
8
8
Formatter ,
9
9
NullFormatter ,
10
- FuncFormatter
10
+ FuncFormatter ,
11
11
)
12
12
13
13
14
+ def _mask_non_prop (a ):
15
+ """
16
+ Return a Numpy array where all values outside ]0, 1[ are
17
+ replaced with NaNs. If all values are inside ]0, 1[, the original
18
+ array is returned.
19
+ """
20
+ mask = (a <= 0.0 ) | (a >= 1.0 )
21
+ if mask .any ():
22
+ return np .where (mask , np .nan , a )
23
+ return a
24
+
25
+
26
+ def _clip_non_positives (a ):
27
+ a = np .array (a , float )
28
+ a [a <= 0.0 ] = 1e-300
29
+ a [a >= 1.0 ] = 1 - 1e-300
30
+ return a
31
+
32
+
14
33
class _minimal_norm (object ):
15
34
_A = - (8 * (np .pi - 3.0 ) / (3.0 * np .pi * (np .pi - 4.0 )))
16
35
@@ -44,7 +63,6 @@ def ppf(cls, q):
44
63
Wikipedia: https://goo.gl/Rtxjme
45
64
46
65
"""
47
-
48
66
return np .sqrt (2 ) * cls ._approx_inv_erf (2 * q - 1 )
49
67
50
68
@classmethod
@@ -57,7 +75,7 @@ def cdf(cls, x):
57
75
return 0.5 * (1 + cls ._approx_erf (x / np .sqrt (2 )))
58
76
59
77
60
- class ProbFormatter (Formatter ):
78
+ class _FormatterMixin (Formatter ):
61
79
@classmethod
62
80
def _sig_figs (cls , x , n , expthresh = 5 , forceint = False ):
63
81
""" Formats a number with the correct number of sig figs.
@@ -128,49 +146,67 @@ def _sig_figs(cls, x, n, expthresh=5, forceint=False):
128
146
return out
129
147
130
148
def __call__ (self , x , pos = None ):
131
- if x < 10 :
149
+ if x < ( 10 / self . factor ) :
132
150
out = self ._sig_figs (x , 1 )
133
- elif x <= 99 :
151
+ elif x <= ( 99 / self . factor ) :
134
152
out = self ._sig_figs (x , 2 )
135
153
else :
136
- order = np .ceil (np .round (np .abs (np .log10 (100 - x )), 6 ))
137
- out = self ._sig_figs (x , order + 2 )
154
+ order = np .ceil (np .round (np .abs (np .log10 (self . top - x )), 6 ))
155
+ out = self ._sig_figs (x , order + self . offset )
138
156
139
157
return '{}' .format (out )
140
158
141
159
142
- class ProbTransform (Transform ):
160
+ class PctFormatter (_FormatterMixin ):
161
+ factor = 1.0
162
+ offset = 2
163
+ top = 100
164
+
165
+
166
+ class ProbFormatter (_FormatterMixin ):
167
+ factor = 100.0
168
+ offset = 0
169
+ top = 1
170
+
171
+
172
+ class _ProbTransformMixin (Transform ):
143
173
input_dims = 1
144
174
output_dims = 1
145
175
is_separable = True
146
176
has_inverse = True
147
177
148
- def __init__ (self , dist ):
178
+ def __init__ (self , dist , as_pct = True , nonpos = 'mask' ):
149
179
Transform .__init__ (self )
150
180
self .dist = dist
181
+ if as_pct :
182
+ self .factor = 100.0
183
+ else :
184
+ self .factor = 1.0
151
185
152
- def transform_non_affine (self , a ):
153
- return self .dist .ppf (a / 100. )
186
+ if nonpos == 'mask' :
187
+ self ._handle_nonpos = _mask_non_positives
188
+ elif nonpos == 'clip' :
189
+ self ._handle_nonpos = _clip_non_positives
190
+ else :
191
+ raise ValueError ("`nonpos` muse be either 'mask' or 'clip'" )
154
192
155
- def inverted (self ):
156
- return InvertedProbTransform (self .dist )
157
193
194
+ class ProbTransform (_ProbTransformMixin ):
195
+ def transform_non_affine (self , prob ):
196
+ q = self .dist .ppf (prob / self .factor )
197
+ return q
158
198
159
- class InvertedProbTransform (Transform ):
160
- input_dims = 1
161
- output_dims = 1
162
- is_separable = True
163
- has_inverse = True
199
+ def inverted (self ):
200
+ return QuantileTransform (self .dist , as_pct = self .as_pct , nonpos = self .nonpos )
164
201
165
- def __init__ (self , dist ):
166
- self .dist = dist
167
- Transform .__init__ (self )
168
202
169
- def transform_non_affine (self , a ):
170
- return self .dist .cdf (a ) * 100.
203
+ class QuantileTransform (_ProbTransformMixin ):
204
+ def transform_non_affine (self , q ):
205
+ prob = self .dist .cdf (q ) * self .factor
206
+ return prob
171
207
172
208
def inverted (self ):
173
- return ProbTransform (self .dist )
209
+ return ProbTransform (self .dist , as_pct = self . as_pct , nonpos = self . nonpos )
174
210
175
211
176
212
class ProbScale (ScaleBase ):
@@ -199,13 +235,19 @@ class ProbScale(ScaleBase):
199
235
200
236
def __init__ (self , axis , ** kwargs ):
201
237
self .dist = kwargs .pop ('dist' , _minimal_norm )
202
- self ._transform = ProbTransform (self .dist )
238
+ self .as_pct = kwargs .pop ('as_pct' , True )
239
+ self .nonpos = kwargs .pop ('nonpos' , 'mask' )
240
+ self ._transform = ProbTransform (self .dist , as_pct = self .as_pct )
203
241
204
242
@classmethod
205
- def _get_probs (cls , nobs ):
243
+ def _get_probs (cls , nobs , as_pct ):
206
244
""" Returns the x-axis labels for a probability plot based on
207
245
the number of observations (`nobs`).
208
246
"""
247
+ if as_pct :
248
+ factor = 1.0
249
+ else :
250
+ factor = 100.0
209
251
210
252
order = int (np .floor (np .log10 (nobs )))
211
253
base_probs = np .array ([10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 ])
@@ -219,19 +261,23 @@ def _get_probs(cls, nobs):
219
261
lower_fringe = np .array ([1 ])
220
262
upper_fringe = np .array ([9 ])
221
263
222
- new_lower = lower_fringe / 10 ** (n )
223
- new_upper = upper_fringe / 10 ** (n ) + axis_probs .max ()
264
+ new_lower = lower_fringe / 10 ** (n )
265
+ new_upper = upper_fringe / 10 ** (n ) + axis_probs .max ()
224
266
axis_probs = np .hstack ([new_lower , axis_probs , new_upper ])
225
-
226
- return axis_probs
267
+ locs = axis_probs / factor
268
+ return locs
227
269
228
270
def set_default_locators_and_formatters (self , axis ):
229
271
"""
230
272
Set the locators and formatters to specialized versions for
231
273
log scaling.
232
274
"""
233
- axis .set_major_locator (FixedLocator (self ._get_probs (1e10 )))
234
- axis .set_major_formatter (FuncFormatter (ProbFormatter ()))
275
+
276
+ axis .set_major_locator (FixedLocator (self ._get_probs (1e8 , self .as_pct )))
277
+ if self .as_pct :
278
+ axis .set_major_formatter (FuncFormatter (PctFormatter ()))
279
+ else :
280
+ axis .set_major_formatter (FuncFormatter (ProbFormatter ()))
235
281
axis .set_minor_locator (NullLocator ())
236
282
axis .set_minor_formatter (NullFormatter ())
237
283
0 commit comments