---
title: "Association targets, pooling, and imperfect adjustment"
subtitle: "Estimands, parameters, misspecification, and pulling out the bias term."
guide-status: "Available"
---
**Before reading:** The target/estimator distinction in [What is your estimator actually estimating?](../questions-and-design/estimands-and-misspecification.qmd), covariance, and basic regression. This is a worked network-analysis extension; it is not an entry requirement for the theory guides.
## Specify the research question
A useful target statement names the population, the independent sampling unit, the measurement scale, the contrast, and any adjustment. For example, let $A$ indicate group and let $Y_g$ be a gene's measured log-expression. The descriptive contrast $E[Y_g\mid A=1]-E[Y_g\mid A=0]$ is not automatically a treatment effect. Averaging conditional contrasts over a specified distribution of covariates defines a further, distinct target.
For an edge, $\operatorname{Corr}(X,Y)$ in the pooled population, $\operatorname{Corr}(X,Y\mid A=a)$ within a group, and a residual association after adjustment for $Z$ are different estimands. A pooled correlation can consistently estimate the first while failing to estimate the second. That is a mismatch between question and procedure, even when the procedure has no difficulty estimating its own target.
Defining a target is also different from identifying it from observed data. Sampling, missingness, measurement, and any causal interpretation require their own assumptions. Use the [Research questions, study design, and estimands](../questions-and-design/index.qmd) overview before fitting the final model.
## The one idea
### Working model, population, and misspecification
A **model** is a set of distributions, usually described by an equation or a list of assumptions with a few free numbers. A **working model** supplies assumptions used to justify a particular interpretation or uncertainty calculation. A procedure may remain well-defined outside that model. Sample correlation estimates the pooled population correlation under suitable moment conditions, including in a heterogeneous mixture. Least squares estimates a linear projection without requiring a correct linear conditional mean. Interpreting either as a within-group association, a structural coefficient, or a causal effect requires additional assumptions. State which claim the working model supports.
The data come from the **population**, a single distribution we write $F$ (for two variables, their joint distribution). Nobody knows $F$ in real life. In theory and in simulation we choose one. We write down a **richer model**, a bigger set of distributions that contains the working model as a special case (for example, two groups with different means, or an outcome that also depends on a variable we left out), and we pick one member of it to play the role of $F$. So the working model and the richer model are both things we write down, and $F$ is one distribution. They differ in their role. The working model is what the procedure assumes, and $F$ is what we ask the procedure to face.
We call it **model misspecification** when $F$ lies outside the working model, so that the assumptions behind the procedure are false. Research mostly lives here: a procedure is designed for one setting and used in another. The structure the working model leaves out may be observed but ignored (group labels we have but the procedure never uses) or truly latent (hidden subtypes, unmeasured cell composition). The bias formulas below are the same either way. What changes is what you can do about the bias: known labels let you adjust for it directly, while latent structure can usually only be diagnosed or estimated. The question of this page is what a procedure is actually estimating when the population departs from its working model in one specific way.
### Three objects that are easy to confuse
Answering that question means keeping three objects apart:
- The **estimand** is the quantity you want. It is a feature of a population, not of your data: a mean, a correlation, a regression slope, a network edge. Write it as $\theta(F)$, a functional of a distribution $F$.
- The **estimator** is the recipe you apply to data: $\hat\theta = T(\text{data})$.
- The **limit** of the estimator is what the recipe converges to as the sample grows (its probability limit). Call it $\theta^\ast$. When a model is misspecified, you will also see it called the pseudo-true value.
The picture below (@fig-three-objects) puts the models and the three objects together. In this example the richer model is two groups whose means differ by $\delta$, and $\delta = 0$ is the working model. The population $F$ is the member of the richer model at $\delta = 2$, and turning $\delta$ up asks what the procedure does as the departure from its working model grows.
::: {#fig-three-objects}
```{=html}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 464" role="img" aria-label="The estimand, the estimator, the limit, and where the models come in. Left: we posit a richer model for the population, a family of worlds F-delta indexed by a departure delta, here a shift in means between two groups that the working model ignores. The working model, which the procedure assumes, sits inside it as the special case delta equals zero. The population F is the member of the family at delta equals 2, the world we ask about. Asking the question of F gives the estimand. Drawing data from F and applying the estimator gives an estimate that converges to the limit as n grows. The gap between them is the bias term. Right: as the departure is turned up from zero, the estimand stays at 0.5 while the limit rises from 0.5 toward 0.86, so the bias term grows. At a shift of 2 the limit is 0.695." style="display:block;width:100%;max-width:780px;height:auto;margin:0 auto">
<defs>
<marker id="tri-arrow-dark" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="currentColor"/></marker>
<marker id="tri-arrow-blue" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#2F80C8"/></marker>
<marker id="tri-arrow-verm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#D55E00"/></marker>
</defs>
<rect x="10" y="10" width="380" height="444" rx="10" fill="currentColor" fill-opacity="0.04" stroke="currentColor" stroke-opacity="0.3"/>
<text x="26" y="37" font-size="16" text-anchor="start" fill="currentColor" font-weight="700">One world, two numbers</text>
<rect x="24" y="54" width="352" height="172" rx="12" fill="currentColor" fill-opacity="0.05" stroke="currentColor" stroke-opacity="0.55" stroke-width="2"/>
<text x="36" y="76" font-size="13" text-anchor="start" fill="currentColor" font-weight="700">The richer model we posit</text>
<text x="36" y="92" font-size="12" text-anchor="start" fill="currentColor" fill-opacity="0.85">a family of worlds F<tspan font-size="10" dy="3">δ</tspan><tspan dy="-3">, one for each departure δ</tspan></text>
<text x="36" y="118" font-size="13" text-anchor="start" fill="currentColor" font-weight="700">Working model</text>
<text x="36" y="134" font-size="12" text-anchor="start" fill="currentColor">what the procedure</text>
<text x="36" y="149" font-size="12" text-anchor="start" fill="currentColor">assumes: no groups</text>
<text x="360" y="118" font-size="13" text-anchor="end" fill="currentColor" font-weight="700">The world we ask about</text>
<text x="360" y="134" font-size="12" text-anchor="end" fill="currentColor">two groups, shifted means,</text>
<text x="360" y="149" font-size="12" text-anchor="end" fill="currentColor">which the working model ignores</text>
<line x1="88" y1="172" x2="298" y2="172" stroke="currentColor" stroke-width="2" stroke-opacity="0.8" marker-end="url(#tri-arrow-dark)"/>
<text x="194" y="192" font-size="12" text-anchor="middle" fill="currentColor">departure δ (a group shift)</text>
<circle cx="80" cy="172" r="6" fill="currentColor" fill-opacity="0.15" stroke="currentColor" stroke-width="2.5"/>
<circle cx="308" cy="172" r="7" fill="currentColor"/>
<text x="80" y="194" font-size="12" text-anchor="middle" fill="currentColor">δ = 0</text>
<text x="320" y="172" font-size="12" text-anchor="start" fill="currentColor">F = F<tspan font-size="10" dy="3">δ</tspan></text>
<text x="320" y="188" font-size="12" text-anchor="start" fill="currentColor">δ = 2</text>
<text x="300" y="216" font-size="12" text-anchor="end" fill="currentColor">draw n points</text>
<path d="M306 250 H96 V364" fill="none" stroke="#2F80C8" stroke-width="2.5" marker-end="url(#tri-arrow-blue)"/>
<text x="200" y="243" font-size="12" text-anchor="middle" fill="currentColor">ask the question of F</text>
<line x1="308" y1="180" x2="308" y2="262" stroke="currentColor" stroke-width="2" stroke-opacity="0.8" marker-end="url(#tri-arrow-dark)"/>
<rect x="240" y="264" width="136" height="44" rx="9" fill="currentColor" fill-opacity="0.06" stroke="currentColor" stroke-opacity="0.7" stroke-width="2"/>
<text x="308" y="283" font-size="14" text-anchor="middle" fill="currentColor" font-weight="700">Estimator</text>
<text x="308" y="300" font-size="13" text-anchor="middle" fill="currentColor">T(data)</text>
<line x1="308" y1="308" x2="308" y2="324" stroke="currentColor" stroke-width="2" stroke-opacity="0.8" marker-end="url(#tri-arrow-dark)"/>
<rect x="258" y="324" width="100" height="24" rx="12" fill="currentColor" fill-opacity="0.06" stroke="currentColor" stroke-opacity="0.4"/>
<text x="308" y="341" font-size="13" text-anchor="middle" fill="currentColor">estimate θ̂<tspan font-size="10" dy="3">n</tspan></text>
<line x1="308" y1="348" x2="308" y2="366" stroke="#D55E00" stroke-width="2.5" stroke-opacity="1" marker-end="url(#tri-arrow-verm)"/>
<text x="300" y="362" font-size="12" text-anchor="end" fill="currentColor">as n → ∞</text>
<rect x="26" y="368" width="140" height="46" rx="9" fill="#2F80C8" fill-opacity="0.12" stroke="#2F80C8" stroke-width="2.5"/>
<text x="96" y="387" font-size="14" text-anchor="middle" fill="currentColor" font-weight="700">Estimand</text>
<text x="96" y="407" font-size="16" text-anchor="middle" fill="currentColor">θ(F)</text>
<rect x="240" y="368" width="136" height="46" rx="9" fill="#D55E00" fill-opacity="0.12" stroke="#D55E00" stroke-width="2.5"/>
<text x="308" y="387" font-size="14" text-anchor="middle" fill="currentColor" font-weight="700">Limit</text>
<text x="308" y="407" font-size="16" text-anchor="middle" fill="currentColor">θ*</text>
<line x1="170" y1="391" x2="236" y2="391" stroke="currentColor" stroke-width="2" stroke-opacity="0.8" marker-end="url(#tri-arrow-dark)" marker-start="url(#tri-arrow-dark)"/>
<text x="203" y="382" font-size="12" text-anchor="middle" fill="currentColor">bias term</text>
<text x="203" y="408" font-size="12" text-anchor="middle" fill="currentColor">θ* − θ(F)</text>
<text x="26" y="432" font-size="12" text-anchor="start" fill="currentColor" fill-opacity="0.85">what you want:</text>
<text x="26" y="446" font-size="12" text-anchor="start" fill="currentColor" fill-opacity="0.85">a feature of F</text>
<text x="240" y="432" font-size="12" text-anchor="start" fill="currentColor" fill-opacity="0.85">what the recipe</text>
<text x="240" y="446" font-size="12" text-anchor="start" fill="currentColor" fill-opacity="0.85">converges to</text>
<rect x="400" y="10" width="370" height="444" rx="10" fill="currentColor" fill-opacity="0.04" stroke="currentColor" stroke-opacity="0.3"/>
<text x="416" y="37" font-size="16" text-anchor="start" fill="currentColor" font-weight="700">Turn the departure up</text>
<line x1="450" y1="86" x2="450" y2="382" stroke="currentColor" stroke-opacity="0.5"/>
<line x1="450" y1="382" x2="752" y2="382" stroke="currentColor" stroke-opacity="0.5"/>
<line x1="450" y1="347.8" x2="752" y2="347.8" stroke="currentColor" stroke-opacity="0.08"/>
<text x="445" y="351.778" font-size="12" text-anchor="end" fill="currentColor">0.5</text>
<line x1="450" y1="283.3" x2="752" y2="283.3" stroke="currentColor" stroke-opacity="0.08"/>
<text x="445" y="287.333" font-size="12" text-anchor="end" fill="currentColor">0.6</text>
<line x1="450" y1="218.9" x2="752" y2="218.9" stroke="currentColor" stroke-opacity="0.08"/>
<text x="445" y="222.889" font-size="12" text-anchor="end" fill="currentColor">0.7</text>
<line x1="450" y1="154.4" x2="752" y2="154.4" stroke="currentColor" stroke-opacity="0.08"/>
<text x="445" y="158.444" font-size="12" text-anchor="end" fill="currentColor">0.8</text>
<line x1="450" y1="90.0" x2="752" y2="90.0" stroke="currentColor" stroke-opacity="0.08"/>
<text x="445" y="94" font-size="12" text-anchor="end" fill="currentColor">0.9</text>
<line x1="458.0" y1="382" x2="458.0" y2="387" stroke="currentColor" stroke-opacity="0.5"/>
<text x="458" y="402" font-size="12" text-anchor="middle" fill="currentColor">0</text>
<line x1="530.0" y1="382" x2="530.0" y2="387" stroke="currentColor" stroke-opacity="0.5"/>
<text x="530" y="402" font-size="12" text-anchor="middle" fill="currentColor">1</text>
<line x1="602.0" y1="382" x2="602.0" y2="387" stroke="currentColor" stroke-opacity="0.5"/>
<text x="602" y="402" font-size="12" text-anchor="middle" fill="currentColor">2</text>
<line x1="674.0" y1="382" x2="674.0" y2="387" stroke="currentColor" stroke-opacity="0.5"/>
<text x="674" y="402" font-size="12" text-anchor="middle" fill="currentColor">3</text>
<line x1="746.0" y1="382" x2="746.0" y2="387" stroke="currentColor" stroke-opacity="0.5"/>
<text x="746" y="402" font-size="12" text-anchor="middle" fill="currentColor">4</text>
<text x="598" y="422" font-size="12" text-anchor="middle" fill="currentColor">departure δ (the group shift)</text>
<text transform="rotate(-90 416 234)" x="416" y="234" font-size="12" text-anchor="middle" fill="currentColor">value of the number</text>
<polygon points="458.0,347.8 465.2,347.3 472.4,345.7 479.6,343.2 486.8,339.7 494.0,335.4 501.2,330.2 508.4,324.4 515.6,317.8 522.8,310.8 530.0,303.3 537.2,295.5 544.4,287.4 551.6,279.2 558.8,270.9 566.0,262.5 573.2,254.1 580.4,245.9 587.6,237.8 594.8,229.8 602.0,222.0 609.2,214.5 616.4,207.2 623.6,200.1 630.8,193.2 638.0,186.7 645.2,180.4 652.4,174.3 659.6,168.5 666.8,162.9 674.0,157.6 681.2,152.5 688.4,147.7 695.6,143.1 702.8,138.6 710.0,134.4 717.2,130.4 724.4,126.6 731.6,122.9 738.8,119.4 746.0,116.1 746.0,347.8 458.0,347.8" fill="#D55E00" fill-opacity="0.16"/>
<line x1="458" y1="347.778" x2="746" y2="347.778" stroke="#2F80C8" stroke-width="3" stroke-opacity="1"/>
<polyline fill="none" stroke="#D55E00" stroke-width="3" points="458.0,347.8 465.2,347.3 472.4,345.7 479.6,343.2 486.8,339.7 494.0,335.4 501.2,330.2 508.4,324.4 515.6,317.8 522.8,310.8 530.0,303.3 537.2,295.5 544.4,287.4 551.6,279.2 558.8,270.9 566.0,262.5 573.2,254.1 580.4,245.9 587.6,237.8 594.8,229.8 602.0,222.0 609.2,214.5 616.4,207.2 623.6,200.1 630.8,193.2 638.0,186.7 645.2,180.4 652.4,174.3 659.6,168.5 666.8,162.9 674.0,157.6 681.2,152.5 688.4,147.7 695.6,143.1 702.8,138.6 710.0,134.4 717.2,130.4 724.4,126.6 731.6,122.9 738.8,119.4 746.0,116.1"/>
<text x="746" y="365.778" font-size="12" text-anchor="end" fill="currentColor">estimand θ(F)</text>
<text x="662" y="150" font-size="12" text-anchor="end" fill="currentColor">limit θ*</text>
<text x="718" y="228" font-size="12" text-anchor="middle" fill="currentColor">bias term</text>
<circle cx="458.0" cy="347.8" r="5" fill="currentColor"/>
<line x1="458" y1="140" x2="458" y2="339.778" stroke="currentColor" stroke-width="1.2" stroke-opacity="0.5" stroke-dasharray="3 3"/>
<text x="466" y="114" font-size="12" text-anchor="start" fill="currentColor">δ = 0 is the working model itself,</text>
<text x="466" y="130" font-size="12" text-anchor="start" fill="currentColor">so the two numbers agree</text>
<line x1="602" y1="225.033" x2="602" y2="344.778" stroke="currentColor" stroke-width="2" stroke-opacity="0.8" marker-end="url(#tri-arrow-dark)" marker-start="url(#tri-arrow-dark)"/>
<circle cx="602.0" cy="222.0" r="4" fill="#D55E00"/>
<text x="610" y="295.778" font-size="12" text-anchor="start" fill="currentColor">δ = 2, the example below:</text>
<text x="610" y="311.778" font-size="12" text-anchor="start" fill="currentColor">θ* = 0.695, θ(F) = 0.5</text>
</svg>
```
The estimand, the estimator, and the limit, and where the models come in. Left: the richer model is a family of worlds $F_\delta$ indexed by a departure $\delta$ (here, a shift in group means). The working model, the one the procedure assumes, sits inside it as the special case $\delta = 0$ (drawn as a point, with the other parameters held fixed). The population $F = F_\delta$ at $\delta = 2$ is the world we ask about. The question asked of $F$ gives the estimand $\theta(F)$. Drawing data from $F$ and applying the recipe $T$ gives an estimate $\hat\theta_n$, which converges to the limit $\theta^\ast$ as $n$ grows. Right: as the departure is turned up from zero, the estimand stays put and the limit moves, and the gap between them is the bias term. The curves are for the pooled-correlation example below (within-group correlation 0.5, 20% controls).
:::
A fourth word is probably more familiar than any of these: the **parameter**. It is easy to blur with the estimand and the limit, so it helps to be exact. A parameter belongs to a *model*: it is a number that indexes a family of distributions, such as $\beta$ in $Y = \beta X + \varepsilon$, and its interpretation depends on the model and domain on which it is defined. A model parameter may also define a useful functional beyond that model, such as a linear projection coefficient. An estimand belongs to the *question and the population*, and must be defined on a stated set of distributions. Some scientific targets require identifying assumptions before they can be expressed using observed data. A limit belongs to an *estimator and the population*, and is whatever that estimator converges to, again whether or not the model behind it is right. The first example below pins this down, and a table after it summarizes.
Statistics courses spend most of their time on the case where the working model is correct, so the parameter, the estimand, and the limit are all the same number and you only have to worry about variance. Under misspecification the limit quietly moves. The question "what is this estimator actually estimating?" means: compute $\theta^\ast$ under the conditions you actually have, and compare it to $\theta(F)$.
### The bias term
The difference $\theta^\ast - \theta(F)$ is the **bias term** (strictly, the asymptotic bias, since $\theta^\ast$ is a limit). Writing it down explicitly is the core of a great deal of methodological work:
$$
\text{procedure's limit}=\text{target}+\text{asymptotic bias}.
$$
Once the bias term is a formula, two things become possible. If it depends on quantities you can estimate, you can subtract it or redesign the procedure so it vanishes: a **correction**. If it depends on quantities you can test for, you can check whether the conditions that create it are present: a **diagnostic**. A bias you have not written down can do neither.
## A first example: omitted variable bias
The classic case. We posit a richer model for the population, in which the outcome depends on two variables:
$$
Y = \beta X + \gamma Z + \varepsilon,
$$
with mean-zero $\varepsilon$ independent of $(X,Z)$, finite second moments, and $\operatorname{Var}(X)>0$. Here $\beta$ describes the conditional mean slope at fixed $Z$. Reading it as an intervention effect would require a causal model and additional identifying assumptions. An equation like this, together with distributions for $X$ and $Z$, pins down a joint distribution $F$ of $(X,Y,Z)$. The analyst leaves out $Z$ and fits
$$
Y = a + bX + e,
$$
but wants its slope $b$ to describe the conditional slope $\beta$. Setting $\gamma=0$ is one sufficient case in which those coefficients agree. More generally, the least-squares slope of Y on X alone converges to its **linear projection coefficient**:
$$
\begin{aligned}
\beta^\ast&= \frac{\operatorname{Cov}(X, Y)}{\operatorname{Var}(X)}\\
&= \beta + \gamma\,\frac{\operatorname{Cov}(X, Z)}{\operatorname{Var}(X)}.
\end{aligned}
$$
To derive this, expand $\operatorname{Cov}(X,Y)$ using the richer equation. The three terms are $\beta\operatorname{Var}(X)$, $\gamma\operatorname{Cov}(X,Z)$, and $\operatorname{Cov}(X,\varepsilon)=0$. Dividing by $\operatorname{Var}(X)$ gives the result. No assumption that the reduced conditional mean is linear is needed for this projection limit.
The estimand is $\beta$; the limit is $\beta^\ast$. The discrepancy is the product of how much Z contributes to the conditional mean and how much Z co-moves with X per unit of X variation. It vanishes if $\gamma=0$ or $\operatorname{Cov}(X,Z)=0$. The latter guarantees equality of these **slopes**; uncorrelatedness alone does not guarantee that the entire reduced conditional-mean or error-distribution model is correct. When Z is recorded, its contribution can be investigated directly. When it is unmeasured, this decomposition exposes what is missing from identification (@fig-ovb-concept).
::: {#fig-ovb-concept}
```{=html}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 355" role="img" aria-label="Omitted variable bias. Left, the richer model: Y depends on X directly with slope beta equal to 1 and on Z with slope gamma equal to 2, and Z moves with X. Right, a regression of Y on X alone cannot see Z, so association through Z is mixed into the coefficient on X: the reported slope is the conditional slope of 1 plus a discrepancy of 0.88, which is 1.88." style="display:block;width:100%;max-width:780px;height:auto;margin:0 auto">
<defs>
<marker id="ovb-arrow-dark" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="4" markerHeight="4" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="currentColor"/></marker>
<marker id="ovb-arrow-blue" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#2F80C8"/></marker>
<marker id="ovb-arrow-verm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#D55E00"/></marker>
</defs>
<rect x="10" y="10" width="370" height="335" rx="10" fill="currentColor" fill-opacity="0.04" stroke="currentColor" stroke-opacity="0.3"/>
<text x="26" y="37" font-size="16" text-anchor="start" fill="currentColor" font-weight="700">The richer model</text>
<rect x="167" y="58" width="56" height="40" rx="9" fill="currentColor" fill-opacity="0.06" stroke="currentColor" stroke-opacity="0.7" stroke-width="2"/>
<text x="195" y="85" font-size="20" text-anchor="middle" fill="currentColor" font-weight="700" fill-opacity="1">Z</text>
<rect x="52" y="160" width="56" height="40" rx="9" fill="currentColor" fill-opacity="0.06" stroke="currentColor" stroke-opacity="0.7" stroke-width="2"/>
<text x="80" y="187" font-size="20" text-anchor="middle" fill="currentColor" font-weight="700" fill-opacity="1">X</text>
<rect x="282" y="160" width="56" height="40" rx="9" fill="currentColor" fill-opacity="0.06" stroke="currentColor" stroke-opacity="0.7" stroke-width="2"/>
<text x="310" y="187" font-size="20" text-anchor="middle" fill="currentColor" font-weight="700" fill-opacity="1">Y</text>
<line x1="178" y1="100" x2="94" y2="158" stroke="#D55E00" stroke-width="2.5" stroke-opacity="1" marker-end="url(#ovb-arrow-verm)" marker-start="url(#ovb-arrow-verm)"/>
<line x1="212" y1="100" x2="298" y2="158" stroke="#D55E00" stroke-width="2.5" stroke-opacity="1" marker-end="url(#ovb-arrow-verm)"/>
<text x="122" y="122" font-size="13" text-anchor="end" fill="currentColor">Cov(X, Z) = 0.6</text>
<text x="114" y="138" font-size="12" text-anchor="end" fill="currentColor" fill-opacity="0.8">Z moves with X</text>
<text x="278" y="122" font-size="13" text-anchor="start" fill="currentColor">γ = 2</text>
<text x="278" y="138" font-size="12" text-anchor="start" fill="currentColor" fill-opacity="0.8">Z matters for Y</text>
<line x1="110" y1="180" x2="280" y2="180" stroke="#2F80C8" stroke-width="3.5" stroke-opacity="1" marker-end="url(#ovb-arrow-blue)"/>
<text x="195" y="172" font-size="14" text-anchor="middle" fill="currentColor" font-weight="700">β = 1</text>
<text x="195" y="206" font-size="12" text-anchor="middle" fill="currentColor" fill-opacity="0.8">the effect we want</text>
<text x="52" y="242" font-size="13" text-anchor="start" fill="currentColor">what we want (the estimand)</text>
<rect x="52" y="250" width="152" height="16" rx="3" fill="#2F80C8"/>
<text x="128" y="282" font-size="12" text-anchor="middle" fill="currentColor">direct: β = 1</text>
<text x="26" y="315" font-size="13" text-anchor="start" fill="currentColor" fill-opacity="0.85">Y depends on X directly (β) and on Z (γ),</text>
<text x="26" y="331" font-size="13" text-anchor="start" fill="currentColor" fill-opacity="0.85">and Z moves with X.</text>
<rect x="400" y="10" width="370" height="335" rx="10" fill="currentColor" fill-opacity="0.04" stroke="currentColor" stroke-opacity="0.3"/>
<text x="416" y="37" font-size="16" text-anchor="start" fill="currentColor" font-weight="700">The working model: Y on X alone</text>
<rect x="557" y="58" width="56" height="40" rx="9" fill="currentColor" fill-opacity="0.03" stroke="currentColor" stroke-opacity="0.35" stroke-width="2" stroke-dasharray="4 3"/>
<text x="585" y="85" font-size="20" text-anchor="middle" fill="currentColor" font-weight="700" fill-opacity="0.5">Z</text>
<rect x="442" y="160" width="56" height="40" rx="9" fill="currentColor" fill-opacity="0.06" stroke="currentColor" stroke-opacity="0.7" stroke-width="2"/>
<text x="470" y="187" font-size="20" text-anchor="middle" fill="currentColor" font-weight="700" fill-opacity="1">X</text>
<rect x="672" y="160" width="56" height="40" rx="9" fill="currentColor" fill-opacity="0.06" stroke="currentColor" stroke-opacity="0.7" stroke-width="2"/>
<text x="700" y="187" font-size="20" text-anchor="middle" fill="currentColor" font-weight="700" fill-opacity="1">Y</text>
<line x1="568" y1="100" x2="484" y2="158" stroke="#D55E00" stroke-width="2.5" stroke-opacity="0.75" stroke-dasharray="5 4" marker-end="url(#ovb-arrow-verm)" marker-start="url(#ovb-arrow-verm)"/>
<line x1="602" y1="100" x2="688" y2="158" stroke="#D55E00" stroke-width="2.5" stroke-opacity="0.75" stroke-dasharray="5 4" marker-end="url(#ovb-arrow-verm)"/>
<text x="622" y="82" font-size="12" text-anchor="start" fill="currentColor" fill-opacity="0.8">(left out of the model)</text>
<line x1="500" y1="180" x2="670" y2="180" stroke="currentColor" stroke-width="5" stroke-opacity="1" marker-end="url(#ovb-arrow-dark)"/>
<text x="585" y="170" font-size="14" text-anchor="middle" fill="currentColor" font-weight="700">slope = 1.88</text>
<text x="585" y="206" font-size="12" text-anchor="middle" fill="currentColor" fill-opacity="0.8">all the regression can see</text>
<text x="442" y="242" font-size="13" text-anchor="start" fill="currentColor">what the regression reports (the limit)</text>
<rect x="442" y="250" width="152" height="16" rx="3" fill="#2F80C8"/>
<rect x="594" y="250" width="134.118" height="16" rx="3" fill="#D55E00"/>
<text x="518" y="282" font-size="12" text-anchor="middle" fill="currentColor">direct: β = 1</text>
<text x="661.059" y="282" font-size="12" text-anchor="middle" fill="currentColor">through Z: bias</text>
<text x="661.059" y="296" font-size="12" text-anchor="middle" fill="currentColor">2 × 0.6 / 1.36 = 0.88</text>
<text x="416" y="315" font-size="13" text-anchor="start" fill="currentColor" fill-opacity="0.85">Leaving Z out mixes its association</text>
<text x="416" y="331" font-size="13" text-anchor="start" fill="currentColor" fill-opacity="0.85">counted as part of X's effect: 1 + 0.88 = 1.88.</text>
</svg>
```
Omitted variable bias as a picture. Left: the richer conditional-mean model contains an X term (slope $\beta$) and a Z term (slope $\gamma$), and Z moves with X. Arrows show these model terms; they do not identify a causal graph. The figure's “effect” labels refer here to conditional mean slopes. Right: regressing Y on X alone mixes the Z association into the X coefficient. The reported slope is the conditional slope plus $\gamma\operatorname{Cov}(X,Z)/\operatorname{Var}(X)$. The numbers are those of the simulation below, with both bars on the same scale.
:::
```{r}
set.seed(1)
n <- 1e5
z <- rnorm(n)
x <- 0.6 * z + rnorm(n) # Cov(X, Z) = 0.6, Var(X) = 1.36
y <- 1 * x + 2 * z + rnorm(n) # beta = 1, gamma = 2
coef(lm(y ~ x))["x"] # estimates the limit, about 1.88
1 + 2 * 0.6 / 1.36 # the formula, from the true Cov(X, Z) and Var(X)
```
The regression recovers the limit, about 1.88, which is well above the estimand $\beta = 1$.
### Estimand, parameter, and limit in this example
This example shows how the parameter, the estimand, and the limit line up. In the richer model, $\beta$ and $\gamma$ are parameters, and the estimand we care about is how much the mean of $Y$ changes per unit change in $X$ at a fixed value of $Z$. That estimand is $\beta$. In the working model, the parameters are $a$ and $b$. Fitting it by least squares gives $\hat b$, which converges to the limit $\beta^\ast$ found above, so the question is how $b$ relates to $\beta$.
The working model asserts that its error is unrelated to $X$. Writing the error as $e = Y - a - bX$, being uncorrelated with $X$ means $\operatorname{Cov}(X, Y) - b\operatorname{Var}(X) = 0$, which gives $b = \operatorname{Cov}(X, Y)/\operatorname{Var}(X)$. This is the expression for $\beta^\ast$ at the start of the section, so the working model's slope is
$$
\begin{aligned}
b&= \beta^\ast\\
&= \beta + \gamma\,\frac{\operatorname{Cov}(X, Z)}{\operatorname{Var}(X)}.
\end{aligned}
$$
The ratio $\operatorname{Cov}(X,Z)/\operatorname{Var}(X)$ is the slope of Z's linear projection on X. The reduced regression combines the conditional X slope $\beta$ with the contribution from the associated Z. All of these are associational statements in this example.
In the jointly Gaussian simulation, the regression residual is jointly normal with $X$, so being uncorrelated also makes it independent, so the working model describes the $(X, Y)$ data exactly, and $\hat b$ estimates $b$ without difficulty. The gap between $b$ and $\beta$ arises because $b$ answers a different question, namely how much $Y$ differs per unit of $X$ if we ignore $Z$. The conditional slope at a fixed $Z$ is a feature of the richer model, and the $(X, Y)$ data cannot reveal it by themselves. Different richer models, with different values of $\gamma$ and $\operatorname{Cov}(X, Z)$, can produce the same $(X, Y)$ distribution and therefore the same $b$, while having different values of $\beta$.
A working model can also be wrong about the data themselves. In the nonlinear example in optional exercise 4, no value of $b$ makes the straight-line conditional-mean model true. Its least-squares limit is still a well-defined linear projection, the line minimizing population mean squared error. That is a pseudo-true value for the restricted conditional-mean model.
::: {.table-responsive}
| | Belongs to | Still meaningful if the model is wrong? | In this example |
|---|---|---|---|
| Parameter | a model | not necessarily | $\beta$ in the richer model, $b$ in the working model |
| Estimand | the population and your question | yes | $\beta$, the conditional mean slope at fixed $Z$ |
| Limit | an estimator and the population | yes | $\beta^\ast = \beta + \gamma\operatorname{Cov}(X,Z)/\operatorname{Var}(X)$ |
:::
The textbook order is to start with a parameter and ask whether $\hat\theta$ is consistent for it. The order here is the reverse: start with the estimand, work out the limit, and compare. A parameter enters only if you have a model, and the model behind a procedure is often not the model that generated your data.
## A running example: the pooled correlation
Now a version that comes up constantly in expression studies. Two groups, controls and patients, with proportions $\pi$ and $1-\pi$. Within each group the two genes have the same correlation $\rho$. The patients have higher mean expression of both genes, by $\delta$ standard deviations. Here $\pi$ is the control fraction in the population being pooled. In a deliberately sampled case–control cohort it can be the sampling mixture, rather than the natural control prevalence. Changing that mixture changes a pooled target even when neither within-group distribution changes.
The picture below (@fig-pooled) shows one sample of 20 controls and 80 patients, first without a shift and then with it.
::: {#fig-pooled}
```{=html}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 490" role="img" aria-label="The pooled correlation example with one illustrative sample of 20 controls, drawn as open circles, and 80 patients, drawn as filled circles. Within each group the correlation of two genes is 0.5. Left: with no shift between the groups the line through everyone has the same slope as the line within each group, and the correlation is 0.5. Right: the same sample with the patients shifted by 2 in both genes. Each group still has correlation 0.5, but the line through everyone is steeper, and the pooled correlation is 0.695. Bars under each panel compare the within-group correlation with the pooled correlation." style="display:block;width:100%;max-width:780px;height:auto;margin:0 auto">
<rect x="10" y="10" width="370" height="470" rx="10" fill="currentColor" fill-opacity="0.04" stroke="currentColor" stroke-opacity="0.3"/>
<text x="26" y="37" font-size="16" text-anchor="start" fill="currentColor" font-weight="700">No shift (δ = 0)</text>
<rect x="66.0" y="58.0" width="285.0" height="285.0" fill="none" stroke="currentColor" stroke-opacity="0.4"/>
<line x1="85.0" y1="343.0" x2="85.0" y2="347.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="85" y="360" font-size="12" text-anchor="middle" fill="currentColor">-2</text>
<line x1="62.0" y1="324.0" x2="66.0" y2="324.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="58" y="328" font-size="12" text-anchor="end" fill="currentColor">-2</text>
<line x1="161.0" y1="343.0" x2="161.0" y2="347.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="161" y="360" font-size="12" text-anchor="middle" fill="currentColor">0</text>
<line x1="62.0" y1="248.0" x2="66.0" y2="248.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="58" y="252" font-size="12" text-anchor="end" fill="currentColor">0</text>
<line x1="237.0" y1="343.0" x2="237.0" y2="347.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="237" y="360" font-size="12" text-anchor="middle" fill="currentColor">2</text>
<line x1="62.0" y1="172.0" x2="66.0" y2="172.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="58" y="176" font-size="12" text-anchor="end" fill="currentColor">2</text>
<line x1="313.0" y1="343.0" x2="313.0" y2="347.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="313" y="360" font-size="12" text-anchor="middle" fill="currentColor">4</text>
<line x1="62.0" y1="96.0" x2="66.0" y2="96.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="58" y="100" font-size="12" text-anchor="end" fill="currentColor">4</text>
<text x="208.5" y="378" font-size="12" text-anchor="middle" fill="currentColor">gene A (within-group SDs)</text>
<text transform="rotate(-90 26 200.5)" x="26" y="200.5" font-size="12" text-anchor="middle" fill="currentColor">gene B</text>
<circle cx="214.5" cy="234.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="203.6" cy="209.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="101.8" cy="310.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="77.1" cy="324.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="157.0" cy="256.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="194.6" cy="199.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="155.1" cy="224.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="98.0" cy="277.2" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="139.9" cy="214.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="223.9" cy="227.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="101.4" cy="300.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="207.1" cy="145.6" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="184.2" cy="259.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="182.4" cy="220.6" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="194.8" cy="216.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="137.7" cy="260.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="156.3" cy="246.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="239.9" cy="188.6" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="144.7" cy="240.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="143.2" cy="282.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="100.5" cy="282.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="141.1" cy="212.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="193.6" cy="210.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="215.8" cy="240.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="189.7" cy="236.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="189.0" cy="274.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="186.9" cy="286.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="176.1" cy="264.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="205.3" cy="173.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="155.3" cy="277.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="208.1" cy="300.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="158.6" cy="257.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="92.5" cy="311.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="106.2" cy="253.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="153.3" cy="212.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="141.9" cy="273.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="109.2" cy="273.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="128.7" cy="300.6" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="169.6" cy="205.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="179.1" cy="269.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="198.4" cy="233.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="124.4" cy="185.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="178.0" cy="277.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="206.3" cy="224.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="176.9" cy="246.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="237.8" cy="197.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="141.2" cy="245.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="161.4" cy="198.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="169.3" cy="230.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="144.3" cy="239.2" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="159.5" cy="297.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="171.6" cy="232.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="160.1" cy="300.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="140.1" cy="219.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="142.5" cy="263.6" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="158.3" cy="219.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="95.8" cy="302.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="169.6" cy="239.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="114.6" cy="272.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="151.0" cy="237.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="115.0" cy="278.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="165.9" cy="271.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="145.4" cy="273.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="150.5" cy="280.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="129.9" cy="300.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="166.5" cy="293.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="229.7" cy="270.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="84.4" cy="222.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="183.6" cy="267.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="171.3" cy="287.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="145.1" cy="224.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="107.2" cy="268.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="228.9" cy="223.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="169.6" cy="207.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="204.3" cy="164.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="145.7" cy="290.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="129.0" cy="217.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="207.7" cy="208.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="193.0" cy="194.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="148.7" cy="277.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="126.7" cy="261.9" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="127.8" cy="279.0" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="190.7" cy="237.0" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="88.1" cy="291.0" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="162.5" cy="261.5" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="89.5" cy="285.9" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="205.6" cy="181.3" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="174.5" cy="246.3" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="214.4" cy="228.3" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="150.1" cy="237.2" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="181.3" cy="190.0" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="157.6" cy="258.8" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="142.0" cy="275.1" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="172.5" cy="180.6" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="124.0" cy="303.9" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="155.2" cy="263.5" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="191.6" cy="222.9" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="169.5" cy="186.9" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="153.3" cy="280.0" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="243.2" cy="288.7" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<line x1="73.6" y1="291.7" x2="248.4" y2="204.3" stroke="#2F80C8" stroke-width="2.5"/>
<line x1="73.6" y1="291.7" x2="248.4" y2="204.3" stroke="#2F80C8" stroke-width="2.5"/>
<line x1="67.1" y1="294.9" x2="275.0" y2="191.0" stroke="#D55E00" stroke-width="2.5" stroke-dasharray="7 4"/>
<text x="71.7" y="75.1" font-size="12" text-anchor="start" fill="currentColor">no shift: δ = 0</text>
<circle cx="32" cy="398" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<text x="42" y="402" font-size="12" text-anchor="start" fill="currentColor">controls: 20 points (20%)</text>
<circle cx="206" cy="398" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<text x="216" y="402" font-size="12" text-anchor="start" fill="currentColor">patients: 80 points (80%)</text>
<text x="26" y="430" font-size="12" text-anchor="start" fill="currentColor">within each group</text>
<rect x="132" y="422" width="180" height="10" rx="3" fill="currentColor" fill-opacity="0.08"/>
<rect x="132" y="422" width="90.0" height="10" rx="3" fill="#2F80C8"/>
<text x="320" y="431" font-size="12" text-anchor="start" fill="currentColor" font-weight="700">0.5</text>
<text x="26" y="452" font-size="12" text-anchor="start" fill="currentColor">all together</text>
<rect x="132" y="444" width="180" height="10" rx="3" fill="currentColor" fill-opacity="0.08"/>
<rect x="132" y="444" width="90.0" height="10" rx="3" fill="#D55E00" fill-opacity="0.35" stroke="#D55E00" stroke-width="1.5" stroke-dasharray="7 4"/>
<text x="320" y="453" font-size="12" text-anchor="start" fill="currentColor" font-weight="700">0.5</text>
<text x="26" y="472" font-size="11" text-anchor="start" fill="currentColor" fill-opacity="0.7">correlation (0 to 1)</text>
<rect x="400" y="10" width="370" height="470" rx="10" fill="currentColor" fill-opacity="0.04" stroke="currentColor" stroke-opacity="0.3"/>
<text x="416" y="37" font-size="16" text-anchor="start" fill="currentColor" font-weight="700">A shift between groups (δ = 2)</text>
<rect x="456.0" y="58.0" width="285.0" height="285.0" fill="none" stroke="currentColor" stroke-opacity="0.4"/>
<line x1="475.0" y1="343.0" x2="475.0" y2="347.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="475" y="360" font-size="12" text-anchor="middle" fill="currentColor">-2</text>
<line x1="452.0" y1="324.0" x2="456.0" y2="324.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="448" y="328" font-size="12" text-anchor="end" fill="currentColor">-2</text>
<line x1="551.0" y1="343.0" x2="551.0" y2="347.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="551" y="360" font-size="12" text-anchor="middle" fill="currentColor">0</text>
<line x1="452.0" y1="248.0" x2="456.0" y2="248.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="448" y="252" font-size="12" text-anchor="end" fill="currentColor">0</text>
<line x1="627.0" y1="343.0" x2="627.0" y2="347.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="627" y="360" font-size="12" text-anchor="middle" fill="currentColor">2</text>
<line x1="452.0" y1="172.0" x2="456.0" y2="172.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="448" y="176" font-size="12" text-anchor="end" fill="currentColor">2</text>
<line x1="703.0" y1="343.0" x2="703.0" y2="347.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="703" y="360" font-size="12" text-anchor="middle" fill="currentColor">4</text>
<line x1="452.0" y1="96.0" x2="456.0" y2="96.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="448" y="100" font-size="12" text-anchor="end" fill="currentColor">4</text>
<text x="598.5" y="378" font-size="12" text-anchor="middle" fill="currentColor">gene A (within-group SDs)</text>
<text transform="rotate(-90 416 200.5)" x="416" y="200.5" font-size="12" text-anchor="middle" fill="currentColor">gene B</text>
<circle cx="680.5" cy="158.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="669.6" cy="133.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="567.8" cy="234.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="543.1" cy="248.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="623.0" cy="180.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="660.6" cy="123.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="621.1" cy="148.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="564.0" cy="201.2" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="605.9" cy="138.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="689.9" cy="151.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="567.4" cy="224.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="673.1" cy="69.6" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="650.2" cy="183.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="648.4" cy="144.6" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="660.8" cy="140.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="603.7" cy="184.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="622.3" cy="170.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="705.9" cy="112.6" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="610.7" cy="164.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="609.2" cy="206.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="566.5" cy="206.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="607.1" cy="136.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="659.6" cy="134.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="681.8" cy="164.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="655.7" cy="160.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="655.0" cy="198.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="652.9" cy="210.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="642.1" cy="188.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="671.3" cy="97.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="621.3" cy="201.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="674.1" cy="224.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="624.6" cy="181.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="558.5" cy="235.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="572.2" cy="177.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="619.3" cy="136.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="607.9" cy="197.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="575.2" cy="197.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="594.7" cy="224.6" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="635.6" cy="129.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="645.1" cy="193.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="664.4" cy="157.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="590.4" cy="109.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="644.0" cy="201.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="672.3" cy="148.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="642.9" cy="170.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="703.8" cy="121.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="607.2" cy="169.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="627.4" cy="122.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="635.3" cy="154.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="610.3" cy="163.2" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="625.5" cy="221.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="637.6" cy="156.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="626.1" cy="224.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="606.1" cy="143.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="608.5" cy="187.6" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="624.3" cy="143.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="561.8" cy="226.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="635.6" cy="163.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="580.6" cy="196.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="617.0" cy="161.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="581.0" cy="202.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="631.9" cy="195.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="611.4" cy="197.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="616.5" cy="204.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="595.9" cy="224.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="632.5" cy="217.1" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="695.7" cy="194.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="550.4" cy="146.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="649.6" cy="191.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="637.3" cy="211.0" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="611.1" cy="148.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="573.2" cy="192.8" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="694.9" cy="147.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="635.6" cy="131.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="670.3" cy="88.3" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="611.7" cy="214.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="595.0" cy="141.4" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="673.7" cy="132.7" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="659.0" cy="118.9" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="614.7" cy="201.5" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<circle cx="516.7" cy="261.9" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="517.8" cy="279.0" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="580.7" cy="237.0" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="478.1" cy="291.0" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="552.5" cy="261.5" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="479.5" cy="285.9" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="595.6" cy="181.3" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="564.5" cy="246.3" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="604.4" cy="228.3" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="540.1" cy="237.2" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="571.3" cy="190.0" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="547.6" cy="258.8" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="532.0" cy="275.1" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="562.5" cy="180.6" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="514.0" cy="303.9" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="545.2" cy="263.5" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="581.6" cy="222.9" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="559.5" cy="186.9" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="543.3" cy="280.0" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<circle cx="633.2" cy="288.7" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<line x1="463.6" y1="291.7" x2="638.4" y2="204.3" stroke="#2F80C8" stroke-width="2.5"/>
<line x1="539.6" y1="215.7" x2="714.4" y2="128.3" stroke="#2F80C8" stroke-width="2.5"/>
<line x1="467.4" y1="287.6" x2="725.8" y2="108.0" stroke="#D55E00" stroke-width="2.5" stroke-dasharray="7 4"/>
<text x="461.7" y="75.1" font-size="12" text-anchor="start" fill="currentColor">patients shifted by δ = 2</text>
<circle cx="422" cy="398" r="3.6" fill="none" stroke="#2F80C8" stroke-width="1.6"/>
<text x="432" y="402" font-size="12" text-anchor="start" fill="currentColor">controls: 20 points (20%)</text>
<circle cx="596" cy="398" r="3.2" fill="#2F80C8" fill-opacity="0.55"/>
<text x="606" y="402" font-size="12" text-anchor="start" fill="currentColor">patients: 80 points (80%)</text>
<text x="416" y="430" font-size="12" text-anchor="start" fill="currentColor">within each group</text>
<rect x="522" y="422" width="180" height="10" rx="3" fill="currentColor" fill-opacity="0.08"/>
<rect x="522" y="422" width="90.0" height="10" rx="3" fill="#2F80C8"/>
<text x="710" y="431" font-size="12" text-anchor="start" fill="currentColor" font-weight="700">0.5</text>
<text x="416" y="452" font-size="12" text-anchor="start" fill="currentColor">all together</text>
<rect x="522" y="444" width="180" height="10" rx="3" fill="currentColor" fill-opacity="0.08"/>
<rect x="522" y="444" width="125.1" height="10" rx="3" fill="#D55E00" fill-opacity="0.35" stroke="#D55E00" stroke-width="1.5" stroke-dasharray="7 4"/>
<text x="710" y="453" font-size="12" text-anchor="start" fill="currentColor" font-weight="700">0.695</text>
<text x="416" y="472" font-size="11" text-anchor="start" fill="currentColor" fill-opacity="0.7">correlation (0 to 1)</text>
</svg>
```
The pooled-correlation example, drawn with one illustrative sample of 20 controls (open circles) and 80 patients (filled circles). The points are constructed so that the correlation within each group is exactly 0.5. Left: with no shift between the groups ($\delta = 0$), the line through everyone has the same slope as the line within each group, and the correlation is 0.5 either way. Right: the same sample with the patients shifted by $\delta = 2$ in both genes. Each group still has correlation 0.5 (blue lines), but the cloud as a whole is stretched along the diagonal, and the line through everyone (dashed) is steeper, with correlation $\rho^\ast = 0.695$. Both genes have equal variance, so the slope of each line equals its correlation, and the bars compare the two correlations. The patients outnumber the controls four to one, so the line through everyone passes much closer to the patients' center than to the controls'.
:::
The estimand most people have in mind is $\rho$, the within-group correlation. It is also the parameter of a simple model: each group is bivariate normal with correlation $\rho$. The procedure most people run is `cor(x, y)` on everyone. What is its limit?
Write the pooled distribution as a mixture $F = \pi F_0 + (1 - \pi) F_1$, where $F_0$ is the distribution in the controls and $F_1$ in the patients. Each of these is a distribution for the pair of genes (gene A, gene B), so the group means $\mu_0$ and $\mu_1$ are vectors with one entry per gene, and the group covariance matrices $\Sigma_0$ and $\Sigma_1$ are $2 \times 2$. The mean of a mixture is the weighted mean, $\mu = \pi\mu_0 + (1-\pi)\mu_1$. The covariance of a mixture is the weighted within-group covariance plus a between-group term:
$$
\begin{aligned}
\Delta_\mu&=\mu_1-\mu_0,\\
\Sigma&=\pi\Sigma_0+(1-\pi)\Sigma_1 +\pi(1-\pi)\Delta_\mu\Delta_\mu^\top.
\end{aligned}
$$
This is the law of total covariance. To derive it, write the feature vector minus its pooled mean as a within-group residual plus its group's displacement from the pooled mean. The residual has conditional mean zero, so the two cross products vanish after averaging. The residual outer product contributes the weighted within-group covariances; the mean-displacement outer product contributes the between-group term. Both kinds of variation are real components of the pooled population, but only the first answers the within-group question.
In our example both groups have unit variances and correlation $\rho$. The mean difference is $(\delta,\delta)$, so its outer product has every entry equal to $\delta^2$. Set $c=\pi(1-\pi)\delta^2$ to keep the matrix readable:
$$
\Sigma = \begin{pmatrix}1+c&\rho+c\\\rho+c&1+c\end{pmatrix}.
$$
and dividing its off-diagonal entry (the covariance) by its diagonal entry (the common variance) gives the pooled correlation:
$$
\begin{aligned}
\rho^\ast&= \frac{\rho+c}{1+c},\\
c&=\pi(1-\pi)\delta^2.
\end{aligned}
$$
Using the target/limit distinction, the bias term is the difference between the limit and the estimand, $\rho^\ast - \rho$. To write it out, put $\rho$ over the denominator of the formula above and subtract. The numerator becomes $\rho + \pi(1-\pi)\delta^2 - \rho\bigl(1 + \pi(1-\pi)\delta^2\bigr)$, which simplifies to $(1-\rho)\,\pi(1-\pi)\,\delta^2$, so
$$
\rho^\ast - \rho = \frac{(1-\rho)\,\pi(1-\pi)\,\delta^2}{1 + \pi(1-\pi)\delta^2}.
$$
The limit is therefore the estimand plus the bias term:
$$
\rho^\ast = \rho + \underbrace{\frac{(1-\rho)\,\pi(1-\pi)\,\delta^2}{1 + \pi(1-\pi)\delta^2}}_{\text{bias term}}.
$$
For $\rho < 1$, the bias term is positive whenever $\delta \neq 0$ and both groups are present, it grows with $\delta^2$, and it is largest at $\pi = 1/2$. With $\rho = 0.5$, $\delta = 2$, and $\pi = 0.2$, the formula gives $\rho^\ast = (0.5 + 0.64)/(1 + 0.64) = 0.695$, so the bias term is $0.695 - 0.5 = 0.195$. The limit $\rho^\ast$ is the correlation of the mixture. It is also the pseudo-true value of the correlation if you fit a single bivariate normal to everyone, and it is a derived functional of the two-group model parameters, rather than the common within-group correlation parameter.
```{r}
pooled_cor <- function(rho, delta, pi, delta_b = delta) { # delta_b: the second gene's shift (default: same as the first)
v <- pi * (1 - pi)
(rho + v * delta * delta_b) / sqrt((1 + v * delta^2) * (1 + v * delta_b^2))
}
pooled_cor(rho = 0.5, delta = 2, pi = 0.2)
set.seed(1)
SimulateCohort <- function(n_ctrl, n_pat, shift, rho = 0.5) {
group <- rep(c("control", "patient"), c(n_ctrl, n_pat))
n <- length(group)
z1 <- rnorm(n)
z2 <- rho * z1 + sqrt(1 - rho^2) * rnorm(n)
data.frame(group = group,
gene_a = z1 + shift * (group == "patient"),
gene_b = z2 + shift * (group == "patient"))
}
d <- SimulateCohort(n_ctrl = 20000, n_pat = 80000, shift = 2)
cor(d$gene_a, d$gene_b) # the limit
with(subset(d, group == "control"), cor(gene_a, gene_b)) # the estimand
```
The simulation agrees with the formula. That agreement is the point of the simulation: it checks that the algebra is right, and the algebra tells you what the simulation would never tell you on its own, namely exactly which quantities drive the bias.
Shifting both genes by the same amount is a special case. If the shifts are $\delta_a$ and $\delta_b$ (in within-group standard deviations), so that $\mu_1-\mu_0=(\delta_a,\delta_b)$, put $v=\pi(1-\pi)$. The pooled correlation is
$$
\rho^\ast = \frac{\rho+v\delta_a\delta_b}
{\sqrt{(1+v\delta_a^2)(1+v\delta_b^2)}}.
$$
The numerator contains the product $\delta_a\delta_b$, so the direction of the distortion depends on whether the two genes move in the same direction between groups. Genes that shift by similar amounts in the same direction look more correlated than they are. When one shift is much larger than the other, the denominator grows faster than the numerator, and the pooled correlation can fall below $\rho$ even though both shifts are positive. Genes that move in opposite directions look less correlated, and can look negatively correlated:
```{r}
pooled_cor(rho = 0.5, delta = 2, delta_b = -2, pi = 0.2) # the within-group correlation is +0.5
```
### What the bias term buys you
Because the bias term is a formula, it supports three uses.
The first is a correction. The mean difference $\mu_1 - \mu_0$ enters the pooled covariance only through the last term of the law of total covariance, $\pi(1-\pi)(\mu_1 - \mu_0)(\mu_1 - \mu_0)^\top$. Without that term the pooled covariance would equal the within-group covariance, and the pooled correlation would equal $\rho$. So the bias term comes entirely from the mean difference. The formula for it shows the same thing: it increases with $\delta^2$ and is zero exactly when $\delta = 0$. If each group's mean is subtracted from its own members before the correlation is computed, the last term is zero by construction and the bias term disappears. Within-group centering is a correction that follows directly from the bias formula.
```{r}
d$a_c <- d$gene_a - ave(d$gene_a, d$group)
d$b_c <- d$gene_b - ave(d$gene_b, d$group)
cor(d$a_c, d$b_c)
```
The second is detection. The derivation of the bias term also tells us which hypothesis to test. In this section's setting the bias term is zero exactly when the two groups have the same mean vector, so the hypothesis
$$
\begin{aligned}
H_0&:\ \mu_1-\mu_0=(0,0),\\
H_1&:\ \mu_1-\mu_0\ne(0,0)
\end{aligned}
$$
is the one that matters for whether a pooled correlation can be trusted as an estimate of $\rho$. Rejecting $H_0$ is a warning that the bias term is generally nonzero, and the pooled correlation should then be compared with the within-group correlation. The choice of test depends on the model we assume. Here each group is bivariate normal with the same covariance matrix, and under that model Hotelling's $T^2$ is an appropriate test of $H_0$. In R, `manova()` with `test = "Hotelling-Lawley"` gives it for two groups, and testing each gene separately with a two-sample $t$ test, the usual differential expression test, is a simpler alternative. The size of the distortion is estimated by the pooled sample correlation minus the correlation after within-group centering.
```{r}
# the diagnostic: test H0 that the two mean vectors are equal
summary(manova(cbind(gene_a, gene_b) ~ group, data = d), test = "Hotelling-Lawley")
# the estimated bias term: pooled correlation minus the correlation after centering
cor(d$gene_a, d$gene_b) - cor(d$a_c, d$b_c)
```
In the simulated cohort the test rejects decisively, and the estimated bias term is 0.195, in line with the formula.
The third is design. The bias term vanishes at $\delta = 0$ and is largest at $\pi = 1/2$. In a different problem the dependence on $\pi$ is reversed and the bias vanishes at balance (see the [Jackknife pseudo-values](jackknife-pseudo-values.qmd) page). Either way, the formula shows how the cohort's makeup, meaning the group sizes and the size of the mean shift, controls the artifact.
## The general recipe
When a procedure is applied outside the setting it was built for, these steps show what it is doing.
1. Write down the working model the procedure assumes, and a richer model that departs from it in one specific way. Pick one member of the richer model to play the role of $F$.
2. Name the estimand, $\theta(F)$, as a functional of that distribution. Be concrete about which distribution: the control group's, the patients', or the conditional distribution given a covariate value.
3. Work out the limit, $\theta^\ast$, which is what the procedure converges to when the data come from $F$, with heterogeneity, unbalanced groups, estimated nuisances, or whatever the richer model contains. Often it is the same functional applied to a different distribution, such as the pooled one or an adjusted one.
4. Subtract the estimand from the limit. The difference $\theta^\ast - \theta(F)$ is the bias term, and the aim is to write it in closed form. Mixtures, projections, and first-order expansions are the three tools that work most often.
5. Check the formula by simulation. Simulate from $F$, compute the procedure's output at large $n$, and compare it with the limit. If they disagree, either the formula or the simulation code is wrong, so check both (the [Simulation studies for methods research](../method-development/simulation-studies.qmd) page says to include a cell whose answer you already know).
6. Read the formula. When is the bias term zero? What makes it large? When its ingredients are identified from observed data, a zero-bias condition can sometimes motivate a diagnostic or correction. Restrictions involving unobserved outcomes or confounders generally cannot be tested this way; a non-rejection does not certify absence of bias. Its dependence on the design, such as the group sizes, gives advice on how to collect data.
## A second pattern: adjustment that leaves something behind
The limit can also move because an adjustment is incomplete. Suppose you want the association between $X$ and $Y$ after removing a nuisance $Z$, and you do this by regressing both on an *estimate* $\hat Z$ and correlating the residuals. The working model treats $\hat Z$ as good enough to stand in for $Z$. In the richer model, $\hat Z$ captures only part of $Z$, and since only the estimate is available, the rest of $Z$ is unobserved even though a proxy for Z is recorded.
To see what the residuals contain, write $X = bZ + e_X$ and $Y = cZ + e_Y$, where the errors $e_X$ and $e_Y$ are unrelated to $Z$ and to $\hat Z$, and $\operatorname{Cov}(e_X, e_Y)$ is the association given $Z$ that we want. Write $Z = \kappa\hat Z + U$, where $U$ is what is left of $Z$ after projecting it on $\hat Z$, so that $U$ is uncorrelated with $\hat Z$. If the estimate is calibrated, $\kappa = 1$ and $U = Z - \hat Z$. Let $r_X$ and $r_Y$ be the residuals from regressing $X$ and $Y$ on $\hat Z$. They still contain $U$, and their covariance is
$$
\operatorname{Cov}(r_X,r_Y)=\operatorname{Cov}(e_X,e_Y)+bc\operatorname{Var}(U).
$$
The bias term, $bc\operatorname{Var}(U)$, is the part of the nuisance the adjustment missed, weighted by how strongly the nuisance drives each variable (the product $bc$). If the missed part differs between two groups, the adjusted correlations differ between groups even when the true associations given $Z$ are identical. This is the mechanism behind cell-composition adjustment in bulk expression data, where $Z$ is composition and $\hat Z$ comes from a deconvolution method.
This is a projection calculation with centered variables (or intercepts in both regressions). Substituting $Z=\kappa\hat Z+U$ gives $r_X=bU+e_X$ and $r_Y=cU+e_Y$. Their covariance has the desired error covariance, the $bc\operatorname{Var}(U)$ term, and zero cross terms under the stated uncorrelatedness assumptions. For a residual **correlation**, the remaining U also enters both variances:
$$
\begin{aligned}
v_X^r&=\operatorname{Var}(e_X)+b^2\operatorname{Var}(U),\\
v_Y^r&=\operatorname{Var}(e_Y)+c^2\operatorname{Var}(U).
\end{aligned}
$$
Divide the residual covariance by $\sqrt{v_X^rv_Y^r}$ to get the residual correlation. A noisy proxy $\hat Z$ is different from an estimated regression that becomes consistent as the training sample grows. The proxy's error can remain a property of the measurement process at arbitrarily large sample sizes. Splitting the sample does not remove that measurement error.
```{r}
set.seed(2)
n <- 20000
z <- rnorm(n) # the nuisance
z_hat <- 0.8 * z + rnorm(n, sd = 0.6) # an imperfect estimate of it
x <- 1.5 * z + rnorm(n)
y <- 1.5 * z + rnorm(n) # X and Y are independent given Z
cor(resid(lm(x ~ z)), resid(lm(y ~ z))) # adjust on the truth: about 0
cor(resid(lm(x ~ z_hat)), resid(lm(y ~ z_hat))) # adjust on the estimate: not 0
```
In this example $b = c = 1.5$, $\operatorname{Var}(Z) = 1$, and $\operatorname{Cor}(Z, \hat Z) = 0.8$, so $\operatorname{Var}(U) = 1 - 0.8^2 = 0.36$. The bias term for the covariance is $2.25 \times 0.36 = 0.81$. The association we want, $\operatorname{Cov}(e_X, e_Y)$, is zero, so the residual covariance is 0.81. Each residual has variance $1 + 1.5^2 \times 0.36 = 1.81$, so the residual correlation is $0.81 / 1.81 = 0.45$, which is what the second line above returns. That is the limit of the adjusted correlation, whose estimand is 0. Here $\hat Z$ is not calibrated, with $\kappa = 0.8$, so $U$ is not simply $Z - \hat Z$, and using that would give a covariance of 0.90 instead of 0.81.
The same formula explains the claim about groups. Give two groups the same truth, but let the estimate of $Z$ be noisier in the second:
```{r}
set.seed(3)
Adj <- function(n, sd_hat) { # X and Y are independent given Z; the estimate of Z has noise SD sd_hat
z <- rnorm(n)
z_hat <- 0.8 * z + rnorm(n, sd = sd_hat)
x <- 1.5 * z + rnorm(n)
y <- 1.5 * z + rnorm(n)
cor(resid(lm(x ~ z_hat)), resid(lm(y ~ z_hat)))
}
Pred <- function(sd_hat) { # b * c * Var(U) / (1 + b^2 * Var(U)), with Var(U) = 1 - Cov(Z, Zhat)^2 / Var(Zhat)
var_u <- 1 - 0.8^2 / (0.8^2 + sd_hat^2)
2.25 * var_u / (1 + 2.25 * var_u)
}
round(rbind(simulated = c(group_1 = Adj(1e5, 0.3), group_2 = Adj(1e5, 0.9)),
predicted = c(group_1 = Pred(0.3), group_2 = Pred(0.9))), 3)
```
Both groups have a true association given $Z$ of zero. The adjusted correlations are about 0.22 and 0.56, matching the formula, because the part of $Z$ that the estimate misses, $\operatorname{Var}(U)$, differs between the groups.
## Why this matters in practice
The three examples in this page fit one pattern:
::: {.table-responsive}
| | Omitted variable | Pooled correlation | Imperfect adjustment |
|---|---|---|---|
| Working model | $Y = a + bX + e$, with $b$ read as the conditional X slope at fixed Z | everyone comes from one homogeneous population | adjusting for $\hat Z$ removes the nuisance $Z$ |
| Richer model | $Y = \beta X + \gamma Z + \varepsilon$, with $Z$ related to $X$ | two groups whose means differ by $\delta$ | $\hat Z$ is a noisy estimate of $Z$ |
| Estimand | $\beta$ | the within-group correlation $\rho$ | $\operatorname{Cov}(e_X, e_Y)$, the association given $Z$ (zero in the example) |
| Limit | $\beta^\ast$, the slope of $Y$ on $X$ alone | $\rho^\ast$, the correlation of everyone pooled | the covariance of the adjusted residuals |
| Bias term | $\gamma\operatorname{Cov}(X,Z)/\operatorname{Var}(X)$ | $\frac{(1-\rho)\pi(1-\pi)\delta^2}{1 + \pi(1-\pi)\delta^2}$ | $bc\operatorname{Var}(U)$ |
| Bias term is zero when | $\gamma = 0$ or $\operatorname{Cov}(X, Z) = 0$ | $\delta = 0$ | $bc\operatorname{Var}(U) = 0$, for example when $\hat Z$ captures all of $Z$ |
:::
Each column identifies conditions under which the displayed discrepancy vanishes. Whether those conditions are testable depends on which quantities are observed and identified.
Much methodological work starts by working out the limit. Someone uses a procedure (a sample-specific network, a composition adjustment, a permutation test) in a setting it was not built for (unbalanced groups, repeated measures, estimated nuisances). The paper is then: here is the working model and the departure from it, here is the estimand, here is the limit, here is the difference in closed form, here is the simulation that confirms it, here is the correction or the diagnostic, here is what it changes in real data.
When you read a methods paper in this area, look for these seven pieces. In well-built papers they are almost always there.
## The expansion applied to the wrong distribution
For a differentiable functional, write $T(G)=T(F)+G\phi_F+R_2(G,F)$, with $F\phi_F=0$. This applies the local expansion from [Influence functions](../statistical-models/influence-functions.qmd) to a different subgroup distribution; a larger cohort does not force that distributional change to be small.
```{r}
SdN <- function(v) sqrt(mean((v - mean(v))^2))
```
Now the part that matters for pseudo-value regression. Suppose the data are a mixture $F = \pi F_0 + (1-\pi) F_1$ of controls (a fraction $\pi$) and patients, and the estimand is $T(F_1)$, the patients' correlation. The pseudo-values are computed at the pooled $F_N \approx F$. By the expansion with $G = F_1$,
$$
T(F_1)=T(F) +\int\operatorname{IF}(x;T,F)\,dF_1(x) +R_2(F_1,F).
$$
The average pseudo-value over the patients converges to the limit $T(F) + \int \operatorname{IF}(x; T, F)\,dF_1(x)$, which is the first-order approximation to the estimand $T(F_1)$ around the pooled distribution. The gap between the estimand and the limit is the remainder $R_2(F_1, F)$. It does not shrink as $N$ grows, because it depends on how far $F_1$ is from $F$, a property of the population that a larger sample cannot change.
There are two different approximations here. Estimating a fixed pooled F more accurately makes sampling error smaller. Moving from F to a fixed subgroup distribution $F_1$ is a population change whose size does not decrease with N. Local differentiability gives a tangent at F; it does not say that tangent must be accurate all the way to $F_1$. This differs from a nuisance remainder that vanishes because the fitted regression approaches its truth.
The picture below (@fig-tangent) shows the expansion for the two groups of this example. The pooled sample is the starting point, and the paths lead toward each group's own distribution.
::: {#fig-tangent}
```{=html}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 470" role="img" aria-label="The correlation along a path from the pooled sample toward each group. Both paths start at the pooled correlation, 0.695, and end at the estimand, 0.5, when the group stands alone. The path toward the patients falls gently and the path toward the controls first rises to 0.75 and then falls. The tangent line at the start is the first-order approximation, and its value at the far end is the limit of the average pseudo-value: 0.61 for the patients, a bias term of 0.11, and 1.05 for the controls, a bias term of 0.55." style="display:block;width:100%;max-width:780px;height:auto;margin:0 auto">
<rect x="10" y="10" width="760" height="440" rx="10" fill="currentColor" fill-opacity="0.04" stroke="currentColor" stroke-opacity="0.3"/>
<text x="26" y="37" font-size="16" text-anchor="start" fill="currentColor" font-weight="700">From the pooled sample toward one group</text>
<text x="26" y="54" font-size="12" text-anchor="start" fill="currentColor" fill-opacity="0.85">correlation of two genes; 20% controls, 80% patients; shift of 2 in both genes; within-group correlation 0.5</text>
<rect x="90" y="84" width="400" height="260" fill="none" stroke="currentColor" stroke-opacity="0.4"/>
<line x1="86" y1="309.3" x2="90" y2="309.3" stroke="currentColor" stroke-opacity="0.5"/>
<text x="82" y="313.333" font-size="12" text-anchor="end" fill="currentColor">0.5</text>
<line x1="90" y1="309.3" x2="490" y2="309.3" stroke="currentColor" stroke-opacity="0.1"/>
<line x1="86" y1="222.7" x2="90" y2="222.7" stroke="currentColor" stroke-opacity="0.5"/>
<text x="82" y="226.667" font-size="12" text-anchor="end" fill="currentColor">0.75</text>
<line x1="90" y1="222.7" x2="490" y2="222.7" stroke="currentColor" stroke-opacity="0.1"/>
<line x1="86" y1="136.0" x2="90" y2="136.0" stroke="currentColor" stroke-opacity="0.5"/>
<text x="82" y="140" font-size="12" text-anchor="end" fill="currentColor">1</text>
<line x1="90" y1="136.0" x2="490" y2="136.0" stroke="currentColor" stroke-opacity="0.1"/>
<text transform="rotate(-90 40 214.0)" x="40" y="214.0" font-size="12" text-anchor="middle" fill="currentColor">correlation</text>
<line x1="90.0" y1="344" x2="90.0" y2="348" stroke="currentColor" stroke-opacity="0.5"/>
<text x="90" y="362" font-size="12" text-anchor="middle" fill="currentColor">pooled sample</text>
<line x1="490.0" y1="344" x2="490.0" y2="348" stroke="currentColor" stroke-opacity="0.5"/>
<text x="490" y="362" font-size="12" text-anchor="middle" fill="currentColor">the group alone</text>
<text x="290" y="382" font-size="12" text-anchor="middle" fill="currentColor">move from the pooled distribution toward the group's own distribution</text>
<polyline points="90.0,241.7 94.0,240.5 98.0,239.3 102.0,238.2 106.0,237.2 110.0,236.2 114.0,235.3 118.0,234.4 122.0,233.5 126.0,232.7 130.0,232.0 134.0,231.2 138.0,230.5 142.0,229.9 146.0,229.3 150.0,228.7 154.0,228.1 158.0,227.6 162.0,227.1 166.0,226.6 170.0,226.2 174.0,225.8 178.0,225.4 182.0,225.1 186.0,224.7 190.0,224.4 194.0,224.2 198.0,223.9 202.0,223.7 206.0,223.5 210.0,223.3 214.0,223.1 218.0,223.0 222.0,222.9 226.0,222.8 230.0,222.7 234.0,222.7 238.0,222.7 242.0,222.7 246.0,222.7 250.0,222.7 254.0,222.8 258.0,222.9 262.0,223.0 266.0,223.1 270.0,223.3 274.0,223.5 278.0,223.7 282.0,223.9 286.0,224.2 290.0,224.4 294.0,224.7 298.0,225.1 302.0,225.4 306.0,225.8 310.0,226.2 314.0,226.6 318.0,227.1 322.0,227.6 326.0,228.1 330.0,228.7 334.0,229.3 338.0,229.9 342.0,230.5 346.0,231.2 350.0,232.0 354.0,232.7 358.0,233.5 362.0,234.4 366.0,235.3 370.0,236.2 374.0,237.2 378.0,238.2 382.0,239.3 386.0,240.5 390.0,241.7 394.0,243.0 398.0,244.3 402.0,245.7 406.0,247.2 410.0,248.7 414.0,250.4 418.0,252.1 422.0,253.9 426.0,255.8 430.0,257.9 434.0,260.0 438.0,262.3 442.0,264.7 446.0,267.2 450.0,269.9 454.0,272.8 458.0,275.8 462.0,279.1 466.0,282.5 470.0,286.3 474.0,290.2 478.0,294.5 482.0,299.1 486.0,304.0 490.0,309.3" fill="none" stroke="#2F80C8" stroke-width="3"/>
<line x1="90.0" y1="241.7" x2="490.0" y2="118.0" stroke="#D55E00" stroke-width="2.5" stroke-dasharray="8 5"/>
<polyline points="90.0,241.7 94.0,242.0 98.0,242.3 102.0,242.6 106.0,243.0 110.0,243.3 114.0,243.6 118.0,244.0 122.0,244.3 126.0,244.6 130.0,245.0 134.0,245.3 138.0,245.7 142.0,246.1 146.0,246.4 150.0,246.8 154.0,247.2 158.0,247.6 162.0,247.9 166.0,248.3 170.0,248.7 174.0,249.1 178.0,249.5 182.0,249.9 186.0,250.4 190.0,250.8 194.0,251.2 198.0,251.7 202.0,252.1 206.0,252.5 210.0,253.0 214.0,253.4 218.0,253.9 222.0,254.4 226.0,254.9 230.0,255.3 234.0,255.8 238.0,256.3 242.0,256.8 246.0,257.3 250.0,257.9 254.0,258.4 258.0,258.9 262.0,259.5 266.0,260.0 270.0,260.6 274.0,261.1 278.0,261.7 282.0,262.3 286.0,262.9 290.0,263.5 294.0,264.1 298.0,264.7 302.0,265.3 306.0,265.9 310.0,266.6 314.0,267.2 318.0,267.9 322.0,268.5 326.0,269.2 330.0,269.9 334.0,270.6 338.0,271.3 342.0,272.0 346.0,272.8 350.0,273.5 354.0,274.3 358.0,275.0 362.0,275.8 366.0,276.6 370.0,277.4 374.0,278.2 378.0,279.1 382.0,279.9 386.0,280.8 390.0,281.7 394.0,282.5 398.0,283.5 402.0,284.4 406.0,285.3 410.0,286.3 414.0,287.2 418.0,288.2 422.0,289.2 426.0,290.2 430.0,291.3 434.0,292.3 438.0,293.4 442.0,294.5 446.0,295.6 450.0,296.7 454.0,297.9 458.0,299.1 462.0,300.3 466.0,301.5 470.0,302.7 474.0,304.0 478.0,305.3 482.0,306.6 486.0,308.0 490.0,309.3" fill="none" stroke="#2F80C8" stroke-width="3"/>
<line x1="90.0" y1="241.7" x2="490.0" y2="272.6" stroke="#D55E00" stroke-width="2.5" stroke-dasharray="8 5"/>
<circle cx="490.0" cy="309.3" r="6.5" fill="#2F80C8"/>
<circle cx="490.0" cy="118.0" r="6.5" fill="#D55E00"/>
<circle cx="490.0" cy="272.6" r="6.5" fill="#D55E00"/>
<circle cx="90.0" cy="241.7" r="5" fill="currentColor"/>
<line x1="490.0" y1="309.3" x2="490.0" y2="118.0" stroke="currentColor" stroke-opacity="0.5" stroke-width="1.5"/>
<text x="100" y="271.691" font-size="12" text-anchor="start" fill="currentColor">pooled sample:</text>
<text x="100" y="285.691" font-size="12" text-anchor="start" fill="currentColor">correlation 0.695</text>
<text x="290" y="203.6" font-size="12" text-anchor="start" fill="#2F80C8" font-weight="700">path toward the controls</text>
<text x="210" y="297.2" font-size="12" text-anchor="start" fill="#2F80C8" font-weight="700">path toward the patients</text>
<text x="504" y="121.955" font-size="13" text-anchor="start" fill="#D55E00" font-weight="700">controls: limit 1.05</text>
<text x="504" y="137.955" font-size="12" text-anchor="start" fill="currentColor">bias term 0.55</text>
<text x="504" y="276.625" font-size="13" text-anchor="start" fill="#D55E00" font-weight="700">patients: limit 0.61</text>
<text x="504" y="292.625" font-size="12" text-anchor="start" fill="currentColor">bias term 0.11</text>
<text x="504" y="333.333" font-size="13" text-anchor="start" fill="#2F80C8" font-weight="700">estimand 0.5, both groups</text>
<line x1="26" y1="408" x2="54" y2="408" stroke="#2F80C8" stroke-width="3"/>
<text x="62" y="412" font-size="12" text-anchor="start" fill="currentColor">the correlation along the path (the truth)</text>
<line x1="330" y1="408" x2="358" y2="408" stroke="#D55E00" stroke-width="2.5" stroke-dasharray="8 5"/>
<text x="366" y="412" font-size="12" text-anchor="start" fill="currentColor">its tangent at the start: the first-order approximation</text>
<text x="26" y="432" font-size="12" text-anchor="start" fill="currentColor" fill-opacity="0.85">The tangent's slope is the group's average influence function.</text>
<text x="26" y="448" font-size="12" text-anchor="start" fill="currentColor" fill-opacity="0.85">Its value at the far end is the limit of the group's average pseudo-value.</text>
</svg>
```
Where the first-order approximation goes wrong. Both paths start at the pooled sample (20% controls and 80% patients, a shift of 2 in both genes, within-group correlation 0.5) and move toward one group's own distribution. The blue curves are the correlation along each path, which is the truth at every point, and they end at the estimand, 0.5. The dashed vermilion lines are the tangents at the start, the first-order approximation. A tangent's slope is the group's average influence function, and its value at the far end is the limit of the group's average pseudo-value. The vertical gap at the far end is the bias term, which is minus the remainder $R_2$. It is larger for the controls because their own distribution is farther from the pooled distribution. The curves are exact for the population mixture, and the limits are 1.05 for the controls and 0.61 for the patients.
:::
For a linear functional, $T(F) = \int h\,dF$, the remainder is zero, so the limit equals the estimand. For correlation the remainder can be nonzero when group means or variances differ; the discrepancy depends on their actual moment configuration and can cancel in particular cases. In the [What is your estimator actually estimating?](../questions-and-design/estimands-and-misspecification.qmd) page the bias term is the limit minus the estimand, so it is minus this remainder: that bias term is now identified as the second-order remainder of a von Mises expansion. Pulling it out tells you the correction (center or standardize within group so that $F_1$ and $F$ agree in the moments that drive $R_2$) and the diagnostic (check whether the group difference in pseudo-values tracks the mean difference).
In the setting of this section, with equal unit variances, within-group correlation $\rho$ in both groups, and means that differ by $\delta$ in both genes, the difference between the two remainders has a closed form. Again put $c=\pi(1-\pi)\delta^2$:
$$
R_2(F_1,F)-R_2(F_0,F)=-(1-\rho)\frac{\delta^2(2\pi-1)}{(1+c)^2}.
$$
It has the sign of $1 - 2\pi$, so it changes sign when the group sizes are swapped, and it vanishes at balance. A variance difference between the groups adds a separate issue that centering does not remove (optional exercise 1).
```{r}
set.seed(4)
n_ctrl <- 2000; n_pat <- 8000; rho <- 0.5; shift <- 2
grp <- rep(0:1, c(n_ctrl, n_pat))
z1 <- rnorm(n_ctrl + n_pat); z2 <- rho * z1 + sqrt(1 - rho^2) * rnorm(n_ctrl + n_pat)
a <- z1 + shift * grp; b <- z2 + shift * grp
r_pool <- cor(a, b)
IFcor <- function(x, y) { r <- cor(x, y); xt <- (x - mean(x)) / SdN(x); yt <- (y - mean(y)) / SdN(y); xt * yt - r / 2 * (xt^2 + yt^2) }
first_order <- r_pool + tapply(IFcor(a, b), grp, mean) # what pseudo-value means estimate
truth <- c(cor(a[grp == 0], b[grp == 0]), cor(a[grp == 1], b[grp == 1]))
rbind(first_order = first_order, truth = truth, remainder_R2 = truth - first_order)
```
In this equal-variance example, the population remainder is zero when the mean shift is zero; the displayed empirical remainder still fluctuates. With a fixed nonzero shift, a larger sample estimates the persistent discrepancy more precisely rather than removing it. It differs between the two groups and is larger for the smaller group, here $-0.58$ for the controls and $-0.11$ for the patients. A between-group comparison of average pseudo-values converges to the difference of the two limits, which misses the comparison of estimands, $T(F_1) - T(F_0)$, by minus the difference between the two remainders. At balance each group's remainder is still far from zero (try `n_ctrl <- n_pat <- 5000`, where both are about $-0.25$), so a between-group comparison can have the right limit while each group's own limit differs from its estimand. That distinction, contrast versus level, matters whenever the quantity of interest is a comparison between groups.
## Exercises
**Checkpoints are completion targets for this page. Additional practice below is optional.** Each checkpoint gives a completion standard, a hint, and a solution outline.
::: {#checkpoint-des-01 .checkpoint}
### Checkpoint · DES-01 — Separate the scientific target from a pooled association {.unnumbered}
**Task type:** Derivation and interpretation.
Suppose group $G$ is 0 or 1 with $P(G=0)=\pi$, retaining the control-fraction convention from the worked example. Within each group, $X$ and $Y$ have variance 1 and correlation $\rho$. Their means are $(0,0)$ in group 0 and $(\delta_X,\delta_Y)$ in group 1.
Use total covariance and total variance to derive the pooled correlation. Identify the within-group target and the probability limit of a pooled sample correlation. Explain when their difference represents a mismatch with the research question.
**Ready to continue when:** you can name the population target and derive the procedure's limit without treating a pooled quantity as intrinsically wrong.
**Transfer:** Let the shifts have opposite signs. Can the pooled association have the opposite sign from the within-group association?
<details class="checkpoint-help">
<summary>Hint</summary>
The between-group covariance is $\operatorname{Cov}(\delta_XG,\delta_YG)$, and $\operatorname{Var}(G)=\pi(1-\pi)$.
</details>
<details class="checkpoint-help">
<summary>Solution outline — open after trying</summary>
The pooled covariance is $\rho+\pi(1-\pi)\delta_X\delta_Y$ and its marginal variances are $1+\pi(1-\pi)\delta_X^2$ and $1+\pi(1-\pi)\delta_Y^2$. Their normalized ratio is the pooled correlation. A pooled sample correlation consistently targets that quantity under suitable sampling/moment conditions. If the scientific question is within-group correlation, the target is $\rho$ and the pooled limit may differ. If the pooled population correlation is the intended target, heterogeneity alone does not make that estimator inconsistent.
</details>
:::
### Additional practice (optional) {.unnumbered}
These exercises extend the ideas through further derivations, simulations, or applications. Choose those useful for your work.
1. Extend `pooled_cor()` to the case where the groups have different within-group variances, and check it by simulation. Try a variance difference in one gene only, and in both genes equally, with no mean shift. Which of the two artifacts, the mean shift or the variance difference, does within-group centering remove, and what would remove the other?
2. In the omitted-variable example, what happens to the bias if $Z$ is measured with error and you adjust for the noisy version? Write the limit and check it by simulation. This is the "adjustment leaves something behind" pattern in its simplest form.
3. Use `pooled_cor()` with `delta_b = -delta` to plot the pooled correlation against $\pi$ for a few values of $\delta$. At what $\pi$ is it most negative, and for which $\delta$ does it change sign? Check one point by simulation.
4. Let $X \sim N(1, 1)$ and $Y = X^2 + \varepsilon$ with $E[\varepsilon\mid X]=0$. A linear conditional-mean model is wrong, but a linear projection coefficient is still a valid population target. Work out the limit of the least-squares slope by hand (it is $\operatorname{Cov}(X,Y)/\operatorname{Var}(X)$), check it by simulation, and say what question it answers.
## Reading
- Any regression text's treatment of omitted variable bias; Wooldridge, *Introductory Econometrics: A Modern Approach*, does it cleanly.
- Hernán and Robins, *Causal Inference: What If*, Chapters 1 to 3, for a careful treatment of "what are we estimating" that applies well beyond causal inference.
- Vansteelandt, S. and Dukes, O. (2022). Assumption-lean inference for generalised linear model parameters. *Journal of the Royal Statistical Society B*, 84(3), 657 to 685. Sections 1 and 2 ask, of the ordinary regression coefficient, exactly the question this page asks, and answer it by defining an estimand that stays meaningful when the model is wrong. The rest of the paper constructs an estimator and its inferential justification.
- White, H. (1982). Maximum likelihood estimation of misspecified models. *Econometrica*, 50(1), 1 to 25. The standard reference for what a maximum likelihood estimator converges to when its model is wrong, which is the pseudo-true value.
<span id="continue-through-the-collection"></span>