R/ctmaFit.R

Defines functions ctmaFit

Documented in ctmaFit

#' ctmaFit
#'
#' @description Fits a ctsem model with invariant drift effects across primary studies, possible multiple moderators (but all of them of the
#' the same type, either "cont" or "cat"), and possible cluster (e.g., countries where primary studies were conducted).
#'
#' @param ctmaInitFit object to which all single ctsem fits of primary studies has been assigned to (i.e., what has been returned by \code{\link{ctmaInit}})
#' @param primaryStudyList  could be a list of primary studies compiled with \code{\link{ctmaPrep}} that defines the subset of studies in ctmaInitFit that should actually be used
#' @param cluster  vector with cluster variables (e.g., countries). Has to be set up carfully. Will be included in \code{\link{ctmaPrep}} in later 'CoTiMA' versions.
#' @param activeDirectory  defines another active directory than the one used in ctmaInitFit
#' @param activateRPB  set to TRUE to receive push messages with 'CoTiMA' notifications on your phone
#' @param digits Number of digits used for rounding (in outputs)
#' @param invariantDrift  drift labels for drift effects that are set invariant across primary studies (default = all drift effects).
#' @param drift labels for drift effects. Have to be either of the type 'V1toV2' or '0' for effects to be excluded.
#' @param moderatedDrift labels for drift effects that are moderated (default = all drift effects)
#' @param mod.number which in the vector of moderator values shall be used (e.g., 2 for a single moderator or 1:3 for 3 moderators simultaneously)
#' @param mod.type 'cont' or 'cat' (mixing them in a single model not yet possible)
#' @param mod.names vector of names for moderators used in output
#' @param coresToUse if negative, the value is subtracted from available cores, else value = cores to use
#' @param indVarying allows continuous time intercepts to vary at the individual level (random effects model, accounts for unobserved heterogeneity)
#' @param scaleTI scale TI predictors - not recommended until version 0.5.3.1. Does not change aggregated results anyways, just interpretation of effects for dimmies representing primary studies.
#' @param scaleClus scale vector of cluster indicators - TRUE (default) yields avg. drift estimates, FALSE yields drift estimates of last cluster
#' @param scaleMod scale moderator variables - TRUE (default) recommended for continuous and categorical moderators, to separate withing and betwen efeccts
#' @param transfMod more general option to change moderator values. A vector as long as number of moderators analyzed (e.g., c("mean(x)", "x - median(x)"))
#' @param scaleTime scale time (interval) - sometimes desirable to improve fitting
#' @param optimize if set to FALSE, Stan’s Hamiltonian Monte Carlo sampler is used (default = TRUE = maximum a posteriori / importance sampling) .
#' @param nopriors if TRUE, any priors are disabled – sometimes desirable for optimization
#' @param finishsamples number of samples to draw (either from hessian based covariance or posterior distribution) for final results computation (default = 1000).
#' @param iter number of iterations (defaul = 1000). Sometimes larger values could be required fom Bayesian estimation
#' @param chains number of chains to sample, during HMC or post-optimization importance sampling.
#' @param verbose integer from 0 to 2. Higher values print more information during model fit – for debugging
#' @param allInvModel estimates a model with all parameters invariant (DRIFT, DIFFUSION, T0VAR) if set TRUE (defautl = FALSE)
#' @param customPar logical. If set TRUE leverages the first pass using priors and ensure that the drift diagonal cannot easily go too negative (helps since ctsem > 3.4)
#' @param equalDrift Not enabled
#' @param inits vector of start values
#' @param modsToCompare when performing contrasts for categorical moderators, the moderator numbers (position in mod.number) that is used
#' @param catsToCompare when performing contrasts for categorical moderators, the categories (values, not positions) for which effects are set equal
#' @param driftsToCompare when performing contrasts for categorical moderators, the (subset of) drift effects analyzed
#' @param useSampleFraction to speed up debugging. Provided as fraction (e.g., 1/10).
#' @param T0means Default 0 (assuming standardized variables). Can be assigned labels to estimate them freely.
#' @param manifestMeans Default 0 (assuming standardized variables). Can be assigned labels to estimate them freely.
#' @param CoTiMAStanctArgs parameters that can be set to improve model fitting of the \code{\link{ctStanFit}} Function
#' @param lambda R-type matrix with pattern of fixed (=1) or free (any string) loadings.
#' @param manifestVars define the error variances of the manifests with a single time point using R-type lower triangular matrix with nrow=n.manifest & ncol=n.manifest.

#'
#'
#' @importFrom  RPushbullet pbPost
#' @importFrom  parallel detectCores
#' @importFrom  ctsem ctWideToLong ctDeintervalise ctModel ctStanFit ctCollapse
#' @importFrom  OpenMx vech2full expm
#' @importFrom openxlsx addWorksheet writeData createWorkbook openXL saveWorkbook
#' @importFrom  stats cov2cor quantile sd
#'
#' @export ctmaFit
#'
#' @examples
#' \dontrun{
#' # Example 1. Fit a CoTiMA to all primary studies previously fitted one by one
#' # with the fits assigned to CoTiMAInitFit_6
#' CoTiMAFullFit_6 <- ctmaFit(ctmaInitFit=CoTiMAInitFit_6)
#' summary(CoTiMAFullFit_6)
#' }
#'
#' @examples
#' \dontrun{
#' # Example 2. Fit a CoTiMA with only 2 cross effects invariant (not the auto
#' # effects) to all primary studies previously fitted one by one with the fits
#' # assigned to CoTiMAInitFit_6
#' CoTiMAInitFit_6$activeDirectory <- "/Users/tmp/" # adapt!
#' CoTiMAFullInv23Fit_6 <- ctmaFit(ctmaInitFit=CoTiMAInitFit_6,
#'                         invariantDrift=c("V1toV2", "V2toV1"))
#' summary(CoTiMAFullInv23Fit_6)
#' }
#'
#' @examples
#' \dontrun{
#' # Example 3. Fit a moderated CoTiMA
#' CoTiMAInitFit_6$activeDirectory <- "/Users/tmp/" # adapt!
#' CoTiMAMod1onFullFit_6 <- ctmaFit(ctmaInitFit=CoTiMAInitFit_6,
#'                                  mod.number=1, mod.type="cont",
#'                                  mod.names=c("Control"))
#' summary(CoTiMAMod1onFullFit_6)
#' }
#'
#' @return ctmaFit returns a list containing somearguments supplied, the fitted model, different elements summarizing the main results,
#' model type, and the type of plot that could be performed with the returned object. The arguments in the returned object are activeDirectory,
#' coresToUse, moderator names (mod.names), and moderator type (mod.type). Further arguments, which are just copied from the init-fit object
#' supplied, are, n.latent, studyList, parameterNames, and statisticsList. The fitted model is found in studyFitList, which is a large list
#' with many elements (e.g., the ctsem model specified by CoTiMA, the rstan model created by ctsem, the fitted rstan model etc.). Further
#' results returned are n.studies = 1 (required for proper plotting), data (created pseudo raw data), and a list with modelResults (i.e.,
#' DRIFT=model_Drift_Coef, DIFFUSION=model_Diffusion_Coef, T0VAR=model_T0var_Coef, CINT=model_Cint_Coef, MOD=modTI_Coeff,  and
#' CLUS=clusTI_Coeff). Possible invariance constraints are included in invariantDrift. The number of moderators simultaneously analyzed are
#' included in ' n.moderators. The most important new results are returned as the list element "summary", which is printed if the summary
#' function is applied to the returned object. The summary list element comprises "estimates" (the aggregated effects), possible
#' randomEffects (not yet fully working),  the minus2ll value and its n.parameters, the opt.lag sensu Dormann & Griffin (2015) and the
#' max.effects that occur at the opt.lag, clus.effects and mod.effects, and possible warning messages (message). Plot type is
#' plot.type=c("drift") and model.type="stanct" ("omx" was deprecated).
#'
ctmaFit <- function(
  ctmaInitFit=NULL,
  primaryStudyList=NULL,
  cluster=NULL,
  activeDirectory=NULL,
  activateRPB=FALSE,
  digits=4,
  drift=NULL,
  invariantDrift=NULL,
  moderatedDrift=NULL,
  equalDrift=NULL,
  mod.number=NULL,
  mod.type="cont",
  mod.names=NULL,
  #n.manifest=0,
  indVarying=FALSE,
  coresToUse=c(1),
  scaleTI=TRUE,
  scaleMod=NULL,
  transfMod=NULL,
  scaleClus=NULL,
  scaleTime=NULL,
  optimize=TRUE,
  nopriors=TRUE,
  finishsamples=NULL,
  iter=NULL,
  chains=NULL,
  verbose=NULL,
  allInvModel=FALSE,
  customPar=FALSE,
  inits=NULL,
  modsToCompare=NULL,
  catsToCompare=NULL,
  driftsToCompare=NULL,
  useSampleFraction=NULL,
  T0means=0,
  manifestMeans=0,
  CoTiMAStanctArgs=NULL,
  lambda=NULL,
  manifestVars=NULL
)


