先写 set.seed()
每次运行都会变的随机抽取,对教学、评分、调试或科研都毫无用处:如果你的模拟今天得出 0.146,明天得出 0.153,报告里该写哪个数?set.seed() 固定了生成器的起点,让之后的整串“随机数”都能精确地重现:
种子相同,抽取的结果就完全相同,无论运行多少次、在哪台机器上。这些数字是伪随机的:它们是一个确定性的序列,经过专门设计,能通过所有随机性统计检验,而种子决定了你从序列的哪个位置开始。种子的值本身没有任何意义,42、7、20260807,随便选一个,记下来就行。要养成的习惯是:在任何用到随机性的脚本顶部写一次 set.seed()。(注意,每次抽取都会推进生成器的状态,所以调用的顺序也会影响可复现性。)
d/p/q/r 体系:一张表看懂整个分布函数库
R 中每个分布函数的名字都是“前缀 + 分布族”,一旦看懂这张表,整个统计函数库你都能读懂:
| 前缀 | 回答的问题 | 正态分布示例 |
|---|---|---|
r | 给我一些随机抽取的值 | rnorm(5) |
d | 密度:曲线在 x 处有多高? | dnorm(0) |
p | 概率:P(X ≤ x) 是多少? | pnorm(1.96) |
q | 分位数:哪个 x 位于这个百分位? | qnorm(0.975) |
p 和 q 互为反函数,你在置信区间中见过的 qt(0.975, df) 就是这一对中的一个:
换个分布族的名字,一切照样适用:runif/dunif/punif/qunif、rbinom/...、rpois/...、rt/...、rexp/...。学会四个前缀,就能用几十种分布。
rnorm():正态分布随机数
rnorm(n, mean, sd) 从钟形曲线中抽取数值,是模拟类似测量值数据的主力工具:
样本的均值和标准差落在 100 和 15 附近,但不会正好等于它们:这个差距就是抽样噪声,n 越大它就越小。最后一行是个值得借鉴的技巧:对逻辑向量求 mean(),得到的就是 TRUE 的比例,而大于 130 的比例接近理论值 1 - pnorm(130, 100, 15) ≈ 2.3%。默认参数是 mean = 0, sd = 1(标准正态分布)。画一张 iq 的直方图,就能看到熟悉的钟形。
runif()、rbinom()、rpois()
再加上三个分布族,就能满足大多数模拟需求:
runif() 把抽取的值均匀地分布在一个范围内(是“uniform”,不是“run if”,每个人都会看错一次)。rbinom(n, size, prob) 模拟 n 次实验,每次实验包含 size 次试验,返回每次实验的成功次数,所以上面的每个值都是 10 次抛硬币中正面朝上的次数。rpois(n, lambda) 生成按已知平均速率独立发生的事件的次数:每小时的客服工单数、每页的错别字数。
sample():抽样与打乱顺序
r* 函数是从分布中凭空生成值,而 sample() 是从你已有的值中抽取:
replace 参数就是关键所在:FALSE(默认)像发牌,每个值只能出现一次,要的数量超过已有的数量就会报错;TRUE 像掷骰子,每次抽取都会重置。从你自己的数据中有放回地抽样,就是 bootstrap 自助法的核心。只传入一个向量时,sample(x) 返回一个随机排列,这是打乱顺序的惯用写法。
对数据框的行抽样,就是在行索引里用 sample():
sample(nrow(mtcars), 5) 随机选出 5 个行号,再用索引取出这些行。这是抽查大数据集或划分训练集和测试集的标准做法。
一个小型蒙特卡洛模拟
整套工具的价值在这里体现出来。问题是:某个过程产生的测量值服从 N(100, 15);你取其中 10 个的平均值,这个平均值超过 105 的概率是多少? 不用推导答案,直接模拟:把实验做 10,000 次,然后数一数:
模拟结果和精确值(约 0.146)只差千分之几。这就是蒙特卡洛方法的全部:把一次试验写成一个函数,用 replicate() 重复几千次,再对成功的次数求 mean()。 这里之所以有精确答案,是因为问题设置得像教科书一样简单;一旦问题变得复杂(奇怪的分布、相关抽取值的最大值、基于规则的游戏),解析的路就走不通了,而模拟的方法照样能用,一点都不用改。注意验证中的 15 / sqrt(10):均值的波动比单个抽取值小,这和决定置信区间宽度的平方根规律是同一个。
适合模拟,不适合加密
有一个边界要注意:R 默认的生成器(Mersenne Twister)是为统计质量和速度设计的,而不是为了保密。给定种子,它的输出是确定的,而且可以根据观察到的输出重建出它的内部状态,对密码、令牌或任何与安全相关的东西来说,这是致命的缺陷。用于模拟、bootstrap 自助法和教学,它非常出色;用于加密,请使用专门的库(例如 openssl 包),绝不要用 sample() 或 runif()。
要点回顾
- 在顶部写一次
set.seed(),所有“随机”结果都能复现:种子相同,抽取的值就相同。 - d/p/q/r 前缀能帮你读懂整个分布函数库:随机抽取、密度、累积概率、分位数。
rnorm(n, mean, sd)、runif(n, min, max)、rbinom(n, size, prob)、rpois(n, lambda)能满足大多数模拟需求。sample()从你自己的值中抽取:replace = TRUE像掷骰子,默认像发牌,不传size就是打乱顺序;df[sample(nrow(df), k), ]对行抽样。- 蒙特卡洛 = 单次试验函数 +
replicate()+mean(),用这个方法能回答数学上不方便直接求解的概率问题。 - R 的随机数生成器是用来做模拟的,不是用来加密的。
下一篇:调试,R 的错误信息到底是什么意思,以及如何阅读 traceback。
常见问题
R 中 set.seed() 有什么作用?
它固定 R 随机数生成器的起点,这样之后的“随机”抽取每次运行都完全相同。在任何用到随机性的脚本顶部调用一次,比如 set.seed(42),你的模拟就可以复现了:同事、评分的人,还有将来的你,看到的都是同样的数字。
R 中怎么生成随机数?
先选分布:rnorm(n, mean, sd) 生成正态分布随机数,runif(n, min, max) 生成均匀分布随机数,rbinom(n, size, prob) 生成成功次数,rpois(n, lambda) 生成事件次数。如果要从已有的值中抽样,用 sample(x, size)。
rnorm、dnorm、pnorm 和 qnorm 有什么区别?
同一个分布,四个前缀:r 抽取随机值,d 给出密度曲线的高度,p 给出累积概率 P(X ≤ x),q 是它的反函数,即给定百分位对应的值。R 认识的每个分布都使用这四个前缀:runif/dunif/punif/qunif、rbinom/dbinom/pbinom/qbinom,依此类推。
R 中怎么从数据框中随机抽取几行?
用 sample() 作为行索引:df[sample(nrow(df), 5), ] 无放回地抽取 5 行。加上 replace = TRUE 就是有放回抽样(这正是 bootstrap 自助法背后的做法)。