为了学R,同时也为了顺带着练习练习英语,订了R-help的maillist。几个月来,每天都能收到百来封邮件,可惜时间有限,大部分邮件都被我直接跳过了。最近坚持着读了一些,学到不少东西。其中的一组邮件,让我理解了R语言中同一问题在不同范型中的解决方案。

问题是: 如何生成1000个大小为100的正态分布的数据样本?

回复中出现了一系列的解答,如:

方法1:

r
1> z <- list()
2> for(i in 1:1000) { z[[i]] <- rnorm(100,0,1) }

方法2:

r
1> z <- matrix(rnorm(1000*100), 1000, 100)

方法3:

r
1> z <- replicate(1000, rnorm(100))

有人觉得方法1的写法比较适合初学者理解,但是在for循环中做rnorm计算,然后再追加到list中,可能会很费时。然而其实这种方法的效率与直接计算rnorm(1000*100)的差别不是太大,有人做了如下比较:

r
 1library(rbenchmark)
 2
 3n=100; m=100
 4benchmark(replications=100,
 5columns=c('test', 'elapsed'), order=NULL,
 6list={ l=list();
 7for (i in 1:n) l[[i]] = rnorm(m) },
 8liist={ l=vector('list', n);
 9for (i in 1:n) l[[i]] = rnorm(m) },
10matrix=matrix(rnorm(n*m), n, m),
11replicate=replicate(m, rnorm(n)) )
12# test elapsed
13# 1 list 0.247
14# 2 liist 0.235
15# 3 matrix 0.172
16# 4 replicate 0.247
17
18n=10; m=1000
19...
20# test elapsed
21# 1 list 0.169
22# 2 liist 0.169
23# 3 matrix 0.173
24# 4 replicate 0.771
25
26n=1000; m=10
27...
28# test elapsed
29# 1 list 1.428
30# 2 liist 0.902
31# 3 matrix 0.172
32# 4 replicate 0.185

从上述结果还能看出在样本大小与样本数量变化时,list的方式与replicate的方式各自所需要的时间的确是存在差异的。因此具体使用哪种方式,应该还是由数据和目的而定。

此后的回复邮件中,还修改了原题,每个样本的大小使用样本的序号(第一个样本含一个数据,第二个样本含两个数据,以此类推),解决的方法包含了使用for循环和lapply函数:

r
1x=list()
2for(i in 1:n){
3    x[[i]]=rnorm(i,0,1)
4}

和

r
1lapply(1:100, rnorm, 0, 1)

在这两种方法中,点出了使用for循环与使用lapply函数之间的差异。在R语言中,提供了类似lapply的强大的函数,相当于在一个函数中就对一系列的数据循环地执行了某个操作,这种方式,其实对于像R这种解释型语言来说,是能很大程度解决运行效率问题的。然而在这里,它们的效率其实差异不大:

r
1benchmark(columns=c('test','elapsed'),
2        for_loop = { x = list();
3        for (i in 1:100) { x[[i]] = rnorm(i,0,1) } },
4        lapply = lapply(1:100, rnorm, 0, 1) );
5test elapsed
61 for_loop 0.22
72 lapply 0.17

关键在于两种写法在对程序的理解角度不同。有人举了另一个“break 6 eggs”例子:要打破六个蛋,可以“for n from 1 to 6, break then nth egg.”(n循环从1到6,分别打破第n个蛋),也可以“apply break to the eggs”(向所有蛋都执行打破动作)。这下子,不同的写法差异,成了一个有点哲学性的问题,也就体现了不同的编程范型(paradigm)。

评论(备份自LiveSpace):

  • 2009-05-18 - Alan Chen: 老婆,快带着孩子一起来看外星人。。。