{  # begin function definition (until end of file)

  # adapt display of information during model fit
  if (is.null(verbose) & (optimize == FALSE) )  {verbose <- 0} else {verbose <- CoTiMAStanctArgs$verbose}

  # check if fit object is specified
  if (is.null(ctmaInitFit)){
    if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Data processing stopped.\nYour attention is required."))}
    ErrorMsg <- "\nA fitted CoTiMA object has to be supplied to plot something. \nGood luck for the next try!"
    stop(ErrorMsg)
  }

  { # set fitting params
    # Added 17. Aug 2022
    tmp1 <- names(CoTiMA::CoTiMAStanctArgs) %in% names(CoTiMAStanctArgs); tmp1
    tmp2 <- CoTiMA::CoTiMAStanctArgs
    if (!(is.null(CoTiMAStanctArgs))) tmp2[tmp1] <- CoTiMAStanctArgs
    CoTiMAStanctArgs <- tmp2

    if (!(is.null(scaleTI))) CoTiMAStanctArgs$scaleTI <- scaleTI
    if (!(is.null(scaleClus))) CoTiMAStanctArgs$scaleClus <- scaleClus
    if (!(is.null(scaleMod))) CoTiMAStanctArgs$scaleMod <- scaleMod
    if (!(is.null(scaleTime))) CoTiMAStanctArgs$scaleTime <- scaleTime
    if (!(is.null(optimize))) CoTiMAStanctArgs$optimize <- optimize
    if (!(is.null(nopriors))) CoTiMAStanctArgs$nopriors <- nopriors
    if (!(is.null(finishsamples))) CoTiMAStanctArgs$optimcontrol$finishsamples <- finishsamples
    if (!(is.null(chains))) CoTiMAStanctArgs$chains <- chains
    if (!(is.null(iter))) CoTiMAStanctArgs$iter <- iter
    if (!(is.null(verbose))) CoTiMAStanctArgs$verbose <- verbose
  }

  { # check of catsToCompare or catsToCompare is used only with mod.type = "cat"
    if ( ((!(is.null(modsToCompare))) & (mod.type != "cat")) |
         ((!(is.null(catsToCompare))) & (mod.type != "cat")) ) {
      if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Attention!"))}
      ErrorMsg <- "The arguments modsToCompare or catsToCompare are only allowed for mod.typ=\"cat\" "
      stop(ErrorMsg)
    }
  }
  if ( (!(is.null(catsToCompare))) & (is.null(modsToCompare)) ) modsToCompare <- 1


  { # check if scaleMod is not used in combination with transfMod
    if ( (!(is.null(scaleMod))) & (!(is.null(transfMod))) ) {
      if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Attention!"))}
      ErrorMsg <- "The arguments scaleMod and transfMod cannot be used in combination. Set one of them NULL (leave out)."
      stop(ErrorMsg)
    }
    }


  #######################################################################################################################
  ####### Copy/Change INIT File based on information delivered by different PREP files (e.g., moderator studies ) #######
  #######################################################################################################################

  # if primary study list is provided in addition to initfit-object, take primary study information from  primary study
  if (!(is.null(primaryStudyList))) {
    ctmaTempFit <- ctmaInitFit
    targetStudyNumbers <- unlist(primaryStudyList$studyNumbers); targetStudyNumbers; length(targetStudyNumbers)
    if (any(is.na(targetStudyNumbers))) targetStudyNumbers <- targetStudyNumbers[-which(is.na(targetStudyNumbers))]
    #targetStudyNumbers <- targetStudyNumbers[-which(is.na(targetStudyNumbers))]; targetStudyNumbers
    for (i in (length(ctmaTempFit$studyFitList)):1) {
      if (!(ctmaTempFit$studyList[[i]]$originalStudyNo %in% targetStudyNumbers)) {
        ctmaTempFit$studyList[[i]] <- NULL
        ctmaTempFit$studyFitList[[i]] <- NULL
        ctmaTempFit$emprawList[[i]] <- NULL
        ctmaTempFit$statisticsList$originalStudyNumbers[i] <- NA
        ctmaTempFit$statisticsList$allSampleSizes[i+1] <- NA
        ctmaTempFit$statisticsList$allTpoints[i] <- NA
        ctmaTempFit$modelResults[[1]][[i]] <- NULL
        ctmaTempFit$modelResults[[2]][[i]] <- NULL
        ctmaTempFit$modelResults[[3]][[i]] <- NULL
      }
    }
    ctmaTempFit$n.studies <- length(targetStudyNumbers); ctmaTempFit$n.studies
    ctmaTempFit$statisticsList$allDeltas <- unlist(lapply(ctmaTempFit$studyList, function(extract) extract$delta_t))
    ctmaTempFit$statisticsList$minDelta <- min(ctmaTempFit$statisticsList$allDeltas, na.rm=TRUE)
    ctmaTempFit$statisticsList$maxDelta <- max(ctmaTempFit$statisticsList$allDeltas, na.rm=TRUE)
    ctmaTempFit$statisticsList$meanDelta <- mean(ctmaTempFit$statisticsList$allDeltas, na.rm=TRUE)
    ctmaTempFit$statisticsList$overallSampleSize <- sum(ctmaTempFit$statisticsList$allSampleSizes, na.rm = TRUE)
    ctmaTempFit$statisticsList$meanSampleSize <- mean(ctmaTempFit$statisticsList$allSampleSizes, na.rm = TRUE)
    ctmaTempFit$statisticsList$maxSampleSize <- max(ctmaTempFit$statisticsList$allSampleSizes, na.rm = TRUE)
    ctmaTempFit$statisticsList$minSampleSize <- min(ctmaTempFit$statisticsList$allSampleSizes, na.rm = TRUE)
    ctmaTempFit$statisticsList$overallTpoints <- sum(ctmaTempFit$statisticsList$allTpoints, na.rm = TRUE)
    ctmaTempFit$statisticsList$meanTpoints <- mean(ctmaTempFit$statisticsList$allTpoints, na.rm = TRUE)
    ctmaTempFit$statisticsList$maxTpoints <- max(ctmaTempFit$statisticsList$allTpoints, na.rm = TRUE)
    ctmaTempFit$statisticsList$minTpoints <- min(ctmaTempFit$statisticsList$allTpoints, na.rm = TRUE)
    ctmaTempFit$summary$model <- "Moderator Model (for details see model summary)"
    tmpStudyNumber <- as.numeric(gsub("Study No ", "", rownames(ctmaTempFit$summary$estimates))); tmpStudyNumber
    targetRows <- which(tmpStudyNumber %in% targetStudyNumbers); targetRows; length(targetRows)
    ctmaTempFit$summary$estimates <- ctmaTempFit$summary$estimates[targetRows, ]
    ctmaTempFit$summary$confidenceIntervals <- ctmaTempFit$summary$confidenceIntervals[targetRows, ]
    ctmaTempFit$summary$n.parameters <- ctmaTempFit$studyFitList[[1]]$resultsSummary$npars * length(targetRows)
    ctmaTempFit$statisticsList$originalStudyNumbers <-
      ctmaTempFit$statisticsList$originalStudyNumbers[which(!(is.na(ctmaTempFit$statisticsList$originalStudyNumbers)))]
    ctmaTempFit$statisticsList$allSampleSizes <-
      ctmaTempFit$statisticsList$allSampleSizes[which(!(is.na(ctmaTempFit$statisticsList$allSampleSizes)))]
    ctmaTempFit$statisticsList$allTpoints <-
      ctmaTempFit$statisticsList$allTpoints[which(!(is.na(ctmaTempFit$statisticsList$allTpoints)))]

    ctmaInitFit <- ctmaTempFit
  }


  #######################################################################################################################
  ################################################# Check Cores To Use ##################################################
  #######################################################################################################################

  {
    if  (length(coresToUse) > 0) {
      if (coresToUse < 1)  coresToUse <- parallel::detectCores() + coresToUse
    }

    if (coresToUse >= parallel::detectCores()) {
      if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Attention!"))}
      coresToUse <- parallel::detectCores() - 1
      Msg <- "No of coresToUsed was set to >= all cores available. Reduced to max. no. of cores - 1 to prevent crash."
      message(Msg)
    }
  }

  #######################################################################################################################
  ############# Extracting Parameters from Fitted Primary Studies created with ctmaInit Function  #####################
  #######################################################################################################################

  start.time <- Sys.time(); start.time

  {
    if (is.null(ctmaInitFit$n.latent)) {
      n.latent <- length(ctmaInitFit$modelResults$DRIFT[[1]])^.5; n.latent
    } else {
      n.latent <- ctmaInitFit$n.latent
    }
    if (!(is.null(ctmaInitFit$n.manifest))) n.manifest <- ctmaInitFit$n.manifest else n.manifest <- n.latent
    if (is.null(activeDirectory)) activeDirectory <- ctmaInitFit$activeDirectory; activeDirectory
    n.studies <- unlist(ctmaInitFit$n.studies); n.studies
    allTpoints <- ctmaInitFit$statisticsList$allTpoints; allTpoints
    maxTpoints <- max(allTpoints); maxTpoints
    allDeltas <- ctmaInitFit$statisticsList$allDeltas; allDeltas
    maxDelta <- max(allDeltas, na.rm=TRUE); maxDelta
    #usedTimeRange <- seq(0, 1.5*maxDelta, 1); usedTimeRange
    usedTimeRange <- seq(0, 3*maxDelta, 1); usedTimeRange # new 8.7.2022
    lambda <- ctmaInitFit$statisticsList$lambda; lambda
  }


  # check match between cluster vector and n.studies
  if (!(is.null(cluster))) {
    if (length(cluster) != n.studies) {
      if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Data processing stopped.\nYour attention is required."))}
      ErrorMsg <- "\nThe vector of cluster numbers does not match the number of primary studies.\nGood luck for the next try!"
      stop(ErrorMsg)
    }
  }

  # check moderator information
  {
    n.moderators <- length(mod.number); n.moderators
    { # check if transfMod is as long as n.moderators
      if ( (!(is.null(transfMod))) ) {
        if ( length(transfMod) != n.moderators ) {
        if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Attention!"))}
        ErrorMsg <- "More transformations for moderators (transfMod) provided than moderators."
        stop(ErrorMsg)
        }
      }
    }
    if (n.moderators > 0) {
      currentModerators <- matrix(as.numeric(unlist(lapply(ctmaInitFit$studyList, function(extract) extract$moderators[mod.number]))), ncol=n.moderators); currentModerators
      if (!(is.null(primaryStudyList))) currentModerators <- matrix(as.numeric(unlist(lapply(primaryStudyList$moderators, function(extract) extract[mod.number]))), ncol=n.moderators, byrow=TRUE)
      #currentModerators
      #if (!is.null(primaryStudyList)) currentModerators <- matrix(as.numeric(unlist(lapply(primaryStudyList$moderators, function(extract) extract[mod.number]))), ncol=n.moderators, byrow=TRUE); currentModerators
      if (is.na((currentModerators[length(currentModerators)])[[1]][1])) currentModerators <- currentModerators[-dim(currentModerators)[1],]; currentModerators
      if (is.null(dim(currentModerators)[1])) currentModerators <- matrix(currentModerators, ncol=1); currentModerators
      #table(currentModerators)

      if (any(is.na(currentModerators)) == TRUE) {
        if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Data processing stopped.\nYour attention is required."))}
        ErrorMsg <- "\nAt least one of the primary studies does not have a valid value for the requested moderator. \nGood luck for the next try!"
        stop(ErrorMsg)
      }
      if (var(currentModerators) == 0) {
        if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Data processing stopped.\nYour attention is required."))}
        ErrorMsg <- "\nModerator is constant across cases.\nGood luck for the next try!"
        stop(ErrorMsg)
      }
    }
  }

  #######################################################################################################################
  ################################################# data preparation ####################################################
  #######################################################################################################################

  {
    # manifests & latent names
    if (n.manifest > n.latent) {
      manifestNames <- paste0("y", 1:n.manifest); manifestNames
      latentNames <- paste0("V", 1:n.latent); latentNames
    } else {
      manifestNames <- paste0("V", 1:n.latent); manifestNames
      latentNames <- paste0("V", 1:n.latent); latentNames
    }

    # combine pseudo raw data
    {
      if (n.moderators > 0) {
        tmp <- ctmaCombPRaw(listOfStudyFits=ctmaInitFit, moderatorValues= currentModerators)
      } else {
        tmp <- ctmaCombPRaw(listOfStudyFits=ctmaInitFit)
      }
      datawide_all <- tmp$alldata
      groups <- tmp$groups # vector of study IDs
    }

    # delete cases with missing moderator values
    if (n.moderators > 0) {
      casesToDelete <- tmp$casesToDelete; casesToDelete
      if (!(is.null(casesToDelete))) {
        datawide_all <- datawide_all[-casesToDelete, ]
        groups <- groups[-casesToDelete]
        n.studies <- length(unique(groups))
      }
    }

    # make data matrix with moderators
    if (n.moderators > 0) {
      moderatorGroups <- tmp$moderatorGroups
      if (!(is.matrix(moderatorGroups))) moderatorGroups <- matrix(moderatorGroups, ncol=1)
      colnames(moderatorGroups) <- paste0("mod", 1:(dim(currentModerators)[2])); colnames(moderatorGroups)

      # change category values for making specific contrasts
      if (!(is.null(catsToCompare))) { # change category values to make the cats to compare the largest ones

        ### check if comparison of requested categories is possible
        tmp1 <- as.numeric(names(table(moderatorGroups))); tmp1
        counter <- 0
        for (c1 in catsToCompare) if (c1 %in% tmp1) counter <- counter + 1
        if (counter < length(catsToCompare)){
          if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Data processing stopped.\nYour attention is required."))}
          ErrorMsg <- "\nAt least one of the categories that should be compared is not available in the data. \nGood luck for the next try!"
          stop(ErrorMsg)
        }

        # check if each requested moderators is available in more than 1 study
        for (c1 in catsToCompare) if (!(c1 %in% as.numeric(names(table(currentModerators))))) {
          if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Data processing stopped.\nYour attention is required."))}
          ErrorMsg <- "\nOne of the categories that should be compared exists in a single primary study only. This model is not identified. Eliminate the primary study and redo ctmaPrep.. \nGood luck for the next try!"
          stop(ErrorMsg)
        } #

        for (c1 in 1:dim(moderatorGroups)[2]) {
          if (c1 %in% modsToCompare) {
            #maxModeratorGroups <- max(moderatorGroups[, c1]); maxModeratorGroups
            maxAbsModeratorGroups <- max(abs(moderatorGroups[, c1])) ; maxAbsModeratorGroups
            minAbsModeratorGroups <- min(abs(moderatorGroups[, c1])) ; minAbsModeratorGroups
            moderatorGroupsOffset <- maxAbsModeratorGroups + minAbsModeratorGroups; moderatorGroupsOffset
            for (c2 in catsToCompare) {
              #moderatorGroups[moderatorGroups[ ,c1] == c2] <- moderatorGroups[moderatorGroups[ ,c1] == c2] + maxModeratorGroups
              moderatorGroups[moderatorGroups[ ,c1] == c2] <- moderatorGroups[moderatorGroups[ ,c1] == c2] - moderatorGroupsOffset
            }
          }
        }
      }  # END if (!(is.null(catsToCompare)))

    }

    # label group vectors
    names(groups) <- c("Study_No_"); groups
    groupsNamed <- (paste0("Study_No_", groups)); groupsNamed

    # augment pseudo raw data by group ID and moderators
    if (n.moderators > 0) {
      dataTmp <- cbind(datawide_all, groups, moderatorGroups)
    } else {
      dataTmp <- cbind(datawide_all, groups)
    }

    # make TI out of group membership
    for (i in 1:(n.studies-1)) {
      tmp <- matrix(0, nrow=nrow(dataTmp)); tmp
      colnames(tmp) <- paste0("TI", i); tmp
      dataTmp <- cbind(dataTmp, tmp); dim(dataTmp)
      tmp <- which(dataTmp[,"groups"] == i); tmp
      dataTmp[tmp, ncol(dataTmp)] <- 1
      if (CoTiMAStanctArgs$scaleTI == TRUE) dataTmp[ , ncol(dataTmp)] <- scale(dataTmp[ , ncol(dataTmp)])
    }
    targetCols <- which(colnames(dataTmp) == "groups"); targetCols
    dataTmp <- dataTmp[ ,-targetCols]

    # make TI out of moderators
    modTIstartNum <- n.studies; modTIstartNum
    if (n.moderators > 0) {
      # make TI predictors as replacements for each moderator
      if (mod.type=="cont") {
        tmp1 <- paste0("mod", 1:n.moderators); tmp1; dim(tmp1)
        if (length(tmp1) == 1) tmp <- matrix(dataTmp[ , tmp1], ncol=length(tmp1)) else tmp <- dataTmp[ , tmp1]
        if (CoTiMAStanctArgs$scaleMod == TRUE) tmp[ , 1:ncol(tmp)] <- scale(tmp[ , 1:ncol(tmp)])
        if (!(is.null(transfMod))) {
          tmp2 <- tmp #[ , 1:ncol(tmp)]
          for (t in 1:length(transfMod)) {
            x <- tmp2[, t]
            tmp2[, t] <- as.numeric(eval(parse(text=transfMod[t])))
            }
          tmp[ , 1:ncol(tmp)] <- tmp2
        }
        currentStartNumber <- modTIstartNum; currentStartNumber
        currentEndNumber <- currentStartNumber + n.moderators-1; currentEndNumber
        colnames(tmp) <- paste0("TI", currentStartNumber:currentEndNumber); tmp
        dataTmp <- cbind(dataTmp, tmp); dim(dataTmp)
        dataTmp <- dataTmp[ ,-grep("mod", colnames(dataTmp))]
      }
      if (mod.type=="cat") {
        tmp1 <- paste0("mod", 1:n.moderators); tmp1
        if (length(tmp1) == 1) tmp <- matrix(dataTmp[ , tmp1], ncol=length(tmp1)) else tmp <- dataTmp[ , tmp1]
        if (n.moderators > 1) {
          unique.mod <- list()
          for (i in 1:n.moderators) unique.mod[[i]] <- sort(c(unique(tmp[,i])))
        } else {
          unique.mod <- sort(c(unique(tmp)))
        }

        # determine number of required dummies
        if (n.moderators > 1) {
          catCounter <- 0
          for (i in 1:length(unique.mod)) catCounter <- catCounter + length(unique.mod[[i]]) -1
        } else {
          catCounter <- length(unique.mod) -1
        }

        # create dummies
        tmpTI <- matrix(0, dim(tmp)[1], catCounter)
        counter <- 0
        if (n.moderators > 1) {
          for (i in 1:n.moderators) {
            for (j in 2:length(unique.mod[[i]])) {
              counter <- counter + 1
              tmp2 <- which(tmp[, i] == unique.mod[[i]][j]); tmp2
              tmpTI[tmp2, counter] <- 1
            }
          }
        } else {
          for (i in 2:length(unique.mod)) {
            counter <- counter + 1; counter
            tmp2 <- which(tmp == unique.mod[i]); tmp2
            tmpTI[tmp2, counter] <- 1
          }
        }

        if (CoTiMAStanctArgs$scaleMod == TRUE) tmpTI[ , 1:ncol(tmpTI)] <- scale(tmpTI[ , 1:ncol(tmpTI)], scale=FALSE)
        currentStartNumber <- modTIstartNum; currentStartNumber
        currentEndNumber <- currentStartNumber + ncol(tmpTI)-1; currentEndNumber
        colnames(tmpTI) <- paste0("TI", currentStartNumber:currentEndNumber)

        dataTmp <- cbind(dataTmp, tmpTI); dim(dataTmp)
        dataTmp <- dataTmp[ ,-grep("mod", colnames(dataTmp))]
      }
    } # END if (n.moderators > 0)

    # add clusters as dummy moderators
    if (!(is.null(cluster))) {
      # determine number of required dummies
      targetCluster <- which(table(cluster) > 1); targetCluster  # no cluster if only one study is included
      targetCluster <- names(targetCluster); targetCluster
      clusCounter <- length(targetCluster); clusCounter
      if (clusCounter == length(table(cluster))) clusCounter <- clusCounter -1 # if all cluster exits leave the last one as reference cluster
      # create dummies
      tmpTI <- matrix(0, dim(dataTmp)[1], clusCounter)
      for (i in 1:clusCounter) {
        targetGroups <- which(cluster == targetCluster[i]); targetGroups
        tmp2 <- which(groups %in% targetGroups); length(tmp2)
        tmpTI[tmp2, i] <- 1
      }
      if (CoTiMAStanctArgs$scaleClus == TRUE) tmpTI[ , 1:ncol(tmpTI)] <- scale(tmpTI[ , 1:ncol(tmpTI)], scale=FALSE)
      cluster.weights <- cluster.sizes <- matrix(NA, nrow=ncol(tmpTI), ncol=2)
      for (l in 1:ncol(tmpTI)) {
        cluster.weights[l,] <- round(as.numeric(names(table(tmpTI[ , l]))), digits)
        cluster.sizes[l,] <- table(tmpTI[ , l])
      }
      rownames(cluster.weights) <- paste0(1:ncol(tmpTI), "_on_")
      colnames(cluster.weights) <- c("non Members", "Cluster Member")
      rownames(cluster.sizes) <- rep("N", ncol(tmpTI))
      colnames(cluster.sizes) <- c("non Members", "Cluster Member")
      cluster.note <- capture.output(cat("The weights represent standardized cluster dummies. They are used to multiply a cluster's TI effect ",
                                         "and this product is then added to the average effect shown in $estimates, which overall yields",
                                         "the effects within a cluster as shown in $cluster.specific.effect.",
                                         sep="\n"))
      currentStartNumber <- n.studies; currentStartNumber
      currentEndNumber <- currentStartNumber + clusCounter -1; currentEndNumber
      colnames(tmpTI) <- paste0("TI", currentStartNumber:currentEndNumber); colnames(tmpTI)
      dataTmp <- cbind(dataTmp, tmpTI); dim(dataTmp)
    } else {
      clusCounter <- 0
      cluster.weights <- c()
      cluster.sizes <- c()
      cluster.note <- c()
    }

    # params for ctsem model specification
    {
      tmp1 <- 0
      n.all.moderators <- 0
      if (n.moderators > 0) {
        if (mod.type == "cont") tmp1 <- n.moderators
        if (mod.type == "cat") tmp1 <- catCounter
        n.all.moderators <- tmp1
      }
      if (!(is.null(cluster))) tmp1 <- tmp1 + clusCounter

      n.var <- max(n.manifest, n.latent); n.var
    }

    # possible subsample selection
    if (!(is.null(useSampleFraction))) {
      N <- dim(dataTmp)[1]; N
      stepwidth <- 100/useSampleFraction
      targetCases <- round(seq(1, N, stepwidth), 0); targetCases
      dataTmp <- dataTmp[targetCases, ]
      #groups <- groups[targetCases]
    }


    # make long data format
    {
      dataTmp2 <- ctsem::ctWideToLong(dataTmp, Tpoints=maxTpoints, n.manifest=n.var, n.TIpred = (n.studies-1+tmp1),
                                      manifestNames=manifestNames)

      dataTmp3 <- suppressMessages(ctsem::ctDeintervalise(dataTmp2))
      dataTmp3[, "time"] <- dataTmp3[, "time"] * CoTiMAStanctArgs$scaleTime
    }

    # eliminate rows where ALL latents are NA
    {
      if (n.manifest > n.latent) namePart <- "y" else namePart <- "V"
      dataTmp3 <- dataTmp3[, ][ apply(dataTmp3[, paste0(namePart, 1:n.var)], 1, function(x) sum(is.na(x)) != n.var ), ]
      datalong_all <- dataTmp3
    }

    datalong_all <- as.data.frame(datalong_all)
  }

  # TI-identifiers for groups, moderators, and clusters
  {
    groupTIs <- paste0("TI", 1:(length(unique(groups))-1)); groupTIs
    tmp1 <- (length(unique(groups))); tmp1
    if (n.moderators > 0) modTIs <- paste0("TI", tmp1:(tmp1+n.all.moderators-1))
    tmp1 <- (tmp1+n.all.moderators); tmp1
    if (!(is.null(cluster))) clusTIs <- paste0("TI", tmp1:(tmp1+clusCounter-1))
  }


  #######################################################################################################################
  ############################################# CoTiMA (ctsem multigroup) ###############################################
  #######################################################################################################################

  # define Names (labels in compiled output) and Params (= labels for ctsem models)
  if (is.null(moderatedDrift) & (!(is.null(mod.number)))) moderatedDrift <- "all" # will be changed by ctmaLabels

  namesAndParams <- ctmaLabels(
    n.latent=n.latent,
    n.manifest=n.manifest,
    lambda=lambda,
    drift=drift,
    invariantDrift=invariantDrift,
    moderatedDrift=moderatedDrift,
    equalDrift=equalDrift,
    T0means=T0means,
    manifestMeans=manifestMeans,
    manifestVars=manifestVars
  )
  driftNames <- namesAndParams$driftNames; driftNames
  driftFullNames <- namesAndParams$driftFullNames; driftFullNames
  driftParams <- namesAndParams$driftParams; driftParams
  diffNames <- namesAndParams$diffNames; diffNames
  diffParams <- namesAndParams$diffParams; diffParams
  diffFullNames <- namesAndParams$diffFullNames; diffFullNames
  invariantDriftNames <- namesAndParams$invariantDriftNames; invariantDriftNames
  invariantDriftParams <- namesAndParams$invariantDriftParams; invariantDriftParams
  moderatedDriftNames <- namesAndParams$moderatedDriftNames; moderatedDriftNames
  equalDriftNames <- namesAndParams$equalDriftNames; equalDriftNames
  equalDriftParams <- namesAndParams$equalDriftParams; equalDriftParams
  lambdaParams <- namesAndParams$lambdaParams; lambdaParams
  T0VARParams <- namesAndParams$T0VARParams; T0VARParams
  manifestMeansParams <- namesAndParams$manifestMeansParams; manifestMeansParams
  T0meansParams=namesAndParams$T0meansParams
  manifestVarsParams <- namesAndParams$manifestVarsParams; manifestVarsParams

  if (is.null(invariantDriftNames)) invariantDriftNames <- driftNames

  if (allInvModel) {
    allInvModelFit <- ctmaAllInvFit(ctmaInitFit=ctmaInitFit,
                                    activeDirectory=activeDirectory,
                                    activateRPB=activateRPB,
                                    digits=digits,
                                    drift=drift,
                                    coresToUse=coresToUse,
                                    scaleTime=scaleTime,
                                    optimize=optimize,
                                    nopriors=nopriors,
                                    finishsamples=finishsamples,
                                    iter=iter,
                                    chains=chains,
                                    verbose=verbose,
                                    indVarying = indVarying,
                                    customPar = customPar)
    fitStanctModel <- allInvModelFit$studyFitList[[1]]
    invariantDriftStanctFit <- summary(fitStanctModel)
  } else {

    n.TIpred <- (n.studies-1+n.all.moderators+clusCounter); n.TIpred
    # scale Drift to cover changes in ctsem 3.4.1 (this would be for ctmaFit/ctmaModFit, but for Init individual study modification is done later)
    driftParamsTmp <- driftParams; driftParamsTmp
    diffParamsTmp  <- diffParams
    meanLag <- mean(allDeltas, na.rm=TRUE); meanLag
    #if ((meanLag > 6) | (customPar)) {
    if (customPar) {
      counter <- 0
      for (h in 1:(n.latent)) {
        for (j in 1:(n.latent)) {
          counter <- counter + 1
          if (h == j) {
            driftParamsTmp[counter] <- paste0(driftParamsTmp[counter], paste0("|-log1p_exp(-param *.1 -2)"))
            diffParamsTmp[counter] <- paste0(diffParamsTmp[counter], paste0("|log1p_exp(param *.1 +2)"))
          }
        }
      }
    }

    # Make model with max time points
    {
      if ((allInvModel == FALSE) & (indVarying == TRUE)) {
        print(paste0("#################################################################################"))
        print(paste0("######## Just a note: Individually varying intercepts model requested.  #########"))
        print(paste0("#################################################################################"))

        # change 9. Aug. 2022/16. Aug
        #manifestMeansParams <- manifestNames; manifestMeansParams
        #manifestMeansParams <- paste0("mean", manifestNames); manifestMeansParams

        ### added 9. Aug. 2022/16 Aug
        #T0meansParams <-  paste0("T0mean", manifestNames); T0meansParams

        # taken out 29 Sep 2022
        #manifestVarParams <- c()
        #for (u in 1:n.var) {
        #  for (v in 1:n.var) {
        #    if ( v < u) manifestVarParams <- c(manifestVarParams, "0") else manifestVarParams <- c(manifestVarParams, paste0("var_", v, "_", u))
        #  }
        #}
        ## make all 0 if only 1 manifest per latent
        #if (n.var == ncol(lambdaParams)) manifestVarParams <- 0

        stanctModel <- ctsem::ctModel(n.latent=n.latent, n.manifest=n.var, Tpoints=maxTpoints, manifestNames=manifestNames,
                                      DIFFUSION=matrix(diffParamsTmp, nrow=n.latent, ncol=n.latent), #, byrow=TRUE),
                                      DRIFT=matrix(driftParamsTmp, nrow=n.latent, ncol=n.latent),
                                      LAMBDA=lambdaParams,
                                      CINT=matrix(0, nrow=n.latent, ncol=1),
                                      # change 9. Aug. 2022/16. Aug
                                      #T0MEANS = matrix(c(0), nrow = n.latent, ncol = 1),
                                      #T0MEANS = matrix(c(T0meansParams), nrow = n.latent, ncol = 1),
                                      #MANIFESTMEANS = matrix(manifestMeansParams, nrow = n.var, ncol = 1),
                                      T0MEANS = "auto",
                                      MANIFESTMEANS = "auto",
                                      MANIFESTVAR=matrix(manifestVarsParams, nrow=n.var, ncol=n.var),
                                      type = 'stanct',
                                      n.TIpred = n.TIpred,
                                      TIpredNames = paste0("TI", 1:n.TIpred),
                                      #TIPREDEFFECT = matrix(0, n.latent, (n.studies-1+clusCounter+n.all.moderators))
                                      TIPREDEFFECT = matrix(0, n.latent, n.TIpred))
      } else {
        ### added 16. Aug. 2022/16 Aug
        #manifestMeansParams <- 0; manifestMeansParams
        #T0meansParams <-  0; T0meansParams

        stanctModel <- ctsem::ctModel(n.latent=n.latent, n.manifest=n.var, Tpoints=maxTpoints, manifestNames=manifestNames,
                                      DIFFUSION=matrix(diffParamsTmp, nrow=n.latent, ncol=n.latent), #, byrow=TRUE),
                                      DRIFT=matrix(driftParamsTmp, nrow=n.latent, ncol=n.latent),
                                      LAMBDA=lambdaParams,
                                      CINT=matrix(0, nrow=n.latent, ncol=1),
                                      # changed 9. Aug. 2022
                                      #T0MEANS = matrix(c(0), nrow = n.latent, ncol = 1),
                                      T0MEANS = matrix(c(T0meansParams), nrow = n.latent, ncol = 1),
                                      MANIFESTMEANS = matrix(manifestMeansParams, nrow = n.var, ncol = 1),
                                      MANIFESTVAR=matrix(manifestVarsParams, nrow=n.var, ncol=n.var),
                                      type = 'stanct',
                                      n.TIpred = n.TIpred,
                                      #TIpredNames = paste0("TI", 1:(n.studies-1+clusCounter+n.all.moderators)),
                                      TIpredNames = paste0("TI", 1:n.TIpred),
                                      #TIPREDEFFECT = matrix(0, n.latent, (n.studies-1+clusCounter+n.all.moderators
                                      TIPREDEFFECT = matrix(0, n.latent, n.TIpred))
        stanctModel$pars[, "indvarying"] <- FALSE
      }
    }
    #stanctModel$pars

    # general setting for params
    stanctModel$pars[stanctModel$pars$matrix %in% 'DRIFT',paste0(stanctModel$TIpredNames[1:(n.studies-1)],'_effect')] <- FALSE
    stanctModel$pars[stanctModel$pars$matrix %in% 'T0MEANS',paste0(stanctModel$TIpredNames,'_effect')] <- FALSE
    stanctModel$pars[stanctModel$pars$matrix %in% 'LAMBDA',paste0(stanctModel$TIpredNames,'_effect')] <- FALSE
    stanctModel$pars[stanctModel$pars$matrix %in% 'CINT',paste0(stanctModel$TIpredNames,'_effect')] <- FALSE
    stanctModel$pars[stanctModel$pars$matrix %in% 'MANIFESTMEANS',paste0(stanctModel$TIpredNames,'_effect')] <- FALSE
    stanctModel$pars[stanctModel$pars$matrix %in% 'MANIFESTVAR',paste0(stanctModel$TIpredNames,'_effect')] <- FALSE
    if (!(is.null(cluster))) {
      stanctModel$pars[stanctModel$pars$matrix %in% 'DRIFT',paste0(stanctModel$TIpredNames[(n.studies++n.all.moderators):(n.studies+n.all.moderators+clusCounter-1)],'_effect')] <- TRUE
    }

    if (n.moderators > 0) {
      tmp1 <- which(stanctModel$pars$matrix == "DRIFT"); tmp1
      tmp2 <- which((stanctModel$pars[tmp1, "param"] %in% moderatedDriftNames)); tmp2
      targetCols <- (n.studies):(n.studies-1+n.all.moderators); targetCols
      stanctModel$pars[ , paste0(stanctModel$TIpredNames[targetCols],'_effect')] <- FALSE
      stanctModel$pars[tmp1[tmp2] , paste0(stanctModel$TIpredNames[targetCols],'_effect')] <- TRUE

      # change model to allow for comparison of categories (-2ll is the only important result)
      if (!(is.null(catsToCompare))) {
        currentStartNumber <- modTIstartNum; currentStartNumber
        for (c1 in 1:modsToCompare) {
          #c1 <-1
          if (n.moderators > 1) {
            targetCols2 <- c()
            for (c3 in 1:length(unique.mod)) {
              targetCols2 <- c(targetCols2, currentStartNumber:(currentStartNumber+length(catsToCompare)-2))
              currentStartNumber <- currentStartNumber + length(unique.mod[[c3]]) - 1
            }
          } else {
            targetCols2 <- currentStartNumber:(currentStartNumber+length(catsToCompare)-2); targetCols2
          }
        }
        if (is.null(driftsToCompare)) driftsToCompare <- driftFullNames
        targetRows2 <- which(stanctModel$pars[, "param"] %in% driftsToCompare); targetRows2
        targetCols3 <- c()
        for (c4 in targetCols2) targetCols3 <- c(targetCols3, grep(c4, colnames(stanctModel$pars)))
        stanctModel$pars[targetRows2, targetCols3] <- FALSE
      }

    }

    # the target effects
    tmp1 <- which(stanctModel$pars$matrix == "DRIFT"); tmp1
    tmp2 <- which(stanctModel$pars[tmp1, "param"] %in% invariantDriftNames); tmp2
    stanctModel$pars[tmp1[tmp2], paste0(stanctModel$TIpredNames[1:(n.studies-1)],'_effect')] <- FALSE

    if (!(optimize)) {
      customPar <- FALSE
      if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","Attention!"))}
      tmp1a <- paste0(" Bayesian sampling was selected, which does require appropriate scaling of time. ")
      tmp2 <- nchar(tmp1a); tmp2
      tmp3 <- (81 - tmp2)/2; tmp3
      tmp4 <- strrep("#", round(tmp3 + 0.45, 0)); tmp4
      tmp5 <- strrep("#", round(tmp3 - 0.45, 0)); tmp5
      tmp6a <- paste0(tmp4, tmp1a, tmp5); tmp6

      tmp1b <- paste0(" See the end of the summary output ")
      tmp2 <- nchar(tmp1b); tmp2
      tmp3 <- (81 - tmp2)/2; tmp3
      tmp4 <- strrep("#", round(tmp3 + 0.45, 0)); tmp4
      tmp5 <- strrep("#", round(tmp3 - 0.45, 0)); tmp5
      tmp6b <- paste0(tmp4, tmp1b, tmp5); tmp6

      Msg <- paste0("################################################################################# \n", tmp6a, "\n", tmp6b, "\n#################################################################################")
      message(Msg)
      #Msg <- "Bayesian sampling was selected, which does require appropriate scaling of time. See the end of the summary output\n"
      #message(Msg)
    }

    fitStanctModel <- ctsem::ctStanFit(
      datalong = datalong_all,
      ctstanmodel = stanctModel,
      savesubjectmatrices=CoTiMAStanctArgs$savesubjectmatrices,
      stanmodeltext=CoTiMAStanctArgs$stanmodeltext,
      iter=CoTiMAStanctArgs$iter,
      intoverstates=CoTiMAStanctArgs$intoverstates,
      binomial=CoTiMAStanctArgs$binomial,
      fit=CoTiMAStanctArgs$fit,
      intoverpop=CoTiMAStanctArgs$intoverpop,
      stationary=CoTiMAStanctArgs$stationary,
      plot=CoTiMAStanctArgs$plot,
      derrind=CoTiMAStanctArgs$derrind,
      optimize=CoTiMAStanctArgs$optimize,
      optimcontrol=CoTiMAStanctArgs$optimcontrol,
      nlcontrol=CoTiMAStanctArgs$nlcontrol,
      nopriors=CoTiMAStanctArgs$nopriors,
      chains=CoTiMAStanctArgs$chains,
      forcerecompile=CoTiMAStanctArgs$forcerecompile,
      savescores=CoTiMAStanctArgs$savescores,
      gendata=CoTiMAStanctArgs$gendata,
      #control=CoTiMAStanctArgs$control,
      verbose=CoTiMAStanctArgs$verbose,
      warmup=CoTiMAStanctArgs$warmup,
      cores=coresToUse,
      inits=inits)

    ### resample in parcels to avoid memory crash and speed up
    if (!(is.null(CoTiMAStanctArgs$resample))) {
      fitStanctModel <- ctmaStanResample(ctmaFittedModel=fitStanctModel) #, CoTiMAStanctArgs=CoTiMAStanctArgs)
      #saveRDS(fitStanctModel, paste0(activeDirectory, "fitStanctModel.rds"))
    }
    invariantDriftStanctFit <- summary(fitStanctModel, digits=2*digits, parmatrices=TRUE, residualcov=FALSE)
  } # end if else (allInvModel)

  # Extract estimates & statistics
  # added 17. Aug. 2022
  if (indVarying == TRUE) {
    #model_popsd <- model_popcov <- model_popcor <- list()
    model_popsd <- invariantDriftStanctFit$popsd
    e <- ctsem::ctExtract(fitStanctModel)
    model_popcov_m <- round(ctsem::ctCollapse(e$popcov, 1, mean), digits = digits)
    model_popcov_sd <- round(ctsem::ctCollapse(e$popcov, 1, stats::sd), digits = digits)
    model_popcov_T <- round(ctsem::ctCollapse(e$popcov, 1, mean)/ctsem::ctCollapse(e$popcov, 1, stats::sd), digits)
    model_popcov_05 <- ctsem::ctCollapse(e$popcov, 1, function(x) stats::quantile(x, .05))
    model_popcov_50 <- ctsem::ctCollapse(e$popcov, 1, function(x) stats::quantile(x, .50))
    model_popcov_95 <- ctsem::ctCollapse(e$popcov, 1, function(x) stats::quantile(x, .95))
    # convert to correlations and do the same (array to list then list to array)
    e$popcor <- lapply(seq(dim(e$popcov)[1]), function(x) e$popcov[x , ,])
    e$popcor <- lapply(e$popcor, stats::cov2cor)
    e$popcor <- array(unlist(e$popcor), dim=c(n.latent*2, n.latent*2, length(e$popcor)))
    model_popcor_m <- round(ctsem::ctCollapse(e$popcor, 3, mean), digits = digits)
    model_popcor_sd <- round(ctsem::ctCollapse(e$popcor, 3, stats::sd), digits = digits)
    model_popcor_T <- round(ctsem::ctCollapse(e$popcor, 3, mean)/ctsem::ctCollapse(e$popcor, 3, stats::sd), digits)
    model_popcor_05 <- ctsem::ctCollapse(e$popcor, 3, function(x) stats::quantile(x, .05))
    model_popcor_50 <- ctsem::ctCollapse(e$popcor, 3, function(x) stats::quantile(x, .50))
    model_popcor_95 <- ctsem::ctCollapse(e$popcor, 3, function(x) stats::quantile(x, .95))
    #model_popcor <- stats::cov2cor(model_popcov_m)
  } else {
    model_popsd <- "no random effects estimated"
    model_popcov_m <- model_popcov_sd <- model_popcov_T <- model_popcov_05 <- model_popcov_50 <- model_popcov_95 <- "no random effects estimated"
    model_popcor_m <- model_popcor_sd <- model_popcor_T <- model_popcor_05 <- model_popcor_50 <- model_popcor_95 <- "no random effects estimated"
  }

  # account for changes in ctsem 3.4.1
  if ("matrix" %in% colnames(invariantDriftStanctFit$parmatrices)) ctsem341 <- TRUE else ctsem341 <- FALSE
  tmpMean <- grep("ean", colnames(invariantDriftStanctFit$parmatrices)); tmpMean
  tmpSd <- tmpMean+1; tmpSd
  Tvalues <- invariantDriftStanctFit$parmatrices[,tmpMean]/invariantDriftStanctFit$parmatrices[,tmpSd]; Tvalues
  invariantDrift_Coeff <- cbind(invariantDriftStanctFit$parmatrices, Tvalues); invariantDrift_Coeff
  invariantDrift_Coeff[, tmpMean:(dim(invariantDrift_Coeff)[2])] <- round(invariantDrift_Coeff[, tmpMean:(dim(invariantDrift_Coeff)[2])], digits); invariantDrift_Coeff

  # (create & ) re-label rownames
  {
    if (ctsem341) {
      tmp1 <- which(invariantDrift_Coeff[, "matrix"] == "DRIFT"); tmp1
      # replace row numbers by newly created names
      rownames(invariantDrift_Coeff) <- paste0(invariantDrift_Coeff[, c("matrix")], "_",
                                               invariantDrift_Coeff[, c("row")], "_",
                                               invariantDrift_Coeff[, c("col")])
    } else {
      tmp1 <- which(rownames(invariantDrift_Coeff) == "DRIFT")
    }
    rownames(invariantDrift_Coeff)[tmp1] <- driftFullNames; invariantDrift_Coeff
    # relabel if param is invariant
    tmp2 <- which(rownames(invariantDrift_Coeff) %in% invariantDriftNames); tmp2
    tmp3 <- paste0("DRIFT ", rownames(invariantDrift_Coeff)[tmp2] , " (invariant)"); tmp3
    rownames(invariantDrift_Coeff)[tmp2] <- tmp3; invariantDrift_Coeff
    # relabel if param is equal
    tmp2b <- which(rownames(invariantDrift_Coeff) %in% equalDriftNames); tmp2b
    if (length(tmp2b) > 0) {
      tmp3b <- paste0("DRIFT ", rownames(invariantDrift_Coeff)[tmp2b] , " (equal)"); tmp3b
      rownames(invariantDrift_Coeff)[tmp2b] <- tmp3b; invariantDrift_Coeff
    }

    tmp4 <- tmp1[which(!(tmp1 %in% tmp2))]; tmp4 # change to "DRIFT " for later extraction
    rownames(invariantDrift_Coeff)[tmp4] <- paste0("DRIFT ", driftFullNames[which(!(tmp1 %in% tmp2))]); invariantDrift_Coeff

    if (allInvModel == TRUE) {
      tmp5 <- (grep("DIFFUSIONcov", rownames(invariantDrift_Coeff))); tmp5
      tmp6 <- (grep("asymDIFFUSIONcov", rownames(invariantDrift_Coeff))); tmp6
      targetRows <- tmp5[which(!(tmp5 %in% tmp6))]; targetRows
      rownames(invariantDrift_Coeff)[targetRows] <- paste0(rownames(invariantDrift_Coeff)[targetRows], " (invariant)")

      targetRows <- (grep("T0cov", rownames(invariantDrift_Coeff))); targetRows
      rownames(invariantDrift_Coeff)[targetRows] <- paste0(rownames(invariantDrift_Coeff)[targetRows], " (invariant)")

      targetRows <- (grep("T0MEANS", rownames(invariantDrift_Coeff))); targetRows
      rownames(invariantDrift_Coeff)[targetRows] <- paste0(rownames(invariantDrift_Coeff)[targetRows], " (invariant)")

      targetRows <- (grep("MANIFESTMEANS", rownames(invariantDrift_Coeff))); targetRows
      rownames(invariantDrift_Coeff)[targetRows] <- paste0(rownames(invariantDrift_Coeff)[targetRows], " (invariant)")
    }

  }

  # extract fit
  invariantDrift_Minus2LogLikelihood  <- -2*invariantDriftStanctFit$loglik; invariantDrift_Minus2LogLikelihood
  invariantDrift_estimatedParameters  <- invariantDriftStanctFit$npars; invariantDrift_estimatedParameters
  invariantDrift_df <- "deprecated"

  # extract params
  {
    model_Drift_Coef <- invariantDrift_Coeff[(grep("DRIFT ", rownames(invariantDrift_Coeff))), tmpMean]; model_Drift_Coef
    tmp <- grep("DRIFT ", rownames(invariantDrift_Coeff)); tmp
    names(model_Drift_Coef) <- rownames(invariantDrift_Coeff)[tmp]; model_Drift_Coef


    model_Diffusion_Coef <- invariantDrift_Coeff[(rownames(invariantDrift_Coeff) == "DIFFUSIONcov"), tmpMean]; model_Diffusion_Coef
    if (length(model_Diffusion_Coef) < 1) {
      model_Diffusion_Coef <- invariantDrift_Coeff[invariantDrift_Coeff[, "matrix"] == "DIFFUSIONcov",  tmpMean]; model_Diffusion_Coef
    } else {
      model_Diffusion_Coef <- c(OpenMx::vech2full(model_Diffusion_Coef)); model_Diffusion_Coef
    }
    names(model_Diffusion_Coef) <- diffFullNames; model_Diffusion_Coef

    model_T0var_Coef <- invariantDrift_Coeff[(rownames(invariantDrift_Coeff) == "T0VAR"), 3]; model_T0var_Coef
    if (length(model_T0var_Coef) < 1) {
      model_T0var_Coef <- invariantDrift_Coeff[invariantDrift_Coeff[, "matrix"] == "T0cov",  tmpMean]; model_T0var_Coef
    } else {
      model_T0var_Coef <- c(OpenMx::vech2full(model_T0var_Coef)); model_T0var_Coef
    }
    names(model_T0var_Coef) <- driftFullNames; model_T0var_Coef
  }


  ## moderator effects
  modTI_Coeff <- NULL
  if (n.moderators > 0) {
    tmp1 <- c()
    for (i in modTIs) tmp1 <- c(tmp1, grep(i, rownames(invariantDriftStanctFit$tipreds)))
    Tvalues <- invariantDriftStanctFit$tipreds[tmp1, ][,6]; Tvalues
    modTI_Coeff <- round(cbind(invariantDriftStanctFit$tipreds[tmp1, ], Tvalues), digits); modTI_Coeff

    # re-label
    if (!(is.null(mod.names))) {
      if (mod.type == "cont") {
        counter <- 0
        for (i in modTIs) {
          counter <- counter + 1
          targetNamePart <- paste0("tip_", modTIs[counter]); targetNamePart
          rownames(modTI_Coeff) <- sub(targetNamePart, paste0(mod.names[counter], "_on_"), rownames(modTI_Coeff))
        }
      }

      if (mod.type == "cat") {
        counter <- 0
        modNameCounter <- 1
        for (j in modTIs) {
          #j <- modTIs[1]; j
          if (n.moderators == 1) unique.mod.tmp <- unique.mod else unique.mod.tmp <- unique.mod[[counter+1]]
          #modCatOrder <- unique.mod.tmp; modCatOrder
          if (!(is.null(catsToCompare))) {
            origCats <- c(unique.mod.tmp[1:2] + moderatorGroupsOffset, unique.mod.tmp[-c(1:2)]); origCats
          } else {
            origCats <- unique.mod.tmp
          }
          #if (!(is.null(catsToCompare))) {
          #  tmp1 <- which(1:length(unique.mod.tmp) %in% unique.mod.tmp); tmp1
          #  tmp2 <- which(!(1:length(unique.mod.tmp) %in% unique.mod.tmp)); tmp2
          #  modCatOrder <- c(tmp2, tmp1); modCatOrder
          #}
          #moderatorGroupsOffset
          #modTI_Coeff
          #unique.mod.tmp
          for (i in 1:(length(unique.mod.tmp)-1)) {
            #i <- 1
            counter <- counter + 1; counter
            current.mod.names <- mod.names[modNameCounter]; current.mod.names
            targetNamePart <- paste0("tip_", modTIs[i]); targetNamePart
            tmp1 <- grep(targetNamePart, rownames(modTI_Coeff)); tmp1
            #rownames(modTI_Coeff) <- sub(targetNamePart, paste0(modCatOrder[counter+1], ". smallest value (category) of ", mod.names[modNameCounter], "_on"), rownames(modTI_Coeff))
            #rownames(modTI_Coeff) <- sub(targetNamePart, paste0(modCatOrder[counter+1], "  (category value) of ", mod.names[modNameCounter], "_on"), rownames(modTI_Coeff))
            rownames(modTI_Coeff) <- sub(targetNamePart, paste0(origCats[counter+1], "  (category value) of ", mod.names[modNameCounter], "_on"), rownames(modTI_Coeff))
            #origCats[counter+1]
            #rownames(modTI_Coeff)
          }
          counter <- 0
          modNameCounter <- modNameCounter + 1
        }
      }
    }
    # eliminate z
    modTI_Coeff[, "z"] <- NULL; modTI_Coeff
  }

  ## cluster effects
  if (!(is.null(cluster))) {
    cluster.specific.effect <- matrix(NA, clusCounter, n.latent^2)
    tmp <- grep("DRIFT ", rownames(invariantDrift_Coeff)); tmp
    colnames(cluster.specific.effect) <- rownames(invariantDrift_Coeff)[tmp]; cluster.specific.effect
    rownames(cluster.specific.effect) <- paste0("Cluster No. ", seq(1,clusCounter, 1)); cluster.specific.effect
    tmp1 <- c()
    for (i in clusTIs) tmp1 <- c(tmp1, (grep(i, rownames(invariantDriftStanctFit$tipreds))))
    Tvalues <- invariantDriftStanctFit$tipreds[tmp1, ][,6]; Tvalues
    clusTI_Coeff <- round(cbind(invariantDriftStanctFit$tipreds[tmp1, ], Tvalues), digits); clusTI_Coeff
    # re-label
    targetCluster <- which(table(cluster) > 1); targetCluster
    for (i in 1:clusCounter) {
      targetNamePart <- paste0("tip_", clusTIs[i]); targetNamePart
      rownames(clusTI_Coeff) <- sub(targetNamePart, paste0("Cluster_", targetCluster[i], "_on_"), rownames(clusTI_Coeff))
      for (j in 1:length(driftNames)) {
        if (driftNames[j] != 0) {
          tmp0 <- driftNames[j]; tmp0
          tmp0 <- gsub(" \\(invariant\\)", "", tmp0); tmp0
          tmp1 <- grep(tmp0, rownames((clusTI_Coeff))); tmp1
          tmp2 <- grep(tmp0, names((model_Drift_Coef))); tmp2
          cluster.specific.effect[i,j] <- round(model_Drift_Coef[tmp2] + clusTI_Coeff[tmp1, 1] * cluster.weights[i, 2], digits)
        }
      }
    }
  } else {
    clusTI_Coeff <- NULL
  }

  if (ctsem341) invariantDrift_Coeff[, "matrix"] <- NULL

  ### Numerically compute Optimal Time lag sensu Dormann & Griffin (2015)
  if (ctsem341) {
    driftMatrix <- matrix(model_Drift_Coef, n.latent, n.latent, byrow=TRUE); driftMatrix # byrow set because order is different compared to mx model
  } else {
    driftMatrix <- matrix(model_Drift_Coef, n.latent, n.latent, byrow=FALSE); driftMatrix # byrow set because order is different compared to mx model
  }

  if (length(invariantDriftNames) == length(driftNames)) {
    OTL <- function(timeRange) {
      #OpenMx::expm(driftMatrix * timeRange)[targetRow, targetCol]
      OpenMx::expm(tmpDriftMatrix * timeRange)[targetRow, targetCol]}
  # use original time scale
    if (!(is.null(scaleTime))) {
      tmpDriftMatrix <- driftMatrix * scaleTime
    } else {
      tmpDriftMatrix <- driftMatrix
    }
    # loop through all cross effects
    tmp1 <- 0
    if (0 %in% usedTimeRange) tmp1 <- 1
    optimalCrossLag <- matrix(NA, n.latent, n.latent)
    maxCrossEffect <- matrix(NA, n.latent, n.latent)
    for (j in 1:n.latent) {
      for (h in 1:n.latent) {
        if (j != h) {
          #j<-1;h<-2
          targetRow <- j
          targetCol <- h
          #if (driftMatrix[j, h] != 0) { # an effect that is zero has no optimal lag
          if (tmpDriftMatrix[j, h] != 0) { # an effect that is zero has no optimal lag
            targetParameters <- sapply(usedTimeRange, OTL)
            maxCrossEffect[j,h] <- max(abs(targetParameters))
            optimalCrossLag[j,h] <- which(abs(targetParameters)==maxCrossEffect[j,h])*1 - tmp1 # first targetParam is calculated for lag=0
          } else {
            optimalCrossLag[j,h] <- NA
          }
        }
      }
    }
    maxCrossEffect <- round(maxCrossEffect, digits)
  } else {
    optimalCrossLag <- "Drift Matrix is only partially invariant. (Generalizable) optimal intervall cannot be computed."
    maxCrossEffect <- "Drift Matrix is only partially invariant. (Generalizable) Largest effects cannot be computed."
  }
  #} ## END  fit stanct model

  #######################################################################################################################

  end.time <- Sys.time()
  time.taken <- end.time - start.time
  st <- paste0("Computation started at: ", start.time); st
  et <- paste0("Computation ended at: ", end.time); et
  tt <- paste0("Computation lasted: ", round(time.taken, digits)); tt

  meanDeltas <- mean(ctmaInitFit$statisticsList$allDeltas, na.rm=TRUE); meanDeltas
  largeDelta <- which(ctmaInitFit$statisticsList$allDeltas >= meanDeltas); largeDelta
  tmp1 <- table(ctmaInitFit$statisticsList$allDeltas[largeDelta]); tmp1
  tmp2 <- which(names(tmp1) == (max(as.numeric(names(tmp1))))); tmp2
  suggestedScaleTime <- as.numeric(names(tmp1[tmp2])); suggestedScaleTime
  message <- c()
  if (meanDeltas > 3) {
    tmp2 <- paste0("Mean time interval was ", meanDeltas, "."); tmp2
    tmp3 <- paste0("scaleTime=1/", suggestedScaleTime); tmp3
    tmp4 <- paste0("It is recommended to fit the model again using the arguments ", tmp3, " and customPar=FALSE. "); tmp4
    message <- paste(tmp2, tmp4, "If the model fit (-2ll) is better (lower), continue using, e.g.,", tmp3, "in all subsequent models.", collapse="\n"); message
  }


  if (activateRPB==TRUE) {RPushbullet::pbPost("note", paste0("CoTiMA (",Sys.time(),")" ), paste0(Sys.info()[[4]], "\n","CoTiMA has finished!"))}

  if (ctsem341) { # new 8.7.2022
    tmp1 <- grep("CINT", invariantDriftStanctFit$parmatrices[,"matrix"]); tmp1
    tmp2 <- grep("asym", invariantDriftStanctFit$parmatrices[,"matrix"]); tmp2
    tmp3 <- grep("dt", invariantDriftStanctFit$parmatrices[,"matrix"]); tmp3
    tmp4 <- tmp1[(tmp1 %in% c(tmp2, tmp3)) == FALSE]; tmp4
    model_Cint_Coef <- invariantDriftStanctFit$parmatrices[tmp4, 4]; model_Cint_Coef
  } else {
    tmp1 <- grep("CINT", rownames(invariantDriftStanctFit$parmatrices)); tmp1
    tmp2 <- grep("asym", rownames(invariantDriftStanctFit$parmatrices)); tmp2
    tmp3 <- grep("dt", rownames(invariantDriftStanctFit$parmatrices)); tmp3
    tmp4 <- tmp1[(tmp1 %in% c(tmp2, tmp3)) == FALSE]; tmp4
    model_Cint_Coef <- invariantDriftStanctFit$parmatrices[tmp4, 3]; model_Cint_Coef
  }

  if (!(is.null(cluster))) {
    clus.effects=list(effects=clusTI_Coeff, weights=cluster.weights, sizes=cluster.sizes,
                      cluster.specific.effect=cluster.specific.effect, note=cluster.note)
  } else {
    clus.effects <- NULL
  }

  if (is.null(primaryStudyList)) primaryStudies <- ctmaInitFit$primaryStudyList else primaryStudies <- primaryStudyList

  if (is.null(scaleTime)) scaleTime2 <- 1 else scaleTime2 <- scaleTime

  if (!(is.null(scaleTime))) {
    model_Drift_Coef_original_time_scale <- model_Drift_Coef * scaleTime
    model_Diffusion_Coef_original_time_scale <- model_Diffusion_Coef * scaleTime
    if (!(is.null(modTI_Coeff))) {
      modTI_Coeff_original_time_scale <- modTI_Coeff * scaleTime
      modTI_Coeff_original_time_scale[, "Tvalues"] <- modTI_Coeff[, "Tvalues"]
    } else {
      modTI_Coeff_original_time_scale <- NULL
    }
    if (!(is.null(clusTI_Coeff))) {
      clusTI_Coeff_original_time_scale <- clusTI_Coeff * scaleTime
      clusTI_Coeff_original_time_scale[, "Tvalues"] <- clusTI_Coeff[, "Tvalues"]
    } else {
      clusTI_Coeff_original_time_scale <- NULL
    }
    tmp1<- invariantDrift_Coeff
    tmp2 <- grep("toV", rownames(tmp1))
    tmp3 <- grep("DIFFUSIONcov", rownames(tmp1))
    tmp4 <- grep("asym", rownames(tmp1))
    tmp3 <- tmp3[!(tmp3%in% tmp4)]
    tmp1 <- tmp1[c(tmp2, tmp3),]
    tmp1[, c("Mean", "sd", "2.5%", "50%", "97.5%")] <- tmp1[, c("Mean", "sd", "2.5%", "50%", "97.5%")] * scaleTime
    estimates_original_time_scale <- tmp1
    mod_effects_original_time_scale <- modTI_Coeff_original_time_scale
    if (!(is.null(clusTI_Coeff))) {
      clus_effects_original_time_scale <- clusTI_Coeff_original_time_scale
    } else {
      clus_effects_original_time_scale <- NULL
    }
  } else {
    model_Drift_Coef_original_time_scale <- model_Drift_Coef; model_Drift_Coef_original_time_scale
    model_Diffusion_Coef_original_time_scale <- model_Diffusion_Coef; model_Diffusion_Coef_original_time_scale
    if (!(is.null(modTI_Coeff))) { # new 9.7.20222
      modTI_Coeff_original_time_scale <- modTI_Coeff
      mod_effects_original_time_scale <- modTI_Coeff # doubled (why?)
    } else {
      modTI_Coeff_original_time_scale <- NULL
      mod_effects_original_time_scale <- NULL
    }

    { # new 8.7.2022
      tmp1<- invariantDrift_Coeff
      tmp2 <- grep("toV", rownames(tmp1))
      tmp3 <- grep("DIFFUSIONcov", rownames(tmp1))
      tmp4 <- grep("asym", rownames(tmp1))
      tmp3 <- tmp3[!(tmp3%in% tmp4)]
      tmp1 <- tmp1[c(tmp2, tmp3),]
      tmp1[, c("Mean", "sd", "2.5%", "50%", "97.5%")] <- tmp1[, c("Mean", "sd", "2.5%", "50%", "97.5%")] * 1
      estimates_original_time_scale <- tmp1
    }
    #mod_effects_original_time_scale <- NULL         # new 9.7.2022
    #modTI_Coeff_original_time_scale <- modTI_Coeff  # new 9.7.2022
    clus_effects_original_time_scale <- NULL
    if (!(is.null(clusTI_Coeff))) {
      clusTI_Coeff_original_time_scale <- clusTI_Coeff
    } else {
      clusTI_Coeff_original_time_scale <- NULL
    }
    #estimates_original_time_scale <- NULL
    #estimates_original_time_scale
    #modTI_Coeff_original_time_scale
  }

  results <- list(#activeDirectory=activeDirectory,
                  plot.type="drift",  model.type="stanct",
                  #coresToUse=coresToUse,
                  n.studies=1,
                  n.latent=n.latent,
                  n.moderators=length(mod.number),
                  #mod.names=mod.names,
                  #mod.type=mod.type,
                  #primaryStudyList=primaryStudies,
                  studyList=ctmaInitFit$studyList,
                  studyFitList=fitStanctModel,
                  data=datalong_all,
                  statisticsList=ctmaInitFit$statisticsList,
                  argumentList=list(ctmaInitFit=deparse(substitute(ctmaInitFit)),
                                    #primaryStudyList=deparse(substitute(primaryStudyList)),
                                    primaryStudyList=primaryStudies,
                                    cluster=cluster,
                                    activeDirectory=activeDirectory,
                                    activateRPB=activateRPB,
                                    digits=digits,
                                    drift=drift,
                                    invariantDrift=invariantDrift,
                                    moderatedDrift=moderatedDrift,
                                    equalDrift=equalDrift,
                                    mod.number=mod.number,
                                    mod.type=mod.type,
                                    mod.names=mod.names,
                                    indVarying=indVarying,
                                    coresToUse=coresToUse,
                                    scaleTI=scaleTI,
                                    scaleMod=scaleMod,
                                    transfMod=transfMod,
                                    scaleClus=scaleClus,
                                    scaleTime=scaleTime,
                                    optimize=optimize,
                                    nopriors=nopriors,
                                    finishsamples=finishsamples,
                                    iter=iter,
                                    chains=chains,
                                    verbose=verbose,
                                    allInvModel=allInvModel,
                                    customPar=customPar,
                                    inits=inits,
                                    modsToCompare=modsToCompare,
                                    catsToCompare=catsToCompare,
                                    driftsToCompare=driftsToCompare,
                                    useSampleFraction=useSampleFraction,
                                    T0means=T0means,
                                    manifestMeans=manifestMeans,
                                    CoTiMAStanctArgs=CoTiMAStanctArgs),
                  modelResults=list(DRIFToriginal_time_scale=model_Drift_Coef_original_time_scale,
                                    DIFFUSIONoriginal_time_scale=model_Diffusion_Coef_original_time_scale,
                                    T0VAR=model_T0var_Coef,
                                    CINT=model_Cint_Coef,
                                    MOD=modTI_Coeff_original_time_scale,
                                    CLUS=clusTI_Coeff_original_time_scale,
                                    DRIFT=model_Drift_Coef, DIFFUSION=model_Diffusion_Coef),
                  parameterNames=ctmaInitFit$parameterNames,
                  CoTiMAStanctArgs=CoTiMAStanctArgs,
                  invariantDrift=invariantDrift,
                  summary=list(model="Model name not specified", # paste(invariantDrift, "unequal but invariant across samples", collapse=" "),
                               scaledTime=scaleTime2,
                               estimates=invariantDrift_Coeff,
                               # changed 17. Aug. 2022
                               #randomEffects=invariantDriftStanctFit$popsd,
                               randomEffects=list(popsd=model_popsd,
                                                  popcov_mean=model_popcov_m, model_popcov_sd=model_popcov_sd,
                                                  model_popcov_T=model_popcov_T, model_popcov_05=model_popcov_05,
                                                  model_popcov_50=model_popcov_50, model_popcov_95=model_popcov_95,
                                                  popcor_mean=model_popcor_m, model_popcor_sd=model_popcor_sd,
                                                  model_popcor_T=model_popcor_T, model_popcor_05=model_popcor_05,
                                                  model_popcor_50=model_popcor_50, model_popcor_95=model_popcor_95),
                               minus2ll= invariantDrift_Minus2LogLikelihood,
                               n.parameters = invariantDrift_estimatedParameters,
                               #df= invariantDrift_df,
                               optimalLagInfo = "Optimal lag and effect was calculated for original time scale (i.e., ignoring possible scaleTime argument).",
                               opt.lag = optimalCrossLag,
                               max.effects = maxCrossEffect,
                               clus.effects=clus.effects,
                               mod.effects=modTI_Coeff,
                               message=message,
                               estimates_original_time_scale =estimates_original_time_scale,
                               mod_effects_original_time_scale=mod_effects_original_time_scale,
                               clus_effects_original_time_scale=clus_effects_original_time_scale)
                  # excel workbook is added later
  )

  class(results) <- "CoTiMAFit"

  ### prepare Excel Workbook with several sheets ################################################################
  {
    wb <- openxlsx::createWorkbook()
    sheet1 <- openxlsx::addWorksheet(wb, sheetName="model")
    sheet2 <- openxlsx::addWorksheet(wb, sheetName="modelResults")
    sheet3 <- openxlsx::addWorksheet(wb, sheetName="estimates")
    sheet7 <- openxlsx::addWorksheet(wb, sheetName="mod.effects")
    sheet4 <- openxlsx::addWorksheet(wb, sheetName="clus.effects")
    sheet5 <- openxlsx::addWorksheet(wb, sheetName="randomEffects")
    sheet6 <- openxlsx::addWorksheet(wb, sheetName="stats")
    openxlsx::writeData(wb, sheet1, results$summary$model)

    ### modelResults
    # DRIFT
    startCol <- 2; startCol
    startRow <- 1; startRow
    openxlsx::writeData(wb, sheet2, startCol=startCol, startRow = startRow, matrix(driftNames, nrow=1), colNames = FALSE)
    startCol <- 2; startCol
    startRow <- 2; startRow
    openxlsx::writeData(wb, sheet2, startCol=startCol, startRow = startRow, colNames = FALSE,
                        t(c(t(matrix(unlist(results$modelResults$DRIFT),
                                     nrow=n.latent, ncol=n.latent)))))
    startCol <- 1; startCol
    startRow <- 2; startRow
    openxlsx::writeData(wb, sheet2, startCol=startCol, startRow = startRow, colNames = FALSE,
                        matrix("Aggregated Drift Effects", ncol=1))
    # DIFFUSION
    offset <-  1 + 1
    startCol <- 2; startCol
    startRow <- 2 + offset; startRow
    openxlsx::writeData(wb, sheet2, startCol=startCol, startRow = startRow,
                        matrix(names(results$modelResults$DIFFUSION), nrow=1), colNames = FALSE)
    startCol <- 2; startCol
    startRow <- 2 + offset + 1# offset; startRow
    openxlsx::writeData(wb, sheet2, startCol=startCol, startRow = startRow, colNames = FALSE,
                        matrix(results$modelResults$DIFFUSION,
                               nrow=1, byrow=TRUE))
    results$modelResults$DIFFUSION
    startCol <- 1; startCol
    startRow <- 2 + offset + 1; startRow
    openxlsx::writeData(wb, sheet2, startCol=startCol, startRow = startRow, colNames = FALSE,
                        matrix("Diffusion Population Means (not aggregated)", nrow=1, byrow=TRUE))
    # T0Var
    offset <-  offset + 1 + 2
    startCol <- 2; startCol
    startRow <- 2 + offset; startRow
    openxlsx::writeData(wb, sheet2, startCol=startCol, startRow = startRow,
                        matrix(names(results$modelResults$T0VAR), nrow=1), colNames = FALSE)
    startCol <- 2; startCol
    startRow <- 2 + offset + 1# offset; startRow
    openxlsx::writeData(wb, sheet2, startCol=startCol, startRow = startRow, colNames = FALSE,
                        matrix(unlist(results$modelResults$T0VAR), nrow=1, byrow=TRUE))
    startCol <- 1; startCol
    startRow <- 2 + offset + 1; startRow
    openxlsx::writeData(wb, sheet2, startCol=startCol, startRow = startRow, colNames = FALSE,
                        matrix("T0VAR Population Mean (not aggregated)", nrow=1, byrow=TRUE))
    ### estimates
    startCol <- 2; startCol
    startRow <- 1; startRow
    openxlsx::writeData(wb, sheet3, startCol=startCol, startRow = startRow + 1,
                        rownames(results$summary$estimates), colNames = FALSE)
    openxlsx::writeData(wb, sheet3, startCol=startCol+1, startRow = startRow, results$summary$estimates,
                        colNames = TRUE)
    ### moderator Effects
    startCol <- 1; startCol
    startRow <- 1; startRow
    results$summary$mod.effects
    openxlsx::writeData(wb, sheet7, startCol=startCol, startRow = startRow + 1,
                        results$summary$mod.effects, colNames = TRUE, rowNames = TRUE)

    ### cluster Effects
    if (!(is.null(cluster))) {
      startCol <- 2; startCol
      startRow <- 1; startRow
      tmp1 <- dim(results$summary$clus.effects$effects); tmp1
      openxlsx::writeData(wb, sheet4, startCol=startCol, startRow = startRow + 1,
                          rownames(results$summary$clus.effects$effects), colNames = FALSE)
      openxlsx::writeData(wb, sheet4, startCol=startCol+1, startRow = startRow, results$summary$clus.effects$effects,
                          colNames = TRUE)
      openxlsx::writeData(wb, sheet4, startCol=startCol+1, startRow = startRow+tmp1[1] + 2,
                          results$summary$clus.effects$cluster.specific.effect,
                          colNames = TRUE, rowNames = TRUE)
    }
    ### random Effects
    startCol <- 2; startCol
    startRow <- 1; startRow
    openxlsx::writeData(wb, sheet5, startCol=startCol, startRow = startRow, results$summary$randomEffects, colNames = FALSE)
    ### stats
    startCol <- 2; startCol
    startRow <- 1; startRow
    tmp <- cbind("-2ll = ", results$summary$minus2ll, "Number of Parameters = ", results$summary$n.parameters)
    openxlsx::writeData(wb, sheet6, startCol=startCol, startRow = startRow, t(tmp), colNames = FALSE)
  }

  results$excelSheets <- wb

  invisible(results)

} ### END function definition

Try the CoTiMA package in your browser

Any scripts or data that you put into this service are public.

CoTiMA documentation built on Nov. 10, 2022, 5:16 p.m.