← Methods repository
concept

Missing Data, Trimming, and Winsorization in RWE

Practical handling of missing data (under MCAR/MAR/MNAR mechanisms) and of extreme propensity-score weights or outcome/cost outliers via trimming (excluding observations outside a propensity or weight threshold) and Winsorization (capping values at chosen percentiles), each of which changes the estimand and the target population it identifies.

Causal_Inference_Methodmissing-dataMCARMARMNARweight-trimmingwinsorizationpositivityoverlap
Methods reference only. Use primary source citations and local policy before applying this in a study protocol, regulatory submission, payer dossier, or clinical decision.

In plain language

When researchers weight a study to balance who got each treatment, one or two patients can accidentally end up with enormous weights — meaning that single person drives almost the entire result. Trimming drops those extreme patients from the analysis entirely (changing who the answer applies to), while winsorizing caps their weight at a chosen ceiling (keeping them in but limiting their pull). Both choices stabilize the estimate, but both also change what the study is actually measuring, so the rule must be written into the analysis plan before anyone looks at the data.

This entry covers two operations that practitioners routinely apply after covariate adjustment but that quietly alter what is being estimated: handling missing data and taming extreme inverse-probability weights or outlier values through trimming and Winsorization. They are bundled because they share one deep property — both are responses to regions of the data where the estimand is weakly or non-identified (limited overlap, structural absence of records), and both trade variance for a shift in the target population if applied carelessly.

Core conceptual distinction

Three mechanisms govern missingness and they are not testable from observed data alone. MCAR (missing completely at random): missingness is independent of observed and unobserved values — essentially never true in claims or EHR. MAR (missing at random): missingness depends only on observed data, so it can be corrected by multiple imputation or inverse-probability-of-observation weighting conditional on the observed covariates. MNAR (missing not at random): missingness depends on the unobserved value itself — the dominant reality in RWE (a lab is missing because the clinician judged it unnecessary; future claims are absent because the patient died or disenrolled). For trimming and Winsorization, the distinction is between trimming (removing units whose propensity score or weight falls outside a threshold, i.e., dropping the unit entirely) and Winsorization/truncation (retaining the unit but replacing its extreme weight or value with a capped value). Trimming targets positivity/overlap; it deliberately redefines the population to the region of common support, so the estimand becomes a trimmed ATE/ATT on that subpopulation, not the original-cohort ATE. Winsorization keeps every unit but biases the weighted estimator toward the bulk, reducing variance at the cost of residual confounding from the down-weighted tails. The decisive point regulators raise: trimming and Winsorization change the estimand, not merely the estimator, and that change must be stated in the protocol/SAP, not buried in a footnote.

Pros, cons, and trade-offs

(specific and comparative). - Trimming/Winsorization of weights vs. doing nothing (raw IPTW): Raw IPTW with a handful of weights >50 is high-variance, non-robust, and effectively non-positive — one patient can swing the ATE. Trimming/Winsorization stabilizes the estimate dramatically. Cost: bias relative to the full-cohort estimand and sensitivity to the (arbitrary) rule. Prefer when a small tail dominates the effective sample size and the question tolerates a restricted population. - Symmetric PS trimming (Stürmer) vs. data-driven Crump trimming: Stürmer's fixed-percentile trimming of the PS tails is transparent and easy to pre-specify but arbitrary; Crump's optimal-overlap rule chooses the threshold to minimize asymptotic variance subject to retaining support, which is more principled but less interpretable and harder to communicate to a non- statistician reviewer. Prefer Crump (or overlap weights) when positivity is the core threat and you can defend the machinery; prefer fixed symmetric trimming for routine pharmacoepi with mild non-overlap. - Trimming/Winsorization vs. overlap (ATO) weights: Overlap weights (Li, Morgan, Zaslavsky) achieve exact balance and bounded weights by construction, sidestepping the need for an ad hoc cap — but they change the estimand to the overlap population (patients in clinical equipoise). Prefer overlap weights when the equipoise population is itself the policy- relevant target; prefer explicit trimming when stakeholders require the ATE/ATT on a nameable population. - Winsorization of cost outliers vs. two-part / GLM gamma models: Capping a $2M CAR-T claim at the 99th percentile is crude; a gamma or two-part model accommodates the skew without discarding magnitude. Prefer the model when the tail is real signal (oncology HCRU); prefer Winsorization only as a transparent, pre-specified sensitivity layer. - Complete-case vs. multiple imputation for missing covariates: Complete-case analysis is unbiased only under MCAR (and sometimes under MAR conditional on the outcome model) and wastes data; MI under MAR is principled but invalid if the mechanism is MNAR. Neither recovers structurally absent post-death/post-disenrollment data — that requires re-defining the estimand (e.g., a composite or a while-alive contrast).

