import scipy.stats
import math
import numpy
def bisection(function, k, p, a, b, tol):
# http://code.activestate.com/recipes/578417-bisection-method-in-python/
assert (function(a,k)-p)*(function(b,k)-p) < 0 # a, b must bracket root
c = (a+b)/2.0
while (b-a)/2.0 > tol:
if (function(c, k)-p) == 0:
return c
elif (function(a,k)-p)*(function(c,k)-p) < 0:
b = c
else :
a = c
c = (a+b)/2.0
return c
def lower_gamma(s,x):
# lower incomplete gamma function
# https://en.wikipedia.org/wiki/Incomplete_gamma_function#Evaluation_formulae
g = 0
last_g = 1.0
done = False
tol = 1.0e-6
k=0
while not done:
top = pow(x, s)*math.exp(-x)*pow(x,k)
bot = numpy.prod( [float(s+j) for j in range(k+1) ] )
dg = float(top)/float(bot)
if dg == float("Inf"):
break
g += dg
k += 1
if k>100: # get at least 100 terms in the sum
if g==0:
break
delta = abs(dg/g)
if delta == float("Inf"):
break
if delta < tol:
done = True
last_g = g
return g
def chi2_cdf(x, k):
# chi-squared cumulative density function
# cdf(x; k) = lower_gamma(k/2, x/2) / gamma(k/2)
return lower_gamma(k/2.0, x/2.0) / math.gamma(k/2.0)
def chi2_ppf(p, k):
# chi-squared Percent point function (inverse of cdf percentiles).
# look for x such that
# p = chi2_cdf( x=chi2_ppf(p, k), k)
tol = 1e-8
lolim = 0
hilim = k
while (chi2_cdf(lolim,k)-p)*(chi2_cdf(hilim,k)-p) > 0:
hilim *= 1.5
return bisection( chi2_cdf, k, p, lolim, hilim, tol)
print "scipy cdf: ",scipy.stats.chi2.cdf(55, 33)
print "own cdf: ",chi2_cdf(55, 33)
print "scipy ppf ", scipy.stats.chi2.ppf(0.4, 33)
print " own ppf ", chi2_ppf(0.4, 33)
# test that we really found the inverse
print scipy.stats.chi2.cdf(scipy.stats.chi2.ppf(0.4, 33), 33)
print chi2_cdf( chi2_ppf(0.4, 33), 33 )
# try to check the scipy function against our own function
# for some random input of (p, k)
for n in range(100):
k = numpy.random.randint(20, 200)
p = numpy.random.random()
print k, p,
a=scipy.stats.chi2.ppf(p, k)
b=chi2_ppf(p, k)
ok = numpy.isclose(a, b)
if ok:
print ok
else:
print ok, a, b
assert ok |