Nothing
#' Velicer's minimum average partial (MAP) criterion
#'
#' @description
#' Computes Velicer's Minimum Average Partial (MAP) criterion for determining the number of
#' factors/components to retain. The function implements the original MAP criterion
#' (Velicer, 1976), expressed via the \eqn{\mathrm{TR2}} representation, and the revised
#' \eqn{\mathrm{TR4}} variant proposed by Velicer, Eaton, and Fava (2000).
#'
#' @details
#'
#' MAP partials successive principal components out of the correlation matrix and,
#' after removing \eqn{m} components, summarizes the off-diagonal partial
#' correlations \eqn{r^*_{ij}} that remain in the \eqn{m}-th partial correlation
#' matrix \eqn{M} (which has a unit diagonal); the suggested number of factors is
#' the \eqn{m} that minimizes the criterion. Two criteria are returned, each
#' rescaling the trace of a matrix power of \eqn{M} by the number of off-diagonal
#' cells \eqn{p(p-1)}:
#' \itemize{
#' \item **TR2 (original MAP; Velicer, 1976):** the average squared off-diagonal
#' partial correlation,
#' \deqn{\mathrm{TR2}_m = \frac{\mathrm{tr}(M^2) - p}{p(p-1)} = \frac{\sum_{i \neq j} (r^*_{ij})^2}{p(p-1)},}
#' where subtracting \eqn{p} removes the \eqn{p} unit diagonal entries.
#' \item **TR4 (revised MAP; Velicer, Eaton, & Fava, 2000):** the analogous
#' fourth-power summary, formed from the trace of the fourth matrix power,
#' \deqn{\mathrm{TR4}_m = \frac{\mathrm{tr}(M^4) - p}{p(p-1)}.}
#' Moving from the squared to the fourth power downweights the small partial
#' correlations relative to the large ones, which can sharpen the minimum.
#' Unlike TR2, \eqn{\mathrm{tr}(M^4)} is *not* the sum of the fourth powers of the
#' individual partial correlations; the matrix power is intended and is what
#' Velicer, Eaton, and Fava (2000) describe.
#' }
#'
#' Both criteria are returned for every call and they can suggest different numbers
#' of factors on the same correlation matrix. Both are in use in the literature and
#' neither is treated as the default here, so be sure to state which of the two you
#' report, as you would for any other analysis choice.
#'
#' MAP is most dependable when the components are well determined, that is with many
#' indicators per factor and substantial loadings. It has a well-documented tendency
#' to under-extract, particularly with few indicators per factor or weak loadings
#' (Zwick & Velicer, 1986; Auerswald & Moshagen, 2019), so it is best read as a lower
#' bound and paired with a criterion that errs in the other direction, such as the
#' Kaiser-Guttman criterion ([efa_kgc()]).
#'
#' The criterion is evaluated over \eqn{m = 0, \ldots, p - 1}. Each step standardizes
#' the partial covariance matrix by its residual standard deviations, which requires
#' every residual variance to stay positive. Partialling out all but one component
#' leaves a rank-one residual, so the final point \eqn{m = p - 1} is undefined for
#' most correlation matrices and is routinely returned as `NA`. A residual variance
#' can also reach zero earlier, most often on a near-singular matrix; the search then
#' stops there, the criterion values that could be computed are kept, the remaining
#' values stay `NA`, and a warning (class `efa_map_truncated`) reports how far the
#' grid was searched. In that case the suggested \eqn{m} is the minimum over the
#' evaluated range only, so it should be read together with the returned series.
#'
#' A non-positive-definite input correlation matrix (e.g. from sampling error) is
#' smoothed with [psych::cor.smooth()].
#'
#' @param x A numeric `matrix` or `data.frame`. Can be either (a) a correlation matrix, or
#' (b) raw data (rows = observations, columns = variables) from which correlations are computed.
#' @param use Character string specifying the treatment of missing values when computing correlations.
#' Passed to [stats::cor()]. Defaults to `"pairwise.complete.obs"`.
#' @param cor_method Character string specifying the correlation coefficient to be computed if raw
#' data are supplied. One of `"pearson"`, `"spearman"`, or `"kendall"` (passed to
#' [stats::cor()]), or `"poly"` / `"tetra"` for polychoric / tetrachoric correlations
#' of ordinal / binary data (a two-step estimator). Defaults to `"pearson"`.
#'
#' @returns An object of class `efa_retention` (see [print.efa_retention()] for the
#' print method). MAP has no plot; [plot.efa_retention()] returns `NULL` with a
#' message for it. Its main elements are:
#' \itemize{
#' \item `n_factors`: A named numeric vector (`"TR2"`, `"TR4"`) with the index
#' \eqn{m} that minimizes the original (TR2) and revised (TR4) MAP criterion.
#' \item `results`: A list with one record per criterion, each holding the
#' criterion values over \eqn{m} and, in `m_last`, the largest \eqn{m} at which
#' the criterion could be evaluated (see details).
#' \item `settings`: A list containing `use` and `cor_method`.
#' }
#'
#' @source Auerswald, M., & Moshagen, M. (2019). How to determine the number of
#' factors to retain in exploratory factor analysis: A comparison of extraction
#' methods under realistic conditions. *Psychological Methods, 24*(4), 468--491.
#' https://doi.org/10.1037/met0000200
#' @source Velicer, W. F. (1976). Determining the number of components from the matrix of partial correlations.
#' *Psychometrika, 41*, 321--327.
#' @source Velicer, W. F., Eaton, C. A., & Fava, J. L. (2000). Construct explication through factor or component analysis: A review and evaluation of alternative procedures for determining the number of factors or components. In Goffin, R. D. & Helmes, E. (Eds.), *Problems and Solutions in
#' Human Assessment: Honoring Douglas N. Jackson at Seventy* (pp. 41--71). Boston: Kluwer.
#' @source Zwick, W. R., & Velicer, W. F. (1986). Comparison of five rules for
#' determining the number of components to retain. *Psychological Bulletin, 99*,
#' 432--442. https://doi.org/10.1037/0033-2909.99.3.432
#'
#'
#' @examples
#' ## Example with raw data
#' res <- efa_map(GRiPS_raw)
#' res
#'
#' ## Example with a correlation matrix
#' res2 <- efa_map(test_models$baseline$cormat)
#' res2
#'
#' @family factor retention criteria
#'
#' @seealso [efa_retain()] as a wrapper function for this and the other factor
#' retention criteria.
#'
#' @export
efa_map <- function(x,
use = c("pairwise.complete.obs", "all.obs",
"complete.obs", "everything",
"na.or.complete"),
cor_method = c("pearson", "spearman", "kendall", "poly", "tetra")) {
# Perform argument checks
.assert_cor_input(x)
use <- .match_arg_ci(use)
cor_method <- .match_arg_ci(cor_method)
# Detect or compute the correlation matrix, check it, and smooth it if needed
prep <- .prepare_cor_input(x, use = use, cor_method = cor_method,
N_policy = "none")
R <- prep$R
p <- ncol(R)
# set up m_grid
m_max <- p - 1L
ms <- 0:m_max
criteria <- matrix(NA_real_, nrow = p, ncol = 2)
colnames(criteria) <- c("TR2 (orig. MAP)", "TR4 (revised MAP)")
# PCA to get the loadings A
ed <- eigen(R, symmetric = TRUE)
vals <- ed$values
vecs <- ed$vectors
# A = V Lambda^(1/2): scale each eigenvector column by the square root of its
# eigenvalue (equivalent to vecs %*% sqrt(diag(vals)) but without forming the
# p x p diagonal matrix)
A <- sweep(vecs, 2, sqrt(vals), "*")
map_from_partials <- function(M) {
# Velicer's (1976) MAP and the revised criterion of Velicer, Eaton & Fava (2000)
M2 <- M %*% M
M4 <- M2 %*% M2
c((sum(diag(M2)) - p) / (p * (p - 1)), # TR2 criterion (original MAP)
(sum(diag(M4)) - p) / (p * (p - 1))) # TR4 criterion (revised MAP)
}
# m=0, Rstar = R
criteria[1, ] <- map_from_partials(R)
# Largest m at which the criterion could be evaluated, and whether that stop cut
# the grid short. Partialling out all but one component leaves a rank-one residual,
# so the final point m = p - 1 is undefined for most matrices; only a stop before
# that shortens the searched range in a way the user needs to know about.
m_last <- m_max
truncated <- FALSE
# run through ms
for (m in seq_len(m_max)) {
Am <- A[, 1:m]
# Partial covariance: C = R - AA'
Cm <- R - Am %*% t(Am)
d <- diag(Cm)
# Guard against zero/negative residual variances (can happen at very high m or numerical issues)
if (any(!is.finite(d)) || any(d <= 1e-5)) {
m_last <- m - 1L
truncated <- m < m_max
break
}
# D^(-1/2) to standardize to correlation matrix
Dm <- diag(1 / sqrt(d))
Rstar <- Dm %*% Cm %*% Dm
criteria[m + 1, ] <- map_from_partials(Rstar)
}
if (isTRUE(truncated)) {
cli::cli_warn(
c("The MAP criterion could only be evaluated up to {m_last} partialled component{?s} of {m_max}.",
"i" = "A residual variance reached zero there, so the criterion is {.code NA} beyond that point and the suggestion is the minimum over the range that could be computed.",
"i" = "A stop well before the end of the grid usually indicates a near-singular correlation matrix."),
class = "efa_map_truncated"
)
}
n_factors_TR2 <- ms[which.min(criteria[, "TR2 (orig. MAP)"])]
n_factors_TR4 <- ms[which.min(criteria[, "TR4 (revised MAP)"])]
# one record per MAP criterion (criterion values over the number of partialled
# components m)
results <- list(
list(name = "TR2", label = "Original implementation (TR2)",
n_factors = n_factors_TR2, plot_type = "none",
x = ms, y = criteria[, "TR2 (orig. MAP)"], m_last = m_last),
list(name = "TR4", label = "Revised implementation (TR4)",
n_factors = n_factors_TR4, plot_type = "none",
x = ms, y = criteria[, "TR4 (revised MAP)"], m_last = m_last)
)
out <- .new_efa_retention(
"MAP",
results = results,
settings = list(use = use, cor_method = cor_method),
note = if (isTRUE(truncated)) {
paste0("The criterion could only be evaluated up to ", m_last, " of ",
m_max, " partialled components; the suggestion is the minimum over ",
"that range, not over the full grid.")
}
)
return(out)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.