*! version 1.0.0 12may2026 S. Derya Uysal, Tymon Sloczynski, and Jeffrey M. Wooldridge * SPDX-License-Identifier: MIT * Copyright (c) 2026 S. Derya Uysal, Tymon Sloczynski, and Jeffrey M. Wooldridge * See the LICENSE file in this distribution for full text. capture program drop _drlate_ps program define _drlate_ps, sclass // Handles PS estimation for logit / cbps / ipt // Fills ips/wt1/wt0 tempvars and bips/bips1/bips0 matrices // eqips strings are NOT built here - rebuilt locally in calling program version 17 syntax, touse(name) zvar(name) zmodelvars(string) /// zmodel(string) samplew(name) /// pstolerance(real) /// ips(name) wt1(name) wt0(name) /// bips(name) bips1(name) bips0(name) /// [ iteropt(string) iteroptml(string) gmmopts(string) /// stat(string) osample(name) ] // Temporary objects local to this program tempvar ipsxb psxb1_1 psxb1_0 ips_1 ips_0 tempname initial I quietly { // ----------------------------------------------- // LOGIT // ----------------------------------------------- if "`zmodel'" == "logit" { capture logit `zvar' `zmodelvars' [pw = `samplew'] if `touse'==1, asis `iteroptml' if e(converged)==0 { local ncd = 0 if e(N_cds) & !missing(e(N_cds)) local ncd = e(N_cds) if e(N_cdf) & !missing(e(N_cdf)) local ncd = `ncd' + e(N_cdf) if `ncd' != 0 { noi di as error "{p}PS model has {bf:`ncd'} " /// "observations completely determined{p_end}" exit 322 } } if _rc!=0 | e(converged)==0 { noi di as error "convergence not achieved for logit PS estimation" exit 430 } matrix `bips' = e(b) predict double `ips' if `touse'==1 gen double `wt1' = `zvar' / `ips' if `touse'==1 gen double `wt0' = (1-`zvar') / (1-`ips') if `touse'==1 } // ----------------------------------------------- // CBPS // ----------------------------------------------- else if "`zmodel'" == "cbps" { capture logit `zvar' `zmodelvars' if `touse'==1 [pw = `samplew'] local rc_init = _rc if `rc_init'==0 { matrix `initial' = e(b) local kk = colsof(`initial') matrix `I' = I(`kk') } // Build eqips locally just for this GMM call local eqips_cbps (eqips: (`zvar'/(exp({zhat:`zmodelvars' _cons})/(1+exp({zhat:})))-(1-`zvar')/(1/(1+exp({zhat:}))))) local eqips_cbps_inst instruments(eqips: `zmodelvars') if `rc_init'==0 { capture gmm `eqips_cbps' if `touse' [pw = `samplew'], /// `eqips_cbps_inst' onestep winitial(`I') from(`initial') /// quickderivatives `gmmopts' `iteropt' } else { capture gmm `eqips_cbps' if `touse' [pw = `samplew'], /// `eqips_cbps_inst' onestep quickderivatives `gmmopts' `iteropt' } if _rc!=0 | e(converged)==0 { noi di as error "convergence not achieved for CBPS estimation" exit 430 } matrix `bips' = e(b) predict double `ipsxb' gen double `ips' = logistic(`ipsxb') gen double `wt1' = `zvar' / `ips' if `touse'==1 gen double `wt0' = (1-`zvar') / (1-`ips') if `touse'==1 } // ----------------------------------------------- // IPT // ----------------------------------------------- else if "`zmodel'" == "ipt" { capture logit `zvar' `zmodelvars' if `touse'==1 [pw = `samplew'] local rc_init = _rc if `rc_init'==0 { matrix `initial' = e(b) local kk = colsof(`initial') matrix `I' = I(`kk') } // --- Z=1 moment (built locally for this GMM only) --- local eqips1_loc (eqips1: (`zvar'*(1+exp({zhat1:`zmodelvars' _cons}))/(exp({zhat1:})) - 1)) local eqips1_loc_inst instruments(eqips1: `zmodelvars') if `rc_init'==0 { capture gmm `eqips1_loc' if `touse' [pw = `samplew'], /// `eqips1_loc_inst' onestep winitial(`I') from(`initial') /// quickderivatives `gmmopts' `iteropt' } else { capture gmm `eqips1_loc' if `touse' [pw = `samplew'], /// `eqips1_loc_inst' onestep quickderivatives `gmmopts' `iteropt' } if _rc!=0 | e(converged)==0 { noi di as error "convergence not achieved for IPT estimation (Z=1)" exit 430 } matrix `bips1' = e(b) predict double `psxb1_1' gen double `ips_1' = logistic(`psxb1_1') gen double `wt1' = `zvar' / `ips_1' if `touse'==1 // --- Z=0 moment (built locally for this GMM only) --- local eqips0_loc (eqips0: ((1-`zvar')*(1+exp({zhat0:`zmodelvars' _cons})) - 1)) local eqips0_loc_inst instruments(eqips0: `zmodelvars') if `rc_init'==0 { capture gmm `eqips0_loc' if `touse' [pw = `samplew'], /// `eqips0_loc_inst' onestep winitial(`I') from(`initial') /// quickderivatives `gmmopts' `iteropt' } else { capture gmm `eqips0_loc' if `touse' [pw = `samplew'], /// `eqips0_loc_inst' onestep quickderivatives `gmmopts' `iteropt' } if _rc!=0 | e(converged)==0 { noi di as error "convergence not achieved for IPT estimation (Z=0)" exit 430 } matrix `bips0' = e(b) predict double `psxb1_0' gen double `ips_0' = logistic(`psxb1_0') gen double `wt0' = (1-`zvar') / (1-`ips_0') if `touse'==1 // Combined ips for overlap check gen double `ips' = . replace `ips' = `ips_1' if `zvar'==1 replace `ips' = `ips_0' if `zvar'==0 } // ----------------------------------------------- // Overlap check // ----------------------------------------------- tempvar violators qui gen byte `violators' = (`ips' < `pstolerance' | `ips' > (1-`pstolerance')) if `touse'==1 if "`osample'" != "" { qui gen byte `osample' = `violators' label variable `osample' "overlap violation indicator" } count if `violators'==1 local fail = r(N) if `fail' { di as err "{p}" `fail' " observation" /// cond(`fail'>1,"s","") /// " violate the overlap assumption (ps outside [" /// trim(strofreal(`pstolerance',"%9.3e")) /// ", 1-" trim(strofreal(`pstolerance',"%9.3e")) "]){p_end}" di as err "{p}instrument overlap assumption violated" _c local link drlate##osample:osample if "`osample'" != "" { di as err " -- see variable " /// "{helpb `link'}{bf:(`osample')}{p_end}" } else { di as err "; use {helpb `link'}{bf:()} to identify violators{p_end}" } exit 498 } } end