I am writing a function that will use the fa()
function from the "psych" package. When fa()
fails to converge, I want my function to discard the results and return an empty plot that just says "convergence failed".
I have found that fa() has a warnings=
option that will make it print a message when it fails to converge, but I haven't found a way to programmatically check the results returned by fa()
for convergence.
Inside my function, I make the call as fa(...)
passing whatever options to it that the user set when calling my function. So users may be using different factor extraction methods, different values for max.iter, etc.
When using the Principal Axis factor extraction method (eg. fm='pa'
), the object returned by fa()
includes a vector of values produced by iterations called communality.iterations
, so then I can do this:
results <- fa(...)
# Check for Principal Axis factor extraction failing to converge
fa_args <- list(...) # Capture whatever the user gave my function to pass to fa()
if('fm' %in% names(fa_args) && fa_args$fm=='pa') {
max_iter <- ifelse('max.iter' %in% names(fa_args),fa_args$max.iter,50) # 50 is the default if not set
if(length(results$communality.iterations)==max_iter) { # Handle convergence failure
warning('Factor Analysis convergence failed for plot ',plot_title,'.',immediate.=T)
return(ggplot() + theme_void() + labs(title=plot_title,subtitle='Convergence failed'))
}
}
But that only covers one factor extraction method the user might select and does so in a pretty convoluted way.
Is there a better way to check fa()
results for failure to converge?
Note: Sorry I can't provide a reproducible example. I can tell from the documentation that other factor extraction methods like maximum likelihood can fail to converge, but I haven't found a way to make it happen. Here is one attempt that didn't manage to produce a warning about convergence failure:
library('psych')
bad_corr <- matrix(0,nrow=50,ncol=50)
diag(bad_corr) <- 1
x <- fa(r=bad_corr,nfactors=8,fm='ml',warnings=T)
Difficult without a reproducible example, but:
Looking at the code, it seems that warnings
actually throws messages, rather than warnings (these are different). If the package actually threw warnings in this case, you could use options(warn = 2)
to escalate warnings to errors, then use try()
or tryCatch()
to intercept and handle them as you saw fit.
Since these are not warnings, and there is no equivalent way to escalate messages, you might try using capture.output()
around your call to fa()
and using grep
(or equivalent tools from stringr
) to detect specific problems and take the appropriate actions ...