颜林林的个人网站

Linlin Yan's Personal Website

R语言中的编程范型(paradigm)

2009-05-17 14:26

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

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

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

方法1:

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

方法2:

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

方法3:

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

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

library(rbenchmark)

n=100; m=100
benchmark(replications=100,
columns=c('test', 'elapsed'), order=NULL,
list={ l=list();
for (i in 1:n) l[[i]] = rnorm(m) },
liist={ l=vector('list', n);
for (i in 1:n) l[[i]] = rnorm(m) },
matrix=matrix(rnorm(n*m), n, m),
replicate=replicate(m, rnorm(n)) )
# test elapsed
# 1 list 0.247
# 2 liist 0.235
# 3 matrix 0.172
# 4 replicate 0.247

n=10; m=1000
...
# test elapsed
# 1 list 0.169
# 2 liist 0.169
# 3 matrix 0.173
# 4 replicate 0.771

n=1000; m=10
...
# test elapsed
# 1 list 1.428
# 2 liist 0.902
# 3 matrix 0.172
# 4 replicate 0.185

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

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

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

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

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

benchmark(columns=c('test','elapsed'),
        for_loop = { x = list();
        for (i in 1:100) { x[[i]] = rnorm(i,0,1) } },
        lapply = lapply(1:100, rnorm, 0, 1) );
test elapsed
1 for_loop 0.22
2 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: 老婆,快带着孩子一起来看外星人。。。