removing offset terms from a formula
formula, r
Solution
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 `preserve` so, for example, if `preserve = 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, 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 this neither uses string manipulation directly nor recursion. First get the `terms` object, zap its `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. `preserve` acts as in (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
Problem
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 ...