PostgreSQL中的Beta和lognorm发行版?

时间:2018-12-08 23:15:32

标签: postgresql statistics montecarlo

我目前正在代码中运行相当大的蒙特卡洛模拟,而性能则有待改善。

我想知道是否有一种方法可以直接在数据库上运行,我认为性能会更好。我可以生成随机数,但没有看到统计分布函数。

已经对我有很大帮助的第一步是:

我有一张参数表,其中每一行都是一个beta分布及其所有参数。我想使用这些分布参数生成随机值,并将其存储在单独的表中(蒙特卡罗模拟表,每次模拟运行一行)。

我该怎么办?

1 个答案:

答案 0 :(得分:2)

方法论

您已经指出,PostgreSQL可以使用random()函数来生成Uniform发行版。

对此问题的一般回答是Inverse Transform Sampling。 这种方法的局限性是:

也就是说,只要分位数函数是显式的,并且我们能够使用PostgreSQL数学函数来构造它,那么我们就可以使用PPF(u) = CDF^(-1)(u) | u = CDF(x) = int(PDF(x), x=(-infinty,x))作为统一PRG为特定分布创建一个Pseudo Random Generator

简单的例子:指数

Exponential Distribution的逆变换采样效果很好:

random()

此函数从参数CREATE OR REPLACE FUNCTION expon(N INTEGER, l FLOAT = 1) RETURNS SETOF FLOAT AS $BODY$ SELECT -(1/l)*ln(1 - random()) FROM generate_series(1, N) AS i; $BODY$ LANGUAGE SQL; 的指数分布中生成N个样本。

对数正态

对于Lognormal distribution,分位数功能依赖于Error Function,而PostgreSQL中未实现。因此,我们需要实现缺少的功能(这是一个整体,使用WINDOWING functions并非不可能,但可能不是最好的主意)或寻找另一种方法。

幸运的是,我们可以使用Normal Distribution生成Box-Muller Transform个样本:

l

以下通话:

CREATE OR REPLACE FUNCTION norm(N INTEGER, mu FLOAT = 0, sigma FLOAT = 1)
RETURNS SETOF FLOAT AS
$BODY$
SELECT
    sigma*sqrt(-2.*ln(random()))*cos(2*pi()*random()) + mu
FROM
    generate_series(1, N) AS i;
$BODY$
LANGUAGE SQL;

礼物:

enter image description here

并且MLE返回的SELECT norm(10000); 还不错,我们可能步入正轨。

然后我们可以对该函数取指数:

(mu=0.021131501222537274, sigma=1.0042820700537662)

我们有一个PRG用于对数正态分布。

以下通话:

CREATE OR REPLACE FUNCTION lognorm(N INTEGER, mu FLOAT = 0, sigma FLOAT = 1)
RETURNS SETOF FLOAT AS
$BODY$
SELECT
    exp(x)
FROM
    norm(N, mu, sigma) AS x;
$BODY$
LANGUAGE SQL;

也给出可接受的结果:

enter image description here

MLE返回SELECT lognorm(10000);

数值积分和误差函数

尽管它可能没有性能,但是使用Trapezoid Rule在PostgreSQL中估计错误函数非常容易。认为这是一个幼稚的实现:

(sigma=0.9996878296400589, loc=0.0, exp(mu)=1.0002728392916154)

如果我们将结果与精确形式(Python,scipy)进行比较,那么看起来我们至少可以得到6位有效数字:

CREATE OR REPLACE FUNCTION erf(x FLOAT, dx NUMERIC = 1e-3)
RETURNS FLOAT AS
$BODY$
WITH
D AS (
SELECT
    y::FLOAT,
    exp(-((y::FLOAT)^2)) AS fx0,
    LEAD(exp(-((y::FLOAT)^2))) OVER(ORDER BY y) AS fx1
FROM
    generate_series(0, x::NUMERIC, dx) AS y
)
SELECT
    COALESCE((2/sqrt(pi()))*SUM((D.fx1 + D.fx0)*dx::FLOAT/2), 0.)
FROM D;
$BODY$
LANGUAGE SQL IMMUTABLE;

enter image description here

因此我们可以像对指数一样使用 x psql scipy errabs errrel 0 0.0 0.000000 0.000000 0.000000e+00 NaN 5 0.5 0.520500 0.520500 -7.323189e-08 -1.406953e-07 10 1.0 0.842701 0.842701 -6.918458e-08 -8.209863e-08 15 1.5 0.966105 0.966105 -2.973257e-08 -3.077571e-08 20 2.0 0.995322 0.995322 -6.888995e-09 -6.921371e-09 25 2.5 0.999593 0.999593 -9.076190e-10 -9.079885e-10 30 3.0 0.999978 0.999978 -6.962642e-11 -6.962795e-11 35 3.5 0.999999 0.999999 -3.149592e-12 -3.149594e-12 40 4.0 1.000000 1.000000 -8.404388e-14 -8.404388e-14 45 4.5 1.000000 1.000000 1.110223e-16 1.110223e-16 50 5.0 1.000000 1.000000 2.442491e-15 2.442491e-15 函数对Normal和Lognormal进行逆变换采样,但是我可能不是一个好主意。由于算法复杂性和集成不正确性,它的性能应会很差。

测试版

不幸的是,逆变换采样似乎不适用于Beta Distribution,因为不能将分位数函数表示为简单函数:它需要获得Regularized Incomplete Beta Function的逆。我不知道是否有可能:Wikipedia没有引用Beta分发的分位数功能。

在这种情况下,您可能需要使用某种编程语言(例如C / C ++)编译该函数并将其绑定到PostgreSQL函数,如@Nick Barnes在其注释中建议的那样。

技术注意事项

正如@Nick Barnes在其评论中指出的那样:

  • 使用erf的函数不是random()(默认为IMMUTABLE,因为它们会更改PostgreSQL PRG的种子值;
  • 此处介绍的当前实现是幼稚的,它们无法处理诸如VOLATILE;这样的极端情况。
  • ln(0.)中的函数通常运行良好(尽管我们必须考虑它们的复杂性和收敛性);
  • 像在以前版本的SQL函数中一样,返回LANGUAGE SQL比使用SETOF FLOAT更好,并且避免了使用FLOAT[]的需要;
  • 尽可能限制诸如unnest()之类的演员表;
  • 有一个功能::FLOAT,无需使用2.*acos(0.)进行评估。