// Hong Il Yoo (h.i.yoo@durham.ac.uk): 17/02/2020.
// Two-Way Clustered Standard Errors
// v1.0.2
// v1.0.1
program define vce2way, eclass
	version 13.1
	syntax anything(id="command line" name=command_line) [if] [in] [fweight  aweight  pweight  iweight], CLuster(varlist min=2 max=2) [*]
	// Define temporary objects
	tempname break b1 b2 b12 V1 V2 V12 V_2way V_2way_raw
	tempvar cluster12

	// Split 2 varnames in `cluster' into `cluster1' and `cluster2'
	local cluster1: word 1 of `cluster'
	local cluster2: word 2 of `cluster'

	// Mark sample
	marksample touse
	markout `touse' `cluster', strok

	// Set up weight options
	if ("`weight'" != "") local weight [`weight' `exp']

	quietly {
		// Step 1: cluster at `cluster1' and store covariance matrix
		`command_line' `weight' if `touse' `in', vce(cluster `cluster1') `options'
		matrix `b1' = e(b)
		matrix `V1' = e(V)
		local N_clust1 = e(N_clust)

		// Step 2: cluster at `cluster2' and store covariance matrix
		`command_line' `weight' if `touse' `in', vce(cluster `cluster2') `options'
		matrix `b2' = e(b)
		matrix `V2' = e(V)
		local N_clust2 = e(N_clust)

		// Step 3: cluster at intersection of `cluster1' & `cluster2' and store covariance matrix
		egen `cluster12' = group(`cluster1' `cluster2')
		`command_line' `weight' if `touse' `in', vce(cluster `cluster12') `options'
		matrix `b12' = e(b)
		matrix `V12' = e(V)

		// Step 4: compute the two-way clustered covariance matrix
		matrix `V_2way' = `V1' + `V2' - `V12'

		// Step 5: check if all three coefficient vectors are the same; if not, break with error
		scalar `break' = max(mreldif(`b1', `b2'), mreldif(`b2', `b12'), mreldif(`b1', `b12'))
		if (`break' > 1e-4) {
			// NOTE: leave some margin of very small non-zero differences
			di as red "One-way clustering in `cluster1', `cluster2' and their intersection results in different coefficient vectors."
			error 498
		}

		// Step 6. check V_2way is p.s.d.; if not, replace eigenvalues with zeroes and resconstruct
		mata {
			V_2way = st_matrix(st_local("V_2way"))
			symeigensystem(V_2way, EVEC = ., eval = .)
			if (min(eval) < 0) {
				eval = eval :* (eval :> 0)
				st_matrix(st_local("V_2way_raw"), V_2way)
				st_matrix(st_local("V_2way"), EVEC*diag(eval)*EVEC')
				st_local("replace","yes")
			}
		}

		// Final Step: add extra items to ereturn list and post two-way clustered covariance matrix
		ereturn local vce2way "yes"
		ereturn local clustvar "`cluster1' and `cluster2'"
		ereturn local clustvar1 "`cluster1'"
		ereturn local clustvar2 "`cluster2'"
		//ereturn local N_clust "`N_clust1' clusters in `cluster1' and `N_clust2' clusters in `cluster2'"
		if (`=int(c(stata_version))' > 15) ereturn local N_clust = .
		else ereturn local N_clust "N_clust'i' reports the number of clusters in clustvar'i'"

		ereturn scalar N_clust1 = `N_clust1'
		ereturn scalar N_clust2 = `N_clust2'
		if ("`e(F)'" != "") ereturn scalar F = .
		if ("`e(chi2)'" != "") ereturn scalar chi2 = .
		if ("`e(p)'" != "") ereturn scalar p = .

		if ("`replace'" == "yes") ereturn matrix V_raw = `V_2way_raw'
		ereturn repost V=`V_2way'
	}

	// Display estimation results with two-way clustered standard errors
	if ("`e(cmd)'" != "") `e(cmd)'
	else estimates replay

	// Display any notes
	di as text "Notes:"
	di as text " 	As of June 2019, vce2way has been superseded by " as smcl `"{bf:{stata findit vcemway:vcemway}}"' as text "."
	di as text ""
	di as text "	Std. Err. adjusted for " as result `e(N_clust1)' as text " clusters in `e(clustvar1)', AND " ///
	   as result `e(N_clust2)' as text " clusters in `e(clustvar2)'."
	di as text ""
	di as text "	Ignore default Wald chi2(.) and Prob > chi2, or F(.,.) and Prob > F, results above."
	di as text "	  If needed, use command -test- to compute the test statistic and p-value of interest."
	if ("`replace'" == "yes") {
		di as text ""
		di as text "	The initial variance-covariance matrix, " as result "e(V_raw)" as text ", was not positive semi-definite."
		di as text "	  The final matrix, " as result "e(V)" as text ", was computed by replacing negative eigenvalues with 0s."
	}
end

exit