Skip to content

Commit 528026a

Browse files
committed
Merge pull request #28 from phobson/new-api-doc
New api doc
2 parents 075c2b0 + 4d382b1 commit 528026a

15 files changed

+261
-70
lines changed

.travis.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ before_install:
3232
- ./miniconda.sh -b -p $HOME/miniconda
3333
- export PATH="$HOME/miniconda/bin:$PATH"
3434
- conda update --yes conda
35+
- conda install --yes nomkl
3536

3637
install:
3738

3839
# We just set up a conda environment with the right Python version. This
3940
# should not need changing.
4041

41-
- conda create --yes -n test python=$TRAVIS_PYTHON_VERSION
42+
- conda create --yes -n test python=$TRAVIS_PYTHON_VERSION numpy matplotlib docopt requests pyyaml
4243
- source activate test
43-
- conda install --yes --channel=conda-forge numpy matplotlib docopt requests pyyaml
4444
- conda install --yes --channel=${TESTERS}
4545
- if [ ${COVERAGE} = true ]; then conda install scipy --yes; fi
4646
- pip install coveralls

docs/api/validate.rst

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.. _validate_auto:
2+
3+
The ``validate`` API
4+
5+
``validate`` API Reference
6+
============================
7+
8+
.. automodule:: probscale.validate
9+
:members:
10+
:undoc-members:
11+
:show-inheritance:

docs/conf.py

+9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
import os
1818
import shlex
1919

20+
import seaborn
21+
clear_bkgd = {'axes.facecolor':'none', 'figure.facecolor':'none'}
22+
seaborn.set(style='ticks', context='talk', color_codes=True, rc=clear_bkgd)
2023

2124
# If extensions (or modules to document with autodoc) are in another directory,
2225
# add these directories to sys.path here. If the directory is relative to the
@@ -60,6 +63,12 @@
6063
# source_suffix = ['.rst', '.md']
6164
source_suffix = '.rst'
6265

66+
# Include the example source for plots in API docs
67+
plot_include_source = True
68+
plot_formats = [("png", 150)]
69+
plot_html_show_formats = False
70+
plot_html_show_source_link = False
71+
6372
# The encoding of source files.
6473
#source_encoding = 'utf-8-sig'
6574

probscale/formatters.py

+24
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ class PctFormatter(_FormatterMixin):
9494
"""
9595
Formatter class for MPL axes to display probalities as percentages.
9696
97+
Examples
98+
--------
99+
>>> from probscale import formatters
100+
>>> fmt = formatters.PctFormatter()
101+
>>> fmt(0.2)
102+
'0.2'
103+
>>> fmt(10)
104+
'10'
105+
>>> fmt(99.999)
106+
'99.999'
107+
97108
"""
98109

99110
factor = 1.0
@@ -105,6 +116,19 @@ class ProbFormatter(_FormatterMixin):
105116
"""
106117
Formatter class for MPL axes to display probalities as decimals.
107118
119+
Examples
120+
--------
121+
>>> from probscale import formatters
122+
>>> fmt = formatters.ProbFormatter()
123+
>>> fmt(0.01)
124+
'0,01'
125+
>>> fmt(0.2)
126+
'0.20'
127+
>>> try:
128+
... fmt(10.5)
129+
... except(ValueError):
130+
... print('formatter out of bounds')
131+
formatter out of bounds
108132
"""
109133

110134
factor = 100.0

