Choosing a DiD estimand and estimator
for heavy-tailed outcomes

Interactive companion to Winkler, Hotz-Behofsits, Wlömert, Papies & Liaukonytė (2026) and its Practitioner’s Companion.

1 · Estimand — which effect are we trying to learn?

This tab shows why the estimand has to come before the estimator. The same DiD design can answer different economic questions depending on whether the target is the typical unit, the population total, or a level effect. With heavy-tailed outcomes, these estimands can differ sharply, and sometimes even imply effects with opposite signs. Use the sliders below to change the data-generating process and see how the true estimands and the estimated coefficients move.

Typical-unit %
ΔΔ E[log Y]
Percent change in outcome for a representative unit, where each unit contributes equally, regardless of its size.
“Did per-unit usage rise by ~5%?”
Population-total %
ΔΔ log E[Y]
Percent change in the mean of Y — the economically relevant margin under heavy-tailed outcomes.
“Did total revenue rise by ~5%?”
Level effect
ΔΔ E[Y]
Absolute change in units of Y; the average change per unit equals the change in the mean.
“Did this add ~$2M of revenue?”
2 · DGP — Data Generating Process

The simulation creates a matched treated–control panel that simplifies the setting for educational purposes, isolating three ingredients: outcome concentration (the share of outcomes in the top decile), heterogeneous treatment effects (separate effects for the viral head and the long tail), and treatment-induced changes in variance (the proportional change in treated-unit volatility). Treated and control units have the same pre-period baseline levels by construction, so differences across estimators come from the estimand they target and how they weight the outcome distribution. For the case where treated and control units start at different levels, see the Levels-OLS bias map tab →

Presets:
Where the population total comes from
(pre-treatment distribution)
True estimand values
True typical-unit % effect
equal-weighted percent change across units
True population-total % effect
percent change in total treated outcomes
Realized top-decile share
pre-treatment top-decile share in this simulation draw
3 · Estimator — how to estimate it

Each estimator targets a different estimand and applies a different implicit or explicit weighting scheme. Log(1+Y) OLS weights observations equally, so the many low-volume units dominate the estimate. Weighted log OLS uses explicit pre-period outcome shares. PPML weights through the predicted mean and is the natural estimator for the population-total percent effect. Levels OLS targets an effect in units of the outcome. When treatment also changes outcome variance, log-transformed specifications can introduce a bias and even reverse sign. Setting Δ variance to zero isolates the role of weighting and estimand choice.

Estimated parameters relative to the true DGP estimands
Red lines show true DGP effects: solid for the population-total and dashed for the typical-unit. The black line is the point estimate from the corresponding regression, the box spans ±1 standard error (SE), and the whiskers span the 95% confidence interval (CI), clustered by matched pair. Levels OLS is reported as a percent of the treated pre-period mean, with its SE and CI scaled by the delta method.
1 · DGP — Data Generating Process

The DGP carries over from Tab 1 — the treatment effects, the variance shift, the outcome concentration, and the panel structure are whatever the Tab 1 sliders currently say. This tab layers two additional choices on top, defined with their sliders below; their product is the levels-OLS bias under the multiplicative trend assumed here (an additive trend would bias PPML and log-based estimators instead).

bbaseline levels gap
treated units start higher or lower than controls; matching on levels is switched off
gcommon growth
all units share the same proportional growth rate per period
b  ×  g
gap factor b/(1+b)
×
trend-scale factor
=
predicted levels-OLS bias
True population-total % effect
from Tab 1’s DGP; the bias adds on top of this
Bias component of levels OLS (% of counterfactual mean)
Click or drag anywhere on the map to move ✕ — the sliders follow, and the simulated check below re-runs when you release. The white bands are zero bias: the entire b = 0 column and g = 0 row.
2 · Building intuition — why the two parameters multiply
Intuition for how the bias arises. Treatment effect set = 0 for instructional purposes.
Move the b and g sliders (or drag the ✕) to see how the bias changes.
Both paths assume the treatment has no effect. In levels they diverge, so a levels DiD attributes the shaded area to the treatment effect. The shaded gap, as a fraction of the treated line’s height at the final period, equals the predicted levels-OLS bias in the box above. In logs the same paths are parallel, so log and PPML are unaffected. Move the b and g sliders (or drag the ✕) — the area changes, and vanishes whenever either is zero. This is a simplified case; the simulation below adds your Tab 1 treatment effect on top.

