@@ -51,7 +51,7 @@ test_that("exp_factanal with default orthogonal varimax rotation", {
5151 # New report tidy types (issue #37018).
5252 res <- model_df %> % tidy_rowwise(model , type = " suitability" )
5353 expect_equal(colnames(res ), c(" Metric" , " Value" , " Judgement" , " Description" , " status" ))
54- expect_equal(res $ Metric , c(" KMO" , " Bartlett's Test of Sphericity" , " Rows Used" , " Variables Used" ))
54+ expect_equal(res $ Metric , c(" KMO" , " Bartlett's Test of Sphericity (P Value) " , " Rows Used" , " Variables Used" )) # #37340
5555 res <- model_df %> % tidy_rowwise(model , type = " factor_count" )
5656 expect_equal(colnames(res ), c(" Method" , " Recommended Number of Factors" , " Description" ))
5757 expect_equal(res $ Method , c(" Kaiser Criterion" , " Parallel Analysis" , " Scree Plot" ))
@@ -422,12 +422,14 @@ test_that("parallel analysis method: factor_model vs smc (issue #37332)", {
422422 fc_smc <- smc_model_df %> % tidy_rowwise(model , type = " factor_count" )
423423 expect_equal(fc_smc $ Description [[2 ]], " Number of factors whose SMC-based factor eigenvalue exceeds the random-data threshold." )
424424
425- # analysis_method table carries the new row.
425+ # analysis_method table carries the new row. Read BY ITEM NAME: #37340 moved the two data counts
426+ # to the top of the table, so the row's position is no longer 4.
427+ am_value <- function (tbl , item ) tbl $ Value [[which(tbl $ Item == item )]]
426428 am_factor_model <- model_df %> % tidy_rowwise(model , type = " analysis_method" )
427- expect_equal( am_factor_model $ Item [[ 4 ]], " Parallel Analysis Method" )
428- expect_equal(am_factor_model $ Value [[ 4 ]] , " Factor Model" )
429+ expect_true( " Parallel Analysis Method" %in% am_factor_model $ Item )
430+ expect_equal(am_value( am_factor_model , " Parallel Analysis Method " ) , " Factor Model" )
429431 am_smc <- smc_model_df %> % tidy_rowwise(model , type = " analysis_method" )
430- expect_equal(am_smc $ Value [[ 4 ]] , " Diagonal SMC" )
432+ expect_equal(am_value( am_smc , " Parallel Analysis Method " ) , " Diagonal SMC" )
431433
432434 # factanal_parallel_method_label: NULL degrades to the factor_model default; unknown -> Not Available.
433435 expect_equal(factanal_parallel_method_label(NULL ), " Factor Model" )
@@ -440,5 +442,89 @@ test_that("parallel analysis method: factor_model vs smc (issue #37332)", {
440442 legacy_fit <- fit
441443 legacy_fit $ parallel_method <- NULL
442444 legacy_am <- tidy(legacy_fit , type = " analysis_method" )
443- expect_equal(legacy_am $ Value [[4 ]], " Factor Model" )
445+ # #37340 moved the two data counts to the top, so read the row BY NAME rather than by position.
446+ expect_equal(legacy_am $ Value [[which(legacy_am $ Item == " Parallel Analysis Method" )]], " Factor Model" )
447+ })
448+
449+ test_that(" report part 3: variances_judged, suitability P value format, analysis_method order (issue tam#37340)" , {
450+ model_df <- mtcars %> %
451+ exp_factanal(mpg , cyl , disp , hp , drat , wt , qsec , nfactors = 2 , fm = " minres" ,
452+ rotate = " varimax" , cor_type = " pearson" , parallel_n_iter = 5 )
453+ fit <- model_df $ model [[1 ]]
454+
455+ # --- variances_judged: one row per correlation-matrix eigenvalue (per VARIABLE, like PCA's PCn),
456+ # so every candidate factor is judged -- not just the extracted ones.
457+ judged <- tidy(fit , type = " variances_judged" )
458+ expect_equal(colnames(judged ),
459+ c(" Factor" , " Eigenvalue" , " % Variance" , " Cummulated % Variance" ,
460+ " Parallel Analysis" , " Kaiser Criterion" , " Adoption" ,
461+ " parallel_status" , " kaiser_status" , " selected_status" ))
462+ n_var <- length(fit $ communality )
463+ expect_equal(nrow(judged ), n_var )
464+ expect_equal(judged $ Factor , as.character(seq_len(n_var )))
465+ # Eigenvalues come off eigen(x$correlation) -- the SAME basis as screeplot / parallel_screeplot,
466+ # descending, and the ratios are eigenvalue shares summing to 100%.
467+ expect_equal(judged $ Eigenvalue , eigen(fit $ correlation , symmetric = TRUE , only.values = TRUE )$ values )
468+ expect_equal(sum(judged $ `% Variance` ), 100 )
469+ expect_equal(judged $ `Cummulated % Variance` , cumsum(judged $ `% Variance` ))
470+ expect_equal(judged $ `Cummulated % Variance` [[n_var ]], 100 )
471+ # Kaiser mirrors the factor_count branch's kaiser_n (eigenvalue > 1) and is ALWAYS judged for
472+ # factor analysis (always a correlation matrix) -- never "na" the way covariance-scaled PCA is.
473+ expect_equal(sum(judged $ kaiser_status == " adopted" ), sum(judged $ Eigenvalue > 1 ))
474+ expect_false(any(judged $ kaiser_status == " na" ))
475+ # Selected = the factors this analysis actually extracted (nfactors), first rows only.
476+ expect_equal(judged $ selected_status , ifelse(seq_len(n_var ) < = 2 , " adopted" , " not_adopted" ))
477+ expect_equal(judged $ Adoption , ifelse(seq_len(n_var ) < = 2 , " Adopted" , " Not Adopted" ))
478+ # Labels stay English-canonical; the client translates them. "Adopted" (not PCA's "Adopt") so the
479+ # English report reads as a judgment against "Not Adopted".
480+ expect_true(all(judged $ `Parallel Analysis` %in% c(" Adopted" , " Not Adopted" , " Not Available" )))
481+ expect_true(all(judged $ `Kaiser Criterion` %in% c(" Adopted" , " Not Adopted" )))
482+ expect_true(all(judged $ parallel_status %in% c(" adopted" , " not_adopted" , " na" )))
483+ # Parallel analysis unavailable (old saved model) degrades to Not Available / na, same shape.
484+ no_par <- fit
485+ no_par $ parallel <- NULL
486+ judged_no_par <- tidy(no_par , type = " variances_judged" )
487+ expect_equal(unique(judged_no_par $ `Parallel Analysis` ), " Not Available" )
488+ expect_equal(unique(judged_no_par $ parallel_status ), " na" )
489+ expect_equal(nrow(judged_no_par ), n_var )
490+
491+ # --- analysis_method: counts first, then the method rows (#37340).
492+ method_tbl <- tidy(fit , type = " analysis_method" )
493+ expect_equal(method_tbl $ Item , c(" Number of Variables" , " Row Count" , " Correlation" ,
494+ " Factor Extraction Method" , " Rotation" , " Parallel Analysis Method" ))
495+ expect_equal(method_tbl $ Value [[1 ]], as.character(n_var ))
496+ expect_equal(method_tbl $ Value [[2 ]], as.character(nrow(mtcars )))
497+ expect_equal(method_tbl $ Value [[3 ]], " Pearson Correlation" )
498+
499+ # --- suitability: Metric names the P value, the Value cell holds the value ONLY, and the
500+ # small-p threshold is 0.0001 (was 0.001).
501+ suit <- tidy(fit , type = " suitability" )
502+ expect_equal(suit $ Metric [[2 ]], " Bartlett's Test of Sphericity (P Value)" )
503+ expect_false(grepl(" p" , suit $ Value [[2 ]], fixed = TRUE )) # no "p < " / "p = " prefix
504+ expect_true(grepl(" ^(< 0\\ .0001|[0-9]+\\ .[0-9]{4}|N/A)$" , suit $ Value [[2 ]]))
505+ fake <- fit
506+ fake $ bartlett <- list (p.value = 1e-9 )
507+ expect_equal(tidy(fake , type = " suitability" )$ Value [[2 ]], " < 0.0001" )
508+ fake $ bartlett <- list (p.value = 0.00005 )
509+ expect_equal(tidy(fake , type = " suitability" )$ Value [[2 ]], " < 0.0001" )
510+ # Just ABOVE the threshold: 4 fixed decimals, never scientific notation ("2e-04").
511+ fake $ bartlett <- list (p.value = 0.0002 )
512+ expect_equal(tidy(fake , type = " suitability" )$ Value [[2 ]], " 0.0002" )
513+ fake $ bartlett <- list (p.value = 0.03 )
514+ expect_equal(tidy(fake , type = " suitability" )$ Value [[2 ]], " 0.0300" )
515+ fake $ bartlett <- NULL
516+ expect_equal(tidy(fake , type = " suitability" )$ Value [[2 ]], " N/A" )
517+ })
518+
519+ test_that(" report part 3: an over-parameterized fit legitimately has no fit test (issue tam#37340)" , {
520+ # 4 factors on 7 variables drives the model's degrees of freedom NEGATIVE, so psych::fa returns
521+ # no chi-square P value, no RMSEA and no BIC. The report must present that as "not available"
522+ # (and must NOT claim a hypothesis-test verdict) -- these blanks are the true output of an
523+ # over-parameterized fit, not a serialization bug, so glance is intentionally left as-is.
524+ model_df <- mtcars %> %
525+ exp_factanal(mpg , cyl , disp , hp , drat , wt , qsec , nfactors = 4 , fm = " minres" ,
526+ rotate = " varimax" , cor_type = " pearson" , parallel_n_iter = 3 )
527+ g <- glance(model_df $ model [[1 ]])
528+ expect_true(g $ DF < = 0 )
529+ expect_true(is.na(g $ `P Value` ))
444530})
0 commit comments