When to use

(decision rules). Trimming/Winsorization of weights: whenever IPTW or PS-weighted analyses produce extreme weights (max weight ≫ 20, or a few units holding a large share of total weight / collapsing the effective sample size). Outcome/cost Winsorization: heavy-tailed HCRU or cost endpoints where one or two catastrophic claims dominate the mean. Missing-data methods: any analysis with incomplete baseline covariates (MI under a defensible MAR model), informative observation (IPCW / inverse-probability-of- observation weighting), or partially missing outcomes. The governing rule: pre-specify the rule and at least two alternatives, and report what fraction of units and person-time each rule removes or caps.

When NOT to use — and when it is actively misleading or dangerous

(decision rules). - Trimming to "fix" a non-overlap problem that is really confounding-by-indication. If one drug is reserved for renally- impaired patients, the separated PS distribution is a design failure; trimming the non-overlapping tail silently changes the population to one where the comparison may no longer be the question of interest, and reviewers will read the residual estimand as the same one promised in the objectives. Diagnose with the PS-overlap plot and clinical review first. - Winsorizing real outcome signal. In oncology cost studies, the high-cost tail (CAR-T, prolonged ICU) is the phenomenon; capping it can reverse a cost-effectiveness conclusion. Capping outcomes is dangerous whenever the tail is the decision-driver, not noise. - Imputing structurally missing data. Standard MI on observed covariates cannot recover information that is missing because the patient died or disenrolled — that is MNAR by construction. Imputing post-death cost as if it were MAR fabricates person-time and biases PPPM/PMPM. Handle with a while-alive estimand, competing-risks framing, or explicit MNAR sensitivity (pattern-mixture / delta-adjustment), never naive MI. - Imputing time-varying confounders without respecting time order. Imputing a confounder using post-treatment values can open a collider path and induce bias worse than the missingness it cured. - Reporting a single trimmed result with no sensitivity layer. A point estimate that flips under 1% vs. 5% Winsorization is not a finding; it is an artifact. Presenting only the favorable cut is the classic HTA/regulatory red flag.

Data-source operational depth

(claims vs. EHR vs. registry vs. linked). - Claims (FFS vs. MA): "No claim observed" conflates true absence with unobserved care. MA-only person-time lacks the fee-for-service encounter and pharmacy claims that FFS Parts A/B/D generate, so a patient who looks event-free or treatment-naïve may simply be invisible — restrict to fully-observable (A/B/D or commercial medical+pharmacy) person-time rather than imputing the gap. Disenrollment and death are MNAR by design: future records are structurally absent, so censor at the enrollment-span end and never carry costs/outcomes past it. Differential competing risks by exposure in elderly claims (a frailer arm dies sooner, producing systematically shorter, more "complete-looking" follow-up) interact with both weight tails and missingness — the frail tail often carries the largest weights and the heaviest truncated cost. - EHR: Visit-driven capture makes completeness a function of engagement — sicker/more-engaged patients have richer records, so missing labs are typically MNAR. Use missingness indicators or model the visit/observation process (IPCW) rather than assuming MAR. Immortal time in procedure studies can masquerade as missingness: a window with no records before a procedure may reflect care delivered out-of-system, not absence of disease — confirm with linkage before treating it as a clean lookback. - Registry: Lower missingness on adjudicated key endpoints, but utilization, costs, and PROs are often incomplete; use the registry as a validation substrate for claims-based imputation or trimming rules rather than as the costing source. - Linked claims–EHR–vital records: The ideal substrate — EHR severity to enrich the PS (reducing extreme weights at the source) plus claims completeness and a death index to terminate person-time correctly — but linkage selects the linkable subset and creates date discrepancies (order vs. fill vs. service) that must be reconciled before any windowing.

Worked claims example

