├── .bumpver.py ├── .gitignore ├── README.md ├── build └── lmulte.mlib ├── doc ├── examples.do ├── multe-ssc.txt └── multe.sthlp ├── multe.pkg ├── src ├── ado │ └── multe.ado ├── build.do └── mata │ ├── multe.mata │ └── multe_helpers.mata ├── stata.toc └── test ├── example_fryer_levitt.dta ├── example_star.dta ├── test.R ├── test.do ├── test_replicate.do ├── test_speed.do ├── test_unit.do └── test_weights.do /.bumpver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | from datetime import datetime, date 6 | 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument('bump', 9 | nargs = 1, 10 | type = str, 11 | metavar = 'BUMP', 12 | help = "What to bump (major, minor, patch)") 13 | parser.add_argument('--dry', 14 | dest = 'dry', 15 | action = 'store_true', 16 | help = "Dry run (do not run)", 17 | required = False) 18 | args = vars(parser.parse_args()) 19 | 20 | # --------------------------------------------------------------------- 21 | # Config 22 | 23 | config_token = "CrossPlatformCompatibilityCookie" 24 | config_version = "1.1.0" 25 | config_date = date(2024, 3, 9) 26 | config_files = [ 27 | ('.bumpver.py', 'config_version = "{major}.{minor}.{patch}"'), 28 | ('.bumpver.py', f'config_date = date({{date:%Y, {config_token}%m, {config_token}%d}})'), 29 | ('README.md', 'version {major}.{minor}.{patch} {date:%d%b%Y}'), 30 | ('multe.pkg', 'v {major}.{minor}.{patch}'), 31 | ('multe.pkg', 'd Distribution-Date: {date:%Y%m%d}'), 32 | ('stata.toc', 'v {major}.{minor}.{patch}'), 33 | ('doc/multe.sthlp', 'version {major}.{minor}.{patch} {date:%d%b%Y}'), 34 | ('doc/multe-ssc.txt', 'Version: {major}.{minor}.{patch}'), 35 | ('src/ado/multe.ado', 'version {major}.{minor}.{patch} {date:%d%b%Y}') 36 | ] 37 | 38 | # --------------------------------------------------------------------- 39 | # Bump 40 | 41 | 42 | def main(bump, dry = False): 43 | args = ['major', 'minor', 'patch'] 44 | if bump not in args: 45 | msg = f"'{bump}' uknown; can only bump: {', '.join(args)}" 46 | raise Warning(msg) 47 | 48 | current_kwargs, update_kwargs = bump_kwargs(bump, config_version, config_date) 49 | for file, string in config_files: 50 | bump_file(file, string, current_kwargs, update_kwargs, dry) 51 | 52 | 53 | def bump_file(file, string, current, update, dry = False): 54 | find = ( 55 | string 56 | .format(**current) 57 | .replace(config_token + "0", "") 58 | .replace(config_token, "") 59 | ) 60 | with open(file, 'r') as fh: 61 | lines = fh.readlines() 62 | if find not in ''.join(lines): 63 | print(f'WARNING: nothing to bump in {file}') 64 | 65 | replace = ( 66 | string 67 | .format(**update) 68 | .replace(config_token + "0", "") 69 | .replace(config_token, "") 70 | ) 71 | ulines = [] 72 | for line in lines: 73 | if find in line: 74 | print(f'{file}: {find} -> {replace}') 75 | ulines += [line.replace(find, replace)] 76 | else: 77 | ulines += [line] 78 | 79 | if not dry: 80 | with open(file, 'w') as fh: 81 | fh.write(''.join(ulines)) 82 | 83 | 84 | def bump_kwargs(bump, config_version, config_date): 85 | today = datetime.now() 86 | major, minor, patch = config_version.split('.') 87 | umajor, uminor, upatch = bump_sever(bump, major, minor, patch) 88 | 89 | current_kwargs = { 90 | 'major': major, 91 | 'minor': minor, 92 | 'patch': patch, 93 | 'date': config_date 94 | } 95 | 96 | update_kwargs = { 97 | 'major': umajor, 98 | 'minor': uminor, 99 | 'patch': upatch, 100 | 'date': today 101 | } 102 | 103 | return current_kwargs, update_kwargs 104 | 105 | 106 | def bump_sever(bump, major, minor, patch): 107 | if bump == 'major': 108 | return str(int(major) + 1), '0', '0' 109 | elif bump == 'minor': 110 | return major, str(int(minor) + 1), '0' 111 | elif bump == 'patch': 112 | return major, minor, str(int(patch) + 1) 113 | else: 114 | return major, minor, patch 115 | 116 | 117 | if __name__ == "__main__": 118 | main(args['bump'][0], args['dry']) 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/issues 2 | test/scratch* 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MulTE 2 | ===== 3 | 4 | Multiple Treatment Effects 5 | 6 | `version 1.1.0 09Mar2024` | [Installation](#installation) | [Usage](#usage) | [Examples](#examples) 7 | 8 | This package implements contamination bias diagnostics in Stata using procedures from 9 | [Goldsmith-Pinkham, Hull, and Kolesár (2024)](https://arxiv.org/abs/2106.05024), 10 | based on R's [multe](https://github.com/kolesarm/multe?tab=readme-ov-file) package. 11 | 12 | ### Installation 13 | 14 | From Stata, latest version: 15 | 16 | ```stata 17 | local github "https://raw.githubusercontent.com" 18 | cap noi net uninstall multe 19 | net install multe, from(`github'/gphk-metrics/stata-multe/main/) 20 | ``` 21 | 22 | You can also clone or download the code manually, e.g. to 23 | `stata-multe-main`, and install from a local folder: 24 | 25 | ```stata 26 | cap noi net uninstall multe 27 | net install multe, from(`c(pwd)'/stata-multe-main) 28 | ``` 29 | 30 | We typically recommend installing the latest version; however, if you need a specific 31 | tagged version (e.g. for replication purposes): 32 | 33 | ```stata 34 | local github "https://raw.githubusercontent.com" 35 | local multeversion "1.1.0" 36 | cap noi net uninstall multe 37 | net install multe, from(`github'/gphk-metrics/stata-multe/`mutleversion'/) 38 | ``` 39 | 40 | See the [tags](https://github.com/gphk-metrics/stata-multe/tags) page for all 41 | available tagged versions. 42 | 43 | ### Usage 44 | 45 | ``` 46 | multe depvar [controls] [w=weight], treat(varname) [stratum(varname) options] 47 | help multe 48 | ``` 49 | 50 | At least one of `controls` or `stratum` must be specified. 51 | 52 | ### Examples 53 | 54 | The examples here are copied from the [vignette for the R package](https://github.com/kolesarm/multe/blob/master/doc/multe.pdf) and adapted for Stata. The vignette also includes an overview of the methods used in this package. 55 | 56 | See [Fryer and Levitt (2013)](https://www.aeaweb.org/articles?id=10.1257/aer.103.2.981) for a description of the data. First, we fit a regression of test scores on a race dummy (the treatment of interest) and a few controls, using sampling weights. 57 | 58 | ```stata 59 | use test/example_fryer_levitt.dta, clear 60 | * Regress IQ at 24 months on race indicators and baseline controls 61 | multe std_iq_24 i.age_24 female [w=W2C0], treat(race) stratum(SES_quintile) 62 | ``` 63 | 64 | The function posts the estimates from the partly linear model (OLS with robust 65 | SE) and reports a table with alternative estimates free of contamination bias. 66 | 67 | ```stata 68 | Partly Linear Model Estimates (full sample) Number of obs = 8,806 69 | 70 | ------------------------------------------------------------------------------ 71 | std_iq_24 | Coef. Std. Err. z P>|z| [95% Conf. Interval] 72 | -------------+---------------------------------------------------------------- 73 | race | 74 | Black | -.2574154 .0281244 -9.15 0.000 -.3125383 -.2022925 75 | Hispanic | -.2931465 .025962 -11.29 0.000 -.3440311 -.242262 76 | Asian | -.262108 .034262 -7.65 0.000 -.3292603 -.1949557 77 | Other | -.1563373 .0369127 -4.24 0.000 -.2286848 -.0839898 78 | ------------------------------------------------------------------------------ 79 | 80 | Alternative Estimates on Full Sample: 81 | 82 | | PL OWN ATE EW CW 83 | -------------+--------------------------------------------- 84 | Black | -.2574 -.2482 -.2655 -.255 -.2604 85 | SE | .02812 .02906 .02983 .02888 .02925 86 | Hispanic | -.2931 -.2829 -.2992 -.2862 -.2944 87 | SE | .02596 .02673 .02988 .0268 .02792 88 | Asian | -.2621 -.2609 -.2599 -.2611 -.2694 89 | SE | .03426 .03432 .04177 .03433 .04751 90 | Other | -.1563 -.1448 -.1503 -.1447 -.1522 91 | SE | .03691 .03696 .03594 .03684 .03698 92 | 93 | P-values for null hypothesis of no propensity score variation: 94 | Wald test: 2.1e-188 95 | LM test: 8.8e-197 96 | ``` 97 | 98 | In particular, the package computes the following estimates in addition to the 99 | partly linear model (PL): 100 | 101 | - OWN: The own treatment effect component of the PL estimator that subtracts an estimate of the contamination bias 102 | - ATE: The unweighted average treatment effect, implemented using regression that includes interactions of covariates with the treatment indicators 103 | - EW: Weighted ATE estimator based on easiest-to-estimate weighting (EW) scheme, implemented by running one-treatment-at-a-time regressions. 104 | - CW: Weighted ATE estimator using easiest-to-estimate common weighting (CW) scheme, implemented using weighted regression. 105 | 106 | For more precise definitions, see the methods section of the [R vignette](https://github.com/kolesarm/multe/blob/master/doc/multe.pdf). Note any combination of estimates can be posted after `multe` has run: 107 | 108 | ```stata 109 | . multe, est(ATE) 110 | 111 | ATE Estimates (full sample) Number of obs = 8,806 112 | 113 | ------------------------------------------------------------------------------ 114 | std_iq_24 | Coef. Std. Err. z P>|z| [95% Conf. Interval] 115 | -------------+---------------------------------------------------------------- 116 | race | 117 | Black | -.2655242 .0298285 -8.90 0.000 -.3239869 -.2070615 118 | Hispanic | -.2992432 .0298811 -10.01 0.000 -.357809 -.2406774 119 | Asian | -.2598644 .041769 -6.22 0.000 -.3417301 -.1779987 120 | Other | -.1502789 .0359405 -4.18 0.000 -.220721 -.0798368 121 | ------------------------------------------------------------------------------ 122 | ``` 123 | 124 | In this example, the propensity score varies significantly with covariates, as indicated by the p-values of the Wald and LM tests. Including many controls may result in overlap failure, as the next example demonstrates: 125 | 126 | ```stata 127 | . local controls i.age_24 female i.siblings i.family_structure 128 | 129 | . multe std_iq_24 `controls' [w=W2C0], treat(race) stratum(SES_quintile) 130 | (analytic weights assumed) 131 | The following variables have no within-treatment variation 132 | and are dropped from the overlap sample: 133 | 6.siblings 134 | 135 | Partly Linear Model Estimates (full sample) Number of obs = 8,806 136 | 137 | ------------------------------------------------------------------------------ 138 | std_iq_24 | Coef. Std. Err. z P>|z| [95% Conf. Interval] 139 | -------------+---------------------------------------------------------------- 140 | race | 141 | Black | -.2437653 .0307696 -7.92 0.000 -.3040726 -.183458 142 | Hispanic | -.2928037 .0259029 -11.30 0.000 -.3435725 -.2420348 143 | Asian | -.273888 .0341814 -8.01 0.000 -.3408823 -.2068938 144 | Other | -.1519798 .0368914 -4.12 0.000 -.2242857 -.079674 145 | ------------------------------------------------------------------------------ 146 | 147 | Alternative Estimates on Full Sample: 148 | 149 | | PL OWN ATE EW CW 150 | -------------+--------------------------------------------- 151 | Black | -.2438 -.2043 -.2482 -.218 -.2415 152 | SE | .03077 .03321 .03553 .03276 .03853 153 | Hispanic | -.2928 -.2801 -.2878 -.285 -.3001 154 | SE | .0259 .02671 .03 .02671 .02984 155 | Asian | -.2739 -.2836 -.2742 -.2839 -.2863 156 | SE | .03418 .0343 .04195 .03426 .04549 157 | Other | -.152 -.1277 . -.1295 -.1452 158 | SE | .03689 .03736 . .03713 .03817 159 | 160 | P-values for null hypothesis of no propensity score variation: 161 | Wald test: 1.3e-275 162 | LM test: 2.2e-245 163 | 164 | Alternativ Estimates on Overlap Sample: 165 | | PL OWN ATE EW CW 166 | -------------+--------------------------------------------- 167 | Black | -.2458 -.2062 -.2503 -.2199 -.2436 168 | SE | .03074 .03318 .03532 .0327 .03824 169 | Hispanic | -.2932 -.2809 -.288 -.2858 -.2987 170 | SE | .02595 .02676 .03001 .02678 .02987 171 | Asian | -.2741 -.2839 -.274 -.2841 -.2884 172 | SE | .03417 .03429 .04187 .03426 .04563 173 | Other | -.1511 -.127 -.1392 -.1289 -.1459 174 | SE | .03688 .03735 .03618 .03711 .03853 175 | ``` 176 | 177 | The issue is that no observations with 6 siblings have race equal to "Other": 178 | 179 | ```stata 180 | . tab race if siblings == 6, mi 181 | 182 | race | Freq. Percent Cum. 183 | ------------+----------------------------------- 184 | White | 18 40.00 40.00 185 | Black | 10 22.22 62.22 186 | Hispanic | 12 26.67 88.89 187 | Asian | 5 11.11 100.00 188 | ------------+----------------------------------- 189 | Total | 45 100.00 190 | ``` 191 | 192 | Thus, the ATE estimator comparing other to white is not identified. The package drops observations with 6 siblings from the sample to form an "overlap sample," where the all estimators are identified. The overlap sample drops all observations from strata that do not have all levels of the treatment as well as all control variables that do not have all levels of the treatment. In this example `siblings` is a control so the dummy for 6 siblings is dropped; however, if this happened for a level of `SES_quintile` then all the observations associated with that level would be dropped. 193 | 194 | For a researcher who wants to check whether there is a significant difference between the PL estimator and the other estimators, `e(diffmatrix)` and `e(overlapdiffmatrix)` report the differences between the estimates in the full sample and the overlap sample, respectively, with the corresponding standard errors. 195 | 196 | ```stata 197 | . matlist e(diffmatrix), format(%7.4g) 198 | 199 | | OWN ATE EW CW 200 | -------------+------------------------------------ 201 | Black | -.03947 .0044 -.02573 -.00229 202 | SE | .01204 .0173 .01027 .0205 203 | Hispanic | -.01269 -.00496 -.00783 .00725 204 | SE | .00571 .01314 .00497 .01245 205 | Asian | .00975 .00026 .01001 .01238 206 | SE | .00487 .0246 .0048 .02814 207 | Other | -.0243 . -.02246 -.00679 208 | SE | .00738 . .00684 .01326 209 | 210 | . matlist e(overlapdiffmatrix), format(%7.4g) 211 | 212 | | OWN ATE EW CW 213 | -------------+------------------------------------ 214 | Black | -.03958 .00453 -.02592 -.00218 215 | SE | .01201 .01714 .01023 .02023 216 | Hispanic | -.01229 -.00523 -.00738 .00554 217 | SE | .00569 .01319 .00494 .01247 218 | Asian | .00978 -8.7e-05 .01 .01433 219 | SE | .00486 .02454 .0048 .0282 220 | Other | -.02404 -.01189 -.02221 -.0052 221 | SE | .00739 .01098 .00684 .01337 222 | ``` 223 | 224 | We see statistically significant difference between the OWN and PL estimate (i.e. significant contamination bias) for all races, both in the full sample and in the overlap sample. Differences can also be posted after the main estimation: 225 | 226 | ``` 227 | . multe, est(OWN) diff 228 | 229 | Own Treatment Effects Estimates (full sample) Number of obs = 8,806 230 | 231 | ------------------------------------------------------------------------------ 232 | std_iq_24 | Coef. Std. Err. z P>|z| [95% Conf. Interval] 233 | -------------+---------------------------------------------------------------- 234 | race | 235 | Black | -.0394671 .0120418 -3.28 0.001 -.0630686 -.0158656 236 | Hispanic | -.0126872 .0057131 -2.22 0.026 -.0238847 -.0014898 237 | Asian | .009752 .0048651 2.00 0.045 .0002166 .0192874 238 | Other | -.0242985 .0073833 -3.29 0.001 -.0387696 -.0098274 239 | ------------------------------------------------------------------------------ 240 | 241 | . multe, est(OWN) diff overlap 242 | 243 | Own Treatment Effects Estimates (overlap sample) 244 | 245 | Number of obs = 8,806 246 | 247 | ------------------------------------------------------------------------------ 248 | std_iq_24 | Coef. Std. Err. z P>|z| [95% Conf. Interval] 249 | -------------+---------------------------------------------------------------- 250 | race | 251 | Black | -.0395843 .0120096 -3.30 0.001 -.0631227 -.0160458 252 | Hispanic | -.01229 .0056924 -2.16 0.031 -.0234468 -.0011332 253 | Asian | .009776 .0048602 2.01 0.044 .0002502 .0193019 254 | Other | -.0240377 .0073857 -3.25 0.001 -.0385134 -.009562 255 | ------------------------------------------------------------------------------ 256 | ``` 257 | 258 | The package also computes "oracle" standard errors, in addition to the usual 259 | standard errors reported above. These can be accessed by adding the `oracle` 260 | option to `multe` after the main estimation for `ATE`, `EW`, `CW`: 261 | 262 | ```stata 263 | . multe, est(ATE) oracle 264 | 265 | ATE Estimates (full sample) Number of obs = 8,806 266 | 267 | ------------------------------------------------------------------------------ 268 | | Oracle 269 | std_iq_24 | Coef. Std. Err. z P>|z| [95% Conf. Interval] 270 | -------------+---------------------------------------------------------------- 271 | race | 272 | Black | -.2481627 .0354964 -6.99 0.000 -.3177343 -.1785911 273 | Hispanic | -.2878414 .0299246 -9.62 0.000 -.3464926 -.2291902 274 | Asian | -.2741527 .0418856 -6.55 0.000 -.3562469 -.1920584 275 | Other | 0 (omitted) 276 | ------------------------------------------------------------------------------ 277 | ``` 278 | 279 | These oracle standard errors don't account for estimation error in the 280 | propensity score, in contrast to the default standard errors (note since ATE 281 | was not identified for Other, Stata interprets it as an omitted category 282 | for this estimator). Last, Specifying the `cluster()` argument allows for 283 | computation of clustered standard errors: 284 | 285 | ``` 286 | . local controls i.age_24 female 287 | 288 | . local options treat(race) stratum(SES_quintile) 289 | 290 | . multe std_iq_24 `controls' [w=W2C0], `options' cluster(interviewer_ID_24) 291 | (analytic weights assumed) 292 | 293 | Partly Linear Model Estimates (full sample) Number of obs = 8,806 294 | 295 | ------------------------------------------------------------------------------ 296 | | Cluster 297 | std_iq_24 | Coef. Std. Err. z P>|z| [95% Conf. Interval] 298 | -------------+---------------------------------------------------------------- 299 | race | 300 | Black | -.2574154 .0411564 -6.25 0.000 -.3380804 -.1767504 301 | Hispanic | -.2931465 .0440827 -6.65 0.000 -.379547 -.2067461 302 | Asian | -.262108 .0521067 -5.03 0.000 -.3642353 -.1599807 303 | Other | -.1563373 .0403717 -3.87 0.000 -.2354645 -.0772102 304 | ------------------------------------------------------------------------------ 305 | 306 | Alternative Estimates on Full Sample: 307 | 308 | | PL OWN ATE EW CW 309 | -------------+--------------------------------------------- 310 | Black | -.2574 -.2482 -.2655 -.255 -.2604 311 | SE | .04116 .04248 .04099 .0422 .04199 312 | Hispanic | -.2931 -.2829 -.2992 -.2862 -.2944 313 | SE | .04408 .04541 .0495 .04572 .04738 314 | Asian | -.2621 -.2609 -.2599 -.2611 -.2694 315 | SE | .05211 .05234 .06194 .05232 .06755 316 | Other | -.1563 -.1448 -.1503 -.1447 -.1522 317 | SE | .04037 .04161 .04099 .04145 .04278 318 | 319 | P-values for null hypothesis of no propensity score variation: 320 | Wald test: 9.4e-133 321 | LM test: 6.79e-07 322 | ``` 323 | -------------------------------------------------------------------------------- /build/lmulte.mlib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gphk-metrics/stata-multe/5381d57ff91cafb7237b8cf94714848b79ec39da/build/lmulte.mlib -------------------------------------------------------------------------------- /doc/examples.do: -------------------------------------------------------------------------------- 1 | * Data from Fryer and Levitt (2013) 2 | use test/example_fryer_levitt.dta, clear 3 | 4 | * Regress IQ at 24 months on race indicators and baseline controls 5 | multe std_iq_24 i.age_24 female [w=W2C0], treat(race) stratum(SES_quintile) 6 | 7 | * Post ATE estimates 8 | multe, est(ATE) 9 | 10 | * Add covariates that fail overlap 11 | local controls i.age_24 female i.siblings i.family_structure 12 | multe std_iq_24 `controls' [w=W2C0], treat(race) stratum(SES_quintile) 13 | tab race if siblings == 6, mi 14 | 15 | * Show collected results for differences 16 | matlist e(diffmatrix), format(%7.4g) 17 | matlist e(overlapdiffmatrix), format(%7.4g) 18 | 19 | * Post OWN estimates 20 | multe, est(OWN) diff 21 | multe, est(OWN) diff overlap 22 | 23 | * Oracle SE 24 | multe, est(ATE) oracle 25 | 26 | * Cluster SE 27 | local controls i.age_24 female 28 | local options treat(race) stratum(SES_quintile) 29 | multe std_iq_24 `controls' [w=W2C0], `options' cluster(interviewer_ID_24) 30 | 31 | * Example from help file 32 | local nobs 1000 33 | local ktreat 5 34 | clear 35 | set seed 1729 36 | set obs `nobs' 37 | gen T = ceil(runiform() * `ktreat') 38 | gen W = mod(_n, 10) 39 | gen Y = T + runiform() 40 | multe Y, treat(T) strat(W) 41 | ereturn list 42 | -------------------------------------------------------------------------------- /doc/multe-ssc.txt: -------------------------------------------------------------------------------- 1 | Name: multe 2 | Version: 1.1.0 3 | Depends: Stata 14.1 or later 4 | Author: [Paul Goldsmith-Pinkham, Peter Hull, Michal Kolesár] 5 | Support: [email] 6 | Title: Multiple Treatment Effects regression with saturated group control 7 | Description: 8 | 9 | Given a multi-valued treatment, a saturated group variable (or a varlist which will be used to create a single, saturated group variable) within which treatment is as-good-as-randomly assigned, and a dependent variable, multe computes equal-weighted (ATE), variance-weighted, efficiently-weighted treatment effects estimates and contamination bias decomposition as in Goldsmith-Pinkham et al. (2022). 10 | -------------------------------------------------------------------------------- /doc/multe.sthlp: -------------------------------------------------------------------------------- 1 | {smcl} 2 | {* *! version 1.1.0 09Mar2024}{...} 3 | {viewerdialog multe "dialog multe"}{...} 4 | {vieweralsosee "[R] multe" "mansection R multe"}{...} 5 | {viewerjumpto "Syntax" "multe##syntax"}{...} 6 | {viewerjumpto "Description" "multe##description"}{...} 7 | {viewerjumpto "Options" "multe##options"}{...} 8 | {viewerjumpto "Examples" "multe##examples"}{...} 9 | {title:Title} 10 | 11 | {p2colset 5 14 14 2}{...} 12 | {p2col :{cmd:multe} {hline 2}}Multiple Treatment Effects for Contamination Bias Diagnostics{p_end} 13 | {p2colreset}{...} 14 | 15 | {marker syntax}{...} 16 | {title:Syntax} 17 | 18 | {pstd} 19 | This package computes multiple treatment effects regression for contamination bias diagnostics, using the procedures from Goldsmith-Pinkham, Hull, and Kolesár (2024). 20 | 21 | {p 8 15 2} 22 | {cmd:multe} 23 | {depvar} 24 | [{varlist}] 25 | {ifin} 26 | [{it:{help multe##weight:weight}}] 27 | {cmd:,} 28 | {opth treat(varname)} 29 | [{opth stratum(varname)} {it:{help multe##table_options:options}}] 30 | 31 | {pstd} 32 | with {it:depvar} the dependent variable, {opt treat(varname)} the multi-valued treatment, and at least one of {it:varlist} with controls or {opt stratum(varname)} with a stratifying variable must be specified. 33 | 34 | {synoptset 18 tabbed}{...} 35 | {marker table_options}{...} 36 | {synopthdr} 37 | {synoptline} 38 | {syntab :Options} 39 | {synopt :{opt treat:ment(varname)}} Name of multi-valued treatment variable. 40 | {p_end} 41 | {synopt :{opt strat:um(varname)}} Name of stratifying covariate. 42 | {p_end} 43 | {synopt :{opt cluster(varname)}} Name of clustering covariate. 44 | {p_end} 45 | {synopt :{opt cw:_uniform}} Whether the target weighting scheme should give all comparisons equal weight or draw from the marginal empirical treatment distribution. 46 | {p_end} 47 | {synopt :{opt mata:save(str)}} Name of mata object with results (default: multe_results). 48 | {p_end} 49 | 50 | {p2colreset}{...} 51 | {p 4 6 2} 52 | 53 | {marker weight}{...} 54 | {p 4 6 2} 55 | {opt aweight}s and {opt pweight}s are allowed. 56 | 57 | {marker description}{...} 58 | {title:Description} 59 | 60 | {pstd} 61 | {cmd:multe} computes five estimates of treatment effects in settings with multiple treatments and provides estimates of contamination bias. See the package README at {browse "https://github.com/gphk-metrics/stata-multe/blob/main/README.md":github.com/gphk-metrics/stata-multe} for a detailed description and examples. 62 | 63 | {pstd} 64 | Heteroskedasticity-robust standard errors (default) and heteroskedasticity-robust standard errors that treat the treatment propensity scores as known (oracle) are also reported; standard errors can be clustered via {opt cluster()}. Each unique value of the {it:treatment} variable is taken as a distinct level of the treatment. The lowest value of {it:treatment} is picked to be the control group. The {cmd:stratum()} variables can be numeric or string, but should define a series of categories. Finally, the estimates are also reported for an "overlap" sample, which drops covariates that have no variation within a treatment level as well as observations from stratum that do not contain at least one observation from each treatment level. Unlike the full sample, all estimates are identified in the "overlap" sample. 65 | 66 | {marker options}{...} 67 | {title:Options} 68 | 69 | {dlgtab:Command Options} 70 | 71 | {phang}{opth treatment(varname)} Specifies the name of the treatment variable. Each value is taken as a different reatment level. 72 | 73 | {phang}{opth stratum(varname)} Not optional if no covariates are specified. This can be any variable, and each value is taken to define a stratum. If any level does not have all values of the treatment, those observations are dropped in the overlap sample. 74 | 75 | {phang}{opth cluster(varname)} Clustering covariate. 76 | 77 | {phang}{opt cw_uniform} For the CW estimator, should the target weighting scheme give all comparisons equal weight (default), or should it draw from the marginal empirical treatment distribution (if specified)? 78 | 79 | {phang}{opth mata:save(str)} supplies an alternative name for the mata structure which stores all estimates and variables in mata (default name is "multe_results"). Note this is in addition to results stored in {cmd:e()}; see {it:{help multe##results:stored results}} below for details. 80 | 81 | {marker example}{...} 82 | {title:Example 1: Generated data} 83 | 84 | {phang2}{cmd:. local nobs 1000 }{p_end} 85 | {phang2}{cmd:. local ktreat 5 }{p_end} 86 | {phang2}{cmd:. clear }{p_end} 87 | {phang2}{cmd:. set seed 1729 }{p_end} 88 | {phang2}{cmd:. set obs `nobs' }{p_end} 89 | {phang2}{cmd:. gen T = ceil(runiform() * `ktreat') }{p_end} 90 | {phang2}{cmd:. gen W = mod(_n, 10) }{p_end} 91 | {phang2}{cmd:. gen Y = T + runiform() }{p_end} 92 | {phang2}{cmd:. multe Y, treat(T) strat(W) }{p_end} 93 | {phang2}{cmd:. ereturn list }{p_end} 94 | 95 | {title:Example 2: Fryer and Levitt (2013)} 96 | 97 | {pstd}The data for this example can be downloaded with the {cmd:multe} package by specifying the option {cmd:all} (i.e. {it:net install multe, all}). 98 | 99 | {phang2}{cmd:. use example_fryer_levitt.dta, clear }{p_end} 100 | {phang2}{cmd:. local controls i.age_24 female i.siblings i.family_structure }{p_end} 101 | {phang2}{cmd:. multe std_iq_24 `controls' [w=W2C0], treat(race) stratum(SES_quintile)}{p_end} 102 | {phang2}{cmd:. ereturn list }{p_end} 103 | {phang2}{cmd:. multe, est(ATE) }{p_end} 104 | 105 | {marker results}{...} 106 | {title:Stored results} 107 | 108 | {pstd} 109 | {cmd:multe} stores the following in {cmd:e()}: 110 | 111 | {synoptset 23 tabbed}{...} 112 | {p2col 5 23 26 2: Scalars}{p_end} 113 | {synopt:{cmd:e(N)}}number of observations{p_end} 114 | 115 | {p2col 5 23 26 2: Macros}{p_end} 116 | {synopt:{cmd:e(cmd)}}{cmd:multe}{p_end} 117 | {synopt:{cmd:e(cmdline)}}command as typed{p_end} 118 | {synopt:{cmd:e(wtype)}}weight type{p_end} 119 | {synopt:{cmd:e(wexp)}}weight expression{p_end} 120 | {synopt:{cmd:e(depvar)}}name of dependent variable{p_end} 121 | {synopt:{cmd:e(treatment)}}name of multi-valued treatment{p_end} 122 | {synopt:{cmd:e(controls)}}name of control variables{p_end} 123 | {synopt:{cmd:e(vcetype)}}title used to label Std. Err.{p_end} 124 | {synopt:{cmd:e(properties)}}{cmd:b V}{p_end} 125 | {synopt:{cmd:e(mata)}}name of mata object where results are stored (see below){p_end} 126 | 127 | {p2col 5 23 26 2: Matrices}{p_end} 128 | {synopt:{cmd:e(b)}}coefficient vector for requested estimates (default is partly linear model).{p_end} 129 | {synopt:{cmd:e(V)}}cvariance matrix corresponding to the coefficient vector.{p_end} 130 | {synopt:{cmd:e(fullmatrix)}}matrix of results with all five estimates and corresponding SEs.{p_end} 131 | {synopt:{cmd:e(diffmatrix)}}matrix of differences between partly linear estimates and the rest of the estimates, with corresponding SEs.{p_end} 132 | {synopt:{cmd:e(full_beta)}}coefficients for each of the five estimates.{p_end} 133 | {synopt:{cmd:e(full_pop_se)}}SEs for each of the five estimates.{p_end} 134 | {synopt:{cmd:e(full_oracle_se)}}Oracle SEs for each of the five estimates.{p_end} 135 | {synopt:{cmd:e(full_diff)}}differences between partly linear estimates and the rest of the estimates.{p_end} 136 | {synopt:{cmd:e(full_diff_se)}}SEs for each of the differences.{p_end} 137 | {synopt:{cmd:e(overlapdiffmatrix)}}only if overlap sample is computed; analogous to {cmd:e(fullmatrix)}.{p_end} 138 | {synopt:{cmd:e(overlapmatrix)}}only if overlap sample is computed; analogous to {cmd:e(diffmatrix)}.{p_end} 139 | {synopt:{cmd:e(overlap_diff)}}only if overlap sample is computed; analogous to {cmd:e(full_beta)}{p_end} 140 | {synopt:{cmd:e(overlap_diff_se)}}only if overlap sample is computed; analogous to {cmd:e(full_pop_se)}{p_end} 141 | {synopt:{cmd:e(overlap_oracle_se)}}only if overlap sample is computed; analogous to {cmd:e(full_oracle_se}){p_end} 142 | {synopt:{cmd:e(overlap_pop_se)}}only if overlap sample is computed; analogous to {cmd:e(full_diff)}{p_end} 143 | {synopt:{cmd:e(overlap_beta}})only if overlap sample is computed; analogous to {cmd:e(full_diff_se)}{p_end} 144 | 145 | {p2col 5 23 26 2: Functions}{p_end} 146 | {synopt:{cmd:e(sample)}}marks estimation sample{p_end} 147 | {p2colreset}{...} 148 | 149 | {marker mata}{...} 150 | {pstd} 151 | In addition, the following data are available in {cmd:e(mata)} (default name: multe_results): 152 | 153 | real scalar has_overlap 154 | whether estimates for the overlap sample were computed 155 | 156 | real scalar Tk 157 | number of treatment levels 158 | 159 | string rowvector Tlevels 160 | treatment level names or labels 161 | 162 | string scalar Tvar 163 | name of treatment variable 164 | 165 | string scalar Yvar 166 | name of dependent variable 167 | 168 | real matrix full.N 169 | number of observations in full sample 170 | 171 | real matrix full.touse 172 | vector if 1s and 0s flagging used observations 173 | 174 | real matrix full.estA 175 | matrix with each of the give estimates 176 | 177 | real matrix full.estB 178 | matrix with the difference between PL and each of the other estimates 179 | 180 | real matrix full.seP 181 | SEs for estA 182 | 183 | real matrix full.seO 184 | Oracle SEs for estA 185 | 186 | real matrix full.seB 187 | SEs for estB 188 | 189 | real matrix full.Vpop_PL 190 | Covariance for PL 191 | 192 | real matrix full.Vpop_OWN 193 | Covariance for OWN 194 | 195 | real matrix full.Vpop_ATE 196 | Covariance for ATE 197 | 198 | real matrix full.Vpop_EW 199 | Covariance for EW 200 | 201 | real matrix full.Vpop_CW 202 | Covariance for CW 203 | 204 | real matrix full.Vo_OWN 205 | Oracle covariance OWN 206 | 207 | real matrix full.Vo_ATE 208 | Oracle covariance ATE 209 | 210 | real matrix full.Vo_EW 211 | Oracle covariance EW 212 | 213 | real matrix full.Vo_CW 214 | Oracle covariance CW 215 | 216 | real matrix full.Vdiff_OWN 217 | Covariance for differences between PL and OWN 218 | 219 | real matrix full.Vdiff_ATE 220 | Covariance for differences between PL and ATE 221 | 222 | real matrix full.Vdiff_EW 223 | Covariance for differences between PL and EW 224 | 225 | real matrix full.Vdiff_CW 226 | Covariance for differences between PL and CW 227 | 228 | real matrix full.LM.stat 229 | LM stat 230 | 231 | real matrix full.LM.df 232 | degrees of freedom 233 | 234 | real matrix full.LM.pval 235 | p-value 236 | 237 | struct full.Wa 238 | analogous to full.LM; contains stat, df, and pval for Wald test 239 | 240 | struct overlap 241 | analogous to full; contains all the same data for the overlap sample 242 | 243 | {marker references}{...} 244 | {title:References} 245 | 246 | {pstd} 247 | Goldsmith-Pinkham, Paul, Peter Hull, Michal Koles{c a'}r (2024): "Contamination Bias in Linear Regressions" Working Paper 248 | -------------------------------------------------------------------------------- /multe.pkg: -------------------------------------------------------------------------------- 1 | v 1.1.0 2 | d 3 | d 'MULTE': Multiple Treatment Effects regression with saturated group control 4 | d 5 | d Given a multi-valued treatment, a saturated group variable (or a 6 | d varlist which will be used to create a single, saturated group variable) 7 | d within which treatment is as-good-as-randomly assigned, and a dependent 8 | d variable, multe computes equal-weighted (ATE), variance-weighted, 9 | d efficiently-weighted treatment effects estimates and contamination bias 10 | d decomposition as in Goldsmith-Pinkham et al. (2022). 11 | d 12 | d KW: multiple 13 | d KW: treatment 14 | d KW: effects 15 | d KW: Goldsmith-Pinkham 16 | d KW: Hull 17 | d KW: Kolesár 18 | d 19 | d Requires: Stata version 14.1 20 | d 21 | d Distribution-Date: 20240309 22 | d 23 | d Author: Paul Goldsmith-Pinkham 24 | d Support: email [email] 25 | d 26 | d Author: Peter Hull 27 | d Support: email [email] 28 | d 29 | d Author: Michal Kolesár 30 | d Support: email [email] 31 | d 32 | f build/lmulte.mlib 33 | f src/ado/multe.ado 34 | f doc/multe.sthlp 35 | f src/mata/multe.mata 36 | f src/mata/multe_helpers.mata 37 | f test/example_star.dta 38 | f test/example_fryer_levitt.dta 39 | -------------------------------------------------------------------------------- /src/ado/multe.ado: -------------------------------------------------------------------------------- 1 | *! version 1.1.0 09Mar2024 2 | *! Multiple Treatment Effects Regression 3 | *! Based code and notes by Michal Kolesár 4 | *! Adapted for Stata by Mauricio Caceres Bravo and Jerray Chang 5 | 6 | capture program drop multe 7 | program multe, eclass 8 | version 14.1 9 | 10 | qui syntax [anything(everything)] [if] [in] [aw pw], [ESTimates(str) full overlap diff oracle *] 11 | if `"`anything'"' == "" { 12 | if ( `"`e(cmd)'"' != "multe" ) error 301 13 | if ( "`estimates'`full'`overlap'`diff'`oracle'" == "" & replay() ) { 14 | Post `e(mata)', `e(displayopts)' repost 15 | } 16 | else { 17 | Post `e(mata)', est(`estimates') `full' `overlap' `diff' `oracle' `options' repost 18 | } 19 | Display, title(`coeftitle') `options' 20 | exit 0 21 | } 22 | 23 | syntax varlist(numeric fv) /// depvar controls (sans stratum) 24 | [if] [in] [aw pw], /// subset, weights 25 | TREATment(varname) /// treatment variable 26 | [ /// 27 | STRATum(varname) /// stratum name 28 | cluster(varname) /// cluster by 29 | CW_uniform /// 30 | GENerate(str) /// save lambdas/taus 31 | MATAsave(str) /// Save resulting mata object 32 | debug /// 33 | ] 34 | 35 | * NB: Sometimes Stata idiosyncrasies drive me to madness. For example, 36 | * in this case to read the correct set of variables used in an interacted 37 | * regression into mata, I have to introduce a fake interaction with a 38 | * constant, lest Stata automagically exclude a base level for mata that it 39 | * actually included in the regression. Is Stata Basilio, keeping me locked 40 | * in the tower, punishing me for the mere crime of daring to code? 41 | 42 | tempvar cons cons2 43 | gen byte `cons' = 1 44 | gen byte `cons2' = 1 45 | gettoken Y X: varlist 46 | local Xbak: copy local X 47 | local Y `Y' 48 | 49 | if ( (`"`X'"' == "") & (`"`stratum'"' == "") ) { 50 | disp as err "no controls; add covariates or specify -stratum()-" 51 | exit 198 52 | } 53 | local X `cons' `X' 54 | 55 | * Mark if, in, and any missing values by obs 56 | local varlist `varlist' `treatment' `stratum' 57 | local wbak: copy local weight 58 | if ( `"`weight'"' != "" ) { 59 | tempvar touse wgt 60 | qui gen double `wgt' `exp' `if' `in' 61 | local wcall [`weight'=`wgt'] 62 | mark `touse' `if' `in' `wcall' 63 | markout `touse' `varlist', strok 64 | } 65 | else { 66 | local wcall 67 | local wgt 68 | marksample touse, strok 69 | } 70 | 71 | qui count if `touse' 72 | if ( `r(N)' == 0 ) { 73 | disp as txt "insufficient observations for estimation" 74 | exit 498 75 | } 76 | 77 | * Copy to mata for mata fun 78 | if `"`matasave'"' == "" local results multe_results 79 | else local results: copy local matasave 80 | 81 | * Encode variables for internal use 82 | qui levelsof `treatment' if `touse', loc(Tlevels) 83 | local Tk: list sizeof Tlevels 84 | 85 | tempvar T W C 86 | qui egen `c(obs_t)' `T' = group(`treatment') if `touse' 87 | if ( `"`stratum'"' != "" ) { 88 | qui egen `c(obs_t)' `W' = group(`stratum') if `touse' 89 | local Wi i.`W' 90 | 91 | qui levelsof `stratum' if `touse', loc(Slevels) 92 | local Sk: list sizeof Slevels 93 | } 94 | else { 95 | local Wi 96 | local Sk = 1 97 | } 98 | 99 | if ( `"`cluster'"' != "" ) { 100 | qui egen `c(obs_t)' `C' = group(`cluster') if `touse' 101 | local cluster: copy local C 102 | } 103 | 104 | * Get omitted varlist 105 | tempname zomit 106 | helper_strip_omitted `X' `Wi' if `touse' `wcall', noconstant extra(b1.`T') 107 | local zfull `r(expanded)' 108 | local zindep `r(varlist)' 109 | matrix `zomit' = r(omit) 110 | foreach var of local zfull { 111 | if regexm("`var'", "([0-9]+|^).*o\.(.+)") { 112 | local name = regexs(2) 113 | if ( `"`stratum'"' != "" ) { 114 | local name: subinstr local name "`W'" "`stratum'" 115 | if ( strpos("`name'", "`stratum'") ) { 116 | mata st_local("map", tokens(st_local("Slevels"))[`=regexs(1)']) 117 | } 118 | else local map = regexs(1) 119 | if ( "`map'" != "" ) local dot . 120 | else local dot 121 | disp "note: `map'`dot'`name' omitted because of collinearity" 122 | } 123 | else { 124 | disp "note: `name' omitted because of collinearity" 125 | } 126 | } 127 | } 128 | 129 | tempname ziomit 130 | helper_strip_omitted ibn.`T'#c.(`zindep') if `touse' `wcall', noconstant 131 | local zifull `r(expanded)' 132 | local ziindep `r(varlist)' 133 | matrix `ziomit' = r(omit) 134 | 135 | * 0. Base decomposition 136 | * --------------------- 137 | 138 | tempname multeworker 139 | mata `multeworker' = MulTE() 140 | mata `results' = MulTE_Return() 141 | mata `results'.Tk = `Tk' 142 | mata `results'.Tvar = st_local("treatment") 143 | mata `results'.Yvar = st_local("Y") 144 | mata `results'.Tlevels = tokens(st_local("Tlevels")) 145 | mata `results'.full = `multeworker'.decomposition(0) 146 | 147 | * 1. Drop strata with no overlap 148 | * ------------------------------ 149 | 150 | local rerun = 0 151 | if ( `Sk' > 1 ) { 152 | tempvar Wtag Ttag Tuniq Tdrop 153 | egen byte `Wtag' = tag(`W') if `touse' 154 | egen byte `Ttag' = tag(`W' `T') if `touse' 155 | egen double `Tuniq' = sum(`Ttag') if `touse', by(`W') 156 | gen byte `Tdrop' = (`Tuniq' < `Tk') if `touse' 157 | 158 | qui count if (`Wtag' == 1) & (`Tdrop' == 1) & `touse' 159 | local nsdrop = `r(N)' 160 | 161 | qui count if (`Tdrop' == 1) & `touse' 162 | local nobs = `r(N)' 163 | 164 | qui replace `touse' = 0 if (`Tdrop' == 1) 165 | if ( `nobs' | `nsdrop' ) { 166 | disp as txt "dropped `nsdrop' control strata without sufficient overlap (`nobs' obs)" 167 | qui drop `W' 168 | qui egen `c(obs_t)' `W' = group(`control') if `touse' 169 | local rerun = 1 170 | } 171 | 172 | qui count if `touse' 173 | if ( `r(N)' == 0 ) { 174 | disp "overlap sample is empty; cannot run overlap" 175 | } 176 | } 177 | 178 | * 2. Drop controls that don't have within-treatment variation 179 | * ----------------------------------------------------------- 180 | 181 | tempname zany zlevel 182 | mata `zany' = J(1, `:list sizeof zindep', 0) 183 | forvalues j = 1 / `Tk' { 184 | if ( `j' == 1 ) local bn bn 185 | else local bn 186 | helper_strip_omitted 1.`cons2'#c.(`zindep') if `touse' & (`T' == `j') `wcall', noconstant 187 | mata `zlevel' = st_matrix("r(omit)") 188 | mata `zany'[selectindex(`zlevel')] = select(`zlevel', `zlevel') 189 | } 190 | mata st_matrix("`zomit'", `zany') 191 | mata st_local("zrerun", strofreal(any(`zany'))) 192 | 193 | * Re-run in overlap sample 194 | if ( `zrerun' ) { 195 | mata st_local("zindep2", invtokens(select(tokens(st_local("zindep")), !`zany'))) 196 | local zdrop: list zindep - zindep2 197 | local zindep: copy local zindep2 198 | disp "The following variables have no within-treatment variation" 199 | disp "and are dropped from the overlap sample:" 200 | foreach var of local zdrop { 201 | disp _col(8) "`var'" 202 | } 203 | local rerun = 1 204 | 205 | fvexpand ibn.`T'#c.(`zindep') 206 | local zifull `r(varlist)' 207 | local ziindep `r(varlist)' 208 | mata st_matrix("`ziomit'", J(1, `:list sizeof zindep' * `Tk', 0)) 209 | 210 | mata `results'.overlap = `multeworker'.decomposition(1) 211 | mata `results'.has_overlap = 1 212 | } 213 | 214 | * Display standard Stata table; save in e() 215 | * ----------------------------------------- 216 | 217 | Post `results', est(PL) cluster(`cluster') 218 | mata st_local("cmdline", "multe " + st_local("0")) 219 | ereturn local cmdline: copy local cmdline 220 | ereturn local wtype = "`weight'" 221 | ereturn local wexp = "`exp'" 222 | ereturn local cmd = "multe" 223 | ereturn local depvar = "`Y'" 224 | ereturn local treatment = "`treatment'" 225 | ereturn local controls = "`X'" 226 | ereturn local cluster = "`cluster'" 227 | ereturn local stratum = "`stratum'" 228 | ereturn local mata = "`results'" 229 | Display, title(`coeftitle') `options' 230 | 231 | * Save full results 232 | tempname full_beta full_pop_se full_oracle_se full_diff full_diff_se levels 233 | 234 | mata st_matrix(st_local("full_beta"), `results'.full.estA) 235 | mata st_matrix(st_local("full_diff"), `results'.full.estB) 236 | mata st_matrix(st_local("full_pop_se"), `results'.full.seP) 237 | mata st_matrix(st_local("full_diff_se"), `results'.full.seB) 238 | mata st_matrix(st_local("full_oracle_se"), `results'.full.seO) 239 | 240 | mata st_matrixcolstripe(st_local("full_beta"), (J(5, 1, ""), `results'.full.labels')) 241 | mata st_matrixcolstripe(st_local("full_diff"), (J(4, 1, ""), `results'.full.labels[2..5]')) 242 | mata st_matrixcolstripe(st_local("full_pop_se"), (J(5, 1, ""), `results'.full.labels')) 243 | mata st_matrixcolstripe(st_local("full_diff_se"), (J(4, 1, ""), `results'.full.labels[2..5]')) 244 | mata st_matrixcolstripe(st_local("full_oracle_se"), (J(5, 1, ""), `results'.full.labels')) 245 | 246 | mata `levels' = `results'.Tlevels[2..`Tk']' 247 | mata st_matrixrowstripe(st_local("full_beta"), (J(`Tk'-1, 1, ""), `levels')) 248 | mata st_matrixrowstripe(st_local("full_diff"), (J(`Tk'-1, 1, ""), `levels')) 249 | mata st_matrixrowstripe(st_local("full_pop_se"), (J(`Tk'-1, 1, ""), `levels')) 250 | mata st_matrixrowstripe(st_local("full_diff_se"), (J(`Tk'-1, 1, ""), `levels')) 251 | mata st_matrixrowstripe(st_local("full_oracle_se"), (J(`Tk'-1, 1, ""), `levels')) 252 | 253 | ereturn matrix full_beta = `full_beta' 254 | ereturn matrix full_pop_se = `full_pop_se' 255 | ereturn matrix full_oracle_se = `full_oracle_se' 256 | ereturn matrix full_diff = `full_diff' 257 | ereturn matrix full_diff_se = `full_diff_se' 258 | 259 | * Results from re-run in overlap sample 260 | * ------------------------------------- 261 | 262 | if ( `rerun' ) { 263 | tempname overlap_beta overlap_pop_se overlap_oracle_se overlap_diff overlap_diff_se 264 | 265 | mata st_matrix(st_local("overlap_beta"), `results'.overlap.estA) 266 | mata st_matrix(st_local("overlap_diff"), `results'.overlap.estB) 267 | mata st_matrix(st_local("overlap_pop_se"), `results'.overlap.seP) 268 | mata st_matrix(st_local("overlap_diff_se"), `results'.overlap.seB) 269 | mata st_matrix(st_local("overlap_oracle_se"), `results'.overlap.seO) 270 | 271 | mata st_matrixcolstripe(st_local("overlap_beta"), (J(5, 1, ""), `results'.overlap.labels')) 272 | mata st_matrixcolstripe(st_local("overlap_diff"), (J(4, 1, ""), `results'.overlap.labels[2..5]')) 273 | mata st_matrixcolstripe(st_local("overlap_pop_se"), (J(5, 1, ""), `results'.overlap.labels')) 274 | mata st_matrixcolstripe(st_local("overlap_diff_se"), (J(4, 1, ""), `results'.overlap.labels[2..5]')) 275 | mata st_matrixcolstripe(st_local("overlap_oracle_se"), (J(5, 1, ""), `results'.overlap.labels')) 276 | 277 | mata st_matrixrowstripe(st_local("overlap_beta"), (J(`Tk'-1, 1, ""), `levels')) 278 | mata st_matrixrowstripe(st_local("overlap_diff"), (J(`Tk'-1, 1, ""), `levels')) 279 | mata st_matrixrowstripe(st_local("overlap_pop_se"), (J(`Tk'-1, 1, ""), `levels')) 280 | mata st_matrixrowstripe(st_local("overlap_diff_se"), (J(`Tk'-1, 1, ""), `levels')) 281 | mata st_matrixrowstripe(st_local("overlap_oracle_se"), (J(`Tk'-1, 1, ""), `levels')) 282 | 283 | ereturn matrix overlap_beta = `overlap_beta' 284 | ereturn matrix overlap_pop_se = `overlap_pop_se' 285 | ereturn matrix overlap_oracle_se = `overlap_oracle_se' 286 | ereturn matrix overlap_diff = `overlap_diff' 287 | ereturn matrix overlap_diff_se = `overlap_diff_se' 288 | } 289 | else { 290 | mata `results'.has_overlap = 0 291 | } 292 | 293 | * Show estimates (including tests) 294 | tempname fullmatrix diffmatrix 295 | mata st_matrix("`fullmatrix'", `results'.full.estA#(1\0) :+ `results'.full.seP#(0\1)) 296 | mata st_matrix("`diffmatrix'", `results'.full.estB#(1\0) :+ `results'.full.seB#(0\1)) 297 | mata st_matrixcolstripe("`fullmatrix'", (J(5, 1, ""), `results'.full.labels')) 298 | mata st_matrixcolstripe("`diffmatrix'", (J(4, 1, ""), `results'.full.labels[2..5]')) 299 | 300 | tempname sub labs 301 | mata `labs' = tokens(st_local("Tlevels"))[2..`Tk'] 302 | if ( strpos("`:type `treatment''", "str") == 0 ) { 303 | mata `sub' = st_varvaluelabel(st_local("treatment")) 304 | mata `labs' = (`sub' != "")? st_vlmap(`sub', strtoreal(`labs')): `labs' 305 | } 306 | mata st_matrixrowstripe("`fullmatrix'", (J(2*(`Tk'-1), 1, ""), vec(`labs' \ J(1, `Tk'-1, "SE")))) 307 | mata st_matrixrowstripe("`diffmatrix'", (J(2*(`Tk'-1), 1, ""), vec(`labs' \ J(1, `Tk'-1, "SE")))) 308 | 309 | mata printf("\nAlternative Estimates on Full Sample:\n") 310 | matlist `fullmatrix', format(%7.4g) 311 | ereturn matrix fullmatrix = `fullmatrix' 312 | ereturn matrix diffmatrix = `diffmatrix' 313 | 314 | mata printf("\nP-values for null hypothesis of no propensity score variation:\n") 315 | mata printf("Wald test: %9.6g\n", `results'.full.Wa.pval) 316 | mata printf(" LM test: %9.6g\n", `results'.full.LM.pval) 317 | 318 | if ( `rerun' ) { 319 | tempname overlapmatrix overlapdiffmatrix 320 | mata st_matrix("`overlapmatrix'", `results'.overlap.estA#(1\0) :+ `results'.overlap.seP#(0\1)) 321 | mata st_matrix("`overlapdiffmatrix'", `results'.overlap.estB#(1\0) :+ `results'.overlap.seB#(0\1)) 322 | 323 | mata st_matrixcolstripe("`overlapmatrix'", (J(5, 1, ""), `results'.overlap.labels')) 324 | mata st_matrixcolstripe("`overlapdiffmatrix'", (J(4, 1, ""), `results'.overlap.labels[2..5]')) 325 | mata st_matrixrowstripe("`overlapmatrix'", (J(2*(`Tk'-1), 1, ""), vec(`labs' \ J(1, `Tk'-1, "SE")))) 326 | mata st_matrixrowstripe("`overlapdiffmatrix'", (J(2*(`Tk'-1), 1, ""), vec(`labs' \ J(1, `Tk'-1, "SE")))) 327 | 328 | mata printf("\nAlternativ Estimates on Overlap Sample:") 329 | matlist `overlapmatrix', format(%7.4g) 330 | ereturn matrix overlapmatrix = `overlapmatrix' 331 | ereturn matrix overlapdiffmatrix = `overlapdiffmatrix' 332 | } 333 | 334 | disp "" 335 | disp "Note: You can post any combination of results from the table to Stata:" 336 | disp "" 337 | disp " multe, est(estimate) [{full|overlap} diff oracle]" 338 | disp "" 339 | disp "Examples:" 340 | disp "" 341 | disp " {stata multe, est(ATE) full oracle}" 342 | disp " {stata multe, est(CW) overlap diff}" 343 | end 344 | 345 | capture program drop Post 346 | program Post, eclass 347 | syntax namelist(max=1), ESTimates(str) [full overlap diff oracle repost cluster(str) *] 348 | 349 | if ( ("`full'" != "") & ("`overlap'" != "") ) { 350 | disp as err "unable to display both full and overlap sample results" 351 | exit 198 352 | } 353 | 354 | if ( ("`full'" == "") & ("`overlap'" == "") ) { 355 | local full full 356 | } 357 | 358 | local estimates = upper("`estimates'") 359 | if ( !inlist(upper("`estimates'"), "PL", "OWN", "ATE", "EW", "CW") ) { 360 | disp as err "estimates(`estimates') unknown; must be one of PL, OWN, ATE, EW, CW" 361 | exit 198 362 | } 363 | 364 | if ( ("`diff'" != "") & ("`estimates'" == "PL") ) { 365 | disp as err "diff estimates only available relative to PL" 366 | exit 198 367 | } 368 | 369 | if ( ("`diff'" != "") & ("`oracle'" == "oracle") ) { 370 | disp as err "oracle SE not available for diff estimates" 371 | exit 198 372 | } 373 | 374 | if ( ("`oracle'" == "oracle") & inlist("`estimates'", "PL", "OWN") ) { 375 | disp as err "oracle SE not available for PL, OWN" 376 | exit 198 377 | } 378 | 379 | mata st_local("has_overlap", strofreal(`namelist'.has_overlap)) 380 | if ( ("`overlap'" != "") & (`has_overlap' == 0) ) { 381 | disp as err "requested overlap sample results but overlap was not computed" 382 | exit 198 383 | } 384 | 385 | local labPL "Partly Linear Model" 386 | local labOWN "Own Treatment Effects" 387 | local labATE "ATE" 388 | local labEW "Easiest-to-estimate Weighted ATE" 389 | local labCW "Easiest-to-estimate Common Weighted ATE" 390 | 391 | FreeMatrix b V 392 | tempname index 393 | mata `index' = selectindex(`namelist'.`full'`overlap'.labels :== "`estimates'") 394 | if ( "`diff'" == "" ) { 395 | mata st_matrix(st_local("b"), `namelist'.`full'`overlap'.estA[., `index']) 396 | if ( "`oracle'" == "oracle" ) { 397 | mata st_matrix(st_local("V"), `namelist'.`full'`overlap'.Vo_`estimates') 398 | } 399 | else { 400 | mata st_matrix(st_local("V"), `namelist'.`full'`overlap'.Vpop_`estimates') 401 | } 402 | } 403 | else { 404 | mata st_matrix(st_local("b"), `namelist'.`full'`overlap'.estB[., `index'-1]) 405 | mata st_matrix(st_local("V"), `namelist'.`full'`overlap'.Vdiff_`estimates') 406 | } 407 | mata multe_helper_post(st_local("b"), st_local("V"), `namelist') 408 | 409 | tempvar touse 410 | qui gen byte `touse' = . 411 | mata st_store(., st_local("touse"), `namelist'.`full'`overlap'.touse) 412 | qui count if `touse' 413 | 414 | if "`repost'" == "repost" { 415 | ereturn scalar N = r(N) 416 | ereturn repost b=`b' V=`V', esample(`touse') 417 | } 418 | else ereturn post `b' `V', esample(`touse') obs(`r(N)') 419 | 420 | if ( "`cluster'" == "" ) { 421 | if ( "`oracle'" == "oracle" ) ereturn local vcetype "Oracle" 422 | else ereturn local vcetype "" 423 | } 424 | else { 425 | if ( "`oracle'" == "oracle" ) ereturn local vcetype "Oracle Cluster" 426 | else ereturn local vcetype "Cluster" 427 | } 428 | ereturn local displayopts estimates(`estimates') `full' `overlap' `diff' `oracle' 429 | 430 | c_local coeftitle `lab`estimates'' Estimates (`full'`overlap' sample) 431 | c_local options: copy local options 432 | end 433 | 434 | capture program drop Display 435 | program Display 436 | syntax, [title(str asis) *] 437 | _coef_table_header, nomodeltest title(`title') 438 | disp "" 439 | _coef_table, noempty `options' 440 | end 441 | 442 | capture program drop FreeMatrix 443 | program FreeMatrix 444 | local FreeCounter 0 445 | local FreeMatrix 446 | foreach FM of local 0 { 447 | cap error 0 448 | while ( _rc == 0 ) { 449 | cap confirm matrix MulTE`++FreeCounter' 450 | c_local `FM' MulTE`FreeCounter' 451 | } 452 | } 453 | end 454 | 455 | capture program drop helper_strip_omitted 456 | program helper_strip_omitted, rclass 457 | syntax anything(equalok) [if] [aw fw pw], [extra(str) *] 458 | if ( `"`weight'"' != "" ) local wcall [`weight' `exp'] 459 | fvexpand `extra' 460 | local extra `r(varlist)' 461 | qui _rmcoll `extra' `anything' `if' `wcall', expand `options' 462 | local expanded `r(varlist)' 463 | 464 | tempname b omit omitbase final keep 465 | mata `keep' = (`:list sizeof extra'+1)..`:list sizeof expanded' 466 | matrix `b' = J(1, `:list sizeof expanded', .) 467 | matrix colnames `b' = `expanded' 468 | matrix `b' = `b'[1,(`:list sizeof extra'+1)..`:list sizeof expanded'] 469 | mata st_local("expanded", invtokens(tokens(st_local("expanded"))[`keep'])) 470 | 471 | _ms_omit_info `b' 472 | matrix `omit' = r(omit) 473 | mata `final' = select(tokens(st_local("expanded")), !st_matrix("r(omit)")) 474 | mata st_local("varlist", invtokens(cols(`final')? `final': "")) 475 | local controls: list expanded - extra 476 | 477 | return local expanded: copy local expanded 478 | return local varlist: copy local varlist 479 | return matrix omit = `omit' 480 | end 481 | -------------------------------------------------------------------------------- /src/build.do: -------------------------------------------------------------------------------- 1 | cap noi ado uninstall multe 2 | mata: mata clear 3 | mata: mata set matastrict on 4 | mata: mata set mataoptimize on 5 | cap noi mkdir build 6 | cap noi erase build/lmulte.mlib 7 | do src/mata/multe_helpers.mata 8 | do src/mata/multe.mata 9 | mata: mata mlib create lmulte, dir("build") replace 10 | mata: mata mlib add lmulte MulTE*() multe_helper*(), dir("build") complete 11 | net install multe, from(`c(pwd)') replace 12 | -------------------------------------------------------------------------------- /src/mata/multe.mata: -------------------------------------------------------------------------------- 1 | cap mata mata drop MulTE() 2 | cap mata mata drop MulTE_Info() 3 | cap mata mata drop MulTE_Return() 4 | cap mata mata drop MulTE_Results() 5 | cap mata mata drop MulTE_Tests() 6 | cap mata mata drop multe_helper_post() 7 | 8 | mata 9 | class MulTE 10 | { 11 | real scalar cache 12 | string rowvector labels 13 | 14 | struct MulTE_Info scalar info 15 | void new() 16 | void setup() 17 | void debug() 18 | struct MulTE_Results scalar decomposition() 19 | } 20 | 21 | struct MulTE_Tests 22 | { 23 | real scalar stat 24 | real scalar df 25 | real scalar pval 26 | } 27 | 28 | struct MulTE_Return 29 | { 30 | struct MulTE_Results scalar full, overlap 31 | real scalar has_overlap, Tk 32 | string scalar Tvar, Yvar 33 | string rowvector Tlevels 34 | } 35 | 36 | struct MulTE_Results 37 | { 38 | string rowvector labels 39 | real matrix estA, estB, seO, seP, seB 40 | struct MulTE_Tests scalar LM, Wa 41 | real matrix Vpop_PL, Vpop_OWN, Vpop_ATE, Vpop_EW, Vpop_CW 42 | real matrix Vo_ATE, Vo_EW, Vo_CW 43 | real matrix Vdiff_OWN, Vdiff_ATE, Vdiff_EW, Vdiff_CW 44 | real vector touse 45 | real scalar N 46 | } 47 | 48 | struct MulTE_Info 49 | { 50 | string scalar cluster 51 | string scalar touse 52 | string scalar wcall 53 | string scalar wgtvar 54 | string scalar Y 55 | string scalar T 56 | string scalar X 57 | string scalar Wi 58 | string scalar cons2 59 | string scalar zindep 60 | string scalar zifull 61 | 62 | real scalar Tk 63 | real scalar N 64 | real scalar Zk 65 | real scalar wgt 66 | real scalar zerotol 67 | real scalar cw_uniform 68 | real scalar debug 69 | 70 | real vector zomit 71 | real vector ziomit 72 | } 73 | 74 | // ----------------------------------------------------------------- // 75 | // Main function // 76 | // ----------------------------------------------------------------- // 77 | 78 | void function MulTE::new() 79 | { 80 | cache = 0 81 | labels = "PL", "OWN", "ATE", "EW", "CW" 82 | this.info.zerotol = 1e-6 83 | } 84 | 85 | void function MulTE::setup() 86 | { 87 | this.info.cluster = st_local("cluster") 88 | this.info.touse = st_local("touse") 89 | this.info.wcall = st_local("wcall") 90 | this.info.wgtvar = st_local("wgt") 91 | this.info.Y = st_local("Y") 92 | this.info.T = st_local("T") 93 | this.info.X = st_local("X") 94 | this.info.Wi = st_local("Wi") 95 | this.info.zindep = st_local("zindep") 96 | this.info.zifull = st_local("zifull") 97 | this.info.cons2 = st_local("cons2") 98 | this.info.zomit = st_matrix(st_local("zomit")) 99 | this.info.ziomit = st_matrix(st_local("ziomit")) 100 | this.info.Tk = strtoreal(st_local("Tk")) 101 | this.info.wgt = (st_local("wgt") != "") 102 | this.info.debug = (st_local("debug") != "") 103 | this.info.cw_uniform = (st_local("cw_uniform") != "") 104 | } 105 | 106 | // res = MulTE_Results() 107 | struct MulTE_Results scalar MulTE::decomposition(real scalar isorted) 108 | { 109 | struct MulTE_Results scalar res 110 | struct multe_helper_results scalar rl, ri, rk, ro, dtXr, Xhat 111 | string scalar regopts, regcall, regtol, zvars, mtol, ir_start 112 | string vector mlfit 113 | string matrix ir_nam 114 | real scalar i, j, wsum, ir_diff 115 | real vector Y, T, X0, C, ws, LM, Wa 116 | real vector Zb, s, w_s, Y_s, lam, ipi, cw 117 | real vector seli, selj, idx1, idx1n, th1, th, si 118 | real matrix Zi, Zm, Xf, tX, deltak, gamk, dtX, pp, Z_s, X_s 119 | real matrix psi_beta, psi_al, psi_po, psi_ownk, psi_k, psi_1, psi_or, psi_rk, psi_ri 120 | real matrix ir_omit, ir_gam, ir_ord, ate0, ate 121 | real matrix pis, vpi, xf1, M0, M, xf1l, xf1r, a 122 | real matrix Sc, He, He1112, Vu, pis0, Scr, Her, Her1112, Vr 123 | 124 | this.setup() 125 | res.labels = this.labels 126 | res.estA = res.estB = res.seP = res.seB = res.seO = J(this.info.Tk-1, 5, .) 127 | 128 | this.debug("read in data") 129 | res.touse = st_data(., this.info.touse) 130 | res.N = sum(res.touse) 131 | Y = st_data(., this.info.Y, this.info.touse) 132 | T = st_data(., this.info.T, this.info.touse) 133 | X0 = T :== 1 134 | zvars = sprintf("%s#c.(%s)", this.info.cons2, this.info.zindep) 135 | Zm = st_data(., zvars, this.info.touse) 136 | Xf = J(res.N, this.info.Tk-1, 0) 137 | tX = J(res.N, this.info.Tk-1, 0) 138 | C = this.info.cluster == ""? .: st_data(., this.info.cluster, this.info.touse) 139 | ws = this.info.wgt? st_data(., this.info.wgtvar, this.info.touse): 1 140 | wsum = this.info.wgt? quadsum(ws): res.N 141 | for (j = 1; j < this.info.Tk; j++) { 142 | Xf[., j] = (T :== (j+1)) 143 | } 144 | 145 | // Regs are largely run within mata because we don't want Stata to drop 146 | // collinear variables without us explicitly allowing for it. 147 | this.debug("run base reg") 148 | this.info.Zk = cols(Zm) 149 | regopts = sprintf("%s if %s, noconstant", this.info.wcall, this.info.touse) 150 | rl = multe_helper_olswr(Y, (Xf, Zm), ws) 151 | res.estA[., 1] = rl.coefficients[1::(this.info.Tk-1)] 152 | 153 | this.debug("run linear propensity") 154 | for (j = 1; j < this.info.Tk; j++) { 155 | tX[., j] = Xf[., j] - Zm * multe_helper_olsw(Xf[., j], Zm, ws) 156 | } 157 | psi_beta = (tX :* ws :* rl.residuals) * invsym(quadcross(tX, ws, tX)) 158 | res.Vpop_PL = multe_helper_Vhat(psi_beta, C) 159 | res.seP[., 1] = sqrt(diagonal(res.Vpop_PL)) 160 | 161 | // Note stata orders variables differently for the collinearity check so 162 | // we can't directly use the info from the previous check. 163 | this.debug("parse omit info for interacted model") 164 | ir_omit = this.info.ziomit 165 | ir_nam = tokens(this.info.zifull)' 166 | ir_ord = order((strtoreal(ustrregexra(ir_nam, "^(\d+).+", "$1")), (1::length(ir_omit))), (1, 2)) 167 | ir_nam = rowshape(ustrregexra(ir_nam[ir_ord], ".+#", ""), this.info.Tk)' 168 | ir_omit = rowshape(isorted? ir_omit: ir_omit[ir_ord], this.info.Tk)' 169 | ir_diff = 0 170 | for(j = 1; j <= this.info.Tk; j++) { 171 | ir_diff = any(ir_nam[., 1] :!= ir_nam[., j]) | any(ir_omit[., j]) 172 | } 173 | // zvars = sprintf("ibn.%s#c.(%s)", this.info.T, this.info.zindep) 174 | // Zi = select(st_data(., zvars, this.info.touse)[., ir_ord'], vec(ir_omit)') 175 | this.debug("run fully interacted model") 176 | Zi = st_data(., this.info.zifull, this.info.touse)[., ir_ord] 177 | ri = multe_helper_olswr(Y, Zi, ws) 178 | ir_gam = ri.coefficients 179 | ir_gam[selectindex(vec(ir_omit))] = J(sum(ir_omit), 1, .) 180 | ir_gam = (rowshape(ir_gam[(this.info.Zk+1)::rows(ir_gam)], this.info.Tk-1)' :- ir_gam[1::this.info.Zk]) 181 | Zb = mean(Zm, ws) 182 | res.estA[., 3] = (Zb * ir_gam)' 183 | 184 | this.debug("interacted model results") 185 | psi_al = (Zi :* ws :* ri.residuals) * invsym(quadcross(Zi, ws, Zi)) 186 | psi_al[., selectindex(vec(ir_omit))] = J(res.N, sum(ir_omit), .) 187 | Zi = . 188 | ate0 = psi_al[., 1..this.info.Zk] * Zb' 189 | ate = J(res.N, this.info.Tk-1, .) 190 | for(j = 1; j < this.info.Tk; j++) { 191 | ate[., j] = psi_al[., ((j * this.info.Zk)+1)..((j+1)*this.info.Zk)] * Zb' 192 | } 193 | psi_al[., selectindex(vec(ir_omit))] = J(res.N, sum(ir_omit), 0) 194 | ate = ate :- ate0 195 | res.Vo_ATE = multe_helper_Vhat(ate, C) 196 | res.seO[., 3] = sqrt(diagonal(res.Vo_ATE)) 197 | psi_po = ate :+ (ws :* ((Zm :- Zb) * ir_gam/wsum)) 198 | res.Vpop_ATE = multe_helper_Vhat(psi_po, C) 199 | res.Vdiff_ATE = multe_helper_Vhat(psi_beta :- psi_po, C) 200 | res.seP[., 3] = sqrt(diagonal(res.Vpop_ATE)) 201 | res.seB[., 3] = sqrt(diagonal(res.Vdiff_ATE)) 202 | 203 | psi_ownk = J(rows(psi_beta), cols(psi_beta), .) 204 | psi_rk = J(rows(psi_beta), cols(psi_beta), 0) 205 | psi_ri = J(rows(psi_beta), cols(psi_beta), 0) 206 | psi_1 = psi_beta 207 | this.debug("run one-at-a-time") 208 | for(j = 1; j < this.info.Tk; j++) { 209 | rk = multe_helper_olswr((Xf[.,j] :* Zm), (Xf, Zm), ws) 210 | deltak = rk.coefficients[j,.] 211 | gamk = select(ir_gam[.,j], deltak') 212 | res.estA[j, 2] = select(deltak, deltak) * gamk 213 | if ( this.info.Tk > 2 ) { 214 | dtXr = multe_helper_olswr(tX[.,j], tX[., multe_helper_antiselect(1..(this.info.Tk-1), j)], ws) 215 | dtX = dtXr.residuals 216 | dtXr = multe_helper_results() 217 | } 218 | else { 219 | dtX = tX 220 | } 221 | pp = psi_al[., ((j * this.info.Zk)+1)..((j + 1)*this.info.Zk)] :- psi_al[., 1..this.info.Zk] 222 | psi_ownk[., j] = pp * deltak' + select(ws :* dtX :* rk.residuals :/ quadsum(ws:*(dtX:^2)), deltak) * gamk 223 | 224 | // 1 at a time 225 | s = selectindex(X0 :| Xf[., j]) 226 | Z_s = Zm[s, .] 227 | X_s = Xf[s, j] 228 | w_s = this.info.wgt? ws[s]: 1 229 | Y_s = Y[s] 230 | Xhat = multe_helper_olswr(X_s, Z_s, w_s) 231 | rk = multe_helper_olswr(Y_s, (Xhat.residuals, Z_s), w_s) 232 | 233 | res.estA[j, 4] = rk.coefficients[1, 1] 234 | psi_k = w_s :* Xhat.residuals :/ quadsum(w_s:*(Xhat.residuals:^2)) 235 | psi_rk[s, j] = rk.residuals :* psi_k 236 | psi_ri[s, j] = ri.residuals[s,.] :* psi_k 237 | 238 | // Test against 1 at a time 239 | psi_1[s, j] = psi_1[s, j] :- (rk.residuals:*psi_k) 240 | } 241 | res.Vpop_OWN = multe_helper_Vhat(psi_ownk, C) 242 | res.Vdiff_OWN = multe_helper_Vhat(psi_beta :- psi_ownk, C) 243 | res.Vpop_EW = multe_helper_Vhat(psi_rk, C) 244 | res.Vdiff_EW = multe_helper_Vhat(psi_1, C) 245 | res.Vo_EW = multe_helper_Vhat(psi_ri, C) 246 | 247 | res.seP[., 2] = sqrt(diagonal(res.Vpop_OWN)) 248 | res.seB[., 2] = sqrt(diagonal(res.Vdiff_OWN)) 249 | res.seP[., 4] = sqrt(diagonal(res.Vpop_EW)) 250 | res.seB[., 4] = sqrt(diagonal(res.Vdiff_EW)) 251 | res.seO[., 4] = sqrt(diagonal(res.Vo_EW)) 252 | 253 | // TODO: If there are collinear variables within treatment (i.e. in 254 | // the interacted model) then their coefficient are not identified and 255 | // Stata loops forever. However, the choice probabilities ARE idnetified 256 | // (i.e. the likelihood has a maximum just not a unique maximizer). In 257 | // this case we check convergence in the choice probabilities. Current 258 | // implementation is ad hoc and neds work. 259 | 260 | this.debug("generalized overlap weights") 261 | mlfit = J(1, this.info.Tk, "") 262 | for(j = 1; j <= this.info.Tk; j++) { 263 | mlfit[j] = st_tempname() 264 | } 265 | regcall = sprintf("qui mlogit %s %s#c.(%s) %s", 266 | this.info.T, 267 | this.info.cons2, 268 | this.info.zindep, 269 | regopts + " base(1) " + (ir_diff? " iter(100) ": "")) 270 | stata(regcall) 271 | stata(sprintf("predict %s, pr", invtokens(mlfit))) 272 | pis = st_data(., mlfit, this.info.touse) 273 | regtol = max(pis) * this.info.zerotol 274 | _edittozero(pis, regtol) 275 | if ( ir_diff ) { 276 | ir_start = st_tempname() 277 | st_matrix(ir_start, st_matrix("e(b)")) 278 | stata(sprintf(regcall + " from(%s, skip)", ir_start)) 279 | stata(sprintf("drop %s", invtokens(mlfit))) 280 | stata(sprintf("predict %s, pr", invtokens(mlfit))) 281 | mtol = max(reldif(pis, st_data(., mlfit, this.info.touse))) 282 | if ( mtol > regtol ) { 283 | pis = J(rows(pis), cols(pis), .) 284 | errprintf("unable to compute generalized overlap weights\n") 285 | } 286 | else { 287 | pis = st_data(., mlfit, this.info.touse) 288 | ir_diff = 0 289 | } 290 | _edittozerotol(pis, regtol) 291 | } 292 | 293 | // xx TODO: check whether omitting is necessary; invsym is a generalized inverse 294 | // stata("_ms_omit_info e(b)") 295 | // ir_omit = st_matrix("r(omit)") 296 | // ir_gam = st_matrix("e(b)") 297 | // ir_nam = st_matrixcolstripe("e(b)")[., 2] 298 | // ir_omit = colshape(ir_omit, this.info.Zk)' 299 | // ir_nam = colshape(ustrregexra(ir_nam, ".+#", ""), this.info.Zk)' 300 | // for(j = 2; j <= this.info.Tk; j++) { 301 | // assert(all(ir_nam[., 2] :== ir_nam[., j])) 302 | // assert(all(ir_omit[., 2] :== ir_omit[., j])) 303 | // } 304 | // ir_gam = (colshape(ir_gam, this.info.Zk) :- ir_gam[1::this.info.Zk])' 305 | // ir_omit = ir_omit[., 2]' 306 | // ir_nam = ir_nam[., 2]' 307 | // Zm = select(st_data(., invtokens(ir_nam), this.info.touse), !ir_omit) 308 | // Zm = select(Zm, !ir_omit) 309 | // this.info.Zk = cols(Zm) 310 | 311 | Sc = J(res.N, (this.info.Tk-1)*this.info.Zk, .) 312 | He = J((this.info.Tk-1) * this.info.Zk, (this.info.Tk-1) * this.info.Zk, .) 313 | for (i = 1; i < this.info.Tk; i++) { 314 | seli = (this.info.Zk * (i-1) + 1)..(this.info.Zk*i) 315 | Sc[., seli] = ws :* (Xf[.,i] :- pis[., i+1]) :* Zm 316 | for (j = 1; j < this.info.Tk; j++) { 317 | selj = (this.info.Zk * (j-1) + 1)..(this.info.Zk*j) 318 | He[seli', selj] = quadcross(Zm, ws :* pis[., i+1] :* ((i == j) :- pis[, j+1]), Zm) 319 | } 320 | } 321 | 322 | this.debug("Wald and LM tests (assumes first column is intercept)") 323 | idx1 = (0..(this.info.Tk-2)):*this.info.Zk:+1 324 | idx1n = multe_helper_antiselect(1..((this.info.Tk-1) * this.info.Zk), idx1) 325 | He1112 = invsym(He[idx1, idx1]) * He[idx1, idx1n] 326 | Vu = multe_helper_Vhat(Sc[., idx1n] :- Sc[., idx1] * He1112, C) 327 | // th1 = vec(ir_gam[selectindex(!ir_omit), 2..this.info.Tk]) 328 | th1 = st_matrix("e(b)")[(this.info.Zk+1)..(this.info.Zk*this.info.Tk)] 329 | th = (He[idx1n, idx1n] :- He[idx1n, idx1] * He1112) * th1[idx1n]' 330 | 331 | // LM, calculate weighted colMeans(Xf) 332 | pis0 = mean(Xf, ws) 333 | Scr = J(rows(Zm), (this.info.Tk-1)*this.info.Zk, .) 334 | Her = J((this.info.Tk-1) * this.info.Zk, (this.info.Tk-1) * this.info.Zk, .) 335 | for (i = 1; i < this.info.Tk; i++) { 336 | seli = (this.info.Zk * (i-1) + 1)..(this.info.Zk*i) 337 | Scr[., seli] = ws :* (Xf[.,i] :- pis0[., i]) :* Zm 338 | for (j = 1; j < this.info.Tk; j++) { 339 | selj = (this.info.Zk * (j-1) + 1)..(this.info.Zk*j) 340 | Her[seli', selj] = quadcross(Zm, ws :* pis0[., i] :* ((i == j) :- pis0[, j]), Zm) 341 | } 342 | } 343 | 344 | Her1112 = invsym(Her[idx1, idx1]) * Her[idx1, idx1n] 345 | Vr = multe_helper_Vhat(Scr[., idx1n] :- Scr[., idx1] * Her1112, C) 346 | LM = multe_helper_quadform(Vr, quadcolsum(Scr[., idx1n])') 347 | Wa = ir_diff? (., .): multe_helper_quadform(Vu, th) 348 | res.LM.stat = LM[1] 349 | res.Wa.stat = Wa[1] 350 | res.LM.df = LM[2] 351 | res.Wa.df = Wa[2] 352 | res.LM.pval = chi2tail(LM[2], LM[1]) 353 | res.Wa.pval = chi2tail(Wa[2], Wa[1]) 354 | 355 | this.debug("generalized overlap weights + standard errors") 356 | pis0 = mean(X0, ws), pis0 357 | if ( !this.info.cw_uniform ) { 358 | vpi = pis0 :* (1 :- pis0) 359 | } 360 | else { 361 | vpi = 1 362 | } 363 | 364 | // Note ipi is always multiplied by lam; if ipi is set to 1/lam then it 365 | // will give a missing when lam is 0, but missing times 0 is missing and 366 | // we want it to be 0, so we set missing ipi values to 0. 367 | lam = editmissing(1 :/ quadrowsum(vpi :/ pis), 0) 368 | _edittozerotol(lam, max(lam) * this.info.zerotol) 369 | ipi = editmissing(1:/pis, 0) 370 | cw = lam :/ quadrowsum((X0, Xf) :* pis) 371 | 372 | this.debug("efficient common weights") 373 | if ( quadsum(lam) > 0 ) { 374 | ro = multe_helper_olswr(Y, (J(rows(Xf), 1, 1), Xf), cw :* ws) 375 | res.estA[., 5] = ro.coefficients[2::this.info.Tk] 376 | psi_or = (ws :* lam :/ quadsum(ws :* lam)) :* ((Xf :* ipi[., 2..this.info.Tk]) :- (X0 :* ipi[, 1])) 377 | res.Vo_CW = multe_helper_Vhat(ri.residuals :* psi_or, C) 378 | res.seO[., 5] = sqrt(diagonal(res.Vo_CW)) 379 | 380 | // Final step: calculate se_po for overlap weights. model.matrix 381 | // rather than ml$residuals to account for rounding 382 | xf1 = lam :* (vpi[., 2..this.info.Tk] :* ipi[., 2..this.info.Tk]) :* X0 383 | M0 = J(this.info.Zk, this.info.Tk-1, .) 384 | for (i = 1; i < this.info.Tk; i++) { 385 | M0[., i] = quadcolsum((xf1[., i] :* ws :* cw :* ro.residuals) :* Zm)' 386 | } 387 | M = J(this.info.Zk * (this.info.Tk-1), this.info.Tk-1, .) 388 | si = J(1, (this.info.Tk-2), 0), 1, J(1, (this.info.Tk-2), 0) 389 | xf1l = lam :* (vpi[., 2..this.info.Tk] :* ipi[., 2..this.info.Tk]) 390 | for (j = 1; j < this.info.Tk; j++) { 391 | xf1r = J(rows(Xf), 1, si[(this.info.Tk-j)..(2*this.info.Tk-2-j)]) 392 | xf1 = (xf1l :- xf1r) :* Xf[., j] 393 | for (i = 1; i < this.info.Tk; i++) { 394 | seli = (this.info.Zk * (i-1) + 1)::(this.info.Zk*i) 395 | M[seli, j] = quadcolsum((xf1[., i] :* ws :* cw :* ro.residuals) :* Zm)' 396 | } 397 | } 398 | 399 | a = (Sc * invsym(He)) * (M :- vec(M0)) :/ quadsum(ws:*lam) 400 | res.Vpop_CW = multe_helper_Vhat(ro.residuals :* psi_or :+ a, C) 401 | res.Vdiff_CW = multe_helper_Vhat(psi_beta :- ro.residuals :* psi_or :- a, C) 402 | res.seP[., 5] = sqrt(diagonal(res.Vpop_CW)) 403 | res.seB[., 5] = sqrt(diagonal(res.Vdiff_CW)) 404 | } 405 | else { 406 | errprintf("Sample for efficient common weights is empty.\n") 407 | } 408 | 409 | res.estB[., 2..5] = res.estA[., 1] :- res.estA[., 2..5] 410 | res.estB = res.estB[., 2..5] 411 | res.seB = res.seB[., 2..5] 412 | 413 | return(res) 414 | } 415 | 416 | void function MulTE::debug(string scalar msg) 417 | { 418 | if ( this.info.debug ) printf("debug %g: %s\n", this.info.debug, msg) 419 | else return 420 | (void) this.info.debug++ 421 | } 422 | 423 | void function multe_helper_post(string scalar b, string scalar V, struct MulTE_Return scalar res) 424 | { 425 | real scalar uselevels 426 | real matrix best, sest 427 | string vector rownames, eqnames 428 | 429 | uselevels = any(st_vartype(res.Tvar) :== ("byte", "int", "long")) 430 | // uselevels = uselevels | all(floor(Tvalues) :== Tvalues) 431 | rownames = colshape(res.Tlevels, 1) 432 | if ( uselevels ) { 433 | rownames[1] = rownames[1] + "b" 434 | rownames = colshape(rownames :+ "." :+ res.Tvar, 1) 435 | } 436 | else if ( !strpos(st_vartype(res.Tvar), "str") ) { 437 | printf("{bf:note:} table coefficients correspond to treatment values\n") 438 | } 439 | best = 0, rowshape(st_matrix(b), 1) 440 | eqnames = J(1, res.Tk, "")' 441 | sest = J(res.Tk, res.Tk, 0) 442 | sest[2::res.Tk, 2..res.Tk] = st_matrix(V) 443 | 444 | st_matrix(b, editmissing(rowshape(best', 1), 0)) 445 | st_matrixcolstripe(b, (eqnames, rownames)) 446 | st_matrixrowstripe(b, ("", res.Yvar)) 447 | 448 | st_matrix(V, sest) 449 | st_matrixcolstripe(V, (eqnames, rownames)) 450 | st_matrixrowstripe(V, (eqnames, rownames)) 451 | } 452 | end 453 | -------------------------------------------------------------------------------- /src/mata/multe_helpers.mata: -------------------------------------------------------------------------------- 1 | cap mata mata drop multe_helper_ols() 2 | cap mata mata drop multe_helper_olsw() 3 | cap mata mata drop multe_helper_olsr() 4 | cap mata mata drop multe_helper_olswr() 5 | cap mata mata drop multe_helper_olse() 6 | cap mata mata drop multe_helper_olswe() 7 | cap mata mata drop multe_helper_results() 8 | cap mata mata drop multe_helper_antiselect() 9 | cap mata mata drop multe_helper_licols() 10 | cap mata mata drop multe_helper_Vhat() 11 | cap mata mata drop multe_helper_sehat() 12 | cap mata mata drop multe_helper_quadform() 13 | 14 | // without clustering multe in R doesn't do the small-sample adjustment 15 | mata: 16 | real matrix function multe_helper_Vhat(real matrix psi, real colvector cluster) 17 | { 18 | real scalar G 19 | real matrix _psi, psisum 20 | _psi = editmissing(psi, 0) 21 | if ( missing(cluster) ) { 22 | // G = rows(_psi) 23 | // return((G / (G - 1)) :* quadcross(_psi, _psi)) 24 | return(quadcross(_psi, _psi)) 25 | } 26 | else { 27 | psisum = sort((cluster, _psi), 1) 28 | psisum = panelsum(psisum[., 2..cols(psisum)], panelsetup(psisum, 1)) 29 | G = rows(psisum) 30 | return((G / (G - 1)) :* quadcross(psisum, psisum)) 31 | } 32 | } 33 | 34 | real vector function multe_helper_sehat(real matrix psi, real colvector cluster) 35 | { 36 | return(sqrt(diagonal(multe_helper_Vhat(psi, cluster)))) 37 | } 38 | 39 | struct multe_helper_results { 40 | real matrix coefficients 41 | real matrix residuals 42 | real matrix vcov 43 | } 44 | 45 | // invsym _should_ work under collinearity; can't recall the example where it failed 46 | real matrix function multe_helper_ols(real matrix Y, real matrix X) 47 | { 48 | return(invsym(quadcross(X, X), 1..cols(X)) * quadcross(X, Y)) 49 | } 50 | 51 | // return(qrsolve(quadcross(X, W, X), quadcross(X, W, Y))) 52 | real matrix function multe_helper_olsw(real matrix Y, real matrix X, real matrix W) 53 | { 54 | return(invsym(quadcross(X, W, X), 1..cols(X)) * quadcross(X, W, Y)) 55 | } 56 | 57 | struct multe_helper_results scalar function multe_helper_olsr(real matrix Y, real matrix X) 58 | { 59 | struct multe_helper_results scalar results 60 | results.coefficients = multe_helper_ols(Y, X) 61 | results.residuals = Y - X * results.coefficients 62 | return(results) 63 | } 64 | 65 | struct multe_helper_results scalar function multe_helper_olswr(real matrix Y, real matrix X, real matrix W) 66 | { 67 | struct multe_helper_results scalar results 68 | results.coefficients = multe_helper_olsw(Y, X, W) 69 | results.residuals = Y - X * results.coefficients 70 | return(results) 71 | } 72 | 73 | struct multe_helper_results scalar function multe_helper_olse(real matrix Y, real matrix X) 74 | { 75 | struct multe_helper_results scalar results 76 | real matrix XX, XDX 77 | results = multe_helper_olsr(Y, X) 78 | XX = invsym(quadcross(X, X), 1..cols(X)) 79 | XDX = quadcross(X, results.residuals:^2, X) 80 | results.vcov = XX * XDX * XX 81 | return(results) 82 | } 83 | 84 | struct multe_helper_results scalar function multe_helper_olswe(real matrix Y, real matrix X, real matrix W) 85 | { 86 | struct multe_helper_results scalar results 87 | real matrix XX, XDX 88 | results = multe_helper_olswr(Y, X, W) 89 | XX = invsym(quadcross(X, W, X), 1..cols(X)) 90 | XDX = quadcross(X, (W :* results.residuals):^2, X) 91 | results.vcov = XX * XDX * XX 92 | return(results) 93 | } 94 | 95 | real matrix function multe_helper_antiselect(real matrix x, real vector ix) 96 | { 97 | real vector sel 98 | if ( length(ix) == 1 ) { 99 | if ( min((rows(x), cols(x))) > 1 ) { 100 | errprintf("multe_helper_antiselect(): ambiguous scalar anti-selection of a matrix") 101 | _error(198) 102 | } 103 | sel = rows(x) > cols(x)? J(rows(x), 1, 1): J(1, cols(x), 1) 104 | } 105 | else { 106 | sel = rows(ix) > cols(ix)? J(rows(x), 1, 1): J(1, cols(x), 1) 107 | } 108 | sel[ix] = J(rows(ix) > cols(ix)? length(ix): 1, rows(ix) > cols(ix)? 1: length(ix), 0) 109 | return(select(x, sel)) 110 | } 111 | 112 | real rowvector function multe_helper_licols(real matrix X) 113 | { 114 | if ( (cols(X) == 0) | (rows(X) == 0) ) return(J(1, 0, 0)) 115 | return(rowshape(diagonal(invsym(quadcross(X, X), 1..cols(X))) :!= 0, 1)) 116 | } 117 | 118 | real vector function multe_helper_quadform(real matrix A, real vector b) 119 | { 120 | real scalar rank, quadf 121 | real matrix Ainv 122 | if ( !issymmetric(A) ) { 123 | errprintf("multe_helper_quadform(): matrix must be symmetric") 124 | _error(198) 125 | } 126 | Ainv = invsym(A) 127 | rank = sum(diagonal(Ainv) :!= 0) 128 | if ( rows(b) > cols(b) ) { 129 | quadf = b' * Ainv * b 130 | } 131 | else { 132 | quadf = b * Ainv * b' 133 | } 134 | return((quadf, rank)) 135 | } 136 | end 137 | -------------------------------------------------------------------------------- /stata.toc: -------------------------------------------------------------------------------- 1 | v 1.1.0 2 | p 'MULTE': Multiple Treatment Effects Regression 3 | -------------------------------------------------------------------------------- /test/example_fryer_levitt.dta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gphk-metrics/stata-multe/5381d57ff91cafb7237b8cf94714848b79ec39da/test/example_fryer_levitt.dta -------------------------------------------------------------------------------- /test/example_star.dta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gphk-metrics/stata-multe/5381d57ff91cafb7237b8cf94714848b79ec39da/test/example_star.dta -------------------------------------------------------------------------------- /test/test.R: -------------------------------------------------------------------------------- 1 | library(readstata13) 2 | library(multe) 3 | 4 | data_dir <- paste0("~/Dropbox/GPH_ExaminerDesign", "/Applications/STAR/Data/STARgk_Lambdas.dta") 5 | dt <- readstata13::read.dta13(data_dir, generate.factors=TRUE) 6 | dt$treatment <- "regular" 7 | dt$treatment[dt$gksmall==1] <- "small" 8 | dt$treatment[dt$gkaide==1] <- "aide" 9 | dt$treatment <- factor(dt$treatment, levels=c("regular", "small", "aide")) 10 | dt <- dt[, c("gkaggregate2", "treatment", "gkschid", "gktchid")] 11 | names(dt) <- c("score", "treatment", "school", "teacher") 12 | dt$school <- as.factor(dt$school) 13 | dt$teacher <- as.factor(dt$teacher) 14 | 15 | Wm <- outer(dt$school, levels(dt$school), `==`) + 0 16 | Y <- dt$score 17 | X <- dt$treatment 18 | rg <- lm(Y ~ X + Wm) 19 | m <- multe(rg, "X", dt$school) 20 | print(m) 21 | -------------------------------------------------------------------------------- /test/test.do: -------------------------------------------------------------------------------- 1 | version 14.1 2 | set seed 5371 3 | set linesize 112 4 | 5 | cap which multe 6 | if _rc { 7 | disp as err "Please install multe" 8 | exit 111 9 | } 10 | 11 | capture program drop main 12 | program main 13 | qui do test/test_unit.do 14 | qui do test/test_replicate.do 15 | qui do test/test_weights.do 16 | 17 | multe_unit_tests 18 | multe_replicate_tests 19 | multe_weight_tests 20 | multe_weight_startest 21 | 22 | qui do doc/examples.do 23 | disp "(multe test success): multe ran README examples" 24 | end 25 | 26 | main 27 | -------------------------------------------------------------------------------- /test/test_replicate.do: -------------------------------------------------------------------------------- 1 | global star "~/Dropbox/GPH_ExaminerDesign/Applications/STAR" 2 | global data "${star}/Data" 3 | * global star "/Users/jchang42/Dropbox (Brown)/GPH_ExaminerDesign/Applications/STAR" 4 | * global data "${star}/Data" 5 | 6 | capture program drop multe_replicate_tests 7 | program multe_replicate_tests 8 | syntax, [Verbose] 9 | if "`verbose'" != "" local noisily noisily 10 | cap findfile example_star.dta 11 | if ( _rc ) { 12 | qui multe_replicate_load 13 | } 14 | else { 15 | use `"`r(fn) '"', clear 16 | } 17 | cap `noisily' multe score, treat(treatment) stratum(school) cluster(school) 18 | if ( _rc != 0 ) { 19 | disp "(multe test fail): multe run on STAR data failed with _rc = `rc1'" 20 | exit 9 21 | } 22 | else { 23 | disp "(multe test success): multe ran successfully on STAR data" 24 | } 25 | * output, treatment(treatment) matasave(multe_results) /// 26 | * outpath("${star}/Output/tables/test") // pick your outpath 27 | * mata mata desc 28 | end 29 | 30 | capture program drop multe_replicate_load 31 | program multe_replicate_load 32 | use `"${data}/STARgk_Lambdas.dta"', clear 33 | 34 | gen treatment = "regular" 35 | replace treatment = "small" if gksmall == 1 36 | replace treatment = "aide" if gkaide == 1 37 | 38 | keep gkaggregate2 treatment gkschid gktchid 39 | rename (gkaggregate2 treatment gkschid gktchid) (score treatmentlab schoollab teacherlab) 40 | gen treatment = 1 41 | replace treatment = 2 if treatmentlab == "small" 42 | replace treatment = 3 if treatmentlab == "aide" 43 | label define treatmentlab 1 "regular" 2 "small" 3 "aide" 44 | label values treatment treatmentlab 45 | multe_test_factor school = schoollab, replace 46 | multe_test_factor teacher = teacherlab, replace 47 | end 48 | 49 | capture program drop multe_test_factor 50 | program multe_test_factor 51 | syntax anything(equalok), [replace] 52 | gettoken gen varlist: anything, p(=) 53 | gettoken _ varlist: varlist 54 | confirm var `varlist' 55 | foreach var of varlist `varlist' { 56 | gettoken outvar gen: gen 57 | local i = 0 58 | local lbldef 59 | glevelsof `var', silent loc(`var') 60 | foreach level of local `var' { 61 | mata st_local("lbldef`++i'", `"`i' "`level'""') 62 | mata st_local("lbldef", st_local("lbldef") + st_local("lbldef`i'")) 63 | } 64 | if "`replace'" != "" cap label drop `var' 65 | label define `var' `lbldef' 66 | gegen `outvar' = group(`var'), `replace' 67 | label values `outvar' `var' 68 | } 69 | end 70 | -------------------------------------------------------------------------------- /test/test_speed.do: -------------------------------------------------------------------------------- 1 | local nobs 50000 2 | local ktreat 10 3 | clear 4 | mata mata clear 5 | qui do src/ado/multe.ado 6 | qui do src/mata/multe_helpers.mata 7 | qui do src/mata/multe.mata 8 | set seed 1729 9 | set obs `nobs' 10 | gen T = ceil(runiform() * `ktreat') 11 | gen W = mod(_n, 100) 12 | gen Y = T + runiform() 13 | multe Y, treat(T) strat(W) 14 | -------------------------------------------------------------------------------- /test/test_unit.do: -------------------------------------------------------------------------------- 1 | capture program drop multe_unit_tests 2 | program multe_unit_tests 3 | syntax, [Verbose] 4 | multe_load_test_data 5 | multe_unit_fail 6 | multe_unit_test_Ytype, `verbose' 7 | multe_unit_test_Ttype, `verbose' 8 | multe_unit_test_Wtype, `verbose' 9 | end 10 | 11 | capture program drop multe_unit_fail 12 | program multe_unit_fail 13 | * missing control|strat 14 | cap multe Ydouble, treat(Tbyte) 15 | local rc = ( _rc == 198 ) 16 | * missing treatment 17 | cap multe Ydouble C* 18 | local rc = `rc' & ( _rc == 198 ) 19 | if ( `rc' == 1 ) { 20 | disp "(multe test success): fail checks present" 21 | } 22 | else { 23 | disp "(multe test fail): fail checks missing; check stop logic" 24 | exit 9 25 | } 26 | end 27 | 28 | capture program drop multe_load_test_data 29 | program multe_load_test_data 30 | syntax, [nobs(int 1000) ktreat(int 5)] 31 | clear 32 | qui set obs `nobs' 33 | gen byte Tbyte = ceil(runiform() * `ktreat') 34 | gen int Tint = Tbyte * 1234 35 | gen long Tlong = Tbyte * 123456 36 | gen float Tfloat = (Tbyte - 2.5) * `c(pi)' 37 | gen double Tdouble = (Tbyte - 2.5) * `c(pi)' / 123456 38 | gen str4 Tstr = "str" + string(Tbyte) 39 | gen byte Wbyte = mod(_n, 10) 40 | gen int Wint = Wbyte * 1234 41 | gen long Wlong = Wbyte * 123456 42 | gen float Wfloat = (Wbyte - 2.5) * `c(pi)' 43 | gen double Wdouble = (Wbyte - 2.5) * `c(pi)' / 123456 44 | gen str4 Wstr = "str" + string(Wbyte) 45 | gen byte Cbyte = mod(_n, 10) 46 | gen int Cint = Wbyte * 1234 47 | gen long Clong = Wbyte * 123456 48 | gen float Cfloat = (Wbyte - 2.5) * `c(pi)' 49 | gen double Cdouble = (Wbyte - 2.5) * `c(pi)' / 123456 50 | gen byte Ybyte = Tbyte - Wbyte + Cdouble + runiform() 51 | gen int Yint = Tbyte - Wbyte + Cdouble + runiform() 52 | gen long Ylong = Tbyte - Wbyte + Cdouble + runiform() 53 | gen float Yfloat = Tbyte - Wbyte + Cdouble + runiform() 54 | gen double Ydouble = Tbyte - Wbyte + Cdouble + runiform() 55 | gen str4 Ystr = "str" + string(Tbyte - Wbyte + runiform()) 56 | end 57 | 58 | capture program drop multe_unit_test_Ytype 59 | program multe_unit_test_Ytype 60 | syntax, [Reload Verbose *] 61 | 62 | if "`reload'" != "" multe_load_test_data, `options' 63 | if ( "`verbose'" != "" ) { 64 | foreach Y in Ybyte Yint Ylong Yfloat Ydouble Ystr { 65 | tab `Y' 66 | } 67 | } 68 | 69 | tempname Yexpected Yresult 70 | local Ypass Ybyte Yint Ylong Yfloat Ydouble 71 | local Yfail Ystr 72 | foreach Y in `Ypass' `Yfail' { 73 | cap multe `Y', treat(Tbyte) strat(Wbyte) 74 | local rc1 = _rc 75 | cap multe `Y' C*, treat(Tbyte) strat(Wbyte) 76 | local rc2 = _rc 77 | cap multe `Y' Cdouble, treat(Tbyte) 78 | local rc3 = _rc 79 | if ( `:list Y in Ypass' ) { 80 | if ( (`rc1' != 0) | (`rc2' != 0) | (`rc3' != 0) ) { 81 | disp "(multe test fail): depvar type `:subinstr local Y "Y" ""' failed with _rc = `rc1'" 82 | exit 9 83 | } 84 | else { 85 | disp "(multe test success): depvar type `:subinstr local Y "Y" ""' gave no errors" 86 | } 87 | } 88 | if ( `:list Y in Yfail' ) { 89 | if ( (`rc1' != 109) | (`rc2' != 109) | (`rc3' != 109) ) { 90 | disp "(multe test fail): depvar type `:subinstr local Y "Y" ""' did not fail with _rc = 109 as expected (_rc = `rc1')" 91 | exit 9 92 | } 93 | else { 94 | disp "(multe test success): depvar type `:subinstr local Y "Y" ""' failed with _rc = 109 as expected" 95 | } 96 | } 97 | } 98 | end 99 | 100 | * Double-check: Allow multe to take any multi-valued treatment T and 101 | * automatically take the lowest value as the control 102 | capture program drop multe_unit_test_Ttype 103 | program multe_unit_test_Ttype 104 | syntax, [Reload Verbose *] 105 | 106 | if "`reload'" != "" multe_load_test_data, `options' 107 | if ( "`verbose'" != "" ) { 108 | foreach T in Tbyte Tint Tlong Tfloat Tdouble Tstr { 109 | tab `T' 110 | } 111 | } 112 | 113 | tempname Texpected Tresult 114 | local Tpass Tbyte Tint Tlong Tfloat Tdouble Tstr 115 | foreach T in `Tpass' `Tfail' { 116 | cap multe Ydouble Cdouble, treat(`T') strat(Wbyte) 117 | local rc1 = _rc 118 | if ( `:list T in Tpass' ) { 119 | if strpos("`:type `T''", "str") { 120 | cap mata assert(all(sort(uniqrows(st_sdata(., "`T'")), 1) :== `e(mata)'.Tlevels')) 121 | } 122 | else { 123 | cap mata assert(all(reldif(sort(uniqrows(st_data(., "`T'")), 1), strtoreal(`e(mata)'.Tlevels')) :< epsilon(1)^(0.75))) 124 | } 125 | local rc2 = _rc 126 | if ( `rc1' != 0 ) { 127 | disp "(multe test fail): treatment type `:subinstr local T "T" ""' failed with _rc = `rc1'" 128 | exit 9 129 | } 130 | else if ( `rc2' != 0 ) { 131 | disp "(multe test fail): treatment type `:subinstr local T "T" ""' levels computed internally do not match the data" 132 | exit 9 133 | } 134 | else { 135 | disp "(multe test success): treatment type `:subinstr local T "T" ""' gave no errors" 136 | } 137 | } 138 | if ( `:list T in Tfail' ) { 139 | if ( `rc1' != 109 ) { 140 | disp "(multe test fail): treatment type `:subinstr local T "T" ""' did not fail with _rc = 109 as expected (_rc = `rc1')" 141 | exit 9 142 | } 143 | else { 144 | disp "(multe test success): treatment type `:subinstr local T "T" ""' failed with _rc = 109 as expected" 145 | } 146 | } 147 | } 148 | end 149 | 150 | * Double-check: Allow multe to take any multi-valued control W 151 | capture program drop multe_unit_test_Wtype 152 | program multe_unit_test_Wtype 153 | syntax, [Verbose *] 154 | 155 | if "`reload'" != "" multe_load_test_data, `options' 156 | if ( "`verbose'" != "" ) { 157 | foreach W in Wbyte Wint Wlong Wfloat Wdouble Wstr { 158 | tab `W' 159 | } 160 | } 161 | 162 | tempname Wexpected Wresult 163 | local Wpass Wbyte Wint Wlong Wfloat Wdouble Wstr 164 | foreach W in `Wpass' { 165 | cap multe Ydouble Cdouble, treat(Tbyte) strat(`W') 166 | local rc1 = _rc 167 | if ( `rc1' != 0 ) { 168 | disp "(multe test fail): control type `:subinstr local W "W" ""' failed with _rc = `rc1'" 169 | exit 9 170 | } 171 | else { 172 | disp "(multe test success): control type `:subinstr local W "W" ""' gave no errors" 173 | } 174 | } 175 | end 176 | -------------------------------------------------------------------------------- /test/test_weights.do: -------------------------------------------------------------------------------- 1 | capture program drop multe_weight_tests 2 | program multe_weight_tests 3 | multe_load_test_data 4 | gen `c(obs_t)' G = _n 5 | gen `c(obs_t)' _expand = ceil(runiform() * _N/50) 6 | gen double Y = Ydouble + rnormal() * _N / 50 7 | qui sum _expand 8 | local ratio = sqrt(r(sum) / _N) 9 | 10 | * -------------------- 11 | * Internal Consistency 12 | * -------------------- 13 | 14 | qui multe Y Cdouble , treat(Tbyte) strat(Wbyte) mata(now) 15 | qui multe Y Cdouble [aw=_expand], treat(Tbyte) strat(Wbyte) mata(aw) 16 | qui multe Y Cdouble [pw=_expand], treat(Tbyte) strat(Wbyte) mata(pw) 17 | 18 | mata assert(max(reldif(aw.full.estA, now.full.estA)) > (epsilon(1))^(1/4)) 19 | mata assert(max(reldif(aw.full.estB, now.full.estB)) > (epsilon(1))^(1/4)) 20 | mata assert(max(reldif(aw.full.seP, now.full.seP)) > (epsilon(1))^(1/4)) 21 | mata assert(max(reldif(aw.full.seB, now.full.seB)) > (epsilon(1))^(1/4)) 22 | mata assert(max(reldif(aw.full.seO, now.full.seO)) > (epsilon(1))^(1/4)) 23 | 24 | mata assert(max(reldif(pw.full.estA, now.full.estA)) > (epsilon(1))^(1/4)) 25 | mata assert(max(reldif(pw.full.estB, now.full.estB)) > (epsilon(1))^(1/4)) 26 | mata assert(max(reldif(pw.full.seP, now.full.seP)) > (epsilon(1))^(1/4)) 27 | mata assert(max(reldif(pw.full.seB, now.full.seB)) > (epsilon(1))^(1/4)) 28 | mata assert(max(reldif(pw.full.seO, now.full.seO)) > (epsilon(1))^(1/4)) 29 | 30 | mata assert(max(reldif(pw.full.estA, aw.full.estA)) < (epsilon(1))^(3/4)) 31 | mata assert(max(reldif(pw.full.estB, aw.full.estB)) < (epsilon(1))^(3/4)) 32 | mata assert(max(reldif(pw.full.seP, aw.full.seP)) < (epsilon(1))^(3/4)) 33 | mata assert(max(reldif(pw.full.seB, aw.full.seB)) < (epsilon(1))^(3/4)) 34 | mata assert(max(reldif(pw.full.seO, aw.full.seO)) < (epsilon(1))^(3/4)) 35 | 36 | disp "(multe test success): multe basic weights consistency on simulated data" 37 | 38 | * NB: Fundamentally aw and fw have different variances. The first 39 | * gives V((X' W X)^-1 X' W e) = (X' W X)^-1 X' W V(e) W X (X' W X)^-1 40 | * and the second gives (X' W X)^-1 X' (W V(e)) X (X' W X)^-1 41 | 42 | * ---------------------- 43 | * vs teffects (ate only) 44 | * ---------------------- 45 | 46 | qui tab Wbyte, gen(_W) 47 | local k = r(r) + 1 48 | 49 | qui teffects ipw (Y) (Tbyte b0.Wbyte) 50 | matrix table = r(table) 51 | matrix table = table[1..2,"ATE:"] 52 | mata table = st_matrix("table") 53 | mata assert(max(reldif(now.full.estA[.,3], table[1,.]')) < (epsilon(1)^(3/4))) 54 | mata assert(max(reldif(now.full.seP[.,3], table[2,.]')) < (epsilon(1)^(3/4))) 55 | 56 | qui teffects ipw (Y) (Tbyte b0.Wbyte) [pw=_expand] 57 | matrix table = r(table) 58 | matrix table = table[1..2,"ATE:"] 59 | mata table = st_matrix("table") 60 | mata assert(max(reldif(pw.full.estA[.,3], table[1,.]')) < (epsilon(1)^(3/4))) 61 | mata assert(max(reldif(pw.full.seP[.,3], table[2,.]')) < (epsilon(1)^(3/4))) 62 | 63 | cap drop _aw 64 | qui sum _expand 65 | gen double _aw = _N * _expand / r(sum) 66 | qui teffects ipw (Y) (Tbyte b0.Wbyte) [pw=_aw] 67 | matrix table = r(table) 68 | matrix table = table[1..2,"ATE:"] 69 | mata table = st_matrix("table") 70 | mata assert(max(reldif(aw.full.estA[.,3], table[1,.]')) < (epsilon(1)^(3/4))) 71 | mata assert(max(reldif(aw.full.seP[.,3], table[2,.]')) < (epsilon(1)^(3/4))) 72 | 73 | disp "(multe test success): multe ATE consistency on simulated data" 74 | 75 | * ---------------------- 76 | * vs regress (oaat only) 77 | * ---------------------- 78 | 79 | mata oaat = J(4, 2, .) 80 | forvalues i = 2 / 5 { 81 | qui reg Y i`i'.Tbyte _W* if inlist(Tbyte, 1, `i'), noc r 82 | mata oaat[`i'-1, .] = st_matrix("r(table)")[1::2, 1]' 83 | } 84 | mata info = panelsetup(sort(st_data(., "Tbyte"), 1), 1) 85 | mata nj = info[2::rows(info), 2] :- info[2::rows(info), 1] :+ 1 :+ info[1, 2] 86 | mata assert(max(reldif(now.full.estA[., 4], oaat[., 1])) < (epsilon(1)^(3/4))) 87 | mata assert(max(reldif((nj :- `k') :/ nj, (now.full.seP[., 4] :/ oaat[., 2]):^2)) < (epsilon(1)^(3/4))) 88 | 89 | mata oaat = J(4, 2, .) 90 | forvalues i = 2 / 5 { 91 | qui reg Y i`i'.Tbyte _W* if inlist(Tbyte, 1, `i') [pw=_expand], noc r 92 | mata oaat[`i'-1, .] = st_matrix("r(table)")[1::2, 1]' 93 | } 94 | mata info = panelsetup(sort(st_data(., "Tbyte"), 1), 1) 95 | mata nj = info[2::rows(info), 2] :- info[2::rows(info), 1] :+ 1 :+ info[1, 2] 96 | mata assert(max(reldif(pw.full.estA[., 4], oaat[., 1])) < (epsilon(1)^(3/4))) 97 | mata assert(max(reldif((nj :- `k') :/ nj, (pw.full.seP[., 4] :/ oaat[., 2]):^2)) < (epsilon(1)^(3/4))) 98 | 99 | mata oaat = J(4, 2, .) 100 | forvalues i = 2 / 5 { 101 | qui reg Y i`i'.Tbyte _W* if inlist(Tbyte, 1, `i') [aw=_expand], noc r 102 | mata oaat[`i'-1, .] = st_matrix("r(table)")[1::2, 1]' 103 | } 104 | mata info = panelsetup(sort(st_data(., "Tbyte"), 1), 1) 105 | mata nj = info[2::rows(info), 2] :- info[2::rows(info), 1] :+ 1 :+ info[1, 2] 106 | mata assert(max(reldif(aw.full.estA[., 4], oaat[., 1])) < (epsilon(1)^(3/4))) 107 | mata assert(max(reldif((nj :- `k') :/ nj, (aw.full.seP[., 4] :/ oaat[., 2]):^2)) < (epsilon(1)^(3/4))) 108 | 109 | disp "(multe test success): multe OAAT consistency on simulated data" 110 | 111 | * -------------------- 112 | * Internal Consistency 113 | * -------------------- 114 | 115 | qui expand _expand 116 | qui multe Y Cdouble, treat(Tbyte) strat(Wbyte) mata(ex) 117 | 118 | * mata assert(max(reldif(fw.full.estA, ex.full.estA)) < (epsilon(1))^(3/4)) 119 | * mata assert(max(reldif(fw.full.estB, ex.full.estB)) < (epsilon(1))^(3/4)) 120 | * mata assert(max(reldif(fw.full.seP, ex.full.seP)) < (epsilon(1))^(3/4)) 121 | * mata assert(max(reldif(fw.full.seB, ex.full.seB)) < (epsilon(1))^(3/4)) 122 | * mata assert(max(reldif(fw.full.seO, ex.full.seO)) < (epsilon(1))^(3/4)) 123 | 124 | mata assert(max(reldif(aw.full.estA, ex.full.estA)) < (epsilon(1))^(3/4)) 125 | mata assert(max(reldif(aw.full.estB, ex.full.estB)) < (epsilon(1))^(3/4)) 126 | mata assert(max(reldif(aw.full.seP, ex.full.seP)) > (epsilon(1))^(1/4)) 127 | mata assert(max(reldif(aw.full.seB, ex.full.seB)) > (epsilon(1))^(1/4)) 128 | mata assert(max(reldif(aw.full.seO, ex.full.seO)) > (epsilon(1))^(1/4)) 129 | 130 | * NB: Fundamentally aw and fw have different variances. The first 131 | * gives V((X' W X)^-1 X' W e) = (X' W X)^-1 X' W V(e) W X (X' W X)^-1 132 | * and the second gives (X' W X)^-1 X' (W V(e)) X (X' W X)^-1 133 | 134 | * -------- 135 | * External 136 | * -------- 137 | 138 | mata oaat = J(4, 2, .) 139 | forvalues i = 2 / 5 { 140 | qui reg Y i`i'.Tbyte _W* if inlist(Tbyte, 1, `i'), noc r 141 | mata oaat[`i'-1, .] = st_matrix("r(table)")[1::2, 1]' 142 | } 143 | mata info = panelsetup(sort(st_data(., "Tbyte"), 1), 1) 144 | mata nj = info[2::rows(info), 2] :- info[2::rows(info), 1] :+ 1 :+ info[1, 2] 145 | mata assert(max(reldif(ex.full.estA[., 4], oaat[., 1])) < (epsilon(1)^(3/4))) 146 | mata assert(max(reldif((nj :- `k') :/ nj, (ex.full.seP[., 4] :/ oaat[., 2]):^2)) < (epsilon(1)^(3/4))) 147 | 148 | qui teffects ipw (Y) (Tbyte b0.Wbyte) 149 | matrix table = r(table) 150 | matrix table = table[1..2,"ATE:"] 151 | mata table = st_matrix("table") 152 | mata assert(max(reldif(ex.full.estA[.,3], table[1,.]')) < (epsilon(1)^(3/4))) 153 | mata assert(max(reldif(ex.full.seP[.,3], table[2,.]')) < (epsilon(1)^(3/4))) 154 | 155 | * ------------- 156 | * More internal 157 | * ------------- 158 | 159 | contract Y Cdouble Tbyte Wbyte G _expand, f(_nobs) 160 | assert _expand == _nobs 161 | gisid G 162 | qui multe Y Cdouble [aw=_expand], treat(Tbyte) strat(Wbyte) mata(con) 163 | 164 | * mata assert(max(reldif(fw.full.estA, con.full.estA)) < (epsilon(1))^(3/4)) 165 | * mata assert(max(reldif(fw.full.estB, con.full.estB)) < (epsilon(1))^(3/4)) 166 | * mata assert(max(reldif(fw.full.seP, con.full.seP)) > (epsilon(1))^(1/4)) 167 | * mata assert(max(reldif(fw.full.seB, con.full.seB)) > (epsilon(1))^(1/4)) 168 | * mata assert(max(reldif(fw.full.seO, con.full.seO)) > (epsilon(1))^(1/4)) 169 | 170 | mata assert(max(reldif(aw.full.estA, con.full.estA)) < (epsilon(1))^(3/4)) 171 | mata assert(max(reldif(aw.full.estB, con.full.estB)) < (epsilon(1))^(3/4)) 172 | mata assert(max(reldif(aw.full.seP, con.full.seP)) < (epsilon(1))^(3/4)) 173 | mata assert(max(reldif(aw.full.seB, con.full.seB)) < (epsilon(1))^(3/4)) 174 | mata assert(max(reldif(aw.full.seO, con.full.seO)) < (epsilon(1))^(3/4)) 175 | 176 | disp "(multe test success): multe internal expand/contract consistency on simulated data" 177 | end 178 | 179 | capture program drop multe_weight_startest 180 | program multe_weight_startest 181 | set seed 1729 182 | use test/example_star.dta, clear 183 | gen `c(obs_t)' G = _n 184 | gen `c(obs_t)' _expand = ceil(runiform() * 10) 185 | 186 | qui multe score , treat(treatment) strat(school) mata(now) 187 | qui multe score [aw=_expand], treat(treatment) strat(school) mata(aw) 188 | qui multe score [pw=_expand], treat(treatment) strat(school) mata(pw) 189 | 190 | * --------------- 191 | * Internal Checks 192 | * --------------- 193 | 194 | mata assert(max(reldif(aw.full.estA, now.full.estA)) > (epsilon(1))^(1/4)) 195 | mata assert(max(reldif(aw.full.estB, now.full.estB)) > (epsilon(1))^(1/4)) 196 | mata assert(max(reldif(aw.full.seP, now.full.seP)) > (epsilon(1))^(1/4)) 197 | mata assert(max(reldif(aw.full.seB, now.full.seB)) > (epsilon(1))^(1/4)) 198 | mata assert(max(reldif(aw.full.seO, now.full.seO)) > (epsilon(1))^(1/4)) 199 | 200 | mata assert(max(reldif(pw.full.estA, now.full.estA)) > (epsilon(1))^(1/4)) 201 | mata assert(max(reldif(pw.full.estB, now.full.estB)) > (epsilon(1))^(1/4)) 202 | mata assert(max(reldif(pw.full.seP, now.full.seP)) > (epsilon(1))^(1/4)) 203 | mata assert(max(reldif(pw.full.seB, now.full.seB)) > (epsilon(1))^(1/4)) 204 | mata assert(max(reldif(pw.full.seO, now.full.seO)) > (epsilon(1))^(1/4)) 205 | 206 | mata assert(max(reldif(pw.full.estA, aw.full.estA)) < (epsilon(1))^(3/4)) 207 | mata assert(max(reldif(pw.full.estB, aw.full.estB)) < (epsilon(1))^(3/4)) 208 | mata assert(max(reldif(pw.full.seP, aw.full.seP)) < (epsilon(1))^(3/4)) 209 | mata assert(max(reldif(pw.full.seB, aw.full.seB)) < (epsilon(1))^(3/4)) 210 | mata assert(max(reldif(pw.full.seO, aw.full.seO)) < (epsilon(1))^(3/4)) 211 | 212 | disp "(multe test success): multe basic weights consistency on simulated data" 213 | 214 | * --------------------- 215 | * vs teffects ipw (ATE) 216 | * --------------------- 217 | 218 | qui tab school, gen(_W) 219 | local k = r(r) + 1 220 | 221 | qui teffects ipw (score) (treatment i.school) 222 | matrix table = r(table) 223 | matrix table = table[1..2,"ATE:"] 224 | mata table = st_matrix("table") 225 | mata assert(max(reldif(now.full.estA[.,3], table[1,.]')) < (epsilon(1)^(3/4))) 226 | mata assert(max(reldif(now.full.seP[.,3], table[2,.]')) < (epsilon(1)^(3/4))) 227 | 228 | qui teffects ipw (score) (treatment i.school) [pw=_expand] 229 | matrix table = r(table) 230 | matrix table = table[1..2,"ATE:"] 231 | mata table = st_matrix("table") 232 | mata assert(max(reldif(pw.full.estA[.,3], table[1,.]')) < (epsilon(1)^(3/4))) 233 | mata assert(max(reldif(pw.full.seP[.,3], table[2,.]')) < (epsilon(1)^(3/4))) 234 | 235 | cap drop _aw 236 | qui sum _expand 237 | gen double _aw = _N * _expand / r(sum) 238 | qui teffects ipw (score) (treatment i.school) [pw=_aw] 239 | matrix table = r(table) 240 | matrix table = table[1..2,"ATE:"] 241 | mata table = st_matrix("table") 242 | mata assert(max(reldif(aw.full.estA[.,3], table[1,.]')) < (epsilon(1)^(3/4))) 243 | mata assert(max(reldif(aw.full.seP[.,3], table[2,.]')) < (epsilon(1)^(3/4))) 244 | 245 | disp "(multe test success): multe ATE consistency on simulated data" 246 | 247 | * ----------------- 248 | * vs regress (OAAT) 249 | * ----------------- 250 | 251 | mata oaat = J(2, 2, .) 252 | forvalues i = 2 / 3 { 253 | qui reg score i`i'.treatment _W* if inlist(treatment, 1, `i'), noc r 254 | mata oaat[`i'-1, .] = st_matrix("r(table)")[1::2, 1]' 255 | } 256 | mata info = panelsetup(sort(st_data(., "treatment"), 1), 1) 257 | mata nj = info[2::rows(info), 2] :- info[2::rows(info), 1] :+ 1 :+ info[1, 2] 258 | mata assert(max(reldif(now.full.estA[., 4], oaat[., 1])) < (epsilon(1)^(3/4))) 259 | mata assert(max(reldif((nj :- `k') :/ nj, (now.full.seP[., 4] :/ oaat[., 2]):^2)) < (epsilon(1)^(3/4))) 260 | 261 | mata oaat = J(2, 2, .) 262 | forvalues i = 2 / 3 { 263 | qui reg score i`i'.treatment _W* if inlist(treatment, 1, `i') [pw=_expand], noc r 264 | mata oaat[`i'-1, .] = st_matrix("r(table)")[1::2, 1]' 265 | } 266 | mata info = panelsetup(sort(st_data(., "treatment"), 1), 1) 267 | mata nj = info[2::rows(info), 2] :- info[2::rows(info), 1] :+ 1 :+ info[1, 2] 268 | mata assert(max(reldif(pw.full.estA[., 4], oaat[., 1])) < (epsilon(1)^(3/4))) 269 | mata assert(max(reldif((nj :- `k') :/ nj, (pw.full.seP[., 4] :/ oaat[., 2]):^2)) < (epsilon(1)^(3/4))) 270 | 271 | mata oaat = J(2, 2, .) 272 | forvalues i = 2 / 3 { 273 | qui reg score i`i'.treatment _W* if inlist(treatment, 1, `i') [aw=_expand], noc r 274 | mata oaat[`i'-1, .] = st_matrix("r(table)")[1::2, 1]' 275 | } 276 | mata info = panelsetup(sort(st_data(., "treatment"), 1), 1) 277 | mata nj = info[2::rows(info), 2] :- info[2::rows(info), 1] :+ 1 :+ info[1, 2] 278 | mata assert(max(reldif(aw.full.estA[., 4], oaat[., 1])) < (epsilon(1)^(3/4))) 279 | mata assert(max(reldif((nj :- `k') :/ nj, (aw.full.seP[., 4] :/ oaat[., 2]):^2)) < (epsilon(1)^(3/4))) 280 | 281 | disp "(multe test success): multe OAAT consistency on simulated data" 282 | 283 | * ---------------------- 284 | * Check in expanded data 285 | * ---------------------- 286 | 287 | qui expand _expand 288 | qui multe score, treat(treatment) strat(school) mata(ex) 289 | 290 | * mata assert(max(reldif(fw.full.estA, ex.full.estA)) < (epsilon(1))^(3/4)) 291 | * mata assert(max(reldif(fw.full.estB, ex.full.estB)) < (epsilon(1))^(3/4)) 292 | * mata assert(max(reldif(fw.full.seP, ex.full.seP)) < (epsilon(1))^(3/4)) 293 | * mata assert(max(reldif(fw.full.seB, ex.full.seB)) < (epsilon(1))^(3/4)) 294 | * mata assert(max(reldif(fw.full.seO, ex.full.seO)) < (epsilon(1))^(3/4)) 295 | 296 | mata assert(max(reldif(aw.full.estA, ex.full.estA)) < (epsilon(1))^(3/4)) 297 | mata assert(max(reldif(aw.full.estB, ex.full.estB)) < (epsilon(1))^(3/4)) 298 | mata assert(max(reldif(aw.full.seP, ex.full.seP)) > (epsilon(1))^(1/4)) 299 | mata assert(max(reldif(aw.full.seB, ex.full.seB)) > (epsilon(1))^(1/4)) 300 | mata assert(max(reldif(aw.full.seO, ex.full.seO)) > (epsilon(1))^(1/4)) 301 | 302 | mata oaat = J(2, 2, .) 303 | forvalues i = 2 / 3 { 304 | qui reg score i`i'.treatment _W* if inlist(treatment, 1, `i'), noc r 305 | mata oaat[`i'-1, .] = st_matrix("r(table)")[1::2, 1]' 306 | } 307 | mata info = panelsetup(sort(st_data(., "treatment"), 1), 1) 308 | mata nj = info[2::rows(info), 2] :- info[2::rows(info), 1] :+ 1 :+ info[1, 2] 309 | mata assert(max(reldif(ex.full.estA[., 4], oaat[., 1])) < (epsilon(1)^(3/4))) 310 | mata assert(max(reldif((nj :- `k') :/ nj, (ex.full.seP[., 4] :/ oaat[., 2]):^2)) < (epsilon(1)^(3/4))) 311 | 312 | qui teffects ipw (score) (treatment i.school) 313 | matrix table = r(table) 314 | matrix table = table[1..2,"ATE:"] 315 | mata table = st_matrix("table") 316 | mata assert(max(reldif(ex.full.estA[.,3], table[1,.]')) < (epsilon(1)^(3/4))) 317 | mata assert(max(reldif(ex.full.seP[.,3], table[2,.]')) < (epsilon(1)^(3/4))) 318 | 319 | disp "(multe test success): multe internal expand consistency on STAR data" 320 | end 321 | --------------------------------------------------------------------------------