________________________________________________________________
/  ____    _                                                     \
| |  _ \  (_)___    __ ___      _____  ___  ___  _ __ ___   ___  |
| | |_) | | / __|  / _` \ \ /\ / / _ \/ __|/ _ \| '_ ` _ \ / _ \ |
| |  _ <  | \__ \ | (_| |\ V  V /  __/\__ \ (_) | | | | | |  __/ |
| |_| \_\ |_|___/  \__,_| \_/\_/ \___||___/\___/|_| |_| |_|\___| |
\                                                                /
 ----------------------------------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
  1. 赋值

从一开始学R就知道R的赋值符号和别的语言不一样,用的是<-,虽然=也用,很多书会告诉你两者是一样的,完全通用,不过用R的人习惯也推倡用<-。所以我基本上就抛弃用=号了,反正在ESS里用shift+-就会自动输入<-,也是很方便的。直到有一天,我发现在switch里,必须用=,而不能用<-,才发现坑爹啊,这两符号是不一样的。

  1. 函数中给全局变量赋值

R里可以用<<-来进行全局赋值,比如我在解Project Euler 15题时,就用了<<-,这个最好还是不要用,用全局赋值可能会给你surprise,会给出惊喜的代码不是好代码。

当然所谓的全局和局部,都是相对的,主要看内存管理的实现方式,内存堆栈,在R里通过environment来实现,S中称之为frames。所谓的全局变量就是位于.GlobalEnv中而已。

所以如果要用<<-来进行全局赋值的话,安全的做法是使用局部的全局变量。如果要做个类比的话,这有点像C/C++里的静态变量。

比如下面这段计算fibonacci数的函数:

fibonacci <- local({
    memo <- c(1, 1, rep(NA, 20))
    f <- function(x) {
        if(x == 0) return(0)
        if(x < 0) return(NA)
        if(x > length(memo))
            stop("''x'' too big for implementation")
        if(!is.na(memo[x])) return(memo[x])
        ans <- f(x-2) + f(x-1)
        memo[x] <<- ans
        ans
    }
})
> fibonacci(15)
[1] 610
> get("memo", envir=environment(fibonacci))
 [1]   1   1   2   3   5   8  13  21  34  55  89 144 233 377 610
[16]  NA  NA  NA  NA  NA  NA  NA

environment是个好东西,我很惊讶于我看过的书里基本都没讲到。R的面向对象系统R5,就目前来看,是纯R实现的,用的就是S4和environment。

PS:S3面向对象就是加个class属性,通常用list来存储数据。我一直觉得,把数据存在封闭的environment里,再加个class属性,基本上就可以当成是个简易版的S4了,当然S4怎么实现,我并不清楚。

  1. 精度

精度的问题,这是所有计算机语言的问题,就像图灵社区里的笑话,0.1 + 0.2 > 0.3 答案是TRUE.

> .1 == .3/3
[1] FALSE
> print(.3/3, digits=16)
[1] 0.09999999999999999
> as.integer(.3/3)
[1] 0
  1. 操作符优先度

这个也是所有语言所共有的问题,加()才是万能的。这个之所以tricky,是因为有些优先度和我们的直觉不符合,如果不注意,就很有可能出错。

比如下面的例子:

> -1.2^2.3
[1] -1.52
> (-1.2)^2.3
[1] NaN
> as.complex(-1.2)^2.3
[1] 0.89+1.23i

在R里,可以用?Syntax通过在线文档查看操作符的优先度。

  1. 类属性

在R里,所有的东西,都是对象,都有类属性。比如下面的例子,我想看x和y是否是一样的。

> x <- 1:2
> y <- c(1,2)
> all(x == y)
[1] TRUE

显然它们应该是一样的,但是如果用identical函数来看,答案却是FALSE。原因是x是整数型数据,而y是浮点型数据,所以它们不一样。这么tricky的事情,原因在于函数c会把数字都存成浮点型。

> print(y, digits=10)
[1] 1 2

其实y就是整数,被认为是浮点数,完全是因为类属性。

  1. 缺失值

比如下面的例子,我想取x中的2,因为x含有NA,就出问题了。当然这个还是比较好解决的,用which就可以了。

> x <- c(1:4,NA, 2)
> x
[1]  1  2  3  4 NA  2
> x[x==2]
[1]  2 NA  2
> x[which(x==2)]
[1] 2 2

这个不算tricky,但是如果我想把x中大于5的数剔除掉,上面这个x没有数字是大于5的,我们期望它返回x自身。

> x[-which(x>5)]
numeric(0)

结果却是返回空向量。因为which(x>5)是空的,做为index去取x的subset,自然也就取不到。

对于各种membership的处理,万能又安全的还是%in%操作符。

> x[! x %in% 5]
[1]  1  2  3  4 NA  2
  1. 空向量

上面例子出现空向量numeric(0),空向量如果出现于计算中,也是很weird的。

> sum(numeric(0))
[1] 0
> prod(numeric(0))
[1] 1
> any(logical(0))
[1] FALSE
> all(logical(0))
[1] TRUE
  1. 操作符是函数

    > "+"(1,2)
    [1] 3
    > "=="(1,2)
    [1] FALSE
    > "<"(1,2)
    [1] TRUE
    
  2. 纯数字的data.frame

如果一个data.frame的内容全是数字,它看起来和matrix长得一模一样,但是它们的实现方式是不一样的,matrix是dimention=2的array,而data.frame是等长的list。

长得像有时候不注意,就会出现confuse,比如你要求一个matrix的均值,很容易,用mean函数就行。但如果你用mean去求一个data.frame的均值,就不work了,给出的结果是每个column的均值所组成的向量,当然你可以对这个column mean再求一次mean就行了,也可以先as.matrix或unlist处理一下,再求mean。

各种函数,包括apply系列,对于data.frame和matrix的行为,很多都是不一样的,需要谨慎。

  1. 哈希表

R的数据类型中没有哈希表,通常我们可以用named vector或者list来实现,可以通过name来取得所对应的值。但这并不是真正意义上的哈希,同样我们可以用environment来实现。

> ee <- new.env()
> assign("a", 10, ee)
> assign("b", 22, ee)
> get("a", ee)
[1] 10
> keys <- ls(ee)
> keys
[1] "a" "b"
> values <- sapply(keys, get, envir=ee)
> values
 a  b 
10 22 
  1. 用数字当变量名

几乎所有的编程语言都不允许以_开头或者数字当变量名,但在R里,这却是可能的,请猛击此处

这么tricky的事情,也是通过environment来实现的。

  1. 部分匹配

正如第一点提到的<-和=是通用的,取list元素是$[[也是通用的,然而它们的默认行为也是不一样的,$默认支持部分匹配,而[[则不是。

> ll <- list(aa=1:2, ab=2:4, cc=1:3) 
> ll$c
[1] 1 2 3
> ll[["c"]]
NULL
> ll[["c", exact=FALSE]]
[1] 1 2 3

部分匹配不是好东西,比如下面的例子:

> ll$c
[1] 1 2 3
> ll$c <- 5:6
> ll$c
[1] 5 6
> ll
$aa
[1] 1 2
$ab
[1] 2 3 4
$cc
[1] 1 2 3
$c
[1] 5 6

如果你习惯用部分匹配去取元素,在对此元素进行重新赋值的时候,就很可能会犯错。

函数参数也支持部分匹配,比如有个参数trim,而你一直用t去传这个参数,如果以后函数加了参数比如treat,那么你原先的代码就会莫名其妙的不干活了。

  1. factor

    > x <- factor(8:12)
    > as.numeric(x)
    [1] 1 2 3 4 5
    > as.numeric(as.character(x))
    [1]  8  9 10 11 12
    > as.numeric(levels(x))
    [1]  8  9 10 11 12
    

factor由于内部是使用自然数去存储的,所以如果想把factor转换成数字,就必须要麻烦点。

这个问题出现于data.frame中,就是个另人upset的玩意了。

> df <- data.frame(a=1:2, b=c("x","y"))
> df[1,]
  a b
1 1 x
> as.character(df[1,])
[1] "1" "1"
> as.character(df)
[1] "1:2" "1:2"

解决的办法是,你先把df转换成matrix。

  1. 不存在的subscript

接着使用上面的df。

> rownames(df) <- c("a", "b")
> colnames(df) <- c("p", "q")
> df[c("a", "e"),]
    p    q
a   1    x
NA NA <NA>
> df[,c("p", "e")]
Error in `[.data.frame`(df, , c("p", "e")) : undefined columns selected

row和column的行为是不一致的,而对于不同的数据类型,其返回值也会不同。

vector会返回NA,list则是NULL,matrix会报错,subscript out of bouds。

  1. 从剪贴板里读数据

在windows和linux下,都可以用read.table("paste")来读取剪贴板里的数据,在Mac OS X下,就不行了。你必须要用read.table(pipe("pbpaste")),而在Linux下,则是用read.table("clipboard"),我其实很纳闷,为什么就不能包装一下,让语法统一。

最后,附送一个tips,从我以前的截屏上看,我最喜欢干的事,就是一打开终端,自动运行一次fortune,顺道看看名言嘛-,-

在R里,有个fortunes包,收集的是R相关的言论,有事没事看一看,有益身心健康啊!

> require(fortunes)
Loading required package: fortunes
> fortune()

[Listing original copyright holders in R packages is] especially useful if
there are areas of doubt and uncertainty (eg code published on ftp sites before
people worried about licenses), since it at least gives you rigidly defined
areas of doubt and uncertainty.[5]
[5] Adams D, (1978) Hitchhiker's Guide. BBC Radio.
   -- Thomas Lumley (in a discussion about copyright vs. licenses)
      R-devel (January 2010)