program define ppml_fe_bias, eclass syntax varlist [if] [in], /// lambda(varname) /// i(varname) /// j(varname) /// [ /// t(varname) /// bias(name) /// v(name) /// w(name) /// b_term(name) /// d_term(name) /// b1(string) /// b2(name) /// d1(name) /// d2(name) /// beta(name) /// NOSTERR /// approx /// exact /// twoway /// NOTABLE /// ] // - what about when i and/or j are non-numeric? // - fix the order of operations for if the user only cares about standard errors or only cares about bias. // - add twoway option (what happens if only one year is provided?) // - collinearity? // - an approximation has been used to facilitate the calculation of the adjustment to standard errors. Use // the "exact" option to disable this approximation. // This program makes use of three excellent user-created packages: // - hdfe by Sergio Correia // - gtools by Mauricio Caceres Bravo // - rowmatutils by Matthew Baker // - frmttable by John Luke Gallup cap which frmttable if _rc == 111 { di in red "You will need to install -outreg- in order to use this command." di di in red "To install, type -ssc install outreg-". exit 111 } cap which hdfe if _rc == 111 { di in red "You will need to install -hdfe- in order to use this command." di di in red "To install, type -ssc install hdfe-". exit 111 } cap which gtools if _rc == 111 { di in red "You will need to install -gtools- in order to use this command." di di in red "To install, type -ssc install gtools-". exit 111 } cap findfile lrowmat_utils.mlib if _rc == 601 { di in red "You will need to install -rowmat_utils- in order to use this command." di di in red "To install, type -ssc install rowmat_utils-". exit 111 } if "`twoway'" == "" & "`t'"=="" { di in red "A name for the t() variable must be provided unless the -twoway- option is also enabled" } if "`twoway'" != "" & "`t'"=="" { tempvar t gen `t' =1 } tokenize `varlist' local y `1' macro shift local xvars `*' unab xvars: `*' tempvar sum_lambda pair_id t_id tempname demeaned local names "w b_term d_term b1 b2 d1 d2" foreach n of local names { if "``n''" == "" { tempname `n' } } marksample todo qui hashsort `i' `j' `t' by `i' `j': gegen `sum_lambda'_ij = sum(`lambda') if `todo' qui replace `todo' = 0 if `sum_lambda'_ij == 0 qui replace `todo' = 0 if `lambda' == 0 gegen `pair_id' = group(`i' `j') if `todo' gegen `t_id' = group(`t') if `todo' // time_id must be 1,2,3... // make sure all id's are numeric. capture confirm numeric `i' if !_rc { tempvar i_var gegen `i_id' = group(i) if `todo' } else{ local i_id = "`i'" } capture confirm numeric `j' if !_rc { tempvar j_var gegen `j_id' = group(j) if `todo' } else { local j_id = "`j'" } if "`exact'" == "" & "`approx'" == "" & "`twoway'" == "" { qui gdistinct `i_id' local n_i = r(ndistinct) qui gdistinct `j_id' local n_j = r(ndistinct) qui gstats sum `t_id' local n_t = r(max) local n_twoway_fes = `n_i'*`n_t' + `n_j'*`n_t' if `n_twoway_fes' > 1000 { local approx = "approx" } } if "`twoway'" != ""{ local approx = "" local nosterr = "" } if "`exact'" != ""{ local approx = "" } // only need this objects if using an approximation for the standard error corrections. if "`approx'" != "" { sort `i' `t' `j' by `i' `t': gegen `sum_lambda'_it = sum(`lambda') if `todo' sort `j' `t' `i' by `j' `t': gegen `sum_lambda'_jt = sum(`lambda') if `todo' di in red "note: because of the size of the data, an approximation will be used to compute the adjusted variance. Use the -exact- option if you wish to compute the variance exactly." } // weighted demeaning of X wrt FEs, weighted by lambda if "`twoway'" != "" { qui hdfe `xvars' if `todo' [aw=`lambda'], absorb(`i_id'#`t_id' `j_id'#`t_id') gen(`demeaned') } else { qui hdfe `xvars' if `todo' [aw=`lambda'], absorb(`i_id'#`t_id' `j_id'#`t_id' `i_id'#`j_id') gen(`demeaned') } local before : list sizeof xvars qui _rmcoll `demeaned'* if `todo' & `lambda'>0, forcedrop // check simple collinearity across `policyvars' local okvars = r(varlist) local after : list sizeof okvars if `before' != `after' { di in red "The set of x variables (`xvars') does not appear to be of full rank after conditioning on the fixed effects." exit 111 } if "`bias'" == "" { tempname bias } if "`v'" == "" { tempname v } tempname orig_ses if "`twoway'" == "" { mata: analyticalbiascorrection("`demeaned'*", "`i_id'", "`j_id'", "`t_id'", "`pair_id'", "`y'", "`lambda'", "`sum_lambda'", "`bias'", "`v'", /// "`w'", "`b_term'", "`d_term'", "`b1'", "`b2'", "`d1'", "`d2'", "`approx'", "`orig_ses'", "`nosterr'", "`todo'" ) } else { mata: twowayse_correction("`demeaned'*", "`i_id'", "`j_id'", "`t_id'", "`pair_id'", "`y'", "`lambda'", "`v'", "`w'", "`orig_ses'", "`todo'" ) } // what type of table to make? if "`notable'" != "" { local table =0 } else if("`nosterr'"=="" & "`beta'" !="") { local table = 1 } else if("`nosterr'"=="" & "`beta'" =="") { local table = 2 } else if ("`nosterr'"!="" & "`beta'" !="") { local table = 3 } else if ("`nosterr'"!="" & "`beta'" =="") { local table = 4 } local n_vars : word count `xvars' // make sure that user-input beta is configured as a row vector and has the right number of elements. if "`beta'" != "" & "`notable'" == "" { if colsof(`beta') > rowsof(`beta') { matrix `beta' = `beta'' } if rowsof(`beta') > `n_vars' { matrix `beta' = `beta'[1..`n_vars',1] local beta_name = "`beta'" di in red "note: `beta_name' matrix will be shortened to the same length as the number of x-variables" } else if rowsof(`beta') < `n_vars' { local table = `table' + 1 } } qui sum `t_id' if `r(max)'==1 { local se_note = "Robust standard errors" } else { local se_note = "Standard errors clustered by pair" } tempname results_matrix if `table' == 1 { ereturn clear tempname ses adj_betas store_results mata st_matrix("`ses'", sqrt(diagonal( st_matrix("`v'") ))) if ("`twoway'" != "") { matrix `adj_betas' = `beta' } else{ matrix `adj_betas' = `beta' - `bias' } matrix rownames `adj_betas' = `xvars' matrix rownames `v' = `xvars' matrix colnames `v' = `xvars' matrix `results_matrix' = `adj_betas', `ses' matrix rownames `results_matrix' = `xvars' if ("`twoway'" == "") { matrix rownames `bias' = `xvars' } matrix rownames `beta' = `xvars' matrix rownames `ses' = `xvars' matrix rownames `orig_ses' = `xvars' tempname orig_results matrix `orig_results' = `beta', `orig_ses' qui frmttable, statmat(`orig_results') substat(1) sdec(7) ctitles("", "original") if ("`twoway'" == "") { qui frmttable, statmat(`bias') sdec(7) ctitles("", "bias") merge } qui frmttable, statmat(`ses') sdec(7) ctitles("", "adjusted SEs") merge local bc = rowsof(`results_matrix') matrix stars = J(`bc',2,0) forvalues k = 1/`bc' { matrix stars[`k',2] = /// (abs(`results_matrix'[`k',1]/`results_matrix'[`k',2]) > abs(invnormal(0.10/2))) + /// (abs(`results_matrix'[`k',1]/`results_matrix'[`k',2]) > abs(invnormal(0.05/2))) + /// (abs(`results_matrix'[`k',1]/`results_matrix'[`k',2]) > abs(invnormal(0.01/2))) } frmttable, statmat(`results_matrix') substat(1) sdec(7) ctitles("", "bias-corrected") /// note("`se_note', using a local de-biasing adjustment"\ /// " to account for estimation noise in the `i'-`t' and `j'-`t' fixed effects."\ /// "* p<0.10; ** p<0.05; *** p<0.01") /// annotate(stars) asymbol(*,**,***) merge matrix `adj_betas' = `adj_betas'' matrix `v' = (`v' + `v'')/2 ereturn post `adj_betas' `v', depname(`y') ereturn local cmdline "ppml_fe_bias `0'" ereturn local cmd "ppml_fe_bias" } if `table' == 2 { tempname ses mata st_matrix("`ses'", sqrt(diagonal( st_matrix("`v'") ))) if ("`twoway'" == "") { matrix `results_matrix' = `bias', `ses' matrix rownames `results_matrix' = `xvars' frmttable, statmat(`results_matrix') sdec(7) ctitles("", "bias", "adjusted SE") /// note("`se_note', using a local de-biasing adjustment"\ /// "to account for estimation noise in the `i'-`t' and `j'-`t' fixed effects.") } else{ matrix rownames `ses' = `xvars' frmttable, statmat(`ses') sdec(7) ctitles("", "adjusted SE") /// note("`se_note', using a local de-biasing adjustment"\ /// "to account for estimation noise in the `i'-`t' and `j'-`t' fixed effects.") } } if `table' == 3 { tempname adj_betas if ("`twoway'" == "") { matrix `adj_betas' = `beta' } else{ matrix `adj_betas' = `beta' - `bias' } matrix `results_matrix' = `beta'', `bias', `adj_betas' matrix rownames `results_matrix' = "`xvars'" frmttable, statmat(`results_matrix') sdec(7) ctitles("", "original", "bias", "bias-corrected") } if `table' == 4 { matrix `results_matrix' = `bias' matrix rownames `results_matrix' = "`xvars'" frmttable, statmat(`results_matrix') sdec(7) ctitles("", "bias") } end mata: scalar altsum (vector r, real todo) { return(sum(r:*todo)) } end mata: void analyticalbiascorrection(string scalar demeaned_x, string scalar i_var, string scalar j_var, string scalar t_var, string scalar pair_var, string scalar y_var, string scalar lam, string scalar sum_lam, string scalar bias_name, string scalar V_name, string scalar W_name, string scalar B_name, string scalar D_name, string scalar B1_name, string scalar B2_name, string scalar D1_name, string scalar D2_name, string scalar approx_lev, string scalar orig_sterr, string scalar no_sterr, | string scalar todo_var) { if(approx_lev=="approx") { vars = st_data(., (demeaned_x), todo_var) vars = st_data(., (i_var, j_var, t_var, pair_var, y_var, lam, sum_lam+"_ij",sum_lam+"_it",sum_lam+"_jt"), todo_var), vars K = cols(vars)-9 } else { vars = st_data(., (demeaned_x), todo_var) vars = st_data(., (i_var, j_var, t_var, pair_var, y_var, lam, sum_lam+"_ij"), todo_var), vars K = cols(vars)-7 } T = max(vars[.,3]) NN_panels = max(vars[.,4]) NNT = NN_panels * T ij = vars[.,1] + vars[.,2]/(2*max(vars[.,2])) ij = uniqrows(ij)#J(T,1,1) i = floor(ij) j = floor((ij-i):*(2*max(vars[.,2])) :+ 1e-6) t = J(NN_panels,1,1)#(1..T)' it = i + t/(2*T) jt = j + t/(2*T) uniq_i = uniqrows(i) uniq_j = uniqrows(j) uniq_t = (1..T)' uniq_it = uniqrows(it) uniq_jt = uniqrows(jt) N_i = rows(uniq_i) N_j = rows(uniq_j) N_j_1 = NN_panels/N_i N_i_1 = NN_panels/N_j // fill out panels with missing years index = T :* (vars[.,4]:-1) :+ vars[.,3] tempvars = J(NNT, 3+K, 0) if(approx_lev=="approx") { // fill out panels with missing years index = T :* (vars[.,4]:-1) :+ vars[.,3] tempvars = J(NNT, 5+K, 0) tempvars[index,.] = editmissing(vars[., 5..(9+K)],0) } else { // fill out panels with missing years index = T :* (vars[.,4]:-1) :+ vars[.,3] tempvars = J(NNT, 3+K, 0) tempvars[index,.] = editmissing(vars[., 5..(7+K)],0) } y = tempvars[.,1] lambda = tempvars[.,2] sum_lambda_ij = tempvars[.,3] if(approx_lev=="approx") { sum_lambda_it = tempvars[.,4] sum_lambda_jt = tempvars[.,5] demeanedX = tempvars[.,6..(5+K)] theta_it = editmissing(lambda:/sum_lambda_it,0) theta_jt = editmissing(lambda:/sum_lambda_jt,0) theta_ij = editmissing(lambda:/sum_lambda_ij,0) } else { demeanedX = tempvars[.,4..(3+K)] theta_ij = editmissing(lambda:/sum_lambda_ij,0) } e = y - lambda W = (1/NN_panels) * ((lambda:*demeanedX)' * demeanedX) // have to assume all t are populated (balanced panels) for this to work s_equals_t = (t # J(1, T, 1) :== uniq_t') // t_equals_s is an (NNT) x T matrix with entries equal to 1 when s=t, where s indexes columns. temp1 = theta_ij#J(1,T,1) temp2 = colshape(theta_ij, T) #J(T,1,1) temp = temp1:*temp2 H_ij = (-temp :+ theta_ij:*s_equals_t):*sum_lambda_ij // H_ij is a matrix that stacks ij-specific TxT blocks, with each ij-specific block containing H_ij. /* // G: Would be stored in memory as an (NNTT) x (T) matrix. Each (TT)xT block would be a single ij-specific element of G // each TxT block will be indexed by r theta_r = theta_ij # J(T,1,1) // within each TxT block in G, rows will be indexed by s and columns will be indexed by t theta_s = colshape(colshape(theta_ij,T)#J(T,1,1),1)#J(1,T,1) theta_t = colshape(theta_ij,T)#J(T*T,1,1) r_equals_t = (t # J(T, T, 1) :== uniq_t') s_equals_t = (colshape(colshape(t,T)#J(T,1,1),1) # J(1,T,1) :== uniq_t') r_equals_s = (t # J(T, T, 1)) :== (colshape(colshape(t,T)#J(T,1,1),1)) G = /*-2 :* theta_r :* theta_s :* theta_t + */ r_equals_s :* theta_r :* theta_t + r_equals_t :* theta_r :* theta_s + s_equals_t :* theta_s :* theta_r /*- r_equals_t :* s_equals_t :* theta_r */ G = G :* (sum_lambda_ij#J(T,1,1)) */ // Fast trace is just eg if T=3 column 1 + column 5 + column 9 fast_trace = rowshape(I(T), T*T) i_short = colshape(i,T)[.,1] j_short = colshape(j,T)[.,1] collapse_i = ((i_short#J(1,N_i,1))':==uniq_i) collapse_j = ((j_short#J(1,N_j,1))':==uniq_j) /* MOVE this code below so that it is only computed optionally and so that user can choose whether to compute bias in point estimates or bias in standard errors. Also create separate Mata function with fast method; link using if statement. */ if (no_sterr == "") { if (approx_lev=="approx") { temp_it = colshape(theta_it,T)#J(T,1,1) :* J(NN_panels,1,1)#I(T) temp_jt = colshape(theta_jt,T)#J(T,1,1) :* J(NN_panels,1,1)#I(T) temp_ij = colshape(theta_ij,T)#J(T,1,1) /* DDDD = -WM_it + WM_it*WM_ij - WM_jt + WM_jt*WM_it - WM_jt*WM_it*WM_ij + WM_jt*WM_ij - WM_ij + WM_ij*WM_it - WM_ij*WM_it*WM_ij + WM_ij*WM_jt - WM_ij*WM_jt*WM_it + WM_ij*WM_jt*WM_it*WM_ij - WM_ij*WM_jt*WM_ij */ // 1, 3, 4, 7, 8, 10, 11 temp_DDDD = -temp_it - temp_jt + temp_jt:*temp_it - temp_ij + colshape(theta_ij :*theta_it,T)#J(T,1,1) + colshape(theta_ij:*theta_jt,T)#J(T,1,1) - colshape(theta_ij:*rowsum(temp_jt:*temp_it),T)#J(T,1,1) stacked_eye = J(NN_panels,1,1)#I(T) opp_eye = J(NNT,T,1)-stacked_eye temp_DDDD = temp_DDDD + colshape(theta_it:*theta_ij,T)#J(T,1,1) :* stacked_eye for (k=1; k <= T-1; k++) { temp_DDDD = temp_DDDD + (theta_it[1..NNT-k]:*theta_ij[1+k..NNT] \ J(k,1,0)) :* (J(NNT,k,0),stacked_eye[.,1..T-k]) + (J(k,1,0)\theta_it[1+k..NNT]:*theta_ij[1..NNT-k] ) :* (stacked_eye[.,1+k..T],J(NNT,k,0)) } temp5 = temp_jt:*temp_it:*theta_ij temp6 = temp_jt:*theta_ij for (k=1; k <= T; k++) { temp5[.,k] = temp5[.,k]+theta_jt:*theta_it:*temp_ij[.,k] :* opp_eye[.,k] temp6[.,k] = temp6[.,k]+theta_jt:*temp_ij[.,k] :* opp_eye[.,k] } temp_DDDD = temp_DDDD - temp5 + temp6 index = (1..NN_panels)*T for (k=1; k <= T; k++) { temp_DDDD[.,k] = temp_DDDD[.,k] - (rowsum(colshape(theta_ij :* theta_it,T)) :* theta_ij[index:-(T-k),.])#J(T,1,1) } for (k=1; k <= T; k++) { temp_DDDD[.,k] = temp_DDDD[.,k] + rowsum(colshape(theta_ij :* temp5[,k],T))#J(T,1,1) } for (k=1; k <= T; k++) { temp_DDDD[.,k] = temp_DDDD[.,k] - (rowsum(colshape(theta_ij :* theta_jt,T)) :* theta_ij[index:-(T-k),.])#J(T,1,1) } } else{ // sort of a fast xi / egen to construct within-transformed sets of it- and jt- dummies d_t = (t # J(1, T, 1) :== uniq_t') temp = colshape(rowsum(theta_ij :* d_t),T)#J(T,1,1) temp = d_t - temp d_it_tilde = J(NNT,N_i*T,0) d_jt_tilde = J(NNT,N_j*T,0) for (k=1; k <= N_i; k++) { idx1 = (1, (k-1)*T+1 \ NNT ,k*T) idx2 = (i:==uniq_i[k]) d_it_tilde[|idx1|] = temp:*idx2 } for (k=1; k <= N_j; k++) { idx1 = (1, (k-1)*T+1 \ NNT ,k*T) idx2 = (j:==uniq_j[k]) d_jt_tilde[|idx1|] = temp:*idx2 } d_tilde = (d_it_tilde, d_jt_tilde) d_ijt = d_tilde :> 0 // This can be shown to be equal to inv (sum_ij (d_ij' H_ij d_ij)) V_FE = invsym((d_tilde:*lambda)' * (d_tilde)) // Next, we need d_ij V_FE d_ij' // - d_ijt is NNT x F (NN vertically stacked T x F matrices) // - V_FE is F x F (NN stacked T x F matrices) // - d_ijt' is F x NNT (NN horizontally stacked F x T matrices) // d_ijt x V_F gives me another NNT x F matrix. // - the elements are sum_f {d_ij1,f V_f1} sum_f {d_ij1,f V_f2} sum_f {d_ij1,f V_f3} ... // sum_f {d_ij2,f V_f1} sum_f {d_ij2,f V_f2} sum_f {d_ij2,f V_f3} ... // sum_f {d_ij3,f V_f1} sum_f {d_ij3,f V_f2} sum_f {d_ij3,f V_f3} ... // Now consider (d_ijt x V_F) :* d_ijt // - the elements would be sum_f {d_ij1,f V_f1} d_ij1,1 sum_f {d_ij1,f V_f2}d_ij1,2 sum_f {d_ij1,f V_f3} d_ij1,3 ... // sum_f {d_ij2,f V_f1} d_ij2,1 sum_f {d_ij2,f V_f2)d_ij2,2 sum_f {d_ij2,f V_f3} d_ij2,3 ... // sum_f {d_ij3,f V_f1} d_ij3,1 sum_f {d_ij3,f V_f2)d_ij3,2 sum_f {d_ij3,f V_f3} d_ij3,3 ... // Finding the rowsum then gives you the main diagonal. // To get the full outer product, pull apart the lefthand side so that it repeats. // For the right-hand side, reshape, pull apart, then reshape again. tempd = colshape(d_ijt, T*cols(d_ijt))#J(T,1,1) tempd = colshape(tempd, cols(d_ijt)) dVd = rowsum( ((d_ijt * V_FE) # J(T,1,1)) :* tempd) dVd = colshape(dVd, T*T) } } // This gets us x_ij V_x x_ij' V_X = (1/NN_panels) * invsym(W) tempX = colshape(demeanedX, T*K)#J(T,1,1) tempX = colshape(tempX, K) xVx = rowsum( ((demeanedX * V_X) # J(T,1,1)) :* tempX) xVx = colshape(xVx, T*T) // sum_j H_ij and sum_i H_ij (then invert and compute trace) H_i = collapse_i * colshape(H_ij,T*T) H_j = collapse_j * colshape(H_ij,T*T) //Fast inversion can be accomplished via rm_newtinv() H_i_inv = rm_newtinv(H_i,30,1e-12) // can confirm using: pinv(colshape(H_i,T)[|1,1 \T,T|]) /* uses Moore-Penrose pseudoinverse */ H_j_inv = rm_newtinv(H_j,30,1e-12) // SS_ij is the OUTER product of scores SS_ij = (e # J(1,T,1)) :*(colshape(e,T)#J(T,1,1)) SS_ij = colshape(SS_ij, T*T) SS_i = (collapse_i * SS_ij) SS_j = (collapse_j * SS_ij) if (no_sterr == "") { if (approx_lev=="approx") { lev_correction = -rm_transpose(colshape(temp_DDDD,T*T)) + rm_matmult(colshape(H_ij,T*T), xVx) lev_correction = rm_newtinv(colshape(fast_trace,T*T):-lev_correction,30,1e-12) } else{ lev_correction = rm_matmult(colshape(H_ij,T*T), xVx+dVd) lev_correction = rm_newtinv(colshape(fast_trace,T*T):-lev_correction,30,1e-12) } SSh_ij = rm_matmult(lev_correction, SS_ij) /* SSh_i = (collapse_i * SSh_ij) SSh_j = (collapse_j * SSh_ij) */ } // construct OMEGA^U // - demeanedX is NNT x K (think of as NN T x K matrices, stacked vertically) // - demeanedX' is K x NNT (think of a NN K x T matrices, stacked horizontally) // - SSh_ij is NN x TT (think of as NN TT row vectors, stacked vertically) tempX = colshape (demeanedX, K*T) X_reshape = J(1,K,1)#(1..T) :* K :+ (-(K-1)..0)#J(1,T,1) tempX = colshape(tempX[.,X_reshape],T) // reshapes demeanedX so that it is now NNK x T - or NN K x T matrices vertically stacked. if (no_sterr == "") { tempO = rm_matvecmult(SSh_ij#J(K,1,1), tempX) // gives me NN K x T matrices, stacked vertically // next I want to construct NN KxK outer products tempO = (tempX#J(K,1,1)) :* colshape(colshape(tempO,T*K)#J(K,1,1),T) // (I) NN blocks; within each block, there are KK rows of T columns, with each k repeated K times // (II) NN blocks; within each block, there are KK rows of T columns, with 1..K repeated K times tempO = rowsum(tempO) tempO = colsum(colshape(tempO, K*K)) OMEGA = (1/(NN_panels)) * colshape(tempO,K) V = (1/(NN_panels)) * invsym(W) * OMEGA * invsym(W) // Stata uses a finite-sample correction to calculate SEs. See: // - https://www.stata.com/meeting/13uk/nichols_crse.pdf. // - https://www.stata.com/manuals/u20.pdf (p. 52) // - https://www.stata.com/manuals/p_robust.pdf (p. 13) V = NN_panels / (NN_panels-1) * V "Adjusted SEs" sqrt(diagonal(V)) st_matrix(V_name, V) } //original VCV matrix tempO = rm_matvecmult(SS_ij#J(K,1,1), tempX) tempO = (tempX#J(K,1,1)) :* colshape(colshape(tempO,T*K)#J(K,1,1),T) tempO = rowsum(tempO) tempO = colsum(colshape(tempO, K*K)) OMEGA = (1/(NN_panels)) * colshape(tempO,K) V_orig = (1/(NN_panels)) * invsym(W) * OMEGA * invsym(W) V_orig = NN_panels / (NN_panels-1) * V_orig //original SEs (for comparison) st_matrix(orig_sterr, (diagonal(sqrt(V_orig)))) B = D = J(K,1,0) B1 = B2 = J(N_i,K,0) D1 = D2 = J(N_j,K,0) tempH = colshape(H_ij,T*T) //tempG = colshape(G,T*T) for (k=1; k <= K; k++) { // compute xHS objects that appear in the first terms in the bias. tempX = colshape(demeanedX[.,k],T) tempHx = rm_matvecmult(tempH, tempX) // gives me a row vector indexed by t, with elements sum_s (H_ij,st * x_ijs) tempHx = tempHx # J(T,1,1) xHS_ij = colshape(tempHx :* e,T*T) // "e" is just the score. thus, this is the outer product of Hx and S. xHS_i = (collapse_i * xHS_ij) xHS_j = (collapse_j * xHS_ij) // compute xG objects that appear in the second terms in the bias. // tempX = colshape(demeanedX,T)#J(T,1,1) // was: colshape(demeanedX[.,k],T)#J(T,1,1) //xG_ij = colshape(rm_matvecmult(tempG, tempX), T*T) tempHx = colshape(H_ij :* demeanedX[.,k],T*T) xG_ij = -tempHx - rm_transpose(tempHx) // this does not give you Gx, but will give you equivalent sums when summed over i or over j. xG_i = (collapse_i * xG_ij) xG_j = (collapse_j * xG_ij) /* tempX = tempX#J(T,1,1) // was: colshape(demeanedX[.,k],T)#J(T,1,1) xG_ij = colshape(rm_matvecmult(tempG, tempX), T*T) xG_i = (collapse_i * xG_ij) xG_j = (collapse_j * xG_ij) xG_ij */ xG_i = (collapse_i * xG_ij) xG_j = (collapse_j * xG_ij) // compute B1 and D1 B1[.,k] = rm_matmult(xHS_i, H_i_inv) * fast_trace D1[.,k] = rm_matmult(xHS_j, H_j_inv) * fast_trace // compute B2 and D2 temp = rm_matmult(H_i_inv,xG_i) temp = rm_matmult(temp,H_i_inv) /* if (V_name != "") { temp_h = rm_matmult(temp,SSh_i) B2_h = temp_h * fast_trace } */ temp = rm_matmult(temp,SS_i) B2[.,k] = temp * fast_trace temp = rm_matmult(H_j_inv,xG_j) temp = rm_matmult(temp,H_j_inv) /* if (V_name != "") { temp_h = rm_matmult(temp,SSh_j) D2_h = temp_h * fast_trace } */ temp = rm_matmult(temp,SS_j) D2[.,k] = temp * fast_trace B[k] = - (1/N_i) * sum( B1[.,k] ) + 1/(2*N_i) * sum( B2[.,k] ) D[k] = - (1/N_j) * sum( D1[.,k] ) + 1/(2*N_j) * sum( D2[.,k] ) } /* "original bias correction" invsym(W) * (B / N_j_1 + D / N_i_1) st_matrix(bias_name, invsym(W) * (B / N_j_1 + D / N_i_1) ) */ "bias corrections (to be subtracted from original coefficients)" invsym(W) * ((N_i / (N_i-1))*B / N_j_1 + (N_j / (N_j-1))*D / N_i_1 ) st_matrix(bias_name, invsym(W) * ((N_i / (N_i-1))*B / N_j_1 + (N_j / (N_j-1))*D / N_i_1) ) /* if (V_name != "") { B_h = - (1/N_i) * sum( B1 ) + 1/(2*N_i) * sum( B2_h ) B_h2 = - (1/N_i) * sum( B1 ) + 1/(2*(N_i-1)) * sum( B2_h ) D_h = - (1/N_j) * sum( D1 ) + 1/(2*N_j) * sum( D2_h ) D_h2 = - (1/N_j) * sum( D1 ) + 1/(2*(N_j-1)) * sum( D2_h ) "bias_h" W^-1 * ((N_i / (N_i-1))*B_h / N_j_1 + (N_j / (N_j-1))*D_h / N_i_1) st_matrix(bias_name+"_h", W^-1 * ((N_i / (N_i-1))*B_h / N_j_1 + (N_j / (N_j-1))*D_h / N_i_1) ) } */ st_matrix(W_name, W) st_matrix(B1_name, -sum(B1)) st_matrix(B2_name, sum(B2)/2) st_matrix(B_name, B) st_matrix(D1_name, -sum(D1)) st_matrix(D2_name, sum(D2)/2) st_matrix(D_name, D) } end mata: void twowayse_correction(string scalar demeaned_x, string scalar i_var, string scalar j_var, string scalar t_var, string scalar pair_var, string scalar y_var, string scalar lam, string scalar V_name, string scalar W_name, string scalar orig_sterr, | string scalar todo_var) { vars = st_data(., (demeaned_x), todo_var) vars = st_data(., (i_var, j_var, t_var, pair_var, y_var, lam), todo_var), vars K = cols(vars)-6 T = max(vars[.,3]) NN_panels = max(vars[.,4]) NNT = NN_panels * T ij = vars[.,1] + vars[.,2]/(2*max(vars[.,2])) ij = uniqrows(ij)#J(T,1,1) i = floor(ij) j = floor((ij-i):*(2*max(vars[.,2])) :+ 1e-6) t = J(NN_panels,1,1)#(1..T)' it = i + t/(2*T) jt = j + t/(2*T) uniq_i = uniqrows(i) uniq_j = uniqrows(j) uniq_t = (1..T)' uniq_it = uniqrows(it) uniq_jt = uniqrows(jt) fast_trace = rowshape(I(T), T*T) N_i = rows(uniq_i) N_j = rows(uniq_j) N_j_1 = NN_panels/N_i N_i_1 = NN_panels/N_j // fill out panels with missing years index = T :* (vars[.,4]:-1) :+ vars[.,3] tempvars = J(NNT, 3+K, 0) // fill out panels with missing years index = T :* (vars[.,4]:-1) :+ vars[.,3] tempvars = J(NNT, 2+K, 0) tempvars[index,.] = editmissing(vars[., 5..(6+K)],0) y = tempvars[.,1] lambda = tempvars[.,2] demeanedX = tempvars[.,3..(2+K)] e = y - lambda // will use clustered standard errors here, which reduces to robust standard errors when T=1. W = (1/NN_panels) * ((lambda:*demeanedX)' * demeanedX) // SS_ij is the OUTER product of scores SS_ij = (e # J(1,T,1)) :*(colshape(e,T)#J(T,1,1)) SS_ij = colshape(SS_ij, T*T) // construct OMEGA^U // - demeanedX is NNT x K (think of as NN T x K matrices, stacked vertically) // - demeanedX' is K x NNT (think of a NN K x T matrices, stacked horizontally) // - SSh_ij is NN x TT (think of as NN TT row vectors, stacked vertically) if(N_i*T+N_j*T<1000) { // sort of a fast xi d_it = it :== (uniq_it')#J(NNT,1,1) d_jt = jt :== (uniq_jt')#J(NNT,1,1) d_ijt = (d_it, d_jt) // This can be shown to be equal to inv (sum_ij (d_ij' Lambda_ij d_ij)) V_FE = invsym((d_ijt:*lambda)' * (d_ijt)) // Next, we need d_ij V_FE d_ij' // - d_ijt is NNT x F (NN vertically stacked T x F matrices) // - V_FE is F x F (NN stacked T x F matrices) // - d_ijt' is F x NNT (NN horizontally stacked F x T matrices) // d_ijt x V_F gives me another NNT x F matrix. // - the elements are sum_f {d_ij1,f V_f1} sum_f {d_ij1,f V_f2} sum_f {d_ij1,f V_f3} ... // sum_f {d_ij2,f V_f1} sum_f {d_ij2,f V_f2} sum_f {d_ij2,f V_f3} ... // sum_f {d_ij3,f V_f1} sum_f {d_ij3,f V_f2} sum_f {d_ij3,f V_f3} ... // Now consider (d_ijt x V_F) :* d_ijt // - the elements would be sum_f {d_ij1,f V_f1} d_ij1,1 sum_f {d_ij1,f V_f2}d_ij1,2 sum_f {d_ij1,f V_f3} d_ij1,3 ... // sum_f {d_ij2,f V_f1} d_ij2,1 sum_f {d_ij2,f V_f2)d_ij2,2 sum_f {d_ij2,f V_f3} d_ij2,3 ... // sum_f {d_ij3,f V_f1} d_ij3,1 sum_f {d_ij3,f V_f2)d_ij3,2 sum_f {d_ij3,f V_f3} d_ij3,3 ... // Finding the rowsum then gives you the main diagonal. // To get the full outer product, pull apart the lefthand side so that it repeats. // For the right-hand side, reshape, pull apart, then reshape again. tempd = colshape(d_ijt, T*cols(d_ijt))#J(T,1,1) tempd = colshape(tempd, cols(d_ijt)) dVd = rowsum( ((d_ijt * V_FE) # J(T,1,1)) :* tempd) dVd = colshape(dVd, T*T) } else { alt_order = order((i,j,t), (3,1,2)) alt_inv_order = invorder(alt_order) alt_i = i[alt_order] alt_j = j[alt_order] alt_lambda = lambda[alt_order,.] for (k=1; k <= T; k++) { t_index = (NN_panels*(k-1)+1..NN_panels*k) alt_uniq_i = uniqrows(alt_i[t_index]) alt_uniq_j = uniqrows(alt_j[t_index]) alt_d_ijt = alt_i[t_index,1] :== alt_uniq_i'#(J(NN_panels,1,1)), alt_j[t_index,1] :== alt_uniq_j'#(J(NN_panels,1,1)) V_FE_t = invsym((alt_d_ijt:*alt_lambda[t_index])' * (alt_d_ijt)) if (k == 1) { dVd_alt = rowsum( (alt_d_ijt * V_FE_t ) :* alt_d_ijt) } else { dVd_alt = dVd_alt \ rowsum( (alt_d_ijt * V_FE_t ) :* alt_d_ijt) } } dVd = dVd[alt_inv_order,.] dVd = colshape(dVd :* (J(NN_panels,1,1) # I(T)), T*T) } // This gets us x_ij V_x x_ij' V_X = (1/NN_panels) * invsym(W) tempX = colshape(demeanedX, T*K)#J(T,1,1) tempX = colshape(tempX, K) xVx = rowsum( ((demeanedX * V_X) # J(T,1,1)) :* tempX) xVx = colshape(xVx, T*T) Lambda_ij = lambda:*(J(NN_panels,1,1)#I(T)) lev_correction = rm_matmult(colshape(Lambda_ij,T*T), xVx+dVd) lev_correction = rm_newtinv(colshape(fast_trace,T*T):-lev_correction,30,1e-12) SSh_ij = rm_matmult(lev_correction, SS_ij) tempX = colshape (demeanedX, K*T) X_reshape = J(1,K,1)#(1..T) :* K :+ (-(K-1)..0)#J(1,T,1) tempX = colshape(tempX[.,X_reshape],T) // reshapes demeanedX so that it is now NNK x T - or NN K x T matrices vertically stacked. tempO = rm_matvecmult(SSh_ij#J(K,1,1), tempX) // gives me NN K x T matrices, stacked vertically // next I want to construct NN KxK outer products tempO = (tempX#J(K,1,1)) :* colshape(colshape(tempO,T*K)#J(K,1,1),T) // (I) NN blocks; within each block, there are KK rows of T columns, with each k repeated K times // (II) NN blocks; within each block, there are KK rows of T columns, with 1..K repeated K times tempO = rowsum(tempO) tempO = colsum(colshape(tempO, K*K)) OMEGA = (1/(NN_panels)) * colshape(tempO,K) V = (1/(NN_panels)) * invsym(W) * OMEGA * invsym(W) // Stata uses a finite-sample correction to calculate SEs. See: // - https://www.stata.com/meeting/13uk/nichols_crse.pdf. // - https://www.stata.com/manuals/u20.pdf (p. 52) // - https://www.stata.com/manuals/p_robust.pdf (p. 13) V = NN_panels / (NN_panels-1) * V "Adjusted SEs" sqrt(diagonal(V)) st_matrix(V_name, V) //original VCV matrix tempO = rm_matvecmult(SS_ij#J(K,1,1), tempX) tempO = (tempX#J(K,1,1)) :* colshape(colshape(tempO,T*K)#J(K,1,1),T) tempO = rowsum(tempO) tempO = colsum(colshape(tempO, K*K)) OMEGA = (1/(NN_panels)) * colshape(tempO,K) V_orig = (1/(NN_panels)) * invsym(W) * OMEGA * invsym(W) V_orig = NN_panels / (NN_panels-1) * V_orig //original SEs (for comparison) st_matrix(orig_sterr, (diagonal(sqrt(V_orig)))) } end