4

If I have, say, an (lots x 5) matrix and a (1 x 5) matrix, is there a better way to multiply them row-wise than this:

> q 
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5

> z
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    6   11   16   21
[2,]    2    7   12   17   22
[3,]    3    8   13   18   23
[4,]    4    9   14   19   24
[5,]    5   10   15   20   25

> t(apply(z,1,function (x) {x*q})) 
     [,1] [,2] [,3] [,4] [,5]
[1,]    1   12   33   64  105
[2,]    2   14   36   68  110
[3,]    3   16   39   72  115
[4,]    4   18   42   76  120
[5,]    5   20   45   80  125

This works but seems bad. Is there a function I'm missing?

Carbon
  • 3,828
  • 3
  • 24
  • 51

2 Answers2

6

You could try

z*q[col(z)]
#     [,1] [,2] [,3] [,4] [,5]
#[1,]    1   12   33   64  105
#[2,]    2   14   36   68  110
#[3,]    3   16   39   72  115
#[4,]    4   18   42   76  120
#[5,]    5   20   45   80  125

Or

t(t(z)*c(q))
#     [,1] [,2] [,3] [,4] [,5]
#[1,]    1   12   33   64  105
#[2,]    2   14   36   68  110
#[3,]    3   16   39   72  115
#[4,]    4   18   42   76  120
#[5,]    5   20   45   80  125

Or

z*rep(q,each=ncol(z))

Benchmarks

z <- matrix(1:5e6,ncol=5)
f1 <- function() {sweep(z, 2, q, "*")}
f2 <- function() {z*q[col(z)]}
f3 <- function() {z*rep(q,each=ncol(z))}
library(microbenchmark)
microbenchmark(f1(), f2(),f3(), unit='relative', times=40L)
# Unit: relative
#expr      min       lq     mean   median       uq      max neval cld
#f1() 4.411211 4.407288 4.370018 4.308901 4.825270 2.396968    40   c
#f2() 2.607341 2.668707 2.916354 3.323934 3.321174 1.437837    40  b 
#f3() 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000    40 a  
akrun
  • 874,273
  • 37
  • 540
  • 662
  • The latter solution seems clever. – Carbon Dec 25 '14 at 04:37
  • @RichardScriven Thanks, it could be. I will run it again with `rep.int` and see if that changes. – akrun Dec 25 '14 at 05:01
  • @RichardScriven The problem with `rep.int` is it doesn't have `each` argument. May be I need to sort it afterwards. I don't know if that is worth the effort – akrun Dec 25 '14 at 05:04
6

Another option is sweep

sweep(z, 2, q, "*")
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    1   12   33   64  105
# [2,]    2   14   36   68  110
# [3,]    3   16   39   72  115
# [4,]    4   18   42   76  120
# [5,]    5   20   45   80  125
Rich Scriven
  • 97,041
  • 11
  • 181
  • 245
  • I regret that I have only one check to give; akrun came up with such a clever solution. Either solution cuts my runtime from ~10m to ~1m. – Carbon Dec 25 '14 at 04:43