The accepted answer is certainly not the fastest way to do this on R. This is significantly faster, I don't know if it is the fastest way:
M * outer(rep.int(1L, nrow(M)), v)
Note also a C/C++ based solution to this problem for both matrices and data frames is provided by collapse::TRA
(which in addition supports grouped sweeping operations). Benckmark:
library(microbenchmark)
library(collapse)
all_obj_equal(t(t(M) * v), M * outer(rep.int(1L, nrow(M)), v), TRA(M, v, "*"))
[1] TRUE
microbenchmark(t(t(M) * v), M * outer(rep.int(1L, nrow(M)), v), TRA(M, v, "*"), times = 100, unit = "relative")
Unit: relative
expr min lq mean median uq max neval cld
t(t(M) * v) 16.256563 12.703469 10.771698 12.113859 10.148008 8.365288 100 c
M * outer(rep.int(1L, nrow(M)), v) 1.097177 1.449713 1.375522 1.426085 1.273911 1.785404 100 b
TRA(M, v, "*") 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 100 a
(Note: M here is the Eora 26 ICIO Matrix 2015 of Dimension 4915 x 4915, v is 1/output)
2022 Update
I found that tcrossprod()
is often a bit faster than outer()
, especially for smaller data since it is .Internal
whereas outer has some R overhead. Regarding package solutions, collapse now introduced operators %r*%
etc. to do this effectively, and also the setop
function and operators %+=%
etc. to preform math by reference. Thus here a new benchmark on generated data, showing that C-level math by reference is an order or magnitude faster than efficient base R for this particular problem.
library(collapse) # 1.8+
library(Rfast)
library(microbenchmark)
M <- matrix(rnorm(1e6), 1000) # 1000 x 1000 matrix
v <- rnorm(1000) # 1000 x 1 vector
# Testing numeric equality of methods
all_obj_equal(t(t(M) * v),
M %r*% v,
M * tcrossprod(rep.int(1L, nrow(M)), v),
M * outer(rep.int(1L, nrow(M)), v),
eachrow(M, v, "*"),
setop(M, "*", v, rowwise = TRUE))
#> [1] TRUE
# Undo modification by reference
setop(M, "/", v, rowwise = TRUE)
# Benchmark
microbenchmark(collapse_ref = setop(M, "*", v, rowwise = TRUE),
collapse_ref_rev = setop(M, "/", v, rowwise = TRUE), # Need to reverse operation by reference each iteration
collapse_op = M %r*% v,
tcrossprod = M * tcrossprod(rep.int(1L, nrow(M)), v),
outer = M * outer(rep.int(1L, nrow(M)), v),
Rfast = eachrow(M, v, "*"), times = 10000)
#> Unit: microseconds
#> expr min lq mean median uq max neval
#> collapse_ref 167.731 185.8530 310.1048 212.175 317.3605 16423.616 10000
#> collapse_ref_rev 188.272 205.4510 323.1454 231.527 326.1960 7483.115 10000
#> collapse_op 183.926 925.7595 1794.6258 1157.738 1666.4040 59253.815 10000
#> tcrossprod 1107.779 1959.0005 3118.5997 2324.700 3302.3040 182811.579 10000
#> outer 1112.904 1981.9195 3096.3271 2345.200 3298.9215 90762.766 10000
#> Rfast 178.432 925.1445 1886.5011 1151.710 1703.3655 103405.239 10000
Created on 2022-08-17 by the reprex package (v2.0.1)
W
into a data.frame is a mistake. AFAIK, matrix subassignment is faster than data.frame subassignment. – Shevlo