2.6 向量化运算符
假设我们希望对向量x中的每一个元素使用函数f()。在很多情况下,我们可以简单地对x调用f()就能完成。
这可以简化我们的代码,不仅如此,还能将代码运行效率显著提高到数百倍甚至更多。
提高R代码执行速度的有效方法之一是向量化(vectorize),这意味着应用到向量上的函数实际上应用在其每一个元素上。
2.6.1 向量输入,向量输出
之前在本章你已经看到向量化运算的一些例子,即+和*运算符。另一个例子是>。
在这里,>函数分别运用在u[1] 和v[1],得到结果TRUE,然后是u[2] 和 v[2],得到结果FALSE,以此类推。
关键在于,如果一个函数使用了向量化的运算符,那么它也被向量化了,从而使速度提升成为可能。下面有一个例子:
在这里,w()使用了向量化的运算符+,从而w()也是向量化的。正如你看到的,存在无数个向量化的函数,因为用简单的向量化函数可以构建更复杂的函数。
注意,甚至超越函数—(平方根、对数、三角函数等)也是向量化的。
这也适用于其他许多内置的R函数。例如,让我们对向量y应用round()函数,其作用是四舍五入到最近整数:
上例的关键在于,round()函数能应用到向量y中的每一个元素上。记住,标量实际上是一元向量,所以通常情况下对单个数值使用round()函数,只是一种特殊情形。
在这里,我们使用内置的函数round(),但用你自己编写的函数同样可以做到这点。
正如前面提到过的,诸如+这样的运算符实际上也是函数。例如,考虑下面的代码
上例中三元向量的每个元素都加上了4,原因在于+实际上也是函数!下面这样写就看得明显了:
同时也要注意,循环补齐在这里起了关键作用,4被循环补齐为(4,4,4)。
我们知道R有没有标量,那么我们考察一下那些看上去有标量参数的向量化函数。
在这里定义的f(),我们希望c是一个标量,但实际上它当然是一个长度为1的向量。即使我们调用f()时给c指定的是单个数值,在f()计算x+c时,它也会通过循环补齐的方式延展为一个向量。因此对于本例中调用的f(1:3,1),x+c的值变为如右所示:
这带来了代码安全性的问题。f()中没有什么告知我们不能使用显式的向量给c赋值,例如:
你需要通过计算确认(4,16,36)是否的确是期望的输出。
如果你确实想把c限制为标量,则需要插入一些判断语句,比如这样:
2.6.2 向量输入,矩阵输出
到目前为止,我们涉及的向量化函数应用于标量时返回值也是标量。对单个数值调用sqrt()函数得到的结果也是单个数值,如果我们将此函数应用到八元向量,则得到的输出结果也是八个数组成的向量。
但如果函数本身的返回值就是向量会怎么样呢?例如这里的z12():
对5使用函数z12(),将得到二元向量(5,25),如果我们将它应用在八元向量,则它生成16个数值:
但我们可以用sapply()(它是simplify apply的缩写)来简化这一切。调用sapply(x,f)即可对x的每一个元素使用函数f(),并将结果转化为矩阵。下面是一个例子:
我们得到2×8而不是8×2维的矩阵,但它同样是有用的。第4章将进一步讨论sapply()函数。