ps_trunc() bounds extreme propensity scores to fixed limits, replacing
out-of-range values with the boundary value (a form of winsorizing). The
result is a vector or matrix of the same length and dimensions as
.propensity, with no observations removed. This contrasts with ps_trim(),
which sets extreme values to NA (effectively removing those observations
from analysis).
Usage
ps_trunc(
.propensity,
method = c("ps", "pctl", "cr"),
lower = NULL,
upper = NULL,
.exposure = NULL,
.focal_level = NULL,
.reference_level = NULL,
...,
.treated = NULL,
.untreated = NULL,
ps = lifecycle::deprecated()
)Arguments
- .propensity
A numeric vector of propensity scores in (0, 1) (binary exposures), or a matrix/data.frame where each column contains propensity scores for one level of a categorical exposure. A data frame truncated for a binary exposure is reduced to a single column: the second column of a two column data frame, which is the probability of the second level in the layout model predictions come in, and the first column otherwise. The column taken is announced;
options(propensity.quiet = TRUE)silences the announcement. A matrix is held to the same open interval as a vector, so a score of exactly 0 or 1 in any cell is refused and a separated multinomial fit cannot be repaired by truncating it; see Propensity scores at 0 and 1 inwt_ate().- method
One of
"ps","pctl", or"cr":"ps"(default): Truncate directly on propensity score values. Values outside[lower, upper]are set to the nearest bound. For categorical exposures, applies symmetric truncation usingloweras the threshold (delta) and renormalizes rows to sum to 1."pctl": Truncate at quantiles of the propensity score distribution. Thelowerandupperarguments specify quantile probabilities. For categorical exposures, quantiles are computed across all columns."cr": Truncate to the common range of propensity scores across exposure groups (binary exposures only). Bounds are[min(.propensity[focal]), max(.propensity[reference])]. Requires.exposure. When those bounds cross, so that the two distributions do not overlap at all, there is no common range to bound the scores to and the call errors with classpropensity_no_overlap_error. That differs deliberately fromps_trim(), which trims every observed unit in the same situation, a truthful record of an empty overlap region.
For categorical exposures, only
"ps"and"pctl"are supported.- lower, upper
Bounds for truncation. Interpretation depends on
method:method = "ps": Propensity score values (defaults: 0.1 and 0.9). For categorical exposures,loweris the truncation threshold delta (default: 0.01) andupperis ignored. That default deliberately differs from the 0.1 thresholdps_trim()uses for categorical exposures: truncation keeps every unit and only pins the most extreme scores back to the threshold, so its default is a gentle winsorization, whereas trimming discards the units it selects and follows common-support trimming practice. Withkexposure levels, a threshold of1/kor larger cannot be met by every column of a row that sums to one, and is an error.method = "pctl": Quantile probabilities (defaults: 0.05 and 0.95; categorical defaults: 0.01 and 0.99).method = "cr": Ignored; bounds are determined by the data.
- .exposure
An exposure vector. Required for method
"cr"(binary exposure vector) and for categorical exposures (factor or character vector) with any method.- .focal_level
The value of
.exposurerepresenting the focal (treated) group, used by"cr". Every binary coding honors it: 0/1 numeric, logical, two-level factor, and two-level character exposures are all coded with the named level as focal, and a level the exposure never takes is an error. With no level named, a binary exposure defaults to its higher level, which is1for a 0/1 exposure,TRUEfor a logical one, and the second of the two levels a factor or character exposure takes. Levels a factor declares but never takes are not candidates. Naming any other level reverses the coding, so.propensitymust then hold the probability of the named level.- .reference_level
The value of
.exposurerepresenting the reference (control) group. Naming it makes the exposure's other level focal, with the same consequence for.propensity, and a level the exposure never takes is an error. Automatically detected if not supplied.- ...
Additional arguments passed to methods.
- .treated
- .untreated
- ps
Use
.propensityinstead. A call that namespsmust name the arguments after it as well, since a positional argument binds to.propensity.
Value
A ps_trunc object (a numeric vector for binary exposures, or a
matrix for categorical exposures). Use ps_trunc_meta() to inspect
metadata including method, lower_bound, upper_bound,
truncated_idx (positions of modified values), and n_obs (the number of
observations those positions describe).
Details
Unlike ps_trim(), truncation preserves all observations. No NA values
are introduced; out-of-range scores are replaced with the boundary value.
For binary exposures, each propensity score \(e_i\) is bounded:
If \(e_i < l\), set \(e_i = l\) (the lower bound).
If \(e_i > u\), set \(e_i = u\) (the upper bound).
For categorical exposures, values below the threshold are set to the threshold and each row is renormalized to sum to 1.
Arithmetic behavior: Arithmetic operations on ps_trunc objects return
plain numeric vectors. Once propensity scores are transformed (e.g., into
weights), the result is no longer a propensity score.
Combining behavior: Combining ps_trunc objects with c() requires
matching truncation parameters. Mismatched parameters produce a warning and
return a plain numeric vector.
Missing values
A propensity score that arrives missing is not one this function pinned to a
bound, so it takes no part in the truncation record: it never joins
truncated_idx, and is_unit_truncated() reports FALSE for it. The value
stays missing in the result. For a matrix of categorical propensity scores, a
row with a missing cell comes back missing throughout, because renormalizing
a row divides by its sum and that sum is unknown.
The methods that read their bounds off the data read them off the data they
have. "pctl" and "cr" work their bounds out from the complete scores, and
the categorical "pctl" bounds come from the cells of the complete rows, so
the bounds are the ones the same call would produce with the missing
observations dropped.
A missing exposure is a different matter, and "cr" refuses one with an
error of class propensity_missing_value_error. Its bounds come from the
exposure groups, and a unit that belongs to neither leaves them undefined.
Remove or impute the missing exposure values first.
The truncation record
A ps_trunc records which units were winsorized as positions among the
observations it was written for, along with how many observations that was.
Operations that hand this package the subscript re-index those positions onto
the result: subsetting with [, sort(), unique(), and rep() all return
a record written for what they return, and a subscript naming a position more
than once reports that unit at every place it now holds.
Operations that change how many observations there are without supplying a
subscript cannot re-index the record, and it is dropped rather than worked
out from the values, since a score that arrived equal to a bound is
indistinguishable from one this function pinned there. vctrs::vec_slice(),
which is how filtering, joining, and grouped summaries in dplyr reach a
column, is the usual route, and dropping the record there raises a warning
of class propensity_trunc_record_warning. Combining with c() drops it
without comment, because concatenation appends one set of observations to
another and the prototype it builds the result from holds no positions to
lose. The values, the class, and the method and its bounds are untouched
either way.
A record can also outlive the observations it describes, because it travels
by routes vctrs does not see: growing a ps_trunc by subassignment carries
it across the length change. is_unit_truncated() therefore checks that the
record covers the object it is given and raises an error of class
propensity_missing_meta_error when it does not, rather than name truncated
units at stale positions.
That check compares how many observations the record was written for against
how many the object holds, which a reordering does not change. An operation
that reorders the observations through vctrs, rather than through [,
therefore keeps a record written for the order they used to be in:
vctrs::vec_slice(x, 5:1) and dplyr::arrange() both return the values in
a new order under positions still naming the old one, and
is_unit_truncated() answers from those positions and names the wrong
units. Subsetting with [ is handed the subscript and re-indexes, so
reorder with [, or put the propensity scores in the order you want before
truncating them.
Casting a numeric vector into a ps_trunc with vctrs::vec_cast() is a type
operation and not a truncation. The result is described by the method and
bounds of the target and records that none of the arriving values was pinned
to a bound, so it can hold scores outside those bounds, including 0 and 1.
Call ps_trunc() on the scores to truncate them.
References
Crump, R. K., Hotz, V. J., Imbens, G. W., & Mitnik, O. A. (2009). Dealing with limited overlap in estimation of average treatment effects. Biometrika, 96(1), 187–199.
Walker, A. M., Patrick, A. R., Lauer, M. S., et al. (2013). A tool for assessing the feasibility of comparative effectiveness research. Comparative Effectiveness Research, 3, 11–20.
See also
ps_trim() for removing (rather than bounding) extreme values,
ps_refit() for refitting the propensity model after trimming,
is_ps_truncated(), is_unit_truncated(), ps_trunc_meta()
Examples
set.seed(2)
n <- 200
x <- rnorm(n)
z <- rbinom(n, 1, plogis(1.2 * x))
fit <- glm(z ~ x, family = binomial)
ps <- predict(fit, type = "response")
# Truncate to [0.1, 0.9]
ps_t <- ps_trunc(ps, method = "ps", lower = 0.1, upper = 0.9)
ps_t
#> <ps_trunc{[0.1,0.9], method=ps}[200]>
#> 1 2 3 4 5 6 7 8
#> 0.2913095 0.5707511 0.8590561 0.2418820 0.4993060 0.5567591 0.7011122 0.4561604
#> 9 10 11 12 13 14 15 16
#> 0.9000000 0.4834312 0.6312438 0.7594616 0.4153613 0.2603881 0.8827176 0.1000000
#> 17 18 19 20 21 22 23 24
#> 0.7384237 0.5307543 0.7655683 0.6349278 0.9000000 0.2283122 0.8592915 0.9000000
#> 25 26 27 28 29 30 31 32
#> 0.5224033 0.1000000 0.6461643 0.3628349 0.7199125 0.5983593 0.7081103 0.6059826
#> 33 34 35 36 37 38 39 40
#> 0.7776782 0.4442186 0.3189618 0.3630602 0.1432262 0.2900408 0.3722937 0.4543264
#> 41 42 43 44 45 46 47 48
#> 0.4177638 0.1148898 0.3038315 0.8956760 0.6813225 0.9000000 0.4385124 0.4964323
#> 49 50 51 52 53 54 55 56
#> 0.4711469 0.2285337 0.3046166 0.9000000 0.3714863 0.8128663 0.2587396 0.1141443
#> 57 58 59 60 61 62 63 64
#> 0.4338453 0.7502468 0.7892860 0.8697086 0.1351329 0.9000000 0.3365403 0.5636424
#> 65 66 67 68 69 70 71 72
#> 0.6533256 0.3088378 0.1105762 0.3927369 0.5438044 0.2916295 0.2858818 0.6089556
#> 73 74 75 76 77 78 79 80
#> 0.4826524 0.6355772 0.5065031 0.2890305 0.8174117 0.7154239 0.7732114 0.1906359
#> 81 82 83 84 85 86 87 88
#> 0.7622717 0.1472971 0.3788316 0.1970403 0.1000000 0.8871259 0.3486998 0.4440783
#> 89 90 91 92 93 94 95 96
#> 0.4168763 0.6233901 0.8606965 0.8708768 0.2314475 0.1994226 0.1740398 0.2183041
#> 97 98 99 100 101 102 103 104
#> 0.9000000 0.5231365 0.3036226 0.3616811 0.7773582 0.5907629 0.4361655 0.3253709
#> 105 106 107 108 109 110 111 112
#> 0.2991480 0.9000000 0.7510710 0.9000000 0.4078242 0.4264336 0.2629645 0.4532487
#> 113 114 115 116 117 118 119 120
#> 0.6448288 0.8262184 0.6674247 0.6408723 0.8053646 0.7907095 0.5498332 0.3173982
#> 121 122 123 124 125 126 127 128
#> 0.8071017 0.5584826 0.8744315 0.4053976 0.2594361 0.6609892 0.3447196 0.6851535
#> 129 130 131 132 133 134 135 136
#> 0.1434914 0.1410494 0.6969682 0.6090883 0.7368408 0.1087328 0.8022199 0.8001309
#> 137 138 139 140 141 142 143 144
#> 0.7692948 0.7186431 0.9000000 0.1834141 0.3662171 0.6292392 0.3118604 0.5441734
#> 145 146 147 148 149 150 151 152
#> 0.7097460 0.3486308 0.6894218 0.6639810 0.3119192 0.2693231 0.7582975 0.4751339
#> 153 154 155 156 157 158 159 160
#> 0.7043397 0.3032090 0.8131267 0.2020948 0.7139969 0.6429234 0.5927018 0.6918371
#> 161 162 163 164 165 166 167 168
#> 0.6263848 0.3524856 0.4486289 0.6165339 0.2074392 0.2942181 0.9000000 0.1003085
#> 169 170 171 172 173 174 175 176
#> 0.2210197 0.7611782 0.7800143 0.7302196 0.5364399 0.6072561 0.2895753 0.3489979
#> 177 178 179 180 181 182 183 184
#> 0.4500408 0.2829251 0.7262051 0.1573127 0.2623292 0.2166743 0.6247878 0.2416706
#> 185 186 187 188 189 190 191 192
#> 0.6625838 0.7959533 0.5278943 0.6555095 0.3485232 0.6526880 0.2148033 0.5002503
#> 193 194 195 196 197 198 199 200
#> 0.2017086 0.4490035 0.7798096 0.6994296 0.4022315 0.3161761 0.3003833 0.3261363
# Truncate at the 1st and 99th percentiles
ps_trunc(ps, method = "pctl", lower = 0.01, upper = 0.99)
#> <ps_trunc{[0.0901,0.912], method=pctl}[200]>
#> 1 2 3 4 5 6 7
#> 0.29130945 0.57075108 0.85905610 0.24188201 0.49930600 0.55675915 0.70111217
#> 8 9 10 11 12 13 14
#> 0.45616038 0.90360175 0.48343119 0.63124383 0.75946160 0.41536130 0.26038808
#> 15 16 17 18 19 20 21
#> 0.88271762 0.09006607 0.73842370 0.53075430 0.76556830 0.63492785 0.91202081
#> 22 23 24 25 26 27 28
#> 0.22831221 0.85929151 0.90074563 0.52240328 0.09006607 0.64616425 0.36283492
#> 29 30 31 32 33 34 35
#> 0.71991248 0.59835929 0.70811028 0.60598256 0.77767823 0.44421858 0.31896179
#> 36 37 38 39 40 41 42
#> 0.36306016 0.14322616 0.29004079 0.37229372 0.45432638 0.41776382 0.11488981
#> 43 44 45 46 47 48 49
#> 0.30383148 0.89567605 0.68132246 0.90420941 0.43851237 0.49643227 0.47114693
#> 50 51 52 53 54 55 56
#> 0.22853370 0.30461661 0.91106441 0.37148631 0.81286633 0.25873963 0.11414425
#> 57 58 59 60 61 62 63
#> 0.43384531 0.75024682 0.78928598 0.86970860 0.13513287 0.90793306 0.33654034
#> 64 65 66 67 68 69 70
#> 0.56364237 0.65332558 0.30883775 0.11057616 0.39273691 0.54380444 0.29162947
#> 71 72 73 74 75 76 77
#> 0.28588182 0.60895559 0.48265241 0.63557725 0.50650310 0.28903045 0.81741170
#> 78 79 80 81 82 83 84
#> 0.71542388 0.77321135 0.19063586 0.76227170 0.14729713 0.37883165 0.19704028
#> 85 86 87 88 89 90 91
#> 0.09015377 0.88712592 0.34869977 0.44407832 0.41687627 0.62339015 0.86069650
#> 92 93 94 95 96 97 98
#> 0.87087680 0.23144748 0.19942258 0.17403983 0.21830407 0.90120123 0.52313647
#> 99 100 101 102 103 104 105
#> 0.30362260 0.36168114 0.77735817 0.59076288 0.43616553 0.32537093 0.29914802
#> 106 107 108 109 110 111 112
#> 0.90944557 0.75107099 0.90586644 0.40782418 0.42643359 0.26296452 0.45324867
#> 113 114 115 116 117 118 119
#> 0.64482877 0.82621843 0.66742471 0.64087231 0.80536462 0.79070955 0.54983317
#> 120 121 122 123 124 125 126
#> 0.31739822 0.80710169 0.55848264 0.87443145 0.40539762 0.25943605 0.66098916
#> 127 128 129 130 131 132 133
#> 0.34471960 0.68515350 0.14349137 0.14104940 0.69696824 0.60908833 0.73684079
#> 134 135 136 137 138 139 140
#> 0.10873282 0.80221987 0.80013088 0.76929476 0.71864309 0.91202081 0.18341413
#> 141 142 143 144 145 146 147
#> 0.36621714 0.62923918 0.31186040 0.54417339 0.70974601 0.34863085 0.68942181
#> 148 149 150 151 152 153 154
#> 0.66398099 0.31191915 0.26932313 0.75829753 0.47513393 0.70433975 0.30320896
#> 155 156 157 158 159 160 161
#> 0.81312672 0.20209485 0.71399686 0.64292343 0.59270175 0.69183710 0.62638476
#> 162 163 164 165 166 167 168
#> 0.35248559 0.44862887 0.61653394 0.20743922 0.29421813 0.91200893 0.10030845
#> 169 170 171 172 173 174 175
#> 0.22101971 0.76117824 0.78001428 0.73021961 0.53643993 0.60725607 0.28957526
#> 176 177 178 179 180 181 182
#> 0.34899794 0.45004081 0.28292513 0.72620510 0.15731275 0.26232917 0.21667429
#> 183 184 185 186 187 188 189
#> 0.62478778 0.24167062 0.66258378 0.79595325 0.52789428 0.65550948 0.34852324
#> 190 191 192 193 194 195 196
#> 0.65268801 0.21480329 0.50025030 0.20170859 0.44900347 0.77980959 0.69942959
#> 197 198 199 200
#> 0.40223149 0.31617609 0.30038326 0.32613630
# Use truncated scores to calculate weights
wt_ate(ps_t, .exposure = z)
#> ℹ Treating `.exposure` as binary
#> <psw{estimand = ate; truncated}[200]>
#> [1] 1.411053 2.329651 1.164068 1.319056 2.002780 2.256110 1.426305 1.838777
#> [9] 1.111111 2.068547 2.711819 1.316722 1.710458 1.352060 1.132865 1.111111
#> [17] 1.354236 1.884111 1.306219 2.739185 1.111111 1.295861 1.163749 1.111111
#> [25] 2.093817 1.111111 1.547594 1.569452 1.389058 1.671237 1.412209 2.537959
#> [33] 4.497985 1.799268 1.468346 2.754364 1.167169 3.447791 2.686051 1.832597
#> [41] 2.393697 1.129803 3.291298 1.116475 1.467734 1.111111 2.280437 2.014373
#> [49] 1.890884 1.296233 1.438056 1.111111 1.591055 1.230215 3.864889 1.128852
#> [57] 1.766302 4.003953 4.745769 1.149810 7.400124 1.111111 1.507251 1.774175
#> [65] 1.530630 3.237946 1.124323 2.546234 1.838896 1.411691 1.400328 1.642156
#> [73] 1.932936 1.573373 1.974322 3.459843 1.223374 1.397773 1.293307 1.235538
#> [81] 1.311868 1.172741 2.639695 1.245392 1.111111 1.127236 2.867797 1.798815
#> [89] 2.398793 2.655268 1.161850 1.148268 1.301147 5.014477 1.210712 1.279270
#> [97] 1.111111 2.097036 1.436003 1.566615 1.286408 1.692727 1.773570 1.482296
#> [105] 1.426835 1.111111 1.331432 1.111111 2.452037 2.345031 1.356787 1.828985
#> [113] 1.550799 1.210334 1.498296 1.560373 5.137812 1.264687 1.818733 3.150616
#> [121] 5.184079 1.790566 1.143600 2.466714 1.350322 1.512884 1.526064 3.176151
#> [129] 1.167531 7.089715 1.434786 2.558123 3.799981 1.121998 1.246541 1.249796
#> [137] 1.299892 1.391511 1.111111 5.452143 1.577827 2.697157 1.453194 2.193817
#> [145] 1.408955 1.535228 1.450491 2.976022 1.453318 3.713012 4.137318 1.905248
#> [153] 1.419769 1.435150 5.351220 1.253282 1.400566 1.555395 1.687189 3.245037
#> [161] 2.676550 1.544367 2.229014 1.621971 4.820689 1.416868 1.111111 1.111492
#> [169] 4.524483 1.313753 4.545750 1.369451 2.157218 2.546188 1.407609 2.865346
#> [177] 2.222021 1.394555 1.377021 1.186680 1.355618 1.276608 2.665158 1.318688
#> [185] 1.509243 1.256355 1.894319 1.525531 2.869249 2.879256 1.273566 2.001002
#> [193] 1.252675 1.814893 1.282364 3.327007 1.672888 1.462365 1.429354 1.483980
# Inspect which observations were truncated
is_unit_truncated(ps_t)
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
#> [13] FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE TRUE
#> [25] FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [37] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
#> [49] FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [61] FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [73] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [85] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [97] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE TRUE
#> [109] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [121] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [133] FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE
#> [145] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [157] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE
#> [169] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [181] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [193] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
