@@ -165,6 +165,75 @@ reliability_prepare_correlation_data <- function(data, item_types) {
165165# Correlation matrix builder
166166# ------------------------------------------------------------
167167
168+ # psych::polychoric / mixedCor can fail on sparse contingency tables when the
169+ # continuity correction (default correct=0.5) produces empty pairwise results.
170+ # psych itself suggests retrying with correct=0; without that retry the failure
171+ # surfaces as cryptic errors like "attempt to set 'rownames' on an object with
172+ # no dimensions" or "supply both 'x' and 'y' or a matrix-like 'x'".
173+ reliability_valid_rho <- function (rho , expected_n = NULL ) {
174+ rho <- tryCatch(as.matrix(rho ), error = function (e ) NULL )
175+ if (is.null(rho ) || is.null(dim(rho )) || any(dim(rho ) == 0 )) {
176+ return (FALSE )
177+ }
178+ if (! is.null(expected_n ) && (nrow(rho ) != expected_n || ncol(rho ) != expected_n )) {
179+ return (FALSE )
180+ }
181+ TRUE
182+ }
183+
184+ reliability_estimate_polychoric_rho <- function (prepared_data , correct = 0.5 ) {
185+ expected_n <- ncol(prepared_data )
186+ run <- function (corr ) {
187+ obj <- suppressWarnings(psych :: polychoric(prepared_data , correct = corr ,
188+ smooth = FALSE , global = FALSE ))
189+ obj $ rho
190+ }
191+ warnings_list <- character ()
192+ rho <- tryCatch(run(correct ), error = function (e ) NULL )
193+ if (! reliability_valid_rho(rho , expected_n ) && correct != 0 ) {
194+ rho <- tryCatch(run(0 ), error = function (e ) NULL )
195+ if (reliability_valid_rho(rho , expected_n )) {
196+ warnings_list <- c(
197+ warnings_list ,
198+ " Polychoric correlation used continuity correction of 0 because the default correction failed." )
199+ }
200+ }
201+ if (! reliability_valid_rho(rho , expected_n )) {
202+ stop(" Failed to estimate polychoric correlations. Check that items are ordinal " ,
203+ " with enough responses across categories." )
204+ }
205+ list (rho = rho , warnings = warnings_list )
206+ }
207+
208+ reliability_estimate_mixed_rho <- function (prepared_data , item_types , correct = 0.5 ) {
209+ expected_n <- ncol(prepared_data )
210+ continuous_index <- match(names(item_types )[item_types == " continuous" ], names(prepared_data ))
211+ polytomous_index <- match(names(item_types )[item_types == " ordinal" ], names(prepared_data ))
212+ dichotomous_index <- match(names(item_types )[item_types == " dichotomous" ], names(prepared_data ))
213+ run <- function (corr ) {
214+ obj <- suppressWarnings(psych :: mixedCor(
215+ prepared_data ,
216+ c = continuous_index , p = polytomous_index , d = dichotomous_index ,
217+ correct = corr , global = FALSE ))
218+ obj $ rho
219+ }
220+ warnings_list <- character ()
221+ rho <- tryCatch(run(correct ), error = function (e ) NULL )
222+ if (! reliability_valid_rho(rho , expected_n ) && correct != 0 ) {
223+ rho <- tryCatch(run(0 ), error = function (e ) NULL )
224+ if (reliability_valid_rho(rho , expected_n )) {
225+ warnings_list <- c(
226+ warnings_list ,
227+ " Mixed correlation used continuity correction of 0 because the default correction failed." )
228+ }
229+ }
230+ if (! reliability_valid_rho(rho , expected_n )) {
231+ stop(" Failed to estimate mixed correlations. Check that items have enough " ,
232+ " responses across categories." )
233+ }
234+ list (rho = rho , warnings = warnings_list )
235+ }
236+
168237reliability_build_correlation <- function (data , item_types , method ,
169238 smooth = TRUE , correct = 0.5 ) {
170239 prepared_data <- reliability_prepare_correlation_data(data , item_types )
@@ -174,17 +243,14 @@ reliability_build_correlation <- function(data, item_types, method,
174243 correlation_matrix <- stats :: cor(prepared_data , use = " pairwise.complete.obs" ,
175244 method = " pearson" )
176245 } else if (method == " polychoric" ) {
177- obj <- suppressWarnings( psych :: polychoric( prepared_data , correct = correct ,
178- smooth = FALSE , global = FALSE ))
179- correlation_matrix <- obj $ rho
246+ poly_result <- reliability_estimate_polychoric_rho( prepared_data , correct = correct )
247+ correlation_matrix <- poly_result $ rho
248+ warnings_list <- c( warnings_list , poly_result $ warnings )
180249 } else if (method == " mixed" ) {
181- continuous_index <- match(names(item_types )[item_types == " continuous" ], names(prepared_data ))
182- polytomous_index <- match(names(item_types )[item_types == " ordinal" ], names(prepared_data ))
183- dichotomous_index <- match(names(item_types )[item_types == " dichotomous" ], names(prepared_data ))
184- obj <- suppressWarnings(psych :: mixedCor(prepared_data ,
185- c = continuous_index , p = polytomous_index , d = dichotomous_index ,
186- correct = correct , global = FALSE ))
187- correlation_matrix <- obj $ rho
250+ mixed_result <- reliability_estimate_mixed_rho(prepared_data , item_types ,
251+ correct = correct )
252+ correlation_matrix <- mixed_result $ rho
253+ warnings_list <- c(warnings_list , mixed_result $ warnings )
188254 } else {
189255 stop(" Unsupported correlation method: " , method )
190256 }
@@ -467,6 +533,16 @@ exp_cronbach_alpha <- function(df, ..., correlation_method = "auto", check_keys
467533 item_types <- vapply(cleaned_df , reliability_detect_item_type , character (1 ))
468534 names(item_types ) <- colnames(cleaned_df )
469535
536+ # Exploratory often stores Likert items as unordered factors. Treat those as
537+ # ordinal when the user asks for polychoric/mixed, or when every selected
538+ # item is categorical (auto -> Ordinal Alpha). Keep rejecting nominal when
539+ # mixed with continuous items under auto/pearson.
540+ if (correlation_method %in% c(" polychoric" , " mixed" ) ||
541+ (correlation_method == " auto" &&
542+ all(item_types %in% c(" nominal" , " ordinal" , " dichotomous" )))) {
543+ item_types [item_types == " nominal" ] <- " ordinal"
544+ }
545+
470546 unsupported <- names(item_types )[item_types %in% c(" nominal" , " unsupported" )]
471547 if (length(unsupported ) > 0 ) {
472548 stop(" These variables cannot be used for reliability analysis (nominal/unsupported): " ,
0 commit comments