Nothing
##Developed 12/1/2025 - 1/18/2026
# Closed form estimation of Confirmatory factoring
#code taken from equations for Spearman method by
#CFA from Dhaene and Rosseel (SEM, 2024 (31) 1-13)
# D and R discuss multiple approaches but recommend the Spearman algorithm as best
# follows a paper by Guttman
#modified 3/6/26 to work with CFA using iterations
#########
CFA.bifactor <- function(
model=NULL,
x=NULL,
all=FALSE,
g=FALSE,
cor="cor",
use="pairwise",
n.obs=NA,
orthog=FALSE,
weight=NULL,
correct=0,
method="regression",
missing=FALSE,
impute="none",
Grice=FALSE ,
n.iter=1) {
cl <- match.call() #save for result
keys <- model
#first some housekeeping
r <- x
if(is.list(model)) {X <- make.keys(r,model)} else {if(is.matrix(model)) {X <- model} else {X <- lavParse(model)} #just a matrix of 1, 0, -1
X[X >0] <- 1
X[X < 0] <- -1 #just a matrix of 1, 0, -1
} #converts the keys variables to 1, 0 matrix (if not already in that form)
if( is.list(model)) {select <- sub("-","",unlist(keys))} else {
select<- rownames(X)} # to speed up scoring one set of scales from many items
if(any( !(select %in% colnames(x)) )) {
cat("\nVariable names in keys are incorrectly specified. Offending items are ", select[which(!(select %in% colnames(x)))],"\n")
stop("I am stopping because of improper input. See above for a list of bad item(s). ")}
if(!all) {x <-x[,select]
X <-X[select,,drop=FALSE]}
nfact <- ncol(X)
#now, we use the keys to reverse code some items
#do we need to find correlations, and if so, to flip them?
original.data <- x #save these for second pass
if(!isCovariance(x)) {n.obs <- nrow(x)
switch(cor,
cor = {if(!is.null(weight)) {r <- cor.wt(x,w=weight)$r} else {
r <- cor(x,use=use)}
},
cov = {if(!is.null(weight)) {r <- cor.wt(r,w=weight,cor=FALSE)$r} else {
r <- cov(x,use=use)}
covar <- TRUE}, #fixed 10/30/25
wtd = { r <- cor.wt(x,w=weight)$r},
spearman = {r <- cor(x,use=use,method="spearman")},
kendall = {r <- cor(x,use=use,method="kendall")},
tet = {r <- tetrachoric(x,correct=correct,weight=weight)$rho},
poly = {r <- polychoric(x,correct=correct,weight=weight)$rho},
tetrachoric = {r <- tetrachoric(x,correct=correct,weight=weight)$rho},
polychoric = {r <- polychoric(x,correct=correct,weight=weight)$rho},
mixed = {r <- mixedCor(x,use=use,correct=correct)$rho},
Yuleb = {r <- YuleCor(x,,bonett=TRUE)$rho},
YuleQ = {r <- YuleCor(x,1)$rho},
YuleY = {r <- YuleCor(x,.5)$rho }
)
}
#flip correlations to go along with directions of keys
nvar <- ncol(r)
flip <- X %*% rep(1,ncol(X)) #combine all keys into one to allow us to select the over all model
flipd <- diag(nrow=ncol(r), ncol=ncol(r))
rownames(flipd) <- colnames(flipd)<- colnames(r)
diag(flipd)[rownames(flipd) %in% rownames(flip)] <- flip
#in the interesting case of no general factor we need to flip some variables
#We do not need to flip them here, because they are flipped in CFA
#r <- flipd %*% r %*% t(flipd) #negatively keyed variables are flipped
if(!g) {
gf <- CFA(x=r,all=TRUE, n.obs=n.obs,n.iter=n.iter)
gf.ci <- gf$ci
gf<- gf$cfa
null.chisq <- gf$stats$null.chisq #this is null of original matrix
n.obs <- gf$stats$n.obs
resid.g <- gf$residual
diag(resid.g) <- 1
group <- CFA(keys,resid.g,all=all,n.obs=n.obs,Grice=Grice,n.iter=n.iter)
gr.ci <- group$ci
group <- group$cfa
bifactor <- cbind(gf$loadings,gr.ci$median)
h2 <- rowSums(bifactor^2)
colnames(bifactor)[1] <- "g"
om.stats <- omegaStats(r, gload=gf$loading,group=gr.ci$median,h2=h2,Phi=group$Phi) #to calculate omega statistics
group$tats$dof <- dof <- nvar*(nvar-1)/2 - nvar - sum(abs(X))
R2 <- fsi(bifactor,r=r,Grice=Grice)
#loadings replaced with median loadings from the iterated CFA solution
scores <- factor.scores(x=original.data,f=cbind(gf.ci$median,gr.ci$median), Phi=NULL,method=method, missing=missing,impute=impute, Grice=Grice)
stats=group$stats
group$stats$dof <- dof
result <- list(loadings=bifactor,communality=h2,n.obs=n.obs,
stats=group$stats,
dof=dof,
Phi=group$Phi, R2=R2,
omegaStats = om.stats,
r = r,
scores= scores$scores,
weights=scores$weights,
rms = stats$rms,
RMSEA =stats$RMSEA[1],
chi = stats$chi,
BIC = stats$BIC,
STATISTIC = stats$STATISTIC,
null.chisq = null.chisq,
null.dof = stats$null.dof,
gf.ci = gf.ci$ci ,
gr.ci= gr.ci$ci,
X=X,
Call=cl)
} else { #the hierarachical case
group <- CFA(model,x=r,all=all,n.obs=n.obs,Grice=Grice,n.iter=n.iter)
gr.ci <- group$ci
group <- group$cfa
gload <- CFA(x=group$Phi,all=all,n.obs=group$stats$n.obs,Grice=Grice,n.iter=n.iter)
gf.ci <- gload$ci
gload <- gload$cfa
loadings <- group$loadings
h2 <- rowSums(loadings^2)
n.obs<- group$n.obs
null.chisq <- group$stats$null.chisq #this is null of original matrix
dof <- nvar*(nvar-1)/2 - sum(abs(X)) - nfact #
omegaStats <- NULL # fix omegaStats for this case
scores <- factor.scores(x=original.data,f=cbind(group$loadings),
Phi=NULL,method=method,
missing=missing,impute=impute, Grice=Grice)
stats=group$stats
stats$dof <- dof
stats$STATISTIC <- stats$STATISTIC + gload$stats$STATISTIC
stats$BIC <- stats$BIC + gload$stats$BIC
result <- list(loadings=loadings,communality=h2 ,gload=gload,
Phi = group$Phi, n.obs=n.obs,
stats=stats,
dof=dof,
omegaStats = omegaStats,
r=r,
scores= scores$scores,
weights=scores$weights,
rms = stats$rms,
RMSEA =stats$RMSEA[1],
chi = stats$chi,
BIC = stats$BIC,
STATISTIC = stats$STATISTIC,
null.chisq = stats$null.chisq,
null.dof = stats$null.dof,
gf.ci = gf.ci$ci ,
gr.ci= gr.ci$ci,
X=X,
Call=cl)}
class(result) <- c("psych", "CFA.bifactor")
return(result)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.