2023-09-12 @Atsushi Kawaguchi
In this vignette, the output is omitted. Please refer to the following book for the output.
Kawaguchi A. (2021). Multivariate Analysis for Neuroimaging Data. CRC Press.
First, we prepare and then we create the data matrix. Install package (as necessary)
if(!require("mand")) install.packages("mand")
Load package
library(mand)
The mand
package has a function to generate simulation
brain data from the base image, the difference image and the standard
deviation image. These basic images are loaded as follows.
data(baseimg)
data(diffimg)
data(mask)
data(sdevimg)
The number of voxels in the original 3D image is as follows.
dim(baseimg)
To understand the result easily, the difference region was restricted to Parahippocampus and Hippocampus.
diffimg2 = diffimg * (tmpatlas %in% 37:40)
An image data matrix with subjects in the rows and voxels in the
columns was generated by using the simbrain
function.
img1 = simbrain(baseimg = baseimg, diffimg = diffimg2, sdevimg=sdevimg, mask=mask, n0=20, c1=0.01, sd1=0.05)
The base image, the difference image and the standard deviation image were specified in the first three arguments. The out-of-brain region was specified by the mask argument, which was the binary image. The remaining arguments are the number of subjects per group, the coefficient multiplied by the difference image and the standard deviation for the noise.
The data matrix dimension was as follows.
dim(img1$S)
The rec
function creates the 3D image from the
vectorized data (the first subject).
coat(rec(img1$S[1,], img1$imagedim, mask=img1$brainpos))
The standard deviation image is created from the resulting data matrix.
sdimg = apply(img1$S, 2, sd)
coat(template, rec(sdimg, img1$imagedim, mask=img1$brainpos))
If the input is a matrix, a principal component analysis is
implemented by the msma
function of the msma
package. Principal component analysis with the number of components of 2
for the image data matrix is performed as follows.
(fit111 = msma(img1$S, comp=2))
The scatter plots for the score vectors specified by the argument
v
. The argument axes
is specified by the two
length vectors on which components are displayed.
plot(fit111, v="score", axes = 1:2, plottype="scatter")
The weight (loading) vectors can be obtained and reconstructed as follows.
midx = 1 ## the index for the modality
vidx = 1 ## the index for the component
Q = fit111$wbX[[midx]][,vidx]
outstat1 = rec(Q, img1$imagedim, mask=img1$brainpos)
The reconstructed loadings as image is overlayed on the template.
coat(template, outstat1)
The output is unclear; however, this will be improved later.
This is an example of the two-step dimension reduction.
Generate radial basis function.
B1 = rbfunc(imagedim=img1$imagedim, seppix=2, hispec=FALSE,
mask=img1$brainpos)
Multiplying the basis function to image data matrix.
SB1 = basisprod(img1$S, B1)
The original dimension was as follows.
dim(img1$S)
The dimension was reduced to as follows.
dim(SB1)
The Principal Component Analysis (PCA) is applied to the dimension-reduced image.
(fit211 = msma(SB1, comp=2))
The loading is reconstructed to the original space by using the
rec
function.
Q = fit211$wbX[[1]][,1]
outstat1 = rec(Q, img1$imagedim, B=B1, mask=img1$brainpos)
The plotted (sign-flipping) loading is smoother than the one without the dimension reduction by the basis function.
outstat2 = -outstat1
coat(template, outstat2)
If lambdaX
(>0) is specified, a sparse principal
component analysis is implemented.
(fit112 = msma(SB1, comp=2, lambdaX=0.075))
The plotted loading is narrower than that from the PCA.
Q = fit112$wbX[[midx]][,vidx]
outstat1 = rec(Q, img1$imagedim, B=B1, mask=img1$brainpos)
outstat2 = outstat1
coat(template, outstat2)
The region is reported as follows to be compared with the next method.
atlastable(tmpatlas, outstat2, atlasdataset)
The simbrain
generates the synthetic brain image data
and the binary outcome. The outcome Z is obtained.
Z = img1$Z
If the outcome Z is specified in the msma
function, a
supervised sparse principal component analysis is implemented.
(fit113 = msma(SB1, Z=Z, comp=2, lambdaX=0.075, muX=0.5))
The plotted loading is located differently from the sparse PCA.
Q = fit113$wbX[[1]][,1]
outstat1 = rec(Q, img1$imagedim, B=B1, mask=img1$brainpos)
outstat2 = -outstat1
coat(template, outstat2)
The region near the hippocampus, which differs from the sparse PCA (without supervision).
atlastable(tmpatlas, outstat2, atlasdataset)
The loading for the second component
Q = fit113$wbX[[1]][,2]
outstat1 = rec(Q, img1$imagedim, B=B1, mask=img1$brainpos)
outstat2 = -outstat1
coat(template, outstat2)
atlastable(tmpatlas, outstat2, atlasdataset)
This is similar to the result from the sparse PCA (without supervision).
The following method can be used to plot the weights of several
components simultaneously. It is first reconstructed in three dimensions
with the multirec
function and then plotted with the
multicompplot
function. It is set to display four columns
per component.
ws = multirec(fit113, imagedim=img1$imagedim, B=B1,
mask=img1$brainpos)
multicompplot(ws, template, col4comp=4)
In this section, we perform some experiments to show the characteristics of the methods in the previous section.
The basis function is parameterized by the radius, which is the
argument seppix
of the rbfunc
function. The
msma
function is applied.
seppixs = 2:7
fit115s = lapply(seppixs, function(sp){
B1 = rbfunc(imagedim=img1$imagedim, seppix=sp,
hispec=FALSE, mask=img1$brainpos)
SB1 = basisprod(img1$S, B1)
fit=msma(SB1, Z=Z, comp=2, lambdaX=0.075, muX=0.5)
list(fit=fit, B1=B1)
})
par(mfrow=c(2,3), mar=c(1,2,1,2))
for(i in 1:length(seppixs)){
Q = fit115s[[i]]$fit$wbX[[midx]][,vidx]
outstat1 = rec(Q, img1$imagedim, B=fit115s[[i]]$B1,
mask=img1$brainpos)
coat(template, -outstat1, pseq=10,color.bar=FALSE,
paron=FALSE, main=paste("seppix =", seppixs[i]))
}
With the larger value it is difficult to determine the region.
The iterative fits with different regularized parameters are
implemented specifying them using the argument lambdaX
.
lambdaXs = round(seq(0, 0.2, by=0.005), 3)
fit114s = lapply(lambdaXs, function(lam)
msma(SB1, Z=Z, comp=2, lambdaX=lam, muX=0.5, type="lasso") )
For some of the parameters, the sparsity can be controlled by \(\lambda\), as shown below.
lambdaXs2 = c(0, 0.025, 0.05, 0.075, 0.1, 0.15)
par(mfrow=c(2,3), mar=c(1,2,1,2))
for(i in which(lambdaXs %in% lambdaXs2)){
Q = fit114s[[i]]$wbX[[1]][,1]
outstat1 = rec(Q, img1$imagedim, B=B1, mask=img1$brainpos)
coat(template, -outstat1, pseq=10,color.bar=FALSE,
paron=FALSE, main=paste("lambda =", lambdaXs[i]))
}
The optimal value can be determined by the BIC.
nzwbXs = unlist(lapply(fit114s, function(x) x$nzwbX[2]))
BICs = unlist(lapply(fit114s, function(x) x$bic[2]))
(optlam = lambdaXs[which.min(BICs)])
(optnzw = nzwbXs[which.min(BICs)])
The plot of BIC values against the number of non-zero loadings and the \(\lambda\).
par(mfrow=c(1,2))
plot(lambdaXs, BICs, xlab="lambda", ylab="BIC")
abline(v=optlam, col="red", lty=2)
plot(nzwbXs, BICs, xlab="Number of non-zeros", ylab="BIC", log="x")
abline(v=optnzw, col="red", lty=2)
The optimal BIC value was \(\lambda\) which was relatively better among reconstructed loading images with different \(\lambda\)’s as shown above.
For the msma
function, four types of penalty functions
are currently available. These depend on parameters, “lasso” and “hard”
depend on one parameter, and “scad” and “mcp” depend on two
parameters.
penalties2 = c("lasso", "hard", "scad", "mcp")
etas = list(lasso=1, hard=1, scad=c(1, 3.7), mcp=c(2, 3))
Using the internal function sparse
in the
mand
package, we see how each penalty function performs the
transformation.
xs = seq(-6, 6, by=0.1)
par(mfrow=c(2,3), mar=c(2,2,3,2))
for(p1 in penalties2){
eta1 = etas[[p1]]
for(e1 in eta1){
sout1 = sparse(xs, 2, type=p1, eta=e1)
plot(xs, sout1, xlab="", ylab="",
main=paste(p1, "(eta =", e1, ")"), type="b")
}}
Next, we apply each penalty function in the msma
function to the brain image data.
par(mfrow=c(2,3), mar=c(1,2,1,2))
for(p1 in penalties2){
eta1 = etas[[p1]]
for(e1 in eta1){
fit = msma(SB1, Z=Z, comp=2, lambdaX=0.025, muX=0.5,
type=p1, eta=e1)
Q = fit$wbX[[midx]][,vidx]
outstat1 = rec(Q, img1$imagedim, B=B1, mask=img1$brainpos)
outstat2 = -outstat1
coat(template, outstat2, pseq=10, color.bar=FALSE,
paron=FALSE, main=paste(p1, "(eta =", e1, ")"))
}}
The number of components was set to be 30.
fit114 = msma(SB1, Z=Z, comp=30, muX=0.5)
The cumulative percentage of the explained variance (CPEV) is plotted
against the numbers of components. When the argument v
is
specified as “cpev”, the CPEVs are plotted.
plot(fit114, v="cpev")
abline(h=0.8, lty=2)
The function ncompsearch
searches for the optimal value
based on the criterion.
(ncomp1 = ncompsearch(SB1, Z=Z, muX=0.5,
comps = 50, criterion="BIC"))
The criterion can use not only BIC but also CV (Cross Validation).
(ncomp2 = ncompsearch(SB1, Z=Z, muX=0.5,
comps = c(1, seq(5, 30, by=5)), criterion="CV"))
par(mfrow=c(1,2))
plot(ncomp1)
plot(ncomp2)
For each component number from 1 to 5, the optimal \(\lambda\) was selected, and the number of non-zero loadings in each component was counted.
maxncomp = 5
opts = sapply(1:maxncomp, function(c1){
opt=regparasearch(SB1, Z=Z, comp=c1, muX=0.5)$optlambdaX
fit = msma(SB1, Z=Z, comp=c1, lambdaX=opt, muX=0.5)
nz = rep(NA, maxncomp)
nz[1:c1] = fit$nzwbX
c(c1, round(opt,3), nz)
})
opts1=t(opts)
colnames(opts1) = c("#comp", "lambda", paste("comp",1:maxncomp))
kable(opts1, "latex", booktabs = T)
From this result, it can be seen that when the number of components is large, a small non-zero loadings are selected for each component, and when the number of components is small, a large non-zero loadings are selected for each component.
(ncomp3 = ncompsearch(SB1, Z=Z, muX=0.5, lambdaX=0.075, comps = 30, criterion="BIC"))
plot(ncomp3)
Thus, there are several strategies for selecting the \(\lambda\) and the number of components. The
msma
package has an optparasearch
function and
the search.method
argument allows you to select one of four
methods.
The regparaonly
method searches for the regularized
parameters with a fixed number of components (set to be 5 and 10 as the
default).
(opt11 = optparasearch(SB1, Z=Z, muX=0.5, comp=5, search.method = "regparaonly", criterion="BIC"))
The optimized parameters are used in the msma
function
as follows.
(fit311 = msma(SB1, Z=Z, muX=0.5, comp=opt11$optncomp, lambdaX=opt11$optlambdaX))
The regpara1st
identifies the regularized parameters by
fixing the number of components, then searching for the number of
components with the selected regularized parameters. Note that the
default is to search up to 10 components.
(opt12 = optparasearch(SB1, Z=Z, muX=0.5, search.method = "regpara1st", criterion="BIC"))
fit312 = msma(SB1, Z=Z, muX=0.5, comp=opt12$optncomp, lambdaX=opt12$optlambdaX)
The ncomp1st
method identifies the number of components
with a regularized parameter of 0, then searches for the regularized
parameters with the selected number of components.
(opt13 = optparasearch(SB1, Z=Z, muX=0.5, search.method = "ncomp1st", criterion="BIC"))
fit313 = msma(SB1, Z=Z, muX=0.5, comp=opt13$optncomp, lambdaX=opt13$optlambdaX)
If the argument criterion4ncomp
is specified as
criterion4ncomp="CV"
, only the criteria for selecting the
number of components can be changed to CV.
The simultaneous
method identifies the number of
components by searching the regularized parameters in each
component.
(opt14 = optparasearch(SB1, Z=Z, muX=0.5, search.method = "simultaneous", criterion="BIC"))
fit314 = msma(SB1, Z=Z, muX=0.5, comp=opt14$optncomp, lambdaX=opt14$optlambdaX)
In this example, the results were the same except for
thencomp1st
.
The NMF can be implemented by invoking the nmf
function
of the NMF library.
if(!require("NMF")) install.packages("NMF")
library(NMF)
NMF can be performed on a brain image data matrix with the number of components as 2 in the following way.
res = nmf(SB1, 2)
Using the coef
function, we can extract the weights and
plot them on the brain image using the coat
function in the
same way as when applying the msma
function.
Q = t(coef(res))[,1]
outstat1 = rec(Q, img1$imagedim, B=B1, mask=img1$brainpos)
coat(template, outstat1)
The ICA can be implemented by invoking the icaimax
function of the ica
library.
if(!require("ica")) install.packages("ica")
library(ica)
ICA using the information-maximization (Infomax) approach can be performed on a brain image data matrix with the number of components as 2 in the following way.
imod = icaimax(SB1,2)
In this case, the weights are extracted as follows and plotted on the brain image in the same way.
Q = imod$M[,1]
outstat1 = rec(Q, img1$imagedim, B=B1, mask=img1$brainpos)
coat(template, outstat1)
The package for drawing more informative dendrograms is loaded.
library(dendextend)
By inputting fit111, which is the result of PCA being applied to the
hcmsma
function by the msma
function,
hierarchical clustering with the score as input is executed.
hcmsma111 = hcmsma(fit111)
The next step is to plot and draw the dendrogram.
dend = as.dendrogram(hcmsma111$hcout)
d1 = color_branches(dend, k=4, groupLabels=TRUE)
labels_colors(d1) = Z[as.numeric(labels(d1))]+1
plot(d1)
The data was set to be divided into four clusters. The number at the bottom of the dendrogram is the subject number, where black is the control and red is the case.
It seems that clusters 1 and 2 have many cases, and cluster 4 also seems to have many cases. The number is summarized as the matrix with the case or control in the row and the cluster to belong in the column.
clus=cutree(d1, 4, order_clusters_as_data = FALSE)
clus=clus[as.character(1:length(clus))]
table(Z, clus)
As can be seen, the PCA score did not yield a cluster that completely bisected case and control.
Similarly, we performed clustering using the scores calculated from the sparse PCA.
hcmsma112 = hcmsma(fit112)
dend = as.dendrogram(hcmsma112$hcout)
d1 = color_branches(dend, k=4, groupLabels=TRUE)
labels_colors(d1) = Z[as.numeric(labels(d1))]+1
plot(d1)
clus=cutree(d1, 4, order_clusters_as_data = FALSE)
clus=clus[as.character(1:length(clus))]
table(Z, clus)
This result shows that case and control are more sparsely clustered.
Finally, we performed clustering with scores from the supervised sparse PCA.
hcmsma113 = hcmsma(fit113)
dend = as.dendrogram(hcmsma113$hcout)
d1 = color_branches(dend, k=4, groupLabels=TRUE)
labels_colors(d1) = Z[as.numeric(labels(d1))]+1
plot(d1)
clus=cutree(d1, 4, order_clusters_as_data = FALSE)
clus=clus[as.character(1:length(clus))]
table(Z, clus)
Because they are supervised by this case or control, the case and control are nicely clustered. This analysis indicates that the case is further divided into two clusters. For practical purposes, it may be useful for disease subtype classification.