scipy 如何在Python中创建对数正态分布?

sr4lhrrt  于 2022-11-10  发布在  Python
关注(0)|答案(2)|浏览(342)

this post之后,我尝试通过创建LogitNormal类来创建一个对数正态分布:

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import logit
from scipy.stats import norm, rv_continuous

class LogitNormal(rv_continuous):
    def _pdf(self, x,**kwargs):
        return norm.pdf(logit(x),**kwargs)/(x*(1-x))

class OtherLogitNormal:
    def pdf(self, x,**kwargs):
        return norm.pdf(logit(x),**kwargs)/(x*(1-x))

fig, ax = plt.subplots()
values = np.linspace(10e-10, 1-10e-10, 1000)
sigma, mu = 1.78, 0
ax.plot(
    values, LogitNormal().pdf(values, loc=mu, scale=sigma), label='subclassed'
)
ax.plot(
    values, OtherLogitNormal().pdf(values, loc=mu, scale=sigma),
    label='not subclassed'
)
ax.legend()
fig.show()

然而,LogitNormal类并没有产生预期的结果。当我没有子类化rv_continuous时,它就可以工作了。为什么呢?我需要子类化来工作,因为我还需要它附带的其他方法,比如rvs
顺便说一句,我在Python中创建自己的对数正态分布的唯一原因是,我能找到的该分布的唯一实现来自PyMC 3包和TensorFlow包,如果你只需要一个函数,这两个包都相当繁重/矫枉过正。我已经尝试过PyMC3,但显然它在scipy中做得不好,我认为,但那是完全不同的故事。

svgewumm

svgewumm1#

前言
这个星期我遇到了这个问题,我发现唯一相关的问题就是这个帖子。我和楼主的要求几乎一样:

但我还需要:

  • 能够进行统计检验;
  • 同时与scipy随机变量接口兼容。

正如@Jacques Gaudin所指出的,rv_continous的接口(详细信息请参见发行版体系结构)在继承此类时无法确保locscale参数的后续操作。这在某种程度上是误导性的和不幸的。
当然,实现__init__方法允许创建缺少的绑定,但代价是:它破坏了当前用于实现随机变量的patternx 1 m6n1x(参见对数正态分布的实现示例)。
因此,我花时间深入研究了scipy代码,并为这个发行版创建了一个MCVE。虽然它不是完全完整的(它主要是错过了时刻覆盖),但它符合OP和我的目的,同时具有令人满意的准确性和性能。
MCVE公司
该随机变量的符合接口的实现可以是:

class logitnorm_gen(stats.rv_continuous):

    def _argcheck(self, m, s):
        return (s > 0.) & (m > -np.inf)

    def _pdf(self, x, m, s):
        return stats.norm(loc=m, scale=s).pdf(special.logit(x))/(x*(1-x))

    def _cdf(self, x, m, s):
        return stats.norm(loc=m, scale=s).cdf(special.logit(x))

    def _rvs(self, m, s, size=None, random_state=None):
        return special.expit(m + s*random_state.standard_normal(size))

    def fit(self, data,**kwargs):
        return stats.norm.fit(special.logit(data),**kwargs)

logitnorm = logitnorm_gen(a=0.0, b=1.0, name="logitnorm")

此实现释放了大多数scipy随机变量的潜力。

N = 1000
law = logitnorm(0.24, 1.31)             # Defining a RV
sample = law.rvs(size=N)                # Sampling from RV
params = logitnorm.fit(sample)          # Infer parameters w/ MLE
check = stats.kstest(sample, law.cdf)   # Hypothesis testing
bins = np.arange(0.0, 1.1, 0.1)         # Bin boundaries
expected = np.diff(law.cdf(bins))       # Expected bin counts

由于它依赖于scipy正态分布,我们可以假设底层函数与正态随机变量对象具有相同的精度和性能,但它可能确实受到浮点运算不准确性的影响,尤其是在处理支持边界上的高度偏斜分布时。
测试次数
为了检验它的表现,我们画出一些兴趣分布并进行检验。让我们创建一些固定装置:

def generate_fixtures(
    locs=[-2.0, -1.0, 0.0, 0.5, 1.0, 2.0],
    scales=[0.32, 0.56, 1.00, 1.78, 3.16],
    sizes=[100, 1000, 10000],
    seeds=[789, 123456, 999999]
):
    for (loc, scale, size, seed) in itertools.product(locs, scales, sizes, seeds):
        yield {"parameters": {"loc": loc, "scale": scale}, "size": size, "random_state": seed}

并对相关分布和样本进行检查:

eps = 1e-8
x = np.linspace(0. + eps, 1. - eps, 10000)

for fixture in generate_fixtures():

    # Reference:
    parameters = fixture.pop("parameters")
    normal = stats.norm(**parameters)
    sample = special.expit(normal.rvs(**fixture))

    # Logit Normal Law:
    law = logitnorm(m=parameters["loc"], s=parameters["scale"])
    check = law.rvs(**fixture)

    # Fit:
    p = logitnorm.fit(sample)
    trial = logitnorm(*p)
    resample = trial.rvs(**fixture)

    # Hypothetis Tests:
    ks = stats.kstest(check, trial.cdf)
    bins = np.histogram(resample)[1]
    obs = np.diff(trial.cdf(bins))*fixture["size"]
    ref = np.diff(law.cdf(bins))*fixture["size"]
    chi2 = stats.chisquare(obs, ref, ddof=2)

使用n=1000, seed=789进行的一些调整(此示例非常正常)如下所示:
第一页第二页

e1xvtsh3

e1xvtsh32#

如果您查看pdf方法的源代码,您会注意到调用_pdf时没有使用scaleloc关键字参数。

if np.any(cond):
        goodargs = argsreduce(cond, *((x,)+args+(scale,)))
        scale, goodargs = goodargs[-1], goodargs[:-1]
        place(output, cond, self._pdf(*goodargs) / scale)

这会导致您覆写的_pdf方法中的kwargs永远是空字典。
如果您更仔细地查看代码,您还会注意到缩放和位置是由pdf处理的,而不是_pdf
在本例中,_pdf方法调用norm.pdf,因此locscale参数必须在LogitNormal._pdf中可用。
例如,在创建LogitNormal的示例时,可以传递scaleloc,并将这些值存储为类属性:

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import logit
from scipy.stats import norm, rv_continuous

class LogitNormal(rv_continuous):
    def __init__(self, scale=1, loc=0):
        super().__init__(self)
        self.scale = scale
        self.loc = loc

    def _pdf(self, x):
        return norm.pdf(logit(x), loc=self.loc, scale=self.scale)/(x*(1-x))

fig, ax = plt.subplots()
values = np.linspace(10e-10, 1-10e-10, 1000)
sigma, mu = 1.78, 0
ax.plot(
    values, LogitNormal(scale=sigma, loc=mu).pdf(values), label='subclassed'
)
ax.legend()
fig.show()

相关问题