*! version 1.0.0 25jul2026 Alexandre Poirier and Tymon Słoczyński * SPDX-License-Identifier: MIT * Copyright (c) 2026 Alexandre Poirier and Tymon Słoczyński * See the LICENSE file in this distribution for full text. capture program drop _causalrep_ols program define _causalrep_ols, eclass sortpreserve version 14 syntax varlist(num fv) [if] [in], Treatment(varname numeric) [Outcome(varname numeric) tmodel(string) cate(varname numeric) NOIsily vce(str)] local controls `"`varlist'"' fvrevar `controls', list local basevars `r(varlist)' if "`outcome'"=="" & "`cate'"!="" { display as error "option {bf:cate} requires outcome to be specified" exit 198 } preserve if "`outcome'"!="" { drop `outcome' capture summarize `treatment' if _rc!=0 { display as error "outcome and treatment must not be the same" exit 498 } } restore, preserve drop `basevars' if "`outcome'"!="" { capture summarize `outcome' if _rc!=0 { display as error "outcome must not appear on the list of control variables" exit 498 } } capture summarize `treatment' if _rc!=0 { display as error "treatment must not appear on the list of control variables" exit 498 } restore if "`tmodel'"=="" { local tmodel regress } if "`tmodel'"!="regress" & "`tmodel'"!="logit" & "`tmodel'"!="probit" { display as error "{bf:tmodel(`tmodel')} not allowed" exit 198 } if "`outcome'"!="" { tempvar touse pscore tempname b V N df_r ols if `"`vce'"'=="" local vce = "robust" quietly { `noisily' regress `outcome' `treatment' `controls' `if' `in', vce(`vce') matrix `b' = e(b) matrix `V' = e(V) scalar `N' = e(N) scalar `df_r' = e(df_r) scalar `ols' = _b[`treatment'] generate `touse' = e(sample) if "`tmodel'"=="regress" regress `treatment' `controls' if `touse'==1 else { capture `tmodel' `treatment' `controls' if `touse'==1, asis if _rc!=0 { noisily display as error "the propensity score model cannot be estimated" exit 430 } if e(converged)==0 { noisily display as error "the propensity score model cannot be estimated" exit 430 } } predict double `pscore' } } else { tempvar touse pscore tempname N quietly { if "`tmodel'"=="regress" regress `treatment' `controls' `if' `in' else { capture `tmodel' `treatment' `controls' `if' `in', asis if _rc!=0 { noisily display as error "the propensity score model cannot be estimated" exit 430 } if e(converged)==0 { noisily display as error "the propensity score model cannot be estimated" exit 430 } } scalar `N' = e(N) generate `touse' = e(sample) predict double `pscore' } } *causalrep ols only allows binary treatments capture tabulate `treatment' if `touse'==1 if _rc!=0 | (_rc==0 & r(r)!=2) { display as error "treatment must be binary" exit 450 } *causalrep ols only allows treatments that take on values zero or one quietly summarize `treatment' if `touse'==1 if r(min)!=0 | r(max)!=1 { display as error "treatment must only take on values zero or one" exit 450 } *verify whether there are any missing CATE values within the estimation sample if "`cate'"!="" { quietly count if `touse'==1 & `cate'<. if r(N)<`N' { display as error "there must be no missing CATE values within the estimation sample" exit 416 } } *verify whether any estimated propensity scores are below 0 or above 1 if "`tmodel'"=="regress" { quietly count if `touse'==1 & (`pscore'<0 | `pscore'>1) if r(N)>0 { display "" display as error "Negative weights detected: " r(N) " of " `N' " estimated propensity scores are below 0 or above 1." display "" display as error "The value reported below as the measure of internal validity (MIV) when the CATE function is unrestricted is the inverse maximum weight. If any population weights are negative, the corresponding MIV is zero. See Poirier and Słoczyński (2026) for details." } else { display "" display as text "No negative weights detected." } } tempvar weight tempname miv quietly { generate double `weight' = `pscore'*(1-`pscore') summarize `weight' if `touse'==1 local max_weight = r(max) if abs(`max_weight') <= 1e-12 { if "`tmodel'"=="regress" noisily display "" noisily display as error "the maximum estimated OLS weight is zero; the unrestricted-CATE MIV cannot be computed" exit 459 } scalar `miv' = r(mean)/`max_weight' if "`cate'"!="" { tempvar diff tempname ate miv_cate generate double `diff' = `cate' - `ols' summarize `diff' if `touse'==1 local full_diff = r(mean) summarize `cate' if `touse'==1 scalar `ate' = r(mean) local min_cate = r(min) local max_cate = r(max) local cate_tol = 1e-10 * max(1, abs(`ols'), abs(`ate'), abs(`min_cate'), abs(`max_cate')) if `ols' < `min_cate' - `cate_tol' | `ols' > `max_cate' + `cate_tol' { scalar `miv_cate' = 0 } else if abs(`full_diff') <= `cate_tol' { scalar `miv_cate' = 1 } else if abs(`ols' - `min_cate') <= `cate_tol' | abs(`ols' - `max_cate') <= `cate_tol' { count if `touse'==1 & abs(`diff') <= `cate_tol' scalar `miv_cate' = r(N)/`N' } else { *if original value of MIV is sufficiently small, search from the top; otherwise, search from the bottom if `miv' < 0.2 { local i = 0 if `ols' > `ate' { *the starting value of local mean can be an arbitrary value with a specific sign local mean = 1 gsort -`touse' -`diff' while `mean'>0 { local i = `i' + 1 summarize `diff' in 1/`i' local mean = r(mean) } } else { local mean = -1 gsort -`touse' +`diff' while `mean'<0 { local i = `i' + 1 summarize `diff' in 1/`i' local mean = r(mean) } } } else { local i = `N' + 1 if `ols' > `ate' { local mean = -1 gsort -`touse' -`diff' while `mean'<0 { local i = `i' - 1 summarize `diff' in 1/`i' local mean = r(mean) } } else { local mean = 1 gsort -`touse' +`diff' while `mean'>0 { local i = `i' - 1 summarize `diff' in 1/`i' local mean = r(mean) } } if `i' < `N' { local i = `i' + 1 summarize `diff' in 1/`i' local mean = r(mean) } } scalar `miv_cate' = (`i'/`N') - ((`i'/`N')*`mean')/`diff'[`i'] } } } if "`outcome'"!="" { display "" display as text `""OLS" is the OLS estimate of the effect of "' as result `"`treatment'"' as text `" on "' as result `"`outcome'"' as text `"."' display "" if abs(`ols')>1 display as text " OLS = " as result %-9.4g `ols' else display as text " OLS = " as result %5.3f `ols' } display "" display as text `""MIV" is the estimated measure of internal validity of the OLS estimand with respect to the entire population."' display "" if "`cate'"=="" display as text " MIV = " as result %5.3f `miv' else { display as text "No restrictions on the CATE function: MIV = " as result %5.3f `miv' display as text " Using the estimated CATE function: MIV = " as result %5.3f `miv_cate' } if "`outcome'"!="" ereturn post `b' `V', esample(`touse') depname(`outcome') buildfvinfo else { ereturn clear ereturn post, esample(`touse') } ereturn scalar N = `N' if "`outcome'"!="" ereturn scalar df_r = `df_r' if "`outcome'"!="" ereturn scalar ols = `ols' if "`cate'"!="" ereturn scalar ate = `ate' ereturn scalar miv = `miv' if "`cate'"!="" ereturn scalar miv_cate = `miv_cate' if "`outcome'"!="" ereturn local depvar `"`outcome'"' end