---
title: "Stylized examples from Masten and Poirier (2026)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Stylized examples from Masten and Poirier (2026)}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 6, fig.height = 4
)
```

This vignette illustrates, on a *stylized* constructed example, the
figures of **Masten and Poirier (2026),** [*The Effect of Omitted
Variables on the Sign of Regression Coefficients*][mp]. The paper's
Figures 1, 2, and S1 are built on its empirical application to
Satyanath, Voigtländer and Voth (2017), whose microdata is distributed
separately (Harvard Dataverse, doi:10.7910/DVN/EE6I7N) and is not
bundled here; the paper itself does not print the moments needed to
regenerate those curves. Its survey tables aggregate across many
published studies. So this vignette exercises the machinery in
`regsensitivity` end-to-end on a data-generating process we control,
and the structural features of the published figures -- at most three
solutions at any $\delta$, the vertical asymptote at $\delta = 1$ --
are what should carry over. The numerical values should not be
compared.

[mp]: https://arxiv.org/abs/2208.00552

## A stylized data-generating process

We construct a small example where the medium-regression coefficient
is positive but Oster's identified set $\mathcal{B}_I(\delta, R_{long})$
crosses zero at moderate $\delta$.

```{r}
library(regsensitivity)
library(ggplot2)

set.seed(2208)
n <- 5000
w1 <- rnorm(n)
w2 <- rnorm(n)
w3 <- rnorm(n)
x  <- 0.3 * w1 + 0.3 * w2 + 0.5 * w3 + rnorm(n)
y  <- 0.8 * x + 0.4 * w1 - 0.3 * w2 + 0.6 * w3 + rnorm(n)
dat <- data.frame(y = y, x = x, w1 = w1, w2 = w2)   # w3 is unobserved!

form <- y ~ x + w1 + w2
```

The "true" long-regression coefficient is 0.8; the medium regression
sees an upward-biased estimate because `w3` is correlated with `x`.

```{r}
coef(lm(form, data = dat))["x"]
```

Two observed controls rather than one is not cosmetic. With a single
control, $\gamma_{med}$ and $\pi_{med}$ are scalars, the numerator and
denominator of $\delta(\beta)$ share a root, and the asymptote at that
$\beta$ cancels into a removable hole -- a degenerate case with none of
the structure the paper's figures show.

## Figure 1: Oster's identified set $\mathcal{B}_I(\delta, R_{long})$

```{r mp-fig1}
res <- regsen_bounds(form, dat,
                      analysis = "oster",
                      delta = seq(-2, 2, 0.02),
                      r2long = 1)
plot(res, ylim = c(-2, 4),
     title = "Figure 1 (stylized): Oster identified set vs delta")
```

The branches of the cubic in $\beta$ that solve Oster's equation give
the identified set. The cubic's leading coefficient is proportional to
$\delta - 1$, so a root escapes to infinity exactly at $\delta = 1$:
below it there is one solution near $\beta_{med}$, and at $\delta = 1$
a second branch enters from $-\infty$ and sweeps up across zero. The
curves are drawn as branches of $\delta(\beta)$ rather than as
functions of $\delta$, which is why a branch can fold back -- one
$\delta$ carrying two solutions on the same branch -- as this one does
just past $\delta = 1$.

## Figure 2: cumulative identified set $\delta \le \bar d$

```{r mp-fig2}
res2 <- regsen_bounds(form, dat,
                       analysis = "oster",
                       delta = seq(0, 1, 0.005),
                       delta_type = "bound",
                       r2long = 1)
plot(res2, ylim = c(0, 2),
     title = "Figure 2 (stylized): cumulative identified set")
```

The cumulative set is a narrow cone around $\beta_{med}$ for every
$\delta$ short of one, and at $\delta = 1$ it is the whole real line:
both bounds leave the panel rather than flattening along its edge. The
sign of the effect is therefore not in doubt anywhere below the
threshold and completely undetermined at it -- the discontinuity, not a
gradual widening, is what does the work.

## Table 1: breakdown points

| Quantity                                         | Value                              |
|--------------------------------------------------|------------------------------------|
| `oster_breakdown_eq`, $\beta = 0$, $R_{long}=1$  | `r round(regsen_breakdown(form, dat, analysis="oster", r2long=1, beta=bnd_eq(0))$results$breakdown[1], 4)` |
| `oster_breakdown_bound`, $\beta > 0$, $R_{long}=1$ | `r round(regsen_breakdown(form, dat, analysis="oster", r2long=1)$results$breakdown[1], 4)` |
| DMP $\bar r_X^{bp}$ at $\bar c = 1$              | `r round(regsen_bounds(form, dat, cbar=1)$breakdown, 4)` |

## Effect of a maxovb constraint

Masten-Poirier (2026) extend Oster by adding a **maximum omitted
variable bias** constraint. A sign change requires the bias to carry
$\beta$ all the way from $\beta_{med}$ to zero, so any cap below
$\beta_{med}$ rules one out and the breakdown point is $+\infty$:

```{r mp-maxovb-inf}
bmed <- regsen_bounds(form, dat, analysis = "oster",
                       delta = 0, r2long = 1)$dgp$beta_med
sapply(c(0.5, 1, round(bmed, 3) + 0.001), function(M) {
    regsen_breakdown(form, dat, analysis = "oster",
                      r2long = 1, maxovb = M)$results$breakdown[1]
})
```

Above that threshold the cap binds without forbidding the sign change,
and the breakdown point falls back towards its uncapped value as the
cap loosens:

```{r mp-maxovb}
ovbs <- seq(1.1, 3, 0.1)
out <- lapply(ovbs, function(M) {
    r <- regsen_breakdown(form, dat,
                           analysis = "oster",
                           r2long = 1, maxovb = M)
    data.frame(maxovb = M, breakdown = r$results$breakdown[1])
})
out <- do.call(rbind, out)
uncapped <- regsen_breakdown(form, dat, analysis = "oster",
                              r2long = 1)$results$breakdown[1]

ggplot(out, aes(maxovb, breakdown)) +
    geom_hline(yintercept = 1, linetype = "dotted", colour = "grey40") +
    geom_line() + geom_point() +
    labs(x = "maxovb", y = "Delta (breakdown)",
         title = "Oster sign-change breakdown vs max OVB") +
    theme_regsen()
```

A tighter cap on the OVB magnitude lifts the breakdown delta, and every
value drawn is above one -- the dotted line. That is the point of the
constraint: without it, Masten and Poirier's Theorem 2 caps the
sign-change breakdown at one (here `r round(uncapped, 4)`), so no sign
conclusion can be called robust by the $|\delta| > 1$ convention. The
cap is the extra assumption that lifts the ceiling, and it has to be
argued for rather than chosen for the answer it gives.

## Meta-analysis tables (paper Tables 3-4)

The paper's Tables 3 and 4 aggregate breakdown-point statistics across
many published studies. (Table 2 belongs to the Satyanath et al.
application, not the survey.) The package contains the primitives needed to
reproduce those tables (`regsen_breakdown(... maxovb = ...)`); plug your
own meta-analysis data in and call:

```r
results <- lapply(study_list, function(study) {
    out <- regsen_breakdown(study$formula, study$data,
                             analysis = "oster",
                             r2long = 1, maxovb = study$maxovb,
                             beta = bnd_eq(0))
    out$results$breakdown[1]
})
```
