模拟 R 中下限或上限的约束法线

机器算法验证 r 正态分布 模拟 截断
2022-02-07 20:01:10

我想使用 R 从受约束的正态分布中生成随机数据。

例如,我可能想从正态分布中模拟一个变量,mean=3, sd= 2并且任何大于 5 的值都从相同的正态分布中重新采样。

因此,对于一般功能,我可以执行以下操作。

rnorm(n=100, mean=3, sd=2)

当时我有几个想法:

  • 使用循环迭代一个ifelse函数,该循环重复直到所有值都被限制在边界内。
  • 模拟比要求更多的值并取第一个n满足约束的值。
  • 避免使用矢量化正态变量模拟器,而是使用内部带有 do while 的 for 循环来一次模拟每个观察,并在需要时循环。

以上所有内容似乎都有些笨拙。

问题

  • 什么是一种简单的方法来模拟 R 中的约束随机正态变量,平均值 = 3,sd = 2 和最大值 = 5?
  • 更一般地说,将约束合并到 R 中的模拟变量中的一般方法是什么?
3个回答

这称为截断正态分布:

http://en.wikipedia.org/wiki/Truncated_normal_distribution

克里斯蒂安·罗伯特(Christian Robert)在这里写了一种针对各种情况的方法(根据截断点的位置使用不同的方法):

Robert, CP (1995)“截断正态变量的模拟”,
统计与计算,第 5 卷,第 2 期,6 月,第 121-125 页

论文可在 http://arxiv.org/abs/0907.4010

这讨论了针对不同截断点的许多不同想法。这不是以任何方式接近这些的唯一方法,但它通常具有相当不错的性能。如果你想用不同的截断点做很多不同的截断法线,这将是一个合理的方法。正如您所指出的,msm::tnorm它基于 Robert 的方法,同时truncnorm::truncnorm实现了 Geweke (1991) 的接受-拒绝采样器;这与罗伯特论文中的方法有关。请注意,它以通常的方式msm::tnorm包括密度、cdf 和分位数(逆 cdf)函数。R

较早的方法参考是Luc Devroye 的书自从它绝版以来,他收回了版权并提供了下载。

您的特定示例与对截断为 1 的标准法线进行采样相同(如果是截断点,(-μ)/σ=(5-3)/2=1),然后缩放结果(乘以σ并添加μ)。

在这种特定情况下,罗伯特建议您的想法(在第二个或第三个化身中)是非常合理的。大约 84% 的时间你会得到一个可接受的值,因此生成大约1.19n平均法线(您可以计算出界限,以便使用矢量化算法生成足够的值,比如 99.5% 的时间,然后偶尔生成最后几个效率较低的值 - 甚至一次生成一个)。

这里还讨论了 R 代码中的实现(以及 Rccp 中对同一问题的另一个答案,但那里的 R 代码实际上更快)。那里的纯 R 代码在 6 毫秒内生成 50000 个截断的法线,尽管那个特定的截断法线只切断了极端的尾部,所以更实质性的截断意味着结果更慢。它通过计算应该生成多少以几乎可以肯定获得足够的数量来实现生成“太多”的想法。

如果我多次只需要一种特定类型的截断法线,我可能会考虑采用 ziggurat 方法或类似方法的一个版本来解决问题。

事实上,尼古拉斯肖邦似乎已经这样做了,所以我不是唯一一个想到:

http://arxiv.org/abs/1201.6140

他讨论了其他几种算法,并将他的算法的 3 个版本与其他算法的时间进行比较,以生成 10^8 个随机法线用于各种截断点。

不出所料,他的算法速度相对较快。

从论文中的图表来看,即使是他比较的最慢的算法,在(对他们而言)最差的截断点也会产生108大约 3 秒内的值 - 这表明如果实施得当,那里讨论的任何算法都可能是可接受的。

编辑:这里提到了我不确定的一个(但也许它在其中一个链接中)是转换(通过逆法线 cdf)截断的制服 - 但可以通过简单地在截断范围内生成制服来截断制服. 如果逆正态 cdf 很快,这既快速又简单,并且适用于各种截断点。

继@glen_b 的参考资料后,专注于 R 实现。有几个函数旨在从截断的正态分布中采样:

  • rtruncnorm(100, a=-Inf, b=5, mean=3, sd=2)truncnorm包装中
  • rtnorm(100, 3, 2, upper=5)msm包装中

@Glen_b 建议的使用逆 CDF(分位数函数)的示例

您可以使用runif生成随机分位数,然后将这些分位数传递给 eg qnorm(或任何其他分布)以查找这些分位数对应于给定分布的值。

如果仅在特定区间内生成分位数,则会截断分布。我们可以使用 CDF(例如pnorm)来找到对应于给定截断的分位数的限制。

rtruncnorm <- function(n, mu, sigma, low, high) {
  # find quantiles that correspond the the given low and high levels.
  p_low <- pnorm(low, mu, sigma)
  p_high <- pnorm(high, mu, sigma)
  
  # draw quantiles uniformly between the limits and pass these
  # to the relevant quantile function.
  qnorm(runif(n, p_low, p_high), mu, sigma)
}

samples <- rtruncnorm(1000, 3, 2, low = -Inf, high = 5)

max(samples)
#> [1] 4.996336

hist(samples)