# and matrix-representation specific functions:
# - computeSigmaHat
# - computeMuHat
# - derivative.F
# initital version: YR 2011-01-21: LISREL stuff
# updates: YR 2011-12-01: group specific extraction
# YR 2012-05-17: thresholds
representation.LISREL <- function(partable = NULL,
target = NULL,
extra = FALSE,
remove.nonexisting = TRUE) {
# prepare target list
if(is.null(target)) target <- partable
stopifnot(!is.null(target$block))
# prepare output
N <- length(target$lhs)
tmp.mat <- character(N); tmp.row <- integer(N); tmp.col <- integer(N)
# global settings
meanstructure <- any(partable$op == "~1")
categorical <- any(partable$op == "|")
group.w.free <- any(partable$lhs == "group" & partable$op == "%")
# gamma?only if conditional.x
if(any(partable$op == "~" & partable$exo == 1L)) {
gamma <- TRUE
} else {
gamma <- FALSE
}
# number of blocks
nblocks <- lav_partable_nblocks(partable)
ov.dummy.names.nox <- vector("list", nblocks)
ov.dummy.names.x <- vector("list", nblocks)
if(extra) {
REP.mmNames <- vector("list", nblocks)
REP.mmNumber <- vector("list", nblocks)
REP.mmRows <- vector("list", nblocks)
REP.mmCols <- vector("list", nblocks)
REP.mmDimNames <- vector("list", nblocks)
REP.mmSymmetric <- vector("list", nblocks)
}
for(g in 1:nblocks) {
# info from user model per block
if(gamma) {
ov.names <- vnames(partable, "ov.nox", block=g)
} else {
ov.names <- vnames(partable, "ov", block=g)
}
nvar <- length(ov.names)
lv.names <- vnames(partable, "lv", block=g); nfac <- length(lv.names)
ov.th <- vnames(partable, "th", block=g); nth <- length(ov.th)
ov.names.x <- vnames(partable, "ov.x",block=g); nexo <- length(ov.names.x)
ov.names.nox <- vnames(partable, "ov.nox",block=g)
# in this representation, we need to create 'phantom/dummy' latent
# variables for all `x' and `y' variables not in lv.names
# (only y if conditional.x = TRUE)
# regression dummys
if(gamma) {
tmp.names <-
unique( partable$lhs[(partable$op == "~" |
partable$op == "<~") &
partable$block == g] )
} else {
tmp.names <-
unique( c(partable$lhs[(partable$op == "~" |
partable$op == "<~") &
partable$block == g],
partable$rhs[(partable$op == "~" |
partable$op == "<~") &
partable$block == g]) )
}
dummy.names1 <- tmp.names[ !tmp.names %in% lv.names ]
# covariances involving dummys
dummy.cov.idx <- which(partable$op == "~~" & partable$block == g &
(partable$lhs %in% dummy.names1 |
partable$rhs %in% dummy.names1))
# new in 0.5-21: also include covariances involving these covariances...
dummy.cov.idx1 <- which(partable$op == "~~" & partable$block == g &
(partable$lhs %in% partable$lhs[dummy.cov.idx] |
partable$rhs %in% partable$rhs[dummy.cov.idx]))
dummy.cov.idx <- unique(c(dummy.cov.idx, dummy.cov.idx1))
dummy.names2 <- unique( c(partable$lhs[dummy.cov.idx],
partable$rhs[dummy.cov.idx]) )
# collect all dummy variables
dummy.names <- unique(c(dummy.names1, dummy.names2))
if(length(dummy.names)) {
# make sure order is the same as ov.names
ov.dummy.names.nox[[g]] <-
ov.names.nox[ ov.names.nox %in% dummy.names ]
ov.dummy.names.x[[g]] <-
ov.names.x[ ov.names.x %in% dummy.names ]
# combine them, make sure order is identical to ov.names
tmp <- ov.names[ ov.names %in% dummy.names ]
# extend lv.names
lv.names <- c(lv.names, tmp)
nfac <- length(lv.names)
# add 'dummy' =~ entries
dummy.mat <- rep("lambda", length(dummy.names))
} else {
ov.dummy.names.nox[[g]] <- character(0)
ov.dummy.names.x[[g]] <- character(0)
}
# 1a. "=~" regular indicators
idx <- which(target$block == g &
target$op == "=~" & !(target$rhs %in% lv.names))
tmp.mat[idx] <- "lambda"
tmp.row[idx] <- match(target$rhs[idx], ov.names)
tmp.col[idx] <- match(target$lhs[idx], lv.names)
# 1b. "=~" regular higher-order lv indicators
idx <- which(target$block == g &
target$op == "=~" & !(target$rhs %in% ov.names))
tmp.mat[idx] <- "beta"
tmp.row[idx] <- match(target$rhs[idx], lv.names)
tmp.col[idx] <- match(target$lhs[idx], lv.names)
# 1c. "=~" indicators that are both in ov and lv
idx <- which(target$block == g &
target$op == "=~" & target$rhs %in% ov.names
& target$rhs %in% lv.names)
tmp.mat[idx] <- "beta"
tmp.row[idx] <- match(target$rhs[idx], lv.names)
tmp.col[idx] <- match(target$lhs[idx], lv.names)
# 2. "~" regressions
if(gamma) {
# gamma
idx <- which(target$rhs %in% ov.names.x &
target$block == g & (target$op == "~" |
target$op == "<~") )
tmp.mat[idx] <- "gamma"
tmp.row[idx] <- match(target$lhs[idx], lv.names)
tmp.col[idx] <- match(target$rhs[idx], ov.names.x)
# beta
idx <- which(!target$rhs %in% ov.names.x &
target$block == g & (target$op == "~" |
target$op == "<~") )
tmp.mat[idx] <- "beta"
tmp.row[idx] <- match(target$lhs[idx], lv.names)
tmp.col[idx] <- match(target$rhs[idx], lv.names)
} else {
idx <- which(target$block == g & (target$op == "~" |
target$op == "<~") )
tmp.mat[idx] <- "beta"
tmp.row[idx] <- match(target$lhs[idx], lv.names)
tmp.col[idx] <- match(target$rhs[idx], lv.names)
}
# 3a. "~~" ov
idx <- which(target$block == g &
target$op == "~~" & !(target$lhs %in% lv.names))
tmp.mat[idx] <- "theta"
tmp.row[idx] <- match(target$lhs[idx], ov.names)
tmp.col[idx] <- match(target$rhs[idx], ov.names)
# 3aa. "~~" ov.x
if(gamma) {
idx <- which(target$block == g &
target$op == "~~" & (target$lhs %in% ov.names.x))
tmp.mat[idx] <- "cov.x"
tmp.row[idx] <- match(target$lhs[idx], ov.names.x)
tmp.col[idx] <- match(target$rhs[idx], ov.names.x)
}
# 3b. "~~" lv
idx <- which(target$block == g &
target$op == "~~" & target$rhs %in% lv.names)
tmp.mat[idx] <- "psi"
tmp.row[idx] <- match(target$lhs[idx], lv.names)
tmp.col[idx] <- match(target$rhs[idx], lv.names)
# 4a. "~1" ov
idx <- which(target$block == g &
target$op == "~1" & !(target$lhs %in% lv.names))
tmp.mat[idx] <- "nu"
tmp.row[idx] <- match(target$lhs[idx], ov.names)
tmp.col[idx] <- 1L
# 4aa, "~1" ov.x
if(gamma) {
idx <- which(target$block == g &
target$op == "~1" & (target$lhs %in% ov.names.x))
tmp.mat[idx] <- "mean.x"
tmp.row[idx] <- match(target$lhs[idx], ov.names.x)
tmp.col[idx] <- 1L
}
# 4b. "~1" lv
idx <- which(target$block == g &
target$op == "~1" & target$lhs %in% lv.names)
tmp.mat[idx] <- "alpha"
tmp.row[idx] <- match(target$lhs[idx], lv.names)
tmp.col[idx] <- 1L
# 5. "|" th
LABEL <- paste(target$lhs, target$op, target$rhs, sep="")
idx <- which(target$block == g &
target$op == "|" & LABEL %in% ov.th)
TH <- paste(target$lhs[idx], "|", target$rhs[idx], sep="")
tmp.mat[idx] <- "tau"
tmp.row[idx] <- match(TH, ov.th)
tmp.col[idx] <- 1L
# 6. "~*~" scales
idx <- which(target$block == g &
target$op == "~*~")
tmp.mat[idx] <- "delta"
tmp.row[idx] <- match(target$lhs[idx], ov.names)
tmp.col[idx] <- 1L
# new 0.5-12: catch lower-elements in theta/psi
idx.lower <- which(tmp.mat %in% c("theta","psi") & tmp.row > tmp.col)
if(length(idx.lower) > 0L) {
tmp <- tmp.row[idx.lower]
tmp.row[idx.lower] <- tmp.col[idx.lower]
tmp.col[idx.lower] <- tmp
}
# new 0.5-16: group weights
idx <- which(target$block == g & target$lhs == "group" &
target$op == "%")
tmp.mat[idx] <- "gw"
tmp.row[idx] <- 1L
tmp.col[idx] <- 1L
if(extra) {
# mRows
mmRows <- list(tau = nth,
delta = nvar,
nu = nvar,
lambda = nvar,
theta = nvar,
alpha = nfac,
beta = nfac,
gamma = nfac,
cov.x = nexo,
mean.x = nexo,
gw = 1L,
psi = nfac)
# mCols
mmCols <- list(tau = 1L,
delta = 1L,
nu = 1L,
lambda = nfac,
theta = nvar,
alpha = 1L,
beta = nfac,
gamma = nexo,
cov.x = nexo,
mean.x = 1L,
gw = 1L,
psi = nfac)
# dimNames for LISREL model matrices
mmDimNames <- list(tau = list( ov.th, "threshold"),
delta = list( ov.names, "scales"),
nu = list( ov.names, "intercept"),
lambda = list( ov.names, lv.names),
theta = list( ov.names, ov.names),
alpha = list( lv.names, "intercept"),
beta = list( lv.names, lv.names),
gamma = list( lv.names, ov.names.x),
cov.x = list( ov.names.x, ov.names.x),
mean.x = list( ov.names.x, "intercepts"),
gw = list( "group", "weight"),
psi = list( lv.names, lv.names))
# isSymmetric
mmSymmetric <- list(tau = FALSE,
delta = FALSE,
nu = FALSE,
lambda = FALSE,
theta = TRUE,
alpha = FALSE,
beta = FALSE,
gamma = FALSE,
cov.x = TRUE,
mean.x = FALSE,
gw = FALSE,
psi = TRUE)
# which mm's do we need? (always include lambda, theta and psi)
# new: 0.6 this block only!!
IDX <- which(target$block == g)
mmNames <- c("lambda", "theta", "psi")
if("beta" %in% tmp.mat[IDX]) {
mmNames <- c(mmNames, "beta")
}
if(meanstructure) {
mmNames <- c(mmNames, "nu", "alpha")
}
if("tau" %in% tmp.mat[IDX]) {
mmNames <- c(mmNames, "tau")
}
if("delta" %in% tmp.mat[IDX]) {
mmNames <- c(mmNames, "delta")
}
if("gamma" %in% tmp.mat[IDX]) {
mmNames <- c(mmNames, "gamma")
}
if("gw" %in% tmp.mat[IDX]) {
mmNames <- c(mmNames, "gw")
}
if("cov.x" %in% tmp.mat[IDX]) {
mmNames <- c(mmNames, "cov.x")
}
if("mean.x" %in% tmp.mat[IDX]) {
mmNames <- c(mmNames, "mean.x")
}
REP.mmNames[[g]] <- mmNames
REP.mmNumber[[g]] <- length(mmNames)
REP.mmRows[[g]] <- unlist(mmRows[ mmNames ])
REP.mmCols[[g]] <- unlist(mmCols[ mmNames ])
REP.mmDimNames[[g]] <- mmDimNames[ mmNames ]
REP.mmSymmetric[[g]] <- unlist(mmSymmetric[ mmNames ])
} # extra
} # nblocks
REP <- list(mat = tmp.mat,
row = tmp.row,
col = tmp.col)
# remove non-existing (NAs)?
# here we remove `non-existing' parameters; this depends on the matrix
# representation (eg in LISREL rep, there is no ~~ between lv and ov)
#if(remove.nonexisting) {
# idx <- which( nchar(REP$mat) > 0L &
# !is.na(REP$row) & REP$row > 0L &
# !is.na(REP$col) & REP$col > 0L )
# # but keep ==, :=, etc.
# idx <- c(idx, which(partable$op %in% c("==", ":=", "<", ">")))
# REP$mat <- REP$mat[idx]
# REP$row <- REP$row[idx]
# REP$col <- REP$col[idx]
#
# always add 'ov.dummy.*.names' attributes
attr(REP, "ov.dummy.names.nox") <- ov.dummy.names.nox
attr(REP, "ov.dummy.names.x") <- ov.dummy.names.x
if(extra) {
attr(REP, "mmNames") <- REP.mmNames
attr(REP, "mmNumber") <- REP.mmNumber
attr(REP, "mmRows") <- REP.mmRows
attr(REP, "mmCols") <- REP.mmCols
attr(REP, "mmDimNames") <- REP.mmDimNames
attr(REP, "mmSymmetric") <- REP.mmSymmetric
}
REP
}
# ETA:
# 1) EETA
# 2) EETAx
# 3) VETA
# 4) VETAx
# 1) EETA
# compute E(ETA): expected value of latent variables (marginal over x)
# - if no eXo (and GAMMA):
# E(ETA) = (I-B)^-1 ALPHA
# - if eXo and GAMMA:
# E(ETA) = (I-B)^-1 ALPHA + (I-B)^-1 GAMMA mean.x
computeEETA.LISREL <- function(MLIST=NULL, mean.x=NULL,
sample.mean=NULL,
ov.y.dummy.ov.idx=NULL,
ov.x.dummy.ov.idx=NULL,
ov.y.dummy.lv.idx=NULL,
ov.x.dummy.lv.idx=NULL) {
LAMBDA <- MLIST$lambda; BETA <- MLIST$beta; GAMMA <- MLIST$gamma
# ALPHA? (reconstruct, but no 'fix')
ALPHA <- .internal_get_ALPHA(MLIST = MLIST, sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# BETA?
if(!is.null(BETA)) {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
# GAMMA?
if(!is.null(GAMMA)) {
eeta <- as.vector(IB.inv %*% ALPHA + IB.inv %*% GAMMA %*% mean.x)
} else {
eeta <- as.vector(IB.inv %*% ALPHA)
}
} else {
# GAMMA?
if(!is.null(GAMMA)) {
eeta <- as.vector(ALPHA + GAMMA %*% mean.x)
} else {
eeta <- as.vector(ALPHA)
}
}
eeta
}
# 2) EETAx
# compute E(ETA|x_i): conditional expected value of latent variable,
# given specific value of x_i
# - if no eXo (and GAMMA):
# E(ETA) = (I-B)^-1 ALPHA
# we return a matrix of size [nobs x nfac] replicating E(ETA)
# - if eXo and GAMMA:
# E(ETA|x_i) = (I-B)^-1 ALPHA + (I-B)^-1 GAMMA x_i
# we return a matrix of size [nobs x nfac]
#
computeEETAx.LISREL <- function(MLIST=NULL, eXo=NULL, N=nrow(eXo),
sample.mean=NULL,
ov.y.dummy.ov.idx=NULL,
ov.x.dummy.ov.idx=NULL,
ov.y.dummy.lv.idx=NULL,
ov.x.dummy.lv.idx=NULL) {
LAMBDA <- MLIST$lambda; BETA <- MLIST$beta; GAMMA <- MLIST$gamma
nfac <- ncol(LAMBDA)
# if eXo, N must be nrow(eXo)
if(!is.null(eXo)) {
N <- nrow(eXo)
}
# ALPHA?
ALPHA <- .internal_get_ALPHA(MLIST = MLIST, sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# construct [nobs x nfac] matrix (repeating ALPHA)
EETA <- matrix(ALPHA, N, nfac, byrow=TRUE)
# put back eXo values if dummy
if(length(ov.x.dummy.lv.idx) > 0L) {
EETA[,ov.x.dummy.lv.idx] <- eXo
}
# BETA?
if(!is.null(BETA)) {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
EETA <- EETA %*% t(IB.inv)
}
# GAMMA?
if(!is.null(GAMMA)) {
if(!is.null(BETA)) {
EETA <- EETA + eXo %*% t(IB.inv %*% GAMMA)
} else {
EETA <- EETA + eXo %*% t(GAMMA)
}
}
EETA
}
# 3) VETA
# compute V(ETA): variances/covariances of latent variables
# - if no eXo (and GAMMA)
# V(ETA) = (I-B)^-1 PSI (I-B)^-T
# - if eXo and GAMMA: (cfr lisrel submodel 3a with ksi=x)
# V(ETA) = (I-B)^-1 [ GAMMA cov.x t(GAMMA) + PSI] (I-B)^-T
computeVETA.LISREL <- function(MLIST = NULL) {
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA)
PSI <- MLIST$psi
THETA <- MLIST$theta
BETA <- MLIST$beta
GAMMA <- MLIST$gamma
if(!is.null(GAMMA)) {
COV.X <- MLIST$cov.x
# we treat 'x' as 'ksi' in the LISREL model; cov.x is PHI
PSI <- tcrossprod(GAMMA %*% COV.X, GAMMA) + PSI
}
# beta?
if(is.null(BETA)) {
VETA <- PSI
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
VETA <- tcrossprod(IB.inv %*% PSI, IB.inv)
}
VETA
}
# 4) VETAx
# compute V(ETA|x_i): variances/covariances of latent variables
# V(ETA) = (I-B)^-1 PSI (I-B)^-T + remove dummies
computeVETAx.LISREL <- function(MLIST=NULL, lv.dummy.idx=NULL) {
PSI <- MLIST$psi
BETA <- MLIST$beta
# beta?
if(is.null(BETA)) {
VETA <- PSI
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
VETA <- tcrossprod(IB.inv %*% PSI, IB.inv)
}
# remove dummy lv?
if(!is.null(lv.dummy.idx)) {
VETA <- VETA[-lv.dummy.idx, -lv.dummy.idx, drop=FALSE]
}
VETA
}
# Y
# 1) EY
# 2) EYx
# 3) EYetax
# 4) VY
# 5) VYx
# 6) VYetax
# 1) EY
# compute E(Y): expected value of observed
# E(Y) = NU + LAMBDA %*% E(eta)
# = NU + LAMBDA %*% (IB.inv %*% ALPHA) # no exo, no GAMMA
# = NU + LAMBDA %*% (IB.inv %*% ALPHA + IB.inv %*% GAMMA %*% mean.x) # eXo
# if DELTA -> E(Y) = delta * E(Y)
#
# this is similar to computeMuHat but:
# - we ALWAYS compute NU+ALPHA, even if meanstructure=FALSE
# - never used if GAMMA, since we then have categorical variables, and the
# 'part 1' structure contains the (thresholds +) intercepts, not
# the means
computeEY.LISREL <- function(MLIST=NULL, mean.x = NULL, sample.mean = NULL,
ov.y.dummy.ov.idx=NULL,
ov.x.dummy.ov.idx=NULL,
ov.y.dummy.lv.idx=NULL,
ov.x.dummy.lv.idx=NULL) {
LAMBDA <- MLIST$lambda
# get NU, but do not 'fix'
NU <- .internal_get_NU(MLIST = MLIST, sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# compute E(ETA)
EETA <- computeEETA.LISREL(MLIST = MLIST, sample.mean = sample.mean,
mean.x = mean.x,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# EY
EY <- as.vector(NU) + as.vector(LAMBDA %*% EETA)
# if delta, scale
if(!is.null(MLIST$delta)) {
EY <- EY * as.vector(MLIST$delta)
}
EY
}
# 2) EYx
# compute E(Y|x_i): expected value of observed, conditional on x_i
# E(Y|x_i) = NU + LAMBDA %*% E(eta|x_i)
# - if no eXo (and GAMMA):
# E(ETA|x_i) = (I-B)^-1 ALPHA
# we return a matrix of size [nobs x nfac] replicating E(ETA)
# - if eXo and GAMMA:
# E(ETA|x_i) = (I-B)^-1 ALPHA + (I-B)^-1 GAMMA x_i
# we return a matrix of size [nobs x nfac]
#
# - we ALWAYS compute NU+ALPHA, even if meanstructure=FALSE
# - never used if GAMMA, since we then have categorical variables, and the
# 'part 1' structure contains the (thresholds +) intercepts, not
# the means
computeEYx.LISREL <- function(MLIST = NULL,
eXo = NULL,
N = nrow(eXo),
sample.mean = NULL,
ov.y.dummy.ov.idx = NULL,
ov.x.dummy.ov.idx = NULL,
ov.y.dummy.lv.idx = NULL,
ov.x.dummy.lv.idx = NULL) {
LAMBDA <- MLIST$lambda
# get NU, but do not 'fix'
NU <- .internal_get_NU(MLIST = MLIST,
sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# compute E(ETA|x_i)
EETAx <- computeEETAx.LISREL(MLIST = MLIST,
eXo = eXo,
N = N,
sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# EYx
EYx <- sweep(tcrossprod(EETAx, LAMBDA), 2L, STATS = NU, FUN = "+")
# if delta, scale
if(!is.null(MLIST$delta)) {
EYx <- sweep(EYx, 2L, STATS = MLIST$delta, FUN = "*")
}
EYx
}
# 3) EYetax
# compute E(Y|eta_i,x_i): conditional expected value of observed variable
# given specific value of eta_i AND x_i
#
# E(y*_i|eta_i, x_i) = NU + LAMBDA eta_i + KAPPA x_i
#
# where eta_i = predict(fit) = factor scores OR specific values for eta_i
# (as in GH integration)
#
# if nexo = 0, and eta_i is single row, YHAT is the same for each observation
# in this case, we return a single row, unless Nobs > 1L, in which case
# we return Nobs identical rows
#
# NOTE: we assume that any effect of x_i on eta_i has already been taken
# care off
# categorical version
computeEYetax.LISREL <- function(MLIST = NULL,
eXo = NULL,
ETA = NULL,
N = nrow(eXo),
sample.mean = NULL,
ov.y.dummy.ov.idx = NULL,
ov.x.dummy.ov.idx = NULL,
ov.y.dummy.lv.idx = NULL,
ov.x.dummy.lv.idx = NULL) {
LAMBDA <- MLIST$lambda
BETA <- MLIST$beta
if(!is.null(eXo)) {
N <- nrow(eXo)
} else if(!is.null(N)) {
# nothing to do
} else {
N <- 1L
}
# create ETA matrix
if(nrow(ETA) == 1L) {
ETA <- matrix(ETA, N, ncol(ETA), byrow=TRUE)
}
# always augment ETA with 'dummy values' (0 for ov.y, eXo for ov.x)
#ndummy <- length(c(ov.y.dummy.lv.idx, ov.x.dummy.lv.idx))
#if(ndummy > 0L) {
# ETA2 <- cbind(ETA, matrix(0, N, ndummy))
#} else {
ETA2 <- ETA
#}
# only if we have dummy ov.y, we need to compute the 'yhat' values
# beforehand
if(length(ov.y.dummy.lv.idx) > 0L) {
# insert eXo values
if(length(ov.x.dummy.lv.idx) > 0L) {
ETA2[,ov.x.dummy.lv.idx] <- eXo
}
# zero ov.y values
if(length(ov.y.dummy.lv.idx) > 0L) {
ETA2[,ov.y.dummy.lv.idx] <- 0
}
# ALPHA? (reconstruct, but no 'fix')
ALPHA <- .internal_get_ALPHA(MLIST = MLIST, sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# BETA?
if(!is.null(BETA)) {
ETA2 <- sweep(tcrossprod(ETA2, BETA), 2L, STATS = ALPHA, FUN = "+")
} else {
ETA2 <- sweep(ETA2, 2L, STATS = ALPHA, FUN = "+")
}
# put back eXo values
if(length(ov.x.dummy.lv.idx) > 0L) {
ETA2[,ov.x.dummy.lv.idx] <- eXo
}
# put back ETA values for the 'real' latent variables
dummy.idx <- c(ov.x.dummy.lv.idx, ov.y.dummy.lv.idx)
if(length(dummy.idx) > 0L) {
lv.regular.idx <- seq_len( min(dummy.idx) - 1L )
ETA2[, lv.regular.idx] <- ETA[,lv.regular.idx, drop = FALSE]
}
}
# get NU, but do not 'fix'
NU <- .internal_get_NU(MLIST = MLIST,
sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# EYetax
EYetax <- sweep(tcrossprod(ETA2, LAMBDA), 2L, STATS = NU, FUN = "+")
# if delta, scale
if(!is.null(MLIST$delta)) {
EYetax <- sweep(EYetax, 2L, STATS = MLIST$delta, FUN = "*")
}
EYetax
}
# unconditional version
computeEYetax2.LISREL <- function(MLIST = NULL,
ETA = NULL,
sample.mean = NULL,
ov.y.dummy.ov.idx = NULL,
ov.x.dummy.ov.idx = NULL,
ov.y.dummy.lv.idx = NULL,
ov.x.dummy.lv.idx = NULL) {
LAMBDA <- MLIST$lambda
BETA <- MLIST$beta
# only if we have dummy ov.y, we need to compute the 'yhat' values
# beforehand, and impute them in ETA[,ov.y]
if(length(ov.y.dummy.lv.idx) > 0L) {
# ALPHA? (reconstruct, but no 'fix')
ALPHA <- .internal_get_ALPHA(MLIST = MLIST, sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# keep all, but ov.y values
OV.NOY <- ETA[,-ov.y.dummy.lv.idx, drop = FALSE]
# ov.y rows, non-ov.y cols
BETAY <- BETA[ov.y.dummy.lv.idx,-ov.y.dummy.lv.idx, drop = FALSE]
# ov.y intercepts
ALPHAY <- ALPHA[ov.y.dummy.lv.idx,, drop=FALSE]
# impute ov.y values in ETA
ETA[,ov.y.dummy.lv.idx] <-
sweep(tcrossprod(OV.NOY, BETAY), 2L, STATS = ALPHAY, FUN = "+")
}
# get NU, but do not 'fix'
NU <- .internal_get_NU(MLIST = MLIST,
sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# EYetax
EYetax <- sweep(tcrossprod(ETA, LAMBDA), 2L, STATS = NU, FUN = "+")
# if delta, scale
if(!is.null(MLIST$delta)) {
EYetax <- sweep(EYetax, 2L, STATS = MLIST$delta, FUN = "*")
}
EYetax
}
# unconditional version
computeEYetax3.LISREL <- function(MLIST = NULL,
ETA = NULL,
sample.mean = NULL,
mean.x = NULL,
ov.y.dummy.ov.idx = NULL,
ov.x.dummy.ov.idx = NULL,
ov.y.dummy.lv.idx = NULL,
ov.x.dummy.lv.idx = NULL) {
LAMBDA <- MLIST$lambda
# special case: empty lambda
if(ncol(LAMBDA) == 0L) {
return( matrix(sample.mean,
nrow(ETA), length(sample.mean), byrow=TRUE) )
}
# lv idx
dummy.idx <- c(ov.y.dummy.lv.idx, ov.x.dummy.lv.idx)
if(length(dummy.idx) > 0L) {
nondummy.idx <- seq_len( min(dummy.idx) - 1L )
} else {
nondummy.idx <- seq_len( ncol(MLIST$lambda) )
}
# beta?
if(is.null(MLIST$beta) || length(ov.y.dummy.lv.idx) == 0L ||
length(nondummy.idx) == 0L) {
LAMBDA..IB.inv <- LAMBDA
} else {
# only keep those columns of BETA that correspond to the
# the `regular' latent variables
# (ie. ignore the structural part altogether)
MLIST2 <- MLIST
MLIST2$beta[,dummy.idx] <- 0
IB.inv <- .internal_get_IB.inv(MLIST = MLIST2)
LAMBDA..IB.inv <- LAMBDA %*% IB.inv
}
# compute model-implied means
EY <- computeEY.LISREL(MLIST = MLIST, mean.x = mean.x,
sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
EETA <- computeEETA.LISREL(MLIST = MLIST, sample.mean = sample.mean,
mean.x = mean.x,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# center regular lv only
ETA[,nondummy.idx] <- sweep(ETA[,nondummy.idx,drop = FALSE], 2L,
STATS = EETA[nondummy.idx], FUN = "-")
# project from lv to ov, if we have any lv
if(length(nondummy.idx) > 0) {
EYetax <- sweep(tcrossprod(ETA[,nondummy.idx,drop=FALSE],
LAMBDA..IB.inv[,nondummy.idx,drop=FALSE]),
2L, STATS = EY, FUN = "+")
} else {
EYetax <- ETA
}
# put back eXo variables
if(length(ov.x.dummy.lv.idx) > 0L) {
EYetax[,ov.x.dummy.ov.idx] <- ETA[,ov.x.dummy.lv.idx, drop = FALSE]
}
# if delta, scale
if(!is.null(MLIST$delta)) {
EYetax <- sweep(EYetax, 2L, STATS = MLIST$delta, FUN = "*")
}
EYetax
}
# 4) VY
# compute the *un*conditional variance/covariance of y: V(Y) or V(Y*)
# 'unconditional' model-implied (co)variances
# - same as Sigma.hat if all Y are continuous
# - diagonal is 1.0 (or delta^2) if categorical
# - if also Gamma, cov.x is used (only if conditional.x)
# only in THIS case, VY is different from diag(VYx)
#
# V(Y) = LAMBDA V(ETA) t(LAMBDA) + THETA
computeVY.LISREL <- function(MLIST = NULL) {
LAMBDA <- MLIST$lambda
THETA <- MLIST$theta
VETA <- computeVETA.LISREL(MLIST = MLIST)
VY <- tcrossprod(LAMBDA %*% VETA, LAMBDA) + THETA
VY
}
# 5) VYx
# compute V(Y*|x_i) == model-implied covariance matrix
# this equals V(Y*) if no (explicit) eXo no GAMMA
computeVYx.LISREL <- computeSigmaHat.LISREL <- function(MLIST = NULL,
delta = TRUE) {
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA)
PSI <- MLIST$psi
THETA <- MLIST$theta
BETA <- MLIST$beta
# beta?
if(is.null(BETA)) {
LAMBDA..IB.inv <- LAMBDA
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
LAMBDA..IB.inv <- LAMBDA %*% IB.inv
}
# compute V(Y*|x_i)
VYx <- tcrossprod(LAMBDA..IB.inv %*% PSI, LAMBDA..IB.inv) + THETA
# if delta, scale
if(delta && !is.null(MLIST$delta)) {
DELTA <- diag(MLIST$delta[,1L], nrow=nvar, ncol=nvar)
VYx <- DELTA %*% VYx %*% DELTA
}
VYx
}
# 6) VYetax
# V(Y | eta_i, x_i) = THETA
computeVYetax.LISREL <- function(MLIST = NULL, delta = TRUE) {
VYetax <- MLIST$theta; nvar <- nrow(MLIST$theta)
# if delta, scale
if(delta && !is.null(MLIST$delta)) {
DELTA <- diag(MLIST$delta[,1L], nrow=nvar, ncol=nvar)
VYetax <- DELTA %*% VYetax %*% DELTA
}
VYetax
}
### compute model-implied sample statistics
#
# 1) MuHat (similar to EY, but continuous only)
# 2) TH
# 3) PI
# 4) SigmaHat == VYx
# compute MuHat for a single block/group; only for the continuous case (no eXo)
#
# this is a special case of E(Y) where
# - we have no (explicit) eXogenous variables
# - only continuous
computeMuHat.LISREL <- function(MLIST=NULL) {
NU <- MLIST$nu
ALPHA <- MLIST$alpha
LAMBDA <- MLIST$lambda
BETA <- MLIST$beta
# shortcut
if(is.null(ALPHA) || is.null(NU)) return(matrix(0, nrow(LAMBDA), 1L))
# beta?
if(is.null(BETA)) {
LAMBDA..IB.inv <- LAMBDA
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
LAMBDA..IB.inv <- LAMBDA %*% IB.inv
}
# compute Mu Hat
Mu.hat <- NU + LAMBDA..IB.inv %*% ALPHA
Mu.hat
}
# compute TH for a single block/group
computeTH.LISREL <- function(MLIST=NULL, th.idx=NULL) {
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA); nfac <- ncol(LAMBDA)
BETA <- MLIST$beta
TAU <- MLIST$tau; nth <- nrow(TAU)
# missing alpha
if(is.null(MLIST$alpha)) {
ALPHA <- matrix(0, nfac, 1L)
} else {
ALPHA <- MLIST$alpha
}
# missing nu
if(is.null(MLIST$nu)) {
NU <- matrix(0, nvar, 1L)
} else {
NU <- MLIST$nu
}
if(is.null(th.idx)) {
th.idx <- seq_len(nth)
nlev <- rep(1L, nvar)
K_nu <- diag(nvar)
} else {
nlev <- tabulate(th.idx, nbins=nvar); nlev[nlev == 0L] <- 1L
K_nu <- matrix(0, sum(nlev), nvar)
K_nu[ cbind(seq_len(sum(nlev)), rep(seq_len(nvar), times=nlev)) ] <- 1.0
}
# shortcut
if(is.null(TAU)) return(matrix(0, length(th.idx), 1L))
# beta?
if(is.null(BETA)) {
LAMBDA..IB.inv <- LAMBDA
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
LAMBDA..IB.inv <- LAMBDA %*% IB.inv
}
# compute pi0
pi0 <- NU + LAMBDA..IB.inv %*% ALPHA
# interleave th's with zeros where we have numeric variables
th <- numeric( length(th.idx) )
th[ th.idx > 0L ] <- TAU[,1L]
# compute TH
TH <- th - (K_nu %*% pi0)
# if delta, scale
if(!is.null(MLIST$delta)) {
DELTA.diag <- MLIST$delta[,1L]
DELTA.star.diag <- rep(DELTA.diag, times=nlev)
TH <- TH * DELTA.star.diag
}
as.vector(TH)
}
# compute PI for a single block/group
computePI.LISREL <- function(MLIST=NULL) {
LAMBDA <- MLIST$lambda
BETA <- MLIST$beta
GAMMA <- MLIST$gamma
# shortcut
if(is.null(GAMMA)) return(matrix(0, nrow(LAMBDA), 0L))
# beta?
if(is.null(BETA)) {
LAMBDA..IB.inv <- LAMBDA
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
LAMBDA..IB.inv <- LAMBDA %*% IB.inv
}
# compute PI
PI <- LAMBDA..IB.inv %*% GAMMA
# if delta, scale
if(!is.null(MLIST$delta)) {
DELTA.diag <- MLIST$delta[,1L]
PI <- PI * DELTA.diag
}
PI
}
computeLAMBDA.LISREL <- function(MLIST = NULL,
ov.y.dummy.ov.idx = NULL,
ov.x.dummy.ov.idx = NULL,
ov.y.dummy.lv.idx = NULL,
ov.x.dummy.lv.idx = NULL,
remove.dummy.lv = FALSE) {
ov.dummy.idx = c(ov.y.dummy.ov.idx, ov.x.dummy.ov.idx)
lv.dummy.idx = c(ov.y.dummy.lv.idx, ov.x.dummy.lv.idx)
# fix LAMBDA
LAMBDA <- MLIST$lambda
if(length(ov.y.dummy.ov.idx) > 0L) {
LAMBDA[ov.y.dummy.ov.idx,] <- MLIST$beta[ov.y.dummy.lv.idx,]
}
# remove dummy lv?
if(remove.dummy.lv && length(lv.dummy.idx) > 0L) {
LAMBDA <- LAMBDA[,-lv.dummy.idx,drop=FALSE]
}
LAMBDA
}
computeTHETA.LISREL <- function(MLIST=NULL,
ov.y.dummy.ov.idx=NULL,
ov.x.dummy.ov.idx=NULL,
ov.y.dummy.lv.idx=NULL,
ov.x.dummy.lv.idx=NULL) {
ov.dummy.idx = c(ov.y.dummy.ov.idx, ov.x.dummy.ov.idx)
lv.dummy.idx = c(ov.y.dummy.lv.idx, ov.x.dummy.lv.idx)
# fix THETA
THETA <- MLIST$theta
if(length(ov.dummy.idx) > 0L) {
THETA[ov.dummy.idx, ov.dummy.idx] <-
MLIST$psi[lv.dummy.idx, lv.dummy.idx]
}
THETA
}
# compute IB.inv
.internal_get_IB.inv <- function(MLIST = NULL) {
BETA <- MLIST$beta; nr <- nrow(MLIST$psi)
if(!is.null(BETA)) {
tmp <- -BETA
tmp[lav_matrix_diag_idx(nr)] <- 1
IB.inv <- solve(tmp)
} else {
IB.inv <- diag(nr)
}
IB.inv
}
# only if ALPHA=NULL but we need it anyway
# we 'reconstruct' ALPHA here (including dummy entries), no fixing
#
# without any dummy variables, this is just the zero vector
# but if we have dummy variables, we need to fill in their values
#
#
.internal_get_ALPHA <- function(MLIST = NULL, sample.mean = NULL,
ov.y.dummy.ov.idx = NULL,
ov.x.dummy.ov.idx = NULL,
ov.y.dummy.lv.idx = NULL,
ov.x.dummy.lv.idx = NULL) {
if(!is.null(MLIST$alpha)) return(MLIST$alpha)
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA); nfac <- ncol(LAMBDA)
BETA <- MLIST$beta
ov.dummy.idx = c(ov.y.dummy.ov.idx, ov.x.dummy.ov.idx)
lv.dummy.idx = c(ov.y.dummy.lv.idx, ov.x.dummy.lv.idx)
if(length(ov.dummy.idx) > 0L) {
ALPHA <- matrix(0, nfac, 1L)
# Note: instead of sample.mean, we need 'intercepts'
# sample.mean = NU + LAMBDA..IB.inv %*% ALPHA
# so,
# solve(LAMBDA..IB.inv) %*% (sample.mean - NU) = ALPHA
# where
# - LAMBDA..IB.inv only contains 'dummy' variables, and is square
# - NU elements are not needed (since not in ov.dummy.idx)
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
LAMBDA..IB.inv <- LAMBDA %*% IB.inv
LAMBDA..IB.inv.dummy <- LAMBDA..IB.inv[ov.dummy.idx, lv.dummy.idx]
ALPHA[lv.dummy.idx] <-
solve(LAMBDA..IB.inv.dummy) %*% sample.mean[ov.dummy.idx]
} else {
ALPHA <- matrix(0, nfac, 1L)
}
ALPHA
}
# only if NU=NULL but we need it anyway
#
# since we have no meanstructure, we can assume NU is unrestricted
# and contains either:
# 1) the sample means (if not eXo)
# 2) the intercepts, if we have exogenous covariates
# since sample.mean = NU + LAMBDA %*% E(eta)
# we have NU = sample.mean - LAMBDA %*% E(eta)
.internal_get_NU <- function(MLIST = NULL, sample.mean = NULL,
ov.y.dummy.ov.idx = NULL,
ov.x.dummy.ov.idx = NULL,
ov.y.dummy.lv.idx = NULL,
ov.x.dummy.lv.idx = NULL) {
if(!is.null(MLIST$nu)) return(MLIST$nu)
# if nexo > 0, substract lambda %*% EETA
if( length(ov.x.dummy.ov.idx) > 0L ) {
EETA <- computeEETA.LISREL(MLIST, mean.x=NULL,
sample.mean=sample.mean,
ov.y.dummy.ov.idx=ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx=ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx=ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx=ov.x.dummy.lv.idx)
# 'regress' NU on X
NU <- sample.mean - MLIST$lambda %*% EETA
# just to make sure we have exact zeroes for all dummies
NU[c(ov.y.dummy.ov.idx,ov.x.dummy.ov.idx)] <- 0
} else {
# unrestricted mean
NU <- sample.mean
}
NU
}
.internal_get_KAPPA <- function(MLIST = NULL,
ov.y.dummy.ov.idx = NULL,
ov.x.dummy.ov.idx = NULL,
ov.y.dummy.lv.idx = NULL,
ov.x.dummy.lv.idx = NULL,
nexo = NULL) {
nvar <- nrow(MLIST$lambda)
if(!is.null(MLIST$gamma)) {
nexo <- ncol(MLIST$gamma)
} else if(!is.null(nexo)) {
nexo <- nexo
} else {
stop("nexo not known")
}
# create KAPPA
KAPPA <- matrix(0, nvar, nexo)
if(!is.null(MLIST$gamma)) {
KAPPA[ov.y.dummy.ov.idx,] <-
MLIST$gamma[ov.y.dummy.lv.idx,,drop=FALSE]
} else if(length(ov.x.dummy.ov.idx) > 0L) {
KAPPA[ov.y.dummy.ov.idx,] <-
MLIST$beta[ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx, drop=FALSE]
}
KAPPA
}
# old version of computeEYetax (using 'fixing')
computeYHATetax.LISREL <- function(MLIST=NULL, eXo=NULL, ETA=NULL,
sample.mean=NULL,
ov.y.dummy.ov.idx=NULL,
ov.x.dummy.ov.idx=NULL,
ov.y.dummy.lv.idx=NULL,
ov.x.dummy.lv.idx=NULL,
Nobs = 1L) {
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA); nfac <- ncol(LAMBDA)
lv.dummy.idx <- c(ov.y.dummy.lv.idx, ov.x.dummy.lv.idx)
ov.dummy.idx <- c(ov.y.dummy.ov.idx, ov.x.dummy.ov.idx)
# exogenous variables?
if(is.null(eXo)) {
nexo <- 0L
} else {
nexo <- ncol(eXo)
# check ETA rows
if(!(nrow(ETA) == 1L || nrow(ETA) == nrow(eXo))) {
stop("psindex ERROR: !(nrow(ETA) == 1L || nrow(ETA) == nrow(eXo))")
}
}
# get NU
NU <- .internal_get_NU(MLIST = MLIST, sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# ALPHA? (reconstruct, but no 'fix')
ALPHA <- .internal_get_ALPHA(MLIST = MLIST, sample.mean = sample.mean,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx)
# fix NU
if(length(lv.dummy.idx) > 0L) {
NU[ov.dummy.idx, 1L] <- ALPHA[lv.dummy.idx, 1L]
}
# fix LAMBDA (remove dummies) ## FIXME -- needed?
LAMBDA <- MLIST$lambda
if(length(lv.dummy.idx) > 0L) {
LAMBDA <- LAMBDA[, -lv.dummy.idx, drop=FALSE]
nfac <- ncol(LAMBDA)
LAMBDA[ov.y.dummy.ov.idx,] <-
MLIST$beta[ov.y.dummy.lv.idx, seq_len(nfac), drop=FALSE]
}
# compute YHAT
YHAT <- sweep(ETA %*% t(LAMBDA), MARGIN=2, NU, "+")
# Kappa + eXo?
# note: Kappa elements are either in Gamma or in Beta
if(nexo > 0L) {
# create KAPPA
KAPPA <- .internal_get_KAPPA(MLIST = MLIST,
ov.y.dummy.ov.idx = ov.y.dummy.ov.idx,
ov.x.dummy.ov.idx = ov.x.dummy.ov.idx,
ov.y.dummy.lv.idx = ov.y.dummy.lv.idx,
ov.x.dummy.lv.idx = ov.x.dummy.lv.idx,
nexo = nexo)
# expand YHAT if ETA only has 1 row
if(nrow(YHAT) == 1L) {
YHAT <- sweep(eXo %*% t(KAPPA), MARGIN=2, STATS=YHAT, FUN="+")
} else {
# add fixed part
YHAT <- YHAT + (eXo %*% t(KAPPA))
}
# put back eXo
if(length(ov.x.dummy.ov.idx) > 0L) {
YHAT[, ov.x.dummy.ov.idx] <- eXo
}
} else {
# duplicate?
if(is.numeric(Nobs) && Nobs > 1L && nrow(YHAT) == 1L) {
YHAT <- matrix(YHAT, Nobs, nvar, byrow=TRUE)
# YHAT <- YHAT[ rep(1L, Nobs), ]
}
}
# delta?
# FIXME: not used here?
#if(!is.null(DELTA)) {
# YHAT <- sweep(YHAT, MARGIN=2, DELTA, "*")
#}
YHAT
}
# deal with 'dummy' OV.X latent variables
# create additional matrices (eg GAMMA), and resize
# remove all ov.x related entries
MLIST2MLISTX <- function(MLIST=NULL,
ov.x.dummy.ov.idx = NULL,
ov.x.dummy.lv.idx = NULL) {
lv.idx <- ov.x.dummy.lv.idx
ov.idx <- ov.x.dummy.ov.idx
if(length(lv.idx) == 0L) return(MLIST)
if(!is.null(MLIST$gamma)) {
nexo <- ncol(MLIST$gamma)
} else {
nexo <- length(ov.x.dummy.ov.idx)
}
nvar <- nrow(MLIST$lambda)
nfac <- ncol(MLIST$lambda) - length(lv.idx)
# copy
MLISTX <- MLIST
# fix LAMBDA:
# - remove all ov.x related columns/rows
MLISTX$lambda <- MLIST$lambda[-ov.idx, -lv.idx,drop=FALSE]
# fix THETA:
# - remove ov.x related columns/rows
MLISTX$theta <- MLIST$theta[-ov.idx, -ov.idx, drop=FALSE]
# fix PSI:
# - remove ov.x related columns/rows
MLISTX$psi <- MLIST$psi[-lv.idx, -lv.idx, drop=FALSE]
# create GAMMA
if(length(ov.x.dummy.lv.idx) > 0L) {
MLISTX$gamma <- MLIST$beta[-lv.idx, lv.idx, drop=FALSE]
}
# fix BETA (remove if empty)
if(!is.null(MLIST$beta)) {
MLISTX$beta <- MLIST$beta[-lv.idx, -lv.idx, drop=FALSE]
if(ncol(MLISTX$beta) == 0L) MLISTX$beta <- NULL
}
# fix NU
if(!is.null(MLIST$nu)) {
MLISTX$nu <- MLIST$nu[-ov.idx, 1L, drop=FALSE]
}
# fix ALPHA
if(!is.null(MLIST$alpha)) {
MLISTX$alpha <- MLIST$alpha[-lv.idx, 1L, drop=FALSE]
}
MLISTX
}
# create MLIST from MLISTX
MLISTX2MLIST <- function(MLISTX=NULL,
ov.x.dummy.ov.idx = NULL,
ov.x.dummy.lv.idx = NULL,
mean.x=NULL,
cov.x=NULL) {
lv.idx <- ov.x.dummy.lv.idx; ndum <- length(lv.idx)
ov.idx <- ov.x.dummy.ov.idx
if(length(lv.idx) == 0L) return(MLISTX)
stopifnot(!is.null(cov.x), !is.null(mean.x))
nvar <- nrow(MLISTX$lambda); nfac <- ncol(MLISTX$lambda)
# copy
MLIST <- MLISTX
# resize matrices
MLIST$lambda <- rbind(cbind(MLISTX$lambda, matrix(0, nvar, ndum)),
matrix(0, ndum, nfac+ndum))
MLIST$psi <- rbind(cbind(MLISTX$psi, matrix(0, nfac, ndum)),
matrix(0, ndum, nfac+ndum))
MLIST$theta <- rbind(cbind(MLISTX$theta, matrix(0, nvar, ndum)),
matrix(0, ndum, nvar+ndum))
if(!is.null(MLISTX$beta)) {
MLIST$beta <- rbind(cbind(MLISTX$beta, matrix(0, nfac, ndum)),
matrix(0, ndum, nfac+ndum))
}
if(!is.null(MLISTX$alpha)) {
MLIST$alpha <- rbind(MLISTX$alpha, matrix(0, ndum, 1))
}
if(!is.null(MLISTX$nu)) {
MLIST$nu <- rbind(MLISTX$nu, matrix(0, ndum, 1))
}
# fix LAMBDA:
# - add columns for all dummy latent variables
MLIST$lambda[ cbind(ov.idx, lv.idx) ] <- 1
# fix PSI
# - move cov.x elements to PSI
MLIST$psi[lv.idx, lv.idx] <- cov.x
# move (ov.x.dummy elements of) GAMMA to BETA
MLIST$beta[seq_len(nfac), ov.x.dummy.lv.idx] <- MLISTX$gamma
MLIST$gamma <- NULL
# fix ALPHA
if(!is.null(MLIST$alpha)) {
MLIST$alpha[lv.idx] <- mean.x
}
MLIST
}
# if DELTA parameterization, compute residual elements (in theta, or psi)
# of observed categorical variables, as a function of other model parameters
setResidualElements.LISREL <- function(MLIST=NULL,
num.idx=NULL,
ov.y.dummy.ov.idx=NULL,
ov.y.dummy.lv.idx=NULL) {
# remove num.idx from ov.y.dummy.*
if(length(num.idx) > 0L && length(ov.y.dummy.ov.idx) > 0L) {
n.idx <- which(ov.y.dummy.ov.idx %in% num.idx)
if(length(n.idx) > 0L) {
ov.y.dummy.ov.idx <- ov.y.dummy.ov.idx[-n.idx]
ov.y.dummy.lv.idx <- ov.y.dummy.lv.idx[-n.idx]
}
}
# force non-numeric theta elements to be zero
if(length(num.idx) > 0L) {
diag(MLIST$theta)[-num.idx] <- 0.0
} else {
diag(MLIST$theta) <- 0.0
}
if(length(ov.y.dummy.ov.idx) > 0L) {
MLIST$psi[ cbind(ov.y.dummy.lv.idx, ov.y.dummy.lv.idx) ] <- 0.0
}
# special case: PSI=0, and lambda=I (eg ex3.12)
if(ncol(MLIST$psi) > 0L &&
sum(diag(MLIST$psi)) == 0.0 && all(diag(MLIST$lambda) == 1)) {
### FIXME: more elegant/general solution??
diag(MLIST$psi) <- 1
Sigma.hat <- computeSigmaHat.LISREL(MLIST = MLIST, delta=FALSE)
diag.Sigma <- diag(Sigma.hat) - 1.0
} else if(ncol(MLIST$psi) == 0L) {
diag.Sigma <- rep(0, ncol(MLIST$theta))
} else {
Sigma.hat <- computeSigmaHat.LISREL(MLIST = MLIST, delta=FALSE)
diag.Sigma <- diag(Sigma.hat)
}
if(is.null(MLIST$delta)) {
delta <- rep(1, length(diag.Sigma))
} else {
delta <- MLIST$delta
}
# theta = DELTA^(-1/2) - diag( LAMBDA (I-B)^-1 PSI (I-B)^-T t(LAMBDA) )
RESIDUAL <- as.vector(1/(delta*delta) - diag.Sigma)
if(length(num.idx) > 0L) {
diag(MLIST$theta)[-num.idx] <- RESIDUAL[-num.idx]
} else {
diag(MLIST$theta) <- RESIDUAL
}
# move ov.y.dummy 'RESIDUAL' elements from THETA to PSI
if(length(ov.y.dummy.ov.idx) > 0L) {
MLIST$psi[cbind(ov.y.dummy.lv.idx, ov.y.dummy.lv.idx)] <-
MLIST$theta[cbind(ov.y.dummy.ov.idx, ov.y.dummy.ov.idx)]
MLIST$theta[cbind(ov.y.dummy.ov.idx, ov.y.dummy.ov.idx)] <- 0.0
}
MLIST
}
# if THETA parameterization, compute delta elements
# of observed categorical variables, as a function of other model parameters
setDeltaElements.LISREL <- function(MLIST=NULL, num.idx=NULL) {
Sigma.hat <- computeSigmaHat.LISREL(MLIST = MLIST, delta=FALSE)
diag.Sigma <- diag(Sigma.hat)
# (1/delta^2) = diag( LAMBDA (I-B)^-1 PSI (I-B)^-T t(LAMBDA) ) + THETA
#tmp <- diag.Sigma + THETA
tmp <- diag.Sigma
tmp[tmp < 0] <- as.numeric(NA)
MLIST$delta[, 1L] <- sqrt(1/tmp)
# numeric delta's stay 1.0
if(length(num.idx) > 0L) {
MLIST$delta[num.idx] <- 1.0
}
MLIST
}
# compute Sigma/ETA: variances/covariances of BOTH observed and latent variables
computeCOV.LISREL <- function(MLIST = NULL, delta = TRUE) {
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA)
PSI <- MLIST$psi; nlat <- nrow(PSI)
THETA <- MLIST$theta
BETA <- MLIST$beta
# 'extend' matrices
LAMBDA2 <- rbind(LAMBDA, diag(nlat))
THETA2 <- lav_matrix_bdiag(THETA, matrix(0,nlat,nlat))
# beta?
if(is.null(BETA)) {
LAMBDA..IB.inv <- LAMBDA2
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
LAMBDA..IB.inv <- LAMBDA2 %*% IB.inv
}
# compute augment COV matrix
COV <- tcrossprod(LAMBDA..IB.inv %*% PSI, LAMBDA..IB.inv) + THETA2
# if delta, scale
if(delta && !is.null(MLIST$delta)) {
DELTA <- diag(MLIST$delta[,1L], nrow=nvar, ncol=nvar)
COV[seq_len(nvar),seq_len(nvar)] <-
DELTA %*% COV[seq_len(nvar),seq_len(nvar)] %*% DELTA
}
# if GAMMA, also x part
GAMMA <- MLIST$gamma
if(!is.null(GAMMA)) {
COV.X <- MLIST$cov.x
if(is.null(BETA)) {
SX <- tcrossprod(GAMMA %*% COV.X, GAMMA)
} else {
IB.inv..GAMMA <- IB.inv %*% GAMMA
SX <- tcrossprod(IB.inv..GAMMA %*% COV.X, IB.inv..GAMMA)
}
COV[(nvar+1):(nvar+nlat),(nvar+1):(nvar+nlat)] <-
COV[(nvar+1):(nvar+nlat),(nvar+1):(nvar+nlat)] + SX
}
COV
}
# derivative of the objective function
derivative.F.LISREL <- function(MLIST=NULL, Omega=NULL, Omega.mu=NULL) {
LAMBDA <- MLIST$lambda
PSI <- MLIST$psi
BETA <- MLIST$beta
ALPHA <- MLIST$alpha
# beta?
if(is.null(BETA)) {
LAMBDA..IB.inv <- LAMBDA
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
LAMBDA..IB.inv <- LAMBDA %*% IB.inv
}
# meanstructure?
meanstructure <- FALSE; if(!is.null(Omega.mu)) meanstructure <- TRUE
# group weight?
group.w.free <- FALSE; if(!is.null(MLIST$gw)) group.w.free <- TRUE
# pre-compute some values
tLAMBDA..IB.inv <- t(LAMBDA..IB.inv)
if(!is.null(BETA)) {
Omega..LAMBDA..IB.inv..PSI..tIB.inv <-
( Omega %*% LAMBDA..IB.inv %*% PSI %*% t(IB.inv) )
} else {
Omega..LAMBDA <- Omega %*% LAMBDA
}
# 1. LAMBDA
if(!is.null(BETA)) {
if(meanstructure) {
LAMBDA.deriv <- -1.0 * ( Omega.mu %*% t(ALPHA) %*% t(IB.inv) +
Omega..LAMBDA..IB.inv..PSI..tIB.inv )
} else {
LAMBDA.deriv <- -1.0 * Omega..LAMBDA..IB.inv..PSI..tIB.inv
}
} else {
# no BETA
if(meanstructure) {
LAMBDA.deriv <- -1.0 * ( Omega.mu %*% t(ALPHA) +
Omega..LAMBDA %*% PSI )
} else {
LAMBDA.deriv <- -1.0 * (Omega..LAMBDA %*% PSI)
}
}
# 2. BETA
if(!is.null(BETA)) {
if(meanstructure) {
BETA.deriv <- -1.0*(( t(IB.inv) %*%
(t(LAMBDA) %*% Omega.mu %*% t(ALPHA)) %*%
t(IB.inv)) +
(tLAMBDA..IB.inv %*%
Omega..LAMBDA..IB.inv..PSI..tIB.inv))
} else {
BETA.deriv <- -1.0 * ( tLAMBDA..IB.inv %*%
Omega..LAMBDA..IB.inv..PSI..tIB.inv )
}
} else {
BETA.deriv <- NULL
}
# 3. PSI
PSI.deriv <- -1.0 * ( tLAMBDA..IB.inv %*% Omega %*% LAMBDA..IB.inv )
diag(PSI.deriv) <- 0.5 * diag(PSI.deriv)
# 4. THETA
THETA.deriv <- -1.0 * Omega
diag(THETA.deriv) <- 0.5 * diag(THETA.deriv)
if(meanstructure) {
# 5. NU
NU.deriv <- -1.0 * Omega.mu
# 6. ALPHA
ALPHA.deriv <- -1.0 * t( t(Omega.mu) %*% LAMBDA..IB.inv )
} else {
NU.deriv <- NULL
ALPHA.deriv <- NULL
}
if(group.w.free) {
GROUP.W.deriv <- 0.0
} else {
GROUP.W.deriv <- NULL
}
list(lambda = LAMBDA.deriv,
beta = BETA.deriv,
theta = THETA.deriv,
psi = PSI.deriv,
nu = NU.deriv,
alpha = ALPHA.deriv,
gw = GROUP.W.deriv)
}
# dSigma/dx -- per model matrix
# note:
# we avoid using the duplication and elimination matrices
# for now (perhaps until we'll use the Matrix package)
derivative.sigma.LISREL_OLD <- function(m="lambda",
# all model matrix elements, or only a few?
# NOTE: for symmetric matrices,
# we assume that the have full size
# (nvar*nvar) (but already correct for
# symmetry)
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL,
delta = TRUE) {
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA); nfac <- ncol(LAMBDA)
PSI <- MLIST$psi
# only lower.tri part of sigma (not same order as elimination matrix?)
v.idx <- lav_matrix_vech_idx( nvar ); pstar <- nvar*(nvar+1)/2
# shortcut for gamma, nu, alpha and tau: empty matrix
if(m == "nu" || m == "alpha" || m == "tau" || m == "gamma" || m == "gw" ||
m == "cov.x" || m == "mean.x") {
return( matrix(0.0, nrow=pstar, ncol=length(idx)) )
}
# Delta?
delta.flag <- FALSE
if(delta && !is.null(MLIST$delta)) {
DELTA <- MLIST$delta
delta.flag <- TRUE
} else if(m == "delta") { # modindices?
return( matrix(0.0, nrow=pstar, ncol=length(idx)) )
}
# beta?
if(!is.null(MLIST$ibeta.inv)) {
IB.inv <- MLIST$ibeta.inv
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
}
# pre
if(m == "lambda" || m == "beta")
IK <- diag(nvar*nvar) + lav_matrix_commutation(nvar, nvar)
if(m == "lambda" || m == "beta") {
IB.inv..PSI..tIB.inv..tLAMBDA <-
IB.inv %*% PSI %*% t(IB.inv) %*% t(LAMBDA)
}
if(m == "beta" || m == "psi") {
LAMBDA..IB.inv <- LAMBDA %*% IB.inv
}
# here we go:
if(m == "lambda") {
DX <- IK %*% t(IB.inv..PSI..tIB.inv..tLAMBDA %x% diag(nvar))
if(delta.flag)
DX <- DX * as.vector(DELTA %x% DELTA)
} else if(m == "beta") {
DX <- IK %*% ( t(IB.inv..PSI..tIB.inv..tLAMBDA) %x% LAMBDA..IB.inv )
# this is not really needed (because we select idx=m.el.idx)
# but just in case we need all elements of beta...
DX[,lav_matrix_diag_idx(nfac)] <- 0.0
if(delta.flag)
DX <- DX * as.vector(DELTA %x% DELTA)
} else if(m == "psi") {
DX <- (LAMBDA..IB.inv %x% LAMBDA..IB.inv)
# symmetry correction, but keeping all duplicated elements
# since we depend on idx=m.el.idx
# otherwise, we could simply postmultiply with the duplicationMatrix
# we sum up lower.tri + upper.tri (but not the diagonal elements!)
#imatrix <- matrix(1:nfac^2,nfac,nfac)
#lower.idx <- imatrix[lower.tri(imatrix, diag=FALSE)]
#upper.idx <- imatrix[upper.tri(imatrix, diag=FALSE)]
lower.idx <- lav_matrix_vech_idx(nfac, diagonal = FALSE)
upper.idx <- lav_matrix_vechru_idx(nfac, diagonal = FALSE)
# NOTE YR: upper.idx (see 3 lines up) is wrong in MH patch!
# fixed again 13/06/2012 after bug report of Mijke Rhemtulla.
offdiagSum <- DX[,lower.idx] + DX[,upper.idx]
DX[,c(lower.idx, upper.idx)] <- cbind(offdiagSum, offdiagSum)
if(delta.flag)
DX <- DX * as.vector(DELTA %x% DELTA)
} else if(m == "theta") {
DX <- diag(nvar*nvar) # very sparse...
# symmetry correction not needed, since all off-diagonal elements
# are zero?
if(delta.flag)
DX <- DX * as.vector(DELTA %x% DELTA)
} else if(m == "delta") {
Omega <- computeSigmaHat.LISREL(MLIST, delta=FALSE)
DD <- diag(DELTA[,1], nvar, nvar)
DD.Omega <- (DD %*% Omega)
A <- DD.Omega %x% diag(nvar); B <- diag(nvar) %x% DD.Omega
DX <- A[,lav_matrix_diag_idx(nvar),drop=FALSE] +
B[,lav_matrix_diag_idx(nvar),drop=FALSE]
} else {
stop("wrong model matrix names: ", m, "\n")
}
DX <- DX[v.idx, idx, drop=FALSE]
DX
}
# dSigma/dx -- per model matrix
derivative.sigma.LISREL <- function(m = "lambda",
# all model matrix elements, or only a few?
# NOTE: for symmetric matrices,
# we assume that the have full size
# (nvar*nvar) (but already correct for
# symmetry)
idx = seq_len(length(MLIST[[m]])),
MLIST = NULL,
vech = TRUE,
delta = TRUE) {
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA); nfac <- ncol(LAMBDA)
PSI <- MLIST$psi
# only lower.tri part of sigma (not same order as elimination matrix?)
v.idx <- lav_matrix_vech_idx( nvar ); pstar <- nvar*(nvar+1)/2
# shortcut for gamma, nu, alpha, tau,.... : empty matrix
if(m == "nu" || m == "alpha" || m == "tau" || m == "gamma" ||
m == "gw" || m == "cov.x" || m == "mean.x") {
return( matrix(0.0, nrow=pstar, ncol=length(idx)) )
}
# Delta?
delta.flag <- FALSE
if(delta && !is.null(MLIST$delta)) {
DELTA <- MLIST$delta
delta.flag <- TRUE
} else if(m == "delta") { # modindices?
return( matrix(0.0, nrow=pstar, ncol=length(idx)) )
}
# beta?
if(!is.null(MLIST$ibeta.inv)) {
IB.inv <- MLIST$ibeta.inv
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
}
# pre
#if(m == "lambda" || m == "beta")
# IK <- diag(nvar*nvar) + lav_matrix_commutation(nvar, nvar)
if(m == "lambda" || m == "beta") {
L1 <- LAMBDA %*% IB.inv %*% PSI %*% t(IB.inv)
}
if(m == "beta" || m == "psi") {
LAMBDA..IB.inv <- LAMBDA %*% IB.inv
}
# here we go:
if(m == "lambda") {
KOL.idx <- matrix(1:(nvar*nfac), nvar, nfac, byrow = TRUE)[idx]
DX <- (L1 %x% diag(nvar))[,idx, drop = FALSE] +
(diag(nvar) %x% L1)[,KOL.idx, drop = FALSE]
} else if(m == "beta") {
KOL.idx <- matrix(1:(nfac*nfac), nfac, nfac, byrow = TRUE)[idx]
DX <- (L1 %x% LAMBDA..IB.inv)[,idx, drop = FALSE] +
(LAMBDA..IB.inv %x% L1)[, KOL.idx, drop = FALSE]
# this is not really needed (because we select idx=m.el.idx)
# but just in case we need all elements of beta...
DX[, which(idx %in% lav_matrix_diag_idx(nfac))] <- 0.0
} else if(m == "psi") {
DX <- (LAMBDA..IB.inv %x% LAMBDA..IB.inv)
# symmetry correction, but keeping all duplicated elements
# since we depend on idx=m.el.idx
lower.idx <- lav_matrix_vech_idx(nfac, diagonal = FALSE)
upper.idx <- lav_matrix_vechru_idx(nfac, diagonal = FALSE)
offdiagSum <- DX[,lower.idx] + DX[,upper.idx]
DX[,c(lower.idx, upper.idx)] <- cbind(offdiagSum, offdiagSum)
DX <- DX[,idx, drop = FALSE]
} else if(m == "theta") {
#DX <- diag(nvar*nvar) # very sparse...
DX <- matrix(0, nvar*nvar, length(idx))
DX[cbind(idx,seq_along(idx))] <- 1
# symmetry correction not needed, since all off-diagonal elements
# are zero?
} else if(m == "delta") {
Omega <- computeSigmaHat.LISREL(MLIST, delta=FALSE)
DD <- diag(DELTA[,1], nvar, nvar)
DD.Omega <- (DD %*% Omega)
A <- DD.Omega %x% diag(nvar); B <- diag(nvar) %x% DD.Omega
DX <- A[,lav_matrix_diag_idx(nvar),drop=FALSE] +
B[,lav_matrix_diag_idx(nvar),drop=FALSE]
DX <- DX[,idx, drop = FALSE]
} else {
stop("wrong model matrix names: ", m, "\n")
}
if(delta.flag && !m == "delta") {
DX <- DX * as.vector(DELTA %x% DELTA)
}
# vech?
if(vech) {
DX <- DX[v.idx,, drop=FALSE]
}
DX
}
# dMu/dx -- per model matrix
derivative.mu.LISREL <- function(m="alpha",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA); nfac <- ncol(LAMBDA)
# shortcut for empty matrices
if(m == "gamma" || m == "psi" || m == "theta" || m == "tau" ||
m == "delta"|| m == "gw" || m == "cov.x" || m == "mean.x") {
return( matrix(0.0, nrow=nvar, ncol=length(idx) ) )
}
# missing alpha
if(is.null(MLIST$alpha))
ALPHA <- matrix(0, nfac, 1L)
else
ALPHA <- MLIST$alpha
# beta?
if(!is.null(MLIST$ibeta.inv)) {
IB.inv <- MLIST$ibeta.inv
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
}
if(m == "nu") {
DX <- diag(nvar)
} else if(m == "lambda") {
DX <- t(IB.inv %*% ALPHA) %x% diag(nvar)
} else if(m == "beta") {
DX <- t(IB.inv %*% ALPHA) %x% (LAMBDA %*% IB.inv)
# this is not really needed (because we select idx=m.el.idx)
DX[,lav_matrix_diag_idx(nfac)] <- 0.0
} else if(m == "alpha") {
DX <- LAMBDA %*% IB.inv
} else {
stop("wrong model matrix names: ", m, "\n")
}
DX <- DX[, idx, drop=FALSE]
DX
}
# dTh/dx -- per model matrix
derivative.th.LISREL <- function(m="tau",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
th.idx=NULL,
MLIST=NULL,
delta = TRUE) {
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA); nfac <- ncol(LAMBDA)
TAU <- MLIST$tau; nth <- nrow(TAU)
# missing alpha
if(is.null(MLIST$alpha)) {
ALPHA <- matrix(0, nfac, 1L)
} else {
ALPHA <- MLIST$alpha
}
# missing nu
if(is.null(MLIST$nu)) {
NU <- matrix(0, nvar, 1L)
} else {
NU <- MLIST$nu
}
# Delta?
delta.flag <- FALSE
if(delta && !is.null(MLIST$delta)) {
DELTA <- MLIST$delta
delta.flag <- TRUE
}
if(is.null(th.idx)) {
th.idx <- seq_len(nth)
nlev <- rep(1L, nvar)
K_nu <- diag(nvar)
} else {
nlev <- tabulate(th.idx, nbins=nvar); nlev[nlev == 0L] <- 1L
K_nu <- matrix(0, sum(nlev), nvar)
K_nu[ cbind(seq_len(sum(nlev)), rep(seq_len(nvar), times=nlev)) ] <- 1.0
}
# shortcut for empty matrices
if(m == "gamma" || m == "psi" || m == "theta" || m == "gw" ||
m == "cov.x" || m == "mean.x") {
return( matrix(0.0, nrow=length(th.idx), ncol=length(idx) ) )
}
# beta?
if(!is.null(MLIST$ibeta.inv)) {
IB.inv <- MLIST$ibeta.inv
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
}
if(m == "tau") {
DX <- matrix(0, nrow=length(th.idx), ncol=nth)
DX[ th.idx > 0L, ] <- diag(nth)
if(delta.flag)
DX <- DX * as.vector(K_nu %*% DELTA)
} else if(m == "nu") {
DX <- (-1) * K_nu
if(delta.flag)
DX <- DX * as.vector(K_nu %*% DELTA)
} else if(m == "lambda") {
DX <- (-1) * t(IB.inv %*% ALPHA) %x% diag(nvar)
DX <- K_nu %*% DX
if(delta.flag)
DX <- DX * as.vector(K_nu %*% DELTA)
} else if(m == "beta") {
DX <- (-1) * t(IB.inv %*% ALPHA) %x% (LAMBDA %*% IB.inv)
# this is not really needed (because we select idx=m.el.idx)
DX[,lav_matrix_diag_idx(nfac)] <- 0.0
DX <- K_nu %*% DX
if(delta.flag)
DX <- DX * as.vector(K_nu %*% DELTA)
} else if(m == "alpha") {
DX <- (-1) * LAMBDA %*% IB.inv
DX <- K_nu %*% DX
if(delta.flag)
DX <- DX * as.vector(K_nu %*% DELTA)
} else if(m == "delta") {
DX1 <- matrix(0, nrow=length(th.idx), ncol=1)
DX1[ th.idx > 0L, ] <- TAU
DX2 <- NU + LAMBDA %*% IB.inv %*% ALPHA
DX2 <- K_nu %*% DX2
DX <- K_nu * as.vector(DX1 - DX2)
} else {
stop("wrong model matrix names: ", m, "\n")
}
DX <- DX[, idx, drop=FALSE]
DX
}
# dPi/dx -- per model matrix
derivative.pi.LISREL <- function(m="lambda",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
LAMBDA <- MLIST$lambda; nvar <- nrow(LAMBDA); nfac <- ncol(LAMBDA)
GAMMA <- MLIST$gamma; nexo <- ncol(GAMMA)
# Delta?
delta.flag <- FALSE
if(!is.null(MLIST$delta)) {
DELTA.diag <- MLIST$delta[,1L]
delta.flag <- TRUE
}
# shortcut for empty matrices
if(m == "tau" || m == "nu" || m == "alpha" || m == "psi" ||
m == "theta" || m == "gw" || m == "cov.x" || m == "mean.x") {
return( matrix(0.0, nrow=nvar*nexo, ncol=length(idx) ) )
}
# beta?
if(!is.null(MLIST$ibeta.inv)) {
IB.inv <- MLIST$ibeta.inv
} else {
IB.inv <- .internal_get_IB.inv(MLIST = MLIST)
}
if(m == "lambda") {
DX <- t(IB.inv %*% GAMMA) %x% diag(nvar)
if(delta.flag)
DX <- DX * DELTA.diag
} else if(m == "beta") {
DX <- t(IB.inv %*% GAMMA) %x% (LAMBDA %*% IB.inv)
# this is not really needed (because we select idx=m.el.idx)
DX[,lav_matrix_diag_idx(nfac)] <- 0.0
if(delta.flag)
DX <- DX * DELTA.diag
} else if(m == "gamma") {
DX <- diag(nexo) %x% (LAMBDA %*% IB.inv)
if(delta.flag)
DX <- DX * DELTA.diag
} else if(m == "delta") {
PRE <- rep(1, nexo) %x% diag(nvar)
DX <- PRE * as.vector(LAMBDA %*% IB.inv %*% GAMMA)
} else {
stop("wrong model matrix names: ", m, "\n")
}
DX <- DX[, idx, drop=FALSE]
DX
}
# dGW/dx -- per model matrix
derivative.gw.LISREL <- function(m="gw",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
# shortcut for empty matrices
if(m != "gw") {
return( matrix(0.0, nrow=1L, ncol=length(idx) ) )
} else {
# m == "gw"
DX <- matrix(1.0, 1, 1)
}
DX <- DX[, idx, drop=FALSE]
DX
}
# dlambda/dx -- per model matrix
derivative.lambda.LISREL <- function(m="lambda",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
LAMBDA <- MLIST$lambda
# shortcut for empty matrices
if(m != "lambda") {
return( matrix(0.0, nrow=length(LAMBDA), ncol=length(idx) ) )
} else {
# m == "lambda"
DX <- diag(1, nrow=length(LAMBDA), ncol=length(LAMBDA))
}
DX <- DX[, idx, drop=FALSE]
DX
}
# dpsi/dx -- per model matrix - FIXME!!!!!
derivative.psi.LISREL <- function(m="psi",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
PSI <- MLIST$psi; nfac <- nrow(PSI)
v.idx <- lav_matrix_vech_idx( nfac )
# shortcut for empty matrices
if(m != "psi") {
DX <- matrix(0.0, nrow=length(PSI), ncol=length(idx))
return(DX[v.idx,,drop=FALSE])
} else {
# m == "psi"
DX <- diag(1, nrow=length(PSI), ncol=length(PSI))
}
DX <- DX[v.idx, idx, drop=FALSE]
DX
}
# dtheta/dx -- per model matrix
derivative.theta.LISREL <- function(m="theta",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
THETA <- MLIST$theta; nvar <- nrow(THETA)
v.idx <- lav_matrix_vech_idx(nvar)
# shortcut for empty matrices
if(m != "theta") {
DX <- matrix(0.0, nrow=length(THETA), ncol=length(idx))
return(DX[v.idx,,drop=FALSE])
} else {
# m == "theta"
DX <- diag(1, nrow=length(THETA), ncol=length(THETA))
}
DX <- DX[v.idx, idx, drop=FALSE]
DX
}
# dbeta/dx -- per model matrix
derivative.beta.LISREL <- function(m="beta",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
BETA <- MLIST$beta
# shortcut for empty matrices
if(m != "beta") {
return( matrix(0.0, nrow=length(BETA), ncol=length(idx)) )
} else {
# m == "beta"
DX <- diag(1, nrow=length(BETA), ncol=length(BETA))
}
DX <- DX[, idx, drop=FALSE]
DX
}
# dgamma/dx -- per model matrix
derivative.gamma.LISREL <- function(m="gamma",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
GAMMA <- MLIST$gamma
# shortcut for empty matrices
if(m != "gamma") {
return( matrix(0.0, nrow=length(GAMMA), ncol=length(idx)) )
} else {
# m == "gamma"
DX <- diag(1, nrow=length(GAMMA), ncol=length(GAMMA))
}
DX <- DX[, idx, drop=FALSE]
DX
}
# dnu/dx -- per model matrix
derivative.nu.LISREL <- function(m="nu",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
NU <- MLIST$nu
# shortcut for empty matrices
if(m != "nu") {
return( matrix(0.0, nrow=length(NU), ncol=length(idx)) )
} else {
# m == "nu"
DX <- diag(1, nrow=length(NU), ncol=length(NU))
}
DX <- DX[, idx, drop=FALSE]
DX
}
# dtau/dx -- per model matrix
derivative.tau.LISREL <- function(m="tau",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
TAU <- MLIST$tau
# shortcut for empty matrices
if(m != "tau") {
return( matrix(0.0, nrow=length(TAU), ncol=length(idx)) )
} else {
# m == "tau"
DX <- diag(1, nrow=length(TAU), ncol=length(TAU))
}
DX <- DX[, idx, drop=FALSE]
DX
}
# dalpha/dx -- per model matrix
derivative.alpha.LISREL <- function(m="alpha",
# all model matrix elements, or only a few?
idx=seq_len(length(MLIST[[m]])),
MLIST=NULL) {
ALPHA <- MLIST$alpha
# shortcut for empty matrices
if(m != "alpha") {
return( matrix(0.0, nrow=length(ALPHA), ncol=length(idx)) )
} else {
# m == "alpha"
DX <- diag(1, nrow=length(ALPHA), ncol=length(ALPHA))
}
DX <- DX[, idx, drop=FALSE]
DX
}
# MLIST = NULL; meanstructure=TRUE; th=TRUE; delta=TRUE; pi=TRUE; gw=FALSE
# lav_matrix_vech_idx <- psindex:::lav_matrix_vech_idx; lav_matrix_vechru_idx <- psindex:::lav_matrix_vechru_idx
# vec <- psindex:::vec; lav_func_jacobian_complex <- psindex:::lav_func_jacobian_complex
# computeSigmaHat.LISREL <- psindex:::computeSigmaHat.LISREL
# setDeltaElements.LISREL <- psindex:::setDeltaElements.LISREL
TESTING_derivatives.LISREL <- function(MLIST = NULL,
nvar = NULL, nfac = NULL, nexo = NULL,
th.idx = NULL, num.idx = NULL,
meanstructure = TRUE,
th = TRUE, delta = TRUE, pi = TRUE,
gw = FALSE, theta = FALSE,
debug = FALSE) {
if(is.null(MLIST)) {
# create artificial matrices, compare 'numerical' vs 'analytical'
# derivatives
#nvar <- 12; nfac <- 3; nexo <- 4 # this combination is special?
if(is.null(nvar)) {
nvar <- 20
}
if(is.null(nfac)) {
nfac <- 6
}
if(is.null(nexo)) {
nexo <- 5
}
if(is.null(num.idx)) {
num.idx <- sort(sample(seq_len(nvar), ceiling(nvar/2)))
}
if(is.null(th.idx)) {
th.idx <- integer(0L)
for(i in seq_len(nvar)) {
if(i %in% num.idx) {
th.idx <- c(th.idx, 0)
} else {
th.idx <- c(th.idx, rep(i, sample(c(1,1,2,6), 1L)))
}
}
}
nth <- sum(th.idx > 0L)
MLIST <- list()
MLIST$lambda <- matrix(0,nvar,nfac)
MLIST$beta <- matrix(0,nfac,nfac)
MLIST$theta <- matrix(0,nvar,nvar)
MLIST$psi <- matrix(0,nfac,nfac)
if(meanstructure) {
MLIST$alpha <- matrix(0,nfac,1L)
MLIST$nu <- matrix(0,nvar,1L)
}
if(th) MLIST$tau <- matrix(0,nth,1L)
if(delta) MLIST$delta <- matrix(0,nvar,1L)
MLIST$gamma <- matrix(0,nfac,nexo)
if(gw) MLIST$gw <- matrix(0, 1L, 1L)
# feed random numbers
MLIST <- lapply(MLIST, function(x) {x[,] <- rnorm(length(x)); x})
# fix
diag(MLIST$beta) <- 0.0
diag(MLIST$theta) <- diag(MLIST$theta)*diag(MLIST$theta) * 10
diag(MLIST$psi) <- diag(MLIST$psi)*diag(MLIST$psi) * 10
MLIST$psi[ lav_matrix_vechru_idx(nfac) ] <-
MLIST$psi[ lav_matrix_vech_idx(nfac) ]
MLIST$theta[ lav_matrix_vechru_idx(nvar) ] <-
MLIST$theta[ lav_matrix_vech_idx(nvar) ]
if(delta) MLIST$delta[,] <- abs(MLIST$delta)*10
} else {
nvar <- nrow(MLIST$lambda)
}
compute.sigma <- function(x, mm="lambda", MLIST=NULL) {
mlist <- MLIST
if(mm %in% c("psi", "theta")) {
mlist[[mm]] <- lav_matrix_vech_reverse(x)
} else {
mlist[[mm]][,] <- x
}
if(theta) {
mlist <- setDeltaElements.LISREL(MLIST = mlist, num.idx = num.idx)
}
lav_matrix_vech(computeSigmaHat.LISREL(mlist))
}
compute.mu <- function(x, mm="lambda", MLIST=NULL) {
mlist <- MLIST
if(mm %in% c("psi", "theta")) {
mlist[[mm]] <- lav_matrix_vech_reverse(x)
} else {
mlist[[mm]][,] <- x
}
if(theta) {
mlist <- setDeltaElements.LISREL(MLIST = mlist, num.idx = num.idx)
}
computeMuHat.LISREL(mlist)
}
compute.th2 <- function(x, mm="tau", MLIST=NULL, th.idx) {
mlist <- MLIST
if(mm %in% c("psi", "theta")) {
mlist[[mm]] <- lav_matrix_vech_reverse(x)
} else {
mlist[[mm]][,] <- x
}
if(theta) {
mlist <- setDeltaElements.LISREL(MLIST = mlist, num.idx = num.idx)
}
computeTH.LISREL(mlist, th.idx=th.idx)
}
compute.pi <- function(x, mm="lambda", MLIST=NULL) {
mlist <- MLIST
if(mm %in% c("psi", "theta")) {
mlist[[mm]] <- lav_matrix_vech_reverse(x)
} else {
mlist[[mm]][,] <- x
}
if(theta) {
mlist <- setDeltaElements.LISREL(MLIST = mlist, num.idx = num.idx)
}
computePI.LISREL(mlist)
}
compute.gw <- function(x, mm="gw", MLIST=NULL) {
mlist <- MLIST
if(mm %in% c("psi", "theta")) {
mlist[[mm]] <- lav_matrix_vech_reverse(x)
} else {
mlist[[mm]][,] <- x
}
if(theta) {
mlist <- setDeltaElements.LISREL(MLIST = mlist, num.idx = num.idx)
}
mlist$gw[1,1]
}
# if theta, set MLIST$delta
if(theta) {
MLIST <- setDeltaElements.LISREL(MLIST = MLIST, num.idx = num.idx)
}
for(mm in names(MLIST)) {
if(mm %in% c("psi", "theta")) {
x <- lav_matrix_vech(MLIST[[mm]])
} else {
x <- lav_matrix_vec(MLIST[[mm]])
}
if(mm == "delta" && theta) next
if(debug) {
cat("### mm = ", mm, "\n")
}
# 1. sigma
DX1 <- lav_func_jacobian_complex(func=compute.sigma, x=x, mm=mm, MLIST=MLIST)
DX2 <- derivative.sigma.LISREL(m=mm, idx=seq_len(length(MLIST[[mm]])),
MLIST=MLIST, delta = !theta)
if(mm %in% c("psi","theta")) {
# remove duplicated columns of symmetric matrices
idx <- lav_matrix_vechru_idx(sqrt(ncol(DX2)), diagonal=FALSE)
if(length(idx) > 0L) DX2 <- DX2[,-idx]
}
if(theta) {
sigma.hat <- computeSigmaHat.LISREL(MLIST=MLIST, delta=FALSE)
R <- lav_deriv_cov2cor(sigma.hat, num.idx = num.idx)
DX3 <- DX2
DX2 <- R %*% DX2
}
if(debug) {
cat("[SIGMA] mm = ", sprintf("%-8s:", mm), "DX1 (numerical):\n");
print(zapsmall(DX1)); cat("\n")
cat("[SIGMA] mm = ", sprintf("%-8s:", mm), "DX2 (analytical):\n");
print(DX2); cat("\n")
if(theta) {
cat("[SIGMA] mm = ", sprintf("%-8s:", mm), "DX3 (analytical):\n");
print(DX3); cat("\n")
}
}
cat("[SIGMA] mm = ", sprintf("%-8s:", mm), "sum delta = ",
sprintf("%12.9f", sum(DX1-DX2)), " max delta = ",
sprintf("%12.9f", max(DX1-DX2)), "\n")
# 2. mu
DX1 <- lav_func_jacobian_complex(func=compute.mu, x=x, mm=mm, MLIST=MLIST)
DX2 <- derivative.mu.LISREL(m=mm, idx=seq_len(length(MLIST[[mm]])),
MLIST=MLIST)
if(mm %in% c("psi","theta")) {
# remove duplicated columns of symmetric matrices
idx <- lav_matrix_vechru_idx(sqrt(ncol(DX2)), diagonal = FALSE)
if(length(idx) > 0L) DX2 <- DX2[,-idx]
}
cat("[MU ] mm = ", sprintf("%-8s:", mm), "sum delta = ",
sprintf("%12.9f", sum(DX1-DX2)), " max delta = ",
sprintf("%12.9f", max(DX1-DX2)), "\n")
if(debug) {
cat("[MU ] mm = ", sprintf("%-8s:", mm), "DX1 (numerical):\n");
print(zapsmall(DX1)); cat("\n")
cat("[MU ] mm = ", sprintf("%-8s:", mm), "DX2 (analytical):\n");
print(DX2); cat("\n")
}
# 3. th
if(th) {
DX1 <- lav_func_jacobian_complex(func=compute.th2, x=x, mm=mm, MLIST=MLIST,
th.idx=th.idx)
DX2 <- derivative.th.LISREL(m=mm, idx=seq_len(length(MLIST[[mm]])),
MLIST=MLIST, th.idx=th.idx,
delta=TRUE)
if(theta) {
# 1. compute dDelta.dx
dxSigma <-
derivative.sigma.LISREL(m=mm, idx=seq_len(length(MLIST[[mm]])),
MLIST=MLIST, delta = !theta)
var.idx <- which(!lav_matrix_vech_idx(nvar) %in%
lav_matrix_vech_idx(nvar, diagonal = FALSE))
sigma.hat <- computeSigmaHat.LISREL(MLIST=MLIST, delta=FALSE)
dsigma <- diag(sigma.hat)
# dy/ddsigma = -0.5/(ddsigma*sqrt(ddsigma))
dDelta.dx <- dxSigma[var.idx,] * -0.5 / (dsigma*sqrt(dsigma))
# 2. compute dth.dDelta
dth.dDelta <-
derivative.th.LISREL(m="delta",
idx=seq_len(length(MLIST[["delta"]])),
MLIST=MLIST, th.idx=th.idx)
# 3. add dth.dDelta %*% dDelta.dx
no.num.idx <- which(th.idx > 0)
DX2[no.num.idx,] <- DX2[no.num.idx,,drop=FALSE] +
(dth.dDelta %*% dDelta.dx)[no.num.idx,,drop=FALSE]
#DX2 <- DX2 + dth.dDelta %*% dDelta.dx
}
if(mm %in% c("psi","theta")) {
# remove duplicated columns of symmetric matrices
idx <- lav_matrix_vechru_idx(sqrt(ncol(DX2)), diagonal = FALSE)
if(length(idx) > 0L) DX2 <- DX2[,-idx]
}
cat("[TH ] mm = ", sprintf("%-8s:", mm), "sum delta = ",
sprintf("%12.9f", sum(DX1-DX2)), " max delta = ",
sprintf("%12.9f", max(DX1-DX2)), "\n")
if(debug) {
cat("[TH ] mm = ",sprintf("%-8s:", mm),"DX1 (numerical):\n")
print(zapsmall(DX1)); cat("\n")
cat("[TH ] mm = ",sprintf("%-8s:", mm),"DX2 (analytical):\n")
print(DX2); cat("\n")
}
}
# 4. pi
if(pi) {
DX1 <- lav_func_jacobian_complex(func=compute.pi, x=x, mm=mm, MLIST=MLIST)
DX2 <- derivative.pi.LISREL(m=mm, idx=seq_len(length(MLIST[[mm]])),
MLIST=MLIST)
if(mm %in% c("psi","theta")) {
# remove duplicated columns of symmetric matrices
idx <- lav_matrix_vechru_idx(sqrt(ncol(DX2)), diagonal = FALSE)
if(length(idx) > 0L) DX2 <- DX2[,-idx]
}
if(theta) {
# 1. compute dDelta.dx
dxSigma <-
derivative.sigma.LISREL(m=mm, idx=seq_len(length(MLIST[[mm]])),
MLIST=MLIST, delta = !theta)
if(mm %in% c("psi","theta")) {
# remove duplicated columns of symmetric matrices
idx <- lav_matrix_vechru_idx(sqrt(ncol(dxSigma)), diagonal = FALSE)
if(length(idx) > 0L) dxSigma <- dxSigma[,-idx]
}
var.idx <- which(!lav_matrix_vech_idx(nvar) %in%
lav_matrix_vech_idx(nvar, diagonal = FALSE))
sigma.hat <- computeSigmaHat.LISREL(MLIST=MLIST, delta=FALSE)
dsigma <- diag(sigma.hat)
# dy/ddsigma = -0.5/(ddsigma*sqrt(ddsigma))
dDelta.dx <- dxSigma[var.idx,] * -0.5 / (dsigma*sqrt(dsigma))
# 2. compute dpi.dDelta
dpi.dDelta <-
derivative.pi.LISREL(m="delta",
idx=seq_len(length(MLIST[["delta"]])),
MLIST=MLIST)
# 3. add dpi.dDelta %*% dDelta.dx
no.num.idx <- which(! seq.int(1L, nvar) %in% num.idx )
no.num.idx <- rep(seq.int(0,nexo-1) * nvar,
each=length(no.num.idx)) + no.num.idx
DX2[no.num.idx,] <- DX2[no.num.idx,,drop=FALSE] +
(dpi.dDelta %*% dDelta.dx)[no.num.idx,,drop=FALSE]
}
cat("[PI ] mm = ", sprintf("%-8s:", mm), "sum delta = ",
sprintf("%12.9f", sum(DX1-DX2)), " max delta = ",
sprintf("%12.9f", max(DX1-DX2)), "\n")
if(debug) {
cat("[PI ] mm = ",sprintf("%-8s:", mm),"DX1 (numerical):\n")
print(zapsmall(DX1)); cat("\n")
cat("[PI ] mm = ",sprintf("%-8s:", mm),"DX2 (analytical):\n")
print(DX2); cat("\n")
}
}
# 5. gw
if(gw) {
DX1 <- lav_func_jacobian_complex(func=compute.gw, x=x, mm=mm, MLIST=MLIST)
DX2 <- derivative.gw.LISREL(m=mm, idx=seq_len(length(MLIST[[mm]])),
MLIST=MLIST)
if(mm %in% c("psi","theta")) {
# remove duplicated columns of symmetric matrices
idx <- lav_matrix_vechru_idx(sqrt(ncol(DX2)), diagonal = FALSE)
if(length(idx) > 0L) DX2 <- DX2[,-idx]
}
cat("[GW ] mm = ", sprintf("%-8s:", mm), "sum delta = ",
sprintf("%12.9f", sum(DX1-DX2)), " max delta = ",
sprintf("%12.9f", max(DX1-DX2)), "\n")
if(debug) {
cat("[GW ] mm = ",sprintf("%-8s:", mm),"DX1 (numerical):\n")
print(DX1); cat("\n\n")
cat("[GW ] mm = ",sprintf("%-8s:", mm),"DX2 (analytical):\n")
print(DX2); cat("\n\n")
}
}
}
MLIST$th.idx <- th.idx
MLIST$num.idx <- num.idx
MLIST
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.