Question: 12-month all-cause cost (PPPM) of an injectable specialty drug vs. an active comparator among adults with continuous commercial + Medicare FFS A/B/D enrollment. (1) Cohort: new initiators, 365-day continuous, FFS-observable enrollment lookback (exclude MA-only person-time so "no prior fill" is real, not missing). (2) Confounding: high-dimensional PS, then stabilized IPTW. Inspect the weight distribution — suppose `max(weight)=84` and the top 0.5% of units hold 22% of total weight (effective sample size collapsed from 9,800 to ~4,100). (3) Pre-specified primary rule: symmetric PS trimming at the 1st/99th percentiles of the treated-arm PS (Stürmer), recomputing weights on the trimmed cohort; report that 1.9% of units and 2.4% of person-time are dropped, and that the estimand is now the trimmed-population ATT. (4) Outcome handling: per-member-per-month cost is right-censored at death and disenrollment (no post-exit imputation — MNAR by design); the cost distribution is Winsorized at the 99th percentile as a sensitivity layer only, with the gamma two-part model as primary. (5) Sensitivity grid: {no trim, 1/99 trim, Crump optimal, overlap weights} × {no Winsor, 99th- pct Winsor} reported side by side, with ESS and the share of trimmed person-time for each cell. The conclusion is reported as robust only if the sign and rough magnitude of the cost difference survive the grid; cells that diverge are flagged as the locus of residual positivity/MNAR risk rather than averaged away.

Interpreting the output

Consider the six-patient IPTW cost comparison: the raw weighted cost difference between treated and comparator arms is approximately $32,740. After symmetric PS trimming at the 1st/99th percentiles (dropping the highest-weight units), the trimmed difference is approximately $10,000. Winsorizing costs at the 99th percentile as a sensitivity layer yields a difference of approximately $29,640.

(1) Formal statistical interpretation. Trimming and Winsorization both change the estimand, not just the estimator. Trimming removes the highest-weight units from the cohort entirely; the resulting estimate applies to a trimmed target population — the ATT among patients with PS within the 1st–99th percentile range — not the full treated population. Winsorization caps extreme cost values rather than removing patients; the estimand remains the ATT for all treated patients, but extreme costs are replaced by the cap value. Neither approach is bias-free: both sacrifice some external validity for robustness to extreme observations. The wide divergence between the trimmed ($10,000) and winsorized ($29,640) estimates signals that a small number of high-cost, high-weight patients drive a large share of the raw difference ($32,740), and that the conclusion is sensitive to how those observations are handled.

(2) Practical interpretation for a decision-maker. The "true" cost difference lies somewhere in the range defined by the sensitivity grid, not at any single number. The primary estimate from the pre-specified method (here, PS trimming) should be the headline, with the winsorized and untrimmed estimates reported as sensitivity. Divergence across cells flags positivity and influence concerns that warrant further investigation before a formulary or coverage decision.

Worked example

Scenario

A pharmacoepidemiology team is estimating the average difference in one-year total cost between patients on a specialty drug (treated, T=1) and an active comparator (T=0). They fit a logistic model to get a propensity score for each patient, then compute stabilized inverse-probability weights. The marginal probability of being treated is 3/6 = 0.50, so the stabilized weight for each treated patient is 0.50 divided by their propensity score, and for each comparator patient is 0.50 divided by (1 minus their propensity score). Inspecting the weights reveals two patients — one in each arm — with a propensity score near the boundary of their group, producing a weight of 10.0. Those two patients together hold most of the total weight in the analysis. The team pre-specified a rule: compare trimming (drop patients with weight above 5.0) versus winsorizing (cap weights above 5.0 at 5.0), and report both alongside the raw result.

Dataset

Analysis-ready table: one row per patient with propensity score, stabilized weight, and one-year total cost in thousands of dollars.

patient_idarmpropensity_scorestabilized_weighttotal_cost_k
P01treated0.80.62518
P02treated0.41.2522
P03treated0.0510.045
P04comparator0.20.62512
P05comparator0.61.2510
P06comparator0.9510.08

