R 语言中的一些神奇操作

众所周知,R语言的优点是“一门统计学家开发的语言”,缺点也是“一门统计学家开发的语言”。相比于Python等比较偏计算机的语言,R语言中奇奇怪怪的操作实在是太多了。这篇博客就简单记录一下R语言中我所遇到的、感到叹为观止的操作,以备日后使用。

# 枚举字符串匹配

学C语言都知道枚举的重要性,用于将一系列固定的选项代码进行语义化。比如我现在有一个函数 `area()` ,其中有一个参数是 `type` ,可选值只有 `circle` 和 `square` 两种。如何在R语言中用枚举去表示呢?目前个人比较推荐的做法是,使用 `match.arg()` 函数,例如:

```R
area <- function(a, type = c("circle", "square")) {
    switch(match.arg(type), 
           "circle" = pi * a * a,
           "square" = a * a)
}
```

这样一来,函数 `match.arg()` 会自动根据函数定义中 `type` 的可选值,为我们匹配 `type` 的具体值。那么这个函数使用起来就可以用下面的方式

```R
area(1)              ## 3.14159265358979
area(1, "circle")    ## 3.14159265358979
area(1, "square")    ## 1
area(1, "rectangle") ## Error in match.arg(type): 'arg' should be one of “circle”, “square”
```

甚至枚举字符串不用写全

```R
area(1, "cir")  ## 3.14159265358979
area(1, "s")    ## 1
```

这样就解决了枚举字符串的问题。

# 模型数据提取

在 **GWmodel** 和 **lm** 等做回归分析的包的代码中,往往会看到一段令人莫名其妙的代码,类似于

```R
function(formula, data) {
    mf <- match.call(expand.dots = FALSE)            ### 将函数的调用进行分析
    m <- match(c("formula", "data"), names(mf), 0L)  ### 找到 formula 和 data 两个参数在调用中的位置
  
    ### 以下四行用于构造类似于下面代码的调用
    ### model.frame(frame = frame, data = data, drop.unused.levels = T)
    mf <- mf[c(1L, m)]
    mf$drop.unused.levels <- TRUE
    mf[[1L]] <- as.name("model.frame")

    mf <- eval(mf, parent.frame())      ### 执行上面构造好的调用,得到 model.frame 的对象
    mt <- attr(mf, "terms")             ### 提出 model.frame 对象中的符号
    y <- model.extract(mf, "response")  ### 提出 model.frame 对象中 response 所对应的数据
    x <- model.matrix(mt, mf)           ### 根据 model.frame 对象构造设计矩阵
}
```

这段代码非常神奇地,把存储于 `data` 中的数据,根据 `formula` 的形式,分离了出来。这里会用到一个除非开发包否则不太常用的类型 `model.frame` 用于描述模型,它可以将 `formula` 对象和 `data.frame` 对象存储到一起,并进行一些操作(例如代码中设定的 `drop.unused.levels` 就是去掉没有使用的 `factor` 类型的因素)。`formula` 对象的 `terms` 被存储到了 `data.frame` 对象中,成为后者的一个 `attr` (属性),我们可以通过 `attributes()` 函数查看。

```plaintext
attributes(mf)
$names
[1] "Murder"  "Rape"    "Assault"

$terms
Murder ~ Rape + Assault
attr(,"variables")
list(Murder, Rape, Assault)
attr(,"factors")
        Rape Assault
Murder     0       0
Rape       1       0
Assault    0       1
attr(,"term.labels")
[1] "Rape"    "Assault"
attr(,"order")
[1] 1 1
attr(,"intercept")
[1] 1
attr(,"response")
[1] 1
attr(,".Environment")
<environment: R_GlobalEnv>
attr(,"predvars")
list(Murder, Rape, Assault)
attr(,"dataClasses")
   Murder      Rape   Assault 
"numeric" "numeric" "numeric" 

$row.names
 [1] "Alabama"        "Alaska"         "Arizona"        "Arkansas"  

$class
[1] "data.frame"
```

所以我们将 `mf` 的 `terms` 属性提取出来,交给 `model.matrix()` 函数,就可以提取设计矩阵。但事实上,这里直接传入一个 `formula` 也是可以的。

这种方式还是有局限性的,几乎仅限于普通线性回归所支持的表达式。如果有其他特殊标记,可以自己实现解析的方法。