cap program drop tsti program define tsti, rclass *Syntax: estimate se rope_lb rope_ub, options. df has a second companion 'ghost' option designed to see whether it has been provided version 9.0 *Get tokens gettoken estimate 0 : 0, parse(" ,") gettoken se 0 : 0, parse(" ,") gettoken rope_lb 0 : 0, parse(" ,") gettoken rope_ub 0 : 0, parse(" ,") *Confirm that all entries are numbers confirm number `estimate' confirm number `se' confirm number `rope_lb' confirm number `rope_ub' *If se <= 0... if (`se' <= 0) { *... then stop the function display "{it:se} must be strictly greater than zero" exit } *If rope_lb >= rope_ub... if (`rope_lb' >= `rope_ub') { *... then stop the function display "{it:rope_lb} must be strictly less than {it:rope_ub}" exit } syntax [, df(real 10000000000000000000) alpha(real 0.05) df2(real 10000000000000000000)] ************************* ***** ERRORS & PREP ***** ************************* *If alpha is not between 0 and 0.5... if (`alpha' <= 0 | `alpha' >= 0.5) { *... then stop the function display "{opt alpha()} must be between 0 and 0.5" exit } *If the estimate is exactly midway between the lower and upper bounds of the ROPE... if (`estimate' == (`rope_lb' + `rope_ub')/2) { *Then select the upper bound as the relevant TOST bound local bound = `rope_ub' } *Otherwise... if (`estimate' != (`rope_lb' + `rope_ub')/2) { *If the upper bound is closer to the estimate than the lower bound... if (abs(`estimate' - `rope_ub') < abs(`estimate' - `rope_lb')) { *Then select the upper bound as the relevant TOST bound local bound = `rope_ub' } *Otherwise... if (abs(`estimate' - `rope_ub') > abs(`estimate' - `rope_lb')) { *Select the lower bound as the relevant TOST bound local bound = `rope_lb' } } *Generate confidence percentage local confidence_pct = round(1 - `alpha', .01)*100 *Generate a test matrix tempname test_mat matrix `test_mat' = J(3, 3, .) *If the estimate is located above the ROPE... if (`estimate' > `rope_ub') { *Then designate the test for bounding above the ROPE as relevant and the other two tests as irrelevant mat `test_mat'[1, 3] = 1 mat `test_mat'[2, 3] = 0 mat `test_mat'[3, 3] = 0 } *If the estimate is located below the ROPE... if (`estimate' < `rope_lb') { *Then designate the test for bounding below the ROPE as relevant and the other two tests as irrelevant mat `test_mat'[1, 3] = 0 mat `test_mat'[2, 3] = 0 mat `test_mat'[3, 3] = 1 } *If the estimate is located inside the ROPE... if (`estimate' <= `rope_ub' & `estimate' >= `rope_lb') { *Then designate the TOST p-value as relevant and the other two tests as irrelevant mat `test_mat'[1, 3] = 0 mat `test_mat'[2, 3] = 1 mat `test_mat'[3, 3] = 0 } ************************ ***** SUB-ROUTINES ***** ************************ *If df is not provided... if (`df' == `df2') { *Generate the bounds of the ECI local ECI_LB = `estimate' - invnormal(1 - `alpha')*`se' local ECI_UB = `estimate' + invnormal(1 - `alpha')*`se' *Generate the bounds of the classic confidence interval local CI_classic_LB = `estimate' - invnormal(1 - `alpha'/2)*`se' local CI_classic_UB = `estimate' + invnormal(1 - `alpha'/2)*`se' *Generate the bounds of the TST confidence interval if (`estimate' < `rope_lb' + invnormal(1 - `alpha')*`se') { local CI_TST_LB = `estimate' - invnormal(1 - `alpha')*`se' } if (`estimate' >= `rope_lb' + invnormal(1 - `alpha')*`se' & `estimate' <= `rope_lb' + invnormal(1 - `alpha'/2)*`se') { local CI_TST_LB = `rope_lb' } if (`estimate' > `rope_lb' + invnormal(1 - `alpha'/2)*`se' & `estimate' < `rope_ub' + invnormal(1 - `alpha'/2)*`se') { local CI_TST_LB = `estimate' - invnormal(1 - `alpha'/2)*`se' } if (`estimate' >= `rope_ub' + invnormal(1 - `alpha'/2)*`se') { local CI_TST_LB = `rope_ub' } if (`estimate' <= `rope_lb' - invnormal(1 - `alpha'/2)*`se') { local CI_TST_UB = `rope_lb' } if (`estimate' > `rope_lb' - invnormal(1 - `alpha'/2)*`se' & `estimate' < `rope_ub' - invnormal(1 - `alpha'/2)*`se') { local CI_TST_UB = `estimate' + invnormal(1 - `alpha'/2)*`se' } if (`estimate' >= `rope_ub' - invnormal(1 - `alpha'/2)*`se' & `estimate' <= `rope_ub' - invnormal(1 - `alpha')*`se') { local CI_TST_UB = `rope_ub' } if (`estimate' > `rope_ub' - invnormal(1 - `alpha')*`se') { local CI_TST_UB = `estimate' + invnormal(1 - `alpha')*`se' } *Store the z-statistic and p-value of the two-sided test for bounding above the ROPE mat `test_mat'[1, 1] = (`estimate' - `rope_ub')/`se' mat `test_mat'[1, 2] = min((1 - normal(`test_mat'[1, 1]))*2, 1) *If the lower bound of the ROPE is the relevant TOST bound... if (`bound' == `rope_lb') { *Then store the z-statistic as estimate - min(ROPE) in standard error units... mat `test_mat'[2, 1] = (`estimate' - `rope_lb')/`se' *... and store the p-value of the one-sided test in the upper tail mat `test_mat'[2, 2] = 1 - normal(`test_mat'[2, 1]) } *If the upper bound of the ROPE is the relevant TOST bound... if (`bound' == `rope_ub') { *Then store the z-statistic as estimate - max(ROPE) in standard error units... mat `test_mat'[2, 1] = (`estimate' - `rope_ub')/`se' *... and store the p-value of the one-sided test in the lower tail mat `test_mat'[2, 2] = normal(`test_mat'[2, 1]) } *Store the z-statistic and p-value of the two-sided test for bounding below the ROPE mat `test_mat'[3, 1] = (`estimate' - `rope_lb')/`se' mat `test_mat'[3, 2] = min(normal(`test_mat'[3, 1])*2, 1) *If no p-value is < alpha... if (`test_mat'[1, 2] >= `alpha' & `test_mat'[2, 2] >= `alpha' & `test_mat'[3, 2] >= `alpha') { *Then store the conclusion local conclusion "The practical significance of the estimate is inconclusive." } *If the p-value of the two-sided test for bounding above the ROPE < alpha... if (`test_mat'[1, 2] < `alpha') { *Then store the conclusion local conclusion "The estimate is significantly bounded above the ROPE." } *If the p-value of the TOST procedure < alpha... if (`test_mat'[2, 2] < `alpha') { *Then store the conclusion local conclusion "The estimate is significantly bounded within the ROPE." } *If the p-value of the two-sided test for bounding below the ROPE < alpha... if (`test_mat'[3, 2] < `alpha') { *Then store the conclusion local conclusion "The estimate is significantly bounded below the ROPE." } ******************** *** BOUNDS TABLE *** ******************** disp "" disp in smcl in gr "{ralign 59: Approximate bounds}" _col(59) " {c |}" _col(71) in gr "Lower bound" _col(94) in gr "Upper bound" disp in smcl in gr "{hline 60}{c +}{hline 52}" disp in smcl in gr "{ralign 59:Region of practical equivalence (ROPE)}" _col(59) " {c |} " _col(71) as result %9.3f `rope_lb' _col(94) %9.3f `rope_ub' disp in smcl in gr "{ralign 59:`confidence_pct'% TST confidence interval (for precision)}" _col(59) " {c |} " _col(71) as result %9.3f `CI_TST_LB' _col(94) %9.3f `CI_TST_UB' disp in smcl in gr "{ralign 59:`confidence_pct'% equivalence confidence interval (for conclusions)}" _col(59) " {c |} " _col(71) as result %9.3f `ECI_LB' _col(94) %9.3f `ECI_UB' disp in smcl in gr "{ralign 59:`confidence_pct'% classic confidence interval (for conclusions)}" _col(59) " {c |} " _col(71) as result %9.3f `CI_classic_LB' _col(94) %9.3f `CI_classic_UB' ********************* *** RESULTS TABLE *** ********************* disp "" disp in smcl in gr "{ralign 46: Testing results}" _col(47) " {c |} " _col(52) in gr "z-statistic" _col(67) in gr "p-value" _col(80) in gr "Relevant" disp in smcl in gr "{hline 47}{c +}{hline 40}" disp in smcl in gr "{ralign 46:Test: Estimate bounded above ROPE (two-sided)}" _col(47) " {c |} " _col(52) as result %9.3f `test_mat'[1, 1] _col(64) %9.3f `test_mat'[1, 2] _col(76) %9.0f `test_mat'[1, 3] disp in smcl in gr "{ralign 46:Test: Estimate bounded within ROPE (TOST)}" _col(47) " {c |} " _col(52) as result %9.3f `test_mat'[2, 1] _col(64) %9.3f `test_mat'[2, 2] _col(76) %9.0f `test_mat'[2, 3] disp in smcl in gr "{ralign 46:Test: Estimate bounded below ROPE (two-sided)}" _col(47) " {c |} " _col(52) as result %9.3f `test_mat'[3, 1] _col(64) %9.3f `test_mat'[3, 2] _col(76) %9.0f `test_mat'[3, 3] ************************* *** PRINT DISCLAIMERS *** ************************* disp "" disp "`conclusion'" disp "" disp "Asymptotically approximate equivalence confidence intervals (ECIs) and three-sided testing (TST) results reported" disp "If using for academic/research purposes, please cite the papers underlying this program:" disp "Fitzgerald, J. (2025). The Need for Equivalence Testing in Economics. MetaArXiv, https://doi.org/10.31222/osf.io/d7sqr_v1." disp "Isager, P. & Fitzgerald, J. (2024). Three-Sided Testing to Establish Practical Significance: A Tutorial. https://doi.org/10.31234/osf.io/8y925." } *If df is provided... if (`df' != `df2') { *If df is not greater than zero... if (`df' <= 0) { *... then stop the function display "If {opt df()} is specified, then it must be greater than zero" exit } *Generate the bounds of the TST confidence interval if (`estimate' < `rope_lb' + invt(`df', 1 - `alpha')*`se') { local CI_TST_LB = `estimate' - invt(`df', 1 - `alpha')*`se' } if (`estimate' >= `rope_lb' + invt(`df', 1 - `alpha')*`se' & `estimate' <= `rope_lb' + invt(`df', 1 - `alpha'/2)*`se') { local CI_TST_LB = `rope_lb' } if (`estimate' > `rope_lb' + invt(`df', 1 - `alpha'/2)*`se' & `estimate' < `rope_ub' + invt(`df', 1 - `alpha'/2)*`se') { local CI_TST_LB = `estimate' - invt(`df', 1 - `alpha'/2)*`se' } if (`estimate' >= `rope_ub' + invt(`df', 1 - `alpha'/2)*`se') { local CI_TST_LB = `rope_ub' } if (`estimate' <= `rope_lb' - invt(`df', 1 - `alpha'/2)*`se') { local CI_TST_UB = `rope_lb' } if (`estimate' > `rope_lb' - invt(`df', 1 - `alpha'/2)*`se' & `estimate' < `rope_ub' - invt(`df', 1 - `alpha'/2)*`se') { local CI_TST_UB = `estimate' + invt(`df', 1 - `alpha'/2)*`se' } if (`estimate' >= `rope_ub' - invt(`df', 1 - `alpha'/2)*`se' & `estimate' <= `rope_ub' - invt(`df', 1 - `alpha')*`se') { local CI_TST_UB = `rope_ub' } if (`estimate' > `rope_ub' - invt(`df', 1 - `alpha')*`se') { local CI_TST_UB = `estimate' + invt(`df', 1 - `alpha')*`se' } *Generate the bounds of the ECI local ECI_LB = `estimate' - invt(`df', 1 - `alpha')*`se' local ECI_UB = `estimate' + invt(`df', 1 - `alpha')*`se' *Generate the bounds of the classic confidence interval local CI_classic_LB = `estimate' - invt(`df', 1 - `alpha'/2)*`se' local CI_classic_UB = `estimate' + invt(`df', 1 - `alpha'/2)*`se' *Store the t-statistic and p-value of the two-sided test for bounding above the ROPE mat `test_mat'[1, 1] = (`estimate' - `rope_ub')/`se' mat `test_mat'[1, 2] = min((1 - t(`df', `test_mat'[1, 1]))*2, 1) *If the lower bound of the ROPE is the relevant TOST bound... if (`bound' == `rope_lb') { *Then store the t-statistic as estimate - min(ROPE) in standard error units... mat `test_mat'[2, 1] = (`estimate' - `rope_lb')/`se' *... and store the p-value of the one-sided test in the upper tail mat `test_mat'[2, 2] = 1 - t(`df', `test_mat'[2, 1]) } *If the upper bound of the ROPE is the relevant TOST bound... if (`bound' == `rope_ub') { *Then store the t-statistic as estimate - max(ROPE) in standard error units... mat `test_mat'[2, 1] = (`estimate' - `rope_ub')/`se' *... and store the p-value of the one-sided test in the upper tail mat `test_mat'[2, 2] = t(`df', `test_mat'[2, 1]) } *Store the t-statistic and p-value of the two-sided test for bounding below the ROPE mat `test_mat'[3, 1] = (`estimate' - `rope_lb')/`se' mat `test_mat'[3, 2] = min(t(`df', `test_mat'[3, 1])*2, 1) *If no p-value is < alpha... if (`test_mat'[1, 2] >= `alpha' & `test_mat'[2, 2] >= `alpha' & `test_mat'[3, 2] >= `alpha') { *Then store the conclusion local conclusion "The practical significance of the estimate is inconclusive." } *If the p-value of the two-sided test for bounding above the ROPE < alpha... if (`test_mat'[1, 2] < `alpha') { *Then store the conclusion local conclusion "The estimate is significantly bounded above the ROPE." } *If the p-value of the TOST procedure < alpha... if (`test_mat'[2, 2] < `alpha') { *Then store the conclusion local conclusion "The estimate is significantly bounded within the ROPE." } *If the p-value of the two-sided test for bounding below the ROPE < alpha... if (`test_mat'[3, 2] < `alpha') { *Then store the conclusion local conclusion "The estimate is significantly bounded below the ROPE." } ******************** *** BOUNDS TABLE *** ******************** disp "" disp in smcl in gr "{ralign 59: Exact bounds}" _col(59) " {c |}" _col(71) in gr "Lower bound" _col(94) in gr "Upper bound" disp in smcl in gr "{hline 60}{c +}{hline 52}" disp in smcl in gr "{ralign 59:Region of practical equivalence (ROPE)}" _col(59) " {c |} " _col(71) as result %9.3f `rope_lb' _col(94) %9.3f `rope_ub' disp in smcl in gr "{ralign 59:`confidence_pct'% TST confidence interval (for precision)}" _col(59) " {c |} " _col(71) as result %9.3f `CI_TST_LB' _col(94) %9.3f `CI_TST_UB' disp in smcl in gr "{ralign 59:`confidence_pct'% equivalence confidence interval (for conclusions)}" _col(59) " {c |} " _col(71) as result %9.3f `ECI_LB' _col(94) %9.3f `ECI_UB' disp in smcl in gr "{ralign 59:`confidence_pct'% classic confidence interval (for conclusions)}" _col(59) " {c |} " _col(71) as result %9.3f `CI_classic_LB' _col(94) %9.3f `CI_classic_UB' ********************* *** RESULTS TABLE *** ********************* disp "" disp in smcl in gr "{ralign 46: Testing results}" _col(47) " {c |} " _col(52) in gr "z-statistic" _col(67) in gr "p-value" _col(80) in gr "Relevant" disp in smcl in gr "{hline 47}{c +}{hline 40}" disp in smcl in gr "{ralign 46:Test: Estimate bounded above ROPE (two-sided)}" _col(47) " {c |} " _col(52) as result %9.3f `test_mat'[1, 1] _col(64) %9.3f `test_mat'[1, 2] _col(76) %9.0f `test_mat'[1, 3] disp in smcl in gr "{ralign 46:Test: Estimate bounded within ROPE (TOST)}" _col(47) " {c |} " _col(52) as result %9.3f `test_mat'[2, 1] _col(64) %9.3f `test_mat'[2, 2] _col(76) %9.0f `test_mat'[2, 3] disp in smcl in gr "{ralign 46:Test: Estimate bounded below ROPE (two-sided)}" _col(47) " {c |} " _col(52) as result %9.3f `test_mat'[3, 1] _col(64) %9.3f `test_mat'[3, 2] _col(76) %9.0f `test_mat'[3, 3] ************************* *** PRINT DISCLAIMERS *** ************************* disp "" disp "`conclusion'" disp "" disp "Exact equivalence confidence intervals (ECIs) and three-sided testing (TST) results reported" disp "If using for academic/research purposes, please cite the papers underlying this program:" disp "Fitzgerald, J. (2025). The Need for Equivalence Testing in Economics. MetaArXiv, https://doi.org/10.31222/osf.io/d7sqr_v1." disp "Isager, P. & Fitzgerald, J. (2024). Three-Sided Testing to Establish Practical Significance: A Tutorial. https://doi.org/10.31234/osf.io/8y925." } *Return results return local estimate = `estimate' return local se = `se' return local ROPE_LB = `rope_lb' return local ROPE_UB = `rope_ub' return local alpha = `alpha' return local CI_classic_LB = `CI_classic_LB' return local CI_classic_UB = `CI_classic_UB' return local CI_TST_LB = `CI_TST_LB' return local CI_TST_UB = `CI_TST_UB' return local ECI_LB = `ECI_LB' return local ECI_UB = `ECI_UB' return local ts_above = `test_mat'[1, 1] return local p_above = `test_mat'[1, 2] return local relevant_above = `test_mat'[1, 3] return local ts_TOST = `test_mat'[2, 1] return local p_TOST = `test_mat'[2, 2] return local relevant_TOST = `test_mat'[2, 3] return local ts_below = `test_mat'[3, 1] return local p_below = `test_mat'[3, 2] return local relevant_below = `test_mat'[3, 3] return local conclusion `conclusion' end