Python 具有可变上限的双变量正态 CDF

Python Bivariate Normal CDF with variable upper bound

我正在尝试找到一种优雅的方法来计算具有 python 的双变量正态 CDF,其中 CDF 的一个上限是两个变量的函数,其中一个是双变量正态密度的变量(积分变量)。

示例:

from scipy import integrate
import numpy as np

# First define f(x, y) as the bivariate normal distribution with fixed correlation p
p = 0.4
def f(x, y):
    Q = x**2 + y**2 - 2*p*x*y
    return 1/(2*np.pi*np.sqrt(1-p**2))*np.exp(-1/(2*(1-p**2))*Q)

# Define N2(a, b) as the cumulative bivariate normal distribution f where a is constant 
# and b is a function of two variables
def N2(a, b):
    prob, error = integrate.dblquad(f, np.NINF, a, lambda x: np.NINF, b)
    return prob

# Upper bound function of 2 variables example where x is an integral variable
def upper_bound(x, v):
    return 0.5*v*x

# My approach which doesn't work
# Calculate bivariate normal CDF for different values of v in the upper bound
results = [N2(1, upper_bound(x, v)) for v in np.linspace(0.01, 4, 200)]

关于如何改变我的方法的任何想法,以便调用 upper_bound(x, v) results = [N2(1, upper_bound(x, v)) for v in np.linspace(0.01, 4, 200)] 可以吗?也欢迎其他解决问题的方法。

编辑:这是我要计算的积分,其中 f(x,y) 是二元正态密度函数。请注意,我要计算的实际上限 f(x,v) = 0.5*v*x 要复杂得多,这只是一个示例,因此我不想以符号方式计算它,例如使用 sympy。此外,我的目标是计算数百个不同 v 值的积分。

积分:

虽然速度很慢,但这种方法似乎有效。

前几行,直到 'this should produce 1',是完整性检查。我想验证我的方法是否能正确计算密度下的体积。确实如此。

我使用 variance-covariance 矩阵来获得所需的 0.4 相关性,避免编写自己的 pdf。

我在两个地方柯里化函数,这样函数只有一个参数。这使得可以将内积分计算为 x 的函数。也使得取v参数'outside'其他计算成为可能。

from toolz import curry
from scipy.stats import multivariate_normal
from scipy.integrate import quad
import numpy as np

@curry
def bivariate(x,y):
    return multivariate_normal.pdf([x,y],cov=[[1,.4],[.4,1]])

def out_y(x):
    marginal = bivariate(x)
    return quad(marginal, np.NINF, np.PINF)[0]

# this should produce 1
print (quad(out_y, np.NINF, np.PINF)[0])

# now to what the OP wants

@curry
def inner_integral(v,x):
    marginal = bivariate(x)
    return quad(marginal, np.NINF, 0.5*v*x)[0]

inner_integral_for_one_v = inner_integral(0.8)
print (quad(inner_integral_for_one_v,np.NINF, 1)[0])

要使用此代码,您需要编写等同于:

for v in range(0,1,0.1):
    inner_integral_for_one_v = inner_integral(v)
    print (quad(inner_integral_for_one_v,np.NINF, 1)[0])

我不得不在 Python 中编写一个使用双变量分布的期权模型。但是,我没有找到一个快速的预建函数 - 有些似乎使用随机 scipy 生成器来模拟它与多元函数。但是......如果你真的深入挖掘并查看其他财务软件包正在使用什么,那么它几乎都是由华盛顿大学的 Alan Genz 一个人编写的所有代码。他几乎用 Fortan 或 MATLAB 编写所有内容,仅此而已。因此,如果您查看具有双变量 CDF 的包,您会在那里找到他的名字和他的代码(实际上我在 MATLAB 中找到了一个旧版本)。 http://www.math.wsu.edu/faculty/genz/software/software.html

那么为什么 SciPy 或 NumPy 还没有内置它,我不知道。但我在几个小时内重写了它,同时使用 MATLAB 和 Python 检查生成的代码。他正在用高斯勒让德正交求解,所以这总是比使用随机数生成器的解决方案快得多:https://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_quadrature

