【发布时间】:2016-11-11 23:02:01
【问题描述】:
如何使用 Clojure 生成值的正态分布?实际上不一定是真正的正态分布,而是可以偏斜的分布。
作为一个例子,我想创建一个函数,输出一个生成的(伪随机)数字,用于空气中氧气的体积浓度。可能的最低输出应为 19.5%,最大可能为 23.5%,而模态值应为 20.95%。该函数应该适用于这种“偏态正态”分布,其中尾部的下部具有 1.45% 的范围,而尾部的较高部分具有 2.55% 的范围。
【问题讨论】:
标签: clojure
如何使用 Clojure 生成值的正态分布?实际上不一定是真正的正态分布,而是可以偏斜的分布。
作为一个例子,我想创建一个函数,输出一个生成的(伪随机)数字,用于空气中氧气的体积浓度。可能的最低输出应为 19.5%,最大可能为 23.5%,而模态值应为 20.95%。该函数应该适用于这种“偏态正态”分布,其中尾部的下部具有 1.45% 的范围,而尾部的较高部分具有 2.55% 的范围。
【问题讨论】:
标签: clojure
您可以使用 Incanter 的 sample-normal 生成(非偏斜)正态分布数字。例如,这将生成 20 个均值为 2,标准差为 5 的正态分布值:
(ns foo
(:use [incanter.stats]))
(sample-normal 20 :mean 2 :sd 5)
您还可以使用 Java 的 Random 或 Java 中更好的随机数生成器,例如 Sean Luke 的 MersenneTwisterFast。例如,要使用MersenneTwisterFast,请下载 java 源文件并将其放入 e.g.在 Leiningen 项目树中的 src/java 中。然后加
像:java-source-paths ["src/java"] 这样的行到project.clj。在 Clojure 源文件中:
(ns foo
(:import [ec.util MersenneTwisterFast]))
(defn make-rng
"Make an instance of a MersenneTwisterFast RNG and flush out its initial
minimal lack of entropy."
[seed]
(let [rng (MersenneTwisterFast. seed)]
(dotimes [_ 1500] (.nextInt rng)) ; needed because of a quirk of Mersenne Twisters
rng))
(def my-rng (make-rng 42))
;; Now call this as many times as you want to generate standard Normal data:
(.nextGaussian my-rng)
使用 Java 的 Random 是类似的,无需下载并移动到 src/java 等,但我认为您不需要用 1500 .nextInts 刷新 Java 的 Random(尽管您的随机数可能不像 Mersenne Twister 中的随机数那样随机)。
正如this answer at stats.SE 所解释的,您可以通过将上述值乘以所需的标准差,然后加上所需的均值,从标准正态分布中得到任意正态分布。
Alan Thompson 的回答解释了如何从正态分布生成截断分布。
This answer at stats.SE 提供有关从正态分布生成偏态正态分布的建议。
编辑 (2021):SciCloj 社区项目here 列出了一些值得探索的其他库。我没有使用过这些库中的大多数,所以我无法提供更详细的建议,但似乎值得一提。
【讨论】:
如果您可以绘制描述概率密度函数的函数y=f(x),那么有一种简单的方法可以获得您想要的任何分布。
对于高斯,这个函数是 f(x)=exp( -(x-m)^2 / (2 * s^2) ) / sqrt(2pi s^2) (见https://en.wikipedia.org/wiki/Gaussian_function)
其中 m 是 x 的平均值,s 是 x 的标准差。
对于 m=0 和 s=1 的“正常”高斯,在 +/-3 之外有“几乎没有”的值(作为练习留给读者的确切数量)。给定这个近似值,获得高斯分布的最简单方法是在区间 [-3..3] 中生成一个 x 浮点值并在区间 [0..1] 中生成一个 y 值。然后按上述方法计算 f(x):exp(...) 等。然后,IFF y
虽然这种技术会丢弃一些(或许多)值,但它非常简单且防弹。
您可以对“偏斜高斯”近似值使用类似的方法,只需按照您的描述定义您自己的 f(x)。对于一个非常简单的近似值,您甚至可以使用从 (19.5,0) 到 (20.95,1) 到 (23.5,0) 的直线近似值,其中 f(x) 形成三角形。在这种情况下,在区间 [19.5..23.5] 中绘制 x,并计算 f(x) 左右两半的直线公式。像以前一样在 [0..1] 中绘制 y。
我刚刚发现*对此进行了更详细的描述:https://en.wikipedia.org/wiki/Rejection_sampling
更新:
如果您只想要高斯随机变量(或其他常见分布),您可以使用Apache Commons Math library。
【讨论】:
此实现受到 Alan Thompson 回答中的两个想法的启发:“拒绝采样”和使用三角形,而不是钟形曲线形状,即缩放。
(defn generator [modal-val low-val high-val]
(fn []
(let [gen-val (fn []
(let [diff-range (- high-val low-val)
picked-in-range (rand diff-range)
perhaps-res (+ low-val picked-in-range)
;; partial distance left or right, that will be negative if to left
modal-delta (- perhaps-res modal-val)
extremity (if (neg? modal-delta) low-val high-val)
;; full distance left or right, that will be negative if to left
total-dist (- extremity modal-val)
closeness-to-modal (- 1 (/ modal-delta total-dist))
]
(when (<= (rand) closeness-to-modal)
perhaps-res)))]
(first (drop-while nil? (repeatedly gen-val))))))
可以这样使用:
((generator 20.95 19.5 23.5))
【讨论】:
rand 是基于 Java 的 Random,所以如果后者对于特定用途来说不是一个足够高质量的随机数生成器,那么 rand 将不会是任何一个。不过,在许多情况下,这已经足够了。