从公式中删除偏移项

removing offset terms from a formula

R 有一个操作公式的便捷工具,update.formula()。当你想得到类似 "formula containing all terms in previous formula except x" 的东西时,这很好用,例如

f1 <- z ~ a + b + c
(f2 <- update.formula(f1, . ~ . - c))
## z ~ a + b

但是,这似乎不适用于偏移项:

f3 <- z ~ a + offset(b) 
update(f3, . ~ . - offset(b))
## z ~ a + offset(b)

我已经深入挖掘 terms.formula,其中 ?update.formula 引用:

[after substituting, ...] The result is then simplified via ‘terms.formula(simplify = TRUE)’.

terms.formula(z ~ a + offset(b) - offset(b), simplify=TRUE)
## z ~ a + offset(b)

(即,这似乎没有删除 offset(b) ...)

我知道我可以通过使用 deparse() 和文本处理来破解解决方案,或者通过递归处理公式以删除我不想要的术语,但这些解决方案很难看 and/or 实施起来很烦人。关于 为什么 这不起作用的启示,或者一个相当紧凑的解决方案,都会很棒 ...

这似乎是设计使然。但一个简单的解决方法是

offset2 = offset
f3 <- z ~ a + offset2(b) 
update(f3, . ~ . - offset2(b))
# z ~ a

如果您需要灵活地接受 do 包括 offset() 的公式,例如,如果公式是由可能不知道需要的软件包用户提供的要使用 offset2 代替 offset,那么我们还应该添加一行来更改传入公式中 offset() 的任何实例:

f3 <- z ~ a + offset(b) 

f4 <- as.formula(gsub("offset\(", "offset2(", deparse(f3)))
f4 <- update(f4, . ~ . - offset2(b))

# finally, just in case there are any references to offset2 remaining, we should revert them back to offset
f4 <- as.formula(gsub("offset2\(", "offset(", deparse(f4)))
# z ~ a

1) 递归 通过将offset(...)替换为offset的公式递归下降,然后使用update删除offset。没有进行任何字符串操作,尽管它确实需要多行代码,但它仍然相当短,并且确实删除了单个和多个 offset 项。

如果有多个偏移量,可以通过设置 preserve 来保留其中的一些偏移量,例如,如果 preserve = 2 则保留第二个偏移量并删除任何其他偏移量。默认是保留 none,即全部删除。

no.offset <- function(x, preserve = NULL) {
  k <- 0
  proc <- function(x) {
    if (length(x) == 1) return(x)
    if (x[[1]] == as.name("offset") && !((k<<-k+1) %in% preserve)) return(x[[1]])
    replace(x, -1, lapply(x[-1], proc))
  }
  update(proc(x), . ~ . - offset)
}

# tests

no.offset(z ~ a + offset(b))
## z ~ a

no.offset(z ~ a + offset(b) + offset(c))
## z ~ a

Note that if you don't need the preserve argument then the line initializing k can be omitted and the if simplified to:

if (x[[1]] == as.name("offset")) return(x[[1]])

2) terms 这既不直接使用字符串操作也不使用递归。首先获取 terms 对象,删除它的 offset 属性并使用我们从 terms.formula 中提取的 fixFormulaObject 修复它。通过将 fixFormulaObject 的源代码复制到您的源代码并删除下面的 eval 行,可以使这变得不那么脆弱。 preserve 与 (1) 中的行为相同。

no.offset2 <- function(x, preserve = NULL) {
  tt <- terms(x)
  attr(tt, "offset") <- if (length(preserve)) attr(tt, "offset")[preserve]
  eval(body(terms.formula)[[2]]) # extract fixFormulaObject
  f <- fixFormulaObject(tt)
  environment(f) <- environment(x)
  f
}

# tests

no.offset2(z ~ a + offset(b))
## z ~ a

no.offset2(z ~ a + offset(b) + offset(c))
## z ~ a

Note that if you don't need the preserve argument then the line that zaps the offset attribute can be simplified to:

attr(tt, "offset") <- NULL