#
# Copyright (C) 2013-2019 University of Amsterdam
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
LatentGrowthCurveInternal <- function(jaspResults, dataset, options, ...) {
ready <- length(options[["variables"]]) > 2
# Read dataset
dataset <- .lgcmReadData(dataset, options)
# Preprocess options
options <- .lgcmPreprocessOptions(dataset, options)
# Enrich dataset
dataset <- .lgcmEnrichData(dataset, options)
# Error checking
errors <- .lgcmCheckErrors(dataset, options)
# Create model container
modelContainer <- .lgcmModelContainer(jaspResults, options)
# output
.lgcmFitTable( modelContainer, dataset, options, ready )
.lgcmParameterTables( modelContainer, dataset, options, ready )
.lcgmAdditionalFitTables( modelContainer, dataset, options, ready )
.lcgmRSquaredTable( modelContainer, dataset, options, ready )
.lgcmImpliedCovTable( modelContainer, dataset, options, ready )
.lgcmResidualCovTable( modelContainer, dataset, options, ready )
.lgcmCurvePlot( modelContainer, dataset, options, ready )
.lgcmPathPlot( modelContainer, dataset, options, ready )
.lgcmSyntax( modelContainer, dataset, options, ready )
#.lgcmMisfitPlot( modelContainer, dataset, options, ready )
}
# Preprocessing functions ----
.lgcmPreprocessOptions <- function(dataset, options) {
# add dummy names
if (length(options[["categorical"]]) > 0) {
frml <- as.formula(paste("~", paste(options[["categorical"]], collapse = "+")))
dumnames <- colnames(model.matrix(frml, dataset))[-1]
options[["dummy_names"]] <- dumnames
}
return(options)
}
.lgcmReadData <- function(dataset, options) {
if (!is.null(dataset)) return(dataset)
.readDataSetToEnd(columns = c(
options[["variables"]],
options[["regressions"]],
options[["categorical"]],
options[["covariates"]]
))
}
.lgcmEnrichData <- function(dataset, options) {
currentNa <- getOption("na.action")
# Add dummies
if (length(options[["categorical"]]) > 0) {
frml <- as.formula(paste("~", paste(options[["categorical"]], collapse = "+")))
# not error when the categorical variable has NAs:
options(na.action = "na.pass")
mm <- model.matrix(frml, dataset)[,-1, drop = FALSE]
options(na.action = currentNa)
colnames(mm) <- options[["dummy_names"]]
dataset <- cbind(dataset, mm)
}
return(dataset)
}
.lgcmCheckErrors <- function(dataset, options) {
# some error check
return(TRUE)
}
# Results functions ----
.lgcmComputeResults <- function(modelContainer, dataset, options) {
lgcmResult <- try(lavaan::growth(
model = .lgcmOptionsToMod(options),
data = dataset,
se = ifelse(options[["errorCalculationMethod"]] == "bootstrap", "standard", options[["errorCalculationMethod"]]),
mimic = options[["emulation"]],
estimator = options[["estimator"]],
missing = options[["naAction"]]
))
if (inherits(lgcmResult, "try-error")) {
modelContainer$setError(paste(
"Model error:",
.decodeVarsInMessage(names(dataset), attr(lgcmResult, "condition")$message))
)
return()
}
if (!lgcmResult@optim$converged) {
modelContainer$setError(gettext("The model could not be estimated due to nonconvergence."))
return()
}
if (lgcmResult@test[[1]]$df < 0) {
modelContainer$setError(gettext("The model could not be estimated: No degrees of freedom left."))
return()
}
# Bootstrapping with interruptible progress bar
if (options[["errorCalculationMethod"]] == "bootstrap") {
startProgressbar(options[["bootstrapSamples"]])
boot_1 <- lavaan::bootstrapLavaan(lgcmResult, R = 1)
bootres <- matrix(0, options[["bootstrapSamples"]], length(boot_1))
bootres[1,] <- boot_1
for (i in 2:options[["bootstrapSamples"]]) {
bootres[i,] <- lavaan::bootstrapLavaan(lgcmResult, 1)
progressbarTick()
}
lgcmResult@boot <- list(coef = bootres)
lgcmResult@Options[["se"]] <- "bootstrap"
}
# Save cfaResult as state so it's available even when opts don't change
modelContainer[["model"]] <- createJaspState(lgcmResult)
return(lgcmResult)
}
.lgcmOptionsToMod <- function(options, base64 = TRUE) {
if (!base64) .v <- I
timings <- sapply(options[["timings"]], function(t) t$timing)
# Header info
Hed <- paste0(
"# -------------------------------------------\n",
"# Latent Growth Curve model generated by JASP\n",
"# -------------------------------------------\n"
)
# Basic LGCM curve information
Int <- if (options[["intercept"]])
paste("I =~", paste0("1*", options[["variables"]], collapse = " + "))
else NULL
Lin <- if (options[["linear"]])
paste("\nL =~", paste0(timings, "*", options[["variables"]], collapse = " + "))
else NULL
Qua <- if (options[["quadratic"]])
paste("\nQ =~", paste0(timings^2, "*", options[["variables"]], collapse = " + "))
else NULL
Cub <- if (options[["cubic"]])
paste("\nC =~", paste0(timings^3, "*", options[["variables"]], collapse = " + "))
else NULL
LGC <- paste0("\n# Growth curve\n", Int, Lin, Qua, Cub)
curve <- c("I", "L", "Q", "C")[with(options, c(intercept, linear, quadratic, cubic))]
# Covarying latents
if (!options[["covaryingLatentCurve"]]) {
Cov <- "\n\n# Suppress latent covariance"
for (i in seq_along(curve))
for (j in seq_along(curve))
if (i < j) Cov <- paste0(Cov, "\n", curve[i], " ~~ 0*", curve[j])
} else {
Cov <- NULL
}
# Add regressions
Reg <- if (length(options[["regressions"]]) > 0)
paste0("\n\n# Regressions\n", paste(curve, collapse = " + "), " ~ ",
paste(options[["regressions"]], collapse = " + "))
else NULL
# Add dummy variables
Dum <- if (length(options[["dummy_names"]]) > 0)
paste0("\n\n# Dummy-coded categorical predictors\n", paste(curve, collapse = " + "), " ~ ",
paste(options[["dummy_names"]], collapse = " + "))
# Add time-varying covariates
# eww this is hard
# Put everything together
paste0(Hed, LGC, Cov, Reg, Dum)
}
# Output functions ----
.lgcmModelContainer <- function(jaspResults, options) {
if (!is.null(jaspResults[["modelContainer"]])) {
modelContainer <- jaspResults[["modelContainer"]]
} else {
modelContainer <- createJaspContainer()
modelContainer$dependOn(c(
"variables", "regressions", "covariates", "categorical", "timings",
"intercept", "linear", "quadratic", "cubic", "covaryingLatentCurve",
"errorCalculationMethod", "bootstrapSamples"
))
jaspResults[["modelContainer"]] <- modelContainer
}
return(modelContainer)
}
.lgcmFitTable <- function(modelContainer, dataset, options, ready) {
if (!is.null(modelContainer[["maintab"]])) return()
maintab <- createJaspTable(gettext("Chi-square Test"))
maintab$addColumnInfo(name = "mod", title = "Model", type = "string")
maintab$addColumnInfo(name = "chisq", title = "\u03a7\u00b2", type = "number", format = "dp:3")
maintab$addColumnInfo(name = "df", title = "df", type = "integer")
maintab$addColumnInfo(name = "pvalue", title = "p", type = "number", format = "dp:3;p:.001")
modelContainer[["maintab"]] <- createJaspContainer("Model fit")
modelContainer[["maintab"]]$position <- 1
modelContainer[["maintab"]][["chisqtab"]] <- maintab
# add data to the table!
if (!ready) return()
lgcmResult <- .lgcmComputeResults(modelContainer, dataset, options)
if (modelContainer$getError()) return()
fm <- lavaan::fitMeasures(lgcmResult)
maintab[["mod"]] <- c(gettext("Baseline model"), gettext("Growth curve model"))
maintab[["chisq"]] <- fm[c("baseline.chisq", "chisq")]
maintab[["df"]] <- fm[c("baseline.df", "df")]
maintab[["pvalue"]] <- c(NA, fm["pvalue"])
# display warnings in footnote
admissible <- .withWarnings(lavaan:::lav_object_post_check(lgcmResult))
if (!admissible$value) {
maintab$addFootnote(
message = gettextf(
"The model is not admissible: %s",
.decodeVarsInMessage(names(dataset), admissible$warnings[[1]]$message)
),
symbol = gettext("Warning.")
)
}
}
.lgcmParameterTables <- function(modelContainer, dataset, options, ready) {
partabs <- if (!is.null(modelContainer[["partabs"]])) {
modelContainer[["partabs"]]
} else {
modelContainer[["partabs"]] <- createJaspContainer(gettext("Parameter estimates"))
}
partabs$dependOn("standardizedEstimate")
partabs$position <- 2
# create tables
# latent curve
latcur <- createJaspTable("Latent curve")
latcur$addColumnInfo("component", title = gettext("Component"), type = "string", combine = TRUE)
latcur$addColumnInfo("param", title = gettext("Parameter"), type = "string")
latcur$addColumnInfo("est", title = gettext("Estimate"), type = "number", format = "dp:3")
latcur$addColumnInfo("se" , title = gettext("Std. Error"), type = "number", format = "dp:3")
latcur$addColumnInfo("zval", title = gettext("z-value"), type = "number", format = "dp:3")
latcur$addColumnInfo("pval", title = gettext("p"), type = "number", format = "dp:3;p:.001")
latcur$addColumnInfo("cilo", title = gettext("Lower"), type = "number", format = "dp:3",
overtitle = gettextf("95%% Confidence Interval"))
latcur$addColumnInfo("ciup", title = "Upper" , type = "number", format = "dp:3",
overtitle = gettextf("95%% Confidence Interval"))
if (options[["standardizedEstimate"]]) {
latcur$addColumnInfo("std.lv", title = "LV", type = "number", format = "dp:3", overtitle = "Std. Est.")
latcur$addColumnInfo("std.all", title = "All", type = "number", format = "dp:3", overtitle = "Std. Est.")
latcur$addColumnInfo("std.nox", title = "No X", type = "number", format = "dp:3", overtitle = "Std. Est.")
}
modelContainer[["partabs"]][["latcur"]] <- latcur
# covariance
if (options[["covaryingLatentCurve"]]) {
latcov <- createJaspTable(gettext("Latent covariances"))
latcov$addColumnInfo("lhs", title = "", type = "string")
latcov$addColumnInfo("sep", title = "", type = "separator")
latcov$addColumnInfo("rhs", title = "", type = "string")
latcov$addColumnInfo("est", title = gettext("Estimate"), type = "number", format = "dp:3")
latcov$addColumnInfo("se" , title = gettext("Std. Error"), type = "number", format = "dp:3")
latcov$addColumnInfo("zval", title = gettext("z-value"), type = "number", format = "dp:3")
latcov$addColumnInfo("pval", title = gettext("p"), type = "number", format = "dp:3;p:.001")
latcov$addColumnInfo("cilo", title = gettext("Lower"), type = "number", format = "dp:3",
overtitle = gettextf("95%% Confidence Interval"))
latcov$addColumnInfo("ciup", title = "Upper" , type = "number", format = "dp:3",
overtitle = gettextf("95%% Confidence Interval"))
if (options[["standardizedEstimate"]]) {
latcov$addColumnInfo("std.lv", title = gettext("LV"), type = "number", format = "dp:3", overtitle = gettext("Std. Est."))
latcov$addColumnInfo("std.all", title = gettext("All"), type = "number", format = "dp:3", overtitle = gettext("Std. Est."))
latcov$addColumnInfo("std.nox", title = gettext("No X"), type = "number", format = "dp:3", overtitle = gettext("Std. Est."))
}
modelContainer[["partabs"]][["latcov"]] <- latcov
}
# regressions
if (length(c(options[["regressions"]], options[["categorical"]])) > 0) {
latreg <- createJaspTable("Regressions")
latreg$addColumnInfo("component", title = gettext("Component"), type = "string", combine = TRUE)
latreg$addColumnInfo("predictor", title = gettext("Predictor"), type = "string")
latreg$addColumnInfo("est", title = gettext("Estimate"), type = "number", format = "dp:3")
latreg$addColumnInfo("se" , title = gettext("Std. Error"), type = "number", format = "dp:3")
latreg$addColumnInfo("zval", title = gettext("z-value"), type = "number", format = "dp:3")
latreg$addColumnInfo("pval", title = gettext("p"), type = "number", format = "dp:3;p:.001")
latreg$addColumnInfo("cilo", title = gettext("Lower"), type = "number", format = "dp:3",
overtitle = gettextf("95%% Confidence Interval"))
latreg$addColumnInfo("ciup", title = "Upper" , type = "number", format = "dp:3",
overtitle = gettextf("95%% Confidence Interval"))
if (options[["standardizedEstimate"]]) {
latreg$addColumnInfo("std.lv", title = gettext("LV"), type = "number", format = "dp:3", overtitle = gettext("Std. Est."))
latreg$addColumnInfo("std.all", title = gettext("All"), type = "number", format = "dp:3", overtitle = gettext("Std. Est."))
latreg$addColumnInfo("std.nox", title = gettext("No X"), type = "number", format = "dp:3", overtitle = gettext("Std. Est."))
}
modelContainer[["partabs"]][["latreg"]] <- latreg
}
# residual variances
resvar <- createJaspTable("Residual variances")
resvar$addColumnInfo("var", title = gettext("Variable"), type = "string")
resvar$addColumnInfo("est", title = gettext("Estimate"), type = "number", format = "dp:3")
resvar$addColumnInfo("se" , title = gettext("Std. Error"), type = "number", format = "dp:3")
resvar$addColumnInfo("zval", title = gettext("z-value"), type = "number", format = "dp:3")
resvar$addColumnInfo("pval", title = gettext("p"), type = "number", format = "dp:3;p:.001")
resvar$addColumnInfo("cilo", title = gettext("Lower"), type = "number", format = "dp:3",
overtitle = gettextf("95%% Confidence Interval"))
resvar$addColumnInfo("ciup", title = "Upper" , type = "number", format = "dp:3",
overtitle = gettextf("95%% Confidence Interval"))
if (options[["standardizedEstimate"]]) {
resvar$addColumnInfo("std.lv", title = gettext("LV"), type = "number", format = "dp:3", overtitle = gettext("Std. Est."))
resvar$addColumnInfo("std.all", title = gettext("All"), type = "number", format = "dp:3", overtitle = gettext("Std. Est."))
resvar$addColumnInfo("std.nox", title = gettext("No X"), type = "number", format = "dp:3", overtitle = gettext("Std. Est."))
}
modelContainer[["partabs"]][["resvar"]] <- resvar
if (!ready || modelContainer$getError()) return()
lgcmResult <- modelContainer[["model"]][["object"]]
pe <- lavaan::parameterestimates(lgcmResult, level = options[["ciLevel"]],
standardized = options[["standardizedEstimate"]])
slope_names <- c(
"^I$" = gettext("Intercept"),
"^L$" = gettext("Linear slope"),
"^Q$" = gettext("Quadratic slope"),
"^C$" = gettext("Cubic slope")
)
pe[["lhs"]] <- stringr::str_replace_all(pe[["lhs"]], slope_names)
pe[["rhs"]] <- stringr::str_replace_all(pe[["rhs"]], slope_names)
# latent curve
pecur <- pe[pe$lhs %in% slope_names & (pe$op == "~1" | pe$rhs == pe$lhs),]
pecur <- pecur[order(pecur$lhs, rev(pecur$op)),]
latcur[["component"]] <- pecur[["lhs"]]
latcur[["param"]] <- ifelse(pecur[["op"]] == "~1", gettext("Mean"), gettext("Variance"))
latcur[["est"]] <- pecur[["est"]]
latcur[["se" ]] <- pecur[["se"]]
latcur[["zval"]] <- pecur[["z"]]
latcur[["pval"]] <- pecur[["pvalue"]]
latcur[["cilo"]] <- pecur[["ci.lower"]]
latcur[["ciup"]] <- pecur[["ci.upper"]]
if (options[["standardizedEstimate"]]) {
latcur[["std.lv"]] <- pecur[["std.lv"]]
latcur[["std.all"]] <- pecur[["std.all"]]
latcur[["std.nox"]] <- pecur[["std.nox"]]
}
# covariance
if (options[["covaryingLatentCurve"]]) {
pecov <- pe[pe$lhs %in% slope_names & pe$op == "~~" & pe$lhs != pe$rhs,]
latcov[["lhs"]] <- pecov[["lhs"]]
latcov[["sep"]] <- rep("\u2B64\u00A0", nrow(pecov))
latcov[["rhs"]] <- pecov[["rhs"]]
latcov[["est"]] <- pecov[["est"]]
latcov[["se" ]] <- pecov[["se"]]
latcov[["zval"]] <- pecov[["z"]]
latcov[["pval"]] <- pecov[["pvalue"]]
latcov[["cilo"]] <- pecov[["ci.lower"]]
latcov[["ciup"]] <- pecov[["ci.upper"]]
if (options[["standardizedEstimate"]]) {
latcov[["std.lv"]] <- pecov[["std.lv"]]
latcov[["std.all"]] <- pecov[["std.all"]]
latcov[["std.nox"]] <- pecov[["std.nox"]]
}
}
# regressions
if (length(c(options[["regressions"]], options[["categorical"]])) > 0) {
pereg <- pe[pe$lhs %in% slope_names & pe$op == "~",]
pereg <- pereg[order(pereg$lhs), ]
latreg[["component"]] <- pereg[["lhs"]]
latreg[["predictor"]] <- pereg[["rhs"]]
latreg[["est"]] <- pereg[["est"]]
latreg[["se" ]] <- pereg[["se"]]
latreg[["zval"]] <- pereg[["z"]]
latreg[["pval"]] <- pereg[["pvalue"]]
latreg[["cilo"]] <- pereg[["ci.lower"]]
latreg[["ciup"]] <- pereg[["ci.upper"]]
if (options[["standardizedEstimate"]]) {
latreg[["std.lv"]] <- pereg[["std.lv"]]
latreg[["std.all"]] <- pereg[["std.all"]]
latreg[["std.nox"]] <- pereg[["std.nox"]]
}
}
# residual variances
perev <- pe[pe$lhs %in% options[["variables"]] & pe$lhs == pe$rhs,]
resvar[["var"]] <- perev[["lhs"]]
resvar[["est"]] <- perev[["est"]]
resvar[["se"]] <- perev[["se"]]
resvar[["zval"]] <- perev[["z"]]
resvar[["pval"]] <- perev[["pvalue"]]
resvar[["cilo"]] <- perev[["ci.lower"]]
resvar[["ciup"]] <- perev[["ci.upper"]]
if (options[["standardizedEstimate"]]) {
resvar[["std.lv"]] <- perev[["std.lv"]]
resvar[["std.all"]] <- perev[["std.all"]]
resvar[["std.nox"]] <- perev[["std.nox"]]
}
if (any(perev[["est"]] < 0)) {
resvar$addFootnote(gettext("Residual variance is negative. This may indicate model misspecification."),
colNames = "est",
rowNames = paste0("row", which(perev[["est"]] < 0) - 1))
}
}
.lcgmAdditionalFitTables <- function(modelContainer, dataset, options, ready) {
if (!options[["additionalFitMeasures"]]) return()
fitms <- createJaspContainer(gettext("Additional Fit measures"))
fitms$dependOn("additionalFitMeasures")
fitms$position <- 3
modelContainer[["fitMeasures"]] <- fitms
# Fit indices
fitms[["indices"]] <- fitin <- createJaspTable(gettext("Fit indices"))
fitin$addColumnInfo(name = "index", title = gettext("Index"), type = "string")
fitin$addColumnInfo(name = "value", title = gettext("Value"), type = "number", format = "sf:4;dp:3")
fitin$setExpectedSize(rows = 1, cols = 2)
# information criteria
fitms[["incrits"]] <- fitic <- createJaspTable(gettext("Information criteria"))
fitic$addColumnInfo(name = "index", title = "", type = "string")
fitic$addColumnInfo(name = "value", title = gettext("Value"), type = "number", format = "sf:4;dp:3")
fitic$setExpectedSize(rows = 1, cols = 2)
# other fit measures
fitms[["others"]] <- fitot <- createJaspTable(gettext("Other fit measures"))
fitot$addColumnInfo(name = "index", title = gettext("Metric"), type = "string")
fitot$addColumnInfo(name = "value", title = gettext("Value"), type = "number", format = "sf:4;dp:3")
fitot$setExpectedSize(rows = 1, cols = 2)
if (!ready || modelContainer$getError()) return()
# actually compute the fit measures
fm <- lavaan::fitmeasures(modelContainer[["model"]][["object"]])
# Fit indices
fitin[["index"]] <- c(
gettext("Comparative Fit Index (CFI)"),
gettext("Tucker-Lewis Index (TLI)"),
gettext("Bentler-Bonett Non-normed Fit Index (NNFI)"),
gettext("Bentler-Bonett Normed Fit Index (NFI)"),
gettext("Parsimony Normed Fit Index (PNFI)"),
gettext("Bollen's Relative Fit Index (RFI)"),
gettext("Bollen's Incremental Fit Index (IFI)"),
gettext("Relative Noncentrality Index (RNI)")
)
fitin[["value"]] <- fm[c("cfi", "tli", "nnfi", "nfi", "pnfi", "rfi", "ifi", "rni")]
# information criteria
fitic[["index"]] <- c(
gettext("Log-likelihood"),
gettext("Number of free parameters"),
gettext("Akaike (AIC)"),
gettext("Bayesian (BIC)"),
gettext("Sample-size adjusted Bayesian (SSABIC)")
)
fitic[["value"]] <- fm[c("logl", "npar", "aic", "bic", "bic2")]
# other fitmeasures
fitot[["index"]] <- c(
gettext("Root mean square error of approximation (RMSEA)"),
gettextf("RMSEA 90%% CI lower bound"),
gettextf("RMSEA 90%% CI upper bound"),
gettext("RMSEA p-value"),
gettext("Standardized root mean square residual (SRMR)"),
gettextf("Hoelter's critical N (%s = .05)","\u03B1"),
gettextf("Hoelter's critical N (%s = .01)","\u03B1"),
gettext("Goodness of fit index (GFI)"),
gettext("McDonald fit index (MFI)"),
gettext("Expected cross validation index (ECVI)")
)
fitot[["value"]] <- fm[c("rmsea", "rmsea.ci.lower", "rmsea.ci.upper", "rmsea.pvalue",
"srmr", "cn_05", "cn_01", "gfi", "mfi", "ecvi")]
return()
}
.lcgmRSquaredTable <- function(modelContainer, dataset, options, ready) {
if (!options[["rSquared"]] || !is.null(modelContainer[["rsquared"]])) return()
tabr2 <- createJaspTable(gettext("R-Squared"))
tabr2$position <- 3.5
tabr2$addColumnInfo(name = "__var__", title = "Variable", type = "string")
tabr2$addColumnInfo(name = "rsq", title = "R\u00B2", type = "number", format = "sf:4;dp:3")
tabr2$dependOn("rSquared")
modelContainer[["rsquared"]] <- tabr2
if (!ready || modelContainer$getError()) return()
# get r2 of variables, excluding the latent variables.
r2res <- lavaan::inspect(modelContainer[["model"]][["object"]], "r2")
r2res <- r2res[!names(r2res) %in% c("I", "L", "Q", "C")]
varnames <- names(r2res)
tabr2[["__var__"]] <- varnames
tabr2[["rsq"]] <- r2res
}
.lgcmImpliedCovTable <- function(modelContainer, dataset, options, ready) {
if (!options[["impliedCovariance"]]) return()
tab <- createJaspTable(gettext("Implied covariance matrix"))
tab$dependOn("impliedCovariance")
tab$position <- 4
modelContainer[["impliedCovTab"]] <- tab
if (!ready || modelContainer$getError()) return()
# actually compute the implied covariance
fv <- lavaan::fitted.values(modelContainer[["model"]][["object"]])
ic <- fv$cov
ic[upper.tri(ic)] <- NA
for (i in 1:ncol(ic)) {
nm <- colnames(ic)[i]
tab$addColumnInfo(nm, title = nm, type = "number", format = "sf:4;dp:3")
}
tab$addRows(ic, rowNames = colnames(ic))
return()
}
.lgcmResidualCovTable <- function(modelContainer, dataset, options, ready) {
if (!options[["residualCovariance"]]) return()
tab <- createJaspTable(gettext("Residual covariance matrix"))
tab$dependOn("residualCovariance")
tab$position <- 5
modelContainer[["rescov"]] <- tab
if (!ready || modelContainer$getError()) return()
# actually compute the implied covariance
rv <- lavaan::residuals(modelContainer[["model"]][["object"]])
rc <- rv$cov
rc[upper.tri(rc)] <- NA
for (i in 1:ncol(rc)) {
nm <- colnames(rc)[i]
tab$addColumnInfo(nm, title = nm, type = "number", format = "sf:4;dp:3")
}
tab$addRows(rc, rowNames = colnames(rc))
return()
}
.lgcmCurvePlot <- function(modelContainer, dataset, options, ready) {
if (!options[["curvePlot"]] || !is.null(modelContainer[["curveplot"]])) return()
curveplot <- createJaspPlot(title = gettext("Curve plot"), width = 480, height = 320)
curveplot$dependOn(c("curvePlot", "curvePlotCategorical", "curvePlotMaxLines", "colorPalette"))
curveplot$position <- 8
modelContainer[["curveplot"]] <- curveplot
if (!ready || modelContainer$getError()) return()
lgcmResult <- modelContainer[["model"]][["object"]]
plt <- .lgcmComputeCurvePlot(lgcmResult, dataset, options)
curveplot$plotObject <- plt
}
.lgcmComputeCurvePlot <- function(lgcmResult, dataset, options) {
N <- lgcmResult@Data@nobs[[1]]
P <- length(options[["variables"]])
idx <- 1:N
if (N > options[["curvePlotMaxLines"]]) {
idx <- 1:options[["curvePlotMaxLines"]]
N <- options[["curvePlotMaxLines"]]
}
ctgcl <- options[["curvePlotCategorical"]] != ""
# plot the individual-level growth curves
preds <- lavaan::lavPredict(lgcmResult)[idx, , drop = FALSE]
preds <- cbind(preds, matrix(0, nrow(preds), 4 - ncol(preds)))
timings <- sapply(options[["timings"]], function(t) t$timing)
xrange <- range(timings)
xx <- seq(xrange[1], xrange[2], length.out = 1000)
df_wide <- data.frame(xx = xx, apply(preds, 1, function(b) b[1] + xx*b[2] + xx^2*b[3] + xx^3*b[4]))
df_long <- tidyr::gather(df_wide, key = "Participant", value = "Val", -"xx")
if (ctgcl)
df_long[[options[["curvePlotCategorical"]]]] <- rep(dataset[[options[["curvePlotCategorical"]]]][idx], each = 1000)
# create raw data points data frame
points <- data.frame(lgcmResult@Data@X[[1]])[idx, lgcmResult@Data@ov.names[[1]] %in% options[["variables"]]]
names(points) <- timings
points[["Participant"]] <- paste0("X", 1:nrow(points))
points_long <- tidyr::gather(points, key = "xx", value = "Val", -"Participant")
points_long[["xx"]] <- as.numeric(points_long[["xx"]])
if (ctgcl)
points_long[[options[["curvePlotCategorical"]]]] <- rep(dataset[[options[["curvePlotCategorical"]]]][idx],
length(timings))
# points may need to be jittered
jitwidth <- if (N > 30) diff(range(timings) / (15 * P)) else 0
pos <- ggplot2::position_jitter(width = jitwidth)
# lines may need to be transparent
cc <- if (ctgcl) length(unique(points_long[[options[["curvePlotCategorical"]]]])) else 1
transparency <- min(1, (log(cc) + 1) / log(N))
# create the plot
p <-
ggplot2::ggplot(df_long, ggplot2::aes(x = xx, y = Val, group = Participant)) +
ggplot2::geom_point(data = points_long, position = pos, size = 2) +
ggplot2::geom_line(alpha = transparency, size = 0.8) +
ggplot2::labs(y = "Value", x = "Time") +
jaspGraphs::themeJaspRaw() +
jaspGraphs::scale_x_continuous() +
jaspGraphs::scale_y_continuous()
if (ctgcl)
return(
p +
ggplot2::aes_(colour = as.name(options[["curvePlotCategorical"]]),
shape = as.name(options[["curvePlotCategorical"]])) +
jaspGraphs::scale_JASPcolor_discrete(options[["colorPalette"]])
)
return(p)
}
.lgcmPathPlot <- function(modelContainer, dataset, options, ready) {
if (!options$pathPlot || !is.null(modelContainer[["pathplot"]])) return()
modelContainer[["pathplot"]] <- createJaspPlot(title = gettext("Model plot"), height = 500, width = 640)
modelContainer[["pathplot"]]$dependOn(c("pathPlot", "pathPlotMean", "pathPlotParameter"))
modelContainer[["pathplot"]]$position <- 9
if (!ready || modelContainer$getError()) return()
lgcmResult <- modelContainer[["model"]][["object"]]
png() # semplot opens a device even though we specify doNotPlot, so we hack.
pathplot <- semPlot::semPaths(
object = .lavToPlotObj(lgcmResult),
DoNotPlot = TRUE,
ask = FALSE,
layout = "tree2",
intercepts = options$pathPlotMean,
whatLabels = ifelse(options$pathPlotParameter, "par", "name"),
edge.color = "black",
color = list(lat = "#EAEAEA", man = "#EAEAEA", int = "#FFFFFF"),
border.width = 1.5,
edge.label.cex = 0.9,
lty = 2,
title = FALSE
)
dev.off()
modelContainer[["pathplot"]]$plotObject <- pathplot
}
.lgcmSyntax <- function(modelContainer, dataset, options, ready) {
if (!ready || !options[["syntax"]]) return()
modelContainer[["model_syntax"]] <- createJaspHtml(
text = .lgcmOptionsToMod(options, FALSE),
class = "jasp-code",
position = 10,
title = "Model Syntax",
dependencies = "syntax"
)
}
# Unused functions ----
.lgcmMisfitPlot <- function(modelContainer, dataset, options, ready) {
if (!options[["misfitPlot"]] || !is.null(modelContainer[["misfitplot"]])) return()
wh <- 50 + 50*length(options[["variables"]])
misplot <- createJaspPlot(title = gettext("Misfit plot"), width = wh, height = wh)
misplot$dependOn("misfitPlot")
misplot$position <- 9
modelContainer[["misfitplot"]] <- misplot
if (!ready || modelContainer$getError()) return()
lgcmResult <- modelContainer[["model"]][["object"]]
rescor <- lavaan::residuals(lgcmResult, type = "cor")
cc <- rescor[["cov"]]
cc[upper.tri(cc)] <- NA
gg <- .resCorToMisFitPlot(cc)
misplot$plotObject <- gg
}
.lgcmPlotRibbon <- function(lgcmResult, options) {
# plot uncertainty ribbon conditional on regressors == 0
# get parameter values
pe <- lavaan::parameterestimates(lgcmResult)
mu <- pe[pe$lhs %in% c("I", "L", "Q", "C") & pe$rhs == "", ]
addrow <- matrix(0, 4 - nrow(mu), ncol(mu))
colnames(addrow) <- names(mu)
mu <- rbind(mu, addrow)
s2 <- pe[pe$lhs %in% c("I", "L", "Q", "C") & pe$lhs == pe$rhs,]
s2 <- rbind(s2, addrow)
# create x range
timings <- sapply(options[["timings"]], function(t) t$timing)
xrange <- range(timings)
xx <- seq(xrange[1], xrange[2], length.out = 1000)
# inner ribbon (with only parameter uncertainty, no variance)
# growth curve for a typical person with covariates at 0
mu_mu <- mu$est[1] + xx*mu$est[2] + xx^2*mu$est[3] + xx^3*mu$est[4]
mu_up <- mu$ci.upper[1] + xx*mu$ci.upper[2] + xx^2*mu$ci.upper[3] + xx^3*mu$ci.upper[4]
mu_lo <- mu$ci.lower[1] + xx*mu$ci.lower[2] + xx^2*mu$ci.lower[3] + xx^3*mu$ci.lower[4]
# growth curve for draws as if our group were the population
mp <- qnorm(0.975)
s2_up <- (mu$est[1] + mp*sqrt(s2$est[1])) +
xx * (mu$est[2] + mp*sqrt(s2$est[2])) +
xx^2 * (mu$est[3] + mp*sqrt(s2$est[3])) +
xx^3 * (mu$est[4] + mp*sqrt(s2$est[4]))
s2_lo <- (mu$est[1] - mp*sqrt(s2$est[1])) +
xx * (mu$est[2] - mp*sqrt(s2$est[2])) +
xx^2 * (mu$est[3] - mp*sqrt(s2$est[3])) +
xx^3 * (mu$est[4] - mp*sqrt(s2$est[4]))
# growth curve for groups of people with covariates 0 and parameter uncertainty
ms_up <- (mu$ci.upper[1] + mp*sqrt(s2$ci.upper[1])) +
xx * (mu$ci.upper[2] + mp*sqrt(s2$ci.upper[2])) +
xx^2 * (mu$ci.upper[3] + mp*sqrt(s2$ci.upper[3])) +
xx^3 * (mu$ci.upper[4] + mp*sqrt(s2$ci.upper[4]))
ms_lo <- (mu$ci.lower[1] - mp*sqrt(s2$ci.upper[1])) +
xx * (mu$ci.lower[2] - mp*sqrt(s2$ci.upper[2])) +
xx^2 * (mu$ci.lower[3] - mp*sqrt(s2$ci.upper[3])) +
xx^3 * (mu$ci.lower[4] - mp*sqrt(s2$ci.upper[4]))
fac <- forcats::fct_rev(forcats::as_factor(rep(c("mu", "s2", "ms"), each = 1000)))
df <- data.frame(xx = rep(xx, 3), up = c(mu_up, s2_up, ms_up), lo = c(mu_lo, s2_lo, ms_lo), which = fac)
mu_df <- data.frame(xx = xx, y = mu_mu)
# create raw data points data frame
points <- data.frame(lgcmResult@Data@X[[1]])[idx, lgcmResult@Data@ov.names[[1]] %in% options[["variables"]]]
names(points) <- timings
points[["Participant"]] <- paste0("X", 1:nrow(points))
points_long <- tidyr::gather(points, key = "xx", value = "Val", -"Participant")
points_long[["xx"]] <- as.numeric(points_long[["xx"]])
# points may need to be jittered
jitwidth <- if (N > 30) diff(range(timings) / (15 * P)) else 0
pj <- ggplot2::position_jitter(width = jitwidth)
ggplot2::ggplot(df, mapping = ggplot2::aes(x = xx)) +
ggplot2::geom_ribbon(ggplot2::aes(ymax = up, ymin = lo, fill = which)) +
ggplot2::geom_line(data = mu_df, col = "#454545", ggplot2::aes(y = y)) +
ggplot2::geom_point(ggplot2::aes(y = Val), data = points_long, position = pj, col = "#454545") +
ggplot2::scale_fill_manual(
values = c("#ABABAB", "#909090", "#777777"),
guide = 'legend',
labels = c(gettext("Curve + variance + uncertainty"),
gettext("Curve + variance"),
gettext("Curve + parameter uncertainty"))
) +
labs(fill = "", x = "Time", y = "Value") +
theme_minimal()
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.