*! Program to estimate the spatial lag, the spatial error, the spatial durbin, and the general spatial models with moderately large datasets
*! Author: P. Wilner Jeanty
*!
*! Version 2.0: January 6, 2014- Help file updated to Stata 12, bug fixed in reporting results for the spatial durbin model and in predict command
*! Version 1.5: January 2013   - Bug involving the if option fixed
*! Version 1.4: November, 2012 - Options if and in and posestimation commands now allowed
*! Version 1.3: October, 2012  - Error message "classdef _b_stat() in use" fixed
*! Version 1.2: February 2010  - Spatial Durbin Model added
*! Version 1.1: January 2010   - General Spatial Model also known as Spatial Mixed Model added
*! Version 1.0: Born: December 18, 2009

program define spmlreg
	version 12.0
	if replay() {
      	if "`e(cmd)'"!="spmlreg" {
            	error 301
        }
        Display `0'
	}
	else {
        Estimate `0'
	}
end
program define Estimate, eclass
	version 12.0
	syntax varlist(numeric min=2) [if] [in], Weights(str) WFrom(str) Eignvar(str) Model(str) ///
           [noLOG Robust Level(passthru) favor(str) wrho(str) EIGWrho(str) ///
		   INITRho(real 0) INITLambda(real 0) sr2 *]

	marksample touse

	if !inlist("`model'", "lag", "error", "sac", "durbin") {
   		di as err "Option {bf:model()} accepts one of the following: {bf:lag}, {bf:error}, {bf:sac}, {bf:durbin}"
   		exit
	}
	if "`model'"!="sac" & `:word count `wrho' `eigwrho''!=0 {
		di as err "Options {bf:wrho()} and {bf:eigwrho()} may only be specified with {bf:model(sac)}"
		exit 198
	}
	local usew1 :word count `wrho' `eigwrho'
	if "`model'"=="sac" & !inlist(`usew1',0,2) {
		di as err "Options {bf:wrho()} and {bf:eigwrho()} must be combined"
		exit 198
	}
	if inlist("`model'", "error", "sac") & "`robust'"!="" {
		di as err "{bf:robust} may not be combined with {bf:model(`model')}"
		exit 198
	}
	if "`model'"=="error" & "`sr2'"!="" local sr2 ""
	preserve
	cap mata: mata drop spmlreg_*
 	cap macro drop spmlreg_*
	cap drop spmlreg_*
	if inlist("`model'", "error", "sac", "durbin") cap drop wx_*
	if "`model'"=="sac" cap drop w2x_*

	mlopts mlopts, `options'
	gettoken depv indvar: varlist
	global spmlreg_nv : word count `indvar'
	global spmlreg_sr2=0
	if "`sr2'"!="" global spmlreg_sr2=1

	qui count if `touse'
	if r(N) == 0 {
        exit 2000
    }
	else local N=r(N)

	cap confirm numeric var `eignvar'
	qui sum `eignvar' if `touse'
	local LOWER=1/r(min)
	local UPPER=1/r(max)
	gen double spmlreg_eigv = `eignvar'

	capture drop wy_`depv'
	qui splagvar `depv' if `touse', wn(`weights') wfrom(`wfrom') favor(`favor') // calculate spatial lag for y
	local a_estimer "spmlreg_lag"
	local titl "Spatial Lag Model"
	global spmlreg_mdf=$spmlreg_nv

	if "`model'"=="error" {
		qui splagvar if `touse', wn(`weights') wfrom(`wfrom') ind(`indvar') favor(`favor') // calculate spatial lags for X
		local i=1
		foreach var of local indvar {
			gen double spmlreg_wx`i' = wx_`var'
			local ++i
		}
		local a_estimer "spmlreg_error"
		local titl "Spatial Error Model"
	}

	if "`model'"=="sac" {
		qui splagvar if `touse', wn(`weights') wfrom(`wfrom') ind(`indvar') favor(`favor')
		local i=1
		foreach var of local indvar {
			gen double spmlreg_wx`i' = wx_`var'
			local ++i
		}
		if `usew1'==2 {
			cap confirm numeric var `eigwrho'
			global spmlreg_w1=1
			qui gen double spmlreg_y=`depv'
			qui splagvar spmlreg_y if `touse', wn(`wrho') wfrom(`wfrom') favor(`favor')
			gen double spmlreg_w1y=wy_spmlreg_y
			gen double spmlreg_eigv1=`eigwrho'
			qui sum `eigwrho' if `touse'
			local LOWER1=1/r(min)
			local UPPER1=1/r(max)
			qui splagvar wy_spmlreg_y if `touse', wn(`weights') wfrom(`wfrom') favor(`favor')
			gen double spmlreg_w2w1y=wy_wy_spmlreg_y
		}
		else {
			global spmlreg_w1=0
			qui splagvar if `touse', wn(`weights') wfrom(`wfrom') ind(`depv') order(2) favor(`favor')
			qui gen double spmlreg_w2w2y=w2x_`depv'
		}
		local a_estimer "spmlreg_sac"
		local titl "Spatial Mixed Model"
	}

	if "`model'"=="durbin" {
		local xxlist `indvar'
		qui splagvar if `touse', wn(`weights') wfrom(`wfrom') ind(`indvar') favor(`favor')
		local titl "Spatial Durbin Model"
		unab wxxs: wx_*
		local indvar `indvar' `wxxs'
		global spmlreg_ldf=1 + $spmlreg_nv
		global spmlreg_mdf=2 * $spmlreg_nv
	}

	* Get initial values
	qui _regress `depv' `indvar' if `touse'
	tempname matinit
	if inlist("`model'", "lag", "durbin")  matrix `matinit'=e(b),`initrho',e(rmse)
	if "`model'"=="error" matrix `matinit'=e(b),`initlambda',e(rmse)
	if "`model'"=="sac" matrix `matinit'=e(b),`initrho',`initlambda',e(rmse)
	local initopt init(`matinit', copy) search(off) `log' `mlopts'
	matrix spmlreg_matols=`matinit'[1,1..$spmlreg_nv]

	tempname olsll
	scalar `olsll' = -0.5 * e(N) * (ln(2*_pi) + ln(e(rss)/e(N)) + 1)


	* Estimate the spatial lag or spatial durbin model
	if inlist("`model'", "lag", "durbin") {
		ml model lf `a_estimer' (`depv': `depv'=`indvar') (rho:) (sigma:) if `touse', `robust' ///
		maximize `initopt' title(`titl')  missing

		if $spmlreg_sr2==1 {
			tempname bet r2b brho
			matrix `bet'=e(b)
			matrix `r2b'=`bet'[1, "`depv':"]
			scalar `brho'=[rho]_cons
			cap erase spmlreg_filefeig
			cap drop spmlregy_pred
			mata: spmlreg_CalcSR2("`brho'", "`r2b'", "`indvar'", "wfrom", "weights", "`touse'")
			qui sum spmlregy_pred if `touse'
   			local NUM=r(Var)
   			qui summ `depv' if `touse'
   			local DEN=r(Var)
  			local VARRAT=`NUM'/`DEN'
   			qui correlate spmlregy_pred `depv' if `touse'
   			local SQCORR=r(rho)*r(rho)
      		ereturn scalar varRatio=`VARRAT'
      		ereturn scalar sqCorr=`SQCORR'
		}

		qui test [rho]_cons = 0
        local Wald=r(chi2)

		if "`model'"=="durbin" {
			qui testparm `wxxs'
			local wald_d=r(chi2)
			local p_wald_d=r(p)
			ereturn scalar wald_durbin=`wald_d'
			ereturn scalar p_durbin=`p_wald_d'
			if "`robust'"=="" ereturn scalar df_d=$spmlreg_ldf
		}

		if "`robust'"=="" {
			local chi2_lr= 2 * (e(ll) - `olsll')
			ereturn scalar chi2_lr=`chi2_lr'
		}

		ereturn scalar rho=[rho]_cons
		ereturn scalar df_m=$spmlreg_mdf
		ereturn scalar k_eq=3
        ereturn scalar k_aux=1
	}

    forv i=1/$spmlreg_nv  {
      	local ITEM : word `i' of `indvar'
		local MODEL "`MODEL'(`ITEM':) "
		local spmlreg_ARGS "`spmlreg_ARGS' beta`i'"
   	}

	if "`model'"=="error" {
   		local MODEL "`MODEL'(_cons:) (lambda:) (sigma:)"
   		global spmlreg_ARGS "`spmlreg_ARGS' beta0 lambda sigma"
	}
	else if "`model'"=="sac" {
   		local MODEL "`MODEL'(_cons:) (rho:) (lambda:) (sigma:)"
   		global spmlreg_ARGS "`spmlreg_ARGS' beta0 rho lambda sigma"
	}

	* Estimate the spatial error model
	if "`model'"=="error" {
        ml model lf `a_estimer' `MODEL' if `touse', waldtest(1) ///
        maximize continue `initopt' title(`titl') missing
        ereturn scalar df_m=$spmlreg_mdf
        ereturn scalar k_eq=3
        ereturn scalar k_aux=1

        tempname BETA
        matrix `BETA'=e(b)
        forv i=1/$spmlreg_nv {
			local ITEM : word `i' of `indvar'
			local COLNAME "`COLNAME'`depv':`ITEM' "
        }
		local COLNAME "`COLNAME'`depv':_cons lambda:_cons sigma:_cons"
   		matrix colnames `BETA'=`COLNAME'
   		ereturn repost b=`BETA', rename
        tempvar YHAT
        qui _predict double `YHAT' if `touse'
		qui summ `YHAT' if `touse'
   		local NUM=r(Var)
   		qui summ `depv' if `touse'
   		local DEN=r(Var)
   		local VARRAT=`NUM'/`DEN'
   		qui correlate `YHAT' `depv' if `touse'
   		local SQCORR=r(rho)*r(rho)

		capture qui	test [`depv']: `indvar', min
		if _rc==0 {
			local chi2=r(chi2)
			eret scalar chi2=`chi2'
		}
		qui test [lambda]_cons = 0
		local Wald=r(chi2)

		local chi2_lr= 2 * (e(ll) - `olsll')
		ereturn scalar chi2_lr=`chi2_lr'

		ereturn scalar lambda=[lambda]_cons
      	ereturn scalar varRatio=`VARRAT'
      	ereturn scalar sqCorr=`SQCORR'
	}

	* Estimate the spatial mixed or the general spatial model
	if "`model'"=="sac" {
        ml model lf `a_estimer' `MODEL' if `touse',  ///
		maximize continue `initopt' title(`titl') missing
		ereturn scalar df_m=$spmlreg_mdf
        ereturn scalar k_eq=4
        ereturn scalar k_aux=1

        tempname BETA
        matrix `BETA'=e(b)
        forv i=1/$spmlreg_nv {
			local ITEM : word `i' of `indvar'
			local COLNAME "`COLNAME'`depv':`ITEM' "
        }

		local COLNAME "`COLNAME'`depv':_cons rho:_cons lambda:_cons sigma:_cons"
   		matrix colnames `BETA'=`COLNAME'
   		ereturn repost b=`BETA', rename
		ereturn scalar w1 =$spmlreg_w1

		if $spmlreg_sr2==1 {
			tempname bet r2b brho
			matrix `bet'=e(b)
			matrix `r2b'=`bet'[1, "`depv':"]
			scalar `brho'=[rho]_cons
			cap erase spmlreg_filefeig
			cap drop spmlregy_pred
			mata: spmlreg_CalcSR2("`brho'", "`r2b'", "`indvar'", "wfrom", "weights", "`touse'")
			qui sum spmlregy_pred if `touse'
   			local NUM=r(Var)
   			qui summ `depv' if `touse'
   			local DEN=r(Var)
  			local VARRAT=`NUM'/`DEN'
   			qui correlate spmlregy_pred `depv' if `touse'
   			local SQCORR=r(rho)*r(rho)
      		ereturn scalar varRatio=`VARRAT'
      		ereturn scalar sqCorr=`SQCORR'
		}

		capture qui	test [`depv']: `indvar', min
		if _rc==0 {
			local chi2=r(chi2)
			eret scalar chi2=`chi2'
		}

		qui test ([rho]_cons=0) ([lambda]_cons=0)
		local Wald=r(chi2)

		local chi2_lr= 2 * (e(ll) - `olsll')
		ereturn scalar chi2_lr=`chi2_lr'

		if $spmlreg_w1==1 {
      		ereturn scalar minEigen1=`LOWER1'
      		ereturn scalar maxEigen1=`UPPER1'
		}
	}
	if "`sr2'"!="" {
		cap drop spmlregy_pred
		mata: spmlreg_geteigen("`touse'")
	}

	ereturn scalar Wald=`Wald'

    ereturn scalar minEigen=`LOWER'
    ereturn scalar maxEigen=`UPPER'

	ereturn scalar N=`N'
	ereturn scalar k_dv=1
	ereturn scalar sigma=[sigma]_cons
	ereturn scalar sr2=$spmlreg_sr2
	ereturn local chi2type "Wald"
    ereturn local depvar "`depv'"
	ereturn local indvar "`xxlist'"
	ereturn local wname `weights'
	ereturn local wfrom `wfrom'
	ereturn local model `model'
	ereturn local eignvar `eignvar'
	ereturn local predict "spmlreg_p"
	ereturn local estat_cmd "spmlreg_estat"
	ereturn local cmd "spmlreg"

	* Display results
	Display, `level' `robust'
	ereturn repost, esample(`touse')
	macro drop spmlreg_*
end
program define Display
	version 12.0
	syntax, [Level(int $S_level) robust]
	di _newline

	di as txt "`e(title)'" _col(52) "Number of obs" _col(68) "=" as res %10.0f `e(N)'
	di as txt _col(52) "Wald chi2(" as res `e(df_m)' as txt ")" _col(68) "=" as res %10.3f `e(chi2)'
	di as txt _col(52) "Prob > chi2" _col(68) "=" as res %10.3f chi2tail(`e(df_m)',`e(chi2)')
	if "`e(title)'"=="Spatial Error Model" | `e(sr2)' {
		di as txt _col(52) "Variance ratio" _col(68) "=" as res %10.3f `e(varRatio)'
		di as txt _col(52) "Squared corr." _col(68) "=" as res %10.3f `e(sqCorr)'
	}
	di as txt "Log likelihood = " as res `e(ll)' as txt _col(52) "Sigma"   /*
     */   _col(68) "=" as res %10.2f [sigma]_cons
	di ""
	if inlist("`e(title)'", "Spatial Error Model", "Spatial Lag Model", "Spatial Durbin Model") {

		ml display, level(`level') neq(1) plus noheader
		if inlist("`e(title)'", "Spatial Lag Model", "Spatial Durbin Model") {
			_diparm rho, level(`level') label("rho")
			local PARM "rho"
		}
		if "`e(title)'"=="Spatial Error Model" {
			_diparm lambda, level(`level') label("lambda")
			local PARM "lambda"
		}
		di as txt "{hline 13}{c BT}{hline 64}"
		if inlist("`e(title)'", "Spatial Error Model", "Spatial Lag Model") {
			di as txt "Wald test of `PARM'=0:" _col(40) "chi2(1) = "   /*
			*/ as res _col(50) %7.3f `e(Wald)' as txt " ("             /*
			*/ as res %5.3f chi2tail(1,`e(Wald)') as txt ")"
			if "`robust'"=="" {
   				di as txt "Likelihood ratio test of `PARM'=0:" _col(40) "chi2(1) = "   /*
   				*/ as res _col(50) %7.3f `e(chi2_lr)' as txt " ("                         /*
   				*/ as res %5.3f chi2tail(1,`e(chi2_lr)') as txt ")"
			}
		}
		if "`e(title)'"=="Spatial Durbin Model" {
			di as txt "Wald test of `PARM'=0:" _col(50) "chi2(1) = "   /*
			*/ as res _col(58) %7.3f `e(Wald)' as txt " ("             /*
			*/ as res %5.3f chi2tail(1,`e(Wald)') as txt ")"
			di as txt "Wald test for coefficients on lags of X's =0:" _col(50) "chi2(" `e(df_m)' as txt ") = "   /*
			*/ as res _col(58) %7.3f `e(wald_durbin)' as txt " ("             /*
			*/ as res %5.3f chi2tail(`e(df_m)',`e(wald_durbin)') as txt ")"
			if "`robust'"=="" {
   				di as txt "Likelihood ratio test of SDM vs. OLS:" _col(50) "chi2("`e(df_d)' as txt ") = "   /*
   				*/ as res _col(58) %7.3f `e(chi2_lr)' as txt " ("                         /*
   				*/ as res %5.3f chi2tail(`e(df_d)',`e(chi2_lr)') as txt ")"
			}
		}
		di ""
		di as txt "Acceptable range for `PARM': " as res %5.3f `e(minEigen)'   /*
   		*/   " < `PARM' < " %5.3f `e(maxEigen)'
	}
	if "`e(title)'"=="Spatial Mixed Model" {
		ml display, level(`level') neq(1) noheader diparm(rho, label("rho")) diparm(lambda, label("lambda"))
		di as txt "Wald test of SAC vs. OLS:" _col(40) "chi2(2) = "   /*
		*/ as res _col(50) %7.3f `e(Wald)' as txt " ("             /*
		*/ as res %5.3f chi2tail(2,`e(Wald)') as txt ")"
		di
   		di as txt "Likelihood ratio test of SAC vs. OLS:" _col(40) "chi2(2) = "   /*
   			*/ as res _col(50) %7.3f `e(chi2_lr)' as txt " ("                         /*
   			*/ as res %5.3f chi2tail(2,`e(chi2_lr)') as txt ")"
		if `e(w1)'==0 {
			di
			di as txt "Acceptable range for Rho and Lambda: " as res %5.3f `e(minEigen)'   /*
   			*/   " < Rho, Lambda < " %5.3f `e(maxEigen)'
		}
		else {
			di
			di as txt "Acceptable range for Rho: " as res %5.3f `e(minEigen1)'   /*
   			*/   " < Rho < " %5.3f `e(maxEigen1)'
			di
			di as txt "Acceptable range for Lambda: " as res %5.3f `e(minEigen)'   /*
   			*/   " < Lambda < " %5.3f `e(maxEigen)'
		}
	}
end
version 12.0
mata
mata set matastrict on
void spmlreg_CalcSR2(string scalar brho, string scalar bvec,
					string scalar xvars, string scalar m_wfrom,
					string scalar m_wname, string scalar touseobs)
{
    rho=st_numscalar(brho)
	real matrix B, C, xxs, invIRW
	st_view(xxs=., ., tokens(xvars), touseobs)
	C=J(nc=rows(xxs),1,1)
    xxs=xxs,C
	if (st_local(m_wfrom)=="Mata") {
        fh = fopen(st_local(m_wname), "r")
        spmlreg_w=fgetmatrix(fh)
        fclose(fh)
	}
	else spmlreg_w=st_matrix(st_local(m_wname))
    nw=rows(spmlreg_w)
	invIRW=luinv(I(nw)-rho*spmlreg_w)
    B=st_matrix(bvec)
    XB=xxs*B'
    ypred=invIRW*XB
    fhh = fopen("spmlreg_filefeig", "w")
    fputmatrix(fhh, ypred)
    fclose(fhh)
    st_store(., st_addvar("double", "spmlregy_pred"), touseobs, ypred)
}
void spmlreg_geteigen(string scalar tousev) {
	fh = fopen("spmlreg_filefeig", "r")
	predy=fgetmatrix(fh)
	fclose(fh)
	st_store(., st_addvar("double", "spmlregy_pred"), tousev, predy)
	unlink("spmlreg_filefeig")
}
end