-
Notifications
You must be signed in to change notification settings - Fork 37
Open
Labels
Function ideaidea for a new semTools functionidea for a new semTools function
Description
semTools currently includes function compareFit for comparing fit indices between multiple models. Users may also be interested in comparing coefficients across different models. I created a function for that. Please let me know whether you think that the following would be a useful addition to the package. Parts of the code have been copied from the compareFit function and the print function of the lavaan summary method.
require(plyr)
compareEstimates <- function (...,
ci = FALSE,
fmi = FALSE,
standardized = FALSE,
rsquare = FALSE,
std.nox = FALSE,
nd = 3L)
{
arg <- match.call()
mods <- list(...)
if (any(sapply(mods, is, "list"))) {
temp <- list()
for (i in seq_along(mods)) {
if (!is(mods[[i]], "list")) {
temp <- c(temp, list(mods[[i]]))
}
else {
temp <- c(temp, mods[[i]])
}
}
mods <- temp
}
if (any(!sapply(mods, is, "lavaan")))
stop("Some models specified here are not lavaan outputs or list of lavaan outputs")
PElist <- lapply(mods,function(object){
PE <- parameterEstimates(object, ci = ci, standardized = standardized,
rsquare = rsquare, fmi = fmi,
remove.eq = FALSE, remove.system.eq = TRUE,
remove.ineq = FALSE, remove.def = FALSE,
add.attributes = TRUE)
if(standardized && std.nox) {
PE$std.all <- PE$std.nox
}
PE
})
PEframe <- join_all(PElist, by = c("lhs","op","rhs","exo"), match = "first")
class(PEframe) <- c("CoefDiff","data.frame")
PEframe
}
#' @param cols which columns to print. Defaults to \code{NULL} for all columns
print.CoefDiff <- function(x, ..., nd = 3L, cols = NULL) {
# format for numeric values
num.format <- paste("%", max(8, nd + 5), ".", nd, "f", sep = "")
char.format <- paste("%", max(8, nd + 5), "s", sep="")
# output sections
GSECTIONS <- c("Latent Variables",
"Composites",
"Regressions",
"Covariances",
"Intercepts",
"Thresholds",
"Variances",
"Scales y*",
"Group Weight",
"R-Square")
ASECTIONS <- c("Defined Parameters",
"Constraints")
cat("\nParameter Estimates:\n\n")
# number of groups
if(is.null(x$group)) {
ngroups <- 1L
x$group <- rep(1L, length(x$lhs))
} else {
ngroups <- lav_partable_ngroups(x)
}
# number of levels
if(is.null(x$level)) {
nlevels <- 1L
x$level <- rep(1L, length(x$lhs))
} else {
nlevels <- lav_partable_nlevels(x)
}
# block column
if(is.null(x$block)) {
x$block <- rep(1L, length(x$lhs))
}
# round to 3 digits after the decimal point
y <- as.data.frame(
lapply(x, function(x) {
if(is.numeric(x)) {
sprintf(num.format, x)
} else {
x
}
}),
stringsAsFactors = FALSE, optional = TRUE)
# always remove /block/level/group/op/rhs/label/exo columns
y$op <- y$group <- y$rhs <- y$label <- y$exo <- NULL
y$block <- y$level <- NULL
# if standardized, remove std.nox column (space reasons only)
y$std.nox <- NULL
# convert to character matrix
m <- as.matrix(format.data.frame(y, na.encode = FALSE,
justify = "right"))
# use empty row names
rownames(m) <- rep("", nrow(m))
# missing and zero parameter values, one model at at time
xStartIndices <- which(colnames(x)=="est")
xEndIndices <- c(xStartIndices[-1]-1, length(colnames(x)))
mStartIndices <- which(colnames(m)=="est")
mEndIndices <- c(mStartIndices[-1]-1, length(colnames(m)))
for(model in 1:length(xStartIndices)){
xblock <- xStartIndices[model]:xEndIndices[model]
mblock <- mStartIndices[model]:mEndIndices[model]
# handle missing estimates
m[is.na(x[,xblock]$est),mblock] <- ""
# handle se == 0.0
if(!is.null(x[,xblock]$se)) {
se.idx <- which(x[,xblock]$se == 0)
if(length(se.idx) > 0L) {
m[,mblock][se.idx, "se"] <- ""
if(!is.null(x[,xblock]$z)) {
m[,mblock][se.idx, "z"] <- ""
}
if(!is.null(x[,xblock]$pvalue)) {
m[,mblock][se.idx, "pvalue"] <- ""
}
}
# handle se == NA
se.idx <- which(is.na(x[,xblock]$se))
if(length(se.idx) > 0L) {
if(!is.null(x[,xblock]$z)) {
m[,mblock][se.idx, "z"] <- ""
}
if(!is.null(x[,xblock]$pvalue)) {
m[,mblock][se.idx, "pvalue"] <- ""
}
}
}
# handle fmi
if(!is.null(x[,xblock]$fmi)) {
se.idx <- which(x[,xblock]$se == 0)
if(length(se.idx) > 0L) {
m[,mblock][se.idx, "fmi"] <- ""
}
not.idx <- which(x$op %in% c(":=", "<", ">", "=="))
if(length(not.idx) > 0L) {
if(!is.null(x[,xblock]$fmi)) {
m[,mblock][not.idx, "fmi"] <- ""
}
}
}
# for blavaan, handle Post.SD and PSRF
if(!is.null(x[,xblock]$Post.SD)) {
se.idx <- which(x[,xblock]$Post.SD == 0)
if(length(se.idx) > 0L) {
m[,mblock][se.idx, "Post.SD"] <- ""
if(!is.null(x[,xblock]$psrf)) {
m[,mblock][se.idx, "psrf"] <- ""
}
if(!is.null(x[,xblock]$PSRF)) {
m[,mblock][se.idx, "PSRF"] <- ""
}
}
# handle psrf for defined parameters
not.idx <- which(x$op %in% c(":=", "<", ">", "=="))
if(length(not.idx) > 0L) {
if(!is.null(x[,xblock]$psrf)) {
m[,mblock][not.idx, "psrf"] <- ""
}
if(!is.null(x[,xblock]$PSRF)) {
m[,mblock][not.idx, "PSRF"] <- ""
}
}
}
}
# Print only subset of columns if the cols argument is not null
if(!is.null(cols)){
m <- m[,colnames(m) %in% c("lhs",cols)]
}
# rename some column names
colnames(m)[ colnames(m) == "lhs" ] <- ""
colnames(m)[ colnames(m) == "op" ] <- ""
colnames(m)[ colnames(m) == "rhs" ] <- ""
colnames(m)[ colnames(m) == "est" ] <- "Estimate"
colnames(m)[ colnames(m) == "se" ] <- "Std.Err"
colnames(m)[ colnames(m) == "z" ] <- "z-value"
colnames(m)[ colnames(m) == "pvalue" ] <- "P(>|z|)"
colnames(m)[ colnames(m) == "std.lv" ] <- "Std.lv"
colnames(m)[ colnames(m) == "std.all"] <- "Std.all"
colnames(m)[ colnames(m) == "std.nox"] <- "Std.nox"
colnames(m)[ colnames(m) == "prior" ] <- "Prior"
colnames(m)[ colnames(m) == "fmi" ] <- "FMI"
# format column names
colnames(m) <- sprintf(char.format, colnames(m))
# exceptions for blavaan: Post.Mean (width = 9), Prior (width = 14)
if(!is.null(x$Post.Mean)) {
tmp <- gsub("[ \t]+", "", colnames(m), perl=TRUE)
# reformat "Post.Mean" column
col.idx <- which(tmp == "Post.Mean")
if(length(col.idx) > 0L) {
tmp.format <- paste("%", max(9, nd + 5), "s", sep="")
colnames(m)[col.idx] <- sprintf(tmp.format, colnames(m)[col.idx])
m[,col.idx] <- sprintf(tmp.format, m[,col.idx])
}
# reformat "Prior" column
col.idx <- which(tmp == "Prior")
if(length(col.idx) > 0L) {
MAX <- max( nchar( m[,col.idx] ) ) + 1L
tmp.format <- paste("%", max(MAX, nd + 5), "s", sep="")
colnames(m)[col.idx] <- sprintf(tmp.format, colnames(m)[col.idx])
m[,col.idx] <- sprintf(tmp.format, m[,col.idx])
}
}
b <- 0L
# group-specific sections
for(g in 1:ngroups) {
# group header
if(ngroups > 1L) {
group.label <- attr(x, "group.label")
cat("\n\n")
cat("Group ", g, " [", group.label[g], "]:\n", sep="")
}
for(l in 1:nlevels) {
# block number
b <- b + 1L
# ov/lv names
ov.names <- lavNames(x, "ov", block = b)
lv.names <- lavNames(x, "lv", block = b)
# level header
if(nlevels > 1L) {
level.label <- attr(x, "level.label")
cat("\n\n")
cat("Level ", l, " [", level.label[l], "]:\n", sep="")
}
# group-specific sections
for(s in GSECTIONS) {
if(s == "Latent Variables") {
row.idx <- which( x$op == "=~" & !x$lhs %in% ov.names &
x$block == b)
if(length(row.idx) == 0L) next
m[row.idx,1] <- lavaan:::.makeNames(x$rhs[row.idx], x$label[row.idx])
} else if(s == "Composites") {
row.idx <- which( x$op == "<~" & x$block == b)
if(length(row.idx) == 0L) next
m[row.idx,1] <- lavaan:::.makeNames(x$rhs[row.idx], x$label[row.idx])
} else if(s == "Regressions") {
row.idx <- which( x$op == "~" & x$block == b)
if(length(row.idx) == 0L) next
m[row.idx,1] <- lavaan:::.makeNames(x$rhs[row.idx], x$label[row.idx])
} else if(s == "Covariances") {
row.idx <- which(x$op == "~~" & x$lhs != x$rhs & !x$exo &
x$block == b)
if(length(row.idx) == 0L) next
# make distinction between residual and plain
y.names <- unique( c(lavNames(x, "eqs.y"),
lavNames(x, "ov.ind")) )
PREFIX <- rep("", length(row.idx))
PREFIX[ x$rhs[row.idx] %in% y.names ] <- " ."
m[row.idx,1] <- lavaan:::.makeNames(x$rhs[row.idx], x$label[row.idx],
PREFIX = PREFIX)
#m[row.idx,1] <- lavaan:::.makeNames(x$rhs[row.idx], x$label[row.idx])
} else if(s == "Intercepts") {
row.idx <- which(x$op == "~1" & !x$exo & x$block == b)
if(length(row.idx) == 0L) next
# make distinction between intercepts and means
y.names <- unique( c(lavNames(x, "eqs.y"),
lavNames(x, "ov.ind")) )
PREFIX <- rep("", length(row.idx))
PREFIX[ x$lhs[row.idx] %in% y.names ] <- " ."
m[row.idx,1] <- lavaan:::.makeNames(x$lhs[row.idx], x$label[row.idx],
PREFIX = PREFIX)
#m[row.idx,1] <- lavaan:::.makeNames(x$lhs[row.idx], x$label[row.idx])
} else if(s == "Thresholds") {
row.idx <- which(x$op == "|" & x$block == b)
if(length(row.idx) == 0L) next
m[row.idx,1] <- lavaan:::.makeNames(paste(x$lhs[row.idx], "|",
x$rhs[row.idx], sep=""), x$label[row.idx])
} else if(s == "Variances") {
row.idx <- which(x$op == "~~" & x$lhs == x$rhs & !x$exo &
x$block == b)
if(length(row.idx) == 0L) next
# make distinction between residual and plain
y.names <- unique( c(lavNames(x, "eqs.y"),
lavNames(x, "ov.ind")) )
PREFIX <- rep("", length(row.idx))
PREFIX[ x$rhs[row.idx] %in% y.names ] <- " ."
m[row.idx,1] <- lavaan:::.makeNames(x$rhs[row.idx], x$label[row.idx],
PREFIX = PREFIX)
} else if(s == "Scales y*") {
row.idx <- which(x$op == "~*~" & x$block == b)
if(length(row.idx) == 0L) next
m[row.idx,1] <- lavaan:::.makeNames(x$rhs[row.idx], x$label[row.idx])
} else if(s == "Group Weight") {
row.idx <- which(x$lhs == "group" & x$op == "%" & x$block == b)
if(length(row.idx) == 0L) next
m[row.idx,1] <- lavaan:::.makeNames(x$rhs[row.idx], x$label[row.idx])
} else if(s == "R-Square") {
row.idx <- which(x$op == "r2" & x$block == b)
if(length(row.idx) == 0L) next
m[row.idx,1] <- lavaan:::.makeNames(x$rhs[row.idx], x$label[row.idx])
} else {
row.idx <- integer(0L)
}
# do we need special formatting for this section?
# three types:
# - regular (nothing to do, except row/colnames)
# - R-square
# - Latent Variables (and Composites), Regressions and Covariances
# 'bundle' the output per lhs element
# bundling
if(s %in% c("Latent Variables", "Composites",
"Regressions", "Covariances")) {
nel <- length(row.idx)
M <- matrix("", nrow = nel*2, ncol = ncol(m))
colnames(M) <- colnames(m)
rownames(M) <- rep("", NROW(M))
#colnames(M)[1] <- sprintf("%-17s", paste(s, ":", sep = ""))
LHS <- paste(x$lhs[row.idx], x$op[row.idx])
lhs.idx <- seq(1, nel*2L, 2L)
rhs.idx <- seq(1, nel*2L, 2L) + 1L
if(s == "Covariances") {
# make distinction between residual and plain
y.names <- unique( c(lavNames(x, "eqs.y"),
lavNames(x, "ov.ind")) )
PREFIX <- rep("", length(row.idx))
PREFIX[ x$lhs[row.idx] %in% y.names ] <- "."
} else {
PREFIX <- rep("", length(LHS))
}
M[lhs.idx, 1] <- sprintf("%1s%-15s", PREFIX, LHS)
M[rhs.idx, ] <- m[row.idx,]
# avoid duplicated LHS labels
if(nel > 1L) {
del.idx <- integer(0)
old.lhs <- ""
for(i in 1:nel) {
if(LHS[i] == old.lhs) {
del.idx <- c(del.idx, lhs.idx[i])
}
old.lhs <- LHS[i]
}
if(length(del.idx) > 0L) {
M <- M[-del.idx,,drop=FALSE]
}
}
cat("\n", s, ":\n", sep = "")
#cat("\n")
print(M, quote = FALSE)
# R-square
} else if(s == "R-Square") {
M <- m[row.idx,1:2,drop=FALSE]
colnames(M) <- colnames(m)[1:2]
rownames(M) <- rep("", NROW(M))
#colnames(M)[1] <- sprintf("%-17s", paste(s, ":", sep = ""))
cat("\n", s, ":\n", sep = "")
#cat("\n")
print(M, quote = FALSE)
# Regular
} else {
#M <- rbind(matrix("", nrow = 1L, ncol = ncol(m)),
# m[row.idx,])
M <- m[row.idx,,drop=FALSE]
colnames(M) <- colnames(m)
rownames(M) <- rep("", NROW(M))
#colnames(M)[1] <- sprintf("%-17s", paste(s, ":", sep = ""))
cat("\n", s, ":\n", sep = "")
#cat("\n")
print(M, quote = FALSE)
}
} # GSECTIONS
} # levels
} # groups
# asections
for(s in ASECTIONS) {
if(s == "Defined Parameters") {
row.idx <- which(x$op == ":=")
m[row.idx,1] <- lavaan:::.makeNames(x$lhs[row.idx], "")
M <- m[row.idx,,drop=FALSE]
colnames(M) <- colnames(m)
} else if(s == "Constraints") {
row.idx <- which(x$op %in% c("==", "<", ">"))
if(length(row.idx) == 0) next
m[row.idx,1] <- .makeConNames(x$lhs[row.idx], x$op[row.idx],
x$rhs[row.idx], nd = nd)
m[row.idx,2] <- sprintf(num.format, abs(x$est[row.idx]))
M <- m[row.idx,1:2,drop=FALSE]
colnames(M) <- c("", sprintf(char.format, "|Slack|"))
} else {
row.idx <- integer(0L)
}
if(length(row.idx) == 0L) {
next
}
rownames(M) <- rep("", NROW(M))
#colnames(M)[1] <- sprintf("%-17s", paste(s, ":", sep = ""))
#cat("\n")
cat("\n", s, ":\n", sep = "")
print(M, quote = FALSE)
}
cat("\n")
invisible(m)
}
Metadata
Metadata
Assignees
Labels
Function ideaidea for a new semTools functionidea for a new semTools function