#       Alan Genz - original MATLAB code converted by Matt Slezak to Python
#       http://www.math.wsu.edu/faculty/genz/software/matlab/bvnl.m
#       Department of Mathematics
#       Washington State University
#       This is the bivariate CDF distribution
#
#       dh 1st upper integration limit
#       dk 2nd upper integration limit
#       r   correlation coefficient

import numpy as np
from scipy.stats import norm

def bivariate_cdf(dh, dk, r):
    # dh and dk get signs flipped right away
    dh = float(-dh)
    dk = float(-dk)

    if (dh == np.inf) or (dk == np.inf): 
        return 0
    
    else:
        if dh == - np.inf:
            if dk == - np.inf:
                return 1
            else:
                return norm.cdf(- dk)
        if dk == - np.inf:
            return norm.cdf(- dh)
        else:
            if r == 0:
                return norm.cdf(- dh)*norm.cdf(- dk)
            else:
                    tp=2*np.pi
                    h=dh
                    k=dk
                    hk=h*k
                    bvn=0
                    if abs(r) < 0.3:
                        w=np.array([0.1713244923791705,0.3607615730481384,0.4679139345726904])
                        x=np.array([0.9324695142031522,0.6612093864662647,0.238619186083197])
                    else:
                        if abs(r) < 0.75:
                            w=np.array([0.04717533638651177,0.1069393259953183,0.1600783285433464,0.2031674267230659,0.2334925365383547,0.2491470458134029])
                            x=np.array([0.9815606342467191,0.904117256370475,0.769902674194305,0.5873179542866171,0.3678314989981802,0.1252334085114692])
                        else:
                            w=np.array([0.01761400713915212,0.04060142980038694,0.06267204833410905,0.08327674157670475,0.1019301198172404,0.1181945319615184,0.1316886384491766,0.1420961093183821,0.1491729864726037,0.1527533871307259])
                            x=np.array([0.9931285991850949, 0.9639719272779138, 0.9122344282513259,0.8391169718222188, 0.7463319064601508, 0.6360536807265150,0.5108670019508271,0.3737060887154196,0.2277858511416451,0.07652652113349733])
                    w = np.concatenate((w, w), axis=0)
                    x = np.concatenate((1 - x, 1 + x), axis=0)

                    if abs(r) < 0.925:
                        hs=(h*h+k*k) / 2
                        asr=np.arcsin(r) / 2
                        sn=np.sin(asr*x)
                        bvn=np.dot(np.exp((sn*hk-hs)/(1-sn**2)),w.T)
                        bvn=bvn*asr/tp + norm.cdf(-h)*norm.cdf(-k)

                    else:
                        if r < 0:
                            k=- k
                            hk=- hk
                            
                        if abs(r) < 1:
                            as1=1 - r ** 2
                            a=np.sqrt(as1)
                            bs=(h - k) ** 2
                            asr=- (bs / as1 + hk) / 2
                            c=(4 - hk) / 8
                            d=(12 - hk) / 80

                            if asr > - 100:
                                bvn= a*np.exp(asr)*(1-c*(bs-as1)*(1-d*bs)/3+c*d*as1**2)
                            if hk > - 100:
                                b=np.sqrt(bs)
                                sp=np.sqrt(tp)*norm.cdf(-b/a)
                                bvn=bvn - np.exp(-hk/2)*sp*b*( 1 - c*bs*(1-d*bs)/3 )
                            a=a / 2
                            xs=(a*x) ** 2
                            asr=- (bs / xs + hk) / 2
                            ix=asr > - 100
                            xs=xs[ix]
                            sp=( 1 + c*xs*(1+5*d*xs) )
                            rs=np.sqrt(1 - xs)
                            ep=np.exp(- (hk / 2)*xs / (1 + rs) ** 2) / rs
                            bvn=( a*( np.dot((np.exp(asr[ix])*(sp-ep)),w[ix].T) ) - bvn )/tp

                        if r > 0:
                            bvn=bvn + norm.cdf(- max(h,k))

                        else:
                            if h >= k:
                                bvn=- bvn

                            else:
                                if h < 0:
                                    L=norm.cdf(k) - norm.cdf(h)
                                else:
                                    L=norm.cdf(- h) - norm.cdf(- k)
                                bvn=L - bvn

                    return max(0,min(1,bvn))