Under common proportional growth g, absolute changes scale with baseline levels: a treated unit (1+b) times larger mechanically adds (1+b) times more under the no-treatment counterfactual, and a levels DiD attributes the difference to treatment. With balanced windows the bias is exactly [b/(1+b)] × [(Ḡpost − Ḡpre)/Ḡpost], with Ḡ the window-average growth factor; in this 10+10 design the trend-scale factor is 1 − (1+g)−10. Either parameter at zero closes the channel: matching on baseline levels shrinks b toward zero, and no common growth means no trend-scale factor. The bias is separate from the treatment effect and adds to the scaled levels estimate. Proportional estimators (PPML, log specifications) impose proportional counterfactual trends and carry no bias from this channel. See Roth & Sant’Anna (2023) on when parallel trends is sensitive to functional form.

3 · Estimator behavior
Simulated check at ✕, with Tab 1’s treatment effects
The solid red line is the true population-total estimand from Tab 1’s DGP; the dark dashed line is the levels-OLS estimate from this simulated dataset. The red shaded band shows the realized bias, the distance between the truth and the estimate. The equation card above gives the predicted bias, so any difference between the two reflects sampling noise. The black line is the point estimate from the corresponding regression, the box spans ±1 standard error (SE), and the whiskers show the 95% confidence interval (CI), clustered by matched pair. Levels OLS is reported as a percent of the counterfactual mean, with the SE and CI scaled by the delta method.
Related literature
Econometrica · “When Is Parallel Trends Sensitive to Functional Form?”
Parallel trends can hold in levels but fail in logs, or the reverse — so the estimate depends on the chosen transformation. The premise behind the Levels-OLS bias map.
Review of Economics and Statistics · “The Log of Gravity”
When the error variance depends on the mean, log-linear OLS is biased for percent effects on the mean; PPML is not. Why PPML anchors the population-total % here.
Quarterly Journal of Economics · “Logs with Zeros? Some Problems and Solutions”
With zeros, log(1+Y) effects depend on the units of Y and have no clean percent interpretation. Here zeros are rare and treatment barely moves them, so log(1+Y) remains a practical typical-unit summary.
Journal of Econometric Methods · “Dif-in-Dif Estimators of Multiplicative Treatment Effects”
If treatment changes the outcome’s variance, log-transformed DiD picks up the variance change on top of the mean effect — the Δ-variance slider channel on Tab 1.
Working paper · “On the Perils of Log Dependent Variables and Difference-in-Differences”
With baseline level differences between groups and a common trend, log DiD can flip the sign of a levels DiD; gives the condition for the flip. The b × g logic of Tab 2, in log form.
The Econometrics Journal · “Simple Approaches to Nonlinear Difference-in-Differences with Panel Data”
Poisson regression with two-way fixed effects identifies the treatment effect under an exponential-mean model. The PPML specification used throughout this companion.
1 · What these scripts replicate

The applet generates the data; the scripts estimate. The buttons below export the simulated panel for the current slider settings as a CSV file, and each code below (Python, R, and Julia) reads that file, runs the four specifications from Tabs 1 and 2, and prints estimates with confidence intervals. A reference panel for the default settings is bundled in the zip, and every script includes reference values for it, so you can verify your setup before swapping in your own export.

