Parses a model formula, builds the model frame and classifies the resulting
design into one of seven dependency structures. The pieces of the design are
returned under a fixed set of names, so that every function offering a
formula interface can share one entry point instead of re-implementing the
parsing, the subset handling and the distinction between a grouping
factor, a numeric predictor and a blocking variable.
Usage
resolveFormula(
formula,
data,
subset = NULL,
na.action = na.pass,
allowed = c("one-sample", "two-sample-independent", "two-sample-dependent",
"n-sample-independent", "n-sample-dependent", "numeric-numeric", "regression")
)Arguments
- formula
a two-sided model formula. Supported forms are:
y ~ 1one-sample design.
Pair(x, y) ~ 1two-sample dependent (paired).
Pair()constructs a two-column matrix of paired observations.y ~ gtwo-sample or n-sample independent group comparison.
y ~ x,xnumericnumeric-numeric (correlation, simple regression).
y ~ x1 + x2 + ...general regression.
y ~ trt | blockn-sample dependent (blocked design).
- data
an optional data frame containing the variables in
formula. A matrix is coerced to a data frame.- subset
an already captured subset expression, an index vector, or
NULL(the default). The argument is taken by value, never bysubstitute(). See Details.- na.action
a function specifying how missing values are handled, defaults to
na.pass().- allowed
a character vector restricting which design types are accepted, any combination of
"one-sample","two-sample-independent","two-sample-dependent","n-sample-independent","n-sample-dependent","numeric-numeric"and"regression". The values are matched exactly, an unknown one is an error rather than being ignored. A further error is raised if the detected type is not among the allowed ones. Defaults to all types.
Value
a named list containing at least:
- type
character, one of the design types listed above.
- mf
the
model.frame()the design was read from.- rows
integer, the positions of the retained observations in the original data, or
NULLif they cannot be determined.- response
the left-hand side of the formula.
- dataName
character, the deparsed formula, for use as the
data.nameof anhtestobject.
plus the design-specific components described under Details.
Details
Design types
one-sampley ~ 1, as in the one-sample t-test or the one-sample Wilcoxon test.two-sample-independenty ~ gwith two groups, as in the two-sample t-test or the Wilcoxon rank-sum test.two-sample-dependentPair(x, y) ~ 1, as in the paired t-test or the Wilcoxon signed-rank test.n-sample-independenty ~ gwith more than two groups, as in the analysis of variance or the Kruskal-Wallis test.n-sample-dependenty ~ trt | block, as in a repeated-measures analysis of variance or the Friedman test.numeric-numericy ~ xwith a numeric right-hand side, as in correlation or simple regression.regressiona general regression formula with one or more predictors.
Type detection
The type follows from the shape of the formula and from the class of the
right-hand side variable, not from allowed. allowed only decides
whether the detected type is accepted, with four exceptions worth knowing.
A grouping factor carrying a single level is reported as
one-sampleif that type is allowed.A two-group design is reported as
n-sample-independentif"two-sample-independent"is not among the allowed types. Together with the previous rule this lets a caller that treats every group count alike allow one type only.allowed = "regression"on its own forces theregressiontype for every formula, includingy ~ 1andy ~ g. This is the entry point for model-fitting callers, which interpret the right-hand side themselves.Otherwise
regressionis reported only for more than one right-hand side variable. Withallowedcontaining both"regression"and"numeric-numeric",y ~ xis thereforenumeric-numericwhiley ~ x1 + x2isregression.
Field naming contract (binding across all types)
responseis present for every type and always holds the left-hand side of the formula. It is the one field a caller can rely on without branching ontype.xis an alias ofresponsefor the types that are conventionally described in terms of a sample rather than a model (one-sample,two-sample-*,n-sample-independent,numeric-numeric).groupis reserved for a categorical, factor-coercible variable of lengthn(the full sample) that splits the response into groups. It is never pre-split and never used for a continuous variable.xandgrouphave an identical shape fortwo-sample-independentand forn-sample-independent, so that a caller can usesplit(r$x, r$group)uniformly, without branching on the number of groups.predictoris used for a continuous, numeric right-hand side variable (numeric-numeric), nevergroup.treatmentis used for the explanatory variable of a blocked design (n-sample-dependent), as distinct fromblock, the stratification factor. Neither is ever calledgroup.y, where present, is a convenience field only, holding the second group of a two-sample design or the second paired vector. It is never needed for correct use:xandgroup(orxandpredictor, ortreatmentandblock) are always sufficient and are the canonical access path.rowsis present for every type and holds the positions of the retained observations in the original data, aftersubsetand afterna.action. It is the handle for synchronising an external vector (an ordering variable, weights) with the model frame:z <- z[r$rows]. It isNULLin the rare case where the row names of the model frame cannot be matched back, e.g. when a numericsubsetselects a row twice.termsis returned for theregressiontype. Build the design matrix from it,model.matrix(r$terms, r$mf), never from the original formula: the columns of a model frame are named after the deparsed expressions ("log(x)"), so re-evaluating the formula against the model frame fails for every transformed term.
Missing values
Missing values are left to na.action and are not touched otherwise, so
with the default na.pass() they reach the caller untouched. The one
exception is the grouping factor of an independent design, where empty and
missing levels are dropped before the groups are counted. A grouping
variable that is missing throughout leaves no level at all and is an error.
Rows removed by na.action are recorded in attr(r$mf, "na.action"), but
those indices are relative to the already subsetted frame. To align an
external vector with the model frame use rows, which accounts for
subset and na.action at once.
subset handling
subset is taken by value. resolveFormula() does not call
substitute() on it, so the calling function must capture the expression
and hand the resulting language object on:
myFun <- function(formula, data, subset, na.action = na.pass, ...) {
subsetExpr <- if (missing(subset)) NULL else substitute(subset)
resolveFormula(formula, data,
subset = subsetExpr,
na.action = na.action)
}A language object is evaluated in data, with environment(formula) as
the enclosure; anything else is passed on to model.frame() as an index
vector. A bare expression written directly in the call
(resolveFormula(y ~ g, df, subset = g == "A")) is evaluated as an ordinary
argument and therefore only works if the variables live in the caller's
frame; use subset = quote(g == "A") for a column of data.
The model frame is built by constructing the model.frame() call with the
resolved values inlined. Never route this through do.call() with the
default quote = FALSE: model.frame() applies substitute() to its own
subset argument, so an argument that is still a symbol or an unevaluated
call is re-evaluated in the wrong frame.
Return components by type
Every return value contains type, mf, rows, response and
dataName, in that order. The remaining components depend on the design:
one-samplextwo-sample-independentx,group,y(convenience: the second group)two-sample-dependentx,yn-sample-independentx,groupn-sample-dependenttreatment,blocknumeric-numericx,predictorregressionterms
See also
model.frame(), Pair(), resolveGroups()
Other data.resolve:
resolveContingency(),
resolveGroups()
Examples
set.seed(1)
df <- data.frame(
y = rnorm(30, 50, 10),
g2 = rep(c("A", "B"), 15),
g3 = rep(c("A", "B", "C"), 10),
trt = rep(c("T1", "T2", "T3"), 10),
blk = rep(1:10, 3)
)
# one-sample
resolveFormula(y ~ 1, data = df)$type
#> [1] "one-sample"
## [1] "one-sample"
# two-sample independent: x and group have full length, the same shape
# as for more than two groups
r2 <- resolveFormula(y ~ g2, data = df,
allowed = c("two-sample-independent",
"n-sample-independent"))
r2$type
#> [1] "two-sample-independent"
## [1] "two-sample-independent"
length(r2$x) == length(r2$group)
#> [1] TRUE
## [1] TRUE
# n-sample independent
resolveFormula(y ~ g3, data = df,
allowed = "n-sample-independent")$type
#> [1] "n-sample-independent"
## [1] "n-sample-independent"
# two-sample dependent (paired)
df2 <- data.frame(pre = rnorm(15, 50, 10), post = rnorm(15, 55, 10))
resolveFormula(Pair(pre, post) ~ 1, data = df2,
allowed = c("one-sample",
"two-sample-dependent"))$type
#> [1] "two-sample-dependent"
## [1] "two-sample-dependent"
# n-sample dependent (blocked): treatment, not group
r4 <- resolveFormula(y ~ trt | blk, data = df,
allowed = "n-sample-dependent")
names(r4)
#> [1] "type" "mf" "rows" "response" "treatment" "block"
#> [7] "dataName"
## [1] "type" "mf" "rows" "response" "treatment" "block" "dataName"
# numeric-numeric: predictor, not group
df3 <- data.frame(y = rnorm(20), x = rnorm(20))
r5 <- resolveFormula(y ~ x, data = df3, allowed = "numeric-numeric")
is.numeric(r5$predictor)
#> [1] TRUE
## [1] TRUE
# regression: build the design matrix from 'terms', not from the formula
r6 <- resolveFormula(y ~ log(abs(x)) + I(x^2), data = df3,
allowed = "regression")
colnames(model.matrix(r6$terms, r6$mf))
#> [1] "(Intercept)" "log(abs(x))" "I(x^2)"
# subset, captured by the caller
resolveFormula(y ~ g3, data = df, subset = quote(g3 != "C"),
allowed = "two-sample-independent")$type
#> [1] "two-sample-independent"
## [1] "two-sample-independent"
