第 2 章 数字

2.1 生成随机数

2.1.1 问题

你想要生成随机数。

2.1.2 方案

要生成均匀分布的随机数,可以使用 runif() 函数,它的默认范围是从 0 到 1。

runif(1)
#> [1] 0.5766

# 得到 4 个数字的向量
runif(4)
#> [1] 0.35106 0.72683 0.07206 0.60731

# 得到 3 个范围在 0 到 100 的随机数向量
runif(3, min = 0, max = 100)
#> [1] 64.48362  0.04288  4.28471

# 得到 3 个范围在 0 到 100 的整数 使用 max=101
# 因为它永远不可能等于 101,这里利用 floor
# 函数实现向下取整数
floor(runif(3, min = 0, max = 101))
#> [1] 70 29 93

# 以下方式得到的结果一样
sample(1:100, 3, replace = TRUE)
#> [1] 76 15  8

# 生成无重复的随机整数
sample(1:100, 3, replace = FALSE)
#> [1] 63 33 36

要生成服从正态分布的数字,使用 rnorm() 函数,该函数默认均值是 0,标准差是 1。

rnorm(4)
#> [1]  0.73708 -1.50965 -0.62844 -0.09321

# 使用不同的均值和标准差
rnorm(4, mean = 50, sd = 10)
#> [1] 50.26 43.97 56.63 37.10

# 通过直方图检查分布是否正确
x <- rnorm(400, mean = 50, sd = 10)
hist(x)

2.1.3 注意

如果你想要生成随机数的结果可重复,参阅生成可重复的随机序列

2.2 生成可重复的随机序列

2.2.1 问题

你想要生成可重复的随机数序列。

2.2.2 方案

使用 set.seed() 函数,并在括号内放入数字作为种子数。

set.seed(423)
runif(3)
#> [1] 0.1090 0.5973 0.9726

set.seed(423)
runif(3)
#> [1] 0.1090 0.5973 0.9726

2.3 保持随机数生成器的状态

2.3.1 问题

你想要保存和恢复随机数生成器的状态。

2.3.2 方案

.Random.seed 保存到其他变量,之后将变量值赋给 .Random.seed 从而恢复原来的值。

# 这个例子中,先设定随机数种子
set.seed(423)
runif(3)
#> [1] 0.1090 0.5973 0.9726

# 保存种子
oldseed <- .Random.seed

runif(3)
#> [1] 0.7974 0.2278 0.5190

# 做其他随机数生成相关的事情,比如: runif(30) ...
# 恢复种子
.Random.seed <- oldseed

# 保存种子之后,得到与之前相同的随机数
runif(3)
#> [1] 0.7974 0.2278 0.5190

如果你之前还没有在 R 线程中用过随机数生成器,变量 .Random.seed 将不会存在。如果你对此不确定,应当在保存和恢复之前进行检查:

oldseed <- NULL
if (exists(".Random.seed")) oldseed <- .Random.seed

# 做一些随机数生成操作,比如: runif(30) ...

if (!is.null(oldseed)) .Random.seed <- oldseed

2.3.2.1 在函数中保存和恢复随机数生成器的状态

如果你试图在函数中通过使用 .Random.seed <- x 来恢复随机数生成器的状态,结果是行不通的,因为这个操作改变的是名为 .Random.seed 的本地变量,而不是全局环境中的这个变量。

这里有两个例子。这些函数想要做的是生成一些随机数,并使得随机数生成器保留未改变的状态。

# 这是个坏的版本
bad_rand_restore <- function() {
  if (exists(".Random.seed"))
    oldseed <- .Random.seed else oldseed <- NULL

  print(runif(3))

  if (!is.null(oldseed))
    .Random.seed <- oldseed else rm(".Random.seed")
}


# 这是个好的版本
rand_restore <- function() {
  if (exists(".Random.seed", .GlobalEnv))
    oldseed <- .GlobalEnv$.Random.seed else oldseed <- NULL

  print(runif(3))

  if (!is.null(oldseed))
    .GlobalEnv$.Random.seed <- oldseed else rm(".Random.seed", envir = .GlobalEnv)
}


# 坏的版本没有正确地重置随机数生成器状态,因此随机数一直在改变
set.seed(423)
bad_rand_restore()
#> [1] 0.1090 0.5973 0.9726
bad_rand_restore()
#> [1] 0.7974 0.2278 0.5190
bad_rand_restore()
#> [1] 0.6929 0.8104 0.1019

# 好的版本每次都正确地重置了随机数生成器的状态,因此随机数可以保持一致
set.seed(423)
rand_restore()
#> [1] 0.1090 0.5973 0.9726
rand_restore()
#> [1] 0.1090 0.5973 0.9726
rand_restore()
#> [1] 0.1090 0.5973 0.9726

2.3.2.2 注意

使用者最好不要修改 .Random.seed 变量。

2.4 对数值取整

2.4.1 问题

你想要对数值取整。

2.4.2 方案

存在许多种取整的方式:向最近的整数取整,向上或向下取整或者向 0 取整。

x <- seq(-2.5, 2.5, by = 0.5)

# 向最近的整数取整, 含有 .5 的值取整到最近一个偶数。
round(x)
#>  [1] -2 -2 -2 -1  0  0  0  1  2  2  2

# 向上取整
ceiling(x)
#>  [1] -2 -2 -1 -1  0  0  1  1  2  2  3

# 向下取整
floor(x)
#>  [1] -3 -2 -2 -1 -1  0  0  1  1  2  2

# 向 0 取整
trunc(x)
#>  [1] -2 -2 -1 -1  0  0  0  1  1  2  2

也可以近似到其它小数位:

x <- c(0.001, 0.07, 1.2, 44.02, 738, 9927)

# 1 位小数近似
round(x, digits = 1)
#> [1]    0.0    0.1    1.2   44.0  738.0 9927.0

# 10 位取整
round(x, digits = -1)
#> [1]    0    0    0   40  740 9930

# 向最近的 5 的倍数近似
round(x/5) * 5
#> [1]    0    0    0   45  740 9925

# 向最近的 .02 的倍数近似
round(x/0.02) * 0.02
#> [1]    0.00    0.08    1.20   44.02  738.00 9927.00

2.5 比较浮点数

2.5.1 问题

浮点数比较结果通常未能如你所想。比如:

0.3 == 3 * 0.1
#> [1] FALSE

(0.1 + 0.1 + 0.1) - 0.3
#> [1] 5.551e-17

x <- seq(0, 1, by = 0.1)
x
#>  [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

10 * x - round(10 * x)
#>  [1] 0.000e+00 0.000e+00 0.000e+00 4.441e-16 0.000e+00
#>  [6] 0.000e+00 8.882e-16 8.882e-16 0.000e+00 0.000e+00
#> [11] 0.000e+00

2.5.2 方案

不存在通用的解决方案,因为这个问题通常是由于非整数(浮点数)在计算机和 R 中的存储方式所导致的。可以通过网址 http://www.mathworks.com/support/tech-notes/1100/1108.html 查阅更多信息。虽然里面使用 Matlab 代码写的,但是基本与 R 一致。