Ben Bolker Ben Bolker - 1 month ago 15
R Question

removing offset terms from a formula

R has a handy tool for manipulating formulas,

update.formula()
. This works nicely when you want to get something like "formula containing all terms in previous formula except
x
", e.g.

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


However, this doesn't seem to work with offset terms:

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


I've dug down as far as
terms.formula
, which
?update.formula
references:


[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)


(i.e., this doesn't seem to remove
offset(b)
...)

I know I can hack up a solution either by using
deparse()
and text-processing, or by processing the formula recursively to remove the term I don't want, but these solutions are ugly and/or annoying to implement. Either enlightenment as to why this doesn't work, or a reasonably compact solution, would be great ...

Answer

1) Recursion Recursively descend through the formula replacing offset(...) with offset and then remove offset using update. No string manipulation is done and although it does require a number of lines of code it's still fairly short and does remove single and multiple offset terms.

If there are multiple offsets one can preserve some of them by setting subset so, for example, if subset = 2 then the second offset is preserved and any others are removed. The default is to preserve none, i.e. remove them all.

no.offset <- function(x, subset = NULL) {
  k <- 0
  proc <- function(x) {
    if (length(x) == 1) return(x)
    if (x[[1]] == as.name("offset") && !((k<<-k+1) %in% subset)) 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

2) terms this neither uses string manipulation directly nor recursion. First get the terms object, zap it's offset attribute and fix it using fixFormulaObject which we extract out of the guts of terms.formula. This could be made a bit less brittle by copying the source code of fixFormulaObject into your source and removing the eval line below. subset acts as in (1).

no.offset2 <- function(x, subset = NULL) {
  tt <- terms(x)
  attr(tt, "offset") <- if (length(subset)) attr(tt, "offset")[subset]
  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