2 · Download
3 · Code
did_lab_replication.py (requires numpy, pandas, scipy, pyfixest — click to view)
""" DiD estimation script (Python), companion to Winkler et al. (2026): https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6143552 Estimates the companion's four specifications on the exported panel, all with unit and period fixed effects and one Treat x Post regressor (D): levels OLS reported as % of the counterfactual mean (treated pre-mean x control post/pre ratio); delta-method CI log(1+Y) OLS weighted log(1+Y) OLS (pre-period mean outcome weights) PPML reported as exp(b) - 1 Input: did_lab_panel.csv. Download it from the applet ("Panel (CSV)" buttons on the Replication tab; any slider settings) or use the bundled copy (default settings, seed 4). Reference values for the bundled panel: n = 32,000 sum(y) = 23,551,023 levels -0.03861113 [-4.4695%, -3.2527%] log(1+Y) +0.00760246 [-0.3962%, +1.9167%] w-log -0.03704146 [-4.3714%, -3.0369%] PPML -0.03859994 [-4.4669%, -3.2492%] (package small-sample conventions can shift CI ends in the 3rd decimal) True estimand values are properties of the DGP, not recoverable from the CSV; read them off the applet's Tab 1 cards. Requires: numpy, pandas, scipy, pyfixest (pip install numpy pandas scipy pyfixest) """ import numpy as np import pandas as pd import pyfixest as pf from pyfixest.estimation import demean from scipy.stats import t as t_dist df = pd.read_csv("did_lab_panel.csv") print(f"rows = {len(df)} sum(y) = {int(df.y.sum())}") # scale for the levels estimate: treated pre-mean x control post/pre ratio mean_treat_pre = df.loc[(df.treat == 1) & (df.post == 0), "y"].mean() mean_ctrl_pre = df.loc[(df.treat == 0) & (df.post == 0), "y"].mean() mean_ctrl_post = df.loc[(df.treat == 0) & (df.post == 1), "y"].mean() scale = mean_treat_pre * mean_ctrl_post / mean_ctrl_pre n_pairs = df["pair"].nunique() tcrit = t_dist.ppf(0.975, df=n_pairs - 1) df["log_y"] = np.log1p(df.y) w_pre = df[df.post == 0].groupby("unit")["y"].mean().rename("w_pre") df = df.merge(w_pre, on="unit") # SEs: CRV1 clustered by matched pair. With 1:1 matching WITHOUT replacement # (this panel), each unit belongs to exactly one pair, so two-way unit-and-pair # clustering is identical to pair clustering. If you match WITH replacement # (controls reused across pairs), units no longer nest in pairs, so cluster # two-way instead: vcov={"CRV1": "unit+pair"}. V = {"CRV1": "pair"} m_levels = pf.feols("y ~ D | unit + period", data=df, vcov=V) m_log = pf.feols("log_y ~ D | unit + period", data=df, vcov=V) m_wlog = pf.feols("log_y ~ D | unit + period", data=df[df.w_pre > 0], weights="w_pre", vcov=V) # pyfixest requires strictly # positive weights, so drop zero-weight units (they carry # no information either way) m_ppml = pf.fepois("y ~ D | unit + period", data=df, vcov=V) # (fepois may note that all-zero units were dropped for separation; this is expected) b = lambda m: m.coef()["D"] se = lambda m: m.tidy()["Std. Error"]["D"] # delta-method CI for the scaled levels effect theta = b/scale: the scale is # estimated from the same sample and strongly negatively correlated with b, # so a fixed-scale CI would be ~2.5x too wide. D_dem, _ = demean(df[["D"]].to_numpy(float), df[["unit", "period"]].to_numpy(), np.ones(len(df))) D_dem = D_dem[:, 0] denom = D_dem @ D_dem resid_lv = m_levels.resid() score_pair = np.bincount(df["pair"], weights=D_dem * resid_lv, minlength=n_pairs) pair_mean = lambda rows: df[rows].groupby("pair")["y"].mean().to_numpy() pm_treat_pre = pair_mean((df.treat == 1) & (df.post == 0)) pm_ctrl_pre = pair_mean((df.treat == 0) & (df.post == 0)) pm_ctrl_post = pair_mean((df.treat == 0) & (df.post == 1)) theta = b(m_levels) / scale psi = (score_pair / (denom * scale) - (theta / n_pairs) * ( (pm_treat_pre - pm_treat_pre.mean()) / pm_treat_pre.mean() - (pm_ctrl_pre - pm_ctrl_pre.mean()) / pm_ctrl_pre.mean() + (pm_ctrl_post - pm_ctrl_post.mean()) / pm_ctrl_post.mean())) se_theta = np.sqrt((psi**2).sum()) * n_pairs / (n_pairs - 1) ci = lambda est, s: f"[{100*(est - tcrit*s):+.4f}%, {100*(est + tcrit*s):+.4f}%]" print(f"levels {theta:+.8f} {ci(theta, se_theta)} (delta method)") print(f"log1y {b(m_log):+.8f} {ci(b(m_log), se(m_log))}") print(f"wlog {b(m_wlog):+.8f} {ci(b(m_wlog), se(m_wlog))}") d = b(m_ppml) print(f"ppml {np.exp(d)-1:+.8f} [{100*(np.exp(d - tcrit*se(m_ppml))-1):+.4f}%, " f"{100*(np.exp(d + tcrit*se(m_ppml))-1):+.4f}%]") # Standard regression table (raw coefficients). # NOTE: levels is in outcome units here (not the scaled % above), PPML is the # log coefficient (not exp(b)-1), and SEs use pyfixest's default convention. print() pf.etable([m_levels, m_log, m_wlog, m_ppml], type="md")
did_lab_replication.R (requires fixest — click to view)
# ============================================================================== # DiD estimation script (R), companion to Winkler et al. (2026): # https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6143552 # # Estimates the companion's four specifications on the exported panel, all with unit # and period fixed effects and one Treat x Post regressor (D): # levels OLS reported as % of the counterfactual mean # (treated pre-mean x control post/pre ratio); delta-method CI # log(1+Y) OLS # weighted log(1+Y) OLS (pre-period mean outcome weights) # PPML reported as exp(b) - 1 # # Input: did_lab_panel.csv. Download it from the applet ("Panel (CSV)" buttons # on the Replication tab; any slider settings) or use the bundled copy # (default settings, seed 4). Reference values for the bundled panel: # n = 32,000 sum(y) = 23,551,023 # levels -0.03861113 [-4.4695%, -3.2527%] log(1+Y) +0.00760246 [-0.3962%, +1.9167%] # w-log -0.03704146 [-4.3714%, -3.0369%] PPML -0.03859994 [-4.4669%, -3.2492%] # (package small-sample conventions can shift CI ends in the 3rd decimal) # True estimand values are properties of the DGP, not recoverable from the CSV; # read them off the applet's Tab 1 cards. # # Requires: fixest (install.packages("fixest")) # ============================================================================== library(fixest) df <- read.csv("did_lab_panel.csv") cat(sprintf("rows = %d sum(y) = %.0f\n", nrow(df), sum(as.numeric(df$y)))) # scale for the levels estimate: treated pre-mean x control post/pre ratio mean_treat_pre <- mean(df$y[df$treat == 1 & df$post == 0]) mean_ctrl_pre <- mean(df$y[df$treat == 0 & df$post == 0]) mean_ctrl_post <- mean(df$y[df$treat == 0 & df$post == 1]) scale <- mean_treat_pre * mean_ctrl_post / mean_ctrl_pre n_pairs <- length(unique(df$pair)) tcrit <- qt(0.975, df = n_pairs - 1) df$log_y <- log1p(df$y) w_pre <- tapply(df$y[df$post == 0], df$unit[df$post == 0], mean) df$w_pre <- as.numeric(w_pre[as.character(df$unit)]) # SEs: CRV1 clustered by matched pair. With 1:1 matching WITHOUT replacement # (this panel), each unit belongs to exactly one pair, so two-way unit-and-pair # clustering is identical to pair clustering. If you match WITH replacement # (controls reused across pairs), units no longer nest in pairs, so cluster # two-way instead: cluster = ~unit + pair. m_levels <- feols(y ~ D | unit + period, data = df, cluster = ~pair) m_log <- feols(log_y ~ D | unit + period, data = df, cluster = ~pair) m_wlog <- feols(log_y ~ D | unit + period, data = df[df$w_pre > 0, ], weights = ~w_pre, cluster = ~pair) # zero-weight units drop out anyway m_ppml <- fepois(y ~ D | unit + period, data = df, cluster = ~pair) # delta-method CI for the scaled levels effect theta = b/scale: the scale is # estimated from the same sample and strongly negatively correlated with b, # so a fixed-scale CI would be ~2.5x too wide. D_dem <- demean(X = data.frame(D = df$D), f = df[, c("unit", "period")], tol = 1e-8)[, 1] resid_lv <- resid(m_levels) denom <- sum(D_dem^2) score_pair <- tapply(D_dem * resid_lv, df$pair, sum) pm_treat_pre <- tapply(df$y[df$treat == 1 & df$post == 0], df$pair[df$treat == 1 & df$post == 0], mean) pm_ctrl_pre <- tapply(df$y[df$treat == 0 & df$post == 0], df$pair[df$treat == 0 & df$post == 0], mean) pm_ctrl_post <- tapply(df$y[df$treat == 0 & df$post == 1], df$pair[df$treat == 0 & df$post == 1], mean) theta <- unname(coef(m_levels)["D"]) / scale psi <- score_pair / (denom * scale) - (theta / n_pairs) * ((pm_treat_pre - mean(pm_treat_pre)) / mean(pm_treat_pre) - (pm_ctrl_pre - mean(pm_ctrl_pre)) / mean(pm_ctrl_pre) + (pm_ctrl_post - mean(pm_ctrl_post)) / mean(pm_ctrl_post)) se_theta <- sqrt(sum(psi^2)) * n_pairs / (n_pairs - 1) ci <- function(est, s) sprintf("[%+.4f%%, %+.4f%%]", 100*(est - tcrit*s), 100*(est + tcrit*s)) cat(sprintf("levels %+.8f %s (delta method)\n", theta, ci(theta, se_theta))) cat(sprintf("log1y %+.8f %s\n", coef(m_log)["D"], ci(coef(m_log)["D"], se(m_log)["D"]))) cat(sprintf("wlog %+.8f %s\n", coef(m_wlog)["D"], ci(coef(m_wlog)["D"], se(m_wlog)["D"]))) d <- coef(m_ppml)["D"] sd_ <- se(m_ppml)["D"] cat(sprintf("ppml %+.8f [%+.4f%%, %+.4f%%]\n", exp(d) - 1, 100*(exp(d - tcrit*sd_) - 1), 100*(exp(d + tcrit*sd_) - 1))) # Standard regression table (raw coefficients). # NOTE: levels is in outcome units here (not the scaled % above), PPML is the # log coefficient (not exp(b)-1), and SEs use fixest's default convention. cat("\n") etable(m_levels, m_log, m_wlog, m_ppml)
did_lab_replication.jl (requires DataFrames, FixedEffectModels, GLFixedEffectModels — click to view)
# ============================================================================== # DiD estimation script (Julia), companion to Winkler et al. (2026): # https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6143552 # # Estimates the companion's four specifications on the exported panel, all with unit # and period fixed effects and one Treat x Post regressor (D): # levels OLS reported as % of the counterfactual mean # (treated pre-mean x control post/pre ratio); delta-method CI # log(1+Y) OLS # weighted log(1+Y) OLS (pre-period mean outcome weights) # PPML reported as exp(b) - 1 # # Input: did_lab_panel.csv. Download it from the applet ("Panel (CSV)" buttons # on the Replication tab; any slider settings) or use the bundled copy # (default settings, seed 4). Reference values for the bundled panel: # n = 32,000 sum(y) = 23,551,023 # levels -0.03861113 [-4.4695%, -3.2527%] log(1+Y) +0.00760246 [-0.3962%, +1.9167%] # w-log -0.03704146 [-4.3714%, -3.0369%] PPML -0.03859994 [-4.4669%, -3.2492%] # (package small-sample conventions can shift CI ends in the 3rd decimal) # True estimand values are properties of the DGP, not recoverable from the CSV; # read them off the applet's Tab 1 cards. # # Requires: import Pkg; Pkg.add(["DelimitedFiles","DataFrames","Distributions", # "FixedEffects","FixedEffectModels","GLFixedEffectModels","RegressionTables"]) # ============================================================================== using DelimitedFiles, DataFrames, Statistics, Printf using FixedEffectModels, GLFixedEffectModels using FixedEffects: FixedEffect, solve_residuals! using RegressionTables using Distributions: TDist M, hdr = readdlm("did_lab_panel.csv", ',', Int; header = true) df = DataFrame(M, vec(Symbol.(hdr))) @printf("rows = %d sum(y) = %d\n", nrow(df), sum(df.y)) # scale for the levels estimate: treated pre-mean x control post/pre ratio mean_treat_pre = mean(df.y[(df.treat .== 1) .& (df.post .== 0)]) mean_ctrl_pre = mean(df.y[(df.treat .== 0) .& (df.post .== 0)]) mean_ctrl_post = mean(df.y[(df.treat .== 0) .& (df.post .== 1)]) scale = mean_treat_pre * mean_ctrl_post / mean_ctrl_pre n_pairs = length(unique(df.pair)) tcrit = quantile(TDist(n_pairs - 1), 0.975) df.log_y = log1p.(df.y) w_pre = combine(groupby(df[df.post .== 0, :], :unit), :y => mean => :w_pre) df = innerjoin(df, w_pre, on = :unit) # SEs: CRV1 clustered by matched pair. With 1:1 matching WITHOUT replacement # (this panel), each unit belongs to exactly one pair, so two-way unit-and-pair # clustering is identical to pair clustering. If you match WITH replacement # (controls reused across pairs), units no longer nest in pairs, so cluster # two-way instead: Vcov.cluster(:unit, :pair). m_levels = reg(df, @formula(y ~ D + fe(unit) + fe(period)), Vcov.cluster(:pair), save = :residuals) m_log = reg(df, @formula(log_y ~ D + fe(unit) + fe(period)), Vcov.cluster(:pair)) m_wlog = reg(df[df.w_pre .> 0, :], @formula(log_y ~ D + fe(unit) + fe(period)), Vcov.cluster(:pair), weights = :w_pre) # zero-weight units drop out anyway m_ppml = nlreg(df, @formula(y ~ D + fe(unit) + fe(period)), Poisson(), LogLink(), Vcov.cluster(:pair), separation = [:fe]) # drop all-zero units, like the other languages bof(m) = coef(m)[findfirst(==("D"), coefnames(m))] sof(m) = stderror(m)[findfirst(==("D"), coefnames(m))] # delta-method CI for the scaled levels effect theta = b/scale: the scale is # estimated from the same sample and strongly negatively correlated with b, # so a fixed-scale CI would be ~2.5x too wide. D_dem = solve_residuals!(Float64.(df.D), [FixedEffect(df.unit), FixedEffect(df.period)])[1] resid_lv = Float64.(residuals(m_levels)) denom = sum(abs2, D_dem) df.score = D_dem .* resid_lv score_pair = sort!(combine(groupby(df, :pair), :score => sum => :s), :pair).s pair_mean(rows) = sort!(combine(groupby(df[rows, :], :pair), :y => mean => :m), :pair).m pm_treat_pre = pair_mean((df.treat .== 1) .& (df.post .== 0)) pm_ctrl_pre = pair_mean((df.treat .== 0) .& (df.post .== 0)) pm_ctrl_post = pair_mean((df.treat .== 0) .& (df.post .== 1)) theta = bof(m_levels) / scale psi = score_pair ./ (denom * scale) .- (theta / n_pairs) .* ((pm_treat_pre .- mean(pm_treat_pre)) ./ mean(pm_treat_pre) .- (pm_ctrl_pre .- mean(pm_ctrl_pre)) ./ mean(pm_ctrl_pre) .+ (pm_ctrl_post .- mean(pm_ctrl_post)) ./ mean(pm_ctrl_post)) se_theta = sqrt(sum(abs2, psi)) * n_pairs / (n_pairs - 1) ci(est, s) = @sprintf("[%+.4f%%, %+.4f%%]", 100*(est - tcrit*s), 100*(est + tcrit*s)) @printf("levels %+.8f %s (delta method)\n", theta, ci(theta, se_theta)) @printf("log1y %+.8f %s\n", bof(m_log), ci(bof(m_log), sof(m_log))) @printf("wlog %+.8f %s\n", bof(m_wlog), ci(bof(m_wlog), sof(m_wlog))) d = bof(m_ppml) @printf("ppml %+.8f [%+.4f%%, %+.4f%%]\n", exp(d) - 1, 100*(exp(d - tcrit*sof(m_ppml)) - 1), 100*(exp(d + tcrit*sof(m_ppml)) - 1)) # Standard regression table (raw coefficients). # NOTE: levels is in outcome units here (not the scaled % above), PPML is the # log coefficient (not exp(b)-1), and SEs use the package default convention. println(regtable(m_levels, m_log, m_wlog, m_ppml))
matched pairs seed
DGP and specification details