Steps

  • Compute each stabilized weight: for treated patients, divide 0.50 by the propensity score; for comparator patients, divide 0.50 by (1 minus the propensity score). P03 has propensity score 0.05, meaning the model thought it very unlikely this patient would be treated — yet they were, so the weight is 0.50 / 0.05 = 10.0. P06 has propensity score 0.95, meaning the model expected them to be treated but they were not, so 0.50 / (1 - 0.95) = 10.0.

  • RAW weighted mean for the treated arm: multiply each patient's cost by their weight, sum across treated patients, then divide by the sum of treated weights. Numerator = (0.625 x 18) + (1.25 x 22) + (10.0 x 45) = 11.25 + 27.50 + 450.00 = 488.75. Denominator = 0.625 + 1.25 + 10.0 = 11.875. Weighted mean treated = 488.75 / 11.875 = 41.16 ($41,160).

  • RAW weighted mean for the comparator arm: numerator = (0.625 x 12) + (1.25 x 10) + (10.0 x 8) = 7.50 + 12.50 + 80.00 = 100.00. Denominator = 0.625 + 1.25 + 10.0 = 11.875. Weighted mean comparator = 100.00 / 11.875 = 8.42 ($8,420). Raw weighted cost difference = 41.16 - 8.42 = 32.74 ($32,740). Notice that P03 and P06 each contribute roughly 84 percent of their arm's total weight, so P03's cost of $45k is essentially driving the treated-arm estimate on its own.

  • TRIMMING: drop P03 and P06 (both have weight 10.0, which exceeds the 5.0 threshold). The marginal treatment probability in the retained four patients is still 2/4 = 0.50, so stabilized weights are recomputed identically: P01 = 0.625, P02 = 1.25, P04 = 0.625, P05 = 1.25. Weighted mean treated: numerator = (0.625 x 18) + (1.25 x 22) = 11.25 + 27.50 = 38.75; denominator = 0.625 + 1.25 = 1.875; result = 38.75 / 1.875 ≈ 20.67 ($20,670). Weighted mean comparator: numerator = (0.625 x 12) + (1.25 x 10) = 7.50 + 12.50 = 20.00; denominator = 1.875; result = 20.00 / 1.875 ≈ 10.67 ($10,670). Trimmed weighted cost difference = 20.67 - 10.67 = 10.00 ($10,000).

  • WINSORIZING: keep all six patients but cap any weight above 5.0 at exactly 5.0. P03 weight becomes 5.0 (was 10.0); P06 weight becomes 5.0 (was 10.0). Weighted mean treated: numerator = (0.625 x 18) + (1.25 x 22) + (5.0 x 45) = 11.25 + 27.50 + 225.00 = 263.75; denominator = 0.625 + 1.25 + 5.0 = 6.875; result = 263.75 / 6.875 ≈ 38.36 ($38,360). Weighted mean comparator: numerator = (0.625 x 12) + (1.25 x 10) + (5.0 x 8) = 7.50 + 12.50 + 40.00 = 60.00; denominator = 6.875; result = 60.00 / 6.875 ≈ 8.73 ($8,730). Winsorized weighted cost difference = 38.36 - 8.73 = 29.64 ($29,640).

Result

Raw (no adjustment): $32,740 — dominated by P03, a single treated patient whose low propensity score (0.05) gave them a weight of 10.0. Trimmed (drop weights above 5.0, cap at 1st/99th in practice): $10,000, on the four-patient overlap cohort only — a population defined by having a propensity score that plausibly appears in both arms. Winsorized (cap weights at 5.0): $29,640, retaining all six patients but limiting any single patient's influence. The three estimates span a $22,740 range, illustrating why the rule must be pre-specified: the analyst who looks at the numbers first can always choose the most favorable one. The team reports all three as the sensitivity grid required by their SAP, with the trimmed result as primary because it targets a clearly definable overlap population and the two extreme patients were clinically implausible comparisons.

Runnable example

python implementation

