Definition

For any two matrices \(\textbf{A} = \{a_{ij}\}\) and \(\textbf{B} = \{b_{ij}\}\) of the same dimensions \(m\times n\), the Hadamard product between them is defined as the element-wise or entry-wise product

\[ \textbf{A}\odot\textbf{B} = \{a_{ij}b_{ij}\} \]

This can be performed using the R’s product operator (*). Alternatively, the Hadamard() function from the ‘tensorEVD’ R-package can be used.

Examples

Example 1

Direct product of compatible matrices, i.e., having the same dimension.

# Simulating matrices A and B
m = 100; n = 150
A <- matrix(rnorm(m*n), ncol=n)
B <- matrix(rnorm(m*n), ncol=n)

# Making the Hadamard product
K1 <- A*B
K2 <- Hadamard(A, B)

all.equal(K1, K2) # should be equal
## [1] TRUE

Example 2

Simple scalar multiplication. Let \(a\) be a scalar and \(\textbf{B}\) any matrix. Then, using the R’s product operator (*) will multiply \(\textbf{B}\) by the scalar. However, the Hadamard() function will produce an error because incompatibility. In this case, the Kronecker() function can be used.

a <- 10
( B <- matrix(1:6, ncol=2) )
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6
a*B    # using the product operator
##      [,1] [,2]
## [1,]   10   40
## [2,]   20   50
## [3,]   30   60
try(Hadamard(a, B), silent=TRUE)[[1]]
## [1] "Error in Hadamard(a, B) : 'nrow(A)' should be the same as 'nrow(B)'.\nOtherwise, provide parameter(s) 'rowsA' and 'rowsB'\n"
Kronecker(a, B)  # Kronecker instead of Hadamard
##      [,1] [,2]
## [1,]   10   40
## [2,]   20   50
## [3,]   30   60

Hadamard with incompatible matrices

If \(\textbf{A}\) and \(\textbf{B}\) are not of the same dimensions (e.g., \(\textbf{A}\) is \(m \times n\) and \(\textbf{B}\) is \(p \times q\)), the Hadamard product is not defined

# Simulating A and B of different dimensions
m = 100; n = 150
p = 200; q = 120
A <- matrix(rnorm(m*n), ncol=n)  # m x n
B <- matrix(rnorm(p*q), ncol=q)  # p x q

try(A*B, silent=TRUE)[[1]]
## [1] "Error in A * B : non-conformable arrays\n"

However, a Hadamard product can be still performed between \(\textbf{A}\) and \(\textbf{B}\) using incidence matrices of appropriate dimensions

Subsetting one matrix

A Hadamard product between \(\textbf{B}\) and a submatrix of \(\textbf{A}\) can be performed by doing

