Fitting a linear model with multiple LHS

2019-01-01 16:00发布

问题:

I am new to R and I want to improve the following script with an *apply function (I have read about apply, but I couldn\'t manage to use it). I want to use lm function on multiple independent variables (which are columns in a data frame). I used

for (i in (1:3) {
  assign(paste0(\'lm.\',names(data[i])), lm(formula=formula(i),data=data))
  } 

Formula(i) is defined as

formula=function(x)
{
  as.formula ( paste(names(data[x]),\'~\', paste0(names(data[-1:-3]), collapse = \'+\')), env=parent.frame() )
}

Thank you.

回答1:

If I don\'t get you wrong, you are working with a dataset like this:

set.seed(0)
dat <- data.frame(y1 = rnorm(30), y2 = rnorm(30), y3 = rnorm(30),
                  x1 = rnorm(30), x2 = rnorm(30), x3 = rnorm(30))

x1, x2 and x3 are covariates, and y1, y2, y3 are three independent response. You are trying to fit three linear models:

y1 ~ x1 + x2 + x3
y2 ~ x1 + x2 + x3
y3 ~ x1 + x2 + x3

Currently you are using a loop through y1, y2, y3, fitting one model per time. You hope to speed the process up by replacing the for loop with lapply.

You are on the wrong track. lm() is an expensive operation. As long as your dataset is not small, the costs of for loop is negligible. Replacing for loop with lapply gives no performance gains.

Since you have the same RHS (right hand side of ~) for all three models, model matrix is the same for three models. Therefore, QR factorization for all models need only be done once. lm allows this, and you can use:

fit <- lm(cbind(y1, y2, y3) ~ x1 + x2 + x3, data = dat)
#Coefficients:
#             y1         y2         y3       
#(Intercept)  -0.081155   0.042049   0.007261
#x1           -0.037556   0.181407  -0.070109
#x2           -0.334067   0.223742   0.015100
#x3            0.057861  -0.075975  -0.099762

If you check str(fit), you will see that this is not a list of three linear models; instead, it is a single linear model with a single $qr object, but with multiple LHS. So $coefficients, $residuals and $fitted.values are matrices. The resulting linear model has an additional \"mlm\" class besides the usual \"lm\" class. I created a special mlm tag collecting some questions on the theme, summarized by its tag wiki.

If you have a lot more covariates, you can avoid typing or pasting formula by using .:

fit <- lm(cbind(y1, y2, y3) ~ ., data = dat)
#Coefficients:
#             y1         y2         y3       
#(Intercept)  -0.081155   0.042049   0.007261
#x1           -0.037556   0.181407  -0.070109
#x2           -0.334067   0.223742   0.015100
#x3            0.057861  -0.075975  -0.099762

Caution: Do not write

y1 + y2 + y3 ~ x1 + x2 + x3

This will treat y = y1 + y2 + y3 as a single response. Use cbind().


Follow-up:

I am interested in a generalization. I have a data frame df, where first n columns are dependent variables (y1,y2,y3,....) and next m columns are independent variables (x1+x2+x3+....). For n = 3 and m = 3 it is fit <- lm(cbind(y1, y2, y3) ~ ., data = dat)). But how to do this automatically, by using the structure of the df. I mean something like (for i in (1:n)) fit <- lm(cbind(df[something] ~ df[something], data = dat)). That \"something\" I have created it with paste and paste0. Thank you.

So you are programming your formula, or want to dynamically generate / construct model formulae in the loop. There are many ways to do this, and many Stack Overflow questions are about this. There are commonly two approaches:

  1. use reformulate;
  2. use paste / paste0 and formula / as.formula.

I prefer to reformulate for its neatness, however, it does not support multiple LHS in the formula. It also needs some special treatment if you want to transform the LHS. So In the following I would use paste solution.

For you data frame df, you may do

paste0(\"cbind(\", paste(names(df)[1:n], collapse = \", \"), \")\", \" ~ .\")

A more nice-looking way is to use sprintf and toString to construct the LHS:

sprintf(\"cbind(%s) ~ .\", toString(names(df)[1:n]))

Here is an example using iris dataset:

string_formula <- sprintf(\"cbind(%s) ~ .\", toString(names(iris)[1:2]))
# \"cbind(Sepal.Length, Sepal.Width) ~ .\"

You can pass this string formula to lm, as lm will automatically coerce it into formula class. Or you may do the coercion yourself using formula (or as.formula):

formula(string_formula)
# cbind(Sepal.Length, Sepal.Width) ~ .

Remark:

This multiple LHS formula is also supported elsewhere in R core:

  • the formula method for function aggregate;
  • ANOVA analysis with aov.