Stabilized or Unstabilized IPTW: The Same Balance in a Pseudo-Population of 200 or 100

On this page
อ่านฉบับภาษาไทย (Thai version)
Abstract
Inverse probability of treatment weighting (IPTW) weights each patient by one over the probability of the treatment received, given measured characteristics. Unstabilised weights for the average treatment effect (ATE) make a weighted population, or pseudo-population, of about twice the sample size. Stabilisation multiplies every weight in an arm by that arm's share of patients, so the total is about the sample size. In a 100-patient hand example, where treatment follows severity, unstabilised weights build a pseudo-population of 200 and stabilised weights one of 100. Either way, 50% of each arm has high severity, against 57.1% of the treated and 33.3% of the untreated before weighting. Because that multiplier is constant within an arm, the balance cannot change. For a binary treatment, when each arm's risk is a normalised weighted mean and the weights are not truncated or otherwise changed, the weighted risks and their difference are identical. This article works the arithmetic by hand, checks it in simulated data and sets out what to report.
A weighted table with 200 patients
A registry analyst presents a weighted analysis of a treatment given to some of 100 patients, adjusted for disease severity. A reviewer stops at the weighted baseline table: it shows 200 patients, although the registry holds 100. Were some patients counted twice? The registry is fictional, and its counts are the hand example below.
A colleague suggests stabilised weights "to fix the count". The team wants to know whether that would change the adjustment for confounding or only the bookkeeping. This article works the answer on paper, then repeats it in simulated data with Stata and R.
The hand example: 100 patients, two severity levels
Hand example. Of the 100 patients, 50 had high disease severity and 50 had low severity, recorded before treatment. Clinicians treated 40 of the 50 High patients and 30 of the 50 Low patients, so 70 were treated and 30 were not. Severity also affects the outcome, so it confounds any crude comparison of the two arms.
Here $X$ is severity, and the propensity score is the share treated at each level: $e = 40/50 = 0.80$ for High and $e = 30/50 = 0.60$ for Low. Before weighting, 40 of the 70 treated patients are High (57.1%), against 10 of the 30 untreated (33.3%). The treated arm is sicker, and that is the imbalance the weights must remove.
Hand example: unstabilised weights, a pseudo-population of 200
Hand example, continued. The unstabilised weight is one over the probability of the treatment a patient actually received: $w = 1/e$ if treated and $w = 1/(1 - e)$ if untreated. The probability of staying untreated is $1 - e = 0.20$ for High and $0.40$ for Low. The pseudo-population size is the sum of all the weights.
-
High, treated: 40 patients
\[ 40 \times \frac{1}{0.80} = 40 \times 1.25 = 50 \]
The 40 treated High patients stand for all 50 High patients.
-
High, untreated: 10 patients
\[ 10 \times \frac{1}{0.20} = 10 \times 5.00 = 50 \]
Only 10 High patients went untreated, so each counts as 5.00.
-
Low, treated: 30 patients
\[ \frac{30}{0.60} = 50 \]
The weight is $1/0.60 = 1.667$ after rounding; dividing directly gives exactly 50.
-
Low, untreated: 20 patients
\[ 20 \times \frac{1}{0.40} = 20 \times 2.50 = 50 \]
Each untreated Low patient counts as 2.50.
-
Arm totals and pseudo-population size
\[ 100 + 100 = 200 \]
Each arm holds 50 + 50 = 100 weighted patients, so the weights of 100 patients sum to 200.
Result: Each arm is now a copy of the whole sample, 50% High, and the pseudo-population totals 200. No patient was counted twice; each weight is the number of patients that one patient stands for.
Hand example: stabilised weights, a pseudo-population of 100
Hand example, continued. The marginal probability of treatment is the share of all patients who received each treatment: $P(A = 1) = 70/100 = 0.70$ and $P(A = 0) = 0.30$. The stabilised weight puts that share in the numerator in place of 1 [1, 2]:
$$sw_i = \frac{P(A = a_i)}{P(A = a_i \mid X_i)}$$Here $a_i$ is the treatment patient $i$ received and $X_i$ that patient's severity, so a treated patient gets $0.70/e$ and an untreated patient $0.30/(1 - e)$.
-
High, treated: 40 patients
\[ 40 \times \frac{0.70}{0.80} = 40 \times 0.875 = 35 \]
Each treated High patient now counts as 0.875.
-
High, untreated: 10 patients
\[ 10 \times \frac{0.30}{0.20} = 10 \times 1.500 = 15 \]
Each untreated High patient counts as 1.500.
-
Low, treated: 30 patients
\[ 0.70 \times \frac{30}{0.60} = 0.70 \times 50 = 35 \]
The weight is $0.70/0.60 = 1.167$ after rounding; the cell is 0.70 times its unstabilised total.
-
Low, untreated: 20 patients
\[ 20 \times \frac{0.30}{0.40} = 20 \times 0.750 = 15 \]
Each untreated Low patient counts as 0.750.
-
Arm totals and pseudo-population size
\[ 70 + 30 = 100 \]
The treated arm holds 35 + 35 = 70 weighted patients and the untreated arm 15 + 15 = 30, the real arm sizes.
Result: The stabilised weights keep each arm at its real size and the pseudo-population at 100. Both arms are still 50% High: 35 of 70 and 15 of 30.
| Cell or arm | Patients | Weight w | Weighted patients, w | Weight sw | Weighted patients, sw |
|---|---|---|---|---|---|
| High, treated | 40 | 1.25 | 50 | 0.875 | 35 |
| High, untreated | 10 | 5.00 | 50 | 1.500 | 15 |
| Low, treated | 30 | 1.667 | 50 | 1.167 | 35 |
| Low, untreated | 20 | 2.50 | 50 | 0.750 | 15 |
| Treated arm | 70 | 100 | 70 | ||
| Untreated arm | 30 | 100 | 30 | ||
| All patients | 100 | 200 | 100 |
Why stabilising cannot change the balance
Compare two weights within one arm. Among the untreated, a High patient outweighs a Low patient by the same factor under both weightings: $5/2.5 = 2$ and $1.5/0.75 = 2$. The same holds among the treated.
The reason is in the definition: stabilising multiplies every weight in an arm by one constant, 0.70 for the treated and 0.30 for the untreated. A share computed within an arm, such as the High share, has the arm's weights in both its numerator and its denominator, so the constant cancels. Balance compares such shares between arms, so it stays where the unstabilised weights put it.
Treatment here is decided once, so this is a point-treatment analysis. A marginal structural model instead handles treatment that changes over time, with a weight at every time point multiplied along the way. The box below states what stabilisation means for unusually large weights, for capping them (truncation) and for removing patients (trimming). The extreme-weights part of this series covers all three in full.
The same result from the rules of probability
The hand arithmetic follows from one rule of probability. For a severity level $X$ and a treatment $a$, the joint probability factorises:
$$P(X, A = a) = P(A = a \mid X)\,P(X)$$Here $P(X)$ is the share of the whole sample at that severity, and $P(A = a \mid X)$ is the probability of treatment $a$ at that severity. The patients at severity $X$ in arm $a$ are in proportion to $P(X, A = a)$, which still carries the dependence of treatment on severity. The unstabilised weight divides it out:
$$\frac{P(X, A = a)}{P(A = a \mid X)} = P(X)$$Each arm now follows $P(X)$, the severity mix of the whole sample, so treatment no longer depends on severity. The stabilised weight divides by the same probability and also multiplies by $P(A = a)$:
$$\frac{P(X, A = a)\,P(A = a)}{P(A = a \mid X)}$$The same cancellation leaves $P(A = a)\,P(X)$: the severity mix is again $P(X)$, scaled by a constant that sets only the size of arm $a$. In the hand example that constant turns two arms of 100 into arms of 70 and 30. Both results concern the measured covariates only and need a correct propensity model and a real chance of either treatment for every patient. The weighted comparison estimates the ATE only if those covariates include every confounder [3].
What stays the same and what can differ
In general the totals are expected values, not exact counts: unstabilised ATE weights sum to about twice the sample size, and stabilised weights to about the sample size [3]. The hand example hits 200 and 100 exactly only because each score equals the observed treated share in its severity level. With a continuous covariate or a smoother propensity model, the sums usually land near those values; a large departure is worth a look.
The point estimate does not depend on the choice when each arm's risk is a normalised weighted mean. That mean divides the sum of weight times outcome by the sum of the weights, so a constant multiplier cancels; the extreme-weights part of this series gives its formula. For a binary treatment with no truncation or other change to the weights, both weightings therefore give the same weighted risks, risk difference and risk ratio [3]. One cap applied to both sets of weights would break this, because it cuts different patients on the two scales.
What can differ is anything that reads the weights as counts. Software that treats them as frequency weights, the number of identical patients each row stands for, reports 200 patients for the unstabilised weights: the reviewer's table. A standard error built on those counts acts as if 200 patients had been observed, so it is too small; stabilised weights shrink the problem by matching the sample size [4].
A robust (sandwich) standard error is read from the spread of the weighted data rather than from the counts, so it does not depend on the scale here. Stabilised weights tend to narrow an interval only when the weighted outcome model is not saturated, that is, when it has fewer parameters than there are combinations of treatment level and covariate pattern in the model itself [3]. A straight line across several doses of a treatment is not saturated. A model with treatment alone, as in the simulated data below, is saturated for a binary treatment.
The simulated data: the same 100 patients with an outcome
The simulated dataset types in the same 100 patients, with the same counts by severity and treatment, and adds one outcome, death. By construction, severity is the only confounder in these data. Within each severity level the treated have half the risk of death: 0.250 against 0.500 among High patients and 0.100 against 0.200 among Low patients. The crude comparison mixes that difference with severity: 0.186 among the treated against 0.300 among the untreated, a risk difference of -0.114.
A logistic regression of treatment on severity estimates the propensity score. With one binary covariate the fitted scores are the observed shares, 0.80 and 0.60, so the weights are those of the hand example. Balance is summarised by the standardised mean difference (SMD), the difference between arms in a covariate's mean divided by a pooled standard deviation. The balance part of this series explains how to read it.
| Quantity | Unweighted | Unstabilised w | Stabilised sw |
|---|---|---|---|
| Treated arm, sum of weights (patients) | 70 | 100 | 70 |
| Untreated arm, sum of weights (patients) | 30 | 100 | 30 |
| All patients, sum of weights | 100 | 200 | 100 |
| High severity share, treated | 0.571 | 0.500 | 0.500 |
| High severity share, untreated | 0.333 | 0.500 | 0.500 |
| Severity SMD | 0.49 | 0.00 | 0.00 |
| Risk of death, treated | 0.186 | 0.175 | 0.175 |
| Risk of death, untreated | 0.300 | 0.350 | 0.350 |
| Risk difference | -0.114 | -0.175 | -0.175 |
| Count-based SE of the risk difference | not computed | 0.061 | 0.098 |
| Robust SE of the risk difference (weights treated as known) | not computed | 0.106 | 0.106 |
Reading the simulated results
Both weightings remove the severity imbalance, with the SMD falling from 0.49 to 0.00. Both give a weighted risk of death of 0.175 among the treated against 0.350 among the untreated. The risk difference, -0.175, averages the within-level differences, -0.250 and -0.100, over the severity mix of the whole sample, and the risk ratio is 0.50, as within each level.
That risk difference is the ATE, a marginal effect for all 100 patients. Reading it as causal needs severity to be the only confounder, which holds here by construction. It also needs a chance of either treatment for every patient and a correct propensity model [3].
Between the two weightings, the standard errors differ only where the weights are read as counts: 0.061 with the unstabilised weights and 0.098 with the stabilised ones. The robust standard error is 0.106 under both, because a model with treatment alone is saturated. Stata's teffects ipw and glm_weightit() from R's WeightIt package also account for estimating the propensity score, and both give the same risk difference, -0.175. Their standard error is 0.101, with a 95% confidence interval of -0.373 to 0.023.
The robust standard error treats the weights as known. For the ATE it is then valid but tends to be conservative, meaning its interval covers the true value more than 95% of the time [3]. The standard error that accounts for the estimated score is a little smaller here, as expected: 0.101 against 0.106. The count-based 0.098 lands near 0.101, but it ignores the unequal weights and the estimated score, so it carries no such guarantee.
Stata: the propensity model and both weights
logit treated high, nolog
predict double ps, pr
quietly summarize treated
scalar sc_pa = r(mean)
canon p_treated 4 scalar(sc_pa)
canon p_untreated 4 1 - scalar(sc_pa)
generate double w = cond(treated == 1, 1/ps, 1/(1 - ps))
generate double sw = cond(treated == 1, scalar(sc_pa)/ps, (1 - scalar(sc_pa))/(1 - ps))
. logit treated high, nolog
Logistic regression Number of obs = 100
LR chi2(1) = 4.83
Prob > chi2 = 0.0279
Log likelihood = -58.670705 Pseudo R2 = 0.0395
------------------------------------------------------------------------------
treated | Coefficient Std. err. z P>|z| [95% conf. interval]
-------------+----------------------------------------------------------------
high | .9808289 .4564354 2.15 0.032 .0862319 1.875426
_cons | .4054651 .2886751 1.40 0.160 -.1603278 .971258
------------------------------------------------------------------------------
. predict double ps, pr
. quietly summarize treated
. scalar sc_pa = r(mean)
. canon p_treated 4 scalar(sc_pa)
CANON a6.p_treated 0.7000
. canon p_untreated 4 1 - scalar(sc_pa)
CANON a6.p_untreated 0.3000
. generate double w = cond(treated == 1, 1/ps, 1/(1 - ps))
. generate double sw = cond(treated == 1, scalar(sc_pa)/ps, (1 - scalar(sc_pa))/(1 - ps))
generate lines build w and sw. Lines beginning CANON are printed by a small helper function in the script that echoes each result; read the number at the end of each line and ignore the prefix.R: the propensity model and both weights
psm <- glm(treated ~ high, family = binomial, data = d)
print(summary(psm)$coefficients)
d$ps <- fitted(psm)
pa <- mean(d$treated)
canon("p_treated", pa)
canon("p_untreated", 1 - pa)
d$w <- ifelse(d$treated == 1, 1 / d$ps, 1 / (1 - d$ps))
d$sw <- ifelse(d$treated == 1, pa / d$ps, (1 - pa) / (1 - d$ps))
print(aggregate(cbind(ps, w, sw) ~ high + treated, data = d, FUN = mean))
> psm <- glm(treated ~ high, family = binomial, data = d)
> print(summary(psm)$coefficients)
Estimate Std. Error z value Pr(>|z|)
(Intercept) 0.4054651 0.2886751 1.404572 0.16014849
high 0.9808293 0.4564354 2.148890 0.03164316
> d$ps <- fitted(psm)
> pa <- mean(d$treated)
> canon("p_treated", pa)
CANON a6.p_treated 0.7000
> canon("p_untreated", 1 - pa)
CANON a6.p_untreated 0.3000
> d$w <- ifelse(d$treated == 1, 1/d$ps, 1/(1 - d$ps))
> d$sw <- ifelse(d$treated == 1, pa/d$ps, (1 - pa)/(1 -
+ d$ps))
> print(aggregate(cbind(ps, w, sw) ~ high + treated,
+ data = d, FUN = mean))
high treated ps w sw
1 0 0 0.6 2.500000 0.750000
2 1 0 0.8 5.000000 1.500000
3 0 1 0.6 1.666667 1.166667
4 1 1 0.8 1.250000 0.875000
glm() call fits the propensity model, pa is $P(A = 1)$, the ifelse() lines build w and sw, and the last table lists them by cell. Lines beginning CANON are printed by a small helper function in the script that echoes each result; read the number at the end of each line and ignore the prefix.Stata: the sum of each weight by arm
* the arm sums of each weight: the pseudo-population in each arm
tabstat w sw, by(treated) statistics(sum)
. * the arm sums of each weight: the pseudo-population in each arm
. tabstat w sw, by(treated) statistics(sum)
Summary statistics: Sum
Group variable: treated
treated | w sw
---------+--------------------
0 | 99.99998 30
1 | 100 70
---------+--------------------
Total | 200 100
------------------------------
99.99998 because Stata's logit fit stops a hair short of the exact score of 0.80; R prints 100.R: the sum of each weight by arm
# the arm sums of each weight: the pseudo-population in each arm
print(tapply(d$w, d$treated, sum))
print(tapply(d$sw, d$treated, sum))
> print(tapply(d$w, d$treated, sum))
0 1
100 100
> print(tapply(d$sw, d$treated, sum))
0 1
30 70
R: WeightIt with stabilize = TRUE
Ws <- weightit(treated ~ high, data = d, method = "glm", estimand = "ATE", stabilize = TRUE)
print(tapply(Ws$weights, d$treated, sum))
print(all.equal(unname(Ws$weights), d$sw))
> Ws <- weightit(treated ~ high, data = d, method = "glm",
+ estimand = "ATE", stabilize = TRUE)
> print(tapply(Ws$weights, d$treated, sum))
0 1
30 70
> print(all.equal(unname(Ws$weights), d$sw))
[1] TRUE
stabilize = TRUE, the WeightIt package's weightit() returns weights that sum to 30 and 70 by arm, and all.equal() confirms they equal the hand-built sw.Stata: teffects ipw as a cross-check
teffects ipw (died) (treated high, logit), ate
. teffects ipw (died) (treated high, logit), ate
Iteration 0: EE criterion = 2.649e-17
Iteration 1: EE criterion = 1.126e-32
Treatment-effects estimation Number of obs = 100
Estimator : inverse-probability weights
Outcome model : weighted mean
Treatment model: logit
------------------------------------------------------------------------------
| Robust
died | Coefficient std. err. z P>|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treated |
(1 vs 0) | -.175 .1011342 -1.73 0.084 -.3732194 .0232194
-------------+----------------------------------------------------------------
POmean |
treated |
0 | .35 .0920598 3.80 0.000 .1695662 .5304338
------------------------------------------------------------------------------
R: glm_weightit() as a cross-check
W <- weightit(treated ~ high, data = d, method = "glm", estimand = "ATE")
fw <- glm_weightit(died ~ treated, data = d, weightit = W)
print(cbind(estimate = coef(fw), se = sqrt(diag(vcov(fw)))))
> W <- weightit(treated ~ high, data = d, method = "glm",
+ estimand = "ATE")
> fw <- glm_weightit(died ~ treated, data = d, weightit = W)
> print(cbind(estimate = coef(fw), se = sqrt(diag(vcov(fw)))))
estimate se
(Intercept) 0.350 0.09205976
treated -0.175 0.10113419
weightit() refits the logit propensity model without stabilisation, and glm_weightit() fits the weighted outcome model with a standard error that accounts for estimating the score. The treated row gives -0.175 and 0.101, as teffects ipw does in Stata.What to report
Three items let a reader check a weighted analysis:
- The weight type and the estimand, the precise quantity the analysis sets out to estimate, for example stabilised weights for the ATE.
- The sum of the weights in each arm beside the real arm sizes, so that 200 from 100 patients reads as a property of the weights. Stabilised weights should average close to 1; a mean far from 1 may point to a misspecified propensity model or to patients with almost no chance of one treatment [2].
- One balance table, with the SMD of each covariate before and after weighting.
For the reviewer in the opening scene, the answer is short: the 200 is the unstabilised pseudo-population, stabilised weights would show 100, and the adjustment for severity is the same.
Common misreadings and their fixes
-
"Stabilised weights adjust for confounding a second time."
Stabilised weights use the same propensity score as the unstabilised ones and add no information about confounders.
Fix: Stabilisation multiplies every weight in an arm by that arm's share of patients. It rescales the pseudo-population and leaves the balance that the unstabilised weights achieved unchanged.
-
"A weighted total of 200 from 100 patients means the data were duplicated."
Unstabilised ATE weights make each arm stand for the whole sample, so their sum is about twice the sample size by design.
Fix: Report the sum of the weights in each arm beside the real arm sizes.
-
"The totals are always exactly twice the sample size, or the sample size."
In the hand example they are exact only because each score equals the observed treated share in its covariate pattern. With a smoother propensity model the totals are usually close, not equal.
Fix: Read the totals as expected values and look into a large departure from them.
-
"For extra safety, apply both weights."
Multiplying the two weights divides by the probability of the treatment received twice, so the arms no longer share the covariate mix of the whole sample.
Fix: Use one weight per analysis; for a point treatment, either gives the same balance.
-
"A standard error from the weighted counts is fine with either weight."
With unstabilised weights it acts as if 200 patients had been observed: 0.061 in the simulated data, against a robust 0.106, or 0.101 once the estimated score is accounted for. With stabilised weights it is 0.098, closer, yet still blind to how unequal the weights are and to the estimated score.
Fix: Use a robust (sandwich) standard error, or one that also accounts for estimating the propensity score: teffects ipw in Stata or glm_weightit() in R.
What to do in your own analysis
- Name the estimand before choosing a weight; for the ATE of a point treatment, both weightings answer the same question.
- Put the sum of the weights in each arm next to the real arm sizes in the weighted baseline table.
- Check balance with the SMD of every covariate, using the same weights as the outcome analysis.
- Consider a robust standard error, or one that accounts for estimating the score (
teffects ipwin Stata,glm_weightit()in R), rather than one that reads the weights as counts. - If you cap the weights, state on which scale, because one cap cuts different patients under the two weightings; the extreme-weights part of this series covers truncation.
- For a treatment that changes over time, see the marginal structural models part of this series.
Glossary
- inverse probability of treatment weighting (IPTW) (การถ่วงน้ำหนักด้วยส่วนกลับของความน่าจะเป็นในการได้รับการรักษา)
- Weighting each patient by one over the probability of the treatment that patient received.
- propensity score (คะแนนแนวโน้มการได้รับการรักษา)
- The probability of treatment given the measured baseline covariates.
- pseudo-population (ประชากรเสมือน)
- The weighted population, in which each patient counts as many times as their weight.
- pseudo-population size (ขนาดของ pseudo-population)
- The sum of the weights: about twice the sample size for unstabilised ATE weights, about the sample size for stabilised ones.
- unstabilised weight (น้ำหนักแบบไม่ปรับเสถียร)
- One over the probability of the treatment a patient actually received.
- stabilised weight (น้ำหนักแบบปรับเสถียร)
- The unstabilised weight times the marginal probability of the treatment received.
- marginal probability of treatment (สัดส่วนผู้ได้รับการรักษาในภาพรวม)
- The share of all patients who received a given treatment.
- standardised mean difference (ผลต่างค่าเฉลี่ยมาตรฐาน)
- The difference between arms in a covariate's mean, divided by a pooled standard deviation.
- normalised weighted mean
- The sum of weight times value divided by the sum of the weights.
- frequency weight
- A weight read as a number of identical patients, so that its sum is reported as the sample size.
- robust (sandwich) standard error (ค่าคลาดเคลื่อนมาตรฐานแบบแซนด์วิช)
- A standard error that stays valid when the variance assumption is wrong, provided the mean model is right; with weights, it is read from the spread of the weighted data rather than from the weighted counts.
- saturated model
- A model with a separate mean for every combination of the variables it includes, such as one mean per arm for a binary treatment.
- estimand (ปริมาณเป้าหมายของการประมาณ)
- The precise quantity an analysis sets out to estimate, for example the ATE.
References
- Robins JM, Hernán MA, Brumback B. Marginal structural models and causal inference in epidemiology. Epidemiology. 2000;11(5):550-560. doi:10.1097/00001648-200009000-00011 https://doi.org/10.1097/00001648-200009000-00011
- Cole SR, Hernán MA. Constructing inverse probability weights for marginal structural models. Am J Epidemiol. 2008;168(6):656-664. doi:10.1093/aje/kwn164 https://doi.org/10.1093/aje/kwn164
- Hernán MA, Robins JM. Causal inference: what if [Internet]. Boca Raton: Chapman & Hall/CRC; 2020. https://miguelhernan.org/whatifbook
- Xu S, Ross C, Raebel MA, Shetterly S, Blanchette C, Smith D. Use of stabilized inverse propensity scores as weights to directly estimate relative risk and its confidence intervals. Value Health. 2010;13(2):273-277. doi:10.1111/j.1524-4733.2009.00671.x https://doi.org/10.1111/j.1524-4733.2009.00671.x
Key takeaways
- Unstabilised ATE weights build a pseudo-population of about twice the sample size and stabilised weights one of about the sample size; in the 100-patient hand example the totals are exactly 200 and 100 because each score equals the observed share in its severity level.
- Stabilisation multiplies every weight in an arm by that arm's share of patients, so it keeps the balance the unstabilised weights achieved: 50% High in both arms.
- With a binary treatment, normalised weighted means in each arm and no truncation or other change to the weights, both weightings give the same weighted risks and risk difference.
- What can differ is anything that reads the weights as counts, such as the number of patients software reports and a count-based standard error; the robust standard error of a treatment-only weighted model does not change with the scale.
- Report the weight type, the sum of the weights in each arm beside the real arm sizes, and one balance table.
Related in the wiki: [[extreme-propensity-weights-stabilization-truncation-trimming]] [[iptw-guide]] [[propensity-weighting-balance-check-and-model-revision]]