The third option is to update the R matrix in the QR decomposition, as is done in the code below. You can speed it up by doing this in C ++, but you will need the dchud and dchdd from LINPACK (or another function to update R)
library(SamplerCompare) # for LINPACK `chdd` and `chud` roll_coef <- function(X, y, width){ n <- nrow(X) p <- ncol(X) out <- matrix(NA_real_, n, p) is_first <- TRUE i <- width while(i <= n){ if(is_first){ is_first <- FALSE qr. <- qr(X[1:width, ]) R <- qr.R(qr.) # Use X^T for the rest X <- t(X) XtY <- drop(tcrossprod(y[1:width], X[, 1:width])) } else { x_new <- X[, i] x_old <- X[, i - width] # update RR <- .Fortran( "dchud", R, p, p, x_new, 0., 0L, 0L, 0., 0., numeric(p), numeric(p), PACKAGE = "SamplerCompare")[[1]] # downdate R R <- .Fortran( "dchdd", R, p, p, x_old, 0., 0L, 0L, 0., 0., numeric(p), numeric(p), integer(1), PACKAGE = "SamplerCompare")[[1]] # update XtY XtY <- XtY + y[i] * x_new - y[i - width] * x_old } coef. <- .Internal(backsolve(R, XtY, p, TRUE, TRUE)) out[i, ] <- .Internal(backsolve(R, coef., p, TRUE, FALSE)) i <- i + 1 } out } # simulate data set.seed(101) n <- 1000 wdth = 100 X <- matrix(rnorm(10 * n), n, 10) y <- drop(X %*% runif(10)) + rnorm(n) Z <- cbind(y, X) # assign other function dolm <- function(x) coef(lm.fit(x[, -1], x[, 1])) # show that they yield the same library(zoo) all.equal( rollapply(Z, wdth, FUN = dolm, by.column = FALSE, align = "right", fill = NA_real_), roll_coef(X, y, wdth), check.attributes = FALSE) #R> [1] TRUE # benchmark library(compiler) roll_coef <- cmpfun(roll_coef) dolm <- cmpfun(dolm) microbenchmark::microbenchmark( new = roll_coef(X, y, wdth), prev = rollapply(Z, wdth, FUN = dolm, by.column = FALSE, align = "right", fill = NA_real_), times = 10) #R> Unit: milliseconds #R> expr min lq mean median uq max neval cld #R> new 8.631319 9.010579 9.808525 9.659665 9.973741 11.87083 10 a #R> prev 118.257128 121.734860 124.489826 122.882318 127.195410 135.21280 10 b
To solve the above, you first need to form model.matrix and model.response , but these are just three calls (one additional to model.frame ) before calling roll_coef .