import numpy
from matplotlib.scale import ScaleBase
from matplotlib.ticker import (
FixedLocator,
NullLocator,
NullFormatter,
FuncFormatter,
)
from .transforms import ProbTransform
from .formatters import PctFormatter, ProbFormatter
class _minimal_norm(object):
"""
A basic implmentation of a normal distribution, minimally
API-complient with scipt.stats.norm
"""
_A = -(8 * (numpy.pi - 3.0) / (3.0 * numpy.pi * (numpy.pi - 4.0)))
@classmethod
def _approx_erf(cls, x):
""" Approximate solution to the error function
http://en.wikipedia.org/wiki/Error_function
"""
guts = -x**2 * (4.0 / numpy.pi + cls._A * x**2) / (1.0 + cls._A * x**2)
return numpy.sign(x) * numpy.sqrt(1.0 - numpy.exp(guts))
@classmethod
def _approx_inv_erf(cls, z):
""" Approximate solution to the inverse error function
http://en.wikipedia.org/wiki/Error_function
"""
_b = (2 / numpy.pi / cls._A) + (0.5 * numpy.log(1 - z**2))
_c = numpy.log(1 - z**2) / cls._A
return numpy.sign(z) * numpy.sqrt(numpy.sqrt(_b**2 - _c) - _b)
@classmethod
def ppf(cls, q):
""" Percent point function (inverse of cdf)
Wikipedia: https://goo.gl/Rtxjme
"""
return numpy.sqrt(2) * cls._approx_inv_erf(2*q - 1)
@classmethod
def cdf(cls, x):
""" Cumulative density function
Wikipedia: https://goo.gl/ciUNLx
"""
return 0.5 * (1 + cls._approx_erf(x/numpy.sqrt(2)))
[docs]class ProbScale(ScaleBase):
""" A probability scale for matplotlib Axes.
Parameters
----------
axis : a matplotlib axis artist
The axis whose scale will be set.
dist : scipy.stats probability distribution, optional
The distribution whose ppf/cdf methods should be used to compute
the tick positions. By default, a minimal implimentation of the
``scipy.stats.norm`` class is used so that scipy is not a
requirement.
Examples
--------
The most basic use:
.. plot::
:context: close-figs
>>> from matplotlib import pyplot
>>> import probscale
>>> fig, ax = pyplot.subplots(figsize=(4, 7))
>>> ax.set_ylim(bottom=0.5, top=99.5)
>>> ax.set_yscale('prob')
"""
name = 'prob'
def __init__(self, axis, **kwargs):
self.dist = kwargs.pop('dist', _minimal_norm)
self.as_pct = kwargs.pop('as_pct', True)
self.nonpos = kwargs.pop('nonpos', 'mask')
self._transform = ProbTransform(self.dist, as_pct=self.as_pct)
@classmethod
def _get_probs(cls, nobs, as_pct):
""" Returns the x-axis labels for a probability plot based on
the number of observations (`nobs`).
"""
if as_pct:
factor = 1.0
else:
factor = 100.0
order = int(numpy.floor(numpy.log10(nobs)))
base_probs = numpy.array([10, 20, 30, 40, 50, 60, 70, 80, 90])
axis_probs = base_probs.copy()
for n in range(order):
if n <= 2:
lower_fringe = numpy.array([1, 2, 5])
upper_fringe = numpy.array([5, 8, 9])
else:
lower_fringe = numpy.array([1])
upper_fringe = numpy.array([9])
new_lower = lower_fringe / 10**(n)
new_upper = upper_fringe / 10**(n) + axis_probs.max()
axis_probs = numpy.hstack([new_lower, axis_probs, new_upper])
locs = axis_probs / factor
return locs
[docs] def limit_range_for_scale(self, vmin, vmax, minpos):
"""
Limit the domain to positive values.
"""
return (vmin <= 0.0 and minpos or vmin, vmax <= 0.0 and minpos or vmax)