在Python中从对数正态分布生成随机数

11
我需要在Python中从对数正态分布产生伪随机数。问题是我从对数正态分布的众数和标准差开始。我没有对数正态分布的均值或中位数,也没有底层正态分布的任何参数。 numpy.random.lognormal需要底层正态分布的均值和标准差。我尝试根据我有的参数计算这些参数,但得到了一个四次函数。它有一个解,但我希望有更简单的方法来做这个。 scipy.stats.lognorm需要我不理解的参数。我不是母语为英语的人,文档看起来很费解。
请帮帮我,谢谢!

1
在你提供的文档中,有这样一段代码:r = lognorm.rvs(s, size=1000),其中s是分布的形状参数。 - Pankaj Daga
2
在我开始工作之前,我想要明确一点:您想要生成具有给定模式和标准差(而不是给定均值和标准差)的随机数。 - Bill Bell
顺便提一下,在英语中,“standard”的拼写是以D结尾,而不是以T结尾。 - zwol
这里的麻烦之处在于,scipy.stats版本的lognorm是基于中位数和标准差进行参数化的,而不是基于众数。我们需要一种方法来用众数和标准差表示scipy版本。确实令人困惑。 - Bill Bell
1
不,我正在尝试制作适合我的需求的随机数据生成器,因此每次生成新数据时我需要设置模式和标准差。 - Bobesh
显示剩余4条评论
3个回答

17

您已经拥有对数正态分布的众数和标准差。要使用scipy的lognorm的rvs()方法,您需要以形状参数s为代价来参数化分布,该参数是底层正态分布的标准差sigma,并且scale是exp(mu),其中mu是底层分布的平均值。

您指出进行这种重新参数化需要解决一个四次多项式方程。为此,我们可以使用numpy.poly1d类。该类的实例具有根属性。

一些代数运算表明,exp(sigma**2)是该多项式的唯一正实根。

x**4 - x**3 - (stddev/mode)**2 = 0

其中stddevmode是对数正态分布的给定标准差和众数,对于此解决方案,scale(即exp(mu))为

scale = mode*x

以下是一个将模式和标准差转换为形状和尺度的函数:

def lognorm_params(mode, stddev):
    """
    Given the mode and std. dev. of the log-normal distribution, this function
    returns the shape and scale parameters for scipy's parameterization of the
    distribution.
    """
    p = np.poly1d([1, -1, 0, 0, -(stddev/mode)**2])
    r = p.roots
    sol = r[(r.imag == 0) & (r.real > 0)].real
    shape = np.sqrt(np.log(sol))
    scale = mode * sol
    return shape, scale
例如,
In [155]: mode = 123

In [156]: stddev = 99

In [157]: sigma, scale = lognorm_params(mode, stddev)

使用计算出的参数生成一个样本:

In [158]: from scipy.stats import lognorm

In [159]: sample = lognorm.rvs(sigma, 0, scale, size=1000000)

这是样本的标准差:

In [160]: np.std(sample)
Out[160]: 99.12048952171304

这里是一些 Matplotlib 代码,用于绘制样本的直方图,并在分布的众数处绘制一条竖直线:

In [176]: tmp = plt.hist(sample, normed=True, bins=1000, alpha=0.6, color='c', ec='c')

In [177]: plt.xlim(0, 600)
Out[177]: (0, 600)

In [178]: plt.axvline(mode)
Out[178]: <matplotlib.lines.Line2D at 0x12c5a12e8>

直方图:

histogram


如果你想使用 numpy.random.lognormal() 生成样本,而不是使用 scipy.stats.lognorm.rvs(),你可以这样做:

In [200]: sigma, scale = lognorm_params(mode, stddev)

In [201]: mu = np.log(scale)

In [202]: sample = np.random.lognormal(mu, sigma, size=1000000)

In [203]: np.std(sample)
Out[203]: 99.078297384090902

我还没有研究过 poly1droots 算法的稳健性,因此请务必测试各种可能的输入值。或者,您可以使用 scipy 中的求解器来解决上述多项式中的 x。您可以使用以下方法限制解的范围:

max(sqrt(stddev/mode), 1) <= x <= sqrt(stddev/mode) + 1

