3.3 对矩阵的行和列调用函数
*apply()函数系列是R中最受欢迎同时也是最常用的,该函数系列包括apply()、tapply()和lapply()。这里我们主要介绍apply()。apply()函数允许用户在矩阵的各行或各列上调用指定的函数。
3.3.1 使用apply()函数
以下是apply()函数的一般形式:
参数解释如下:
m 是一个矩阵。
dimcode 是维度编号,若取值为1代表对每一行应用函数,若取值为2代表对每一列应用函数。
f是应用在行或列上的函数。
fargs是f的可选参数集。
例如,对矩阵z的每一列应用函数mean():
本例也可以用colMeans()函数直接实现,不过这里主要为了提供使用apply()的简单示例。
apply()里也可以使用用户自己定义的函数,就和使用R内部函数(比如mean())一样。下面是使用自定义函数f的例子:
f()函数把一个向量除以(2,8)。(如果向量x的长度大于2,那么(2,8)就会循环补齐)。apply()函数则对z的每一行分别调用f()。z的第一行是(1,4),所以在调用f()时,形式参数x对应的实际参数是(1,4)。然后R计算(1,4)/(2,8)的值,这在R中是向量对应元素运算,得到(0.5,0.5)。对其他两行的计算与此类似。
你可能会惊讶,所得结果y是一个2×3的矩阵而非3×2的。第一行的计算结果(0.5, 0.5)构成apply()函数输出结果的第一列,而不是第一行。这就是apply()函数的默认方式。如果所调用的函数返回的是一个包含k个元素的向量,那么apply()的结果就有k行。如果需要的话,可以使用转置函数t(),例如:
如果所调用的函数只返回一个标量(即单元素向量),那么apply()的结果就是一个向量,而非矩阵。
在用apply()时,待调用的函数至少需要一个参数。上例中,f()的形式参数在这里对应的实际参数就是矩阵的一行(或一列)。有时,待调用函数需要多个参数,用apply()调用这类函数时,需要把这些额外的参数列举在函数名字后面,用逗号隔开。
例如,我们有一个由0和1组成的矩阵,想要生成如下向量:向量每个元素对应矩阵的每行,如果该行前d个元素中1较多,向量的对应元素就取1,反之取0。其中d是可以变的参数。程序可以这样写:
这里3和2是函数copymaj()中形式参数d的实际取值。矩阵的第一行是(1,0,1,1,0),当d取3时,前d个元素是(1,0,1)。1占多数,因此copymaj()返回1。所以apply()返回的第一个元素就是1。
使用apply()函数并不像很多人以为的那样,能使程序的运行速度加快。其优点是使程序更紧凑,更易于阅读和修改,并且避免产生使用循环语句时可能带来的bug。此外,并行运算是R目前发展的方向之一,apply()这类函数会变得越来越重要。例如,snow包中的clusterApply()函数能够把子矩阵的数据分配到多个网络节点上,在每个网络节点上对子矩阵调用给定的函数,达到并行计算的目的。
3.3.2 扩展案例:寻找异常值
在统计学中,“异常值”(outlier)指的是那些和大多数观测值离得很远的少数点。所以异常值要么是有问题(例如数字写错了),要么是不具有代表性(例如比尔盖茨的收入和华盛顿州居民的收入相比)。检测异常值有很多方法,我们这里构造一个非常简单的方法。
假如矩阵rs用来存储零售业销售数据,每行对应一家商店,一行里的观测值对应每天的销售数据。我们用非常简单的方法识别出每家商店数据中偏离最远的观测值,也就是与中位数差别最大的观测值。代码如下:
那么上述中的findols(rs)是如何运作的?首先需要定义一个函数,作为调用apply()函数时的参数。
该函数要应用于销售数据矩阵的每一行,并返回该行中最异常的元素所在位置。函数findol()就是为完成此目的而定义的,请看代码的第4、5行。(注意,我们在一个函数的内部又定义了另一个函数。如果内部函数很短的话,这么做是很常见的。)在表达式xrow-mdn中,向量xrow减去单元素向量mdn,而前者的长度通常大于1,因此在做向量减法运算之前,mdn被循环补齐(recycling),扩展到与xrow的长度一致。
代码第五行使用R函数which.max(),而不是max()。因为max()返回的是向量元素的最大值,而which.max()返回最大值所在位置(即索引)。这正是我们需要的。
最后在第七行,对x的每一行调用findol(),得到各行“异常值”所在位置。
请发表评论