Title: | A Fast Algorithm to Factorize High-Dimensional Tensor Product Matrices |
---|---|
Description: | Here we provide tools for the computation and factorization of high-dimensional tensor products that are formed by smaller matrices. The methods are based on properties of Kronecker products (Searle 1982, p. 265, ISBN-10: 0470009616). We evaluated this methodology by benchmark testing and illustrated its use in Gaussian Linear Models ('Lopez-Cruz et al., 2024') <doi:10.1093/g3journal/jkae001>. |
Authors: | Marco Lopez-Cruz [aut, cre], Gustavo de los Campos [aut], Paulino Perez-Rodriguez [aut] |
Maintainer: | Marco Lopez-Cruz <[email protected]> |
License: | GPL-3 |
Version: | 0.1.4 |
Built: | 2024-11-05 05:43:55 UTC |
Source: | https://github.com/marcoolopez/tensorevd |
Computes the Hadamard product between two matrices
Hadamard(A, B, IDrowA, IDrowB, IDcolA = NULL, IDcolB = NULL, a = 1, make.dimnames = FALSE, drop = TRUE, inplace = FALSE)
Hadamard(A, B, IDrowA, IDrowB, IDcolA = NULL, IDcolB = NULL, a = 1, make.dimnames = FALSE, drop = TRUE, inplace = FALSE)
A |
(numeric) Numeric matrix |
B |
(numeric) Numeric matrix |
IDrowA |
(integer/character) Vector of length m with either indices or row names mapping from rows of |
IDrowB |
(integer/character) Vector of length m with either indices or row names mapping from rows of |
IDcolA |
(integer/character) (Optional) Similar to |
IDcolB |
(integer/character) (Optional) Similar to |
a |
(numeric) A constant to multiply the resulting Hadamard product by |
drop |
Either |
make.dimnames |
|
inplace |
|
Computes the m × n Hadamard product (aka element-wise or entry-wise product) matrix between matrices A and B,
(R1A C'1) ⊙ (R2B C'2)
where R1 and R2 are incidence matrices mapping from rows of the resulting Hadamard to rows of A and B, respectively; and C1 and C2 are incidence matrices mapping from columns of the resulting Hadamard to columns of A and B, respectively.
Matrix R1A C'1
can be obtained by matrix indexing as A[IDrowA,IDcolA]
, where IDrowA
and IDcolA
are integer vectors whose entries are, respectively, the row and column number of
A that are mapped at each row of
R1 and
C1, respectively.
Likewise, matrix
R2B C'2
can be obtained as B[IDrowB,IDcolB]
, where IDrowB
and IDcolB
are integer vectors whose entries are, respectively, the row and column number of
B that are mapped at each row of
R2 and
C2, respectively. Therefore, the Hadamard product can be obtained directly as
A[IDrowA,IDcolA]*B[IDrowB,IDcolB]
The function computes the Hadamard product directly from A and B without forming R1A C'1 or R2B C'2 matrices. The result can be multiplied by a constant a.
Returns a matrix containing the Hadamard product.
require(tensorEVD) # (a) Example 1. Indexing using row/column names # Generate rectangular matrices A (nrowA x ncolA) and B (nrowB x ncolB) nA = c(10,15) nB = c(12,8) A = matrix(rnorm(nA[1]*nA[2]), nrow=nA[1]) B = matrix(rnorm(nB[1]*nB[2]), nrow=nB[1]) dimnames(A) = list(paste0("row",seq(nA[1])), paste0("col",seq(nA[2]))) dimnames(B) = list(paste0("row",seq(nB[1])), paste0("col",seq(nB[2]))) # Define IDs for a Hadamard of size n1 x n2 n = c(1000,500) IDrowA = sample(rownames(A), n[1], replace=TRUE) IDrowB = sample(rownames(B), n[1], replace=TRUE) IDcolA = sample(colnames(A), n[2], replace=TRUE) IDcolB = sample(colnames(B), n[2], replace=TRUE) K1 = Hadamard(A, B, IDrowA, IDrowB, IDcolA, IDcolB, make.dimnames=TRUE) # (it must equal to:) K2 = A[IDrowA,IDcolA]*B[IDrowB,IDcolB] dimnames(K2) = list(paste0(IDrowA,":",IDrowB), paste0(IDcolA,":",IDcolB)) all.equal(K1,K2) # (b) Example 2. Indexing using integers # Generate squared symmetric matrices A and B nA = 20 nB = 15 A = tcrossprod(matrix(rnorm(nA*nA), nrow=nA)) B = tcrossprod(matrix(rnorm(nB*nB), nrow=nB)) # Define IDs for a Hadamard of size n x n n = 1000 IDA = sample(seq(nA), n, replace=TRUE) IDB = sample(seq(nB), n, replace=TRUE) K1 = Hadamard(A, B, IDA, IDB) # (it must equal to:) K2 = A[IDA,IDA]*B[IDB,IDB] all.equal(K1,K2) # (c) Inplace calculation # overwrite the output at the same address as the input: IDB = sample(seq(nB), nA, replace=TRUE) K1 = A[] # copy of A to be used as input add = pryr::address(K1) # address of K on entry K1 = Hadamard(K1, B, IDrowB=IDB) pryr::address(K1) == add # on exit, K was moved to a different address K2 = A[] add = pryr::address(K2) K2 = Hadamard(K2, B, IDrowB=IDB, inplace=TRUE) pryr::address(K2) == add # on exit, K remains at the same address all.equal(K1,K2)
require(tensorEVD) # (a) Example 1. Indexing using row/column names # Generate rectangular matrices A (nrowA x ncolA) and B (nrowB x ncolB) nA = c(10,15) nB = c(12,8) A = matrix(rnorm(nA[1]*nA[2]), nrow=nA[1]) B = matrix(rnorm(nB[1]*nB[2]), nrow=nB[1]) dimnames(A) = list(paste0("row",seq(nA[1])), paste0("col",seq(nA[2]))) dimnames(B) = list(paste0("row",seq(nB[1])), paste0("col",seq(nB[2]))) # Define IDs for a Hadamard of size n1 x n2 n = c(1000,500) IDrowA = sample(rownames(A), n[1], replace=TRUE) IDrowB = sample(rownames(B), n[1], replace=TRUE) IDcolA = sample(colnames(A), n[2], replace=TRUE) IDcolB = sample(colnames(B), n[2], replace=TRUE) K1 = Hadamard(A, B, IDrowA, IDrowB, IDcolA, IDcolB, make.dimnames=TRUE) # (it must equal to:) K2 = A[IDrowA,IDcolA]*B[IDrowB,IDcolB] dimnames(K2) = list(paste0(IDrowA,":",IDrowB), paste0(IDcolA,":",IDcolB)) all.equal(K1,K2) # (b) Example 2. Indexing using integers # Generate squared symmetric matrices A and B nA = 20 nB = 15 A = tcrossprod(matrix(rnorm(nA*nA), nrow=nA)) B = tcrossprod(matrix(rnorm(nB*nB), nrow=nB)) # Define IDs for a Hadamard of size n x n n = 1000 IDA = sample(seq(nA), n, replace=TRUE) IDB = sample(seq(nB), n, replace=TRUE) K1 = Hadamard(A, B, IDA, IDB) # (it must equal to:) K2 = A[IDA,IDA]*B[IDB,IDB] all.equal(K1,K2) # (c) Inplace calculation # overwrite the output at the same address as the input: IDB = sample(seq(nB), nA, replace=TRUE) K1 = A[] # copy of A to be used as input add = pryr::address(K1) # address of K on entry K1 = Hadamard(K1, B, IDrowB=IDB) pryr::address(K1) == add # on exit, K was moved to a different address K2 = A[] add = pryr::address(K2) K2 = Hadamard(K2, B, IDrowB=IDB, inplace=TRUE) pryr::address(K2) == add # on exit, K remains at the same address all.equal(K1,K2)
Computes the direct Kronecker product between two matrices
Kronecker(A, B, rows = NULL, cols = NULL, a = 1, make.dimnames = FALSE, drop = TRUE, inplace = FALSE)
Kronecker(A, B, rows = NULL, cols = NULL, a = 1, make.dimnames = FALSE, drop = TRUE, inplace = FALSE)
A |
(numeric) Left numeric matrix |
B |
(numeric) Right numeric matrix |
rows |
(integer) Index which rows of the Kronecker are to be returned. They must range from 1 to |
cols |
(integer) Index which columns of the Kronecker are to be returned. They must range from 1 to |
a |
(numeric) A constant to multiply the resulting Kronecker product by |
drop |
Either |
make.dimnames |
|
inplace |
|
For any two matrices A={aij} of dimensions m × n and B={bij} of dimensions p × q, the direct Kronecker product between them is a matrix defined as the block matrix
A⊗B = {aijB}
which is of dimensions mp × nq.
A sub-matrix formed by selecting specific rows and columns from the Kronecker can be obtained by pre- and post- multiplication with incidence matrices
R (A⊗B) C'
where R is an incidence matrix mapping from rows of the resulting sub-matrix to rows of the Kronecker product, and C is an incidence matrix mapping from columns of the resulting sub-matrix to columns of the Kronecker product. This sub-matrix of the Kronecker can be obtained by matrix indexing as
Kronecker(A,B)[rows,cols]
where rows
and cols
are integer vectors whose entries are, respectively, the row and column number of the Kronecker that are mapped at each row of R and C.
The function computes this sub-matrix of the Kronecker product directly from A and B without forming the whole Kronecker product. This is very useful if a relatively small number of row/columns are to be selected.
Returns the Kronecker product matrix. It can be a sub-matrix of it as per the rows
and cols
arguments.
require(tensorEVD) # (a) Kronecker product of 2 vectors A = rnorm(3) B = rnorm(2) (K1 = Kronecker(A, B)) # it must equal when using from the R-base package: (K2 = kronecker(A, B)) # (b) Kronecker product of 2 matrices A = matrix(rnorm(12), ncol=3) B = matrix(rnorm(4), ncol=2) K1 = Kronecker(A, B) # (it must equal (but faster) to:) K2 = kronecker(A, B) all.equal(K1,K2) # (c) Subsetting rows/columns from the Kronecker A = matrix(rnorm(100*150), ncol=150) B = matrix(rnorm(100*120), ncol=120) rows = c(1,3,5,7) cols = c(10,20,30,50) K1 = Kronecker(A, B, rows=rows, cols=cols) # (it must equal (but faster) to:) K2 = Kronecker(A, B)[rows,cols] all.equal(K1,K2) # (d) Inplace calculation # overwrite the output at the same address as the input: K1 = A[] # copy of A to be used as input add = pryr::address(K1) # address of K on entry K1 = Kronecker(K1, B=0.5) pryr::address(K1) == add # on exit, K was moved to a different address K2 = A[] add = pryr::address(K2) K2 = Kronecker(K2, B=0.5, inplace=TRUE) pryr::address(K2) == add # on exit, K remains at the same address all.equal(K1,K2)
require(tensorEVD) # (a) Kronecker product of 2 vectors A = rnorm(3) B = rnorm(2) (K1 = Kronecker(A, B)) # it must equal when using from the R-base package: (K2 = kronecker(A, B)) # (b) Kronecker product of 2 matrices A = matrix(rnorm(12), ncol=3) B = matrix(rnorm(4), ncol=2) K1 = Kronecker(A, B) # (it must equal (but faster) to:) K2 = kronecker(A, B) all.equal(K1,K2) # (c) Subsetting rows/columns from the Kronecker A = matrix(rnorm(100*150), ncol=150) B = matrix(rnorm(100*120), ncol=120) rows = c(1,3,5,7) cols = c(10,20,30,50) K1 = Kronecker(A, B, rows=rows, cols=cols) # (it must equal (but faster) to:) K2 = Kronecker(A, B)[rows,cols] all.equal(K1,K2) # (d) Inplace calculation # overwrite the output at the same address as the input: K1 = A[] # copy of A to be used as input add = pryr::address(K1) # address of K on entry K1 = Kronecker(K1, B=0.5) pryr::address(K1) == add # on exit, K was moved to a different address K2 = A[] add = pryr::address(K2) K2 = Kronecker(K2, B=0.5, inplace=TRUE) pryr::address(K2) == add # on exit, K remains at the same address all.equal(K1,K2)
Ridge penalization of a multi-variate (co)variance matrix taking the form of either a Kronecker or Hadamard product
Kronecker_cov(Sigma = 1, K, Theta, swap = FALSE, rows = NULL, cols = NULL, drop = TRUE, inplace = FALSE) Hadamard_cov(Sigma = 1, K, Theta, IDS, IDK, drop = TRUE, inplace = FALSE)
Kronecker_cov(Sigma = 1, K, Theta, swap = FALSE, rows = NULL, cols = NULL, drop = TRUE, inplace = FALSE) Hadamard_cov(Sigma = 1, K, Theta, IDS, IDK, drop = TRUE, inplace = FALSE)
Sigma |
(numeric) A variance matrix among features. If is scalar, a scaled identity matrix with the same dimension as |
K |
(numeric) Variance matrix among subjects |
Theta |
(numeric) A diagonal-shifting parameter, value to be added to the diagonals of the resulting (co)variance matrix. It should be a (symmetric) matrix with the same dimension as |
rows |
(integer) Index which rows of the (Kronecker product) (co)variance matrix are to be returned. Default |
cols |
(integer) Index which columns of the (Kronecker product) (co)variance are to be returned. Default |
IDS |
(integer/character) Vector with either indices or row names mapping from rows/columns of |
IDK |
(integer/character) Vector with either indices or row names mapping from rows/columns of |
swap |
(logical) Either |
drop |
(logical) Either |
inplace |
(logical) Either |
Assume that a multi-variate random matrix X with n subjects in rows and p features in columns follows a matrix Gaussian distribution with certain matrix of means M and variance matrix K of dimension n × n between subjects, and Σ of dimension p × p between features.
Kronecker product form.The random variable x = vec(X), formed by stacking columns of X, is a vector of length np that also follow a Gaussian distribution with mean vec(M) and (co)variance covariance matrix taking the Kronecker form
Σ⊗K
In the uni-variate case, the problem of near-singularity can be alleviated by penalizing the variance matrix K by adding positive elements θ to its diagonal, i.e., K + θI, where I is an identity matrix. The same can be applied to the multi-variate case where the Kronecker product (co)variance matrix is penalized with Θ={θij} of dimensions p × p, where diagonal entries will penalize within feature i and off-diagonals will penalize between features i and j. This is,
Σ⊗K + Θ⊗I
The second Kronecker summand
Θ⊗I
is a sparse matrix consisting of non-zero diagonal and sub-diagonals. The Kronecker_cov
function derives the penalized Kronecker (co)variance matrix by computing densely only the first Kronecker summand
Σ⊗K,
and then calculating and adding accordingly only the non-zero entries of
Θ⊗I.
Note: Swapping the order of the matrices in the above Kronecker operations will yield a different result. In this case the penalized matrix
K⊗Σ + I⊗Θ
corresponds to the penalized multi-variate (co)variance matrix of the transposed of the above multi-variate random matrix
X, now with features in rows and subjects in columns. This can be achieved by setting swap=TRUE
in the Kronecker_cov
function.
Assume the random variable x0 is a subset of x containing entries corresponding to specific combinations of subjects and features, then the (co)variance matrix of the vector x0 will be a Hadamard product formed by the entry-wise product of only the elements of Σ and K involved in the combinations contained in x0; this is
(Z1Σ Z'1) ⊙ (Z2K Z'2)
where
Z1 and
Z2 are incidence matrices mapping from entries of the random variable x0 to rows (and columns) of Σ and K, respectively. This (co)variance matrix can be obtained using matrix indexing (see help(Hadamard)
), as
Sigma[IDS,IDS]*K[IDK,IDK]
where IDS
and IDK
are integer vectors whose entries are the row (and column) number of
Σ and K, respectively, that are mapped at each row of
Z1 and
Z2, respectively.
The penalized version of this Hadamard product (co)variance matrix will be
(Z1Σ Z'1) ⊙ (Z2K Z'2) + (Z1Θ Z'1) ⊙ (Z2I Z'2)
The Hadamard_cov
function derives this penalized (co)variance matrix using matrix indexing, as
Sigma[IDS,IDS]*K[IDK,IDK] + Theta[IDS,IDS]*I[IDK,IDK]
Likewise, this function computes densely only the first Hadamard summand and then calculates and adds accordingly only the non-zero entries of the second summand.
Returns the penalized (co)variance matrix formed either as a Kronecker or Hadamard product. For the Kronecker product case, it can be a sub-matrix of the Kronecker product as per the rows
and cols
arguments.
require(tensorEVD) # Generate rectangular some covariance matrices n = 30; p = 10 K = crossprod(matrix(rnorm(n*p), ncol=n)) # n x n matrix Sigma = crossprod(matrix(rnorm(n*p), ncol=p)) # p x p matrix Theta = crossprod(matrix(rnorm(n*p), ncol=p)) # p x p matrix # ============================================== # Kronecker covariance # ============================================== G1 = Kronecker_cov(Sigma, K, Theta = Theta) # it must equal to: D = diag(n) # diagonal matrix of dimension n G2 = Kronecker(Sigma, K) + Kronecker(Theta, D) all.equal(G1,G2) # (b) Swapping the order of the matrices G1 = Kronecker_cov(Sigma, K, Theta, swap = TRUE) # in this case the kronecker is swapped: G2 = Kronecker(K, Sigma) + Kronecker(D, Theta) all.equal(G1,G2) # (c) Selecting specific entries of the output # We want only some rows and columns rows = c(1,3,5) cols = c(10,30,50) G1 = Kronecker_cov(Sigma, K, Theta, rows=rows, cols=cols) # this can be preferable instead of: G2 = (Kronecker(Sigma, K) + Kronecker(Theta, D))[rows,cols] all.equal(G1,G2) # (d) Inplace calculation # overwrite the output at the same address as the input: G1 = K[] # copy of K to be used as input add = pryr::address(G1) # address of G on entry G1 = Kronecker_cov(Sigma=0.5, G1, Theta=1.5) pryr::address(G1) == add # on exit, G was moved to a different address G2 = K[] add = pryr::address(G2) G2 = Kronecker_cov(Sigma=0.5, G2, Theta=1.5, inplace=TRUE) pryr::address(G2) == add # on exit, G remains at the same address all.equal(G1,G2) # ============================================== # Hadamard covariance # ============================================== # Define IDs for a Hadamard of size m x m m = 1000 IDS = sample(1:p, m, replace=TRUE) IDK = sample(1:n, m, replace=TRUE) G1 = Hadamard_cov(Sigma, K, Theta, IDS=IDS, IDK=IDK) # it must equal to: G2 = Sigma[IDS,IDS]*K[IDK,IDK] + Theta[IDS,IDS]*D[IDK,IDK] all.equal(G1,G2) # (b) Inplace calculation # overwrite the output at the same address as the input: G1 = K[] # copy of K to be used as input add = pryr::address(G1) # address of G on entry G1 = Hadamard_cov(Sigma=0.5, G1, Theta=1.5, IDS=rep(1,n)) pryr::address(G1) == add # on exit, G was moved to a different address G2 = K[] add = pryr::address(G2) G2 = Hadamard_cov(Sigma=0.5, G2, Theta=1.5, IDS=rep(1,n), inplace=TRUE) pryr::address(G2) == add # on exit, G remains at the same address all.equal(G1,G2)
require(tensorEVD) # Generate rectangular some covariance matrices n = 30; p = 10 K = crossprod(matrix(rnorm(n*p), ncol=n)) # n x n matrix Sigma = crossprod(matrix(rnorm(n*p), ncol=p)) # p x p matrix Theta = crossprod(matrix(rnorm(n*p), ncol=p)) # p x p matrix # ============================================== # Kronecker covariance # ============================================== G1 = Kronecker_cov(Sigma, K, Theta = Theta) # it must equal to: D = diag(n) # diagonal matrix of dimension n G2 = Kronecker(Sigma, K) + Kronecker(Theta, D) all.equal(G1,G2) # (b) Swapping the order of the matrices G1 = Kronecker_cov(Sigma, K, Theta, swap = TRUE) # in this case the kronecker is swapped: G2 = Kronecker(K, Sigma) + Kronecker(D, Theta) all.equal(G1,G2) # (c) Selecting specific entries of the output # We want only some rows and columns rows = c(1,3,5) cols = c(10,30,50) G1 = Kronecker_cov(Sigma, K, Theta, rows=rows, cols=cols) # this can be preferable instead of: G2 = (Kronecker(Sigma, K) + Kronecker(Theta, D))[rows,cols] all.equal(G1,G2) # (d) Inplace calculation # overwrite the output at the same address as the input: G1 = K[] # copy of K to be used as input add = pryr::address(G1) # address of G on entry G1 = Kronecker_cov(Sigma=0.5, G1, Theta=1.5) pryr::address(G1) == add # on exit, G was moved to a different address G2 = K[] add = pryr::address(G2) G2 = Kronecker_cov(Sigma=0.5, G2, Theta=1.5, inplace=TRUE) pryr::address(G2) == add # on exit, G remains at the same address all.equal(G1,G2) # ============================================== # Hadamard covariance # ============================================== # Define IDs for a Hadamard of size m x m m = 1000 IDS = sample(1:p, m, replace=TRUE) IDK = sample(1:n, m, replace=TRUE) G1 = Hadamard_cov(Sigma, K, Theta, IDS=IDS, IDK=IDK) # it must equal to: G2 = Sigma[IDS,IDS]*K[IDK,IDK] + Theta[IDS,IDS]*D[IDK,IDK] all.equal(G1,G2) # (b) Inplace calculation # overwrite the output at the same address as the input: G1 = K[] # copy of K to be used as input add = pryr::address(G1) # address of G on entry G1 = Hadamard_cov(Sigma=0.5, G1, Theta=1.5, IDS=rep(1,n)) pryr::address(G1) == add # on exit, G was moved to a different address G2 = K[] add = pryr::address(G2) G2 = Hadamard_cov(Sigma=0.5, G2, Theta=1.5, IDS=rep(1,n), inplace=TRUE) pryr::address(G2) == add # on exit, G remains at the same address all.equal(G1,G2)
Fast eigen value decomposition (EVD) of the Hadamard product of two matrices
tensorEVD(K1, K2, ID1, ID2, alpha = 1.0, EVD1 = NULL, EVD2 = NULL, d.min = .Machine$double.eps, make.dimnames = FALSE, verbose = FALSE)
tensorEVD(K1, K2, ID1, ID2, alpha = 1.0, EVD1 = NULL, EVD2 = NULL, d.min = .Machine$double.eps, make.dimnames = FALSE, verbose = FALSE)
K1 , K2
|
(numeric) Covariance structure matrices |
ID1 |
(character/integer) Vector of length n with either names or indices mapping from rows/columns of |
ID2 |
(character/integer) Vector of length n with either names or indices mapping from rows/columns of |
alpha |
(numeric) Proportion of variance of the tensor product to be explained by the tensor eigenvectors |
EVD1 |
(list) (Optional) Eigenvectors and eigenvalues of |
EVD2 |
(list) (Optional) Eigenvectors and eigenvalues of |
d.min |
(numeric) Tensor eigenvalue threshold. Default is a numeric zero. Only eigenvectors with eigenvalue passing this threshold are returned |
make.dimnames |
|
verbose |
|
Let the n × n matrix K to be the Hadamard product (aka element-wise or entry-wise product) involving two smaller matrices K1 and K2 of dimensions n1 and n2, respectively,
K = (Z1K1Z'1) ⊙ (Z2K2Z'2)
where Z1 and Z2 are incidence matrices mapping from rows (and columns) of the resulting Hadamard to rows (and columns) of K1 and K2, respectively.
Let the eigenvalue decomposition (EVD) of K1 and K2 to be K1 = V1D1V'1 and K2 = V2D2V'2. Using properties of the Hadamard and Kronecker products, an EVD of the Hadamard product K can be approximated using the EVD of K1 and K2 as
K = V D V'
where D = D1⊗D2 is a diagonal matrix containing N = n1 × n2 tensor eigenvalues d1 ≥ ... ≥ dN ≥ 0 and V = (Z1⋆Z2)(V1⊗V2) = [v1,...,vN] is matrix containing N tensor eigenvectors vk; here the term Z1⋆Z2 is the "face-splitting product" (aka "transposed Khatri–Rao product") of matrices Z1 and Z2.
Each tensor eigenvector k is derived separately as a Hadamard product using the corresponding i(k) and j(k) eigenvectors v1i(k) and v2j(k) from V1 and V2, respectively, this is
vk = (Z1v1i(k))⊙(Z2v2j(k))
The tensorEVD
function derives each of these eigenvectors vk by matrix indexing using integer vectors ID1
and ID2
. The entries of these vectors are the row (and column) number of
K1 and
K2 that are mapped at each row of Z1 and
Z2, respectively.
Returns a list object that contains the elements:
values
: (vector) resulting tensor eigenvalues.
vectors
: (matrix) resulting tensor eigenvectors.
totalVar
: (numeric) total variance of the tensor matrix product.
require(tensorEVD) set.seed(195021) # Generate matrices K1 and K2 of dimensions n1 and n2 n1 = 10; n2 = 15 K1 = crossprod(matrix(rnorm(n1*(n1+10)), ncol=n1)) K2 = crossprod(matrix(rnorm(n2*(n2+10)), ncol=n2)) # (a) Example 1. Full design (Kronecker product) ID1 = rep(seq(n1), each=n2) ID2 = rep(seq(n2), times=n1) # Direct EVD of the Hadamard product K = K1[ID1,ID1]*K2[ID2,ID2] EVD0 = eigen(K) # Tensor EVD using K1 and K2 EVD = tensorEVD(K1, K2, ID1, ID2) # Eigenvectors and eigenvalues are numerically equal all.equal(EVD0$values, EVD$values) all.equal(abs(EVD0$vectors), abs(EVD$vectors)) # (b) If a proportion of variance explained is specified, # only the eigenvectors needed to explain such proportion are derived alpha = 0.95 EVD = tensorEVD(K1, K2, ID1, ID2, alpha=alpha) dim(EVD$vectors) # For the direct EVD varexp = cumsum(EVD0$values/sum(EVD0$values)) index = 1:which.min(abs(varexp-alpha)) dim(EVD0$vectors[,index]) # (c) Example 2. Incomplete design (Hadamard product) # Eigenvectors and eigenvalues are no longer equivalent n = n1*n2 # Sample size n ID1 = sample(seq(n1), n, replace=TRUE) # Randomly sample of ID1 ID2 = sample(seq(n2), n, replace=TRUE) # Randomly sample of ID2 K = K1[ID1,ID1]*K2[ID2,ID2] EVD0 = eigen(K) EVD = tensorEVD(K1, K2, ID1, ID2) all.equal(EVD0$values, EVD$values) all.equal(abs(EVD0$vectors), abs(EVD$vectors)) # However, the sum of the eigenvalues is equal to the trace(K) c(sum(EVD0$values), sum(EVD$values), sum(diag(K))) # And provide the same approximation for K K01 = EVD0$vectors%*%diag(EVD0$values)%*%t(EVD0$vectors) K02 = EVD$vectors%*%diag(EVD$values)%*%t(EVD$vectors) c(all.equal(K,K01), all.equal(K,K02)) # When n is different from N=n1xn2, both methods provide different # number or eigenvectors/eigenvalues. The eigen function provides # a number of eigenvectors equal to the minimum between n and N # for the tensorEVD, this number is always N # (d) Sample size n being half of n1 x n2 n = n1*n2/2 ID1 = sample(seq(n1), n, replace=TRUE) ID2 = sample(seq(n2), n, replace=TRUE) K = K1[ID1,ID1]*K2[ID2,ID2] EVD0 = eigen(K) EVD = tensorEVD(K1, K2, ID1, ID2) c(eigen=sum(EVD0$values>1E-10), tensorEVD=sum(EVD$values>1E-10)) # (e) Sample size n being twice n1 x n2 n = n1*n2*2 ID1 = sample(seq(n1), n, replace=TRUE) ID2 = sample(seq(n2), n, replace=TRUE) K = K1[ID1,ID1]*K2[ID2,ID2] EVD0 = eigen(K) EVD = tensorEVD(K1, K2, ID1, ID2) c(eigen=sum(EVD0$values>1E-10), tensorEVD=sum(EVD$values>1E-10))
require(tensorEVD) set.seed(195021) # Generate matrices K1 and K2 of dimensions n1 and n2 n1 = 10; n2 = 15 K1 = crossprod(matrix(rnorm(n1*(n1+10)), ncol=n1)) K2 = crossprod(matrix(rnorm(n2*(n2+10)), ncol=n2)) # (a) Example 1. Full design (Kronecker product) ID1 = rep(seq(n1), each=n2) ID2 = rep(seq(n2), times=n1) # Direct EVD of the Hadamard product K = K1[ID1,ID1]*K2[ID2,ID2] EVD0 = eigen(K) # Tensor EVD using K1 and K2 EVD = tensorEVD(K1, K2, ID1, ID2) # Eigenvectors and eigenvalues are numerically equal all.equal(EVD0$values, EVD$values) all.equal(abs(EVD0$vectors), abs(EVD$vectors)) # (b) If a proportion of variance explained is specified, # only the eigenvectors needed to explain such proportion are derived alpha = 0.95 EVD = tensorEVD(K1, K2, ID1, ID2, alpha=alpha) dim(EVD$vectors) # For the direct EVD varexp = cumsum(EVD0$values/sum(EVD0$values)) index = 1:which.min(abs(varexp-alpha)) dim(EVD0$vectors[,index]) # (c) Example 2. Incomplete design (Hadamard product) # Eigenvectors and eigenvalues are no longer equivalent n = n1*n2 # Sample size n ID1 = sample(seq(n1), n, replace=TRUE) # Randomly sample of ID1 ID2 = sample(seq(n2), n, replace=TRUE) # Randomly sample of ID2 K = K1[ID1,ID1]*K2[ID2,ID2] EVD0 = eigen(K) EVD = tensorEVD(K1, K2, ID1, ID2) all.equal(EVD0$values, EVD$values) all.equal(abs(EVD0$vectors), abs(EVD$vectors)) # However, the sum of the eigenvalues is equal to the trace(K) c(sum(EVD0$values), sum(EVD$values), sum(diag(K))) # And provide the same approximation for K K01 = EVD0$vectors%*%diag(EVD0$values)%*%t(EVD0$vectors) K02 = EVD$vectors%*%diag(EVD$values)%*%t(EVD$vectors) c(all.equal(K,K01), all.equal(K,K02)) # When n is different from N=n1xn2, both methods provide different # number or eigenvectors/eigenvalues. The eigen function provides # a number of eigenvectors equal to the minimum between n and N # for the tensorEVD, this number is always N # (d) Sample size n being half of n1 x n2 n = n1*n2/2 ID1 = sample(seq(n1), n, replace=TRUE) ID2 = sample(seq(n2), n, replace=TRUE) K = K1[ID1,ID1]*K2[ID2,ID2] EVD0 = eigen(K) EVD = tensorEVD(K1, K2, ID1, ID2) c(eigen=sum(EVD0$values>1E-10), tensorEVD=sum(EVD$values>1E-10)) # (e) Sample size n being twice n1 x n2 n = n1*n2*2 ID1 = sample(seq(n1), n, replace=TRUE) ID2 = sample(seq(n2), n, replace=TRUE) K = K1[ID1,ID1]*K2[ID2,ID2] EVD0 = eigen(K) EVD = tensorEVD(K1, K2, ID1, ID2) c(eigen=sum(EVD0$values>1E-10), tensorEVD=sum(EVD$values>1E-10))
Computes the Hadamard product between two matrices
Sum(a = 1, A, b = 1, B, IDrowA, IDrowB, IDcolA = NULL, IDcolB = NULL, make.dimnames = FALSE, drop = TRUE, inplace = FALSE)
Sum(a = 1, A, b = 1, B, IDrowA, IDrowB, IDcolA = NULL, IDcolB = NULL, make.dimnames = FALSE, drop = TRUE, inplace = FALSE)
a |
(numeric) A constant to multiply the first matrix by |
A |
(numeric) Numeric matrix |
b |
(numeric) A constant to multiply the second matrix by |
B |
(numeric) Numeric matrix |
IDrowA |
(integer/character) Vector of length m with either indices or row names mapping from rows of |
IDrowB |
(integer/character) Vector of length m with either indices or row names mapping from rows of |
IDcolA |
(integer/character) (Optional) Similar to |
IDcolB |
(integer/character) (Optional) Similar to |
drop |
Either |
make.dimnames |
|
inplace |
|
Computes the m × n weighted sum matrix between matrices A and B,
a(R1A C'1) + b(R2B C'2)
where R1 and R2 are incidence matrices mapping from rows of the resulting sum to rows of A and B, respectively; and C1 and C2 are incidence matrices mapping from columns of the resulting sum to columns of A and B, respectively.
Matrix R1A C'1
can be obtained by matrix indexing as A[IDrowA,IDcolA]
, where IDrowA
and IDcolA
are integer vectors whose entries are, respectively, the row and column number of
A that are mapped at each row of
R1 and
C1, respectively.
Likewise, matrix
R2B C'2
can be obtained as B[IDrowB,IDcolB]
, where IDrowB
and IDcolB
are integer vectors whose entries are, respectively, the row and column number of
B that are mapped at each row of
R2 and
C2, respectively. Therefore, the weighted sum can be obtained directly as
a*A[IDrowA,IDcolA] + b*B[IDrowB,IDcolB]
The function computes the Hadamard product directly from A and B without forming R1A C'1 or R2B C'2 matrices. The result can be multiplied by a constant a.
Returns a matrix containing the Hadamard product.
require(tensorEVD) # Generate rectangular matrices A (nrowA x ncolA) and B (nrowB x ncolB) nA = c(10,15) nB = c(12,8) A = matrix(rnorm(nA[1]*nA[2]), nrow=nA[1]) B = matrix(rnorm(nB[1]*nB[2]), nrow=nB[1]) # Define IDs for a Hadamard of size n1 x n2 n = c(1000,500) IDrowA = sample(nA[1], n[1], replace=TRUE) IDrowB = sample(nB[1], n[1], replace=TRUE) IDcolA = sample(nA[2], n[2], replace=TRUE) IDcolB = sample(nB[2], n[2], replace=TRUE) a = rnorm(1) b = rnorm(1) K1 = Sum(a, A, b, B, IDrowA, IDrowB, IDcolA, IDcolB) # (it must equal to:) K2 = a*A[IDrowA,IDcolA] + b*B[IDrowB,IDcolB] all.equal(K1,K2)
require(tensorEVD) # Generate rectangular matrices A (nrowA x ncolA) and B (nrowB x ncolB) nA = c(10,15) nB = c(12,8) A = matrix(rnorm(nA[1]*nA[2]), nrow=nA[1]) B = matrix(rnorm(nB[1]*nB[2]), nrow=nB[1]) # Define IDs for a Hadamard of size n1 x n2 n = c(1000,500) IDrowA = sample(nA[1], n[1], replace=TRUE) IDrowB = sample(nB[1], n[1], replace=TRUE) IDcolA = sample(nA[2], n[2], replace=TRUE) IDcolB = sample(nB[2], n[2], replace=TRUE) a = rnorm(1) b = rnorm(1) K1 = Sum(a, A, b, B, IDrowA, IDrowB, IDcolA, IDcolB) # (it must equal to:) K2 = a*A[IDrowA,IDcolA] + b*B[IDrowB,IDcolB] all.equal(K1,K2)