On this page
Factor analysis – method notes
The decisions behind the factor analysis module – which estimator, threshold, refusal or fallback stands behind each card, and the numbers that validate them, each computed on the example dataset (cafe.csv, its spec beside it). Sections follow the manual’s.
Requirements
The factor count is refused past the Ledermann bound rather than fitted. A correlation matrix on p variables holds distinct numbers, and the common factor model pays for every loading and every uniqueness out of that budget; past the Ledermann bound it estimates more parameters than the matrix determines, so infinitely many solutions fit equally well and the one returned is arbitrary. psych::fa() does not refuse – it returns loadings with zero or negative degrees of freedom – which is why the module computes the bound from the variable count, publishes it as the upper limit of the three count fields, clamps a leftover value when the selection or the extraction changes, and rejects a typed value above it in Step 2 and Step 3 with a message naming the maximum. PCA has no bound: its components are computed from the matrix rather than estimated, all p of them are defined, and at p components the decomposition is lossless.
Step 1: Method & settings
Mixed dispatches one estimator per pair from the declared types, in mixedCor()'s own buckets. The option splits the selected variables into continuous, polytomous (three or more categories) and dichotomous columns and hands them to psych::mixedCor() – Pearson within the continuous set, polychoric within the ordinal one, polyserial and biserial across – or to polychoric() alone when every column is ordinal, which treats a two-category item exactly as tetrachoric() would. The bucket is read from the type set in the data view and never from the values: a 0/1 column imported as continuous stays Pearson, because the import types every numeric column continuous, and inferring a dichotomy from two observed values would turn every yes/no indicator into a latent continuum without being asked. The same three index vectors serve the bootstrap resamples and the parallel-analysis null, so a resample that happens to drop a rare category cannot switch an item’s estimator for that replicate alone.
An ordinal item with more than 8 categories goes through the continuous bucket, and a run left with nothing ordinal falls back to Pearson and says so. polychoric() stops outright above its max.cat of 8 – past about seven categories the categorical and continuous treatments converge anyway – so rather than failing the whole run on a 0–10 item typed ordinal, the module demotes it to the continuous bucket for every pair it is in and lists it in an alert, since a partly Pearson matrix must not pass for a fully polychoric one. When the demotion, or the types as set, leave no column in either ordinal bucket, plain cor() runs and a second alert reports the fallback, because nothing in the solution itself can reveal which estimator produced it. Both alerts are attached to every card computed from the matrix – suitability, Step 3 and the comparison.
Validation. On the café six items typed ordinal build a polychoric matrix (loadings 0.79/0.61/0.69 and 0.59/0.73/0.66, φ = .39, 48.2% of the variance); the same six left continuous fall back to Pearson under the alert, and age typed ordinal beside five items is demoted by name.
Ordinal codes are ranked onto 1…k before the estimator sees them. psych recodes an ordinal column with a minimum shift and tabulate(), which truncates, so a coding that is not integer-spaced reaches the estimator as fabricated cell counts: 0/0.5 returns a sign-flipped tetrachoric and 1/1.5/2/2.5 kills polychoric() with “non-conformable arrays”. The module replaces each ordinal column by the ranks of its distinct values first, which the estimator cannot tell from the original when the codes are already consecutive integers, since it reads only the ordering and the counts.
Maximum likelihood, alpha factoring and minimum rank stop before a non-positive-definite matrix; every other extraction runs on a smoothed one. The three fail on it reliably – L-BFGS-B needs finite values of 'fn' for maximum likelihood, infinite or missing values in 'x' for the other two – so the module stops with a named diagnosis and the smallest eigenvalue before paying for the fit. The bar the eigenvalue must clear is √ε rather than zero, because an eigenvalue that is only a rounding artefact above zero still breaks fa(fm = "ml"), and Step 2’s card and Step 3’s check read the same bar so one matrix cannot pass one and fail the other. The rest let psych smooth the matrix and carry on under a warning; a matrix polychoric() or mixedCor() smoothed while building it reads as positive definite afterwards, so the warning captured at construction is carried to the same alert rather than lost. A severely non-definite matrix can still defeat one of the rest – weighted least squares in particular – and the post-fit branch then reports the same diagnosis rather than R’s error.
Minimum sample size weighted χ² runs with uniform weights. psych’s minchi weights each squared residual by the number of cases its correlation was computed from, which is a different fit only on a pairwise-complete matrix. The module fits complete cases, so every pair carries the same count and the criterion reduces to the least-squares one; the pairwise-count matrix is still passed explicitly, because fa() given a correlation matrix without it silently downgrades the method to minimum residual. The option is kept for parity with psych and with the comparison dialog.
Validation. On the café items it reproduces minimum residual within 0.01.
The bifactor rotations need three factors and a common-factor extraction, and Biquartimin runs the Jennrich–Bentler criterion. A bifactor model is a general factor plus at least two group factors, so both rotations are degenerate below three: GPArotation errors outright at exactly two, and a single factor would return an ordinary solution labelled bifactor – the module stops in Step 3 and in the comparison alike, before the fit. Under PCA the family is withheld because it models a general factor beside variable-specific variance, and a component has none. The oblique option is named Biquartimin (oblique bifactor) because the criterion psych runs under that name is Jennrich and Bentler’s oblique bifactor rotation rather than Carroll’s original biquartimin, and the general factor it imposes and the three-factor minimum both follow from that; for the same reason it takes no Schmid–Leiman transformation, whose second-order general factor would sit on top of one already imposed.
PCA keeps the oblique rotations and the extraction comparison. Unrotated components are orthogonal by construction, but nothing forces the rotated ones to stay so: obliquely rotated principal components are standard practice – SPSS offers Direct Oblimin and Promax under principal component extraction – so the module reports a component correlation matrix and a structure matrix for them exactly as for a factor solution. The comparison dialog is kept under PCA rather than withheld because setting a component solution against the common-factor extractions is the robustness check the literature asks of it, and Tucker’s congruence answers it.
Validation. The café’s two components read 0.995 to 0.998 in congruence against each factor method’s.
Every rotation is driven by the module from the unrotated fit, with twenty random starts under the reproducibility seed. psych’s own normalize flag never reaches the rotation for most of the methods offered – Promax ignores it and fa()'s multi-start refit drops extra arguments – so the fit is asked for rotate = "none" and the module rotates the loadings itself, which costs nothing because every fit statistic is rotation-invariant. Varimax, cluster and promax are called directly with their own normalization off; every other rotation goes through faRotations() with twenty random starts, since each criterion has local optima, and the starts are drawn under Reproducibility seed so that a rerun – and every congruence coefficient the comparison reads off the loadings – is reproducible. The columns are then signed and ordered as psych would, by their sums of squares weighted by Φ for a factor solution, so factors keep psych’s numbering, and a rotation that does not converge is reported rather than presented as settled.
Kaiser normalization is a row-scale sandwich around the rotation, ticked by default for varimax alone. Each variable’s row of loadings is divided by its length before the rotation and multiplied back after, so an item with a low communality has the same say in where the axes land as a well-measured one – see Kaiser normalization. Applied by the module rather than through psych, for the reason above, it is real for every rotation, PCA included. The checkbox follows the rotation’s convention: ticked when Varimax is chosen – SPSS normalizes varimax, and psych computed varimax normalized whatever the flag said – and cleared for every other rotation, whose reference implementations in GPArotation default to unnormalized; it is disabled, and cleared, while no rotation runs (None (unrotated), or a single factor), and re-applying the rotation’s default on the way back is what keeps a 3 → 1 → 3 round trip from silently dropping varimax’s normalization. Because the default follows the rotation, two cards titled with the same extraction and rotation can be different solutions, so whenever a rotation ran the results card records Kaiser normalization: Yes or No beneath the factor count and an exported card carries the setting with it.
Validation. On the café’s oblimin solution the two states differ by 0.02 at most in the loadings and move φ from .41 to .39.
Step 2: Determine number of factors
The verdicts follow the interpretation setting; the alerts never do. With the interpretation setting off, the KMO, determinant, smallest-eigenvalue and cases-to-variables rows carry their numbers alone and Bartlett’s row its p-value, and the alerts beneath the table – rank deficiency, a perfect pair, a non-positive-definite matrix, multicollinearity, a pair above |r| = 0.9, a squared multiple correlation above 0.99, construction smoothing, the Mixed estimator’s fallback and demotion, an inadequate KMO, a non-significant Bartlett test and a thin sample – are drawn whatever the setting says, because the first two of the headline verdicts are the decision on whether the variables can be factored at all, and with interpretation off the alert is the only place either would be spelled out. The KMO alert fires below 0.5 on the value as printed, and is withheld where the value is a placeholder – the rank or perfect-pair alert names the cause instead; the Bartlett alert reads the assumption test significance level, since the test guards an assumption rather than a hypothesis. The alerts are ordered so that the one naming a cause comes before those describing its symptoms: rank deficiency first, the non-positive-definite alert withheld once it has fired, and the multicollinearity alert withheld when a non-definite matrix or a perfect pair already explains the determinant.
Bartlett’s verdict is withheld on a singular or near-singular matrix, and its p-value qualified on a rank-based or polychoric one. psych::cortest.bartlett()'s statistic is , which diverges as the determinant approaches zero, so on a matrix that is singular or a hair from it the χ² is inflated towards certain significance and the p-value collapses to 0 whatever the data look like – it returns NaN only in the harder case where the determinant goes negative. The verdict therefore reads Unreliable (singular matrix) under the same evidence that marks KMO unreliable, widened to a non-positive-definite or rank-deficient matrix where solve() still succeeds and KMO’s own sentinel never fires, and a legend line explains the withholding. The second caveat is statistical rather than numerical: χ² and its reference distribution are derived for a sample Pearson matrix, and the elements of a Spearman, polychoric or polyserial matrix carry different sampling variability, so on any of those the p-value is approximate at best and the row says so – except on a Mixed run that fell back to plain Pearson, whose matrix is Pearson whatever the widget says.
The determinant’s verdict needs a second, item-count-independent finding. A correlation matrix’s determinant shrinks geometrically with the number of variables whatever the data look like: at a uniform r = 0.3 it is about 3.1·10⁻⁴ with 30 variables and 1.2·10⁻⁵ with 40, so Field’s fixed bar of 0.00001 is crossed by every 40-item questionnaire, redundant or not. The module reports the number and the bar, but grades the row Multicollinearity detected and draws its alert only when one of three checks that do not depend on the item count agrees – a rank shortfall (qr()), a pair correlating above |r| = 0.9 in absolute value, or a variable whose squared multiple correlation with the rest exceeds 0.99 – and Acceptable otherwise, so a tiny determinant with no alert reads as a size effect rather than as evidence. The pair check is drawn unconditionally in an alert of its own, because the determinant grades redundancy across the whole matrix and names no variables, while the pair alert points at the two items to drop or merge – and a single near-duplicate pair in an otherwise healthy set often leaves the determinant comfortably above its bar; Step 3 repeats the same check on the fitted solution. The squared-multiple-correlation check sits here rather than only after Step 3 because an item that is almost perfectly reconstructible from the rest all but guarantees a Heywood case in the extracted solution, and naming it before a parallel-analysis run is cheaper than after one.
KMO, the per-variable MSAs and the anti-image partials are withheld on a singular matrix, and the rank alert names the cause. psych::KMO() inverts the correlation matrix, and when its solve() fails it returns an exact 0.5 for every variable and the correlation matrix itself as the anti-image – numbers indistinguishable from a real, if terrible, measurement. The module therefore tests solve(cor_matrix) itself in R and carries the outcome as a flag, and a perfect or near-perfect pair (|r| above 0.9999) marks KMO unreliable outright, since it makes the matrix singular by construction. On that evidence the KMO verdict reads Unreliable (singular matrix), and the per-variable MSA table and the strongest-pairs list are withheld – checkbox or not – along with the near-perfectly-predicted-variables check, which needs the same inverse: on a singular matrix smc()'s fallbacks mislead in both directions, reporting a low squared multiple correlation for a variable that is an exact sum of five others while flagging healthy ones. Redundancy spanning three or more variables – a total analysed beside its items – leaves every pairwise |r| under that bar, so the rank is computed directly by qr(), and when it falls short each redundant variable is named with the variables it is reproduced from, read off the weights of its column on the full-rank block and written Total ← Item A, Item B, Item C; that alert comes first, because everything else on the card is downstream of it.
The smallest eigenvalue is graded against the extraction pre-flight’s bar. The row reads the same √ε tolerance the non-positive-definite refusal applies before a fit, rather than a bare zero, so a matrix a rounding error above singular already reads Matrix is not positive definite here – at that magnitude the extraction fails anyway, and a named diagnosis is more use than R’s optimizer message – and one matrix cannot pass this card and fail Step 3 or the model comparison sweep. A matrix that polychoric() or mixedCor() smoothed while building it reads as positive definite afterwards, so the repair is reported from the flag R returns beside the matrix, as an alert of its own, repeated above the model comparison table and in the extraction comparison.
The sample floors are 50 cases and then 5 per variable, and the absolute floor takes precedence. The rules of thumb for a factor analysis’s sample size disagree by an order of magnitude, and what actually decides recoverability is the strength of the structure (MacCallum, Widaman, Zhang & Hong, 1999), so the module grades the ratio against the lowest bars in the literature rather than the strictest: fewer than 50 complete cases is Sample too small, fewer than five per variable Below the recommended 5:1, and a sample failing both is reported by the first, since a ratio is not a meaningful complaint about 30 cases. One R snippet grades the count for this card and for Step 3’s warning, so the two cannot drift apart on the thresholds.
Parallel analysis runs two nulls through the observed matrix’s own estimator, and each count is read against a 95th percentile. The Monte Carlo is the module’s own loop rather than psych::fa.parallel(), which forks. Each draw builds two datasets – multivariate-normal random data of the sample’s size, and the observed data with every column shuffled independently (Buja & Eyuboglu, 1992), which destroys the correlations and keeps each item’s distribution – and correlates each with the estimator the observed matrix was built with; under Spearman and Mixed the random columns are first cut at the observed category thresholds, so the null carries the real category structure and its ties. Under a factor method both null matrices are reduced by their squared multiple correlations before their eigenvalues are taken, so they are compared with the reduced series the extraction sees, and under PCA with the unreduced one. Each count stops at the first eigenvalue that does not beat its 95th percentile, the draws run under the Reproducibility seed, and the mean curves are drawn faded as context only. A draw that throws – a rare category missing from a polychoric resample – leaves its row NA rather than aborting the loop; the percentiles are then taken over the survivors, and since a run that quietly lost half its draws would otherwise read exactly like a complete one, the survivor count is reported beneath the recommendations whenever it falls short.
Validation. On the café items the reduced series the extraction sees reads 1.73, 0.58, −0.09 against the nulls’ 0.36, 0.21, 0.10.
Kaiser and the empirical Kaiser criterion read the correlation matrix’s eigenvalues, and stop at the first failure. Kaiser’s bar is “above the average eigenvalue”, which is 1 only for a matrix with 1s on its diagonal; on the reduced series, whose average is below 1, a flat bar of 1 is far stricter than the rule intends. So the count, the blue row highlight and the dashed line all read the unreduced series, the model comparison table shows it beside the reduced one as λ (correlation matrix), and both recommendations carry that qualifier under EFA. The count is taken on the eigenvalues as printed, so a raw 0.9998 that renders as 1.000 is neither counted nor highlighted and the recommendation cannot contradict the table. The empirical reference (Braeken & van Assen, 2017) starts from the Marchenko–Pastur bound on the largest eigenvalue of a random correlation matrix, , and rescales it at each later position by the variance the preceding eigenvalues have already taken, , floored at Kaiser’s 1, and the count is the position before the first eigenvalue that fails its reference, as the paper defines it, not the number of positions that clear theirs: the reference is adaptive, so a later position can clear its bar after an earlier one failed, and a plain sum would overstate the count.
Validation. On the café items a bar of 1 on the reduced series keeps a single factor where the correlation matrix’s series keeps two, and the empirical references read 1.38 then 1.00.
The elbow reads the curve as drawn, by the cluster module’s rule. The elbow is a rule about the curve’s shape rather than its level, so it reads the series the chart plots – the reduced one under EFA, negative tail included – and its dot sits on that curve. It is located as the point furthest from the chord joining the first eigenvalue to the last, the same rule the cluster module’s elbow plot applies to its within-cluster sums of squares, and omitted when the curve bends too little to mark. On clean structure the corner is usually the first debris point past the real factors, which is why the count tends to run one above parallel analysis and is presented as a check rather than a recommendation.
Validation. On the café items the elbow reads 3 against parallel analysis’s 2.
Hull reads the share of the off-diagonal correlations each solution reproduces, over every identifiable count, and is not run under PCA. For every factor count from none to the Ledermann bound – those inside the tested range from the sweep’s own fits, the rest fitted by Hull’s own loop – the solution’s fit is 1 minus the sum of its squared off-diagonal residuals over the sum of the squared off-diagonal correlations, with the null model anchored at 0, against its degrees of freedom (Lorenzo-Seva, Timmerman & Kiers, 2011). Solutions that add no fit over a simpler one are dropped, then every point on or below the chord joining its neighbours, until the convex hull remains; the count retained is the vertex at which the ratio of the slope into it to the slope out of it peaks. That fit measure involves no χ², which is what makes the rule equally meaningful for every EFA extraction the module offers, and it weighs parsimony, so it can stop below parallel analysis when a second factor is real but small. Fewer than three vertices leave no interior point to turn on – very few variables, or fit climbing at a steady rate – and the count is then left out with its line rather than reported as a guess. It is not run under PCA because the method was defined for common factors and a component solution’s fit reaches exactly 1 at the saturated count, which bends the frontier onto one component whatever the data; it is skipped along with the sweep when the extraction cannot run on the matrix.
MAP and VSS are evaluated over every identifiable count under varimax, and alpha factoring’s failure is retried with minimum residual. One psych::VSS() call at n = max(2, identifiable_max) – the floor because VSS() fails at n = 1, the ceiling because a count past the Ledermann bound is not a solution one could fit – yields VSS₁, VSS₂ and MAP for every count the model can identify, under rotate = "varimax" because simple structure is what the criterion measures and vss()'s PCA branch ignores the rotation argument in any case, and under the reproducibility seed so the values do not depend on whatever the shared session drew before. The recommendations read the untruncated series and the table bolds its cells against the same series, so a within-window optimum is never called the recommendation while the plot names one outside it; the MAP tie is rounded at four decimals, since neighbouring counts routinely differ in the third. psych fills VSS₂ at one factor with a hard 0 – a complexity the solution cannot support, not a fit – which the table shows as -. VSS() cannot run every extraction (fm = "alpha" always errors), which would blank all three columns; MAP is invariant to the extraction, so a minimum-residual retry recovers the three on a defensible basis and the legend names the substitute, while PCA gets no retry, since substituting a common-factor extraction would report component diagnostics computed from a factor model.
A non-positive-definite matrix stops maximum likelihood, alpha factoring and minimum rank before the sweep, a fit that threw reads Fitting failed, and one that hit the iteration cap is daggered. The sweep runs the same pre-flight as Step 3’s single fit: those three extractions fail on such a matrix reliably, so fitting each count in turn would only reproduce psych’s optimizer message once per row, and every row is marked Not fitted with a tooltip stating the one cause, while the eigenvalue columns, which need no fit, are kept. A count whose fa() call threw keeps its row and its cell carries R’s own message as the tooltip – psych’s canned “Failed to converge” adds nothing and is dropped – because the message usually distinguishes an over-parameterized model from a rotation that does not work at that count; the label is cause-neutral because that path is disjoint from real non-convergence. The iteration cap arrives from psych as a message(), which the warning handler cannot see, so it is caught separately and marked with a dagger: the fit did return numbers and the row stays comparable, but they rest on an unconverged model that would otherwise be indistinguishable from a clean fit.
The N cell is the control that carries a count into Step 3. Each fitted row’s count is a link-styled button whose accessible name stays the number – an aria-label would make every row announce alike – with the action in its tooltip. Assigning the count to the Step 3 field fires no change event, so the rotation options that depend on it are refreshed by hand and a toast confirms the value; a row that threw, or that the pre-flight skipped, has no count worth carrying and keeps a plain cell.
Last factor % is the N-th dimension’s own share, and Cumulative % is the mean communality. Each row of the table is a whole solution, so the per-row variance column is named for the quantity – the last factor’s sum of squared loadings over p – rather than “variance explained”, which would read as the row’s total beside Cumulative %. The cumulative column is , which is also the solution’s mean communality, so it is reported once, in the percentage the module already uses, and bolded at the 0.70 communality bar; Step 3’s solution quality reports the same number as Mean communality (h²) for the count actually run.
Rows with df ≤ 0 are reported and not graded, and every χ²-derived column is a maximum-likelihood discrepancy whatever the extraction. A model with no residual degrees of freedom reproduces the correlation matrix by construction – the Ledermann bound is exactly the count at which df reaches 0 – so a near-perfect CFI or a near-zero SRMR in that row is arithmetic rather than evidence; the row keeps its numbers, loses its bolding and its verdict, and a legend line names the condition. psych::fa() returns χ², RMSEA, CFI, TLI, BIC and SABIC for every extraction, computed from the maximum-likelihood discrepancy at whatever solution the extraction reached, so they are a true likelihood-ratio statistic only under ML and approximations that systematically overstate the misfit under the other eight; the model fit cutoffs are applied as they are, the legend says the grades are conservative, and the same note appears on Step 3’s fit card and in the extraction comparison, where the mechanism is spelled out. The row’s verdict is the worst band any of RMSEA, CFI, TLI and SRMR reaches, each graded on the value as printed, so the cell can never claim a better fit than the cells beside it; under PCA, which has no discrepancy function, the verdict grades Cumulative % at 80, 70 and 60 per cent instead.
EBIC and ESABIC are built from the residual χ², so their minima can leave BIC’s row. BIC and SABIC rest on the χ² implied by the maximum-likelihood discrepancy – the criterion ML is optimizing and the other extractions are merely graded on. psych also computes an empirical χ² from the residual correlations the solution actually leaves behind (their squared sum scaled by the sample size), which every extraction can be judged on, and EBIC and ESABIC apply the BIC and sample-size-adjusted penalties to that statistic instead. The two count misfit differently, so their minima can land on different rows – when they do, EBIC tends to favour the larger count – and the table bolds each at its own minimum rather than pretending they agree.
Validation. On the café items both pick two.
SRMR is computed on the divisor the cutoffs assume; psych’s two residual summaries are reported and not graded. psych’s rms divides the off-diagonal squared-residual sum by p(p − 1), twice the pair count, so it reads low against the lavaan-scaled cutoffs the model fit cutoffs setting encodes, and the gap grows with p – the ratio is , 1.195 at six variables and 1.301 at twelve. The module’s SRMR sums the residual matrix with its diagonal zeroed and divides by p(p + 1), which reproduces fitMeasures(fit, "srmr") exactly, and that is the only residual column graded; rms and crms, the latter divided by the degrees of freedom, are kept so the table matches what psych::fa() prints, under names that claim no standard they do not meet. Step 3’s fit card carries the same three rows under the same rule, and under PCA reports SRMR without a verdict, since its cutoffs are a common-factor convention.
Complexity and the unassigned count are graded on a varimax copy of the unrotated fit. The sweep fits every count with rotate = "none", because the eigenvalue and variance columns need psych’s own column order, but Hoffman’s complexity and the count of variables with no salient loading are simple-structure diagnostics that only mean something on a rotated solution. Each row’s loadings are therefore rotated by stats::varimax(normalize = FALSE) before the two are computed – Hoffman’s formula reproduces psych’s own result$complexity exactly – so the columns measure the same quantity Step 3’s solution quality reports under the same labels; Step 3 grades the rotation actually chosen, so its numbers coincide with these under varimax and differ a little under an oblique rotation. The salience bar is the conventional 0.3, inclusive, and fixed: it does not follow the loadings display threshold, which decides what is worth showing rather than whether a variable found a factor at all.
Step 3: Run full analysis
The one-factor fit is unrotated and titled so, and its rotation-dependent options are disabled rather than ignored. Rotation redistributes variance between factors, so with one there is nothing to redistribute and psych returns the unrotated fit whatever rotate says. The module says the same thing everywhere the rotation is named – the main card’s title, the extraction comparison’s and the dialog’s settings line all read No rotation rather than the dropdown’s value – and disables Kaiser normalization and Factor/component correlations while the count is 1, because an option accepted and silently ignored on an exported card is a false record of how the solution was made. The dropdown keeps its value so that raising the count restores the rotation without re-choosing it.
Scoring is fixed to regression at a single factor, and Anderson–Rubin is allowed under an oblique rotation with an inline caveat that Schmid-Leiman scores never show. With one column every method’s weights are proportional and the four return identical scores, so a choice there would only mislabel the column the scores are inserted under; the dropdown is greyed out with a note rather than hidden, so the fixed value stays visible. Anderson–Rubin under an oblique rotation contradicts the model by construction – the scores are uncorrelated while Φ says the factors are not (Grice, 2001) – but uncorrelated predictors for a downstream regression are a legitimate reason to want it, so the combination is kept, the caveat is drawn under the dropdown, and the correlations among the saved scores are printed so the contradiction is visible rather than assumed. The caveat appears only while Factor/component scores is ticked: Schmid-Leiman scores are computed from an orthogonal hierarchy, which has no factor correlations for the method to contradict.
The loadings display threshold reaches the path diagram, which keeps every variable’s strongest group-factor loading whatever its size. The table and the diagram read one cutoff so the picture never shows an edge the table hides, or the reverse. The diagram keeps each variable’s largest group-factor loading below the cutoff, faded and dashed, because a variable with no edge at all reads as one the model dropped, while a dashed edge reads as what it is – the best of a weak set. The general factor of a bifactor or hierarchical solution is not eligible for the exception: g loads on nearly everything, so keeping its sub-threshold edges would connect every variable to it and hide the group structure the diagram exists to show. A typed value outside 0–1 is clamped and an empty field read as 0, so the control cannot be left in a state the table cannot render. The table’s own Cutoff threshold starts at that value and its Highlight threshold at 0.6 – the salience convention and the usual bar for a strong loading – and both, like the 0.40 communality flag, grade the value as printed: a raw 0.5996 renders as 0.600, so grading the raw value would leave it unbolded under a legend reading ≥ 0.6, and a raw 0.2996 that prints 0.300 is shown at a cutoff of 0.3 rather than blanked beneath it. The structure matrix carries its own copy of the controls, since it is a second table with its own reading, but its cutoff stops at the table: the diagram draws loadings, not correlations, and follows the loadings table alone.
The matrix checks run again before the fit, errors first, and survive a failed run. Step 3 fits whatever selection and correlation type it is given, which need not be what the suitability card saw, so the constant-variable, cases-versus-variables, perfect-pair, positive-definiteness, squared-multiple-correlation, high-correlation, rank and determinant checks run again on the matrix actually fitted – the determinant under the same corroboration rule as Step 2, and the sample floors from the same snippet, where fewer than 50 cases stays a warning because it destabilises every number on the card and a thin ratio is downgraded to a note because it describes the design rather than a fault in the fit. The alerts are attached to the error card when the extraction fails, because they usually name the reason – a rank shortfall, a perfect pair, a non-definite matrix under maximum likelihood – and discarding them would leave the error unexplained.
Heywood cases are graded at two bars, for EFA only, and read off the uniqueness where the extraction bounds it. A communality at or above 0.999 is reported as a warning and one above 1.0 as an error, on a card that still renders so the variable can be read off. PCA is excluded because a component has no uniqueness for the model to squeeze to zero, and principal() at the maximum number of components routinely returns communalities a few ULPs above 1 from rounding, which would raise the error on a correct, lossless decomposition. psych fits minimum residual, maximum likelihood, the three least-squares criteria and minimum sample size weighted χ² by optim() with the uniquenesses bounded below at 0.005, so under those six a Heywood case stops at a communality of 0.995 and could never reach the 0.999 bar; there the warning is raised instead by a uniqueness at the bound, within 0.001 of it, since psych recomputes the uniquenesses from the truncated eigen-decomposition after optim() returns and lands a hair under 0.005. Principal axis, alpha factoring and minimum rank iterate the communalities unbounded and keep the communality bar. The rule is one R snippet read by Step 3 and the extraction comparison alike, so a solution flagged on one card cannot pass clean on the other; the Schmid-Leiman check stays on the bar, since its communalities are the hierarchical table’s rather than a fit’s uniquenesses.
Validation. On the café items at three factors value reads h² 0.995 with a uniqueness of 0.00500 under maximum likelihood and 0.00495 under minimum residual, and both are flagged; the extraction comparison marks ML, MINRES, ULS and GLS as Heywood cases at three factors and none at two.
A collapsed factor is one of the retained eigenvalues of the reduced matrix landing on the positive-definiteness bar. After the fit the correlation matrix’s diagonal is replaced by the communalities and its first k eigenvalues compared with the same √ε bar the pre-flight uses; each below it is a retained factor carrying no common variance. Only the first k are read because the trailing p − k eigenvalues of a good fit are a residual spectrum straddling zero, and counting them would flag every healthy solution; a bar rather than a sign test because an unsupported factor lands on the floor at about 10⁻¹² rather than crossing below it, which a sign test never sees. The check is skipped when a communality is NA, since eigen() errors on an NA diagonal.
Principal axis and alpha factoring are retried from unit communalities when the squared-multiple-correlation start fails. psych’s “imaginary eigen value condition” names SMC = FALSE as the fix, and the two methods are the ones that fail that way – the others do not, so a retry under them would only reproduce the same error under a configuration nobody asked for. The extraction comparison rescues the same failure the same way, so a method that succeeds there is not a dead end in Step 3; the retry is disclosed on the card because a solution that started from unit communalities is a valid fit from different starting conditions than the one requested, and its comparability with the other extractions is qualified by exactly that.
Validation. On the café items principal axis factoring at three factors fails from the SMC start and is rescued.
The RMSEA interval is pinned to 90%. fa() is passed alpha = 0.1, its own default, rather than the app’s confidence setting, because Browne and Cudeck’s cutoffs – the 0.05, 0.08 and 0.10 the model fit cutoffs setting encodes – were developed and are read against a 90% interval, and so is every published RMSEA interval a reader might compare theirs with; the row label names the level so the bracketed bounds cannot be mistaken for a 95% interval. The Confidence level setting governs the bootstrap intervals for the loadings and the omega coefficients instead. alpha drives the interval and nothing else in the fit.
Residuals above 0.05 are graded on Field’s single bar, kept under PCA and withheld at df ≤ 0. The count is over the off-diagonal residuals – psych keeps the uniquenesses on the diagonal, so only pairs are read – and the verdict is two-tiered rather than the four fit bands because the rule is a single bar: more than half above 0.05 in absolute value means the retained factors do not account for the correlation matrix (Field, 2018). It is withheld with the other verdicts at df ≤ 0, where the residuals are near zero by construction, and kept under PCA, which emits no degrees of freedom, so it is the one graded row a component solution has. It is also where PCA and EFA part most visibly, because a component solution reproduces the diagonal at the price of the off-diagonals. The matrix, when Residual correlations is on, prints the same residuals cell by cell – the lower triangle alone, since the matrix is symmetric and psych keeps the uniquenesses on its diagonal, so those cells are exactly the nonredundant residuals the count reads – and highlights each above the same 0.05 bar on the value as printed, so a cell reading 0.05 is never coloured as if it exceeded it: the bar is per cell what Field’s rule is in aggregate, the shortlist of pairs behind the count.
Validation. The café’s two factors leave 0 of 15 residuals above 0.05 and its two components 8 of 15.
PCA’s fit section is the residual rows alone, titled for what it reports. principal() populates STATISTIC, PVAL, objective and the indices derived from them from the maximum-likelihood discrepancy of a model it never fitted that way, so those numbers describe no estimator that ran; the module withholds χ², RMSEA, CFI, TLI, the information criteria and the objective function under PCA, renames the section Residual fit summary, and grades nothing but the residual count, since the SRMR cutoffs are a common-factor convention. What remains is the one question that is well posed for a component solution – how much of the correlation matrix the retained components leave unreproduced – and a note under the table says so.
Off-diagonal fit is computed by the module, because psych’s fit.off halves the residual sum without halving its divisor. fit is psych’s own: 1 minus the sum of squared residuals over the sum of squared correlations, diagonal included, which the 1s down the diagonal dominate, so the row is named for the matrix rather than for “variance”, which it is not. The off-diagonal share is recomputed as over the upper triangle alone, since fit.off as psych returns it reports half the unexplained share; it is the same arithmetic under PCA and EFA, which is what makes the two comparable on it.
Validation. On the café items fit reads 0.78; the off-diagonal fit is 0.999 for the two factors against 0.85 for the two components.
SS loadings are psych’s Vaccounted rows, which under an oblique rotation sum pattern × structure. colSums(loadings²) is wrong once the factors correlate; psych computes each column as diag(ΦΛ′Λ) – the pattern coefficients weighted by the structure coefficients – which counts the variance a factor shares with the factors it correlates with and still sums to exactly, so the total partitions and the last cumulative cell is the mean communality, oblique or not. The legend says so under an oblique rotation because the columns then read as attributions rather than exclusive shares. psych omits the cumulative and the explained rows for a single-factor solution, where the shares are 1 by definition, and the module fills them so the table has the same shape at every count.
The general factor of a bifactor solution is identified from the loadings, never by column position. psych orders columns by their sums of squares, which puts the general factor first on most data and not on all, so the module reads it off the loadings: a column qualifies when it loads at 0.20 or above in absolute value on all but at most 10% of the variables (one, below fifteen) and at least 80% of those salient loadings share a sign, and the qualifying column with the greatest breadth × sign-consistency is labelled g, the rest renumbered on the loadings table, the variance table, the correlation matrix and the path diagram alike. The tolerance is proportional because a couple of items under 0.20 are routine on a thirty-item pool and a fixed “all but one” would disqualify real bifactor solutions there. When no column qualifies the labels stay F1…Fn and a warning says so, rather than a guess being labelled g. The loadings table’s Highest loading sort and the path diagram’s grouping leave g out of the argmax for the reason the diagram’s cutoff exception does: it carries the largest loading for nearly every variable, so sorting by it would collapse every variable into one group, and variables are grouped by their strongest group factor instead.
Loading intervals are bias-corrected percentile, matched to the reported solution by congruence and re-signed, and withheld under 50 usable replicates. Every replicate resamples the cases, rebuilds the correlation matrix with the run’s own estimator – the Mixed buckets included, which is what makes those runs slow – and re-fits extraction, rotation and normalization in full, because a loading’s sampling variability comes through the rotation as much as through the extraction. A resample’s columns can come back in another order or with another sign, and read naively a loading and its negative would land in the same quantile, so each replicate’s columns are assigned to the reported ones by maximum-weight matching on Tucker’s congruence and then reflected to agree in sign before being recorded. The interval is the bias-corrected percentile one – the percentile bounds shifted by the replicate distribution’s median bias against the point estimate, which costs no extra refits – and not full BCa: its acceleration term would need a jackknife, one refit per case, and the rotation, matching and sign steps behind every estimand break the smoothness the term assumes, so a badly estimated acceleration could make the interval worse than none. Fifty usable replicates is the floor below which no bounds are drawn, a guard against mass replicate failure rather than a precision claim – endpoint precision is the Bootstrap replications setting’s – and the alert names that setting when it is itself under 50, since nothing failed there. A replicate either yields every loading or none, so the count is one per matrix.
The PCA path diagram runs its arrows from the variables to the components and draws no error terms. In the common factor model a factor is a latent cause – it produces the observed scores, so the arrow runs factor → variable and every variable keeps an error term for the part no factor explains. A principal component is a weighted sum of its variables, computed from them rather than causing them, so the diagram is drawn formative – variable → component – and without error terms, because a component leaves nothing unexplained by construction and an error term feeding a variable that is itself the cause would contradict the diagram. psych’s fa.diagram draws both kinds the same way, and the module deliberately differs; the legend’s Arrows run variables → components line is there because an exported SVG travels without this note.
A general factor is drawn on the opposite flank, and the error terms move to stubs to make room. A bifactor or Schmid–Leiman g loads directly on every variable, so placed in the factor column its fan would share one corridor with the group factors’ and cross every line in it; on the right of the variable column the two fans approach the variables from opposite sides instead. The error column gives way to a 45° stub off each variable’s top-right corner, and the general factor’s connections are dashed and faded so its fan stays distinguishable from the group loadings. A bifactor solution whose general factor could not be identified has nothing to flank and keeps the ordinary columns, as plain EFA, PCA and CFA do.
The anti-image matrices are the module’s own, from the inverse of the raw matrix, and are withheld where that inverse does not exist. MASS::ginv() of the unsmoothed correlation matrix is the precision matrix Ω; the anti-image covariance is Ω scaled by its diagonal – on the diagonal, off it – and standardizing it gives the negated partial correlations of psych::KMO()'s Image convention, whose diagonal the module then replaces with Kaiser’s per-variable MSA so it reads on the overall KMO’s scale. ginv() rather than solve() because its truncated SVD is the better-conditioned inverse on a near-singular matrix – but on a matrix solve() rejects outright both tables are withheld under a warning, the same test Step 2’s card makes, since what a pseudo-inverse returns there looks like partial correlations and grades like them while being a numerical fallback rather than a measurement. The raw matrix rather than the smoothed one, so the tables describe the correlations observed – which is why an indefinite matrix leaves cells that cannot be computed, a negative anti-image variance among them, blanked rather than printed as a plain number and read N/A under a note naming the cause. The off-diagonals are graded on the two bad tiers of the Correlation strength bands, the same the Step 2 worst-pairs list uses, rather than on cutoffs of their own.
psych’s silent scoring fallbacks are detected and named. factor.scores() substitutes a weaker estimator instead of failing: regression weights on a repaired matrix when the correlation matrix cannot be inverted, a pseudo-inverse when it is singular. Left alone, the card would name a scoring method that did not run and grade its determinacy against another method’s weights, so the module reads the substitution off the result and reports which happened. A scoring failure is split by the method that failed, because the standing advice – switch to regression – is wrong when regression is what failed: it inverts the correlation matrix, and the message then points at the multicollinearity diagnostics instead.
Determinacy is Grice’s ρ from the selected method’s own weights, reported unsquared, for PCA too. psych’s R2 cannot serve the line: factor.scores() overwrites the method-specific value with fsi(), which never receives the weights and so cannot depend on the scoring method, and a determinacy that read the same under every method would grade the solution rather than the scores in the table. The module computes from the weights W the scores were built with, the structure matrix S – the pattern matrix post-multiplied by Φ under an oblique rotation – and the correlation matrix R (Grice, 2001), and reports its square root: the scale Gorsuch’s and Grice’s ≥ .80 / ≥ .90 bars are written on and the number other software prints as “factor determinacy”, so the value is directly comparable with a published one. Only rounding noise is absorbed at the boundaries (10⁻⁶): a near-singular matrix can return an R² of 4.7 or −2.3, and squashing those to 0 or 1 would present a numerical failure as a valid score, so an out-of-range value is carried through unrooted and drawn red. The line is drawn under PCA because it grades the scores actually asked for: a component is an exact weighted sum of its variables, so regression and Ten Berge weights recover it and read 1.000, while Bartlett and Anderson–Rubin impose a property the component lacks and land just below – a real difference, worth seeing before the columns are saved.
Validation. On the café items Bartlett and Anderson–Rubin read 0.999 and 0.99 against the regression and Ten Berge weights’ 1.000.
Score correlations are the saved columns’ own, not the model-implied ones. psych’s r.scores is cov2cor(WᵀRW), the correlations the weights imply on the matrix they were derived from; the module prints cor(scores) instead, the correlations of the columns it actually produced. Under Pearson the two coincide. Under a Spearman, polychoric or mixed estimator they need not: the weights come from the latent matrix while the scores are built from the raw standardized variables, so the model-implied matrix can claim an exact 0 for Anderson–Rubin while the saved columns correlate slightly – and the saved columns are what a later analysis reads. The section is kept under PCA, worded to name which methods reproduce the component correlations – regression and Ten Berge exactly, measured – rather than to speak of an indeterminacy a component does not have, because under Bartlett and Anderson–Rubin it carries information the correlation matrix above it does not.
Schmid-Leiman transformation
The transformation is psych::omega()'s own re-fit, run under the reproducibility seed, and the card records what it ran. The primary loadings never reach it: omega() is handed the correlation matrix, the factor count, the extraction and the rotation, and re-extracts and re-rotates the solution before schmid() transforms it – a one-factor extraction of the first-order factors’ correlation matrix supplies the second-order loadings, each item’s general loading is its first-order loading times its factor’s second-order loading, and its group loading the first-order loading times the square root of what that leaves. The hierarchy is therefore a separate fit whose loadings, Heywood cases and convergence are its own, which is why the card carries its own diagnostics section and why the first-order loadings it starts from are not the primary card’s whenever the two are normalized differently. The call is seeded because every rotation omega() accepts here goes through GPArotation’s random starts. Two group factors is a real identification limit rather than a numerical one: one factor correlation determines two second-order loadings only if they are set equal – each is √φ – and omega() does exactly that, so the card warns at exactly two; the item-level g column still varies because it multiplies each item’s own first-order loading.
Validation. On the café’s oblimin solution taste loads 0.77 on Food inside the transformation against 0.76 on a normalized primary card, and the two second-order loadings are √φ = 0.64 each.
The orthogonal rotations and Quartimin are withheld because omega() cannot run them, Biquartimin because it already imposes a general factor, and one factor because there is nothing to partial out. omega() throws object 'obminfact' not found for every orthogonal rotation and for quartimin – verified live – so the option is offered under oblimin, promax, simplimax, cluster, geominQ and bentlerQ alone, from one list that the checkbox’s visibility and the R-side gate both read, so the two cannot drift apart. Biquartimin does not throw, but schmid() would then rotate the first-order solution with the Jennrich–Bentler bifactor criterion, which imposes a general factor of its own, and a second-order decomposition on top of that is confounded – the Step 1 note says the rest. A single factor has no group factor for the general one to be partialled from, so ωh would be ωt by construction; the checkbox is hidden at a count of 1 and the R side refuses the same case, since a hatch caller bypasses the panel.
Kaiser normalization stops at the primary solution, and schmid() normalizes Promax and Cluster on its own. The module’s normalization sandwich is applied around a rotation it drives itself; schmid() hardcodes its internal fa() call, so the flag never reaches the transformation, and normalize is deliberately not passed to omega(), which would accept and ignore it. What the transformation does instead is fixed by the rotation: promax routes through psych::kaiser(), which is the normalization sandwich itself, and cluster through stats::varimax(), whose default is normalize = TRUE, so those two are always normalized; oblimin, simplimax, geominQ and bentlerQ go to GPArotation, which defaults to normalize = FALSE, so those four never are. The two solutions are scaled alike only where the checkbox happens to agree with schmid()'s convention, and the card says so in either direction – normalization requested and not applied, or applied unasked – rather than letting two differently scaled matrices pass for comparable; with the checkbox’s own default, cleared for every rotation but varimax, the second fires under Promax and Cluster.
Unweighted least squares, alpha factoring and MINCHI are replaced by minimum residual inside the transformation, and a failed oblique rotation by Promax – both read off psych and named on the card. schmid() accepts a narrower set of extractions than fa(): uls and alpha are outside its fm set and throw outright, and minchi is accepted but silently degraded, because omega() drops the pairwise-count matrix that extraction needs. Minimum residual is the substitute in every case – it minimizes very nearly the ULS criterion, and is what omega() already falls back to on its own – and one map supplies it to both omega() calls, the fit and its bootstrap, to the card’s title and to the citation, so what ran and what the card claims cannot drift; the primary solution keeps the extraction chosen. The rotation can degrade too: schmid() wraps oblimin, geominQ and bentlerQ in try() and substitutes Promax when the criterion fails, announcing it with message() rather than warning() – as it announces its own non-convergence – so the module reads both from a calling handler, records the rotation that ran, and titles the card with it.
Items omega() reverse-scores are restored in the loadings and left flipped in the coefficients. omega() runs with flip = TRUE: an item whose loading on the general factor is negative is negated before the coefficients are estimated and its row name marked with a trailing -. That is the convention published ωh values follow – leaving a reverse-worded item unflipped would push ωh down for a reason that has nothing to do with reliability – so the coefficients are kept on the re-keyed items. The loadings are not: the module restores each flipped row’s original signs and strips the marker, so the table and the diagram read in the items’ own direction and a negative g loading stays negative, and it names the flipped items in a warning so the reader knows which items the coefficients describe re-keyed. The bootstrap does the same per resample, since omega() may flip a different set on each one, before the congruence matching below compares replicate with original; ECV is a sum of squares and unaffected either way.
ωh, ωh/ωt and the subscale omegas are graded on the bifactor literature’s bars, ωt and ωs on the reliability ladder, and ωhs on none. Omega hierarchical reads strong from 0.80, moderate from 0.50 and weak below – general-factor bands rather than a reliability verdict, because ωh measures saturation by g, not reliability. Omega total and each subscale’s ωs are score reliabilities and go through the same grading as every coefficient on the reliability module, so a value reads Questionable or Acceptable here as it would there and the two modules never grade one number two ways. ωh/ωt takes the one bar the literature states for it, 0.80, above which a total score may be treated as essentially unidimensional (Rodriguez, Reise and Haviland, 2016), with no middle band because none is published. ωhs is left unlabelled on purpose: those same cutoffs mislead on a residualized quantity, where a small value is a finding about the general factor’s reach rather than a defective subscale, and a label would call a well-behaved subscale defective. The subscale rows are pushed unconditionally, an NA – a group factor that collapsed – rendered as a muted — rather than the row dropped, because a silently absent ωhs row reads as “that subscale is fine”. The subscale values come from omega()'s omega.group table: its group column is ωhs, with g partialled out, and its total column ωs, by Rodriguez, Reise and Haviland’s definitions.
“Essentially unidimensional” needs ECV ≥ 0.70 and a corroborating PUC or ωh, and PUC is counted from a data-driven key. ECV alone cannot carry the verdict (Rodriguez, Reise and Haviland, 2016): its bias is small enough for it to stand on its own only when most item pairs are uncontaminated, so above PUC 0.80 the bar is ECV alone, below it ωh must reach 0.70 as well, and where PUC cannot be computed ECV stands alone. The 0.70 bar is deliberately above the ECV > 0.60 of Reise, Scheines, Widaman and Haviland (2013), so a solution some sources would call unidimensional is not labelled so here, and the legend under the decomposition says so; below the bar the bands are moderate from 0.50 and substantial under it. PUC is the share of the item pairs whose items belong to different group factors, and it is normally quoted against an a priori subscale key; the module has none, so each item is assigned to the group factor it loads on most strongly in absolute value, ties to the first, and the legend states the assignment. Every ECV – the general factor’s and each group factor’s – is that column’s sum of squares over the sum of all of them.
Coefficient H is computed per column of the transformed loadings, with each squared loading pinned below 1. over a factor’s own loadings (Hancock and Mueller, 2001) – how well an optimally weighted composite of its indicators would recover it, which is why it is read as construct replicability rather than reliability. It is computed for g and for every group factor from that column alone, and graded for g only – well-defined from 0.80, adequately defined from 0.70, poorly defined below – the group factors’ H reported unlabelled beside each subscale’s omegas. A loading at or above 1 in absolute value would make its term infinite, so λ² is capped at 0.9999 first, the same clamp the reliability module’s coefficient H applies, which keeps a Heywood case finite rather than blanking the coefficient.
The variance decomposition is over the common variance, and its total tracks the sum of the main solution’s communalities without having to equal it. The total is over the transformed loadings – the sum of the hierarchical table’s h² column, by construction – so the general and group rows add up to it exactly and every percentage is a share of common variance rather than of the number of variables; the legend says so because a reader used to the variance explained table expects the other denominator. schmid() forms each item’s general loading as fload %*% gload, summing across the first-order factors before squaring, so an item that cross-loads carries cross-factor terms into that square which the group residual, fload² (1 − gload²), does not reproduce, and those terms match the main solution’s only where the second-order factor reproduces the factor correlations exactly. Under simple structure there are no such terms, and at two factors the one second-order pair reproduces the one correlation, so the total equals the sum of the communalities; the gap opens with cross-loadings from three factors up, on top of whatever the separate fit and its normalization move.
Validation. The café’s solution meets both conditions – two factors, simple structure – and its total of 2.65 equals the sum of its communalities.
The omega intervals share the loadings’ bootstrap, and the subscale ones are realigned by congruence before pooling. Both halves draw the same resamples and share one correlation matrix per replicate – under a polychoric or mixed estimator that rebuild is most of the cost, and two loops would pay it twice – and each replicate re-runs omega() in full with the same extraction, rotation and count, because a coefficient’s sampling variability comes through the transformation as much as through the matrix. ωh, ωt, ωh/ωt, ECV and H for g are single numbers that do not depend on how the group factors are ordered and are pooled as they come; ωhs, ωs and each group factor’s H are tied to specific group factors, which can swap places or flip sign from one resample to the next, so each replicate’s group columns are assigned to the original ones by maximum-weight matching on Tucker’s congruence – the same step the loading intervals take – and its subscale values recorded in that order. That alignment is itself an estimate, which is why the note under the table says to read the subscale intervals with more caution. The interval is the loadings’ bias-corrected percentile one, and the same 50-usable-replicates floor applies – but here per coefficient: a replicate whose omega() failed contributes nothing to any row, while one that lost a group factor contributes to the global rows and leaves NA in that subscale’s, so every interval carries its own count, the note beneath the table reports the smallest, and a — marks a bound withheld for that row alone. The table has no header row, so the level is stated in the note rather than over the column.
Hierarchical scores are factor.scores() on the transformed loadings with the correlation matrix as rho, and their determinacy is the same formula with the loadings as the structure matrix. The scores are computed by the selected method on the g and group columns as the primary scores are on the pattern matrix, in a block of their own so that a scoring failure keeps the transformation’s card and is reported separately. The SL factors are orthogonal, so the structure matrix the determinacy formula needs is the loadings matrix itself, and there is no Φ for Anderson–Rubin scores to contradict, which is why the scoring caveat is drawn only while primary scores are on. Determinacy is drawn here because it is part of the standard bifactor reporting set (Reise, Bonifay and Haviland, 2013) and because it is exactly where these scores are weakest: within each block the general column is near-proportional to the group column by construction, so the weights that separate them are poorly determined, and for the same reason Bartlett and Anderson–Rubin routinely fail on the SL matrix, so the failure message is split by method as the primary one is. The inserted columns are named SLT_g, SLT_F1… with the method suffixed as the primary insert does, so a dataset holding both sets says which is which.
Validation. The café’s hierarchical scores read 0.67, 0.69 and 0.66 in determinacy against 0.86 and 0.84 for the primary regression scores.
Extraction method comparison
The dialog inherits Step 3’s factor count, rotation and normalization, validates them before it opens, and ticks the running extraction rather than forcing it. A modal covers the page, so none of the three fields is reachable while it is open; the comparison reads them silently, which is why the dialog states them in a line, and why the guards – at least three numerical variables, a count within the Ledermann bound, no bifactor rotation below three factors – run on the button that opens it, where a toast can still be acted on, and again in the runner, which the dialog’s own button and a headless caller reach directly. They run in the order Run full analysis applies them, so the two buttons report the same first problem rather than two different ones. The bound is the EFA one whatever Step 1 selected, because every compared row goes through fa(), and a PCA user who reaches it from a count Step 3 accepted is told that the sweep, not Step 3, is the limit. The extraction Step 3 is set to is ticked on opening rather than forced, because the card is a robustness check on the solution the user is running and can only be that if the sweep contains it, while a sweep that deliberately leaves it out is a legitimate question too; ML, PA and MINRES are the shipped ticks because they are the three most widely used, the set the dialog’s own button offers.
Every row is the Step 3 solution refitted under another extraction, rotated and normalized as the main card is. Each method is asked for an unrotated fit and its loadings are rotated by the module’s own rotation under the Step 3 Kaiser normalization setting – a single factor and None (unrotated) stay on psych’s own path, since there is nothing to rotate – so the row whose extraction Step 3 ran reproduces the main card’s complexity and unassigned count, and the fit statistics, which are rotation-invariant, cost nothing by it; a rotation that fails is reported as that method’s failed row rather than aborting the sweep. Complexity and the unassigned count are therefore graded on the rotation chosen, not on the varimax copy the model comparison table uses, and coincide with Step 3’s solution quality for the extraction that ran. Cumulative % is the sum of squared loadings over p, with Φ folded in under an oblique rotation as psych does for its own Vaccounted, which makes it the mean communality exactly; it is not the sum of the extraction’s eigenvalues, because under alpha factoring those belong to psych’s internally rescaled matrix and would read 1.1 to 1.3 times every other row as a pure scaling artifact. The sweep pins np.obs so that MINCHI runs with its uniform weights rather than being downgraded to minres, and loads Rcsdp for minimum rank.
Best is relative and decided on the printed value, colour is absolute, and both are withheld where they would mislead. Bold marks the best of the methods compared, per column, on each value rounded to the precision its column prints, so the bolding can never contradict two numbers that print alike; the χ²-derived columns are bolded like the others – the χ² cell itself carries none – and the legend says what that bolding means, since under every method but ML they are approximations of a criterion ML alone optimizes. Bold alone would tell a reader which extraction fits best and not whether any of them fits, so RMSEA, CFI, TLI and SRMR are coloured on top of it against the model fit cutoffs – the absolute bands Step 2 and Step 3 grade by – and the colour is withheld on df ≤ 0 rows, where the model reproduces the matrix by construction, exactly as the single-solution tables withhold their verdicts. An ultra-Heywood solution is excluded from the best-value comparison and its row tinted red, because a communality above 1 is not a fit but a failure of the model; a Heywood case is a caution, tinted yellow and still eligible, read by the same rules as Step 3, so a solution flagged on one card cannot pass clean on the other. A method that could not be fitted keeps its row, labelled Fitting failed with psych’s message on hover, so a rotation–count incompatibility is not mislabelled as non-convergence; one that hit the iteration cap is daggered rather than dropped, since it returned loadings, and the cap arrives as a message rather than a warning, so it is caught by its own handler. Principal axis and alpha factoring are retried from unit communalities when their SMC start fails and marked with an asterisk, because a fit from other starting conditions is a valid fit with a comparability caveat rather than an admissibility problem – which is also why the asterisk tints nothing in either table.
Under PCA the component solution is fitted once as the congruence anchor: no fit row, first in every pair, and reported when it fails. Setting a component solution against the common-factor extractions is the robustness check the literature asks of it – does the component structure survive an extraction that models unique variance – so the dialog is kept under PCA and the ticked methods stay common-factor. The component solution is fitted with principal() and rotated as the extractions are, but gets no row in the fit table: a component model has no unique variances, so χ², RMSEA, TLI and the information criteria are undefined for it, and a row of dashes would read as a failed fit. It heads every pair and is the reference the columns are numbered by, because it is the solution the user is actually running. A fit that fails leaves the sweep to pick its own reference and sends psych’s message to the card’s analysis-info line in red, because the dialog promised the anchor and a card that looked like an ordinary EFA sweep would misreport what was compared. The factor count is bounded by the EFA Ledermann bound even though Step 3 accepted more components, since every compared row is a common-factor fit.
Validation. On the café items the two components read 0.995 to 0.998 against each extraction’s factors.
Congruence is computed pairwise on an exact matching, numbered by one alignment against the reference, and reported unsigned on ten digits. Factor numbering is arbitrary – nothing makes one method’s first factor another’s – so each pair’s coefficients are read off a maximum-weight assignment of the two solutions’ factors, the pairing Tucker’s coefficient is defined on, by an exact assignment rather than a greedy nearest match, which can pair one factor twice. If the column labels also came from each pair’s own matching, F1 would name a different factor from one row to the next once three or more methods are compared, so the labels come from a single alignment of every solution against the reference – the component anchor when there is one, otherwise the medoid, the method whose matched congruence with all the others is highest – and only the permutation is carried over; the legend names the reference. The coefficients are absolute values because a reflected factor is the same factor, which psych::factor.congruence() does not do – it returns the signed coefficient, so a cross-check can show a large negative where the table shows a large positive. digits = 10 is passed because psych rounds to two decimals by default, which lands values exactly on the 0.85 and 0.95 cut-points; the module rounds for display and reads the tier off the value as printed, so a colour or verdict never contradicts the number beside it. A degenerate factor with no loadings makes factor.congruence() return NaN, which is treated as no similarity rather than left to abort the matching.
Validation. On the café items the medoid is principal axis factoring.
The summary reports the worst single factor beside the pair means, splits under PCA, and carries every caveat of the pairs it averages. “Can be considered equal” is a statement about a factor, not a solution (Lorenzo-Seva & ten Berge, 2006): a three-factor comparison whose factors replicate at 0.99, 0.99 and 0.67 has a respectable mean and one factor that is not the same construct in the two solutions, and averaging it into its pair’s mean and then minimising over the means hides it twice, so the lowest coefficient any single factor reached in any pair is a fourth figure, tier-coloured like the others. With a component anchor the pairs answer two questions – whether the extractions agree with each other and whether the component structure survives them – and one mean over both is decided by whichever group has more pairs, the anchor’s with two extractions and the extractions’ with eight; the summary is therefore split, and left whole under EFA or when a single extraction survived, where there is nothing to tell apart. Every pair enters the figures whatever its flags, so a pair involving a Heywood or inadmissible solution, an SMC = FALSE retry or a non-convergent fit appends its caution to the summary itself rather than leaving it to the legend under the table, which a reader who stopped at Excellent would never reach; the table prints each row’s mean and not its minimum, since the minimum’s job is done by that fourth figure.
Setting interactions
Dependent options are revealed by what they depend on and cleared when hidden; only the factor correlations box is ticked on reveal, and a bifactor rotation is reset under PCA rather than left disabled. The Φ row carries no checked attribute in the markup, and Φ is the defining output of an oblique solution, so it would never be shown unless the box were set when the row appears; it is set on that transition alone, because a later factor-count pick re-evaluates the same rule and must not re-tick a box the user cleared. The Schmid-Leiman sub-options and the loadings interval are cleared when their parent is turned off rather than merely hidden, because a hidden box that stayed ticked would still run – and the loadings interval is the expensive option on the form, re-fitting the whole solution on every replication. Show values on diagram keeps its tick, because it costs nothing and its default should survive the diagram being toggled. Under PCA the bifactor family is disabled in the dropdown and a selected member reset to Varimax, with the normalization default that goes with it, because a disabled option left selected would reach the fit and be refused there; the other rotations stay, since principal() returns Φ and a structure matrix for an oblique one. Kaiser normalization’s reset on a rotation change and its round trip through a single factor are argued above, the scoring method’s fix at one factor with the scoring guards, and the count ceiling’s re-publication under Requirements.
Missing data
Cases are dropped listwise whatever the missing-data setting says, and the suitability card discloses the cost. Every number the module reports is a decomposition of one correlation matrix, so the matrix has to be a single, consistent estimate: one assembled pairwise, each correlation on its own complete cases, need not be positive definite and carries no one sample size for the fit statistics, the parallel-analysis draws and the cases-to-variables ratio to read. The R side therefore runs na.omit() over the selected variables before anything else – in Step 2, Step 3 and the extraction comparison alike – and the setting’s pairwise option is not offered here. Listwise and imputation are honoured because they act on the dataset before the module sees it: the global listwise pass deletes on every variable selected in the Variables dialog, a net at least as wide as the module’s own, and imputation leaves no blank to drop. The suitability card is the only card that states the sample beside the rows the module’s own pass removed, so the count is reported there and nowhere else; the score tables map their rows back to the original row numbers, so an inserted score column lands on the case it belongs to.
Reproducibility
The citation list follows the run, not the repertoire, and names the criterion that ran rather than the option. A reference list naming every method the module offers would be useless as a methods paragraph, so each citation is emitted by the code path that ran: psych always; GPArotation only for a rotation psych delegates to it – verified live against deparse(), which leaves Varimax (stats::varimax) and Cluster (varimax followed by psych::target.rot, credited to the package row) as the only ones genuinely free of it, Promax not among them, since PCA and EFA alike run it through psych::Promax, which loads GPArotation, and the Schmid-Leiman transformation adding no case of its own; Rcsdp under Minimum rank, whose communalities come from the semidefinite program it solves; MASS when the anti-image matrices were asked for, since they are built on its ginv(). The rotation is cited by the criterion psych actually optimises rather than the dropdown’s name – Equamax is Crawford–Ferguson at , Bifactor and Biquartimin (oblique bifactor) are both the Jennrich–Bentler bi-quartimin criterion, Cluster is varimax – and Kaiser normalization only when the module’s own sandwich ran, which is any rotation, not psych’s varimax-only flag. Minimum sample size weighted χ² cites no method of its own, since psych’s weighting has no literature beyond the package. A Mixed run cites exactly the estimators that ran on the column mix, read back from the dispatch’s own buckets – Pearson while a continuous column is present, polychoric while a polytomous one is, tetrachoric only where two dichotomous columns met, polyserial and biserial for the continuous-by-ordinal pairs – because which of them ran is a property of the data, not of the option. The retention rules, the fit indices, the scoring method, determinacy, Tucker’s congruence and the bootstrap are each cited only when their card was produced, and the grading benchmarks only while the interpretation setting is on – so two runs that differ in one option carry different reference lists, and the list can be pasted as the Method section’s own.