probscale/probscale.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ class ProbScale(ScaleBase):
8585
>>> from matplotlib import pyplot
8686
>>> import probscale
8787
>>> fig, ax = pyplot.subplots()
88-
>>> ax.set_xlim(left=0.2, right=99.9)
89-
>>> ax.set_xscale('prob')
88+
>>> ax.set_ylim(bottom=0.2, top=99.9)
89+
>>> ax.set_yscale('prob')
9090
9191
"""
9292

Loading
Loading
Loading
Loading

probscale/tests/test_probscale.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
try:
88
from scipy import stats
9-
except:
9+
except: # pragma: no cover
1010
stats = None
1111

1212
import pytest

probscale/tests/test_transforms.py

+63-51
Original file line numberDiff line numberDiff line change
@@ -22,69 +22,81 @@ def test__clip_out_of_bounds():
2222
assert numpy.all(diff < 0.0001)
2323

2424

25-
class Mixin_Transform(object):
26-
known_input_dims = 1
27-
known_output_dims = 1
28-
known_is_separable = True
29-
known_has_inverse = True
3025

31-
def test_input_dims(self):
32-
assert hasattr(self.trans, 'input_dims')
33-
assert self.trans.input_dims == self.known_input_dims
26+
@pytest.fixture
27+
def prob_trans():
28+
cls = transforms.ProbTransform
29+
return cls(_minimal_norm)
3430

35-
def test_output_dims(self):
36-
assert hasattr(self.trans, 'output_dims')
37-
assert self.trans.output_dims == self.known_output_dims
3831

39-
def test_is_separable(self):
40-
assert hasattr(self.trans, 'is_separable')
41-
assert self.trans.is_separable == self.known_is_separable
32+
@pytest.fixture
33+
def quant_trans():
34+
cls = transforms.QuantileTransform
35+
return cls(_minimal_norm)
4236

43-
def test_has_inverse(self):
44-
assert hasattr(self.trans, 'has_inverse')
45-
assert self.trans.has_inverse == self.known_has_inverse
4637

47-
def test_dist(self):
48-
assert hasattr(self.trans, 'dist')
49-
assert self.trans.dist == _minimal_norm
38+
@pytest.mark.parametrize('trans', [prob_trans(), quant_trans()])
39+
def test_transform_input_dims(trans):
40+
assert trans.input_dims == 1
5041

51-
def test_transform_non_affine(self):
52-
assert hasattr(self.trans, 'transform_non_affine')
53-
diff = numpy.abs(self.trans.transform_non_affine([0.5]) - self.known_tras_na)
54-
assert numpy.all(diff < 0.0001)
5542

56-
def test_inverted(self):
57-
assert hasattr(self.trans, 'inverted')
43+
@pytest.mark.parametrize('trans', [prob_trans(), quant_trans()])
44+
def test_transform_output_dims(trans):
45+
assert trans.output_dims == 1
5846

59-
def test_bad_non_pos(self):
60-
with pytest.raises(ValueError):
61-
self._trans(_minimal_norm, nonpos='junk')
6247

63-
def test_non_pos_clip(self):
64-
self._trans(_minimal_norm, nonpos='clip')
48+
@pytest.mark.parametrize('trans', [prob_trans(), quant_trans()])
49+
def test_transform_is_separable(trans):
50+
assert trans.is_separable
51+
6552

53+
@pytest.mark.parametrize('trans', [prob_trans(), quant_trans()])
54+
def test_transform_has_inverse(trans):
55+
assert trans.has_inverse
6656

67-
class Test_ProbTransform(Mixin_Transform):
68-
def setup(self):
69-
self._trans = transforms.ProbTransform
70-
self.trans = transforms.ProbTransform(_minimal_norm)
71-
self.known_tras_na = [-2.569150498]
7257

73-
def test_inverted(self):
74-
inv_trans = self.trans.inverted()
75-
assert self.trans.dist == inv_trans.dist
76-
assert self.trans.factor == inv_trans.factor
77-
assert self.trans.nonpos == inv_trans.nonpos
58+
@pytest.mark.parametrize('trans', [prob_trans(), quant_trans()])
59+
def test_transform_dist(trans):
60+
trans.dist == _minimal_norm
7861

7962

80-
class Test_QuantileTransform(Mixin_Transform):
81-
def setup(self):
82-
self._trans = transforms.QuantileTransform
83-
self.trans = transforms.QuantileTransform(_minimal_norm)
84-
self.known_tras_na = [69.1464492]
63+
@pytest.mark.parametrize(('trans', 'known_trans_na'), [
64+
(prob_trans(), -2.569150498), (quant_trans(), 69.1464492)
65+
])
66+
def test_transform_non_affine(trans, known_trans_na):
67+
diff = numpy.abs(trans.transform_non_affine([0.5]) - known_trans_na)
68+
assert numpy.all(diff < 0.0001)
8569

86-
def test_inverted(self):
87-
inv_trans = self.trans.inverted()
88-
assert self.trans.dist == inv_trans.dist
89-
assert self.trans.factor == inv_trans.factor
90-
assert self.trans.nonpos == inv_trans.nonpos
70+
71+
@pytest.mark.parametrize(('trans', 'inver_cls'), [
72+
(prob_trans(), transforms.QuantileTransform),
73+
(quant_trans(), transforms.ProbTransform),
74+
])
75+
def test_transform_inverted(trans, inver_cls):
76+
t_inv = trans.inverted()
77+
assert isinstance(t_inv, inver_cls)
78+
assert trans.dist == t_inv.dist
79+
assert trans.as_pct == t_inv.as_pct
80+
assert trans.out_of_bounds == t_inv.out_of_bounds
81+
82+
83+
@pytest.mark.parametrize('cls', [transforms.ProbTransform, transforms.QuantileTransform])
84+
def test_bad_out_of_bounds(cls):
85+
with pytest.raises(ValueError):
86+
cls(_minimal_norm, out_of_bounds='junk')
87+
88+
89+
@pytest.mark.parametrize('cls', [transforms.ProbTransform, transforms.QuantileTransform])
90+
@pytest.mark.parametrize(('method', 'func'), [
91+
('clip', transforms._clip_out_of_bounds),
92+
('mask', transforms._mask_out_of_bounds),
93+
('junk', None),
94+
])
95+
def test_out_of_bounds(cls, method, func):
96+
if func is None:
97+
with pytest.raises(ValueError):
98+
cls(_minimal_norm, out_of_bounds=method)
99+
else:
100+
t = cls(_minimal_norm, out_of_bounds=method)
101+
assert t.out_of_bounds == method
102+
assert t._handle_out_of_bounds == func

probscale/tests/test_viz.py

+21-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy
44
import matplotlib.pyplot as plt
55

6-
if sys.version_info.major == 2:
6+
if sys.version_info.major == 2: # pragma: no cover
77
import mock
88
else:
99
from unittest import mock
@@ -12,7 +12,7 @@
1212

1313
try:
1414
from scipy import stats
15-
except:
15+
except: # pragma: no cover
1616
stats = None
1717

1818
from probscale import viz
@@ -392,6 +392,16 @@ def test_probplot_qq(plot_data):
392392
return fig
393393

394394

395+
@pytest.mark.mpl_image_compare(baseline_dir=BASELINE_DIR, tolerance=10)
396+
@pytest.mark.skipif(stats is None, reason="no scipy")
397+
def test_probplot_qq_dist(plot_data):
398+
fig, ax = plt.subplots()
399+
norm = stats.norm(*stats.norm.fit(plot_data))
400+
fig = viz.probplot(plot_data, ax=ax, plottype='qq', dist=norm,
401+
datalabel='Test label')
402+
return fig
403+
404+
395405
@pytest.mark.mpl_image_compare(baseline_dir=BASELINE_DIR, tolerance=10)
396406
def test_probplot_pp(plot_data):
397407
fig, ax = plt.subplots()
@@ -482,7 +492,7 @@ def test_probplot_pp_bestfit_probax_y(plot_data):
482492
return fig
483493

484494

485-
@pytest.mark.mpl_image_compare(baseline_dir=BASELINE_DIR, tolerance=15)
495+
@pytest.mark.mpl_image_compare(baseline_dir=BASELINE_DIR, tolerance=10)
486496
@pytest.mark.skipif(stats is None, reason="no scipy")
487497
def test_probplot_beta_dist_best_fit_y(plot_data):
488498
fig, (ax1, ax2) = plt.subplots(ncols=2)
@@ -526,8 +536,6 @@ def test_probplot_test_results(plot_data):
526536
return fig
527537

528538

529-
530-
531539
@pytest.mark.parametrize('probax', ['x', 'y'])
532540
@pytest.mark.parametrize(('N', 'minval', 'maxval'), [
533541
(8, 10, 90),
@@ -544,3 +552,11 @@ def test__set_prob_limits_x(probax, N, minval, maxval):
544552
ax.set_xlim.assert_called_once_with(left=minval, right=maxval)
545553
elif probax == 'y':
546554
ax.set_ylim.assert_called_once_with(bottom=minval, top=maxval)
555+
556+
557+
@pytest.mark.mpl_image_compare(baseline_dir=BASELINE_DIR, tolerance=10)
558+
def test_probplot_color_and_label(plot_data):
559+
fig, ax = plt.subplots()
560+
fig = viz.probplot(plot_data, ax=ax, color='pink', label='A Top-Level Label')
561+
ax.legend(loc='lower right')
562+
return fig

probscale/transforms.py

+35-7
Original file line numberDiff line numberDiff line change
@@ -39,29 +39,43 @@ class _ProbTransformMixin(Transform):
3939
is_separable = True
4040
has_inverse = True
4141

42-
def __init__(self, dist, as_pct=True, nonpos='mask'):
42+
def __init__(self, dist, as_pct=True, out_of_bounds='mask'):
4343
Transform.__init__(self)
4444
self.dist = dist
4545
self.as_pct = as_pct
46-
self.nonpos = nonpos
46+
self.out_of_bounds = out_of_bounds
4747
if self.as_pct:
4848
self.factor = 100.0
4949
else:
5050
self.factor = 1.0
5151

52-
if self.nonpos == 'mask':
52+
if self.out_of_bounds == 'mask':
5353
self._handle_out_of_bounds = _mask_out_of_bounds
54-
elif self.nonpos == 'clip':
54+
elif self.out_of_bounds == 'clip':
5555
self._handle_out_of_bounds = _clip_out_of_bounds
5656
else:
57-
raise ValueError("`nonpos` muse be either 'mask' or 'clip'")
57+
raise ValueError("`out_of_bounds` muse be either 'mask' or 'clip'")
5858

5959

6060
class ProbTransform(_ProbTransformMixin):
6161
"""
6262
MPL axes tranform class to convert quantiles to probabilities
6363
or percents.
6464
65+
Parameters
66+
----------
67+
dist : scipy.stats distribution
68+
The distribution whose ``cdf`` and ``pdf`` methods wiil set the
69+
scale of the axis.
70+
as_pct : bool, optional (True)
71+
Toggles the formatting of the probabilities associated with the
72+
tick labels as percentanges (0 - 100) or fractions (0 - 1).
73+
out_of_bounds : string, optionals ('mask' or 'clip')
74+
Determines how data outside the range of valid values is
75+
handled. The default behavior is to mask the data.
76+
Alternatively, the data can be clipped to values arbitrarily
77+
close to the limits of the scale.
78+
6579
"""
6680

6781
def transform_non_affine(self, prob):
@@ -70,19 +84,33 @@ def transform_non_affine(self, prob):
7084
return q
7185

7286
def inverted(self):
73-
return QuantileTransform(self.dist, as_pct=self.as_pct, nonpos=self.nonpos)
87+
return QuantileTransform(self.dist, as_pct=self.as_pct, out_of_bounds=self.out_of_bounds)
7488

7589

7690
class QuantileTransform(_ProbTransformMixin):
7791
"""
7892
MPL axes tranform class to convert probabilities or percents to
7993
quantiles.
8094
95+
Parameters
96+
----------
97+
dist : scipy.stats distribution
98+
The distribution whose ``cdf`` and ``pdf`` methods wiil set the
99+
scale of the axis.
100+
as_pct : bool, optional (True)
101+
Toggles the formatting of the probabilities associated with the
102+
tick labels as percentanges (0 - 100) or fractions (0 - 1).
103+
out_of_bounds : string, optionals ('mask' or 'clip')
104+
Determines how data outside the range of valid values is
105+
handled. The default behavior is to mask the data.
106+
Alternatively, the data can be clipped to values arbitrarily
107+
close to the limits of the scale.
108+
81109
"""
82110

83111
def transform_non_affine(self, q):
84112
prob = self.dist.cdf(q) * self.factor
85113
return prob
86114

87115
def inverted(self):
88-
return ProbTransform(self.dist, as_pct=self.as_pct, nonpos=self.nonpos)
116+
return ProbTransform(self.dist, as_pct=self.as_pct, out_of_bounds=self.out_of_bounds)

0 commit comments

Comments
 (0)