Подходит много формул одновременно, более быстрые варианты, чем lapply?
У меня есть список формул, которые я хочу вписать в данные, вместо того, чтобы запускать цикл, я бы хотел сделать это сразу, ради производительности. Оценки все еще должны быть отдельными, я не пытаюсь оценить SUR или что-то еще. Следующий код делает то, что я хочу
x <- matrix(rnorm(300),ncol=3)
y <- x %*% c(1,2,3)+rnorm(100)
formulae <-list(y~x[,1],
y~x[,2],
y~x[,1] + x[,2])
lapply(formulae,lm)
К сожалению, это становится несколько медленным, так как длина formulae
увеличивается, есть ли способ действительно векторизовать это?
Если это какая-либо помощь, единственные результаты lm
Меня волнуют коэффициенты и некоторые стандартные ошибки.
2 ответа
Как я уже сказал в своем комментарии, вам действительно нужна более эффективная, но стабильная подпрограмма, отличная от lm()
, Здесь я хотел бы предоставить вам хорошо проверенный написанный мной, названный lm.chol()
, Требуется formula
а также data
и возвращает:
- Сводная таблица коэффициентов, как вы обычно видите в
summary(lm(...))$coef
; - Оценка Пирсона остаточной стандартной ошибки, полученная из
summary(lm(...))$sigma
; - откорректированный-R.squared, как вы получаете от
summary(lm(...))$adj.r.squared
,
## linear model estimation based on pivoted Cholesky factorization with Jacobi preconditioner
lm.chol <- function(formula, data) {
## stage0: get response vector and model matrix
## we did not follow the normal route: match.call, model.frame, model.response, model matrix, etc
y <- data[[as.character(formula[[2]])]]
X <- model.matrix(formula, data)
n <- nrow(X); p <- ncol(X)
## stage 1: XtX and Jacobi diagonal preconditioner
XtX <- crossprod(X)
D <- 1 / sqrt(diag(XtX))
## stage 2: pivoted Cholesky factorization
R <- suppressWarnings(chol(t(D * t(D * XtX)), pivot = TRUE))
piv <- attr(R, "pivot")
r <- attr(R, "rank")
if (r < p) {
warning("Model is rank-deficient!")
piv <- piv[1:r]
R <- R[1:r, 1:r]
}
## stage 3: solve linear system for coefficients
D <- D[piv]
b <- D * crossprod(X, y)[piv]
z <- forwardsolve(t(R), b)
RSS <- sum(y * y) - sum(z * z)
sigma <- sqrt(RSS / (n - r))
para <- D * backsolve(R, z)
beta.hat <- rep(NA, p)
beta.hat[piv] <- para
## stage 4: get standard error
Rinv <- backsolve(R, diag(r))
se <- rep(NA, p)
se[piv] <- D * sqrt(rowSums(Rinv * Rinv)) * sigma
## stage 5: t-statistic and p-value
t.statistic <- beta.hat / se
p.value <- 2 * pt(-abs(t.statistic), df = n - r)
## stage 6: construct coefficient summary matrix
coefficients <- matrix(c(beta.hat, se, t.statistic, p.value), ncol = 4L)
colnames(coefficients) <- c("Estimate", "Std. Error", "t value", "Pr(>|t|)")
rownames(coefficients) <- colnames(X)
## stage 7: compute adjusted R.squared
adj.R2 <- 1 - sigma * sigma / var(y)
## return model fitting results
attr(coefficients, "sigma") <- sigma
attr(coefficients, "adj.R2") <- adj.R2
coefficients
}
Здесь я бы предложил три примера.
Пример 1: линейная модель полного ранга
Мы берем встроенный набор данных R trees
В качестве примера.
# using `lm()`
summary(lm(Height ~ Girth + Volume, trees))
#Coefficients:
# Estimate Std. Error t value Pr(>|t|)
#(Intercept) 83.2958 9.0866 9.167 6.33e-10 ***
#Girth -1.8615 1.1567 -1.609 0.1188
#Volume 0.5756 0.2208 2.607 0.0145 *
#---
#Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#Residual standard error: 5.056 on 28 degrees of freedom
#Multiple R-squared: 0.4123, Adjusted R-squared: 0.3703
#F-statistic: 9.82 on 2 and 28 DF, p-value: 0.0005868
## using `lm.chol()`
lm.chol(Height ~ Girth + Volume, trees)
# Estimate Std. Error t value Pr(>|t|)
#(Intercept) 83.2957705 9.0865753 9.166905 6.333488e-10
#Girth -1.8615109 1.1566879 -1.609346 1.187591e-01
#Volume 0.5755946 0.2208225 2.606594 1.449097e-02
#attr(,"sigma")
#[1] 5.056318
#attr(,"adj.R2")
#[1] 0.3702869
Результаты точно такие же!
Пример 2: линейная модель с недостатком ранга
## toy data
set.seed(0)
dat <- data.frame(y = rnorm(100), x1 = runif(100), x2 = rbeta(100,3,5))
dat$x3 <- with(dat, (x1 + x2) / 2)
## using `lm()`
summary(lm(y ~ x1 + x2 + x3, dat))
#Coefficients: (1 not defined because of singularities)
# Estimate Std. Error t value Pr(>|t|)
#(Intercept) 0.2164 0.2530 0.856 0.394
#x1 -0.1526 0.3252 -0.469 0.640
#x2 -0.3534 0.5707 -0.619 0.537
#x3 NA NA NA NA
#Residual standard error: 0.8886 on 97 degrees of freedom
#Multiple R-squared: 0.0069, Adjusted R-squared: -0.01358
#F-statistic: 0.337 on 2 and 97 DF, p-value: 0.7147
## using `lm.chol()`
lm.chol(y ~ x1 + x2 + x3, dat)
# Estimate Std. Error t value Pr(>|t|)
#(Intercept) 0.2164455 0.2529576 0.8556595 0.3942949
#x1 NA NA NA NA
#x2 -0.2007894 0.6866871 -0.2924030 0.7706030
#x3 -0.3051760 0.6504256 -0.4691944 0.6399836
#attr(,"sigma")
#[1] 0.8886214
#attr(,"adj.R2")
#[1] -0.01357594
#Warning message:
#In lm.chol(y ~ x1 + x2 + x3, dat) : Model is rank-deficient!
Вот, lm.chol()
основанный на факторизации Холецкого с полным поворотом и lm()
основанные на факторизации QR с частичным поворотом сократили различные коэффициенты до NA
, Но две оценки эквивалентны, с одинаковыми установленными значениями и остатками.
Пример 3: производительность для больших линейных моделей
n <- 10000; p <- 300
set.seed(0)
dat <- as.data.frame(setNames(replicate(p, rnorm(n), simplify = FALSE), paste0("x",1:p)))
dat$y <- rnorm(n)
## using `lm()`
system.time(lm(y ~ ., dat))
# user system elapsed
# 3.212 0.096 3.315
## using `lm.chol()`
system.time(lm.chol(y ~ ., dat))
# user system elapsed
# 1.024 0.028 1.056
lm.chol()
в 3 ~ 4 раза быстрее, чем lm()
, Если вы хотите узнать причину, прочитайте мой ответ.
замечание
Я сосредоточился на улучшении производительности вычислительного ядра. Вы можете сделать еще один шаг, используя предложение параллельности Бена Болкера. Если мой подход дает увеличение в 3 раза, а параллельные вычисления дают увеличение в 3 раза на 4 ядрах, вы получите увеличение в 9 раз!
Там не очень простой способ векторизовать это, но pdredge
функция от MuMIn
Пакет дает вам довольно простой способ распараллелить его (это предполагает, что у вас есть несколько ядер на вашем компьютере или вы можете настроить локальный кластер одним из способов, поддерживаемых parallel
пакет...
library(parallel)
clust <- makeCluster(2,"PSOCK")
library(MuMIn)
Построить данные:
set.seed(101)
x <- matrix(rnorm(300),ncol=3)
y <- x %*% c(1,2,3)+rnorm(100)
Это будет легче сделать с помощью именованного фрейма данных, а не анонимной матрицы:
df <- setNames(data.frame(y,x),c("y",paste0("x",1:3)))
Всем узлам кластера необходим доступ к набору данных:
clusterExport(clust,"df")
Подходит полная модель (вы могли бы использовать y~.
чтобы соответствовать всем переменным)
full <- lm(y~x1+x2,data=df,na.action=na.fail)
Теперь подойдут все подмодели (см. ?MuMIn::dredge
для еще многих опций, чтобы контролировать, какие подмодели установлены)
p <- pdredge(full,cluster=clust)
coef(p)
## (Intercept) x1 x2
## 3 -0.003805107 0.7488708 2.590204
## 2 -0.028502039 NA 2.665305
## 1 -0.101434662 1.0490816 NA
## 0 -0.140451160 NA NA