* Version 1.2 15 Aug 2025 by Manh H. B. (hbmanh9492@gmail.com) * Kezdi (2003) test for heteroscedasticity in FE model * Version 1.1 Correct degrees of freedom of tests * in the presence of perfect multicollinearity * Version 1.2 works without vech() matrix function cap program drop xttest4 program define xttest4, rclass version 11.0 syntax if "`e(cmd)'" != "xtreg" { display in red as error "last estimates not xtreg" exit 301 } if "`e(model)'" != "fe" { di in red "last estimates not xtreg, fe" exit 301 } preserve * Get xvar form cmdline (or use -indeplist- command) local cmdline `e(cmdline)' * Step 1: cmd và depvar gettoken cmd rest : cmdline // remove "xtreg" gettoken depvar rest : rest // remove depvar * Step 2: if local pos = strpos("`rest'", " if ") if (`pos' > 0) { local rest = substr("`rest'", 1, `pos' - 1) } else { * Step 3: in local pos = strpos("`rest'", " in ") if (`pos' > 0) { local rest = substr("`rest'", 1, `pos' - 1) } else { * Step 4: "["" (weight) local pos = strpos("`rest'", "[") if (`pos' > 0) { local rest = substr("`rest'", 1, `pos' - 1) } else { * Step 5: "," (option) local pos = strpos("`rest'", ",") if (`pos' > 0) { local rest = substr("`rest'", 1, `pos' - 1) } } } } fvrevar `rest' // treat factor variables, time series operators local xvar "`r(varlist)'" * Predict e_it tempvar e qui predict `e' if e(sample), e * Mark balanced subsample tempvar touse qui xtset if `e(Tcon)'==0 { qui xtbalance2 `r(panelvar)' `r(timevar)' `xvar' if e(sample) , /// gen(`touse') o(NT) } else { qui gen byte `touse'=e(sample) } qui keep if `touse' * id, time, obs tempvar id time obs qui gen `id' = `e(ivar)' if `touse' sort `id' `r(timevar)' qui by `id': gen `time'=_n if `touse' sort `id' `time' qui gen `obs'=_n * Get N, T of balanced subsample tempname NT N T K qui xtsum `e(depvar)' scalar `NT'=r(N) scalar `N'=r(n) scalar `T'=r(Tbar) scalar `K'= e(rank)-1 * Predict e_it tempvar s2 qui sum `e' , d scalar `s2'=r(Var)*(r(N)-1)/(`NT'-`N') * Time-Demeaning sort `id' `time' local xvar_dm foreach var of varlist `xvar' { tempvar `var'_m `var'_dm qui by `id': egen ``var'_m'=mean(`var') qui gen ``var'_dm' = `var'-``var'_m' local xvar_dm `xvar_dm' ``var'_dm' } capture matrix drop `Omega' `V0' `V1' `V2' `V3' `E' tempname Omega V0 V1 V2 V3 E * Omega ======================== capture drop _t* qui tab `time' if `touse', gen(_t) mat opaccum `Omega' = _t* , gr(`id') opvar(`e') nocons capture drop _t* mat `Omega' = `Omega'/`N' * V0 ======================== mat opaccum `V0' = `xvar_dm' , gr(`id') opvar(`e') nocons mat `V0' = `V0'/`N' * V1 ======================== mat glsa `V1' = `xvar_dm' , gr(`id') gl(`Omega') r(`time') nocons mat `V1' = `V1'/`N' * V2 ======================== sort `obs' mat opaccum `V2' = `xvar_dm' , gr(`obs') opvar(`e') nocons mat `V2'=`V2'*`T'/(`NT'-`N') * V3 ======================== qui mat ac `V3' = `xvar_dm' , abs(`id') nocons mat `V3' = `V3'*`s2'/`N' * vj = vech(Vj) ============= tempname v0 v1 v2 v3 if c(version) >= 18 { forvalues i=0/3 { mat `v`i'' = vech(`V`i'') } } else { forvalues i=0/3 { vec_h `V`i'' mat `v`i'' = r(v) } } * Cj ======================== tempname m C1 C2 C3 X c1i c2i c3i M1 M2 M3 scalar `m'=rowsof(`v1') mat `C1' = J(`m',`m',0) mat `C2' = J(`m',`m',0) mat `C3' = J(`m',`m',0) if c(version) >= 18 { forvalues i=1/`=`N'' { mkmat `e' if `id'==`i', mat(`E') mkmat `xvar_dm' if `id'==`i', mat(`X') * C1 mat `M1' = `X''*`E'*`E''*`X'-`X''*`Omega'*`X' mat `c1i' = vech(`M1') mat `C1' = `C1' + `c1i'*`c1i'' * C2 mat `M2' = `X''*`E'*`E''*`X'-`X''*diag(vecdiag(`E'*`E''))*`X' mat `c2i' = vech(`M2') mat `C2' = `C2' + `c2i'*`c2i'' * C3 mat `M3' = `X''*`E'*`E''*`X'-`X''*`s2'*`X' mat `c3i' = vech(`M3') mat `C3' = `C3' + `c3i'*`c3i'' } } else { forvalues i=1/`=`N'' { mkmat `e' if `id'==`i', mat(`E') mkmat `xvar_dm' if `id'==`i', mat(`X') * C1 mat `M1' = `X''*`E'*`E''*`X'-`X''*`Omega'*`X' vec_h `M1' mat `c1i' = r(v) mat `C1' = `C1' + `c1i'*`c1i'' * C2 mat `M2' = `X''*`E'*`E''*`X'-`X''*diag(vecdiag(`E'*`E''))*`X' vec_h `M2' mat `c2i' = r(v) mat `C2' = `C2' + `c2i'*`c2i'' * C3 mat `M3' = `X''*`E'*`E''*`X'-`X''*`s2'*`X' vec_h `M3' mat `c3i' = r(v) mat `C3' = `C3' + `c3i'*`c3i'' } } mat `C1' = `C1'/`N' mat `C2' = `C2'/`N' mat `C3' = `C3'/`N' * Test tempname df h1 h2 h3 h1_p h2_p h3_p scalar `df'=`K'*(`K'+1)/2+1 forvalues i=1/3 { tempname _h`i' mat `_h`i'' = `N'*(`v`i''-`v0')'*invsym(`C`i'')*(`v`i''-`v0') scalar `h`i'' = `_h`i''[1,1] scalar `h`i'_p' = 1-chi2(`df', `h`i'') } * Display di di as text "Kézdi test for heteroscedasticity in Fixed Effects Model" if `e(Tcon)'==0 { di in red "The test is performed on a balanced subsample with N = " /// `N' ", T = " `T' } di as text "{hline 60}" di as text " Test for | Statistic df P-value" di as text "{hline 60}" di as result " H1 vs. Ha " _col(19) %9.3f `h1' /// _col(30) %5.0f `df' /// _col(37) %9.3f `h1_p' di as result " H2 vs. Ha " _col(19) %9.3f `h2' /// _col(30) %5.0f `df' /// _col(37) %9.3f `h2_p' di as result " H3 vs. Ha " _col(19) %9.3f `h3' /// _col(30) %5.0f `df' /// _col(37) %9.3f `h3_p' di as text "{hline 60}" di "H1: Cross-sectional homoskedasticity." di "H2: Serially uncorrelated: e_it, x_it or both." di "H3: Homoskedasticity and serially uncorrelated." di "Ha: Heteroskedasticity." di * Return list ret scalar df1 = `df' ret scalar df2 = `df' ret scalar df3 = `df' ret scalar h1 = `h1' ret scalar h2 = `h2' ret scalar h3 = `h3' ret scalar h1_p = `h1_p' ret scalar h2_p = `h2_p' ret scalar h3_p = `h3_p' end * Define vec_h() for STATA versions not allow vech() capture program drop vec_h program define vec_h, rclass syntax name(name=matname) // Check if matrix exists capture matrix list `matname' if _rc { di as error "Matrix `matname' not found" exit 198 } // Get row/col number local n = rowsof(`matname') local k = colsof(`matname') // Check square matrix if `n' != `k' { di as error "Matrix must be square" exit 198 } // Create empty column vector tempname v matrix `v' = J(`=(`n'*(`n'+1))/2', 1, .) // Get the lower triangle elements local idx = 1 forvalues i = 1/`n' { forvalues j = 1/`i' { matrix `v'[`idx',1] = `matname'[`i',`j'] local idx = `idx' + 1 } } // Return in r() return matrix v = `v' end