向量化R代码的关键是减少或消除R函数的“按行操作”或方法分派。
这意味着,在解决乍看之下需要“逐行操作”的问题(例如计算每行的均值)时,需要问自己:
我要处理的数据集的类别是什么?
是否存在现有的编译代码可以实现此目标而无需重复评估R函数?
如果没有,我可以按列而不是按行执行这些操作吗?
最后,值得花大量时间开发复杂的矢量化代码,而不是仅仅运行简单的apply循环吗?换句话说,数据是否足够大/复杂,以至于R无法使用简单的循环有效地处理它?
撇开内存预分配问题和循环中不断增长的对象,我们将在本示例中重点介绍如何避免apply循环,方法分派或重新评估循环中的R函数。
按行计算均值的标准/简便方法是:
apply(mtcars, 1, mean) Mazda RX4 Mazda RX4 Wag Datsun 710 Hornet 4 Drive Hornet Sportabout Valiant Duster 360 29.90727 29.98136 23.59818 38.73955 53.66455 35.04909 59.72000 Merc 240D Merc 230 Merc 280 Merc 280C Merc 450SE Merc 450SL Merc 450SLC 24.63455 27.23364 31.86000 31.78727 46.43091 46.50000 46.35000 Cadillac Fleetwood Lincoln Continental Chrysler Imperial Fiat 128 Honda Civic Toyota Corolla Toyota Corona 66.23273 66.05855 65.97227 19.44091 17.74227 18.81409 24.88864 Dodge Challenger AMC Javelin Camaro Z28 Pontiac Firebird Fiat X1-9 Porsche 914-2 Lotus Europa 47.24091 46.00773 58.75273 57.37955 18.92864 24.77909 24.88027 Ford Pantera L Ferrari Dino Maserati Bora Volvo 142E 60.97182 34.50818 63.15545 26.26273
但是,我们可以做得更好吗?让我们看看这里发生了什么:
首先,我们将转换data.frame为matrix。(请注意,他发生在apply函数内。)这既低效又危险。一次matrix不能容纳几种列类型。因此,这种转换可能会导致信息丢失,有时甚至会导致误导性的结果(apply(iris, 2, class)与str(iris)或相比sapply(iris, class))。
第二,我们重复执行操作,每行一次。这意味着,我们必须评估一些R功能nrow(mtcars)时间。在这种特定情况下,mean函数不是计算上昂贵的函数,因此即使对于大数据集,R仍可能轻松处理它,但是如果我们需要按行计算标准偏差(这会涉及到昂贵的平方根运算),将会发生什么?这将我们带到下一点:
我们多次评估了R函数,但也许已经有此操作的编译版本了?
确实,我们可以简单地做到:
rowMeans(mtcars) Mazda RX4 Mazda RX4 Wag Datsun 710 Hornet 4 Drive Hornet Sportabout Valiant Duster 360 29.90727 29.98136 23.59818 38.73955 53.66455 35.04909 59.72000 Merc 240D Merc 230 Merc 280 Merc 280C Merc 450SE Merc 450SL Merc 450SLC 24.63455 27.23364 31.86000 31.78727 46.43091 46.50000 46.35000 Cadillac Fleetwood Lincoln Continental Chrysler Imperial Fiat 128 Honda Civic Toyota Corolla Toyota Corona 66.23273 66.05855 65.97227 19.44091 17.74227 18.81409 24.88864 Dodge Challenger AMC Javelin Camaro Z28 Pontiac Firebird Fiat X1-9 Porsche 914-2 Lotus Europa 47.24091 46.00773 58.75273 57.37955 18.92864 24.77909 24.88027 Ford Pantera L Ferrari Dino Maserati Bora Volvo 142E 60.97182 34.50818 63.15545 26.26273
这不涉及逐行运算,因此不涉及R函数的重复评估。但是,我们仍然将转换data.frame为matrix。尽管rowMeans具有错误处理机制,并且无法在无法处理的数据集上运行,但是它仍然具有效率成本。
rowMeans(iris) Error in rowMeans(iris) : 'x' must be numeric
但是,我们还能做得更好吗?我们可以尝试使用具有错误处理功能的矩阵转换来代替矩阵转换,而可以使用另一种方法将其mtcars用作向量(因为adata.frame本质上是a,list而alist是a vector)。
Reduce(`+`, mtcars)/ncol(mtcars) [1] 29.90727 29.98136 23.59818 38.73955 53.66455 35.04909 59.72000 24.63455 27.23364 31.86000 31.78727 46.43091 46.50000 46.35000 66.23273 66.05855 [17] 65.97227 19.44091 17.74227 18.81409 24.88864 47.24091 46.00773 58.75273 57.37955 18.92864 24.77909 24.88027 60.97182 34.50818 63.15545 26.26273
现在,为了提高速度,我们丢失了列名和错误处理(包括NA处理)。
另一个例子是使用基数R逐组计算均值
aggregate(. ~ cyl, mtcars, mean) cyl mpg disp hp drat wt qsec vs am gear carb 1 4 26.66364 105.1364 82.63636 4.070909 2.285727 19.13727 0.9090909 0.7272727 4.090909 1.545455 2 6 19.74286 183.3143 122.28571 3.585714 3.117143 17.97714 0.5714286 0.4285714 3.857143 3.428571 3 8 15.10000 353.1000 209.21429 3.229286 3.999214 16.77214 0.0000000 0.1428571 3.285714 3.500000
尽管如此,我们基本上是在循环中评估R函数,但是该循环现在隐藏在内部C函数中(无论是C还是R循环都无关紧要)。
我们可以避免吗?好吧,R中有一个称为的编译函数rowsum,因此我们可以这样做:
rowsum(mtcars[-2], mtcars$cyl)/table(mtcars$cyl) mpg disp hp drat wt qsec vs am gear carb 4 26.66364 105.1364 82.63636 4.070909 2.285727 19.13727 0.9090909 0.7272727 4.090909 1.545455 6 19.74286 183.3143 122.28571 3.585714 3.117143 17.97714 0.5714286 0.4285714 3.857143 3.428571 8 15.10000 353.1000 209.21429 3.229286 3.999214 16.77214 0.0000000 0.1428571 3.285714 3.500000
尽管我们也必须先转换为矩阵。
在这一点上,我们可能会质疑我们当前的数据结构是否最合适。是data.frame最佳实践吗?还是应该只是matrix为了提高效率而切换到数据结构?
随着我们每次都开始评估昂贵的函数,逐行操作将变得越来越昂贵(甚至在矩阵中)。让我们考虑通过行示例进行方差计算。
假设我们有一个矩阵m:
set.seed(100) m <- matrix(sample(1e2), 10) m [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [1,] 8 33 39 86 71 100 81 68 89 84 [2,] 12 16 57 80 32 82 69 11 41 92 [3,] 62 91 53 13 42 31 60 70 98 79 [4,] 66 94 29 67 45 59 20 96 64 1 [5,] 36 63 76 6 10 48 85 75 99 2 [6,] 18 4 27 19 44 56 37 95 26 40 [7,] 3 24 21 25 52 51 83 28 49 17 [8,] 46 5 22 43 47 74 35 97 77 65 [9,] 55 54 78 34 50 90 30 61 14 58 [10,] 88 73 38 15 9 72 7 93 23 87
一个人可以简单地做:
apply(m, 1, var) [1] 871.6556 957.5111 699.2111 941.4333 1237.3333 641.8222 539.7889 759.4333 500.4889 1255.6111
另一方面,也可以按照方差公式完全向量化此操作
RowVar <- function(x) { rowSums((x - rowMeans(x))^2)/(dim(x)[2] - 1) } RowVar(m) [1] 871.6556 957.5111 699.2111 941.4333 1237.3333 641.8222 539.7889 759.4333 500.4889 1255.6111