1
Formula <- function(object) {
3
stopifnot(inherits(object, "formula"))
5
object_split <- split_formula(object)
7
structure(object, lhs = object_split$lhs, rhs = object_split$rhs,
8
class = c("Formula", "formula"))
11
as.Formula <- function(x, ...) UseMethod("as.Formula")
13
as.Formula.default <- function(x, ..., env = parent.frame()) Formula(as.formula(x, env = env))
15
as.Formula.Formula <- function(x, ...) x
17
as.Formula.formula <- function(x, ..., env) {
19
## preserve original environment
20
if(missing(env)) env <- environment(x)
22
## combine all arguments to formula list
23
x <- c(list(x), list(...))
24
x <- lapply(x, as.formula)
27
x_split <- lapply(x, split_formula)
28
x_lhs <- do.call("c", lapply(x_split, "[[", "lhs"))
29
x_rhs <- do.call("c", lapply(x_split, "[[", "rhs"))
32
x_all <- paste_formula(x_lhs, x_rhs)
35
## (we have everything to do this by hand, but for encapsulating code
36
## call Formula() again...which splits again)
37
rval <- Formula(x_all)
39
## re-attach original environment
40
environment(rval) <- env
44
is.Formula <- function(object) inherits(object, "Formula")
46
formula.Formula <- function(x, lhs = NULL, rhs = NULL, collapse = FALSE,
47
update = FALSE, drop = TRUE, ...)
50
lpart <- 1:length(attr(x, "lhs"))
51
rpart <- 1:length(attr(x, "rhs"))
53
## default: keep all parts
54
lhs <- if(is.null(lhs)) lpart else lpart[lhs]
55
rhs <- if(is.null(rhs)) rpart else rpart[rhs]
57
lhs <- as.vector(na.omit(lhs))
58
if(length(lhs) < 1) lhs <- 0
59
warning("subscript out of bounds, not all 'lhs' available")
62
rhs <- as.vector(na.omit(rhs))
63
if(length(rhs) < 1) rhs <- 0
64
warning("subscript out of bounds, not all 'rhs' available")
67
## collapse: keep parts separated by "|" or collapse with "+"
68
collapse <- rep(as.logical(collapse), length.out = 2)
70
rval <- paste_formula(attr(x, "lhs")[lhs], attr(x, "rhs")[rhs],
71
lsep = ifelse(collapse[1], "+", "|"),
72
rsep = ifelse(collapse[2], "+", "|"))
74
## omit potentially redundant terms
75
if(all(collapse) & update) rval <- update(rval, if(length(rval) > 2) . ~ . else ~ .)
77
## reconvert to Formula if desired
78
if(!drop) rval <- Formula(rval)
80
## re-attach original environment
81
environment(rval) <- environment(x)
86
terms.Formula <- function(x, ..., lhs = NULL, rhs = NULL) {
88
## simplify a Formula to a formula that can be processed with
89
## terms/model.frame etc.
90
simplify_to_formula <- function(Formula, lhs = NULL, rhs = NULL) {
92
## get desired subset as formula and Formula
93
form <- formula(Formula, lhs = lhs, rhs = rhs)
96
## convenience functions for checking extended features
97
is_lhs_extended <- function(Formula) {
98
## check for multiple parts
99
if(length(attr(Formula, "lhs")) > 1) {
102
## and multiple responses
103
if(length(attr(Formula, "lhs")) < 1) return(FALSE)
104
return(length(attr(terms(paste_formula(NULL,
105
attr(Formula, "lhs"), rsep = "+")), "term.labels")) > 1)
109
is_rhs_extended <- function(Formula) {
110
## check for muliple parts
111
length(attr(Formula, "rhs")) > 1
114
## simplify (if necessary)
115
ext_lhs <- is_lhs_extended(Form)
116
if(ext_lhs | is_rhs_extended(Form)) {
117
form <- if(ext_lhs) {
118
if(length(attr(Form, "rhs")) == 1 & identical(attr(Form, "rhs")[[1]], 0)) {
119
paste_formula(NULL, attr(Form, "lhs"), rsep = "+")
121
paste_formula(NULL, c(attr(Form, "lhs"), attr(Form, "rhs")), rsep = "+")
124
paste_formula(attr(Form, "lhs"), attr(Form, "rhs"), rsep = "+")
128
## re-attach original environment and return
129
environment(form) <- environment(Formula)
133
## simplify and then call traditional terms()
134
form <- simplify_to_formula(x, lhs = lhs, rhs = rhs)
138
model.frame.Formula <- function(formula, data = NULL, ..., lhs = NULL, rhs = NULL)
140
model.frame(terms(formula, lhs = lhs, rhs = rhs, data = data), data = data, ...)
143
model.matrix.Formula <- function(object, data = environment(object), ..., lhs = NULL, rhs = 1)
145
form <- formula(object, lhs = lhs, rhs = rhs, collapse = c(FALSE, TRUE))
146
mt <- delete.response(terms(form, data = data))
147
model.matrix(mt, data = data, ...)
150
## as model.response() is not generic, we do this:
151
model.part <- function(object, ...)
152
UseMethod("model.part")
154
model.part.formula <- function(formula, data, ..., drop = FALSE) {
155
formula <- Formula(formula)
159
model.part.Formula <- function(object, data, lhs = 0, rhs = 0, drop = FALSE, terms = FALSE, ...) {
161
## *hs = NULL: keep all parts
162
if(is.null(lhs)) lhs <- 1:length(attr(object, "lhs"))
163
if(is.null(rhs)) rhs <- 1:length(attr(object, "rhs"))
165
if(isTRUE(all.equal(as.numeric(lhs), rep(0, length(lhs)))) &
166
isTRUE(all.equal(as.numeric(rhs), rep(0, length(rhs)))))
167
stop("Either some 'lhs' or 'rhs' has to be selected.")
169
## construct auxiliary terms object
170
mt <- terms(object, lhs = lhs, rhs = rhs, data = data)
172
## subset model frame
173
ix <- attr(mt, "variables")[-1]
177
ix <- sapply(ix, deparse)
178
if(!all(ix %in% names(data))) stop(
179
paste("'data' does not seem to be an appropriate 'model.frame':",
180
paste(paste("'", ix[!(ix %in% names(data))], "'", sep = ""), collapse = ", "),
184
rval <- data[, ix, drop = drop]
185
if(!is.data.frame(rval)) names(rval) <- rownames(data)
186
if(terms) attr(rval, "terms") <- mt
190
update.Formula <- function(object, new,...) {
194
## extract all building blocks
195
o_lhs <- attr(object, "lhs")
196
o_rhs <- attr(object, "rhs")
197
n_lhs <- attr(new, "lhs")
198
n_rhs <- attr(new, "rhs")
199
lhs <- rep(list(NULL), length = max(length(o_lhs), length(n_lhs)))
200
rhs <- rep(list(NULL), length = max(length(o_rhs), length(n_rhs)))
202
## convenience function for updating components
203
update_components <- function(x, y) {
210
if(length(lhs) > 0) for(i in 1:length(lhs)) {
211
lhs[[i]] <- if(length(o_lhs) < i) n_lhs[[i]]
212
else if(length(n_lhs) < i) o_lhs[[i]]
213
else update_components(o_lhs[[i]], n_lhs[[i]])
216
if(length(rhs) > 0) for(i in 1:length(rhs)) {
217
rhs[[i]] <- if(length(o_rhs) < i) n_rhs[[i]]
218
else if(length(n_rhs) < i) o_rhs[[i]]
219
else update_components(o_rhs[[i]], n_rhs[[i]])
223
rval <- paste_formula(lhs, rhs)
226
## (we have everything to do this by hand, but for encapsulating code
227
## call Formula() again...which splits again)
228
rval <- Formula(rval)
230
## preserve original environment
231
environment(rval) <- environment(object)
236
length.Formula <- function(x) {
237
## NOTE: return length of both sides, not only rhs
238
c(length(attr(x, "lhs")), length(attr(x, "rhs")))
241
print.Formula <- function(x, ...) {
242
## we could avoid calling formula() by computing on the internal
243
## structure attr(x, "rhs") <- attr(x, "lhs") <- NULL
244
## but this is probably cleaner...
249
all.equal.Formula <- function(target, current, ...) {
252
if(length(target)[1] != length(current)[1]) {
253
rval <- c(rval, paste("Length mismatch: target, current differ in number of LHS parts: ",
254
length(target)[1], ", ", length(current)[1], sep = ""))
255
} else if(!isTRUE(all.equal(attr(target, "lhs"), attr(current, "lhs")))) {
256
rval <- c(rval, "Formula mismatch: LHS formulas differ in contents")
259
if(length(target)[2] != length(current)[2]) {
260
rval <- c(rval, paste("Length mismatch: target, current differ in number of RHS parts: ",
261
length(target)[2], ", ", length(current)[2], sep = ""))
262
} else if(!isTRUE(all.equal(attr(target, "rhs"), attr(current, "rhs")))) {
263
rval <- c(rval, "Formula mismatch: RHS formulas differ in contents")
266
if(is.null(rval)) TRUE else rval
269
## convenience tools #################################################
272
split_formula <- function(f) {
274
stopifnot(inherits(f, "formula"))
276
rhs <- if(length(f) > 2) f[[3]] else f[[2]]
277
lhs <- if(length(f) > 2) f[[2]] else NULL
279
extract_parts <- function(x, sep = "|") {
280
if(is.null(x)) return(NULL)
283
if(length(x) > 1 && x[[1]] == sep) {
284
while(length(x) > 1 && x[[1]] == sep) {
285
rval <- c(x[[3]], rval)
292
list(lhs = extract_parts(lhs), rhs = extract_parts(rhs))
295
## reassemble formulas
296
paste_formula <- function(lhs, rhs, lsep = "|", rsep = "|") {
298
## combine (parts of) formulas
299
c_formula <- function(f1, f2, sep = "~") {
301
stopifnot(length(sep) == 1, nchar(sep) == 1,
302
sep %in% c("~", "+", "|", "&"))
309
rval <- as.formula(paste(". ~ .", sep, "."))
318
stopifnot(all(nchar(lsep) == 1), all(lsep %in% c("+", "|", "&")))
319
stopifnot(all(nchar(rsep) == 1), all(rsep %in% c("+", "|", "&")))
321
if(length(lhs) > 1) lsep <- rep(lsep, length.out = length(lhs) - 1)
322
if(length(rhs) > 1) rsep <- rep(rsep, length.out = length(rhs) - 1)
324
if(is.null(lhs)) lhs <- list()
325
if(is.null(rhs)) rhs <- list()
327
if(!is.list(lhs)) lhs <- list(lhs)
328
if(!is.list(rhs)) rhs <- list(rhs)
330
lval <- if(length(lhs) > 0) lhs[[1]] else NULL
331
if(length(lhs) > 1) {
332
for(i in 2:length(lhs)) lval <- c_formula(lval, lhs[[i]], sep = lsep[[i-1]])
334
rval <- if(length(rhs) > 0) rhs[[1]] else 0 ## FIXME: Is there something better?
335
if(length(rhs) > 1) {
336
for(i in 2:length(rhs)) rval <- c_formula(rval, rhs[[i]], sep = rsep[[i-1]])
339
c_formula(lval, rval, sep = "~")