\[ (\textbf{R}\textbf{A}\textbf{C}') \odot \textbf{B} \]

where \(\textbf{R}\) and \(\textbf{C}\) are incidence matrices for rows and columns, respectively.

Product matrix \(\textbf{R}\textbf{A}\textbf{C}'\) can be obtained by matrix indexing using, for instance, integer vectors rowsA and colsA, as A[rowsA,colsA]. Therefore, the Hadamard product can be obtained by doing A[rowsA,colsA]*B. This Hadamard will be of the same dimensions as \(\textbf{B}\).

The Hadamard() function computes this Hadamard product directly from \(\textbf{A}\) without forming A[rowsA,colsA] matrices. For example

# Subsetting rows and columns of A each of length 
# equal to nrow(B) and ncol(B), respectively
rowsA <- sample(seq(nrow(A)), nrow(B), replace=TRUE)
colsA <- sample(seq(ncol(A)), ncol(B), replace=TRUE)

# Making the Hadamard product
K1 <- A[rowsA,colsA]*B
K2 <- Hadamard(A, B, rowsA=rowsA, colsA=colsA)

all.equal(K1, K2)
## [1] TRUE
dim(K2) == dim(B) # has the same dimension as B
## [1] TRUE TRUE

Likewise, a Hadamard product between \(\textbf{A}\) and a submatrix of \(\textbf{B}\), say B[rowsB,colsB], can be performed. For example,

rowsB <- sample(seq(nrow(B)), nrow(A), replace=TRUE)
colsB <- sample(seq(ncol(B)), ncol(A), replace=TRUE)

K2 <- Hadamard(A, B, rowsB=rowsB, colsB=colsB)
dim(K2) == dim(A) 
## [1] TRUE TRUE

Hadamard between two submatrices

A Hadamard product between a submatrix of \(\textbf{A}\) and a submatrix of \(\textbf{B}\) can be computed if the indexed matrices A[rowsA,colsA] and B[rowsB,colsB] are compatible, i.e., as long as, length(rowsA) = length(rowsB) and length(colsA) = length(colsB). Therefore, this allows to obtain a Hadamard product of any desirable dimension different from that of \(\textbf{A}\) or \(\textbf{B}\).

dm <- c(1000, 2000)  # a Hadamard of 1000 x 2000

# Obtaining a submatrix from A
rowsA <- sample(seq(nrow(A)), dm[1], replace=TRUE)
colsA <- sample(seq(ncol(A)), dm[2], replace=TRUE)

# Obtaining a submatrix from B
rowsB <- sample(seq(nrow(B)), dm[1], replace=TRUE)
colsB <- sample(seq(ncol(B)), dm[2], replace=TRUE)

# Making the Hadamard product
K1 <- A[rowsA,colsA]*B[rowsB,colsB]
K2 <- Hadamard(A, B, rowsA=rowsA, rowsB=rowsB, colsA=colsA, colsB=colsB)

all.equal(K1, K2)
## [1] TRUE

Benchmark

Here we compare the speed performance of the Hadamard() function and of the product operator (*) on calculating small and large Hadamard products by subsetting from matrices \(\textbf{A}\) and \(\textbf{B}\). The following benchmark was performed using the code provided in this script run on a Linux environment based on the following system settings:

  • Machine: Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz
  • Memory: 64 GB in RAM
  • R version 4.1.1 (2021-08-10)

`

Extras

Inplace calculation

If \(\textbf{A}\) is used as such (i.e., is not indexed), the resulting Hadamard will be of the same dimension as \(\textbf{A}\); therefore, we could overwrite the result on the memory occupied by \(\textbf{A}\)

Usually, assigning an output to an object will occupy a different memory address than inputs:

A <- matrix(rnorm(30), ncol=5)
B <- matrix(rnorm(30), ncol=5)

K <- Hadamard(A, B)
c(K=pryr::address(K), A=pryr::address(A))
##                K                A 
## "0x7fcc50f05a80" "0x7fcc50e651f0"

The parameter inplace can be used to store the output at the same address as the input:

A <- Hadamard(A, B, inplace=TRUE)
c(A=pryr::address(A))  # output address remain unchanged
##                A 
## "0x7fcc50e651f0"
all.equal(K, A)        # contains the desired result 
## [1] TRUE

Making dimension names

Row and column names for the Hadamard product can be retrieved using the make.dimnames argument. Attribute dimnames of the Hadamard will be produced by crossing rownames and colnames of input \(\textbf{A}\) with those of input \(\textbf{B}\). For instance,

A <- matrix(1:16, ncol=4)
B <- matrix(10*(1:16), ncol=4)

dimnames(A) <- list(paste("week",1:4), month.abb[1:4])
dimnames(B) <- list(c("chicken","beef","pork","fish"), LETTERS[1:4])

Hadamard(A, B, make.dimnames=TRUE)
##                Jan:A Feb:B Mar:C Apr:D
## week 1:chicken    10   250   810  1690
## week 2:beef       40   360  1000  1960
## week 3:pork       90   490  1210  2250
## week 4:fish      160   640  1440  2560
Hadamard(A, B, make.dimnames=TRUE, colsA=c(1,1,1,1), rowsB=c(2,3,2,3))
##             Jan:A Jan:B Jan:C Jan:D
## week 1:beef    20    60   100   140
## week 2:pork    60   140   220   300
## week 3:beef    60   180   300   420
## week 4:pork   120   280   440   600