Operationalize stabilized IPTW with diagnostics, then apply (a) symmetric PS trimming and (b) percentile Winsorization, reporting effective sample size for each rule. Required input (one row per person, already cleaned): df : person_id, treat (1/0 study vs...

import numpy as np
import pandas as pd

def stabilized_iptw(df: pd.DataFrame) -> pd.Series:
    """Stabilized inverse-probability-of-treatment weights (Austin & Stuart 2015)."""
    p_treat = df["treat"].mean()
    w = np.where(df["treat"] == 1, p_treat / df["ps"], (1 - p_treat) / (1 - df["ps"]))
    return pd.Series(w, index=df.index)

def effective_sample_size(w: pd.Series) -> float:
    """Kish ESS: collapses as a few weights dominate."""
    return (w.sum() ** 2) / (w ** 2).sum()

def symmetric_ps_trim(df: pd.DataFrame, lo: float = 0.01, hi: float = 0.99) -> pd.DataFrame:
    """Stürmer-style trim on percentiles of the TREATED-arm PS, then keep the overlap region."""
    ps_t = df.loc[df["treat"] == 1, "ps"]
    lo_cut, hi_cut = ps_t.quantile(lo), ps_t.quantile(hi)
    return df[(df["ps"] >= lo_cut) & (df["ps"] <= hi_cut)].copy()

def winsorize(s: pd.Series, lo: float = 0.01, hi: float = 0.99) -> pd.Series:
    return s.clip(lower=s.quantile(lo), upper=s.quantile(hi))

def weighted_mean_diff(df: pd.DataFrame, w: pd.Series) -> float:
    """Weighted ATE-style contrast of y between arms (e.g., cost difference)."""
    t = df["treat"] == 1
    m1 = np.average(df.loc[t, "y"], weights=w[t])
    m0 = np.average(df.loc[~t, "y"], weights=w[~t])
    return m1 - m0

# --- Sensitivity grid: no-trim vs symmetric trim, crossed with no-Winsor vs 99th-pct Winsor ---
rows = []
for trim_name, d in [("no_trim", df), ("trim_1_99", symmetric_ps_trim(df))]:
    w = stabilized_iptw(d)                       # recompute weights AFTER trimming
    for win_name, yy in [("no_winsor", d["y"]), ("winsor_99", winsorize(d["y"]))]:
        dd = d.assign(y=yy.values)
        rows.append({
            "trim": trim_name, "winsor": win_name,
            "n": len(dd), "ess": round(effective_sample_size(w), 1),
            "pct_trimmed": round(100 * (1 - len(dd) / len(df)), 2),
            "max_weight": round(float(w.max()), 1),
            "effect": round(weighted_mean_diff(dd, w), 2),
        })
print(pd.DataFrame(rows))
r implementation

Same sensitivity engine in R: stabilized IPTW with effective sample size, symmetric PS trimming (recompute weights after trimming), and percentile Winsorization, reported as a grid. Input analysis-ready data frame: df : person_id, treat (1/0), ps (0<ps<1),...

library(data.table)

stabilized_iptw <- function(d) {
  p <- mean(d$treat)
  ifelse(d$treat == 1L, p / d$ps, (1 - p) / (1 - d$ps))
}
ess <- function(w) sum(w)^2 / sum(w^2)                 # Kish effective sample size

symmetric_ps_trim <- function(d, lo = 0.01, hi = 0.99) {
  q <- quantile(d$ps[d$treat == 1L], probs = c(lo, hi))
  d[d$ps >= q[1] & d$ps <= q[2]]
}
winsorize <- function(x, lo = 0.01, hi = 0.99) {
  q <- quantile(x, probs = c(lo, hi)); pmin(pmax(x, q[1]), q[2])
}
wmean_diff <- function(d, w) {
  t <- d$treat == 1L
  weighted.mean(d$y[t], w[t]) - weighted.mean(d$y[!t], w[!t])
}

build_grid <- function(df) {
  setDT(df)
  out <- list()
  for (tn in c("no_trim", "trim_1_99")) {
    d <- if (tn == "no_trim") copy(df) else symmetric_ps_trim(df)
    w <- stabilized_iptw(d)                            # recompute AFTER trimming
    for (wn in c("no_winsor", "winsor_99")) {
      yy <- if (wn == "no_winsor") d$y else winsorize(d$y)
      dd <- copy(d)[, y := yy]
      out[[paste(tn, wn)]] <- data.table(
        trim = tn, winsor = wn, n = nrow(dd),
        ess = round(ess(w), 1),
        pct_trimmed = round(100 * (1 - nrow(dd) / nrow(df)), 2),
        max_weight = round(max(w), 1),
        effect = round(wmean_diff(dd, w), 2)
      )
    }
  }
  rbindlist(out)
}
print(build_grid(df))