# All code below is written by Myrsini Katsikatsou (Feb 2015)
#The following function refers to PLRT for nested models and equality constraints.
# Namely, it is developed to test either of the following hypotheses:
# a) H0 states that some parameters are equal to 0
# b) H0 states that some parameters are equal to some others.
#Note that for the latter I haven't checked if it is ok when equality constraints
#are imposed on parameters that refer to different groups in a multi-group
#analysis. All the code below has been developed for a single-group analysis.
# Let fit_objH0 and fit_objH1 be the outputs of psindex() function when we fit
# a model under the null hypothesis and under the alternative, respectively.
# The argument equalConstr is logical (T/F) and it is TRUE if equality constraints
# are imposed on subsets of the parameters.
# The main idea of the code below is that we consider the parameter vector
# under the alternative H1 evaluated at the values derived under H0 and for these
# values we should evaluate the Hessian, the variability matrix (denoted by J)
# and Godambe matrix.
ctr_pml_plrt_nested <- function(fit_objH0, fit_objH1) {
# sanity check, perhaps we misordered H0 and H1 in the function call??
if(fit_objH1@test[[1]]$df > fit_objH0@test[[1]]$df) {
tmp <- fit_objH0
fit_objH0 <- fit_objH1
fit_objH1 <- tmp
}
# check if we have equality constraints
if(fit_objH0@Model@eq.constraints) {
equalConstr = TRUE
} else {
equalConstr = FALSE
}
nsize <- fit_objH0@SampleStats@ntotal
PLRT <- 2 * (fit_objH1@optim$logl - fit_objH0@optim$logl)
# create a new object 'objH1_h0': the object 'H1', but where
# the parameter values are from H0
objH1_h0 <- lav_test_diff_m10(m1 = fit_objH1, m0 = fit_objH0, test = FALSE)
# EqMat
#EqMat <- lav_test_diff_A(m1 = fit_objH1, m0 = fit_objH0)
EqMat <- fit_objH0@Model@ceq.JAC
# DEBUG YR -- eliminate the constraints also present in H1
# -- if we do this, there is no need to use MASS::ginv later
#JAC0 <- fit_objH0@Model@ceq.JAC
#JAC1 <- fit_objH1@Model@ceq.JAC
#unique.idx <- which(apply(JAC0, 1, function(x) {
# !any(apply(JAC1, 1, function(y) { all(x == y) })) }))
#if(length(unique.idx) > 0L) {
# EqMat <- EqMat[unique.idx,,drop = FALSE]
#}
# Observed information (= for PML, this is Hessian / N)
Hes.theta0 <- lavTech(objH1_h0, "information.observed")
# handle possible constraints in H1 (and therefore also in objH1_h0)
Inv.Hes.theta0 <-
lav_model_information_augment_invert(lavmodel = objH1_h0@Model,
information = Hes.theta0,
inverted = TRUE)
# the estimated variability matrix is given (=unit information first order)
J.theta0 <- lavTech(objH1_h0, "first.order")
# the Inverse of the G matrix
Inv.G <- Inv.Hes.theta0 %*% J.theta0 %*% Inv.Hes.theta0
MInvGtM <- EqMat %*% Inv.G %*% t(EqMat)
MinvHtM <- EqMat %*% Inv.Hes.theta0 %*% t(EqMat)
#Inv_MinvHtM <- solve(MinvHtM)
Inv_MinvHtM <- MASS::ginv(MinvHtM)
tmp.prod <- MInvGtM %*% Inv_MinvHtM
tmp.prod2 <- tmp.prod %*% tmp.prod
sum.eig <- sum(diag(tmp.prod))
sum.eigsq <- sum(diag(tmp.prod2))
FSMA.PLRT <- (sum.eig/sum.eigsq) * PLRT
adj.df <- (sum.eig*sum.eig)/sum.eigsq
pvalue <- 1 - pchisq(FSMA.PLRT, df = adj.df)
list(FSMA.PLRT = FSMA.PLRT, adj.df = adj.df, pvalue = pvalue)
}
# for testing: this is the 'original' (using m.el.idx and x.el.idx)
ctr_pml_plrt_nested2 <- function (fit_objH0, fit_objH1) {
if (fit_objH1@test[[1]]$df > fit_objH0@test[[1]]$df) {
tmp <- fit_objH0
fit_objH0 <- fit_objH1
fit_objH1 <- tmp
}
if (fit_objH0@Model@eq.constraints) {
equalConstr = TRUE
} else {
equalConstr = FALSE
}
nsize <- fit_objH0@SampleStats@ntotal
PLRT <- 2 * nsize * (fit_objH0@optim$fx - fit_objH1@optim$fx)
Npar <- fit_objH1@optim$npar
MY.m.el.idx2 <- fit_objH1@Model@m.free.idx
MY.x.el.idx2 <- fit_objH1@Model@x.free.idx
MY.m.el.idx <- MY.m.el.idx2
MY.x.el.idx <- MY.x.el.idx2
#MY.m.el.idx2 <- fit_objH1@Model@m.free.idx
# MY.m.el.idx2 gives the POSITION index of the free parameters within each
# parameter matrix under H1 model.
# The index numbering restarts from 1 when we move to a new parameter matrix.
# Within each matrix the index numbering "moves" columnwise.
#MY.x.el.idx2 <- fit_objH1@Model@x.free.idx
# MY.x.el.idx2 ENUMERATES the free parameters within each parameter matrix.
# The numbering continues as we move from one parameter matrix to the next one.
# In the case of the symmetric matrices, Theta and Psi,in some functions below
# we need to give as input MY.m.el.idx2 and MY.x.el.idx2 after
# we have eliminated the information about the redundant parameters
# (those placed above the main diagonal).
# That's why I do the following:
#MY.m.el.idx <- MY.m.el.idx2
#MY.x.el.idx <- MY.x.el.idx2
# Psi, the variance - covariance matrix of factors
#if( length(MY.x.el.idx2[[3]])!=0 & any(table(MY.x.el.idx2[[3]])>1)) {
# nfac <- ncol(fit_objH1@Model@GLIST$lambda) #number of factors
# tmp <- matrix(c(1:(nfac^2)), nrow= nfac, ncol= nfac )
# tmp_keep <- tmp[lower.tri(tmp, diag=TRUE)]
# MY.m.el.idx[[3]] <- MY.m.el.idx[[3]][MY.m.el.idx[[3]] %in% tmp_keep]
# MY.x.el.idx[[3]] <- unique( MY.x.el.idx2[[3]] )
#}
#for Theta, the variance-covariance matrix of measurement errors
# if( length(MY.x.el.idx2[[2]])!=0 & any(table(MY.x.el.idx2[[2]])>1)) {
# nvar <- fit_objH1@Model@nvar #number of indicators
# tmp <- matrix(c(1:(nvar^2)), nrow= nvar, ncol= nvar )
# tmp_keep <- tmp[lower.tri(tmp, diag=TRUE)]
# MY.m.el.idx[[2]] <- MY.m.el.idx[[2]][MY.m.el.idx[[2]] %in% tmp_keep]
# MY.x.el.idx[[2]] <- unique( MY.x.el.idx2[[2]] )
# }
#below the commands to find the row-column indices of the Hessian that correspond to
#the parameters to be tested equal to 0
#tmp.ind contains these indices
# MY.m.el.idx2.H0 <- fit_objH0@Model@m.free.idx
# tmp.ind <- c()
# for(i in 1:6) {
# tmp.ind <- c(tmp.ind ,
# MY.x.el.idx2[[i]] [!(MY.m.el.idx2[[i]] %in%
# MY.m.el.idx2.H0[[i]] ) ] )
# }
# next line added by YR
# tmp.ind <- unique(tmp.ind)
# YR: use partable to find which parameters are restricted in H0
# (this should work in multiple groups too)
#h0.par.idx <- which( PT.H1.extended$free[PT.H1.extended$user < 2] > 0 &
# !(PT.H0.extended$free[PT.H0.extended$user < 2] > 0) )
#tmp.ind <- PT.H1.extended$free[ h0.par.idx ]
#print(tmp.ind)
if (length(MY.x.el.idx2[[3]]) != 0 & any(table(MY.x.el.idx2[[3]]) > 1)) {
nfac <- ncol(fit_objH1@Model@GLIST$lambda)
tmp <- matrix(c(1:(nfac*nfac)), nrow = nfac, ncol = nfac)
tmp_keep <- tmp[lower.tri(tmp, diag = TRUE)]
MY.m.el.idx[[3]] <- MY.m.el.idx[[3]][MY.m.el.idx[[3]] %in% tmp_keep]
MY.x.el.idx[[3]] <- unique(MY.x.el.idx2[[3]])
}
if (length(MY.x.el.idx2[[2]]) != 0 & any(table(MY.x.el.idx2[[2]]) > 1)) {
nvar <- fit_objH1@Model@nvar
tmp <- matrix(c(1:(nvar*nvar)), nrow = nvar, ncol = nvar)
tmp_keep <- tmp[lower.tri(tmp, diag = TRUE)]
MY.m.el.idx[[2]] <- MY.m.el.idx[[2]][MY.m.el.idx[[2]] %in% tmp_keep]
MY.x.el.idx[[2]] <- unique(MY.x.el.idx2[[2]])
}
MY.m.el.idx2.H0 <- fit_objH0@Model@m.free.idx
tmp.ind <- c()
for (i in 1:6) {
tmp.ind <- c(tmp.ind, MY.x.el.idx2[[i]][!(MY.m.el.idx2[[i]] %in%
MY.m.el.idx2.H0[[i]])])
}
tmp.ind <- unique(tmp.ind)
# if the models are nested because of equality constraints among the parameters, we need
# to construct the matrix of derivatives of function g(theta) with respect to theta
# where g(theta) is the function that represents the equality constraints. g(theta) is
# an rx1 vector where r are the equality constraints. In the null hypothesis
# we test H0: g(theta)=0. The matrix of derivatives is of dimension:
# nrows= number of free non-redundant parameters under H0, namely
# NparH0 <- fit_objH0[[1]]@optim$npar , and ncols= number of free non-redundant
# parameters under H1, namely NparH1 <- fit_objH0[[1]]@optim$npar.
# The matrix of derivatives of g(theta) is composed of 0's, 1's, -1's, and
# in the rows that refer to odd number of parameters that are equal there is one -2.
# The 1's, -1's (and possibly -2) are the contrast coefficients of the parameters.
# The sum of the rows should be equal to 0.
#if(equalConstr==TRUE) {
# EqMat <- fit_objH0@Model@ceq.JAC
#} else {
# no.par0 <- length(tmp.ind)
# tmp.ind2 <- cbind(1:no.par0, tmp.ind)
# EqMat <- matrix(0, nrow = no.par0, ncol = Npar)
# EqMat[tmp.ind2] <- 1
# }
if (equalConstr == TRUE) {
EqMat <- fit_objH0@Model@ceq.JAC
} else {
no.par0 <- length(tmp.ind)
tmp.ind2 <- cbind(1:no.par0, tmp.ind )
EqMat <- matrix(0, nrow=no.par0, ncol=Npar)
EqMat[tmp.ind2] <- 1
}
obj <- fit_objH0
# Compute the sum of the eigenvalues and the sum of the squared eigenvalues
# so that the adjustment to PLRT can be applied.
# Here a couple of functions (e.g. MYgetHessian) which are modifications of
# psindex functions (e.g. getHessian) are needed. These are defined in the end of the file.
#the quantity below follows the same logic as getHessian of psindex 0.5-18
#and it actually gives N*Hessian. That's why the command following the command below.
# NHes.theta0 <- MYgetHessian (object = obj@Model,
# samplestats = obj@SampleStats ,
# X = obj@Data@X ,
# estimator = "PML",
# lavcache = obj@Cache,
# MY.m.el.idx = MY.m.el.idx,
# MY.x.el.idx = MY.x.el.idx,
# MY.m.el.idx2 = MY.m.el.idx2, # input for MYx2GLIST
# MY.x.el.idx2 = MY.x.el.idx2, # input for MYx2GLIST
# Npar = Npar,
# equalConstr=equalConstr)
NHes.theta0 <- MYgetHessian(object = obj@Model, samplestats = obj@SampleStats,
X = obj@Data@X, estimator = "PML", lavcache = obj@Cache,
MY.m.el.idx = MY.m.el.idx, MY.x.el.idx = MY.x.el.idx,
MY.m.el.idx2 = MY.m.el.idx2, MY.x.el.idx2 = MY.x.el.idx2,
Npar = Npar, equalConstr = equalConstr)
Hes.theta0 <- NHes.theta0/nsize
#Inv.Hes.theta0 <- solve(Hes.theta0)
Inv.Hes.theta0 <- MASS::ginv(Hes.theta0)
NJ.theta0 <- MYgetVariability(object = obj, MY.m.el.idx = MY.m.el.idx,
MY.x.el.idx = MY.x.el.idx, equalConstr = equalConstr)
J.theta0 <- NJ.theta0/(nsize*nsize)
Inv.G <- Inv.Hes.theta0 %*% J.theta0 %*% Inv.Hes.theta0
MInvGtM <- EqMat %*% Inv.G %*% t(EqMat)
MinvHtM <- EqMat %*% Inv.Hes.theta0 %*% t(EqMat)
#Inv_MinvHtM <- solve(MinvHtM) #!!! change names
Inv_MinvHtM <- MASS::ginv(MinvHtM)
tmp.prod <- MInvGtM %*% Inv_MinvHtM #!!! change names
tmp.prod2 <- tmp.prod %*% tmp.prod
sum.eig <- sum(diag(tmp.prod))
sum.eigsq <- sum(diag(tmp.prod2))
FSMA.PLRT <- (sum.eig/sum.eigsq) * PLRT
adj.df <- (sum.eig*sum.eig)/sum.eigsq
pvalue <- 1 - pchisq(FSMA.PLRT, df = adj.df)
list(FSMA.PLRT = FSMA.PLRT, adj.df = adj.df, pvalue = pvalue)
}
###################################################################################
# auxiliary functions used above, they are all copy from the corresponding functions
# of psindex where parts no needed were deleted and some parts were modified.
# I mark the modifications with comments.
# library(psindex)
# To run an example for the functions below the following input is needed.
# obj <- fit.objH0[[i]]
# object <- obj@Model
# samplestats = obj@SampleStats
# X = obj@Data@X
# estimator = "PML"
# lavcache = obj@Cache
# MY.m.el.idx = MY.m.el.idx
# MY.x.el.idx = MY.x.el.idx
# MY.m.el.idx2 = MY.m.el.idx2 # input for MYx2GLIST
# MY.x.el.idx2 = MY.x.el.idx2 # input for MYx2GLIST
# Npar = Npar
# equalConstr =TRUE
MYgetHessian <- function (object, samplestats , X ,
estimator = "PML", lavcache,
MY.m.el.idx, MY.x.el.idx,
MY.m.el.idx2, MY.x.el.idx2, # input for MYx2GLIST
Npar, #Npar is the number of parameters under H1
equalConstr ) { # takes TRUE/ FALSE
if(equalConstr){ #!!! added line
}
Hessian <- matrix(0, Npar, Npar) #
#!!!! MYfunction below
x <- MYgetModelParameters(object=object,
GLIST = NULL, N=Npar, #N the number of parameters to consider
MY.m.el.idx=MY.m.el.idx,
MY.x.el.idx= MY.x.el.idx)
for (j in 1:Npar) {
h.j <- 1e-05
x.left <- x.left2 <- x.right <- x.right2 <- x
x.left[j] <- x[j] - h.j
x.left2[j] <- x[j] - 2 * h.j
x.right[j] <- x[j] + h.j
x.right2[j] <- x[j] + 2 * h.j
#!!!! MYfunction below : MYcomputeGradient and MYx2GLIST
g.left <- MYcomputeGradient(object=object,
GLIST = MYx2GLIST(object=object, x = x.left,
MY.m.el.idx=MY.m.el.idx2,
MY.x.el.idx= MY.x.el.idx2),
samplestats = samplestats, X = X,
lavcache = lavcache, estimator = "PML",
MY.m.el.idx=MY.m.el.idx,
MY.x.el.idx= MY.x.el.idx,
equalConstr = equalConstr )
g.left2 <- MYcomputeGradient(object=object,
GLIST = MYx2GLIST(object=object, x = x.left2,
MY.m.el.idx=MY.m.el.idx2,
MY.x.el.idx= MY.x.el.idx2),
samplestats = samplestats, X = X,
lavcache = lavcache, estimator = "PML",
MY.m.el.idx=MY.m.el.idx,
MY.x.el.idx= MY.x.el.idx,
equalConstr = equalConstr )
g.right <- MYcomputeGradient(object=object,
GLIST = MYx2GLIST(object=object, x = x.right,
MY.m.el.idx=MY.m.el.idx2,
MY.x.el.idx= MY.x.el.idx2),
samplestats = samplestats, X = X,
lavcache = lavcache, estimator = "PML",
MY.m.el.idx=MY.m.el.idx,
MY.x.el.idx= MY.x.el.idx,
equalConstr = equalConstr )
g.right2 <- MYcomputeGradient(object=object,
GLIST = MYx2GLIST(object=object, x = x.right2,
MY.m.el.idx=MY.m.el.idx2,
MY.x.el.idx= MY.x.el.idx2),
samplestats = samplestats, X = X,
lavcache = lavcache, estimator = "PML",
MY.m.el.idx=MY.m.el.idx,
MY.x.el.idx= MY.x.el.idx,
equalConstr = equalConstr )
Hessian[, j] <- (g.left2 - 8 * g.left + 8 * g.right - g.right2)/(12 * h.j)
}
Hessian <- (Hessian + t(Hessian))/2
#(-1) * Hessian
Hessian
}
#############################################################################
################################## MYgetModelParameters
#different input arguments: MY.m.el.idx, MY.x.el.idx
MYgetModelParameters <- function (object, GLIST = NULL, N, #N the number of parameters to consider
MY.m.el.idx, MY.x.el.idx) {
if (is.null(GLIST)) {
GLIST <- object@GLIST
}
x <- numeric(N)
for (mm in 1:length(object@GLIST)) { # mm<-1
m.idx <- MY.m.el.idx[[mm]] #!!!!! different here and below
x.idx <- MY.x.el.idx[[mm]]
x[x.idx] <- GLIST[[mm]][m.idx]
}
x
}
#############################################################################
############################# MYcomputeGradient
#the difference are the input arguments MY.m.el.idx, MY.x.el.idx
#used in psindex:::computeDelta
MYcomputeGradient <- function (object, GLIST, samplestats = NULL, X = NULL,
lavcache = NULL, estimator = "PML",
MY.m.el.idx, MY.x.el.idx, equalConstr ) {
if(equalConstr){ #added line
}
num.idx <- object@num.idx
th.idx <- object@th.idx
if (is.null(GLIST)) {
GLIST <- object@GLIST
}
Sigma.hat <- computeSigmaHat(object, GLIST = GLIST, extra = (estimator == "ML"))
Mu.hat <- computeMuHat(object, GLIST = GLIST)
TH <- computeTH(object, GLIST = GLIST)
g<-1
d1 <- pml_deriv1(Sigma.hat = Sigma.hat[[g]], Mu.hat = Mu.hat[[g]],
TH = TH[[g]], th.idx = th.idx[[g]], num.idx = num.idx[[g]],
X = X[[g]], lavcache = lavcache[[g]])
#!? if(equalConstr) { #delete the following three commented lines, wrong
# Delta <- psindex:::computeDelta (lavmodel= object, GLIST. = GLIST)
# } else {
Delta <- computeDelta (lavmodel= object, GLIST. = GLIST,
m.el.idx. = MY.m.el.idx ,
x.el.idx. = MY.x.el.idx)
# }
#!!!!! that was before: as.numeric(t(d1) %*% Delta[[g]])/samplestats@nobs[[g]]
as.numeric(t(d1) %*% Delta[[g]]) #!!! modified to follow current computeGradient() function of psindex
#!!! which gives minus the gradient of PL-loglik
}
###############################################################################
################################## MYx2GLIST
#difference in input arguments MY.m.el.idx, MY.x.el.idx
MYx2GLIST <- function (object, x = NULL, MY.m.el.idx, MY.x.el.idx) {
GLIST <- object@GLIST
for (mm in 1:length(GLIST)) {
m.el.idx <- MY.m.el.idx[[mm]]
x.el.idx <- MY.x.el.idx[[mm]]
GLIST[[mm]][m.el.idx] <- x[x.el.idx]
}
GLIST
}
############################################################################
#####MYgetVariability function
#difference from corresponding of psindex: I use MYNvcov.first.order
MYgetVariability <- function (object, MY.m.el.idx, MY.x.el.idx, equalConstr ) {
NACOV <- MYNvcov.first.order(lavmodel=object@Model,
lavsamplestats = object@SampleStats,
lavdata = object@Data,
estimator = "PML",
MY.m.el.idx=MY.m.el.idx,
MY.x.el.idx= MY.x.el.idx,
equalConstr = equalConstr)
if(equalConstr){ #added lines
}
B0 <- attr(NACOV, "B0")
#!!!! Note below that I don't multiply with nsize
#!!! so what I get is J matrix divided by n
#if (object@Options$estimator == "PML") {
# B0 <- B0 * object@SampleStats@ntotal
#}
#!!!!!!!!!!!!!!!!!!! added the following lines so that the output of
#!!!!! MYgetVariability is in line with that of psindex 0.5-18 getVariability
#!! what's the purpose of the following lines?
if (object@Options$estimator == "PML") {
B0 <- B0 * object@SampleStats@ntotal
}
B0
}
##############################################################################
# example
# obj <- fit.objH0[[i]]
# object <- obj@Model
# samplestats = obj@SampleStats
# X = obj@Data@X
# estimator = "PML"
# lavcache = obj@Cache
# MY.m.el.idx = MY.m.el.idx
# MY.x.el.idx = MY.x.el.idx
# MY.m.el.idx2 = MY.m.el.idx2 # input for MYx2GLIST
# MY.x.el.idx2 = MY.x.el.idx2 # input for MYx2GLIST
# Npar = Npar
# equalConstr =TRUE
MYNvcov.first.order <- function (lavmodel, lavsamplestats = NULL,
lavdata = NULL, lavcache = NULL,
estimator = "PML",
MY.m.el.idx, MY.x.el.idx,
equalConstr ) { #equalConstr takes TRUE/FALSE
if(equalConstr){ #added lines
}
B0.group <- vector("list", lavsamplestats@ngroups) #in my case list of length 1
#!? if (equalConstr) { ###the following three lines are commented because they are wrong
# Delta <- psindex:::computeDelta(lavmodel, GLIST. = NULL)
# } else {
Delta <- computeDelta(lavmodel,
GLIST. = NULL,
m.el.idx. = MY.m.el.idx,#!!!!! different here and below
x.el.idx. = MY.x.el.idx)
# }
Sigma.hat <- computeSigmaHat(lavmodel)
Mu.hat <- computeMuHat(lavmodel)
TH <- computeTH(lavmodel)
g <-1
SC <- pml_deriv1(Sigma.hat = Sigma.hat[[g]], TH = TH[[g]],
Mu.hat = Mu.hat[[g]], th.idx = lavmodel@th.idx[[g]],
num.idx = lavmodel@num.idx[[g]],
X = lavdata@X[[g]], lavcache = lavcache,
scores = TRUE, negative = FALSE)
group.SC <- SC %*% Delta[[g]]
B0.group[[g]] <- lav_matrix_crossprod(group.SC)
#!!!! B0.group[[g]] <- B0.group[[g]]/lavsamplestats@ntotal !!! skip so that the result
# is in line with the 0.5-18 version of psindex
B0 <- B0.group[[1]]
E <- B0
eigvals <- eigen(E, symmetric = TRUE, only.values = TRUE)$values
if (any(eigvals < -1 * .Machine$double.eps^(3/4))) {
warning("psindex WARNING: matrix based on first order outer product of the derivatives is not positive definite; the standard errors may not be thrustworthy")
}
NVarCov <- MASS::ginv(E)
attr(NVarCov, "B0") <- B0
attr(NVarCov, "B0.group") <- B0.group
NVarCov
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.