You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

680 lines
19 KiB
Plaintext

7 months ago
*! Confirmatory factor analysis with a single factor: v.2.2
*! Stas Kolenikov, skolenik-gmail-com
program define cfa1, eclass
version 9.1
if replay() {
if ("`e(cmd)'" != "cfa1") error 301
Replay `0'
}
else Estimate `0'
end
program Estimate `0', eclass
syntax varlist(numeric min=3) [if] [in] [aw pw / ] ///
, [unitvar FREE POSvar FROM(str) CONSTRaint(numlist) LEVel(int $S_level) ///
ROBust VCE(string) CLUster(passthru) SVY SEArch(passthru) * ]
* syntax: cfa1 <list of the effect indicators>
* untivar is for the identification condition of the unit variance of the latent variable
unab varlist : `varlist'
tokenize `varlist'
local q: word count `varlist'
marksample touse
preserve
qui keep if `touse'
* weights!
global CFA1N = _N
/*
* we'll estimate the means instead
qui foreach x of varlist `varlist' {
sum `x', meanonly
replace `x' = `x'-r(mean)
* deviations from the mean
}
*/
if "`weight'" != "" {
local mywgt [`weight'=`exp']
}
if "`robust'`cluster'`svy'`weight'"~="" {
local needed 1
}
else {
local needed 0
}
Parse `varlist' , `unitvar'
local toml `r(toml)'
if "`from'" == "" {
local from `r(tostart)', copy
}
* identification
constraint free
global CFA1constr `r(free)'
if "`unitvar'" ~= "" {
* identification by unit variance
constraint $CFA1constr [phi]_cons = 1
}
else if "`free'"=="" {
* identification by the first variable
constraint $CFA1constr [`1']_cons = 1
}
else {
* identification imposed by user
global CFA1constr
}
local nconstr : word count `constraint'
global CFA1PV = ("`posvar'" != "")
if "`posvar'" ~= "" {
di as text _n "Fitting the model without restrictions on error variances..."
}
* variance estimation
local vce = trim("`vce'")
if "`vce'" == "boot" local vce bootstrap
if "`vce'" == "sbentler" {
global CFA1SBV = 1
local vce
}
else {
if index("robustoimopg","`vce'") {
local vce vce(`vce')
}
else {
di as err "`vce' estimation is not supported"
if "`vce'" == "bootstrap" | "`vce'" == "boot" {
di as err "try {help bootstrap} command directly"
}
exit 198
}
}
tempname ilog1 ilog2
Tryit ml model lf cfa1_lf `toml' `mywgt', constraint($CFA1constr `constraint') ///
init (`from') maximize nooutput `options' `search' ///
`svy' `cluster' `robust' `vce'
/*
ml model lf cfa1_lf `toml' `mywgt', ///
constraint($CFA1constr `constraint') `svy' `robust' `cluster' ///
maximize
* ml check
* ml search, rep(5)
ml init `from', copy
cap noi ml maximize , `options'
*/
mat `ilog1' = e(ilog)
local nz = 0
if $CFA1PV {
* determine if refitting the model is needed
tempname ll_unr
scalar `ll_unr' = e(ll)
forvalues i=1/`q' {
if [``i''_v]_cons <0 {
constraint free
local ccc = `r(free)'
const define `ccc' [``i''_v]_cons = 0
local zerolist `zerolist' `ccc'
local ++nz
}
}
local zerolist = trim("`zerolist'")
global CFA1constr $CFA1constr `zerolist'
if "`zerolist'" ~= "" {
di as text _n "Fitting the model with some error variances set to zero..."
Tryit ml model lf cfa1_lf `toml' `mywgt' , constraint($CFA1constr `constraint') ///
init (`from') maximize nooutput `options' `search' ///
`svy' `robust' `cluster' `vce'
mat `ilog2' = e(ilog)
}
* adjust degrees of freedom!
}
* we better have this before Satorra-Bentler
if "`unitvar'" ~= "" {
ereturn local normalized Latent Variance
}
else if "`free'"=="" {
ereturn local normalized `1'
}
* work out Satorra-Bentler estimates
if "$CFA1SBV"!="" {
* repost Satorra-Bentler covariance matrix
tempname SBVar SBV Delta Gamma
cap SatorraBentler
if _rc {
di as err "Satorra-Bentler standard errors are not supported for this circumstance; revert to vce(oim)"
global CFA1SBV
}
else {
mat `SBVar' = r(SBVar)
mat `Delta' = r(Delta)
mat `Gamma' = r(Gamma)
mat `SBV' = r(SBV)
ereturn repost V = `SBVar'
ereturn matrix SBGamma = `Gamma', copy
ereturn matrix SBDelta = `Delta', copy
ereturn matrix SBV = `SBV', copy
ereturn local vce SatorraBentler
}
}
* get the covariance matrix and the number of observations!
***********************************************************
tempname lambda vars phi S Sindep Sigma trind eb
qui mat accum `S' = `varlist', dev nocons
mat `S' = `S' / $CFA1N
* implied matrix
mat `eb' = e(b)
mat `lambda' = `eb'[1,1..`q']
mat `vars' = `eb'[1,`q'+1..2*`q']
scalar `phi' = `eb'[1,3*`q'+1]
mat `Sigma' = `lambda''*`phi'*`lambda' + diag(`vars')
mat `Sindep' = diag(vecdiag(`S'))
* test against independence
mat `trind' = trace( syminv(`Sindep') * `S' )
local trind = `trind'[1,1]
ereturn scalar ll_indep = -0.5 * `q' * $CFA1N * ln(2*_pi) - 0.5 * $CFA1N * ln(det(`Sindep')) - 0.5 * $CFA1N * `trind'
ereturn scalar lr_indep = 2*(e(ll)-e(ll_indep))
ereturn scalar df_indep = `q'-`nz'-`nconstr'
ereturn scalar p_indep = chi2tail(e(df_indep),e(lr_indep))
* goodness of fit test
ereturn scalar ll_u = -0.5 * `q' * $CFA1N * ln(2*_pi) - 0.5 * $CFA1N * ln(det(`S')) - 0.5 * `q' * $CFA1N
ereturn scalar lr_u = -2*(e(ll)-e(ll_u))
ereturn scalar df_u = `q'*(`q'+1)*.5 - (2*`q' - `nz' - `nconstr')
* wrong if there are any extra constraints in -constraint- command!!!
ereturn scalar p_u = chi2tail(e(df_u),e(lr_u))
ereturn matrix ilog1 `ilog1'
cap ereturn matrix ilog2 `ilog2'
* Satorra-Bentler corrections
if "$CFA1SBV"!="" {
* compute the corrected tests, too
* Satorra-Bentler 1994
tempname U trUG2 Tdf
mat `U' = `SBV' - `SBV'*`Delta'*syminv(`Delta''*`SBV'*`Delta')*`Delta''*`SBV'
ereturn matrix SBU = `U'
mat `U' = trace( e(SBU)*`Gamma' )
ereturn scalar SBc = `U'[1,1]/e(df_u)
ereturn scalar Tscaled = e(lr_u)/e(SBc)
ereturn scalar p_Tscaled = chi2tail( e(df_u), e(Tscaled) )
mat `trUG2' = trace( e(SBU)*`Gamma'*e(SBU)*`Gamma')
ereturn scalar SBd = `U'[1,1]*`U'[1,1]/`trUG2'[1,1]
ereturn scalar Tadj = ( e(SBd)/`U'[1,1]) * e(lr_u)
ereturn scalar p_Tadj = chi2tail( e(SBd), e(Tadj) )
* Yuan-Bentler 1997
* weights!
ereturn scalar T2 = e(lr_u)/(1+e(lr_u)/e(N) )
ereturn scalar p_T2 = chi2tail( e(df_u), e(T2) )
}
if "`posvar'" ~= "" {
ereturn scalar lr_zerov = 2*(`ll_unr' - e(ll))
ereturn scalar df_zerov = `nz'
local replay_opt posvar llu(`ll_unr')
}
ereturn local cmd cfa1
Replay , `replay_opt' level(`level')
Finish
restore
ereturn repost, esample(`touse')
end
program define Tryit
cap noi `0'
local rc=_rc
if `rc' {
Finish
exit `rc'
}
end
program define Finish
* finishing off
constraint drop $CFA1constr
global CFA1S
global CFA1N
global CFA1PV
global CFA1theta
global CFA1arg
global CFA1data
global CFA1constr
global CFA1vars
global CFA1SBV
end
program define Replay
syntax, [posvar llu(str) level(passthru)]
di _n as text "Log likelihood = " as res e(ll) _col(59) as text "Number of obs = " as res e(N)
di as text "{hline 13}{c TT}{hline 64}"
di as text " {c |} Coef. Std. Err. z P>|z| [$S_level% Conf. Interval]"
di as text "{hline 13}{c +}{hline 64}"
tempname vce
mat `vce' = e(V)
local q = colsof(`vce')
local q = (`q'-1)/3
local a : colfullnames(`vce')
tokenize `a'
di as text "Lambda{col 14}{c |}"
forvalues i = 1/`q' {
gettoken v`i' : `i' , parse(":")
_diparm `v`i'' , label("`v`i''") prob `level'
}
di as text "Var[error]{col 14}{c |}"
forvalues i = 1/`q' {
_diparm `v`i''_v , label("`v`i''") prob `level'
}
di as text "Means{col 14}{c |}"
forvalues i = 1/`q' {
_diparm `v`i''_m , label("`v`i''") prob `level'
}
di as text "Var[latent]{col 14}{c |}"
_diparm phi , label("phi1") prob
di as text "{hline 13}{c +}{hline 64}"
di as text "R2{col 14}{c |}"
forvalues i = 1/`q' {
di as text %12s "`v`i''" "{col 14}{c |}{col 20}" ///
as res %6.4f (_b[`v`i'':_cons]^2*_b[phi:_cons]) / ///
(_b[`v`i'':_cons]^2*_b[phi:_cons] + _b[`v`i''_v:_cons])
}
di as text "{hline 13}{c BT}{hline 64}"
if e(df_u)>0 {
di as text _n "Goodness of fit test: LR = " as res %6.3f e(lr_u) ///
as text _col(40) "; Prob[chi2(" as res %2.0f e(df_u) as text ") > LR] = " as res %6.4f e(p_u)
}
else {
di as text "No degrees of freedom to perform the goodness of fit test"
}
di as text "Test vs independence: LR = " as res %6.3f e(lr_indep) ///
as text _col(40) "; Prob[chi2(" as res %2.0f e(df_indep) as text ") > LR] = " as res %6.4f e(p_indep)
if "`e(vce)'" == "SatorraBentler" & e(df_u)>0 {
* need to report all those corrected statistics
di as text _n "Satorra-Bentler Tbar" _col(26) "= " as res %6.3f e(Tscaled) ///
as text _col(40) "; Prob[chi2(" as res %2.0f e(df_u) as text ") > Tbar] = " as res %6.4f e(p_Tscaled)
di as text "Satorra-Bentler Tbarbar" _col(26) "= " as res %6.3f e(Tadj) ///
as text _col(40) "; Prob[chi2(" as res %4.1f e(SBd) as text ") > Tbarbar] = " as res %6.4f e(p_Tadj)
di as text "Yuan-Bentler T2" _col(26) "= " as res %6.3f e(T2) ///
as text _col(40) "; Prob[chi2(" as res %2.0f e(df_u) as text ") > T2] = " as res %6.4f e(p_T2)
}
if "`posvar'" ~= "" {
* just estimated?
if "`llu'" == "" {
di as err "cannot specify -posvar- option, need to refit the whole model"
}
else {
if e(df_zerov)>0 {
di as text "Likelihood ratio against negative variances: LR = " as res %6.3f e(lr_zerov)
di as text "Conservative Prob[chi2(" as res %2.0f e(df_zerov) as text ") > LR] = " ///
as res %6.4f chi2tail(e(df_zerov),e(lr_zerov))
}
else {
di as text "All variances are non-negative, no need to test against zero variances"
}
}
}
end
program define Parse , rclass
* takes the list of variables and returns the appropriate ml model statement
syntax varlist , [unitvar]
global CFA1arg
global CFA1theta
global CFA1vars
local q : word count `varlist'
* lambdas
forvalues i = 1/`q' {
local toml `toml' (``i'': ``i'' = )
local tostart `tostart' 1
global CFA1arg $CFA1arg g_``i''
global CFA1theta $CFA1theta l_`i'
global CFA1vars $CFA1vars ``i''
}
* variances
forvalues i = 1/`q' {
local toml `toml' (``i''_v: )
local tostart `tostart' 1
global CFA1arg $CFA1arg g_``i''_v
global CFA1theta $CFA1theta v_`i'
}
* means
forvalues i = 1/`q' {
local toml `toml' (``i''_m: )
qui sum ``i'', mean
local mean = r(mean)
local tostart `tostart' `mean'
global CFA1arg $CFA1arg g_``i''_m
global CFA1theta $CFA1theta m_`i'
}
* variance of the factor
local toml `toml' (phi: )
local tostart `tostart' 1
global CFA1arg $CFA1arg g_Phi
global CFA1theta $CFA1theta phi
* done!
return local toml `toml'
return local tostart `tostart'
end
**************************** Satorra-Bentler covariance matrix code
program SatorraBentler, rclass
version 9.1
syntax [, noisily]
* assume the maximization completed, the results are in memory as -ereturn data-
* we shall just return the resulting matrix
if "`e(normalized)'" == "" {
di as err "cannot compute Satorra-Bentler variance estimator with arbitrary identification... yet"
exit 198
}
* assume sample is restricted to e(sample)
* preserve
* keep if e(sample)
* get the variable names
tempname VV bb
mat `VV' = e(V)
local q = rowsof(`VV')
local p = (`q'-1)/3
local eqlist : coleq `VV'
tokenize `eqlist'
forvalues k=1/`p' {
local varlist `varlist' ``k''
}
* compute the implied covariance matrix
tempname Lambda Theta phi Sigma
mat `bb' = e(b)
mat `Lambda' = `bb'[1,1..`p']
mat `Theta' = `bb'[1,`p'+1..2*`p']
scalar `phi' = `bb'[1,`q']
mat `Sigma' = `Lambda''*`phi'*`Lambda' + diag(`Theta')
* compute the empirical cov matrix
tempname SampleCov
qui mat accum `SampleCov' = `varlist' , nocons dev
* weights!!!
mat `SampleCov' = `SampleCov' / (r(N)-1)
* compute the matrix Gamma
`noisily' di as text "Computing the Gamma matrix of fourth moments..."
tempname Gamma
SBGamma `varlist'
mat `Gamma' = r(Gamma)
return add
* compute the duplication matrix
* Dupl `p'
* let's call it from within SBV!
* compute the V matrix
`noisily' di as text "Computing the V matrix..."
SBV `SampleCov' `noisily'
tempname V
mat `V' = r(SBV)
return add
* compute the Delta matrix
`noisily' di as text "Computing the Delta matrix..."
tempname Delta
mata : SBDelta("`bb'","`Delta'")
*** put the pieces together now
tempname DeltaId
* enact the constraints!
SBconstr `bb'
mat `DeltaId' = `Delta' * diag( r(Fixed) )
* those should be in there, but it never hurts to fix!
if "`e(normalized)'" == "Latent Variance" {
* make the last column null
mat `DeltaId' = ( `DeltaId'[1...,1...3*`p'] , J(rowsof(`Delta'), 1, 0) )
}
else if "`e(normalized)'" ~= "" {
* normalization by first variable
local idvar `e(normalized)'
if "`idvar'" ~= "`1'" {
di as err "cannot figure out the identification variable"
exit 198
}
mat `DeltaId' = ( J(rowsof(`Delta'), 1, 0) , `DeltaId'[1...,2...] )
}
local dcnames : colfullnames `bb'
local drnames : rownames `Gamma'
mat colnames `DeltaId' = `dcnames'
mat rownames `DeltaId' = `drnames'
return matrix Delta = `DeltaId', copy
tempname VVV
mat `VVV' = ( `DeltaId'' * `V' * `DeltaId' )
mat `VVV' = syminv(`VVV')
mat `VVV' = `VVV' * ( `DeltaId'' * `V' * `Gamma' * `V' * `DeltaId' ) * `VVV'
* add the covariance matrix for the means, which is just Sigma/_N
* weights!
tempname CovM
mat `CovM' = ( J(2*`p',colsof(`bb'),0) \ J(`p',2*`p',0) , `Sigma', J(`p',1,0) \ J(1, colsof(`bb'), 0) )
mat `VVV' = (`VVV' + `CovM')/_N
return matrix SBVar = `VVV'
end
* of satorrabentler
program define SBGamma, rclass
syntax varlist
unab varlist : `varlist'
tokenize `varlist'
local p: word count `varlist'
forvalues k=1/`p' {
* make up the deviations
* weights!!!
qui sum ``k'', meanonly
tempvar d`k'
qui g double `d`k'' = ``k'' - r(mean)
local dlist `dlist' `d`k''
}
local pstar = `p'*(`p'+1)/2
forvalues k=1/`pstar' {
tempvar b`k'
qui g double `b`k'' = .
local blist `blist' `b`k''
}
* convert into vech (z_i-bar z)(z_i-bar z)'
mata : SBvechZZtoB("`dlist'","`blist'")
* blist now should contain the moments around the sample means
* we need to get their covariance matrix
tempname Gamma
qui mat accum `Gamma' = `blist', dev nocons
* weights!
mat `Gamma' = `Gamma'/(_N-1)
mata : Gamma = st_matrix( "`Gamma'" )
* make nice row and column names
forvalues i=1/`p' {
forvalues j=`i'/`p' {
local namelist `namelist' ``i''_X_``j''
}
}
mat colnames `Gamma' = `namelist'
mat rownames `Gamma' = `namelist'
return matrix Gamma = `Gamma'
end
* of computing Gamma
program define SBV, rclass
args A noisily
tempname D Ainv V
local p = rowsof(`A')
`noisily' di as text "Computing the duplication matrix..."
mata : Dupl(`p',"`D'")
mat `Ainv' = syminv(`A')
mat `V' = .5*`D''* (`Ainv' # `Ainv') * `D'
return matrix SBV = `V'
end
* of computing V
* need to figure out whether a constraint has the form parameter = value,
* and to nullify the corresponding column
program define SBconstr, rclass
args bb
tempname Iq
mat `Iq' = J(1,colsof(`bb'),1)
tokenize $CFA1constr
while "`1'" ~= "" {
constraint get `1'
local constr `r(contents)'
gettoken param value : constr, parse("=")
* is the RHS indeed a number?
local value = substr("`value'",2,.)
confirm number `value'
* parse the square brackets and turn them into colon
* replace the opening brackets with nothing, and closing brackets, with :
local param = subinstr("`param'","["," ",1)
local param = subinstr("`param'","]",":",1)
local param = trim("`param'")
local coln = colnumb(`bb',"`param'" )
mat `Iq'[1,`coln']=0
mac shift
}
return matrix Fixed = `Iq'
end
cap mata : mata drop SBvechZZtoB()
cap mata : mata drop Dupl()
cap mata : mata drop SBDelta()
mata:
void SBvechZZtoB(string dlist, string blist) {
// view the deviation variables
st_view(data=.,.,tokens(dlist))
// view the moment variables
// blist=st_local("blist")
st_view(moments=.,.,tokens(blist))
// vectorize!
for(i=1; i<=rows(data); i++) {
B = data[i,.]'*data[i,.]
moments[i,.] = vech(B)'
}
}
void Dupl(scalar p, string Dname) {
pstar = p*(p+1)/2
Ipstar = I(pstar)
D = J(p*p,0,.)
for(k=1;k<=pstar;k++) {
D = (D, vec(invvech(Ipstar[.,k])))
}
st_matrix(Dname,D)
}
void SBDelta(string bbname, string DeltaName) {
bb = st_matrix(bbname)
p = (cols(bb)-1)/3
Lambda = bb[1,1..p]
Theta = bb[1,p+1..2*p]
phi = bb[1,cols(bb)]
Delta = J(0,cols(bb),.)
for(i=1;i<=p;i++) {
for(j=i;j<=p;j++) {
DeltaRow = J(1,cols(Delta),0)
for(k=1;k<=p;k++) {
// derivative wrt lambda_k
DeltaRow[k] = (k==i)*Lambda[j]*phi + (j==k)*Lambda[i]*phi
// derivative wrt sigma^2_k
DeltaRow[p+k] = (i==k)*(j==k)
}
DeltaRow[cols(Delta)] = Lambda[i]*Lambda[j]
Delta = Delta \ DeltaRow
}
}
st_matrix(DeltaName,Delta)
}
end
* of mata piece
***************************** end of Satorra-Bentler covariance matrix code
exit
History:
v.1.0 -- May 19, 2004: basic operation, method d0
v.1.1 -- May 19, 2004: identification by -constraint-
common -cfa1_ll-
from()
v.1.2 -- May 21, 2004: method -lf-, robust
constraint free
v.1.3 -- unknown
v.1.4 -- Feb 15, 2005: pweights, arbitrary constraints
v.2.0 -- Feb 28, 2006: update to version 9 using Mata
v.2.1 -- Apr 11, 2006: whatever
v.2.2 -- Apr 13, 2006: Satorra-Bentler standard errors and test corrections
-vce- option
Apr 14, 2006: degrees of freedom corrected for # constraints
July 5, 2006: minor issue with -from(, copy)-