Comparisons
Comparisons
Comparisons
Douglas Bates
R Development Core Team
[email protected]
December 8, 2021
Abstract
Many statistics methods require one or more least squares problems
to be solved. There are several ways to perform this calculation, using
objects from the base R system and using objects in the classes defined
in the Matrix package.
We compare the speed of some of these methods on a very small ex-
ample and on a example for which the model matrix is large and sparse.
when X has full column rank (i.e. the columns of X are linearly independent)
and all too frequently it is calculated in exactly this way.
1
> (m <- cbind(1, Formaldehyde$carb))
[,1] [,2]
[1,] 1 0.1
[2,] 1 0.3
[3,] 1 0.5
[4,] 1 0.6
[5,] 1 0.7
[6,] 1 0.9
Using t to evaluate the transpose, solve to take an inverse, and the %*% operator
for matrix multiplication, we can translate 2 into the S language as
> solve(t(m) %*% m) %*% t(m) %*% yo
[,1]
[1,] 0.005085714
[2,] 0.876285714
and it provides essentially the same results as the standard lm.fit function that
is called by lm.
> dput(c(solve(t(m) %*% m) %*% t(m) %*% yo))
c(0.00508571428571428, 0.876285714285715)
c(0.00508571428571408, 0.876285714285715)
1 FromR version 2.2.0, system.time() has default argument gcFirst = TRUE which is as-
sumed and relevant for all subsequent timings
2
1.2 A large example
For a large, ill-conditioned least squares problem, such as that described in
Koenker and Ng (2003), the literal translation of (2) does not perform well.
> library(Matrix)
> data(KNex, package = "Matrix")
> y <- KNex$y
> mm <- as(KNex$mm, "matrix") # full traditional matrix
> dim(mm)
X T X βb = X T y
(3)
is solved directly.
Combining these optimizations we obtain
> system.time(cpod.sol <- solve(crossprod(mm), crossprod(mm,y)))
[1] TRUE
3
Note that in newer versions of R and the BLAS library (as of summer 2007),
R’s %*% is able to detect the many zeros in mm and shortcut many operations, and
is hence much faster for such a sparse matrix than crossprod which currently
does not make use of such optimizations. This is not the case when R is linked
against an optimized BLAS library such as GOTO or ATLAS. Also, for fully
dense matrices, crossprod() indeed remains faster (by a factor of two, typically)
independently of the BLAS library:
> fm <- mm
> set.seed(11)
> fm[] <- rnorm(length(fm))
> system.time(c1 <- t(fm) %*% fm)
user system elapsed
0.6 0.0 0.6
> system.time(c2 <- crossprod(fm))
user system elapsed
0.488 0.000 0.488
> stopifnot(all.equal(c1, c2, tol = 1e-12))
4
> mm <- as(KNex$mm, "dgeMatrix")
> class(crossprod(mm))
[1] "dpoMatrix"
attr(,"package")
[1] "Matrix"
The model matrix mm is sparse; that is, most of the elements of mm are zero.
The Matrix package incorporates special methods for sparse matrices, which
produce the fastest results of all.
[1] "dgCMatrix"
attr(,"package")
[1] "Matrix"
As with other classes in the Matrix package, the dsCMatrix retains any
factorization that has been calculated although, in this case, the decomposition
is so fast that it is difficult to determine the difference in the solution times.
5
> xpx <- crossprod(mm)
> xpy <- crossprod(mm, y)
> system.time(solve(xpx, xpy))
user system elapsed
0.001 0.000 0.001
> system.time(solve(xpx, xpy))
user system elapsed
0.001 0.000 0.001
Session Info
> toLatex(sessionInfo())
• R version 4.1.2 Patched (2021-12-04 r81295), x86_64-pc-linux-gnu
• Locale: LC_CTYPE=en_US.UTF-8, LC_NUMERIC=C, LC_TIME=en_US.UTF-8,
LC_COLLATE=C, LC_MONETARY=en_US.UTF-8, LC_MESSAGES=en_US.UTF-8,
LC_PAPER=en_US.UTF-8, LC_NAME=C, LC_ADDRESS=C, LC_TELEPHONE=C,
LC_MEASUREMENT=en_US.UTF-8, LC_IDENTIFICATION=C
• Running under: Debian GNU/Linux 11 (bullseye)
• Matrix products: default
• BLAS: /srv/R/R-patched/build.21-12-06/lib/libRblas.so
• LAPACK: /srv/R/R-patched/build.21-12-06/lib/libRlapack.so
• Base packages: base, datasets, grDevices, graphics, methods, stats, utils
• Other packages: Matrix 1.4-0
• Loaded via a namespace (and not attached): compiler 4.1.2, grid 4.1.2,
lattice 0.20-45, tools 4.1.2
> if(identical(1L, grep("linux", R.version[["os"]]))) { ## Linux - only ---
+ Scpu <- sfsmisc::Sys.procinfo("/proc/cpuinfo")
+ Smem <- sfsmisc::Sys.procinfo("/proc/meminfo")
+ print(Scpu[c("model name", "cpu MHz", "cache size", "bogomips")])
+ print(Smem[c("MemTotal", "SwapTotal")])
+ }
_
model name Intel(R) Xeon(R) Gold 6226R CPU @ 2.90GHz
cpu MHz 2893.222
cache size 22528 KB
bogomips 5786.44
_
MemTotal 24591860 kB
SwapTotal 8388604 kB
6
References
John M. Chambers. Programming with Data. Springer, New York, 1998. ISBN
0-387-98503-4.
Roger Koenker and Pin Ng. SparseM: A sparse matrix package for R. J. of
Statistical Software, 8(6), 2003.