别的语言一般都不能计算猫加狗这样的表达式,但R可以算1:10 + 1:2,两个长度不一样的对象也可以做计算,原因是R总是把短对象自动扩展到长对象的长度再计算;这种扩展有时候很难想象,如matrix(1:10, 5) + 1:2(一个矩阵加一个向量)。向量一般来说看作列向量,也就是n x 1的矩阵,但你可以看见以下表达式都可以正常计算:
matrix(1:10, 5) %*% 1:2 # 5x2乘以2x1,没问题1:2 %*% matrix(1:10, 1) # 2x1乘以1x10,没问题1:2 %*% matrix(1:10, 2) # 你到底是2x1还是1x2?这实在让人防不胜防。除非你事先小心实验,否则这种矩阵乘法出错了都不知道。但这问题其实也来源于作者的懒惰,只要把向量转化为严格的矩阵(不要让R去自动猜测调整),一切问题都解决了。
这些“自动”特征给数据分析其实带来了不少好处,例如在回归设计阵中加一列给截距项的1,你不必写一串1,只要X = cbind(X, 1)就可以了,R会自动把1扩展为X的行数;又如你想让散点图中的点按照数据顺序依次用红色、蓝色、红色、蓝色……那么plot(x, y, col = c('red', 'blue'))就够了,而不必把颜色向量写完整了。对数据分析者来说,那些计算机的严格规则最好是匿得越远越好。
有时候自动扩展悄无声息带来的问题会很难查找,例如在各种巧合之下,kuanguang坛霸问的这个问题下面掩盖了一个极大的阴谋,初学者可能看不出里面的门道,楼主的代码运行表面上看起来成功了,但实际上完全是错误的代码。本来这是个很好的例子,只是这家伙碎碎念实在太多了,一天到晚问题不断,我也来一次小心眼,装没看见好了。
引号R的懒惰是别的语言打死都想不通的,比如把一个不存在的对象转化为字符,这么说有点抽象,我们可以考虑一下library()这个函数。
library(fun)这样一句话是什么意思呢?fun不是一个R对象,它根本不存在,但为什么library(fun)就可以加载一个名叫fun的包?主要原因就是懒,因为懒得打引号:
library("fun")正常来说,这个函数的第一个参数应该是R包的名字,也就是说应该是字符串。在函数内部,最终需要的也是一个字符串。R之所以能把这件事情搞得这么奇葩,也是与它强大的“基于语言的计算”(Computing on the Language)能力有关,参见手册“R Language Definition”第6节。所谓基于语言的计算,就是把代码拿来作计算,各种魔法parse()、deparse()、substitute()、eval()、match.call()等等,极大增强了R的语言功能,所以说它是一门统计计算语言实在太低估了它。
例如这里是一个简单的函数,把输入的合法的R符号转化为字符串:
f = function(x) deparse(substitute(x))f(asdf)f(hahaha)这种懒惰在一些Linux工具中也可以看见身影,例如tar,我们可以按标准写上减号-以传参数,也可以省略减号让tar把第一个参数当作参数,后面的参数当文件名:
tar -x -z -f R-2.15.1.tar.gztar -xzf R-2.15.1.tar.gztar xzf R-2.15.1.tar.gz这就是“多打一个字符会死星人”。
岔开话题回到library()这个函数,我印象中R core一直后悔这个函数的命名,想把它改成use()。因为library()的存在且高频使用,让很多用户称R包为library(例如I'm using the rpart library),这曾经让某R core(M.M.)极度不爽,因为library在R中的概念是“库”,而不是单个的包,一个库可能是多个包的集合,单个包叫package。啥时侯你意识到函数命名可能比写函数本身还难时,就表明你的码农功力又上一层楼了。
LaTeX那个年代科学计算类都和LaTeX能扯上关系,这年头都奔HTML去了,谁还去打印大部头的手册啊。R的文档就是一种伪LaTeX文档,R自身也拼命模仿一些LaTeX程序,例如texi2dvi()函数。这种伪TeX文档带来的就是新的解析工作,参见parse_Rd()魔法,于是各种规矩铺天盖地而来……
值传递与引用传递R一向没有引用传递,但这说法不太严格,我们可以把一个环境当作参数传递来去,环境里的对象可以在任何地方被改变。
z = new.env()z$x = 1f = function(env) { env$x = 2}f(z)z$x # 变成了2