Как вычислить минимальную, но быструю линейную регрессию для каждого столбца матрицы ответов?

Я хочу вычислить обычные оценки методом наименьших квадратов (OLS) в R без использования lm, и это по нескольким причинам. Во-первых, lm также вычисляет множество вещей, которые мне не нужны (например, подогнанные значения), учитывая, что размер данных в моем случае является проблемой. Во-вторых, я хочу иметь возможность самостоятельно реализовать OLS в R, прежде чем делать это на другом языке (например, на C с GSL).

Как вы, возможно, знаете, модель: Y = Xb + E; с E ~ N (0, sigma ^ 2). Как подробно описано ниже, b - это вектор с двумя параметрами: средним значением (b0) и другими коэффициентами (b1). В конце, для каждой линейной регрессии, которую я буду делать, мне нужна оценка для b1 (размер эффекта), ее стандартная ошибка, оценка для сигма ^ 2 (остаточная дисперсия) и R ^ 2 (коэффициент детерминации).

Вот мои данные. У меня есть N образцов (например, индивидуумы, N ~ = 100). Для каждого образца у меня есть выходы Y (ответные переменные, Y ~ = 10 ^ 3) и X точек (независимые переменные, X ~ = 10 ^ 6). Я хочу рассматривать выходы Y отдельно, т.е. Я хочу запустить Y линейную регрессию: одну для выхода 1, одну для выхода 2 и т. Д. Более того, я хочу использовать независимые переменные один y один: для выхода 1 я хочу регрессировать его в точке 1, затем в точке 2, затем ... наконец, по пункту X (надеюсь, все ясно ...!)

Вот мой R-код для проверки скорости "lm" по сравнению с вычислением оценок OLS с помощью матричной алгебры.

Сначала я моделирую фиктивные данные:

nb.samples <-  10  # N
nb.points <- 1000  # X
x <- matrix(data=replicate(nb.samples,sample(x=0:2,size=nb.points, replace=T)),
            nrow=nb.points, ncol=nb.samples, byrow=F,
            dimnames=list(points=paste("p",seq(1,nb.points),sep=""),
              samples=paste("s",seq(1,nb.samples),sep="")))
nb.outputs <- 10  # Y
y <- matrix(data=replicate(nb.outputs,rnorm(nb.samples)),
            nrow=nb.samples, ncol=nb.outputs, byrow=T,
            dimnames=list(samples=paste("s",seq(1,nb.samples),sep=""),
              outputs=paste("out",seq(1,nb.outputs),sep="")))

Вот моя собственная функция, используемая чуть ниже:

GetResFromCustomLinReg <- function(Y, xi){ # both Y and xi are N-dim vectors
  n <- length(Y)
  X <- cbind(rep(1,n), xi)  #
  p <- 1      # nb of explanatory variables, besides the mean
  r <- p + 1  # rank of X: nb of indepdt explanatory variables
  inv.XtX <- solve(t(X) %*% X)
  beta.hat <- inv.XtX %*% t(X) %*% Y
  Y.hat <- X %*% beta.hat
  E.hat <- Y - Y.hat
  E2.hat <- (t(E.hat) %*% E.hat)
  sigma2.hat <- (E2.hat / (n - r))[1,1]
  var.covar.beta.hat <- sigma2.hat * inv.XtX
  se.beta.hat <- t(t(sqrt(diag(var.covar.beta.hat))))
  Y.bar <- mean(Y)
  R2 <- 1 - (E2.hat) / (t(Y-Y.bar) %*% (Y-Y.bar))
  return(c(beta.hat[2], se.beta.hat[2], sigma2.hat, R2))
}

Вот мой код с использованием встроенного lm:

res.bi.all <- apply(x, 1, function(xi){lm(y ~ xi)})

Вот мой собственный код OLS:

res.cm.all <- apply(x, 1, function(xi){apply(y, 2, GetResFromCustomLinReg, xi)})

Когда я запускаю этот пример со значениями, указанными выше, я получаю:

> system.time( res.bi.all <- apply(x, 1, function(xi){lm(y ~ xi)}) )
   user  system elapsed
  2.526   0.000   2.528
> system.time( res.cm.all <- apply(x, 1, function(xi){apply(y, 2, GetResFromCustomLinReg, xi)}) )
   user  system elapsed
  4.561   0.000   4.561

(И, естественно, становится хуже при увеличении N, X и Y.)

Конечно, «lm» имеет приятное свойство «автоматически» подбирать отдельно каждый столбец матрицы ответа (y ~ xi), в то время как я должен использовать «применить» Y раз (для каждого yi ~ xi). Но разве это единственная причина, по которой мой код работает медленнее? Кто-нибудь из вас знает, как это исправить?

