/* GPFOBL: oblique rotation for factor analysis by: HervŽ M. CACI (hcaci@wanadoo.fr) version 1.0 -- Date: February 19th, 2003 Translated from an SPSS macro for OBLIMIN only by Coen A. Bernaards & Robert I. Jennrich http://www.stat.ucla.edu/research version 1.1 -- Date: March 15th, 2003 added GAMMA and FACTOR options version 1.2 -- Date: March 23rd, 2003 bugs correction & other rotation criteria added version 1.3 -- Date: May 2nd, 2003 added Hyperplane Count on rotated loadings */ program define gpfobl version 5.0 *! Version 1.3 -- May 2nd, 2003 /* Parse standard STATA commands */ parse "`*'", parse(" ,") confirm exist `1' matrix A0=`1' mac shift 1 /* Parse options */ #delimit ; local options "Factor(int -1) OBLIMIN(real -2) OBLIMAX CFerguson(real -2) Geomin(real -2) IPS"; #delimit cr parse "`*'" /* Extensive check of the rotation criterion */ di in gr _n "Gradient Projection Algorithm for arbitrary rotation criteria" _n local subr=0 if "`oblimax'" != "" { di in gr _n "OBLIMAX criterion" _n _dup(17) "-" local subr=1 } if `oblimin' != -2 { if `subr'!=0 { di in red "More than one criterion specified" exit } if `oblimin' == .50 { di in gr _n "OBLIMIN rotation" _n _dup(16) "-" local gamma = 0.50 } else if `oblimin' == 0 { di in gr _n "QUARTIMIN rotation" _n _dup(18) "-" _n local gamma=0.00 } else if `oblimin' == 1 { di in gr _n "COVARIMIN rotation" _n _dup(18) "-" _n local gamma = 1 } else if `oblimin'<-1.00 | `oblimin'>1.00 { di in red "Gamma is out of range." exit } else { di in gr "OBLIMIN rotation" _n _dup(16) "-" local gamma=`oblimin' di in gr "Gamma parameter = " in ye %5.4f `gamma' } local subr=2 } if `geomin' != -2 { if `subr'!=0 { di in red "More than one criterion specified." exit } di in gr _n "GEOMIN criterion" _n _dup(16) "-" if `geomin'<-1.00 | `geomin'>1.00 { di in red "Epsilon is out of range." exit } local subr=3 local epsilon=`geomin' di in gr "Epsilon parameter = " in ye %5.4f `epsilon' } if "`ips'"!="" { di in gr _n "Bentler's Invariant Pattern Simplicity" di in gr _dup(38) "-" if `subr'!=0 { di in red "More than one criterion specified." exit } local subr=4 } if `cferguson' != -2 { if `subr'!=0 { di in red "More than one criterion specified." exit } di in gr _n "Crawford-Ferguson criterion" _n _dup(27) "-" if `cferguson'==0 { di in gr _n "Kappa = 0 -> Quartimax" } if `cferguson'==1 { di in gr _n "Kappa = 1 -> Factor parsimony" } if `cferguson'<-1.00 | `cferguson'>1.00 { in red "Kappa is out of range." exit } else { di in gr "Kappa = " in ye %3.2f `cferguson' } local kappa=`cferguson' local subr=5 } if `subr'==0 { di in red "No criterion specified." exit } /* To date, the Tmat matrix is IDENTITY... may change in the future */ di in gr "(Note: Initial normal rotation matrix is IDENTITY by default)." matrix Tmat=I(colsof(A0)) /* FACTOR option */ if `factor'==-1 { local factor=colsof(A0) } if `factor'>colsof(A0) { di in red "Too many factors specified" exit } if `factor'<2 { di in red "Not enough factors specified" exit } matrix A=A0[.,1..`factor'] matrix Tmat=Tmat[1..`factor',1..`factor'] /* Main program starts here */ local maxiter=500 local a1=1 mat Ti=inv(Tmat) mat L=A*Ti' local k=colsof(L) /* Number of factors */ local p=rowsof(L) /* Number of variables */ di in gr _n "Number of Factors : " %3.0f in ye `k' di in gr "Number of Variables: " %3.0f in ye `p' if `k'<2 {di in red _n "Too small number of factors..." exit} if `k'>=`p' {di in red _n "Not enough variables for that number of factor..." exit} /* Call the appropriate subroutine */ global ft=0 mat Gq=J(`p',`k',0) if `subr'==1 { oblimax L Gq } else if `subr'==2 { oblimin L `gamma' Gq } else if `subr'==3 { geomin L `epsilon' Gq } else if `subr'==4 { ips L Gq } else if `subr'==5 { cferg L `kappa' Gq } /* Long loop */ local f=$ft matrix G=-(L'*Gq*Ti)' local iter=0 while `iter'<=`maxiter' { matrix csum=J(1,`k',0) matrix TG=J(`k',`k',0) local i=1 while `i'<=`k' { local j=1 while `j'<=`k' { matrix TG[`i',`j']=Tmat[`i',`j']*G[`i',`j'] matrix csum[1,`j']=csum[1,`j']+TG[`i',`j'] local j=`j'+1 } local i=`i'+1 } matrix Gp=G-Tmat*diag(csum) matrix Y=Gp'*Gp local s=sqrt(trace(Y)) /* di "`iter' | $ft | `s1' | `a1'" */ if int(`iter'/5)==(`iter'/5) { di in ye "." _c } local s1=log10(`s') if `s'<0.00001 { di in gr _n "*** Algorithm converged after " %3.0f in ye `iter' /* */ in gr " iterations ***" local iter=`maxiter'+10 } else { local a1=`a1'*2 local i=0 while `i'<11 { matrix X=J(`k',`k',0) matrix X2=J(`k',`k',0) matrix csum=J(1,`k',0) local x=1 /* This loop computes X=Tmat-a1&*Gp */ while `x'<=`k' { local y=1 while `y'<=`k' { matrix X[`x',`y']=Tmat[`x',`y']-(`a1'*Gp[`x',`y']) matrix X2[`x',`y']=X[`x',`y']*X[`x',`y'] matrix csum[1,`y']=csum[1,`y']+X2[`x',`y'] local y=`y'+1 } local x=`x'+1 } mat v=J(1,`k',0) local y=1 while `y'<=`k' { matrix v[1,`y']=1/sqrt(csum[1,`y']) local y=`y'+1 } matrix Tt=X*diag(v) matrix Ti=inv(Tt) matrix L=A*Ti' /* Call the appropriate subroutine */ global ft=0 mat Gq=J(`p',`k',0) if `subr'==1 { oblimax L Gq } else if `subr'==2 { oblimin L `gamma' Gq } else if `subr'==3 { geomin L `epsilon' Gq } else if `subr'==4 { ips L Gq } else if `subr'==5 { cferg L `kappa' Gq } /* Common part continues... */ matrix G=-(L'*Gq*Ti)' local f0=`f'-(`s'*`s'*`a1')/2 if $ft>`f0' { local a1=`a1'/2 } else if $ft<`f0' { local i=10 } /* Break the loop... */ local i=`i'+1 } /* End of the `i' loop */ matrix Tmat=Tt local f=$ft matrix G=-(L'*Gq*Ti)' } /* end of "else" statement... */ local iter=`iter'+1 } if `iter'==`maxiter'+1 { di in red _n "*** Algorithm didn't converge after `maxiter' iterations." } else { di in gr _n "Transformation matrix" mat list Tmat, format(%4.3f) noheader di in gr _n "Factor loadings after rotation" mat list L, format(%4.3f) noheader di in gr _n "Factor intercorrelations" mat phi=Tmat'*Tmat mat list phi, format(%4.3f) noheader } /* Compute the hyperplane counts per factor */ mat HCOUNT=J(1,`k',0) local i=1 while `i'<=`k' { local j=1 local s=0 while `j'<=`p' { if abs(L[`j',`i'])<=.100 { local s=`s'+1 } local j=`j'+1 } mat HCOUNT[1,`i']=`s' local i=`i'+1 } di in gr _n "Hyperplane count: loadings in range [-0.10 ; +0.10]" mat list HCOUNT,format(%3.0f) noheader end /* OBLIMIN subroutine: Matrixname gamma Gq */ program define oblimin local k=colsof(`1') /* Number of factors */ local p=rowsof(`1') /* Number of variables */ mat N=J(`k',`k',1)-I(`k') mat L2=J(`p',`k',0) mat Y=J(`p',`p',-`2'/`p') local x=1 while `x'<=`p' { mat Y[`x',`x']=1-(`2'/`p') local y=1 while `y'<=`k' { mat L2[`x',`y']=L[`x',`y']*L[`x',`y'] local y=`y'+1 } local x=`x'+1 } mat Y=Y*L2*N mat F=J(`p',`k',0) mat G=J(`p',`k',0) local x=1 local f1=0 while `x'<=`p' { local y=1 while `y'<=`k' { mat F[`x',`y']=L2[`x',`y']*Y[`x',`y'] local f1=`f1'+F[`x',`y'] mat G[`x',`y']=L[`x',`y']*Y[`x',`y'] local y=`y'+1 } local x=`x'+1 } global ft=`f1'/4 matrix `3'=G end /* OBLIMAX subroutine: Matrixname Gq */ program define oblimax local k=colsof(`1') /* Number of factors */ local p=rowsof(`1') /* Number of variables */ local i=1 local L2=0 /* msum(L&*L) */ local L4=0 /* msum(L&**4) */ while `i'<=`p' { local j=1 while `j'<=`k' { local L2=`L2'+`1'[`i',`j']^2 local L4=`L4'+`1'[`i',`j']^4 local j=`j'+1 } local i=`i'+1 } global ft=-(log(`L4')-2*log(`L2')) mat G=J(`p',`k',0) local i=1 while `i'<=`p' { local j=1 while `j'<=`k' { mat G[`i',`j']=-(4*(`1'[`i',`j']^3)/`L4'-4*`1'[`i',`j']/`L2') local j=`j'+1 } local i=`i'+1 } matrix `2'=G end /* GEOMIN subroutine: Matrixname epsilon Gq */ program define geomin local k=colsof(`1') /* Number of factors */ local p=rowsof(`1') /* Number of variables */ local f1=0 mat L2=J(`p',`k',0) mat LL2=J(`p',`k',0) mat SL=J(`p',1,0) local i=1 while `i'<=`p' { local j=1 while `j'<=`k' { mat L2[`i',`j']=(`1'[`i',`j']*`1'[`i',`j'])+`2' mat LL2[`i',`j']=log(L2[`i',`j']) mat SL[`i',1]=SL[`i',1]+LL2[`i',`j'] /* RSUM(LL2) */ local j=`j'+1 } local f1=`f1'+exp(SL[`i',1]/`k') local i=`i'+1 } global ft=`f1' mat RM=LL2*J(`k',`k',1/`k') mat G=J(`p',`k',0) local i=1 while `i'<=`p' { local j=1 while `j'<=`k' { mat RM[`i',`j']=exp(RM[`i',`j']) mat G[`i',`j']=(2*`k')*(`1'[`i',`j']/L2[`i',`j'])*RM[`i',`j'] local j=`j'+1 } local i=`i'+1 } matrix `3'=G end /* Bentler's Invariant Pattern Simplicity */ program define ips local k=colsof(`1') /* Number of factors */ local p=rowsof(`1') /* Number of variables */ local f1=0 mat L2=J(`p',`k',0) local i=1 while `i'<=`p' { local j=1 while `j'<=`k' { mat L2[`i',`j']=`1'[`i',`j']*`1'[`i',`j'] local j=`j'+1 } local i=`i'+1 } mat M=L2'*L2 mat D=diag(vecdiag(M)) global ft=-(log(det(M))-log(det(D)))/4 mat G=J(`p',`k',0) mat X=L2*(inv(M)-inv(D)) local i=1 while `i'<=`p' { local j=1 while `j'<=`k' { mat G[`i',`j']=-`1'[`i',`j']*X[`i',`j'] local j=`j'+1 } local i=`i'+1 } mat `2'=G end /* CRAWFORD-FERGUSON: kappa = 0 -> Quartimax kappa = 1/p -> Varimax kappa = k/(2p) -> Equamax kappa = (k-1)/(p+k-2) -> Parsimax kappa = 1 -> Factor parsimony */ program define cferg local k=colsof(`1') /* Number of factors */ local p=rowsof(`1') /* Number of variables */ local f1=0 mat L2=J(`p',`k',0) local i=1 while `i'<=`p' { local j=1 while `j'<=`k' { mat L2[`i',`j']=`1'[`i',`j']*`1'[`i',`j'] local j=`j'+1 } local i=`i'+1 } mat N=L2*(J(`k',`k',1)-I(`k')) mat M=(J(`p',`p',1)-I(`p'))*L2 mat NN=L2'*N mat MM=L2'*M local f1=(1-`2')*trace(NN)/4 local f2=`2'*trace(MM)/4 global ft=`f1'+`f2' mat G=J(`p',`k',0) local i=1 while `i'<=`p' { local j=1 while `j'<=`k' { mat G[`i',`j']=(1-`2')*`1'[`i',`j']*N[`i',`j'] + /* */ `2'*`1'[`i',`j']*M[`i',`j'] local j=`j'+1 } local i=`i'+1 } mat `3'=G end