【问题标题】:How to get a random value from truncated normal distribution with mean and std in JavaScript?如何从 JavaScript 中的均值和标准差的截断正态分布中获取随机值?
【发布时间】:2020-06-09 20:06:30
【问题描述】:

相同 Python 实现的 JS 替代方案是什么?

import matplotlib.pyplot as plt
from scipy.stats import truncnorm
import numpy as np
mean = 1
std = 2
clip_a = -4
clip_b = 3

a, b = (clip_a - mean) / std, (clip_b - mean) / std
x_range = np.linspace(-3 * std, 3 * std, 1000)
plt.plot(x_range, truncnorm.pdf(x_range, a, b, loc = mean, scale = std));

我想根据分布得到一个随机值(在JS中与size=1相同的代码):

dist = truncnorm.rvs(a, b, loc = mean, scale = std, size=1000000)
plt.hist(dist);

【问题讨论】:

  • 究竟是哪个python实现?绘制图表?
  • @Bergi 感谢您的帮助。不,不是图表。我绘制图表仅用于说明目的。我想根据分布得到一个随机值。我展示了一个截断的正态分布(此代码可以复制粘贴到 Colab),其中用户选择 mean, std, clip_aclip_b 的任何值并获得一个随机值。
  • @Bergi 你认为这不可能吗?
  • 不,为什么不可能?你可以在网上搜索找到类似scipy.stats的js库,也可以看一下他们的源码,自己用javascript重新实现。
  • 我没想到会找到一个 JS one 班轮来完成这项任务,但希望不要从头开始。

标签: javascript normal-distribution


【解决方案1】:

这是一个 JS 函数,它实现了一个截断的、偏斜正态的伪随机数生成器 (PRNG)。它基于 Tom Liao 的 this blog post,并已扩展到考虑上下限(截断)。

本质上,该函数被递归调用,直到找到所需范围内的变量。
您可以使用rng 属性传递您自己的随机数生成器,但默认情况下将使用Math.random。此外,由于您没有要求偏正态分布,因此您可以忽略偏斜属性,因为它默认为 0。这将给您留下截断的正态 PRNG,正如您所要求的那样。

function randomTruncSkewNormal({
  rng = Math.random,
  range = [-Infinity, Infinity],
  mean,
  stdDev,
  skew = 0
}) {
  // Box-Muller transform
  function randomNormals(rng) {
    let u1 = 0,
      u2 = 0;
    //Convert [0,1) to (0,1)
    while (u1 === 0) u1 = rng();
    while (u2 === 0) u2 = rng();
    const R = Math.sqrt(-2.0 * Math.log(u1));
    const Θ = 2.0 * Math.PI * u2;
    return [R * Math.cos(Θ), R * Math.sin(Θ)];
  }

  // Skew-normal transform
  // If a variate is either below or above the desired range,
  // we recursively call the randomSkewNormal function until
  // a value within the desired range is drawn
  function randomSkewNormal(rng, mean, stdDev, skew = 0) {
    const [u0, v] = randomNormals(rng);
    if (skew === 0) {
      const value = mean + stdDev * u0;
      if (value < range[0] || value > range[1])
        return randomSkewNormal(rng, mean, stdDev, skew);
      return value;
    }
    const sig = skew / Math.sqrt(1 + skew * skew);
    const u1 = sig * u0 + Math.sqrt(1 - sig * sig) * v;
    const z = u0 >= 0 ? u1 : -u1;
    const value = mean + stdDev * z;
    if (value < range[0] || value > range[1])
      return randomSkewNormal(rng, mean, stdDev, skew);
    return value;
  }

  return randomSkewNormal(rng, mean, stdDev, skew);
}

按如下方式调用该函数

const data = [];
for (let i = 0; i < 50000; i++) {
  data.push({
    x: i,
    y: randomTruncSkewNormal({ 
      range: [-4,3], 
      mean: 1, 
      stdDev: 2
    })
  });
}

并使用您选择的图表库绘制数据应该可以提供您想要的输出。

我还制作了一个small Observable notebook 以交互方式演示您可能还想查看的功能。

【讨论】: