R/lav_print.R

Defines functions lav_print_psi lav_print_loadings print.lavaan.summary lav_txt2message print.lavaan.fsr summary.lavaan.fsr .makeConNames .makeNames print.lavaan.parameterEstimates print.lavaan.character print.lavaan.vector print.lavaan.matrix print.lavaan.matrix.symmetric print.lavaan.list print.lavaan.data.frame

## NOTE:
## round(1.2355, 3) = 1.236
## but
## round(1.2345, 3) = 1.234
##
## perhaps we should add 0.0005 or something to avoid this?

print.lavaan.data.frame <- function(x, ..., nd = 3L) {
  ROW.NAMES <- rownames(x)
  y <- as.data.frame(lapply(x, function(x) {
    if (is.numeric(x)) round(x, nd) else x
  }))
  rownames(y) <- ROW.NAMES

  if (!is.null(attr(x, "header"))) {
    cat("\n", attr(x, "header"), "\n\n", sep = "")
  }

  print(y, ...)

  if (!is.null(attr(x, "footer"))) {
    cat("\n", attr(x, "footer"), "\n\n", sep = "")
  }

  invisible(x)
}

print.lavaan.list <- function(x, ...) {
  y <- unclass(x)
  attr(y, "header") <- NULL

  header <- attr(x, "header")
  if (!is.null(header)) {
    if (is.character(header)) {
      cat("\n", header, "\n\n", sep = "")
    } else {
      print(header)
      cat("\n")
    }
  }

  print(y, ...)
  invisible(x)
}


# prints only lower triangle of a symmetric matrix
print.lavaan.matrix.symmetric <- function(x, ..., nd = 3L, shift = 0L,
                                          diag.na.dot = TRUE) {
  # print only lower triangle of a symmetric matrix
  # this function was inspired by the `print.correlation' function
  # in package nlme
  x <- as.matrix(x) # just in case
  y <- x
  y <- unclass(y)
  attributes(y)[c("header", "footer")] <- NULL
  ll <- lower.tri(x, diag = TRUE)
  y[ll] <- format(round(x[ll], digits = nd))
  y[!ll] <- ""
  if (diag.na.dot) {
    # print a "." instead of NA on the main diagonal (eg summary.efaList)
    diag.idx <- lav_matrix_diag_idx(ncol(x))
    tmp <- x[diag.idx]
    if (all(is.na(tmp))) {
      y[diag.idx] <- paste(strrep(" ", nd + 2L), ".", sep = "")
    }
  }
  if (!is.null(colnames(x))) {
    colnames(y) <- abbreviate(colnames(x), minlength = nd + 3L)
  }
  if (shift > 0L) {
    empty.string <- rep(strrep(x = " ", times = shift), times = nrow(x))
    if (!is.null(rownames(x))) {
      rownames(y) <- paste(empty.string, rownames(x), sep = "")
    } else {
      rownames(y) <- empty.string
    }
  }

  if (!is.null(attr(x, "header"))) {
    cat("\n", attr(x, "header"), "\n\n", sep = "")
  }

  print(y, ..., quote = FALSE, right = TRUE)

  if (!is.null(attr(x, "footer"))) {
    cat("\n", attr(x, "footer"), "\n\n", sep = "")
  }

  invisible(x)
}


print.lavaan.matrix <- function(x, ..., nd = 3L, shift = 0L) {
  x <- as.matrix(x) # just in case
  y <- unclass(x)
  attributes(y)[c("header", "footer")] <- NULL
  if (!is.null(colnames(x))) {
    colnames(y) <- abbreviate(colnames(x), minlength = nd + 3L)
  }
  if (shift > 0L) {
    empty.string <- rep(strrep(x = " ", times = shift), times = nrow(x))
    if (!is.null(rownames(x))) {
      rownames(y) <- paste(empty.string, rownames(x), sep = "")
    } else {
      rownames(y) <- empty.string
    }
  }
  if (!is.null(attr(x, "header"))) {
    cat("\n", attr(x, "header"), "\n\n", sep = "")
  }

  print(round(y, nd), right = TRUE, ...)

  if (!is.null(attr(x, "footer"))) {
    cat("\n", attr(x, "footer"), "\n\n", sep = "")
  }

  invisible(x)
}

print.lavaan.vector <- function(x, ..., nd = 3L, shift = 0L) {
  y <- unclass(x)
  attributes(y)[c("header", "footer")] <- NULL
  # if(!is.null(names(x))) {
  #    names(y) <- abbreviate(names(x), minlength = nd + 3)
  # }
  if (!is.null(attr(x, "header"))) {
    cat("\n", attr(x, "header"), "\n\n", sep = "")
  }

  if (shift > 0L) {
    empty.string <- strrep(x = " ", times = shift)
    tmp <- format(y, digits = nd, width = 2L + nd)
    tmp[1] <- paste(empty.string, tmp[1], sep = "")
    print(tmp, quote = FALSE, ...)
  } else {
    print(round(y, nd), right = TRUE, ...)
  }

  if (!is.null(attr(x, "footer"))) {
    cat("\n", attr(x, "footer"), "\n\n", sep = "")
  }

  invisible(x)
}

print.lavaan.character <- function(x, ...) {
  cat(x)
  invisible(x)
}

print.lavaan.parameterEstimates <- function(x, ..., nd = 3L) {
  # format for numeric values
  num.format <- paste("%", max(8L, nd + 5L), ".", nd, "f", sep = "")
  int.format <- paste("%", max(8L, nd + 5L), "d", sep = "")
  char.format <- paste("%", max(8L, nd + 5L), "s", sep = "")

  # output sections
  GSECTIONS <- c(
    "Latent Variables",
    "Composites",
    "Regressions",
    "Covariances",
    "Intercepts",
    "Thresholds",
    "Variances",
    "Scales y*",
    "Group Weight",
    "R-Square"
  )
  ASECTIONS <- c(
    "Defined Parameters",
    "Constraints"
  )

  # header?
  header <- attr(x, "header")
  if (is.null(header)) {
    header <- FALSE
  }

  if (header) {
    cat("\nParameter Estimates:\n\n")

    # info about parameterization (if categorical only)
    categorical.flag <- attr(x, "categorical")
    if (categorical.flag) {
      # container
      c1 <- c2 <- character(0L)

      # which parameterization?
      c1 <- c(c1, "Parameterization")
      tmp.txt <- attr(x, "parameterization")
      c2 <- c(c2, paste(toupper(substring(tmp.txt, 1, 1)),
        substring(tmp.txt, 2),
        sep = ""
      ))

      # format c1/c2
      c1 <- format(c1, width = 38L)
      c2 <- format(c2,
        width = 13L + max(0, (nd - 3L)) * 4L, justify = "right"
      )

      # create character matrix
      M <- cbind(c1, c2, deparse.level = 0)
      colnames(M) <- rep("", ncol(M))
      rownames(M) <- rep(" ", nrow(M))

      # print
      write.table(M, row.names = TRUE, col.names = FALSE, quote = FALSE)
    }

    # info about standard errors (if we have x$se only)
    # 1. information
    # 2. se
    # 3. bootstrap requested/successful draws
    if (!is.null(x$se)) {
      # container
      c1 <- c2 <- character(0L)

      # which type of standard errors?
      c1 <- c(c1, "Standard errors")
      if (attr(x, "se") == "robust.huber.white") {
        tmp.txt <- "sandwich" # since 0.6-6
      } else {
        tmp.txt <- attr(x, "se")
      }
      c2 <- c(c2, paste(toupper(substring(tmp.txt, 1, 1)),
        substring(tmp.txt, 2),
        sep = ""
      ))
      # information
      if (attr(x, "se") != "bootstrap") {
        # type for information
        if (attr(x, "se") == "robust.huber.white") {
          c1 <- c(c1, "Information bread")
        } else {
          c1 <- c(c1, "Information")
        }
        tmp.txt <- attr(x, "information")
        c2 <- c(c2, paste(toupper(substring(tmp.txt, 1, 1)),
          substring(tmp.txt, 2),
          sep = ""
        ))

        # if observed, which type? (hessian of h1)
        if (attr(x, "information") == "observed") {
          c1 <- c(c1, "Observed information based on")
          tmp.txt <- attr(x, "observed.information")
          c2 <- c(c2, paste(toupper(substring(tmp.txt, 1, 1)),
            substring(tmp.txt, 2),
            sep = ""
          ))
        }

        # if h1 is involved, structured or unstructured?
        if (attr(x, "information") %in% c("expected", "first.order") ||
          attr(x, "observed.information") == "h1") {
          if (attr(x, "se") == "robust.huber.white" &&
            attr(x, "h1.information") !=
              attr(x, "h1.information.meat")) {
            c1 <- c(c1, "Information bread saturated (h1) model")
          } else {
            c1 <- c(c1, "Information saturated (h1) model")
          }
          tmp.txt <- attr(x, "h1.information")
          c2 <- c(c2, paste(toupper(substring(tmp.txt, 1, 1)),
            substring(tmp.txt, 2),
            sep = ""
          ))
        }

        # if sandwich, which information for the meat? (first.order)
        # only print if it is NOT first.order
        if (attr(x, "se") == "robust.huber.white" &&
          attr(x, "information.meat") != "first.order") {
          c1 <- c(c1, "Information meat")
          tmp.txt <- attr(x, "information.meat")
          c2 <- c(c2, paste(toupper(substring(tmp.txt, 1, 1)),
            substring(tmp.txt, 2),
            sep = ""
          ))
        }

        # if sandwich, structured or unstructured for the meat?
        # only print if it is not the same as h1.information
        if (attr(x, "se") == "robust.huber.white" &&
          attr(x, "h1.information.meat") !=
            attr(x, "h1.information")) {
          c1 <- c(c1, "Information meat saturated (h1) model")
          tmp.txt <- attr(x, "h1.information.meat")
          c2 <- c(c2, paste(toupper(substring(tmp.txt, 1, 1)),
            substring(tmp.txt, 2),
            sep = ""
          ))
        }
      } # no bootstrap

      #TDJ: Pooling options for lavaan.mi-class objects (which NEVER bootstrap)
      if (isTRUE(attr(x, "pooled"))) {
        ## add an empty element for a space before pooling section
        c1 <- c(c1, "", "Pooled across imputations")
        c2 <- c(c2, "", "Rubin's (1987) rules")
      }
      if (!is.null(attr(x, "scale.W"))) {
        c1 <- c(c1, "Augment within-imputation variance")
        if (attr(x, "scale.W")) {
          c2 <- c(c2, "Scale by average RIV")
        } else {
          c2 <- c(c2, "Add between component")
        }
      }
      if (!is.null(attr(x, "asymptotic"))) {
        c1 <- c(c1, "Wald test for pooled parameters")
        if (attr(x, "asymptotic")) {
          c2 <- c(c2, "Normal (z) distribution")
        } else {
          c2 <- c(c2, "t(df) distribution")
        }
      }

      # 4.
      if (attr(x, "se") == "bootstrap" && !is.null(attr(x, "bootstrap"))) {
        c1 <- c(c1, "Number of requested bootstrap draws")
        c2 <- c(c2, attr(x, "bootstrap"))
        c1 <- c(c1, "Number of successful bootstrap draws")
        c2 <- c(c2, attr(x, "bootstrap.successful"))
      }

      # format c1/c2
      c1 <- format(c1, width = 38L)
      c2 <- format(c2,
        width = 13L + max(0, (nd - 3L)) * 4L, justify = "right"
      )

      # create character matrix
      M <- cbind(c1, c2, deparse.level = 0)
      colnames(M) <- rep("", ncol(M))
      rownames(M) <- rep(" ", nrow(M))

      # print
      write.table(M, row.names = TRUE, col.names = FALSE, quote = FALSE)

      #TDJ: Message for lavaan.mi-class objects when df > 1000 for t test
      if (isTRUE(attr(x, "infDF"))) {
        cat(c("\n  Pooled t statistics with df >= 1000 are displayed with",
              "\n  df = Inf(inity) to save space. Although the t distribution",
              "\n  with large df closely approximates a standard normal",
              "\n  distribution, exact df for reporting these t tests can be",
              "\n  obtained from parameterEstimates.mi() \n\n"), sep = "")
      }

    }
  }

  # number of groups
  if (is.null(x$group)) {
    ngroups <- 1L
    x$group <- rep(1L, length(x$lhs))
  } else {
    ngroups <- lav_partable_ngroups(x)
  }

  # number of levels
  if (is.null(x$level)) {
    nlevels <- 1L
    x$level <- rep(1L, length(x$lhs))
  } else {
    nlevels <- lav_partable_nlevels(x)
  }

  # block column
  if (is.null(x$block)) {
    x$block <- rep(1L, length(x$lhs))
  }

  # step column (SAM)
  # if(!is.null(x$step)) {
  #    tmp.LABEL <- rep("", length(x$lhs))
  #    p1.idx <- which(x$step == 1L)
  #    p2.idx <- which(x$step == 2L)
  #    tmp.LABEL[p1.idx] <- "1"
  #    tmp.LABEL[p2.idx] <- "2"
  #
  #    if(is.null(x$label)) {
  #        x$label <- tmp.LABEL
  #    } else {
  #        x$label <- paste(x$label, tmp.LABEL, sep = "")
  #    }
  #
  #    x$step <- NULL
  # }

  # round to 3 digits after the decimal point
  y <- as.data.frame(
    lapply(x, function(x) {
      if (is.integer(x)) {
        sprintf(int.format, x)
      } else if (is.numeric(x)) {
        sprintf(num.format, x)
      } else {
        x
      }
    }),
    stringsAsFactors = FALSE
  )

  # always remove /block/level/group/op/rhs/label/exo columns
  y$op <- y$group <- y$rhs <- y$label <- y$exo <- NULL
  y$block <- y$level <- NULL
  y$efa <- NULL

  # if standardized, remove std.nox column (space reasons only)
  # unless, std.all is already removed
  if (!is.null(y$std.all)) {
    y$std.nox <- NULL
  }

  # convert to character matrix
  m <- as.matrix(format.data.frame(y,
    na.encode = FALSE,
    justify = "right"
  ))

  # use empty row names
  rownames(m) <- rep("", nrow(m))

  # handle se == 0.0
  if (!is.null(x$se)) {
    se.idx <- which(x$se == 0)
    if (length(se.idx) > 0L) {
      m[se.idx, "se"] <- ""
      if (!is.null(x$z)) {
        m[se.idx, "z"] <- ""
      }
      if (!is.null(x$pvalue)) {
        m[se.idx, "pvalue"] <- ""
      }
      ## for lavaan.mi-class objects (semTools)
      if (!is.null(x$t)) {
        m[se.idx, "t"] <- ""
      }
      if (!is.null(x$df)) {
        m[se.idx, "df"] <- ""
      }
    }

    # handle se == NA
    se.idx <- which(is.na(x$se))
    if (length(se.idx) > 0L) {
      if (!is.null(x$z)) {
        m[se.idx, "z"] <- ""
      }
      if (!is.null(x$pvalue)) {
        m[se.idx, "pvalue"] <- ""
      }
      ## for lavaan.mi-class objects (semTools)
      if (!is.null(x$t)) {
        m[se.idx, "t"] <- ""
      }
      if (!is.null(x$df)) {
        m[se.idx, "df"] <- ""
      }
    }
  }

  # handle lower/upper boundary points
  if (!is.null(x$lower)) {
    b.idx <- which(abs(x$lower - x$est) < sqrt(.Machine$double.eps) &
      (is.na(x$se) | (is.finite(x$se) & x$se != 0.0)))
    if (length(b.idx) > 0L && !is.null(x$pvalue)) {
      m[b.idx, "pvalue"] <- ""
      if (is.null(x$label)) {
        x$label <- rep("", length(x$lhs))
      }
      x$label[b.idx] <- ifelse(nchar(x$label[b.idx]) > 0L,
        paste(x$label[b.idx], "+lb", sep = ""),
        "lb"
      )
    }
    # remove lower column
    m <- m[, colnames(m) != "lower"]
  }
  if (!is.null(x$upper)) {
    b.idx <- which(abs(x$upper - x$est) < sqrt(.Machine$double.eps) &
      is.finite(x$se) & x$se != 0.0)
    if (length(b.idx) > 0L && !is.null(x$pvalue)) {
      m[b.idx, "pvalue"] <- ""
      if (is.null(x$label)) {
        x$label <- rep("", length(x$lhs))
      }
      x$label[b.idx] <- ifelse(nchar(x$label[b.idx]) > 0L,
        paste(x$label[b.idx], "+ub", sep = ""),
        "ub"
      )
    }
    # remove upper column
    m <- m[, colnames(m) != "upper"]
  }


  # handle fmi
  if (!is.null(x$fmi)) {
    se.idx <- which(x$se == 0)
    if (length(se.idx) > 0L) {
      m[se.idx, "fmi"] <- ""
      ## for lavaan.mi-class objects (semTools)
      if (!is.null(x$riv)) m[se.idx, "riv"] <- ""
    }

    not.idx <- which(x$op %in% c(":=", "<", ">", "=="))
    if (length(not.idx) > 0L) {
      if (!is.null(x$fmi)) {
        m[not.idx, "fmi"] <- ""
        ## for lavaan.mi-class objects (semTools)
        if (!is.null(x$riv)) m[not.idx, "riv"] <- ""
      }
    }
  }

  # for blavaan, handle Post.SD and PSRF
  if (!is.null(x$Post.SD)) {
    se.idx <- which(x$Post.SD == 0)
    if (length(se.idx) > 0L) {
      m[se.idx, "Post.SD"] <- ""
      if (!is.null(x$psrf)) {
        m[se.idx, "psrf"] <- ""
      }
      if (!is.null(x$PSRF)) {
        m[se.idx, "PSRF"] <- ""
      }
    }

    # handle psrf for defined parameters
    not.idx <- which(x$op %in% c(":=", "<", ">", "=="))
    if (length(not.idx) > 0L) {
      if (!is.null(x$psrf)) {
        m[not.idx, "psrf"] <- ""
      }
      if (!is.null(x$PSRF)) {
        m[not.idx, "PSRF"] <- ""
      }
    }
  }

  # rename some column names
  colnames(m)[colnames(m) == "lhs"] <- ""
  colnames(m)[colnames(m) == "op"] <- ""
  colnames(m)[colnames(m) == "rhs"] <- ""
  colnames(m)[colnames(m) == "step"] <- "Step"
  colnames(m)[colnames(m) == "est"] <- "Estimate"
  colnames(m)[colnames(m) == "se"] <- "Std.Err"
  colnames(m)[colnames(m) == "z"] <- "z-value"
  colnames(m)[colnames(m) == "pvalue"] <- "P(>|z|)"
  colnames(m)[colnames(m) == "std.lv"] <- "Std.lv"
  colnames(m)[colnames(m) == "std.all"] <- "Std.all"
  colnames(m)[colnames(m) == "std.nox"] <- "Std.nox"
  colnames(m)[colnames(m) == "prior"] <- "Prior"
  colnames(m)[colnames(m) == "fmi"] <- "FMI"
  ## for lavaan.mi-class objects (semTools)
  if ("t" %in% colnames(m)) {
    colnames(m)[colnames(m) == "t"] <- "t-value"
    colnames(m)[colnames(m) == "P(>|z|)"] <- "P(>|t|)"
    colnames(m)[colnames(m) == "riv"] <- "RIV"
  }

  # format column names
  colnames(m) <- sprintf(char.format, colnames(m))

  # exceptions for blavaan: Post.Mean (width = 9), Prior (width = 14)
  if (!is.null(x$Post.Mean)) {
    tmp <- gsub("[ \t]+", "", colnames(m), perl = TRUE)

    # reformat "Post.Mean" column
    col.idx <- which(tmp == "Post.Mean")
    if (length(col.idx) > 0L) {
      tmp.format <- paste("%", max(9, nd + 5), "s", sep = "")
      colnames(m)[col.idx] <- sprintf(tmp.format, colnames(m)[col.idx])
      m[, col.idx] <- sprintf(tmp.format, m[, col.idx])
    }

    # reformat "Prior" column
    col.idx <- which(tmp == "Prior")
    if (length(col.idx) > 0L) {
      MAX <- max(nchar(m[, col.idx])) + 1L
      tmp.format <- paste("%", max(MAX, nd + 5), "s", sep = "")
      colnames(m)[col.idx] <- sprintf(tmp.format, colnames(m)[col.idx])
      m[, col.idx] <- sprintf(tmp.format, m[, col.idx])
    }
  }

  b <- 0L
  # group-specific sections
  for (g in 1:ngroups) {
    # group header
    if (ngroups > 1L) {
      group.label <- attr(x, "group.label")
      cat("\n\n")
      cat("Group ", g, " [", group.label[g], "]:\n", sep = "")
    }

    for (l in 1:nlevels) {
      # block number
      b <- b + 1L

      # ov/lv names
      ov.names <- lavNames(x, "ov", block = b)
      lv.names <- lavNames(x, "lv", block = b)

      # level header
      if (nlevels > 1L) {
        level.label <- attr(x, "level.label")
        cat("\n\n")
        cat("Level ", l, " [", level.label[l], "]:\n", sep = "")
      }

      # group-specific sections
      for (s in GSECTIONS) {
        if (s == "Latent Variables") {
          row.idx <- which(x$op == "=~" & !x$lhs %in% ov.names &
            x$block == b)
          if (length(row.idx) == 0L) next
          m[row.idx, 1] <- .makeNames(x$rhs[row.idx], x$label[row.idx])
        } else if (s == "Composites") {
          row.idx <- which(x$op == "<~" & x$block == b)
          if (length(row.idx) == 0L) next
          m[row.idx, 1] <- .makeNames(x$rhs[row.idx], x$label[row.idx])
        } else if (s == "Regressions") {
          row.idx <- which(x$op == "~" & x$block == b)
          if (length(row.idx) == 0L) next
          m[row.idx, 1] <- .makeNames(x$rhs[row.idx], x$label[row.idx])
        } else if (s == "Covariances") {
          row.idx <- which(x$op == "~~" & x$lhs != x$rhs & !x$exo &
            x$block == b)
          if (length(row.idx) == 0L) next
          # make distinction between residual and plain
          y.names <- unique(c(
            lavNames(x, "eqs.y"),
            lavNames(x, "ov.ind"),
            lavNames(x, "lv.ind")
          ))
          PREFIX <- rep("", length(row.idx))
          PREFIX[x$rhs[row.idx] %in% y.names] <- "  ."
          m[row.idx, 1] <- .makeNames(x$rhs[row.idx], x$label[row.idx],
            PREFIX = PREFIX
          )
          # m[row.idx,1] <- .makeNames(x$rhs[row.idx], x$label[row.idx])
        } else if (s == "Intercepts") {
          row.idx <- which(x$op == "~1" & !x$exo & x$block == b)
          if (length(row.idx) == 0L) next
          # make distinction between intercepts and means
          y.names <- unique(c(
            lavNames(x, "eqs.y"),
            lavNames(x, "ov.ind"),
            lavNames(x, "lv.ind")
          ))
          PREFIX <- rep("", length(row.idx))
          PREFIX[x$lhs[row.idx] %in% y.names] <- "  ."
          m[row.idx, 1] <- .makeNames(x$lhs[row.idx], x$label[row.idx],
            PREFIX = PREFIX
          )
          # m[row.idx,1] <- .makeNames(x$lhs[row.idx], x$label[row.idx])
        } else if (s == "Thresholds") {
          row.idx <- which(x$op == "|" & x$block == b)
          if (length(row.idx) == 0L) next
          m[row.idx, 1] <- .makeNames(paste(x$lhs[row.idx], "|",
            x$rhs[row.idx],
            sep = ""
          ), x$label[row.idx])
        } else if (s == "Variances") {
          row.idx <- which(x$op == "~~" & x$lhs == x$rhs & !x$exo &
            x$block == b)
          if (length(row.idx) == 0L) next
          # make distinction between residual and plain
          y.names <- unique(c(
            lavNames(x, "eqs.y"),
            lavNames(x, "ov.ind"),
            lavNames(x, "lv.ind")
          ))
          PREFIX <- rep("", length(row.idx))
          PREFIX[x$rhs[row.idx] %in% y.names] <- "  ."
          m[row.idx, 1] <- .makeNames(x$rhs[row.idx], x$label[row.idx],
            PREFIX = PREFIX
          )
        } else if (s == "Scales y*") {
          row.idx <- which(x$op == "~*~" & x$block == b)
          if (length(row.idx) == 0L) next
          m[row.idx, 1] <- .makeNames(x$rhs[row.idx], x$label[row.idx])
        } else if (s == "Group Weight") {
          row.idx <- which(x$lhs == "group" & x$op == "%" & x$block == b)
          if (length(row.idx) == 0L) next
          m[row.idx, 1] <- .makeNames(x$rhs[row.idx], x$label[row.idx])
        } else if (s == "R-Square") {
          row.idx <- which(x$op == "r2" & x$block == b)
          if (length(row.idx) == 0L) next
          m[row.idx, 1] <- .makeNames(x$rhs[row.idx], x$label[row.idx])
        } else {
          row.idx <- integer(0L)
        }

        # do we need special formatting for this section?
        # three types:
        #  - regular (nothing to do, except row/colnames)
        #  - R-square
        #  - Latent Variables (and Composites), Regressions and Covariances
        #    'bundle' the output per lhs element

        # bundling
        if (s %in% c(
          "Latent Variables", "Composites",
          "Regressions", "Covariances"
        )) {
          nel <- length(row.idx)
          M <- matrix("", nrow = nel * 2, ncol = ncol(m))
          colnames(M) <- colnames(m)
          rownames(M) <- rep("", NROW(M))
          # colnames(M)[1] <- sprintf("%-17s", paste(s, ":", sep = ""))
          if (is.null(x$efa)) {
            LHS <- paste(x$lhs[row.idx], x$op[row.idx])
          } else {
            LHS <- paste(
              x$lhs[row.idx], x$op[row.idx],
              x$efa[row.idx]
            )
          }
          lhs.idx <- seq(1, nel * 2L, 2L)
          rhs.idx <- seq(1, nel * 2L, 2L) + 1L
          if (s == "Covariances") {
            # make distinction between residual and plain
            y.names <- unique(c(
              lavNames(x, "eqs.y"),
              lavNames(x, "ov.ind"),
              lavNames(x, "lv.ind")
            ))
            PREFIX <- rep("", length(row.idx))
            PREFIX[x$lhs[row.idx] %in% y.names] <- "."
          } else {
            PREFIX <- rep("", length(LHS))
          }
          M[lhs.idx, 1] <- sprintf("%1s%-15s", PREFIX, LHS)
          M[rhs.idx, ] <- m[row.idx, ]
          # avoid duplicated LHS labels
          if (nel > 1L) {
            del.idx <- integer(0)
            old.lhs <- ""
            for (i in 1:nel) {
              if (LHS[i] == old.lhs) {
                del.idx <- c(del.idx, lhs.idx[i])
              }
              old.lhs <- LHS[i]
            }
            if (length(del.idx) > 0L) {
              M <- M[-del.idx, , drop = FALSE]
            }
          }
          cat("\n", s, ":\n", sep = "")
          # cat("\n")
          print(M, quote = FALSE)

          # R-square
        } else if (s == "R-Square") {
          M <- m[row.idx, 1:2, drop = FALSE]
          colnames(M) <- colnames(m)[1:2]
          rownames(M) <- rep("", NROW(M))
          # colnames(M)[1] <- sprintf("%-17s", paste(s, ":", sep = ""))
          cat("\n", s, ":\n", sep = "")
          # cat("\n")
          print(M, quote = FALSE)

          # Regular
        } else {
          # M <- rbind(matrix("", nrow = 1L, ncol = ncol(m)),
          #           m[row.idx,])
          M <- m[row.idx, , drop = FALSE]
          colnames(M) <- colnames(m)
          rownames(M) <- rep("", NROW(M))
          # colnames(M)[1] <- sprintf("%-17s", paste(s, ":", sep = ""))
          cat("\n", s, ":\n", sep = "")
          # cat("\n")
          print(M, quote = FALSE)
        }
      } # GSECTIONS
    } # levels
  } # groups

  # asections
  for (s in ASECTIONS) {
    if (s == "Defined Parameters") {
      row.idx <- which(x$op == ":=")
      m[row.idx, 1] <- .makeNames(x$lhs[row.idx], "")
      M <- m[row.idx, , drop = FALSE]
      colnames(M) <- colnames(m)
    } else if (s == "Constraints") {
      row.idx <- which(x$op %in% c("==", "<", ">"))
      if (length(row.idx) == 0) next
      m[row.idx, 1] <- .makeConNames(x$lhs[row.idx], x$op[row.idx],
        x$rhs[row.idx],
        nd = nd
      )
      m[row.idx, 2] <- sprintf(num.format, abs(x$est[row.idx]))
      M <- m[row.idx, 1:2, drop = FALSE]
      colnames(M) <- c("", sprintf(char.format, "|Slack|"))
    } else {
      row.idx <- integer(0L)
    }

    if (length(row.idx) == 0L) {
      next
    }

    rownames(M) <- rep("", NROW(M))
    # colnames(M)[1] <- sprintf("%-17s", paste(s, ":", sep = ""))
    # cat("\n")
    cat("\n", s, ":\n", sep = "")
    print(M, quote = FALSE)
  }
  cat("\n")

  invisible(m)
}

.makeNames <- function(NAMES, LABELS, PREFIX = NULL) {
  W <- 14
  if (is.null(PREFIX)) {
    PREFIX <- rep("", length(NAMES))
  }

  multiB <- FALSE
  if (any(nchar(NAMES) != nchar(NAMES, "bytes"))) {
    multiB <- TRUE
  }
  if (any(nchar(LABELS) != nchar(LABELS, "bytes"))) {
    multiB <- TRUE
  }
  # labels?
  l.idx <- which(nchar(LABELS) > 0L)
  if (length(l.idx) > 0L) {
    if (!multiB) {
      LABELS <- abbreviate(LABELS, 4)
      LABELS[l.idx] <- paste(" (", LABELS[l.idx], ")", sep = "")
      MAX.L <- max(nchar(LABELS))
      NAMES <- abbreviate(NAMES,
        minlength = (W - MAX.L),
        strict = TRUE
      )
    } else {
      # do not abbreviate anything (eg in multi-byte locales)
      MAX.L <- 4L
    }
    NAMES <- sprintf(paste("%-", (W - MAX.L), "s%", MAX.L, "s",
      sep = ""
    ), NAMES, LABELS)
  } else {
    if (!multiB) {
      NAMES <- abbreviate(NAMES, minlength = W, strict = TRUE)
    } else {
      NAMES <- sprintf(paste("%-", W, "s", sep = ""), NAMES)
    }
  }

  char.format <- paste("%3s%-", W, "s", sep = "")
  sprintf(char.format, PREFIX, NAMES)
}

.makeConNames <- function(lhs, op, rhs, nd) {
  nd <- max(nd, 3)
  W <- 41 + (nd - 3) * 3

  nel <- length(lhs)
  if (length(nel) == 0L) {
    return(character(0))
  }
  NAMES <- character(nel)
  for (i in 1:nel) {
    if (rhs[i] == "0" && op[i] == ">") {
      con.string <- paste(lhs[i], " - 0", sep = "")
    } else if (rhs[i] == "0" && op[i] == "<") {
      con.string <- paste(rhs[i], " - (", lhs[i], ")", sep = "")
    } else if (rhs[i] != "0" && op[i] == ">") {
      con.string <- paste(lhs[i], " - (", rhs[i], ")", sep = "")
    } else if (rhs[i] != "0" && op[i] == "<") {
      con.string <- paste(rhs[i], " - (", lhs[i], ")", sep = "")
    } else if (rhs[i] == "0" && op[i] == "==") {
      con.string <- paste(lhs[i], " - 0", sep = "")
    } else if (rhs[i] != "0" && op[i] == "==") {
      con.string <- paste(lhs[i], " - (", rhs[i], ")", sep = "")
    }
    con.string <- abbreviate(con.string, W, strict = TRUE)
    char.format <- paste("   %-", W, "s", sep = "")
    NAMES[i] <- sprintf(char.format, con.string)
  }

  NAMES
}

summary.lavaan.fsr <- function(object, ...) {
  dotdotdot <- list(...)
  if (!is.null(dotdotdot$nd)) {
    nd <- dotdotdot$nd
  } else {
    nd <- 3L
  }

  print.lavaan.fsr(x = object, nd = nd, mm = TRUE, struc = TRUE)
}

print.lavaan.fsr <- function(x, ..., nd = 3L, mm = FALSE, struc = FALSE) {
  y <- unclass(x)

  # print header
  if (!is.null(y$header)) {
    cat(y$header)
    cat("\n")
  }

  if (mm && !is.null(y$MM.FIT)) {
    cat("\n")
    nblocks <- length(y$MM.FIT)
    for (b in seq_len(nblocks)) {
      cat(
        "Measurement block for latent variable(s):",
        paste(lavNames(y$MM.FIT[[b]], "lv")), "\n"
      )

      # fit measures?
      b.options <- lavInspect(y$MM.FIT[[b]], "options")
      if (!(length(b.options$test) == 1L && b.options$test == "none")) {
        cat("\n")
        print(fitMeasures(y$MM.FIT[[b]], c("chisq", "df", "pvalue", "cfi", "rmsea", "srmr")))
      }

      # parameter estimates
      PE <- parameterEstimates(y$MM.FIT[[b]],
        ci = FALSE,
        output = "text", header = TRUE
      )
      print.lavaan.parameterEstimates(PE, ..., nd = nd)
      cat("\n")
    }
  }

  # print PE
  if (struc) {
    cat("Structural Part\n")
    cat("\n")
    print(summary(y$STRUC.FIT,
      fit.measures = FALSE, estimates = FALSE,
      modindices = FALSE
    ))
    FIT <- fitMeasures(y$STRUC.FIT, fit.measures = "default")
    if (FIT["df"] > 0) {
      print.lavaan.fitMeasures(FIT, add.h0 = FALSE)
    }
  }
  PE <- parameterEstimates(y$STRUC.FIT,
    ci = FALSE,
    remove.eq = FALSE, remove.system.eq = TRUE,
    remove.ineq = FALSE, remove.def = FALSE,
    remove.nonfree = FALSE, remove.unused = TRUE,
    output = "text", header = TRUE
  )
  print.lavaan.parameterEstimates(PE, ..., nd = nd)

  invisible(y)
}


# print warnings/errors in a consistent way
# YR 12 July 2018

lav_txt2message <- function(txt, header = "lavaan WARNING:",
                            footer = "", txt.width = 70L, shift = 3L) {
  # make sure we only have a single string
  txt <- paste(txt, collapse = "")

  # split the txt in little chunks
  chunks <- strsplit(txt, "\\s+", fixed = FALSE)[[1]]

  # chunk size (number of characters)
  chunk.size <- nchar(chunks)

  # remove empty chunks (needed?)
  empty.idx <- which(chunk.size == 0)
  if (length(empty.idx) > 0L) {
    chunks <- chunks[-empty.idx]
    chunk.size <- chunk.size[-empty.idx]
  }

  # insert "\n" so the txt never gets wider than txt.width
  num.lines <- floor((sum(chunk.size) + length(chunk.size)) / txt.width + 0.5)
  target <- character(num.lines)

  line.size <- shift
  line.num <- 1L
  start.chunk <- 1L
  end.chunck <- 1L
  for (ch in seq_len(length(chunks))) {
    line.size <- line.size + chunk.size[ch] + 1L
    if (line.size > txt.width) {
      end.chunk <- ch - 1L
      target[line.num] <- paste(
        c(
          rep(" ", (shift - 1)),
          chunks[start.chunk:end.chunk]
        ),
        collapse = " "
      )
      line.num <- line.num + 1L
      start.chunk <- ch
      line.size <- shift + chunk.size[ch] + 1L
    }
  }
  # last line
  target[line.num] <- paste(c(
    rep(" ", (shift - 1)),
    chunks[start.chunk:ch]
  ), collapse = " ")

  body <- paste(target, collapse = "\n")
  if (nchar(footer) == 0L) {
    out <- paste(c(header, body), collapse = "\n")
  } else {
    out <- paste(c(header, body, footer), collapse = "\n")
  }

  out
}

# new in 0.6-12
print.lavaan.summary <- function(x, ..., nd = 3L) {
  y <- unclass(x) # change to ordinary list

  # get nd, if it is stored as an attribute
  ND <- attr(y, "nd")
  if (!is.null(ND) && is.numeric(ND)) {
    nd <- as.integer(ND)
  }

  # header
  if (!is.null(y$header)) {
    lavaan.version <- y$header$lavaan.version
    sam.approach <- y$header$sam.approach
    optim.method <- y$header$optim.method
    optim.iterations <- y$header$optim.iterations
    optim.converged <- y$header$optim.converged

    # sam or sem?
    if (sam.approach) {
      cat("This is ",
        sprintf("lavaan %s", lavaan.version),
        " -- using the SAM approach to SEM\n",
        sep = ""
      )
    } else {
      cat(sprintf("lavaan %s ", lavaan.version))

      # Convergence or not?
      if (optim.method == "none") {
        cat("-- DRY RUN with 0 iterations --\n")
      } else if (optim.iterations > 0) {
        if (optim.converged) {
          if (optim.iterations == 1L) {
            cat("ended normally after 1 iteration\n")
          } else {
            cat(sprintf(
              "ended normally after %i iterations\n",
              optim.iterations
            ))
          }
        } else {
          if (optim.iterations == 1L) {
            cat("did NOT end normally after 1 iteration\n")
          } else {
            cat(sprintf(
              "did NOT end normally after %i iterations\n",
              optim.iterations
            ))
          }
          cat("** WARNING ** Estimates below are most likely unreliable\n")
        }
      } else {
        cat("did not run (perhaps do.fit = FALSE)?\n")
        cat("** WARNING ** Estimates below are simply the starting values\n")
      }
    }
  }

  #TDJ: print header for lavaan.mi object (or nothing when NULL)
  cat(y$top_of_lavaanmi)

  # optim
  if (!is.null(y$optim)) {
    estimator <- y$optim$estimator
    estimator.args <- y$optim$estimator.args
    optim.method <- y$optim$optim.method
    npar <- y$optim$npar
    eq.constraints <- y$optim$eq.constraints
    nrow.ceq.jac <- y$optim$nrow.ceq.jac
    nrow.cin.jac <- y$optim$nrow.cin.jac
    nrow.con.jac <- y$optim$nrow.con.jac
    con.jac.rank <- y$optim$con.jac.rank

    cat("\n")
    # cat("Optimization information:\n\n")

    c1 <- c("Estimator")
    # second column
    tmp.est <- toupper(estimator)
    if (tmp.est == "DLS") {
      dls.first.letter <- substr(
        estimator.args$dls.GammaNT,
        1L, 1L
      )
      tmp.est <- paste("DLS-", toupper(dls.first.letter), sep = "")
    }
    c2 <- tmp.est

    # additional estimator args
    if (!is.null(estimator.args) &&
      length(estimator.args) > 0L) {
      if (estimator == "DLS") {
        c1 <- c(c1, "Estimator DLS value for a")
        c2 <- c(c2, estimator.args$dls.a)
      }
    }

    # optimization method + npar
    c1 <- c(c1, "Optimization method", "Number of model parameters")
    c2 <- c(c2, toupper(optim.method), npar)

    # optional output
    if (eq.constraints) {
      c1 <- c(c1, "Number of equality constraints")
      c2 <- c(c2, nrow.ceq.jac)
    }
    if (nrow.cin.jac > 0L) {
      c1 <- c(c1, "Number of inequality constraints")
      c2 <- c(c2, nrow.cin.jac)
    }
    if (nrow.con.jac > 0L) {
      if (con.jac.rank == (nrow.ceq.jac + nrow.cin.jac)) {
        # nothing to do (don't print, as this is redundant information)
      } else {
        c1 <- c(c1, "Row rank of the constraints matrix")
        c2 <- c(c2, con.jac.rank)
      }
    }

    # format
    c1 <- format(c1, width = 40L)
    c2 <- format(c2,
      width = 11L + max(0, (nd - 3L)) * 4L,
      justify = "right"
    )

    # character matrix
    M <- cbind(c1, c2, deparse.level = 0)
    colnames(M) <- rep("", ncol(M))
    rownames(M) <- rep(" ", nrow(M))

    # print
    write.table(M, row.names = TRUE, col.names = FALSE, quote = FALSE)
  }

  # sam header
  if (!is.null(y$sam.header)) {
    cat("\n")
    sam.method <- y$sam.header$sam.method
    sam.local.options <- y$sam.header$sam.local.options
    sam.mm.list <- y$sam.header$sam.mm.list
    sam.mm.estimator <- y$sam.header$sam.mm.estimator
    sam.struc.estimator <- y$sam.header$sam.struc.estimator

    # sam method
    c1 <- c("SAM method")
    c2 <- toupper(sam.method)

    # options
    if (sam.method == "local") {
      c1 <- c(c1, "Mapping matrix M method")
      c2 <- c(c2, sam.local.options$M.method)
      # TODo: more!
    }

    # number of measurement blocks
    c1 <- c(c1, "Number of measurement blocks")
    c2 <- c(c2, length(sam.mm.list))

    # estimator measurement blocks
    c1 <- c(c1, "Estimator measurement part")
    c2 <- c(c2, sam.mm.estimator)

    # estimator structural part
    c1 <- c(c1, "Estimator  structural part")
    c2 <- c(c2, sam.struc.estimator)

    # format
    c1 <- format(c1, width = 40L)
    c2 <- format(c2,
      width = 11L + max(0, (nd - 3L)) * 4L,
      justify = "right"
    )

    # character matrix
    M <- cbind(c1, c2, deparse.level = 0)
    colnames(M) <- rep("", ncol(M))
    rownames(M) <- rep(" ", nrow(M))

    # print
    write.table(M, row.names = TRUE, col.names = FALSE, quote = FALSE)
  }

  # efa/rotation
  if (!is.null(y$rotation)) {
    cat("\n")
    rotation <- y$rotation
    rotation.args <- y$rotation.args

    # cat("Rotation information:\n\n")
    # container
    c1 <- c2 <- character(0L)

    # rotation method
    c1 <- c(c1, "Rotation method")
    if (rotation$rotation == "none") {
      MM <- toupper(rotation$rotation)
    } else if (rotation$rotation.args$orthogonal) {
      MM <- paste(toupper(rotation$rotation), " ", "ORTHOGONAL",
        sep = ""
      )
    } else {
      MM <- paste(toupper(rotation$rotation), " ", "OBLIQUE",
        sep = ""
      )
    }
    c2 <- c(c2, MM)

    if (rotation$rotation != "none") {
      # method options
      if (rotation$rotation == "geomin") {
        c1 <- c(c1, "Geomin epsilon")
        c2 <- c(c2, rotation$rotation.args$geomin.epsilon)
      } else if (rotation$rotation == "orthomax") {
        c1 <- c(c1, "Orthomax gamma")
        c2 <- c(c2, rotation$rotation.args$orthomax.gamma)
      } else if (rotation$rotation == "cf") {
        c1 <- c(c1, "Crawford-Ferguson gamma")
        c2 <- c(c2, rotation$rotation.args$cf.gamma)
      } else if (rotation$rotation == "oblimin") {
        c1 <- c(c1, "Oblimin gamma")
        c2 <- c(c2, rotation$rotation.args$oblimin.gamma)
      } else if (rotation$rotation == "promax") {
        c1 <- c(c1, "Promax kappa")
        c2 <- c(c2, rotation$rotation.args$promax.kappa)
      }

      # rotation algorithm
      c1 <- c(c1, "Rotation algorithm (rstarts)")
      tmp <- paste(toupper(rotation$rotation.args$algorithm),
        " (", rotation$rotation.args$rstarts, ")",
        sep = ""
      )
      c2 <- c(c2, tmp)

      # Standardized metric (or not)
      c1 <- c(c1, "Standardized metric")
      if (rotation$rotation.args$std.ov) {
        c2 <- c(c2, "TRUE")
      } else {
        c2 <- c(c2, "FALSE")
      }

      # Row weights
      c1 <- c(c1, "Row weights")
      tmp.txt <- rotation$rotation.args$row.weights
      c2 <- c(c2, paste(toupper(substring(tmp.txt, 1, 1)),
        substring(tmp.txt, 2),
        sep = ""
      ))
    }

    # format c1/c2
    c1 <- format(c1, width = 33L)
    c2 <- format(c2,
      width = 18L + max(0, (nd - 3L)) * 4L,
      justify = "right"
    )

    # create character matrix
    M <- cbind(c1, c2, deparse.level = 0)
    colnames(M) <- rep("", ncol(M))
    rownames(M) <- rep(" ", nrow(M))

    # print
    write.table(M, row.names = TRUE, col.names = FALSE, quote = FALSE)
  }

  # data object
  if (!is.null(y$data)) {
    cat("\n")
    lav_data_print_short(y$data, nd = nd)
  }

  # sam local stats: measurement blocks + structural part
  if (!is.null(y$sam)) {
    cat("\n")
    sam.method <- y$sam$sam.method
    sam.mm.table <- y$sam$sam.mm.table
    sam.mm.rel <- y$sam$sam.mm.rel
    sam.struc.fit <- y$sam$sam.struc.fit
    ngroups <- y$sam$ngroups
    nlevels <- y$sam$nlevels
    group.label <- y$sam$group.label
    level.label <- y$sam$level.label
    block.label <- y$sam$block.label

    # measurement
    tmp <- sam.mm.table
    if (sam.method == "global") {
      cat("Summary Information Measurement Part:\n\n")
    } else {
      cat("Summary Information Measurement + Structural:\n\n")
    }
    print(tmp, row.names = rep(" ", nrow(tmp)), nd = nd)

    if (sam.method == "local") {
      # reliability information
      c1 <- c2 <- character(0L)
      if (ngroups == 1L && nlevels == 1L) {
        cat("\n")
        cat("  Model-based reliability latent variables:\n\n")
        tmp <- data.frame(as.list(sam.mm.rel[[1]]))
        class(tmp) <- c("lavaan.data.frame", "data.frame")
        print(tmp, row.names = rep(" ", nrow(tmp)), nd = nd)
      } else if (ngroups > 1L && nlevels == 1L) {
        cat("\n")
        cat("  Model-based reliability latent variables (per group):\n")
        for (g in 1:ngroups) {
          cat("\n")
          cat("  Group ", g, " [", group.label[g], "]:\n\n",
            sep = ""
          )
          tmp <- data.frame(as.list(sam.mm.rel[[g]]))
          class(tmp) <- c("lavaan.data.frame", "data.frame")
          print(tmp, row.names = rep(" ", nrow(tmp)), nd = nd)
        }
      } else if (ngroups == 1L && nlevels > 1L) {
        cat("\n")
        cat("  Model-based reliability latent variables (per level):\n")
        for (g in 1:nlevels) {
          cat("\n")
          cat("  Level ", g, " [", level.label[g], "]:\n\n",
            sep = ""
          )
          tmp <- data.frame(as.list(sam.mm.rel[[g]]))
          class(tmp) <- c("lavaan.data.frame", "data.frame")
          print(tmp, row.names = rep(" ", nrow(tmp)), nd = nd)
        }
      } else if (ngroups > 1L && nlevels > 1L) {
        cat("\n")
        cat("  Model-based reliability latent variables (per group/level):\n")
        for (g in 1:length(block.label)) {
          cat("\n")
          cat("  Group/Level ", g, " [", block.label[g], "]:\n\n",
            sep = ""
          )
          tmp <- data.frame(as.list(sam.mm.rel[[g]]))
          class(tmp) <- c("lavaan.data.frame", "data.frame")
          print(tmp, row.names = rep(" ", nrow(tmp)), nd = nd)
        }
      }

      cat("\n")
      cat("  Summary Information Structural part:\n\n")
      tmp <- data.frame(as.list(sam.struc.fit))
      class(tmp) <- c("lavaan.data.frame", "data.frame")
      print(tmp, row.names = rep(" ", nrow(tmp)), nd = nd)
    }
  }

  # test statistics
  if (!is.null(y$test)) {
    cat("\n")
    lav_test_print(y$test, nd = nd)
  }

  # extra fit measures (if present)
  if (!is.null(y$fit)) {
    add.h0 <- FALSE
    if (!is.null(attr(y$fit, "add.h0"))) {
      add.h0 <- isTRUE(attr(y$fit, "add.h0"))
    }
    print.lavaan.fitMeasures(y$fit, nd = nd, add.h0 = add.h0)
  }

  # efa output
  if (!is.null(y$efa)) {
    # get cutoff, if it is stored as an attribute
    CT <- attr(y, "cutoff")
    if (!is.null(CT) && is.numeric(CT)) {
      cutoff <- CT
    } else {
      cutoff <- 0.3
    }
    # get dot.cutoff, if it is stored as an attribute
    DC <- attr(y, "dot.cutoff")
    if (!is.null(DC) && is.numeric(DC)) {
      dot.cutoff <- DC
    } else {
      dot.cutoff <- 0.1
    }
    # get alpha.level, if it is stored as an attribute
    AL <- attr(y, "alpha.level")
    if (!is.null(AL) && is.numeric(AL)) {
      alpha.level <- AL
    } else {
      alpha.level <- 0.01
    }

    for (b in seq_len(y$efa$nblocks)) {
      if (length(y$efa$block.label) > 0L) {
        cat("\n\n")
        cat(y$efa$block.label[[b]], ":\n\n", sep = "")
      }
      if (!is.null(y$efa$lambda[[b]])) {
        cat("\n")
        if (!is.null(y$efa$lambda.se[[b]]) && alpha.level > 0) {
          cat("Standardized loadings: (* = significant at ",
            round(alpha.level * 100),
            "% level)\n\n",
            sep = ""
          )
        } else {
          cat("Standardized loadings:\n\n")
        }
        LAMBDA <- unclass(y$efa$lambda[[b]])
        THETA <- unname(unclass(y$efa$theta[[b]]))
        lav_print_loadings(LAMBDA,
          nd = nd, cutoff = cutoff,
          dot.cutoff = dot.cutoff,
          alpha.level = alpha.level,
          resvar = THETA, # diag elements only
          x.se = y$efa$lambda.se[[b]]
        )
      }

      if (!is.null(y$efa$sumsq.table[[b]])) {
        cat("\n")
        print(y$efa$sumsq.table[[b]], nd = nd)
      }

      # factor correlations:
      if (!y$efa$orthogonal && !is.null(y$efa$psi[[b]]) &&
        ncol(y$efa$psi[[b]]) > 1L) {
        cat("\n")
        if (!is.null(y$efa$psi.se[[b]]) && alpha.level > 0) {
          cat("Factor correlations: (* = significant at ",
            round(alpha.level * 100),
            "% level)\n\n",
            sep = ""
          )
        } else {
          cat("Factor correlations:\n\n")
        }
        lav_print_psi(y$efa$psi[[b]],
          nd = nd,
          alpha.level = alpha.level,
          x.se = y$efa$psi.se[[b]]
        )
      }

      # factor score determinacy (for regression scores only!)
      if (!is.null(y$efa$fs.determinacy[[b]])) {
        cat("\n")
        cat("Correlation regression factor scores and factors (determinacy):\n\n")
        print(y$efa$fs.determinacy[[b]], nd = nd)
        cat("\n")
        cat("R2 regression factor scores (= squared correlations):\n\n")
        tmp <- y$efa$fs.determinacy[[b]]
        tmp2 <- tmp * tmp
        class(tmp2) <- c("lavaan.vector", "numeric")
        print(tmp2, nd = nd)
      }

      # lambda.structure
      if (!is.null(y$efa$lambda.structure[[b]])) {
        cat("\n")
        cat("Standardized structure (= LAMBDA %*% PSI):\n\n")
        print(y$efa$lambda.structure[[b]], nd = nd)
      }

      # standard errors lambda
      if (!is.null(y$efa$theta.se[[b]])) { # we check for theta.se
        # as lambda.se is needed for '*'
        cat("\n")
        cat("Standard errors standardized loadings:\n\n")
        print(y$efa$lambda.se[[b]], nd = nd)
      }

      # z-statistics lambda
      if (!is.null(y$efa$lambda.zstat[[b]])) {
        cat("\n")
        cat("Z-statistics standardized loadings:\n\n")
        print(y$efa$lambda.zstat[[b]], nd = nd)
      }

      # pvalues lambda
      if (!is.null(y$efa$lambda.pvalue[[b]])) {
        cat("\n")
        cat("P-values standardized loadings:\n\n")
        print(y$efa$lambda.pvalue[[b]], nd = nd)
      }

      # standard errors theta
      if (!is.null(y$efa$theta.se[[b]])) {
        cat("\n")
        cat("Standard errors unique variances:\n\n")
        print(y$efa$theta.se[[b]], nd = nd)
      }

      # z-statistics theta
      if (!is.null(y$efa$theta.zstat[[b]])) {
        cat("\n")
        cat("Z-statistics unique variances:\n\n")
        print(y$efa$theta.zstat[[b]], nd = nd)
      }

      # pvalues theta
      if (!is.null(y$efa$theta.pvalue[[b]])) {
        cat("\n")
        cat("P-values unique variances:\n\n")
        print(y$efa$theta.pvalue[[b]], nd = nd)
      }

      # standard errors psi
      if (!is.null(y$efa$theta.se[[b]])) { # we check for theta.se
        # as psi.se is needed for '*'
        cat("\n")
        cat("Standard errors factor correlations:\n\n")
        print(y$efa$psi.se[[b]], nd = nd)
      }

      # z-statistics psi
      if (!is.null(y$efa$psi.zstat[[b]])) {
        cat("\n")
        cat("Z-statistics factor correlations:\n\n")
        print(y$efa$psi.zstat[[b]], nd = nd)
      }

      # pvalues psi
      if (!is.null(y$efa$psi.pvalue[[b]])) {
        cat("\n")
        cat("P-values factor correlations:\n\n")
        print(y$efa$psi.pvalue[[b]], nd = nd)
      }
    } # blocks
    cat("\n")
  } # efa

  # parameter table
  if (!is.null(y$pe) && is.null(y$efa)) {
    PE <- y$pe
    class(PE) <- c(
      "lavaan.parameterEstimates", "lavaan.data.frame",
      "data.frame"
    )
    print(PE, nd = nd)
  }

  # modification indices
  if (!is.null(y$mi)) {
    cat("Modification Indices:\n\n")
    MI <- y$mi
    rownames(MI) <- NULL
    print(MI, nd = nd)
  }

  invisible(y)
}

# helper function to print the loading matrix, masking small loadings
lav_print_loadings <- function(x, nd = 3L, cutoff = 0.3, dot.cutoff = 0.1,
                               alpha.level = 0.01, resvar = NULL, x.se = NULL) {
  # unclass
  y <- unclass(x)

  # round, and create a character matriy
  y <- format(round(y, nd), width = 3L + nd, justify = "right")

  # right-align column names
  colnames(y) <- format(colnames(y), width = 3L + nd, justify = "right")

  # create dot/empty string
  dot.string <- format(".", width = 3L + nd, justify = "right")
  empty.string <- format(" ", width = 3L + nd)

  # print a 'dot' if dot.cutoff < |loading| < cutoff
  if (dot.cutoff < cutoff) {
    y[abs(x) < cutoff & abs(x) > dot.cutoff] <- dot.string
  }

  # print nothing if |loading| < dot.cutoff
  y[abs(x) < min(dot.cutoff, cutoff)] <- empty.string

  # add 'star' for significant loadings (if provided) using alpha = 0.01
  if (!is.null(x.se) && !any(is.na(x.se))) {
    colNAMES <- colnames(y)
    rowNAMES <- rownames(y)
    x.se[x.se < sqrt(.Machine$double.eps)] <- 1 # to avoid NA
    zstat <- x / x.se
    z.cutoff <- qnorm(1 - (alpha.level / 2))
    zstat.string <- ifelse(abs(zstat) > z.cutoff, "*", " ")
    y <- matrix(paste(y, zstat.string, sep = ""), nrow(y), ncol(y))
    colnames(y) <- colNAMES
    rownames(y) <- rowNAMES
  }

  # add resvar
  if (!is.null(resvar)) {
    NAMES <- colnames(y)
    y <- cbind(y, format(round(cbind(resvar, 1 - resvar), nd),
      width = 12L + nd, justify = "right"
    ))
    resvar.names <- format(c("unique.var", "communalities"),
      width = 12L + nd, justify = "right"
    )
    colnames(y) <- c(NAMES, resvar.names)
  }

  # print
  print(y, quote = FALSE)
}

