2.2 数学运算符和向量

运算符+执行加法,它还有一个特殊技巧:除了把两个数字相加之外,还可把两个向量相加。向量是数值的有序集,它在统计学中极其重要,因为通常的分析对象是整个数据集,而不仅是一条数据。

在上一章你已经看到,冒号运算符:能创建一个从某个数值开始到另一个数值结束的序列。而函数则会把一系列的值拼接起来创建向量(这里c是concatenate的第一个字母,concatenate是一个拉丁词,意思是“把所有东西连接在一起”)。

R中的变量名是区分大小写的,因此下例中我们要留心一点。大写的C函数与小写的c函数作用完全不同1:

1有一些其他的名称冲突:filterFilterfindFindgammaGammanrow/ncolNROW/NCOL。因为R是一种不断演进而非一蹴而就的语言,这是一个令人遗憾的副作用。

  1. 1:5 + 6:10 #look, no loops!
  2. ## [1] 7 9 11 13 15
  3. c(1, 3, 6, 10, 15) + c(0, 1, 3, 6, 10)
  4. ## [1] 1 4 9 16 25

小技巧

冒号运算符和c函数在R代码中几乎无处不在,好好练习使用它们吧。现在,试试创建你自己的向量。

如果要用C或Fortran语言编写代码,我们需要编写一个循环语句来为向量中的每个元素执行加法。R的向量化加法操作符把事情简单化了,使我们无需使用循环语句。2.5节将对向量作更多的介绍。

在R中,向量化有几种含义,其中最常见的含义是:运算符或函数能作用于向量中的每个元素,而无需显式地编写循环语句(这种内置的基于元素的隐式循环也远远快于显式地写循环语句)。向量化的第二个含义是:当一个函数把一个向量作为输入时,能计算汇总统计:

  1. sum(1:5)
  2. ## [1] 15
  3. median(1:5)
  4. ## [1] 3

向量化的第三种含义不太常见,即参数的向量化,例如当函数根据输入参数计算汇总统计时。sum函数就是这样,不过这非常特殊。而median函数则不是这样:

  1. sum(1, 2, 3, 4, 5)
  2. ## [1] 15
  3. median(1, 2, 3, 4, 5) #这会抛出错误
  4. ## Error: unused arguments (3, 4, 5)

在R中,不只是加号(+),其他所有算术运算符都是向量化的。下例将演示减法、乘法、幂运算、两种除法及余数。

  1. c(2, 3, 5, 7, 11, 13) - 2  # 减法
  2. ## [1] 0 1 3 5 9 11
  3. -2:2 * -2:2 # 乘法
  4. ## [1] 4 1 0 1 4
  5. identical(2 ^ 3, 2 ** 3) # 我们可用^或**代表求幂
  6.            # 尽管用^更加普遍 
  7. ## [1] TRUE
  8. 1:10 / 3 # 浮点数除法
  9. ## [1] 0.3333 0.6667 1.0000 1.3333 1.6667 2.0000 2.3333 2.6667 3.0000 3.3333
  10. 1:10 %/% 3 # 整数除法
  11. ## [1] 0 0 1 1 1 2 2 2 3 3
  12. 1:10 %% 3 # 余数
  13. ## [1] 1 2 0 1 2 0 1 2 0 1

R还包含了多种数学函数。有三角函数(sincostan,以及相反的asinacosatan)、对数和指数(logexp,以及它们的变种log1pexpm1,这两个函数对那些非常小的x值能更加精确地计算log(1 + x)exp(x - 1)的值),以及几乎所有其他你能想到的数学函数。请参考下例并加以理解。请再次注意,所有的函数都作用于向量,而不仅仅是单个值。

  1. cos(c(0, pi / 4, pi / 2, pi)) #pi是内置常数
  2. ## [1] 1.000e+00 7.071e-01 6.123e-17 -1.000e+00
  3. exp(pi * 1i) + 1 #欧拉公式
  4. ## [1] 0+1.225e-16i
  5. factorial(7) + factorial(1) - 71 ^ 2 #5041是一个大数字
  6. ## [1] 0
  7. choose(5, 0:5)
  8. ## [1] 1 5 10 10 5 1

要比较整数值是否相等请使用==而不是单个等号=,因为我们将会看到,单个等号另有用途。正如算术运算符一样,==和其他关系运算符都是向量化的。要检查是否不相等,“不等”运算符为!=。你可能已经猜到大于和小于号就是><(如果有可能相等,则使用>=<=)。以下是几个例子:

  1. c(3, 4 - 1, 1 + 1 + 1) == 3 # 操作符也是向量化的
  2. ## [1] TRUE TRUE TRUE
  3. 1:3 != 3:1
  4. ## [1] TRUE FALSE TRUE
  5. exp(1:5) < 100
  6. ## [1] TRUE TRUE TRUE TRUE FALSE
  7. (1:5) ^ 2 >= 16
  8. ## [1] FALSE FALSE FALSE TRUE TRUE

使用==来比较非整型变量可能会带来问题。到目前为止。我们处理的所有数字都是浮点数。这意味着,对于两个数ab来说,它们可存储为a * 2 ^ b。由于它们都以32位存储,所以只能是一个近似值。这意味着舍入误差(rounding error)会常常潜伏在你的计算之中,你预期的答案可能是完全错误的。有很多书专门介绍这个主题,由于内容较多,这里就不过多介绍了。这是个常见的错误,R中的FAQ上有一个关于它的条目(http://bit.ly/17jZFfE),便于你深入了解。

看以下两个数字,它们应该是相同的。

  1. sqrt(2) ^ 2 == 2 #sqrt是函数的平方根
  2. ## [1] FALSE
  3. sqrt(2) ^ 2 - 2 #这个微小的差值就是舍入误差
  4. ## [1] 4.441e-16

R还提供了all.equal函数用于检查数字是否相等。它提供了一个容忍度(tolerance level,默认情况下为1.5e-8),因而那些小于此容忍度的舍入误差将被忽略:

  1. all.equal(sqrt(2) ^ 2, 2)
  2. ## [1] TRUE

如果要比较的值不一样,all.equal返回时将报告其差值。如果你需要它们在比较后返回的是一个TRUEFALSE值,则应把all.equal函数嵌入isTRUE函数中调用:

  1. all.equal(sqrt(2) ^ 2, 3)
  2. ## [1] "Mean relative difference: 0.5"
  3. isTRUE(all.equal(sqrt(2) ^ 2, 3))
  4. ## [1] FALSE

小技巧

要检查两个数字是否一样,不要使用==,而使用all.equal函数。

我们也可以使用==来比较字符串。在这种情况下,比较会区分大小写,所以字符串必须完全匹配。理论上,也可以使用大于或小于(><)来比较字符串:

  1. c(
  2. "Can", "you", "can", "a", "can", "as",
  3. "a", "canner", "can", "can", "a", "can?"
  4. ) == "can"
  5. ## [1] FALSE FALSE TRUE FALSE TRUE FALSE FALSE FALSE TRUE TRUE FALSE ## [12] FALSE
  6. c("A", "B", "C", "D") < "C"
  7. ## [1] TRUE TRUE FALSE FALSE
  8. c("a", "b", "c", "d") < "C" # 你的结果可能有所不同
  9. ## [1] TRUE TRUE TRUE FALSE

然而在实际中,后一种方式总相当蹩脚,因为结果取决于你的语言环境(不同文化中充满了奇怪的字母排序规则,例如在爱沙尼亚语中,“z”排在“s”和“t”之间)。13.2节将讨论更强大的字符串匹配功能。

小技巧

在帮助页面中?Arithmetic?Trig?Special?Comparison有更多的例子,并提供边界情况下的大量处理细节。(如有兴趣,请试试0 ^ 0或使用整数除以非整数。)