Balanced matched-pair panel. Each treated unit i has baseline b0i(1+b); its matched control has b0i, where b is the Tab 2 baseline-gap slider (b = 0 on Tab 1). The b0i sit at the midpoint quantiles of lognormal(0, σ²pop), and σpop is solved numerically on that quantile grid so the realized top-decile share of the total equals the “% of outcome in top decile” slider. Panel: 10 pre + 10 post periods. Outcomes: Yit = b0i·(1+g)t·(1+θi)Dit·εit·100, rounded to integer counts, with g the Tab 2 common-growth slider (g = 0 on Tab 1). θi equals the head slider for the top size decile and the tail slider for the rest. εit is mean-one multiplicative lognormal noise, eσZ−σ²/2, composed of a pair-common component (cv 0.12, shared by the two units of a pair in each period) and a unit-idiosyncratic component. Baseline total cv: σi = 0.3·(b0i/b0,med)−0.2, clipped to [0.12, 0.60]. Treatment multiplies the idiosyncratic component of treated-unit volatility by √(1+Δvar) in post periods, for all treated units; the pair-common component is unchanged. True estimands: typical-unit = equal-weighted mean of θi; population-total = baseline-weighted mean of θi. Specifications: every estimator uses unit and period fixed effects with a single Treat×Post regressor — OLS in levels, OLS on log(1+Y), weighted OLS on log(1+Y) with pre-period mean outcome weights, and Poisson pseudo-maximum-likelihood (log link, reported as eδ−1). Standard errors are CRV1, clustered by matched pair, with t critical values on pairs−1 degrees of freedom; two-way clustering by unit and pair is identical here because units are nested within pairs. The levels coefficient is reported as a percent of the treated pre-period mean multiplied by the control post/pre growth ratio, with a delta-method confidence interval. The Tab 2 bias surface is the exact probability limit [b/(1+b)] · [(Ḡpost−Ḡpre)/Ḡpost], with Ḡ the within-window mean of (1+g)t.