确实非常有帮助。虽然计算出的标准差与给定的标准差一样准确,但我认为这是最好的解决方案。但是让我问一下,有没有办法将标准差的计算误差最小化? - Bobesh
@Bobesh 这并不是真正的“错误”。我理解这个问题是从已知总体模式和标准差的分布中抽取样本。一般来说,样本的统计量不会完全等于总体的统计量。(例如,如果你掷一个六面骰子三次,期望的平均值是3.5,但实际样本的平均值不可能完全是3.5。) - Warren Weckesser
是的,我现在明白了。很抱歉我误解了结果。再次感谢! - Bobesh
仍需要进行一次小的编辑。多项式的期望根必须是实数且大于1,而不是零。这是因为该根的平方根的对数变换必须是实数且大于零。 - Bobesh
如果stddev/mode> 0,则该四次多项式具有两个实根,一个为负数,另一个大于1,因此测试实部是否大于0应该有效。您找到了一个它不起作用的情况吗? - Warren Weckesser

1

对数正态分布(令人困惑地)是将指数函数应用于正态分布的结果。 维基百科给出了参数之间的关系:

mu = log(m/sqrt(1 + v/m^2)), sigma = sqrt(log(1 + v/m^2))

其中,μσ是所谓的“基础正态分布”的平均值和标准偏差,mv是对数正态分布的平均值和方差。

现在,您说您拥有对数正态分布的众数标准偏差。方差v只是标准偏差的平方。从众数到m的过程更加棘手: 再次引用维基百科上的内容,如果均值是exp(mu + sigma^2/2),那么众数就是exp(mu - sigma^2)。通过这个以及上面的内容,我们可以推断出:

log m = log n + 3/2 log (1 + v/m^2)

其中n是对数正态分布的模式,vm如上所述。这可以简化为一个四次方程,

m^8 = n^2m^6 + 3vn^2m^4 + 3n^2v^2m^2 + n^2v^3

或者

u^4 - n^2u^3 - 3vn^2u^2 - 3n^2v^2u - n^2v^3 = 0

其中u = m2。 我怀疑这是您在问题中提到的同一四次方程。 它可以解决,但像大多数四次方程一样,解的根式形式是一个 巨大的乱麻。 对于您的目的,最实用的方法可能是将数字值插入以上内容的 n 和 v ,然后使用 数值求解器找到正根。

抱歉我不能更有帮助。 这真的是一个数学问题,而不是一个编程问题; 您可能会在https://math.stackexchange.com/上获得更有帮助的答案。


因为我不知道那个对数正态分布的平均值和标准差,但是知道那个分布的众数和标准差。 - Bobesh
@Bobesh,你在问题中提到你知道你要采样的对数正态分布的均值和标准差!它们分别是m和*sqrt(v)*。 - zwol
我有些困惑。也许我的英语比我想象的还要糟糕,但是我写的是:我的任务非常简单:我需要在Python中生成来自对数正态分布的伪随机数,并给定该对数正态分布的模式和标准偏差,而不是基础正态分布。 - Bobesh
@Bobesh 我也有点困惑,但我认为我知道如何澄清事情。请回答Bill Bell在你的问题上的评论。 - zwol

0

在@WarrenWeckesser的优秀答案基础上,这里提供一个函数,以模式和标准差的形式提供重新参数化对数正态分布的确切返回值:

import numpy as np
def lognorm_params(mode, stddev):
    a = stddev**2 / mode**2
    x = 1/4*np.sqrt(-(16*(2/3)**(1/3)*a)/(np.sqrt(3)*np.sqrt(256*a**3+27*a**2)-9*a)**(1/3) +
                    2*(2/3)**(2/3)*(np.sqrt(3)*np.sqrt(256*a**3+27*a**2)-9*a)**(1/3)+1) + \
        1/2*np.sqrt((4*(2/3)**(1/3)*a)/(np.sqrt(3)*np.sqrt(256*a**3+27*a**2)-9*a)**(1/3) -
                    (np.sqrt(3)*np.sqrt(256*a**3+27*a**2)-9*a)**(1/3)/(2**(1/3)*3**(2/3)) +
                    1/(2*np.sqrt(-(16*(2/3)**(1/3)*a)/(np.sqrt(3)*np.sqrt(256*a**3+27*a**2)-9*a)**(1/3) +
                                 2*(2/3)**(2/3)*(np.sqrt(3)*np.sqrt(256*a**3+27*a**2)-9*a)**(1/3)+1))+1/2) + \
        1/4
    shape = np.sqrt(np.log(x))
    scale = mode * x
    return shape, scale

本质上,我只是计算了四次方程的精确解。优点是解决方案是a)精确的,b)更快和c)可向量化的。就像@WarrenWeckesser的答案一样,对于给定的模式和SD,此函数返回参数形状和比例,如scipy函数scipy.stats.lognormal()所使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接