# helper function to print the psi matrix, showing signif stars
lav_print_psi <- function(x, nd = 3L, alpha.level = 0.01, x.se = NULL) {
  # unclass
  y <- unclass(x)

  # round, and create a character matriy
  y <- format(round(y, nd), width = 3L + nd, justify = "right")

  # right-align column names
  colnames(y) <- format(colnames(y), width = 3L + nd, justify = "right")

  # add 'star' for significant loadings (if provided) using alpha = 0.01
  if (!is.null(x.se) && !any(is.na(x.se))) {
    colNAMES <- colnames(y)
    rowNAMES <- rownames(y)
    x.se[x.se < sqrt(.Machine$double.eps)] <- 1 # to avoid NA
    zstat <- x / x.se
    z.cutoff <- qnorm(1 - (alpha.level / 2))
    zstat.string <- ifelse(abs(zstat) > z.cutoff, "*", " ")
    y <- matrix(paste(y, zstat.string, sep = ""), nrow(y), ncol(y))
    colnames(y) <- colNAMES
    rownames(y) <- rowNAMES
  }

  # remove upper part
  ll <- upper.tri(x, diag = FALSE)
  y[ll] <- ""

  # print
  print(y, quote = FALSE)
}
yrosseel/lavaan documentation built on May 1, 2024, 5:45 p.m.