1197 lines
39 KiB
Python
1197 lines
39 KiB
Python
'''Various extensions to distributions
|
|
|
|
* skew normal and skew t distribution by Azzalini, A. & Capitanio, A.
|
|
* Gram-Charlier expansion distribution (using 4 moments),
|
|
* distributions based on non-linear transformation
|
|
- Transf_gen
|
|
- ExpTransf_gen, LogTransf_gen
|
|
- TransfTwo_gen
|
|
(defines as examples: square, negative square and abs transformations)
|
|
- this versions are without __new__
|
|
* mnvormcdf, mvstdnormcdf : cdf, rectangular integral for multivariate normal
|
|
distribution
|
|
|
|
TODO:
|
|
* Where is Transf_gen for general monotonic transformation ? found and added it
|
|
* write some docstrings, some parts I do not remember
|
|
* add Box-Cox transformation, parametrized ?
|
|
|
|
|
|
this is only partially cleaned, still includes test examples as functions
|
|
|
|
main changes
|
|
* add transf_gen (2010-05-09)
|
|
* added separate example and tests (2010-05-09)
|
|
* collect transformation function into classes
|
|
|
|
Example
|
|
-------
|
|
|
|
>>> logtg = Transf_gen(stats.t, np.exp, np.log,
|
|
numargs = 1, a=0, name = 'lnnorm',
|
|
longname = 'Exp transformed normal',
|
|
extradoc = '\ndistribution of y = exp(x), with x standard normal'
|
|
'precision for moment andstats is not very high, 2-3 decimals')
|
|
>>> logtg.cdf(5, 6)
|
|
0.92067704211191848
|
|
>>> stats.t.cdf(np.log(5), 6)
|
|
0.92067704211191848
|
|
|
|
>>> logtg.pdf(5, 6)
|
|
0.021798547904239293
|
|
>>> stats.t.pdf(np.log(5), 6)
|
|
0.10899273954837908
|
|
>>> stats.t.pdf(np.log(5), 6)/5. #derivative
|
|
0.021798547909675815
|
|
|
|
|
|
Author: josef-pktd
|
|
License: BSD
|
|
|
|
'''
|
|
|
|
import numpy as np
|
|
from numpy import poly1d, sqrt, exp
|
|
|
|
import scipy
|
|
from scipy import stats, special
|
|
from scipy.stats import distributions
|
|
|
|
from statsmodels.stats.moment_helpers import mvsk2mc, mc2mvsk
|
|
|
|
try:
|
|
from scipy.stats._mvn import mvndst
|
|
except ImportError:
|
|
# Must be using SciPy <1.8.0 where this function was moved (it's not a
|
|
# public SciPy function, but we need it here)
|
|
from scipy.stats.mvn import mvndst
|
|
|
|
|
|
# note copied from distr_skewnorm_0.py
|
|
|
|
|
|
class SkewNorm_gen(distributions.rv_continuous):
|
|
'''univariate Skew-Normal distribution of Azzalini
|
|
|
|
class follows scipy.stats.distributions pattern
|
|
but with __init__
|
|
|
|
|
|
'''
|
|
|
|
def __init__(self):
|
|
# super(SkewNorm_gen,self).__init__(
|
|
distributions.rv_continuous.__init__(
|
|
self,
|
|
name='Skew Normal distribution',
|
|
shapes='alpha', # extradoc = ''' '''
|
|
)
|
|
|
|
def _argcheck(self, alpha):
|
|
return 1 # (alpha >= 0)
|
|
|
|
def _rvs(self, alpha):
|
|
# see http://azzalini.stat.unipd.it/SN/faq.html
|
|
delta = alpha / np.sqrt(1 + alpha ** 2)
|
|
u0 = stats.norm.rvs(size=self._size)
|
|
u1 = delta * u0 + np.sqrt(1 - delta ** 2) * stats.norm.rvs(size=self._size)
|
|
return np.where(u0 > 0, u1, -u1)
|
|
|
|
def _munp(self, n, alpha):
|
|
# use pdf integration with _mom0_sc if only _pdf is defined.
|
|
# default stats calculation uses ppf, which is much slower
|
|
return self._mom0_sc(n, alpha)
|
|
|
|
def _pdf(self, x, alpha):
|
|
# 2*normpdf(x)*normcdf(alpha*x)
|
|
return 2.0 / np.sqrt(2 * np.pi) * np.exp(-x ** 2 / 2.0) * special.ndtr(alpha * x)
|
|
|
|
def _stats_skip(self, x, alpha, moments='mvsk'):
|
|
# skip for now to force moment integration as check
|
|
pass
|
|
|
|
|
|
skewnorm = SkewNorm_gen()
|
|
|
|
|
|
# generated the same way as distributions in stats.distributions
|
|
class SkewNorm2_gen(distributions.rv_continuous):
|
|
'''univariate Skew-Normal distribution of Azzalini
|
|
|
|
class follows scipy.stats.distributions pattern
|
|
|
|
'''
|
|
|
|
def _argcheck(self, alpha):
|
|
return 1 # where(alpha>=0, 1, 0)
|
|
|
|
def _pdf(self, x, alpha):
|
|
# 2*normpdf(x)*normcdf(alpha*x
|
|
return 2.0 / np.sqrt(2 * np.pi) * np.exp(-x ** 2 / 2.0) * special.ndtr(alpha * x)
|
|
|
|
|
|
skewnorm2 = SkewNorm2_gen(name='Skew Normal distribution', shapes='alpha',
|
|
# extradoc = ''' -inf < alpha < inf'''
|
|
)
|
|
|
|
|
|
class ACSkewT_gen(distributions.rv_continuous):
|
|
'''univariate Skew-T distribution of Azzalini
|
|
|
|
class follows scipy.stats.distributions pattern
|
|
but with __init__
|
|
'''
|
|
|
|
def __init__(self):
|
|
# super(SkewT_gen,self).__init__(
|
|
distributions.rv_continuous.__init__(
|
|
self,
|
|
name='Skew T distribution',
|
|
shapes='df, alpha',
|
|
)
|
|
|
|
# extradoc = '''
|
|
# Skewed T distribution by Azzalini, A. & Capitanio, A. (2003)_
|
|
#
|
|
# the pdf is given by:
|
|
# pdf(x) = 2.0 * t.pdf(x, df) * t.cdf(df+1, alpha*x*np.sqrt((1+df)/(x**2+df)))
|
|
#
|
|
# with alpha >=0
|
|
#
|
|
# Note: different from skewed t distribution by Hansen 1999
|
|
# .._
|
|
# Azzalini, A. & Capitanio, A. (2003), Distributions generated by perturbation of
|
|
# symmetry with emphasis on a multivariate skew-t distribution,
|
|
# appears in J.Roy.Statist.Soc, series B, vol.65, pp.367-389
|
|
#
|
|
# ''' )
|
|
|
|
def _argcheck(self, df, alpha):
|
|
return (alpha == alpha) * (df > 0)
|
|
|
|
## def _arg_check(self, alpha):
|
|
## return np.where(alpha>=0, 0, 1)
|
|
## def _argcheck(self, alpha):
|
|
## return np.where(alpha>=0, 1, 0)
|
|
|
|
def _rvs(self, df, alpha):
|
|
# see http://azzalini.stat.unipd.it/SN/faq.html
|
|
# delta = alpha/np.sqrt(1+alpha**2)
|
|
V = stats.chi2.rvs(df, size=self._size)
|
|
z = skewnorm.rvs(alpha, size=self._size)
|
|
return z / np.sqrt(V / df)
|
|
|
|
def _munp(self, n, df, alpha):
|
|
# use pdf integration with _mom0_sc if only _pdf is defined.
|
|
# default stats calculation uses ppf
|
|
return self._mom0_sc(n, df, alpha)
|
|
|
|
def _pdf(self, x, df, alpha):
|
|
# 2*normpdf(x)*normcdf(alpha*x)
|
|
return 2.0 * distributions.t._pdf(x, df) * special.stdtr(df + 1, alpha * x * np.sqrt(
|
|
(1 + df) / (x ** 2 + df)))
|
|
|
|
|
|
##
|
|
##def mvsk2cm(*args):
|
|
## mu,sig,sk,kur = args
|
|
## # Get central moments
|
|
## cnt = [None]*4
|
|
## cnt[0] = mu
|
|
## cnt[1] = sig #*sig
|
|
## cnt[2] = sk * sig**1.5
|
|
## cnt[3] = (kur+3.0) * sig**2.0
|
|
## return cnt
|
|
##
|
|
##
|
|
##def mvsk2m(args):
|
|
## mc, mc2, skew, kurt = args#= self._stats(*args,**mdict)
|
|
## mnc = mc
|
|
## mnc2 = mc2 + mc*mc
|
|
## mc3 = skew*(mc2**1.5) # 3rd central moment
|
|
## mnc3 = mc3+3*mc*mc2+mc**3 # 3rd non-central moment
|
|
## mc4 = (kurt+3.0)*(mc2**2.0) # 4th central moment
|
|
## mnc4 = mc4+4*mc*mc3+6*mc*mc*mc2+mc**4
|
|
## return (mc, mc2, mc3, mc4), (mnc, mnc2, mnc3, mnc4)
|
|
##
|
|
##def mc2mvsk(args):
|
|
## mc, mc2, mc3, mc4 = args
|
|
## skew = mc3 / mc2**1.5
|
|
## kurt = mc4 / mc2**2.0 - 3.0
|
|
## return (mc, mc2, skew, kurt)
|
|
##
|
|
##def m2mc(args):
|
|
## mnc, mnc2, mnc3, mnc4 = args
|
|
## mc = mnc
|
|
## mc2 = mnc2 - mnc*mnc
|
|
## #mc3 = skew*(mc2**1.5) # 3rd central moment
|
|
## mc3 = mnc3 - (3*mc*mc2+mc**3) # 3rd central moment
|
|
## #mc4 = (kurt+3.0)*(mc2**2.0) # 4th central moment
|
|
## mc4 = mnc4 - (4*mc*mc3+6*mc*mc*mc2+mc**4)
|
|
## return (mc, mc2, mc3, mc4)
|
|
|
|
|
|
def _hermnorm(N):
|
|
# return the negatively normalized hermite polynomials up to order N-1
|
|
# (inclusive)
|
|
# using the recursive relationship
|
|
# p_n+1 = p_n(x)' - x*p_n(x)
|
|
# and p_0(x) = 1
|
|
plist = [None] * N
|
|
plist[0] = poly1d(1)
|
|
for n in range(1, N):
|
|
plist[n] = plist[n - 1].deriv() - poly1d([1, 0]) * plist[n - 1]
|
|
return plist
|
|
|
|
|
|
def pdf_moments_st(cnt):
|
|
"""Return the Gaussian expanded pdf function given the list of central
|
|
moments (first one is mean).
|
|
|
|
version of scipy.stats, any changes ?
|
|
the scipy.stats version has a bug and returns normal distribution
|
|
"""
|
|
|
|
N = len(cnt)
|
|
if N < 2:
|
|
raise ValueError("At least two moments must be given to "
|
|
"approximate the pdf.")
|
|
|
|
totp = poly1d(1)
|
|
sig = sqrt(cnt[1])
|
|
mu = cnt[0]
|
|
if N > 2:
|
|
Dvals = _hermnorm(N + 1)
|
|
for k in range(3, N + 1):
|
|
# Find Ck
|
|
Ck = 0.0
|
|
for n in range((k - 3) / 2):
|
|
m = k - 2 * n
|
|
if m % 2: # m is odd
|
|
momdiff = cnt[m - 1]
|
|
else:
|
|
momdiff = cnt[m - 1] - sig * sig * scipy.factorial2(m - 1)
|
|
Ck += Dvals[k][m] / sig ** m * momdiff
|
|
# Add to totp
|
|
raise SystemError
|
|
print(Dvals)
|
|
print(Ck)
|
|
totp = totp + Ck * Dvals[k]
|
|
|
|
def thisfunc(x):
|
|
xn = (x - mu) / sig
|
|
return totp(xn) * exp(-xn * xn / 2.0) / sqrt(2 * np.pi) / sig
|
|
|
|
return thisfunc, totp
|
|
|
|
|
|
def pdf_mvsk(mvsk):
|
|
"""Return the Gaussian expanded pdf function given the list of 1st, 2nd
|
|
moment and skew and Fisher (excess) kurtosis.
|
|
|
|
|
|
|
|
Parameters
|
|
----------
|
|
mvsk : list of mu, mc2, skew, kurt
|
|
distribution is matched to these four moments
|
|
|
|
Returns
|
|
-------
|
|
pdffunc : function
|
|
function that evaluates the pdf(x), where x is the non-standardized
|
|
random variable.
|
|
|
|
|
|
Notes
|
|
-----
|
|
|
|
Changed so it works only if four arguments are given. Uses explicit
|
|
formula, not loop.
|
|
|
|
This implements a Gram-Charlier expansion of the normal distribution
|
|
where the first 2 moments coincide with those of the normal distribution
|
|
but skew and kurtosis can deviate from it.
|
|
|
|
In the Gram-Charlier distribution it is possible that the density
|
|
becomes negative. This is the case when the deviation from the
|
|
normal distribution is too large.
|
|
|
|
|
|
|
|
References
|
|
----------
|
|
https://en.wikipedia.org/wiki/Edgeworth_series
|
|
Johnson N.L., S. Kotz, N. Balakrishnan: Continuous Univariate
|
|
Distributions, Volume 1, 2nd ed., p.30
|
|
"""
|
|
N = len(mvsk)
|
|
if N < 4:
|
|
raise ValueError("Four moments must be given to "
|
|
"approximate the pdf.")
|
|
|
|
mu, mc2, skew, kurt = mvsk
|
|
|
|
totp = poly1d(1)
|
|
sig = sqrt(mc2)
|
|
if N > 2:
|
|
Dvals = _hermnorm(N + 1)
|
|
C3 = skew / 6.0
|
|
C4 = kurt / 24.0
|
|
# Note: Hermite polynomial for order 3 in _hermnorm is negative
|
|
# instead of positive
|
|
totp = totp - C3 * Dvals[3] + C4 * Dvals[4]
|
|
|
|
def pdffunc(x):
|
|
xn = (x - mu) / sig
|
|
return totp(xn) * np.exp(-xn * xn / 2.0) / np.sqrt(2 * np.pi) / sig
|
|
|
|
return pdffunc
|
|
|
|
|
|
def pdf_moments(cnt):
|
|
"""Return the Gaussian expanded pdf function given the list of central
|
|
moments (first one is mean).
|
|
|
|
Changed so it works only if four arguments are given. Uses explicit
|
|
formula, not loop.
|
|
|
|
Notes
|
|
-----
|
|
|
|
This implements a Gram-Charlier expansion of the normal distribution
|
|
where the first 2 moments coincide with those of the normal distribution
|
|
but skew and kurtosis can deviate from it.
|
|
|
|
In the Gram-Charlier distribution it is possible that the density
|
|
becomes negative. This is the case when the deviation from the
|
|
normal distribution is too large.
|
|
|
|
|
|
|
|
References
|
|
----------
|
|
https://en.wikipedia.org/wiki/Edgeworth_series
|
|
Johnson N.L., S. Kotz, N. Balakrishnan: Continuous Univariate
|
|
Distributions, Volume 1, 2nd ed., p.30
|
|
"""
|
|
N = len(cnt)
|
|
if N < 2:
|
|
raise ValueError("At least two moments must be given to "
|
|
"approximate the pdf.")
|
|
|
|
mc, mc2, mc3, mc4 = cnt
|
|
skew = mc3 / mc2 ** 1.5
|
|
kurt = mc4 / mc2 ** 2.0 - 3.0 # Fisher kurtosis, excess kurtosis
|
|
|
|
totp = poly1d(1)
|
|
sig = sqrt(cnt[1])
|
|
mu = cnt[0]
|
|
if N > 2:
|
|
Dvals = _hermnorm(N + 1)
|
|
## for k in range(3,N+1):
|
|
## # Find Ck
|
|
## Ck = 0.0
|
|
## for n in range((k-3)/2):
|
|
## m = k-2*n
|
|
## if m % 2: # m is odd
|
|
## momdiff = cnt[m-1]
|
|
## else:
|
|
## momdiff = cnt[m-1] - sig*sig*scipy.factorial2(m-1)
|
|
## Ck += Dvals[k][m] / sig**m * momdiff
|
|
## # Add to totp
|
|
## raise
|
|
## print Dvals
|
|
## print Ck
|
|
## totp = totp + Ck*Dvals[k]
|
|
C3 = skew / 6.0
|
|
C4 = kurt / 24.0
|
|
totp = totp - C3 * Dvals[3] + C4 * Dvals[4]
|
|
|
|
def thisfunc(x):
|
|
xn = (x - mu) / sig
|
|
return totp(xn) * np.exp(-xn * xn / 2.0) / np.sqrt(2 * np.pi) / sig
|
|
|
|
return thisfunc
|
|
|
|
|
|
class NormExpan_gen(distributions.rv_continuous):
|
|
'''Gram-Charlier Expansion of Normal distribution
|
|
|
|
class follows scipy.stats.distributions pattern
|
|
but with __init__
|
|
|
|
'''
|
|
|
|
def __init__(self, args, **kwds):
|
|
# todo: replace with super call
|
|
distributions.rv_continuous.__init__(
|
|
self,
|
|
name='Normal Expansion distribution',
|
|
shapes=' ',
|
|
)
|
|
# extradoc = '''
|
|
# The distribution is defined as the Gram-Charlier expansion of
|
|
# the normal distribution using the first four moments. The pdf
|
|
# is given by
|
|
#
|
|
# pdf(x) = (1+ skew/6.0 * H(xc,3) + kurt/24.0 * H(xc,4))*normpdf(xc)
|
|
#
|
|
# where xc = (x-mu)/sig is the standardized value of the random variable
|
|
# and H(xc,3) and H(xc,4) are Hermite polynomials
|
|
#
|
|
# Note: This distribution has to be parametrized during
|
|
# initialization and instantiation, and does not have a shape
|
|
# parameter after instantiation (similar to frozen distribution
|
|
# except for location and scale.) Location and scale can be used
|
|
# as with other distributions, however note, that they are relative
|
|
# to the initialized distribution.
|
|
# ''' )
|
|
# print args, kwds
|
|
mode = kwds.get('mode', 'sample')
|
|
|
|
if mode == 'sample':
|
|
mu, sig, sk, kur = stats.describe(args)[2:]
|
|
self.mvsk = (mu, sig, sk, kur)
|
|
cnt = mvsk2mc((mu, sig, sk, kur))
|
|
elif mode == 'mvsk':
|
|
cnt = mvsk2mc(args)
|
|
self.mvsk = args
|
|
elif mode == 'centmom':
|
|
cnt = args
|
|
self.mvsk = mc2mvsk(cnt)
|
|
else:
|
|
raise ValueError("mode must be 'mvsk' or centmom")
|
|
|
|
self.cnt = cnt
|
|
# self.mvsk = (mu,sig,sk,kur)
|
|
# self._pdf = pdf_moments(cnt)
|
|
self._pdf = pdf_mvsk(self.mvsk)
|
|
|
|
def _munp(self, n):
|
|
# use pdf integration with _mom0_sc if only _pdf is defined.
|
|
# default stats calculation uses ppf
|
|
return self._mom0_sc(n)
|
|
|
|
def _stats_skip(self):
|
|
# skip for now to force numerical integration of pdf for testing
|
|
return self.mvsk
|
|
|
|
|
|
## copied from nonlinear_transform_gen.py
|
|
|
|
''' A class for the distribution of a non-linear monotonic transformation of a continuous random variable
|
|
|
|
simplest usage:
|
|
example: create log-gamma distribution, i.e. y = log(x),
|
|
where x is gamma distributed (also available in scipy.stats)
|
|
loggammaexpg = Transf_gen(stats.gamma, np.log, np.exp)
|
|
|
|
example: what is the distribution of the discount factor y=1/(1+x)
|
|
where interest rate x is normally distributed with N(mux,stdx**2)')?
|
|
(just to come up with a story that implies a nice transformation)
|
|
invnormalg = Transf_gen(stats.norm, inversew, inversew_inv, decr=True, a=-np.inf)
|
|
|
|
This class does not work well for distributions with difficult shapes,
|
|
e.g. 1/x where x is standard normal, because of the singularity and jump at zero.
|
|
|
|
Note: I'm working from my version of scipy.stats.distribution.
|
|
But this script runs under scipy 0.6.0 (checked with numpy: 1.2.0rc2 and python 2.4)
|
|
|
|
This is not yet thoroughly tested, polished or optimized
|
|
|
|
TODO:
|
|
* numargs handling is not yet working properly, numargs needs to be specified (default = 0 or 1)
|
|
* feeding args and kwargs to underlying distribution is untested and incomplete
|
|
* distinguish args and kwargs for the transformed and the underlying distribution
|
|
- currently all args and no kwargs are transmitted to underlying distribution
|
|
- loc and scale only work for transformed, but not for underlying distribution
|
|
- possible to separate args for transformation and underlying distribution parameters
|
|
|
|
* add _rvs as method, will be faster in many cases
|
|
|
|
|
|
Created on Tuesday, October 28, 2008, 12:40:37 PM
|
|
Author: josef-pktd
|
|
License: BSD
|
|
|
|
'''
|
|
|
|
|
|
def get_u_argskwargs(**kwargs):
|
|
# Todo: What's this? wrong spacing, used in Transf_gen TransfTwo_gen
|
|
u_kwargs = {k.replace('u_', '', 1): v for k, v in kwargs.items()
|
|
if k.startswith('u_')}
|
|
u_args = u_kwargs.pop('u_args', None)
|
|
return u_args, u_kwargs
|
|
|
|
|
|
class Transf_gen(distributions.rv_continuous):
|
|
'''a class for non-linear monotonic transformation of a continuous random variable
|
|
|
|
'''
|
|
|
|
def __init__(self, kls, func, funcinv, *args, **kwargs):
|
|
# print args
|
|
# print kwargs
|
|
|
|
self.func = func
|
|
self.funcinv = funcinv
|
|
# explicit for self.__dict__.update(kwargs)
|
|
# need to set numargs because inspection does not work
|
|
self.numargs = kwargs.pop('numargs', 0)
|
|
# print self.numargs
|
|
name = kwargs.pop('name', 'transfdist')
|
|
longname = kwargs.pop('longname', 'Non-linear transformed distribution')
|
|
extradoc = kwargs.pop('extradoc', None)
|
|
a = kwargs.pop('a', -np.inf)
|
|
b = kwargs.pop('b', np.inf)
|
|
self.decr = kwargs.pop('decr', False)
|
|
# defines whether it is a decreasing (True)
|
|
# or increasing (False) monotonic transformation
|
|
|
|
self.u_args, self.u_kwargs = get_u_argskwargs(**kwargs)
|
|
self.kls = kls # (self.u_args, self.u_kwargs)
|
|
# possible to freeze the underlying distribution
|
|
|
|
super().__init__(a=a, b=b, name=name, longname=longname)
|
|
# extradoc = extradoc)
|
|
|
|
def _rvs(self, *args, **kwargs):
|
|
self.kls._size = self._size
|
|
return self.funcinv(self.kls._rvs(*args))
|
|
|
|
def _cdf(self, x, *args, **kwargs):
|
|
# print args
|
|
if not self.decr:
|
|
return self.kls._cdf(self.funcinv(x), *args, **kwargs)
|
|
# note scipy _cdf only take *args not *kwargs
|
|
else:
|
|
return 1.0 - self.kls._cdf(self.funcinv(x), *args, **kwargs)
|
|
|
|
def _ppf(self, q, *args, **kwargs):
|
|
if not self.decr:
|
|
return self.func(self.kls._ppf(q, *args, **kwargs))
|
|
else:
|
|
return self.func(self.kls._ppf(1 - q, *args, **kwargs))
|
|
|
|
|
|
def inverse(x):
|
|
return np.divide(1.0, x)
|
|
|
|
|
|
mux, stdx = 0.05, 0.1
|
|
mux, stdx = 9.0, 1.0
|
|
|
|
|
|
def inversew(x):
|
|
return 1.0 / (1 + mux + x * stdx)
|
|
|
|
|
|
def inversew_inv(x):
|
|
return (1.0 / x - 1.0 - mux) / stdx # .np.divide(1.0,x)-10
|
|
|
|
|
|
def identit(x):
|
|
return x
|
|
|
|
|
|
invdnormalg = Transf_gen(stats.norm, inversew, inversew_inv, decr=True, # a=-np.inf,
|
|
numargs=0, name='discf', longname='normal-based discount factor',
|
|
extradoc='\ndistribution of discount factor y=1/(1+x)) with x N(0.05,0.1**2)')
|
|
|
|
lognormalg = Transf_gen(stats.norm, np.exp, np.log,
|
|
numargs=2, a=0, name='lnnorm',
|
|
longname='Exp transformed normal',
|
|
# extradoc = '\ndistribution of y = exp(x), with x standard normal'
|
|
# 'precision for moment andstats is not very high, 2-3 decimals'
|
|
)
|
|
|
|
loggammaexpg = Transf_gen(stats.gamma, np.log, np.exp, numargs=1)
|
|
|
|
## copied form nonlinear_transform_short.py
|
|
|
|
'''univariate distribution of a non-linear monotonic transformation of a
|
|
random variable
|
|
|
|
'''
|
|
|
|
|
|
class ExpTransf_gen(distributions.rv_continuous):
|
|
'''Distribution based on log/exp transformation
|
|
|
|
the constructor can be called with a distribution class
|
|
and generates the distribution of the transformed random variable
|
|
|
|
'''
|
|
|
|
def __init__(self, kls, *args, **kwargs):
|
|
# print args
|
|
# print kwargs
|
|
# explicit for self.__dict__.update(kwargs)
|
|
if 'numargs' in kwargs:
|
|
self.numargs = kwargs['numargs']
|
|
else:
|
|
self.numargs = 1
|
|
if 'name' in kwargs:
|
|
name = kwargs['name']
|
|
else:
|
|
name = 'Log transformed distribution'
|
|
if 'a' in kwargs:
|
|
a = kwargs['a']
|
|
else:
|
|
a = 0
|
|
super().__init__(a=0, name=name)
|
|
self.kls = kls
|
|
|
|
def _cdf(self, x, *args):
|
|
pass
|
|
# print args
|
|
return self.kls.cdf(np.log(x), *args)
|
|
|
|
def _ppf(self, q, *args):
|
|
return np.exp(self.kls.ppf(q, *args))
|
|
|
|
|
|
class LogTransf_gen(distributions.rv_continuous):
|
|
'''Distribution based on log/exp transformation
|
|
|
|
the constructor can be called with a distribution class
|
|
and generates the distribution of the transformed random variable
|
|
|
|
'''
|
|
|
|
def __init__(self, kls, *args, **kwargs):
|
|
# explicit for self.__dict__.update(kwargs)
|
|
if 'numargs' in kwargs:
|
|
self.numargs = kwargs['numargs']
|
|
else:
|
|
self.numargs = 1
|
|
if 'name' in kwargs:
|
|
name = kwargs['name']
|
|
else:
|
|
name = 'Log transformed distribution'
|
|
if 'a' in kwargs:
|
|
a = kwargs['a']
|
|
else:
|
|
a = 0
|
|
|
|
super().__init__(a=a, name=name)
|
|
self.kls = kls
|
|
|
|
def _cdf(self, x, *args):
|
|
# print args
|
|
return self.kls._cdf(np.exp(x), *args)
|
|
|
|
def _ppf(self, q, *args):
|
|
return np.log(self.kls._ppf(q, *args))
|
|
|
|
|
|
## copied from transformtwo.py
|
|
|
|
'''
|
|
Created on Apr 28, 2009
|
|
|
|
@author: Josef Perktold
|
|
'''
|
|
|
|
''' A class for the distribution of a non-linear u-shaped or hump shaped transformation of a
|
|
continuous random variable
|
|
|
|
This is a companion to the distributions of non-linear monotonic transformation to the case
|
|
when the inverse mapping is a 2-valued correspondence, for example for absolute value or square
|
|
|
|
simplest usage:
|
|
example: create squared distribution, i.e. y = x**2,
|
|
where x is normal or t distributed
|
|
|
|
|
|
This class does not work well for distributions with difficult shapes,
|
|
e.g. 1/x where x is standard normal, because of the singularity and jump at zero.
|
|
|
|
|
|
This verifies for normal - chi2, normal - halfnorm, foldnorm, and t - F
|
|
|
|
TODO:
|
|
* numargs handling is not yet working properly,
|
|
numargs needs to be specified (default = 0 or 1)
|
|
* feeding args and kwargs to underlying distribution works in t distribution example
|
|
* distinguish args and kwargs for the transformed and the underlying distribution
|
|
- currently all args and no kwargs are transmitted to underlying distribution
|
|
- loc and scale only work for transformed, but not for underlying distribution
|
|
- possible to separate args for transformation and underlying distribution parameters
|
|
|
|
* add _rvs as method, will be faster in many cases
|
|
|
|
'''
|
|
|
|
|
|
class TransfTwo_gen(distributions.rv_continuous):
|
|
'''Distribution based on a non-monotonic (u- or hump-shaped transformation)
|
|
|
|
the constructor can be called with a distribution class, and functions
|
|
that define the non-linear transformation.
|
|
and generates the distribution of the transformed random variable
|
|
|
|
Note: the transformation, it's inverse and derivatives need to be fully
|
|
specified: func, funcinvplus, funcinvminus, derivplus, derivminus.
|
|
Currently no numerical derivatives or inverse are calculated
|
|
|
|
This can be used to generate distribution instances similar to the
|
|
distributions in scipy.stats.
|
|
|
|
'''
|
|
|
|
# a class for non-linear non-monotonic transformation of a continuous random variable
|
|
def __init__(self, kls, func, funcinvplus, funcinvminus, derivplus,
|
|
derivminus, *args, **kwargs):
|
|
# print args
|
|
# print kwargs
|
|
|
|
self.func = func
|
|
self.funcinvplus = funcinvplus
|
|
self.funcinvminus = funcinvminus
|
|
self.derivplus = derivplus
|
|
self.derivminus = derivminus
|
|
# explicit for self.__dict__.update(kwargs)
|
|
# need to set numargs because inspection does not work
|
|
self.numargs = kwargs.pop('numargs', 0)
|
|
# print self.numargs
|
|
name = kwargs.pop('name', 'transfdist')
|
|
longname = kwargs.pop('longname', 'Non-linear transformed distribution')
|
|
extradoc = kwargs.pop('extradoc', None)
|
|
a = kwargs.pop('a', -np.inf) # attached to self in super
|
|
b = kwargs.pop('b', np.inf) # self.a, self.b would be overwritten
|
|
self.shape = kwargs.pop('shape', False)
|
|
# defines whether it is a `u` shaped or `hump' shaped
|
|
# transformation
|
|
|
|
self.u_args, self.u_kwargs = get_u_argskwargs(**kwargs)
|
|
self.kls = kls # (self.u_args, self.u_kwargs)
|
|
# possible to freeze the underlying distribution
|
|
|
|
super().__init__(
|
|
a=a, b=b, name=name, shapes=kls.shapes, longname=longname
|
|
)
|
|
|
|
# add enough info for self.freeze() to be able to reconstruct the instance
|
|
self._ctor_param.update(
|
|
dict(kls=kls, func=func, funcinvplus=funcinvplus,
|
|
funcinvminus=funcinvminus, derivplus=derivplus,
|
|
derivminus=derivminus, shape=self.shape)
|
|
)
|
|
|
|
def _rvs(self, *args):
|
|
self.kls._size = self._size # size attached to self, not function argument
|
|
return self.func(self.kls._rvs(*args))
|
|
|
|
def _pdf(self, x, *args, **kwargs):
|
|
# print args
|
|
if self.shape == 'u':
|
|
signpdf = 1
|
|
elif self.shape == 'hump':
|
|
signpdf = -1
|
|
else:
|
|
raise ValueError('shape can only be `u` or `hump`')
|
|
|
|
return signpdf * (self.derivplus(x) * self.kls._pdf(self.funcinvplus(x), *args, **kwargs) -
|
|
self.derivminus(x) * self.kls._pdf(self.funcinvminus(x), *args,
|
|
**kwargs))
|
|
# note scipy _cdf only take *args not *kwargs
|
|
|
|
def _cdf(self, x, *args, **kwargs):
|
|
# print args
|
|
if self.shape == 'u':
|
|
return self.kls._cdf(self.funcinvplus(x), *args, **kwargs) - \
|
|
self.kls._cdf(self.funcinvminus(x), *args, **kwargs)
|
|
# note scipy _cdf only take *args not *kwargs
|
|
else:
|
|
return 1.0 - self._sf(x, *args, **kwargs)
|
|
|
|
def _sf(self, x, *args, **kwargs):
|
|
# print args
|
|
if self.shape == 'hump':
|
|
return self.kls._cdf(self.funcinvplus(x), *args, **kwargs) - \
|
|
self.kls._cdf(self.funcinvminus(x), *args, **kwargs)
|
|
# note scipy _cdf only take *args not *kwargs
|
|
else:
|
|
return 1.0 - self._cdf(x, *args, **kwargs)
|
|
|
|
def _munp(self, n, *args, **kwargs):
|
|
args = [np.squeeze(arg) for arg in args]
|
|
out = np.squeeze(self._mom0_sc(n, *args))
|
|
if np.isscalar(out):
|
|
return float(out)
|
|
return out
|
|
|
|
|
|
# ppf might not be possible in general case?
|
|
# should be possible in symmetric case
|
|
# def _ppf(self, q, *args, **kwargs):
|
|
# if self.shape == 'u':
|
|
# return self.func(self.kls._ppf(q,*args, **kwargs))
|
|
# elif self.shape == 'hump':
|
|
# return self.func(self.kls._ppf(1-q,*args, **kwargs))
|
|
|
|
# TODO: rename these functions to have unique names
|
|
|
|
class SquareFunc:
|
|
'''class to hold quadratic function with inverse function and derivative
|
|
|
|
using instance methods instead of class methods, if we want extension
|
|
to parametrized function
|
|
'''
|
|
|
|
def inverseplus(self, x):
|
|
return np.sqrt(x)
|
|
|
|
def inverseminus(self, x):
|
|
return 0.0 - np.sqrt(x)
|
|
|
|
def derivplus(self, x):
|
|
return 0.5 / np.sqrt(x)
|
|
|
|
def derivminus(self, x):
|
|
return 0.0 - 0.5 / np.sqrt(x)
|
|
|
|
def squarefunc(self, x):
|
|
return np.power(x, 2)
|
|
|
|
|
|
sqfunc = SquareFunc()
|
|
|
|
squarenormalg = TransfTwo_gen(stats.norm, sqfunc.squarefunc, sqfunc.inverseplus,
|
|
sqfunc.inverseminus, sqfunc.derivplus, sqfunc.derivminus,
|
|
shape='u', a=0.0, b=np.inf,
|
|
numargs=0, name='squarenorm', longname='squared normal distribution',
|
|
# extradoc = '\ndistribution of the square of a normal random variable' +\
|
|
# ' y=x**2 with x N(0.0,1)'
|
|
)
|
|
# u_loc=l, u_scale=s)
|
|
squaretg = TransfTwo_gen(stats.t, sqfunc.squarefunc, sqfunc.inverseplus,
|
|
sqfunc.inverseminus, sqfunc.derivplus, sqfunc.derivminus,
|
|
shape='u', a=0.0, b=np.inf,
|
|
numargs=1, name='squarenorm', longname='squared t distribution',
|
|
# extradoc = '\ndistribution of the square of a t random variable' +\
|
|
# ' y=x**2 with x t(dof,0.0,1)'
|
|
)
|
|
|
|
|
|
def inverseplus(x):
|
|
return np.sqrt(-x)
|
|
|
|
|
|
def inverseminus(x):
|
|
return 0.0 - np.sqrt(-x)
|
|
|
|
|
|
def derivplus(x):
|
|
return 0.0 - 0.5 / np.sqrt(-x)
|
|
|
|
|
|
def derivminus(x):
|
|
return 0.5 / np.sqrt(-x)
|
|
|
|
|
|
def negsquarefunc(x):
|
|
return -np.power(x, 2)
|
|
|
|
|
|
negsquarenormalg = TransfTwo_gen(stats.norm, negsquarefunc, inverseplus, inverseminus,
|
|
derivplus, derivminus, shape='hump', a=-np.inf, b=0.0,
|
|
numargs=0, name='negsquarenorm',
|
|
longname='negative squared normal distribution',
|
|
# extradoc = '\ndistribution of the negative square of a normal random variable' +\
|
|
# ' y=-x**2 with x N(0.0,1)'
|
|
)
|
|
|
|
|
|
# u_loc=l, u_scale=s)
|
|
|
|
def inverseplus(x):
|
|
return x
|
|
|
|
|
|
def inverseminus(x):
|
|
return 0.0 - x
|
|
|
|
|
|
def derivplus(x):
|
|
return 1.0
|
|
|
|
|
|
def derivminus(x):
|
|
return 0.0 - 1.0
|
|
|
|
|
|
def absfunc(x):
|
|
return np.abs(x)
|
|
|
|
|
|
absnormalg = TransfTwo_gen(stats.norm, np.abs, inverseplus, inverseminus,
|
|
derivplus, derivminus, shape='u', a=0.0, b=np.inf,
|
|
numargs=0, name='absnorm', longname='absolute of normal distribution',
|
|
# extradoc = '\ndistribution of the absolute value of a normal random variable' +\
|
|
# ' y=abs(x) with x N(0,1)'
|
|
)
|
|
|
|
# copied from mvncdf.py
|
|
'''multivariate normal probabilities and cumulative distribution function
|
|
a wrapper for scipy.stats._mvn.mvndst
|
|
|
|
|
|
SUBROUTINE MVNDST( N, LOWER, UPPER, INFIN, CORREL, MAXPTS,
|
|
& ABSEPS, RELEPS, ERROR, VALUE, INFORM )
|
|
*
|
|
* A subroutine for computing multivariate normal probabilities.
|
|
* This subroutine uses an algorithm given in the paper
|
|
* "Numerical Computation of Multivariate Normal Probabilities", in
|
|
* J. of Computational and Graphical Stat., 1(1992), pp. 141-149, by
|
|
* Alan Genz
|
|
* Department of Mathematics
|
|
* Washington State University
|
|
* Pullman, WA 99164-3113
|
|
* Email : AlanGenz@wsu.edu
|
|
*
|
|
* Parameters
|
|
*
|
|
* N INTEGER, the number of variables.
|
|
* LOWER REAL, array of lower integration limits.
|
|
* UPPER REAL, array of upper integration limits.
|
|
* INFIN INTEGER, array of integration limits flags:
|
|
* if INFIN(I) < 0, Ith limits are (-infinity, infinity);
|
|
* if INFIN(I) = 0, Ith limits are (-infinity, UPPER(I)];
|
|
* if INFIN(I) = 1, Ith limits are [LOWER(I), infinity);
|
|
* if INFIN(I) = 2, Ith limits are [LOWER(I), UPPER(I)].
|
|
* CORREL REAL, array of correlation coefficients; the correlation
|
|
* coefficient in row I column J of the correlation matrix
|
|
* should be stored in CORREL( J + ((I-2)*(I-1))/2 ), for J < I.
|
|
* The correlation matrix must be positive semidefinite.
|
|
* MAXPTS INTEGER, maximum number of function values allowed. This
|
|
* parameter can be used to limit the time. A sensible
|
|
* strategy is to start with MAXPTS = 1000*N, and then
|
|
* increase MAXPTS if ERROR is too large.
|
|
* ABSEPS REAL absolute error tolerance.
|
|
* RELEPS REAL relative error tolerance.
|
|
* ERROR REAL estimated absolute error, with 99% confidence level.
|
|
* VALUE REAL estimated value for the integral
|
|
* INFORM INTEGER, termination status parameter:
|
|
* if INFORM = 0, normal completion with ERROR < EPS;
|
|
* if INFORM = 1, completion with ERROR > EPS and MAXPTS
|
|
* function vaules used; increase MAXPTS to
|
|
* decrease ERROR;
|
|
* if INFORM = 2, N > 500 or N < 1.
|
|
*
|
|
|
|
|
|
|
|
>>> mvndst([0.0,0.0],[10.0,10.0],[0,0],[0.5])
|
|
(2e-016, 1.0, 0)
|
|
>>> mvndst([0.0,0.0],[100.0,100.0],[0,0],[0.0])
|
|
(2e-016, 1.0, 0)
|
|
>>> mvndst([0.0,0.0],[1.0,1.0],[0,0],[0.0])
|
|
(2e-016, 0.70786098173714096, 0)
|
|
>>> mvndst([0.0,0.0],[0.001,1.0],[0,0],[0.0])
|
|
(2e-016, 0.42100802096993045, 0)
|
|
>>> mvndst([0.0,0.0],[0.001,10.0],[0,0],[0.0])
|
|
(2e-016, 0.50039894221391101, 0)
|
|
>>> mvndst([0.0,0.0],[0.001,100.0],[0,0],[0.0])
|
|
(2e-016, 0.50039894221391101, 0)
|
|
>>> mvndst([0.0,0.0],[0.01,100.0],[0,0],[0.0])
|
|
(2e-016, 0.5039893563146316, 0)
|
|
>>> mvndst([0.0,0.0],[0.1,100.0],[0,0],[0.0])
|
|
(2e-016, 0.53982783727702899, 0)
|
|
>>> mvndst([0.0,0.0],[0.1,100.0],[2,2],[0.0])
|
|
(2e-016, 0.019913918638514494, 0)
|
|
>>> mvndst([0.0,0.0],[0.0,0.0],[0,0],[0.0])
|
|
(2e-016, 0.25, 0)
|
|
>>> mvndst([0.0,0.0],[0.0,0.0],[-1,0],[0.0])
|
|
(2e-016, 0.5, 0)
|
|
>>> mvndst([0.0,0.0],[0.0,0.0],[-1,0],[0.5])
|
|
(2e-016, 0.5, 0)
|
|
>>> mvndst([0.0,0.0],[0.0,0.0],[0,0],[0.5])
|
|
(2e-016, 0.33333333333333337, 0)
|
|
>>> mvndst([0.0,0.0],[0.0,0.0],[0,0],[0.99])
|
|
(2e-016, 0.47747329317779391, 0)
|
|
'''
|
|
|
|
informcode = {0: 'normal completion with ERROR < EPS',
|
|
1: '''completion with ERROR > EPS and MAXPTS function values used;
|
|
increase MAXPTS to decrease ERROR;''',
|
|
2: 'N > 500 or N < 1'}
|
|
|
|
|
|
def mvstdnormcdf(lower, upper, corrcoef, **kwds):
|
|
'''standardized multivariate normal cumulative distribution function
|
|
|
|
This is a wrapper for scipy.stats._mvn.mvndst which calculates
|
|
a rectangular integral over a standardized multivariate normal
|
|
distribution.
|
|
|
|
This function assumes standardized scale, that is the variance in each dimension
|
|
is one, but correlation can be arbitrary, covariance = correlation matrix
|
|
|
|
Parameters
|
|
----------
|
|
lower, upper : array_like, 1d
|
|
lower and upper integration limits with length equal to the number
|
|
of dimensions of the multivariate normal distribution. It can contain
|
|
-np.inf or np.inf for open integration intervals
|
|
corrcoef : float or array_like
|
|
specifies correlation matrix in one of three ways, see notes
|
|
optional keyword parameters to influence integration
|
|
* maxpts : int, maximum number of function values allowed. This
|
|
parameter can be used to limit the time. A sensible
|
|
strategy is to start with `maxpts` = 1000*N, and then
|
|
increase `maxpts` if ERROR is too large.
|
|
* abseps : float absolute error tolerance.
|
|
* releps : float relative error tolerance.
|
|
|
|
Returns
|
|
-------
|
|
cdfvalue : float
|
|
value of the integral
|
|
|
|
|
|
Notes
|
|
-----
|
|
The correlation matrix corrcoef can be given in 3 different ways
|
|
If the multivariate normal is two-dimensional than only the
|
|
correlation coefficient needs to be provided.
|
|
For general dimension the correlation matrix can be provided either
|
|
as a one-dimensional array of the upper triangular correlation
|
|
coefficients stacked by rows, or as full square correlation matrix
|
|
|
|
See Also
|
|
--------
|
|
mvnormcdf : cdf of multivariate normal distribution without
|
|
standardization
|
|
|
|
Examples
|
|
--------
|
|
|
|
>>> print(mvstdnormcdf([-np.inf,-np.inf], [0.0,np.inf], 0.5))
|
|
0.5
|
|
>>> corr = [[1.0, 0, 0.5],[0,1,0],[0.5,0,1]]
|
|
>>> print(mvstdnormcdf([-np.inf,-np.inf,-100.0], [0.0,0.0,0.0], corr, abseps=1e-6))
|
|
0.166666399198
|
|
>>> print(mvstdnormcdf([-np.inf,-np.inf,-100.0],[0.0,0.0,0.0],corr, abseps=1e-8))
|
|
something wrong completion with ERROR > EPS and MAXPTS function values used;
|
|
increase MAXPTS to decrease ERROR; 1.048330348e-006
|
|
0.166666546218
|
|
>>> print(mvstdnormcdf([-np.inf,-np.inf,-100.0],[0.0,0.0,0.0], corr, \
|
|
maxpts=100000, abseps=1e-8))
|
|
0.166666588293
|
|
|
|
'''
|
|
n = len(lower)
|
|
# do not know if converting to array is necessary,
|
|
# but it makes ndim check possible
|
|
lower = np.array(lower)
|
|
upper = np.array(upper)
|
|
corrcoef = np.array(corrcoef)
|
|
|
|
correl = np.zeros(int(n * (n - 1) / 2.0)) # dtype necessary?
|
|
|
|
if (lower.ndim != 1) or (upper.ndim != 1):
|
|
raise ValueError('can handle only 1D bounds')
|
|
if len(upper) != n:
|
|
raise ValueError('bounds have different lengths')
|
|
if n == 2 and corrcoef.size == 1:
|
|
correl = corrcoef
|
|
# print 'case scalar rho', n
|
|
elif corrcoef.ndim == 1 and len(corrcoef) == n * (n - 1) / 2.0:
|
|
# print 'case flat corr', corrcoeff.shape
|
|
correl = corrcoef
|
|
elif corrcoef.shape == (n, n):
|
|
# print 'case square corr', correl.shape
|
|
correl = corrcoef[np.tril_indices(n, -1)]
|
|
# for ii in range(n):
|
|
# for jj in range(ii):
|
|
# correl[ jj + ((ii-2)*(ii-1))/2] = corrcoef[ii,jj]
|
|
else:
|
|
raise ValueError('corrcoef has incorrect dimension')
|
|
|
|
if 'maxpts' not in kwds:
|
|
if n > 2:
|
|
kwds['maxpts'] = 10000 * n
|
|
|
|
lowinf = np.isneginf(lower)
|
|
uppinf = np.isposinf(upper)
|
|
infin = 2.0 * np.ones(n)
|
|
|
|
np.putmask(infin, lowinf, 0) # infin.putmask(0,lowinf)
|
|
np.putmask(infin, uppinf, 1) # infin.putmask(1,uppinf)
|
|
# this has to be last
|
|
np.putmask(infin, lowinf * uppinf, -1)
|
|
|
|
## #remove infs
|
|
## np.putmask(lower,lowinf,-100)# infin.putmask(0,lowinf)
|
|
## np.putmask(upper,uppinf,100) #infin.putmask(1,uppinf)
|
|
|
|
# print lower,',',upper,',',infin,',',correl
|
|
# print correl.shape
|
|
# print kwds.items()
|
|
error, cdfvalue, inform = mvndst(lower, upper, infin, correl, **kwds)
|
|
if inform:
|
|
print('something wrong', informcode[inform], error)
|
|
return cdfvalue
|
|
|
|
|
|
def mvnormcdf(upper, mu, cov, lower=None, **kwds):
|
|
'''multivariate normal cumulative distribution function
|
|
|
|
This is a wrapper for scipy.stats._mvn.mvndst which calculates
|
|
a rectangular integral over a multivariate normal distribution.
|
|
|
|
Parameters
|
|
----------
|
|
lower, upper : array_like, 1d
|
|
lower and upper integration limits with length equal to the number
|
|
of dimensions of the multivariate normal distribution. It can contain
|
|
-np.inf or np.inf for open integration intervals
|
|
mu : array_lik, 1d
|
|
list or array of means
|
|
cov : array_like, 2d
|
|
specifies covariance matrix
|
|
optional keyword parameters to influence integration
|
|
* maxpts : int, maximum number of function values allowed. This
|
|
parameter can be used to limit the time. A sensible
|
|
strategy is to start with `maxpts` = 1000*N, and then
|
|
increase `maxpts` if ERROR is too large.
|
|
* abseps : float absolute error tolerance.
|
|
* releps : float relative error tolerance.
|
|
|
|
Returns
|
|
-------
|
|
cdfvalue : float
|
|
value of the integral
|
|
|
|
|
|
Notes
|
|
-----
|
|
This function normalizes the location and scale of the multivariate
|
|
normal distribution and then uses `mvstdnormcdf` to call the integration.
|
|
|
|
See Also
|
|
--------
|
|
mvstdnormcdf : location and scale standardized multivariate normal cdf
|
|
'''
|
|
|
|
upper = np.array(upper)
|
|
if lower is None:
|
|
lower = -np.ones(upper.shape) * np.inf
|
|
else:
|
|
lower = np.array(lower)
|
|
cov = np.array(cov)
|
|
stdev = np.sqrt(np.diag(cov)) # standard deviation vector
|
|
# do I need to make sure stdev is float and not int?
|
|
# is this correct to normalize to corr?
|
|
lower = (lower - mu) / stdev
|
|
upper = (upper - mu) / stdev
|
|
divrow = np.atleast_2d(stdev)
|
|
corr = cov / divrow / divrow.T
|
|
# v/np.sqrt(np.atleast_2d(np.diag(covv)))/np.sqrt(np.atleast_2d(np.diag(covv))).T
|
|
|
|
return mvstdnormcdf(lower, upper, corr, **kwds)
|