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.
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
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
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
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
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
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:
`
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
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