(Извините за такой длинный вопрос, но я действительно попытался привести минимальный, но исчерпывающий пример.)

> sessionInfo()
R version 2.12.2 (2011-02-25)
Platform: x86_64-redhat-linux-gnu (64-bit)

person tflutre    schedule 04.06.2011    source источник


Ответы (2)


Взгляните на функцию fastLm() в пакете RcppArmadillo в CRAN. Также есть похожий fastLm() в RcppGSL, который предшествовал этому, но вы, вероятно, захотите Решение на основе Armadillo. У меня есть несколько слайдов в старых презентациях (на HPC с R), которые показывают прирост скорости.

Также обратите внимание на подсказку на странице справки о лучших «поворотных» подходах, чем прямая инверсия X'X, которая может иметь значение с вырожденными модельными матрицами.

person Dirk Eddelbuettel    schedule 04.06.2011
comment
Большое спасибо, Дирк! (Извините за мой поздний ответ). Однако я обнаружил, что использование lm.fit напрямую было быстрее, чем fastLmPure (которое само по себе быстрее, чем fastLm). Таким образом, могу ли я добавить свой ответ на свой вопрос? - person tflutre; 06.06.2011
comment
@wfoolhill Определенно да. Это слишком важная вещь, чтобы оставлять ее в комментариях. - person Marek; 07.06.2011
comment
У меня был такой же опыт. fastLmPure был примерно на той же скорости, что и lm.fit. это ожидается? Я тестировал X = matrix (rnorm (1e8), 1e6,100); y = rnorm (1e6); - person vc273; 22.02.2014
comment
lm.fit выполняется быстро, но не создает и не возвращает ковариационную матрицу, поэтому вы не можете сделать вывод. Но есть fastLm* варианта. - person Dirk Eddelbuettel; 22.02.2014

Следуя комментарию Марека, ниже приведены результаты сравнения встроенных функций «lm» и «lm.fit», моей собственной функции, «fastLm» и «fastLmPure» из пакета RcppArmadillo:

> system.time( res1 <- apply(x, 1, function(xi){lm(y ~ xi)}) )
   user  system elapsed
  2.859   0.005   2.865
> system.time( res2 <- apply(x, 1, function(xi){apply(y, 2, GetResFromCustomLinReg, xi)}) )
   user  system elapsed
  4.620   0.004   4.626
> system.time( res3 <- apply(x, 1, function(xi){lm.fit(x=cbind(1,xi), y=y)}) )
   user  system elapsed
  0.454   0.004   0.458
> system.time( res4 <- apply(x, 1, function(xi){apply(y, 2, fastLm, x=cbind(1,xi))}) )
   user  system elapsed
  2.279   0.005   2.283
> system.time( res5 <- apply(x, 1, function(xi){apply(y, 2, fastLmPure, cbind(1,xi))}) )
   user  system elapsed
  1.053   0.003   1.056

Однако будьте осторожны при сравнении этих чисел. Различия связаны не только с разными реализациями, но и с тем, как эффективно вычисляются результаты:

> names(res1$p1)
 [1] "coefficients"  "residuals"     "effects"       "rank"        
 [5] "fitted.values" "assign"        "qr"            "df.residual" 
 [9] "xlevels"       "call"          "terms"         "model"       
> # res2 (from my own custom function) returns the estimate of beta, its standard error, the estimate of sigma and the R^2
> names(res3$p1)
[1] "coefficients"  "residuals"     "effects"       "rank"        
[5] "fitted.values" "assign"        "qr"            "df.residual" 
> names(res4$p1$out1)
[1] "coefficients"  "stderr"        "df"            "fitted.values"
[5] "residuals"     "call"        
> names(res5$p1$out1)
[1] "coefficients" "stderr"       "df"         

Например, мы можем предпочесть использовать «lm.fit» вместо «lm», но если нам понадобится R ^ 2, нам придется вычислить его самостоятельно. То же самое, мы можем захотеть использовать «fastLm» вместо «lm», но если нам нужна оценка сигмы, нам придется вычислить ее самостоятельно. И вычисление таких вещей с помощью специальной функции R может быть не очень эффективным (по сравнению с тем, что делает «lm»).

В свете всего этого я пока буду продолжать использовать «lm», но действительно комментарий Дирка о «fastLm» - хороший совет (поэтому я выбрал его ответ, поскольку он должен быть интересен другим).

person tflutre    schedule 27.06.2011
comment
Также есть lsfit, который также имеет меньше накладных расходов, чем lm, и предназначен для непосредственного вызова (lm.fit рекомендует не использовать его напрямую на своей странице справки; не знаю, почему). - person Aaron left Stack Overflow; 27.06.2011