Glen Ng
Glen Ng

Reputation: 97

Applying a function that takes columns and rows of matrices as input with a matrix as output without using loop

I would like to write a function that takes columns and rows of matrices as arguments and gives a matrix as an output.

For example, a function that takes rows i of an m by k matrix A and columns j of a k by n matrix B, and return a matrix M with elements m_i,j that equals to min(A[i,] * B[,j]) (element-wise multiplication):

Is there any simple way to avoid using loops? Does an sapply equivalent for matrices exists?

> matrix_A
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5
[2,]    2    3    4    5    6
[3,]    3    4    5    6    7
[4,]    0    1    2    3    4
[5,]    5    6    7    8    9
> matrix_B
     [,1] [,2] [,3] [,4] [,5]
[1,]    7    6    5    4    3
[2,]    6    5    4    3    2
[3,]    1    2    3    4    5
[4,]    8    7    6    5    4
[5,]    9    8    7    6    5
> 
> output_matrix <- matrix(, nrow=nrow(matrix_A), ncol=ncol(matrix_B))
> for (row_i in 1:nrow(matrix_A)) {
+         for (col_j in 1:ncol(matrix_B)) {
+                 output_matrix[row_i, col_j] <- min(matrix_A[row_i,]*matrix_B[,col_j])
+         }
+ }
> output_matrix
     [,1] [,2] [,3] [,4] [,5]
[1,]    3    6    5    4    3
[2,]    4    8   10    8    6
[3,]    5   10   15   12    8
[4,]    0    0    0    0    0
[5,]    7   14   21   18   12
> 

Upvotes: 4

Views: 139

Answers (4)

eipi10
eipi10

Reputation: 93761

Here we use pmap to iterate over the rows and columns of A and B:

library(tidyverse)

pmap_dbl(expand.grid(1:nrow(A), 1:nrow(B)), ~ min(A[..1, ] * B[ , ..2])) %>% 
  matrix(nrow=5)
     [,1] [,2] [,3] [,4] [,5]
[1,]    3    6    5    4    3
[2,]    4    8   10    8    6
[3,]    5   10   15   12    8
[4,]    0    0    0    0    0
[5,]    7   14   21   18   12

Upvotes: 1

Sotos
Sotos

Reputation: 51582

Using apply from base R,

apply(m2, 2, function(i) apply(m1, 1, function(j) min(j*i)))

which gives,

     [,1] [,2] [,3] [,4] [,5]
[1,]    3    6    5    4    3
[2,]    4    8   10    8    6
[3,]    5   10   15   12    8
[4,]    0    0    0    0    0
[5,]    7   14   21   18   12

A fully vectorized solution can be,

t(matrix(do.call(pmin, 
       as.data.frame(
          do.call(rbind, rep(split(m1, 1:nrow(m1)), each = 5)) * do.call(rbind, rep(split(t(m2), 1:nrow(m2)), 5)))), 
       nrow(m1)))

Upvotes: 4

Ronak Shah
Ronak Shah

Reputation: 388862

We use expand.grid to create all possible combinations of row and col pairs. We then use mapply to multiply all the row-column combination element wise and then select the min from it.

mat <- expand.grid(1:nrow(A),1:nrow(B))
mapply(function(x, y) min(matrix_A[x,] * matrix_B[, y]) , mat[,1], mat[,2])

#[1]  3  4  5  0  7  6  8 10  0 14  5 10 15  0 21  4  8 12  0 18  3  6  8  0 12

Assuming matrix_A, matrix_B and output_matrix all have the same dimensions we can relist the output from mapply to get the original dimensions.

output_matrix <- mapply(function(x, y) min(matrix_A[x,] * matrix_B[, y]),
                        mat[,1], mat[,2])

relist(output_matrix, matrix_A)

#     [,1] [,2] [,3] [,4] [,5]
#[1,]    3    6    5    4    3
#[2,]    4    8   10    8    6
#[3,]    5   10   15   12    8
#[4,]    0    0    0    0    0
#[5,]    7   14   21   18   12

Upvotes: 1

Roland
Roland

Reputation: 132696

You can avoid R loops (*apply functions are loops too) for this specific example. Often an efficient solution is possible, but needs a specific algorithm as I demonstrate here. If you don't need to optimize speed, use loops. Your for loop offers the best readability and is easy to understand.

matrix_A <- matrix(c(1,2,3,0,5,
                     2,3,4,1,6,
                     3,4,5,2,7,
                     4,5,6,3,8,
                     5,6,7,4,9), 5)
matrix_B <- matrix(c(7,6,1,8,9,
                     6,5,2,7,8,
                     5,4,3,6,7,
                     4,3,4,5,6,
                     3,2,5,4,5), 5)

#all combinations of i and j
inds <- expand.grid(seq_len(nrow(matrix_A)), seq_len(ncol(matrix_B)))

#subset A and transposed B then multiply the resulting matrices
#then calculate rowwise min and turn result into a matrix
library(matrixStats)
matrix(rowMins(matrix_A[inds[[1]],] * t(matrix_B)[inds[[2]],]), nrow(matrix_A))
#     [,1] [,2] [,3] [,4] [,5]
#[1,]    3    6    5    4    3
#[2,]    4    8   10    8    6
#[3,]    5   10   15   12    8
#[4,]    0    0    0    0    0
#[5,]    7   14   21   18   12

Upvotes: 1

Related Questions