This vignette reproduces every empirical table and figure of
Diegert, Masten and Poirier (2026), Assessing Omitted Variable
Bias when the Controls are Endogenous, using the bundled
bfg2020 data. We label each chunk with the corresponding
table/figure number in the paper.
library(regsensitivity)
library(ggplot2)
data(bfg2020)
bfg2020$statea <- factor(bfg2020$statea)
w1 <- c("log_area_2010", "lat", "lon", "temp_mean", "rain_mean",
"elev_mean", "d_coa", "d_riv", "d_lak", "ave_gyi")
labels <- c(
log_area_2010 = "Land area",
lat = "Centroid Latitude",
lon = "Centroid Longitude",
temp_mean = "Average temperature",
rain_mean = "Average rainfall",
elev_mean = "Elevation",
d_coa = "Distance from centroid to the coast",
d_riv = "Distance from centroid to rivers",
d_lak = "Distance from centroid to lakes",
ave_gyi = "Average potential agricultural yield"
)
form <- avgrep2000to2016 ~ tye_tfe890_500kNI_100_l6 +
log_area_2010 + lat + lon + temp_mean + rain_mean + elev_mean +
d_coa + d_riv + d_lak + ave_gyi + stateaFor the Republican vote share outcome the paper reports:
bp_conservative <- regsen_bounds(form, bfg2020, compare = w1,
cbar = 1)
bp_relaxed <- regsen_bounds(form, bfg2020, compare = w1,
cbar = 1,
rybar_expr = function(rx) rx)Side by side against the published values. published
holds the figures as printed in the paper, so the comparison is checked
on every build rather than asserted in prose:
# digits: the precision the paper prints each value to.
compare_published <- function(quantity, published, computed, digits) {
out <- data.frame(
quantity = quantity,
published = published,
computed = round(computed, 4),
stringsAsFactors = FALSE
)
out$difference <- round(out$computed - out$published, 4)
out$agrees <- abs(out$difference) <= 0.5 * 10^(-digits)
out
}
cmp <- compare_published(
quantity = c("rxbar^bp at cbar = 1",
"r^bp at cbar = 1, rybar = rxbar"),
published = c(0.804, 0.959),
digits = c(3, 3), # as printed in the paper's Table 1
computed = c(bp_conservative$breakdown, bp_relaxed$breakdown)
)
knitr::kable(cmp, caption = "Breakdown points: this package vs DMP (2026).")| quantity | published | computed | difference | agrees |
|---|---|---|---|---|
| rxbar^bp at cbar = 1 | 0.804 | 0.8036 | -4e-04 | TRUE |
| r^bp at cbar = 1, rybar = rxbar | 0.959 | 0.9584 | -6e-04 | FALSE |
The first agrees to the precision the paper prints. The second computes to 0.9584 against the printed 0.959 – a gap of 0.0006, slightly past half a unit of the last printed digit, of the same size and kind as the Elevation entry of Table 4 discussed below. An assertion in the build pins both values: if a future change moved either number, this vignette would stop building rather than quietly publishing a wrong replication.
As before, the published column is the paper’s Table 3
as printed.
tbl3 <- calibrate_partial_r2(form, bfg2020, compare = w1)
tbl3$variable <- labels[tbl3$variable]
dmp_table3 <- c(
"Average temperature" = 0.893,
"Centroid Latitude" = 0.876,
"Elevation" = 0.681,
"Average potential agricultural yield" = 0.648,
"Average rainfall" = 0.560,
"Distance from centroid to the coast" = 0.487,
"Centroid Longitude" = 0.434,
"Distance from centroid to rivers" = 0.135,
"Distance from centroid to lakes" = 0.100,
"Land area" = 0.098
)
cmp3 <- compare_published(
quantity = tbl3$variable,
published = unname(dmp_table3[tbl3$variable]),
digits = 3,
computed = tbl3$R2
)
knitr::kable(cmp3, caption = "Table 3: this package vs DMP (2026).")| quantity | published | computed | difference | agrees |
|---|---|---|---|---|
| Average temperature | 0.893 | 0.8938 | 0.0008 | FALSE |
| Centroid Latitude | 0.876 | 0.8761 | 0.0001 | TRUE |
| Elevation | 0.681 | 0.6809 | -0.0001 | TRUE |
| Average potential agricultural yield | 0.648 | 0.6475 | -0.0005 | TRUE |
| Average rainfall | 0.560 | 0.5588 | -0.0012 | FALSE |
| Distance from centroid to the coast | 0.487 | 0.4872 | 0.0002 | TRUE |
| Centroid Longitude | 0.434 | 0.4344 | 0.0004 | TRUE |
| Distance from centroid to rivers | 0.135 | 0.1346 | -0.0004 | TRUE |
| Distance from centroid to lakes | 0.100 | 0.0999 | -0.0001 | TRUE |
| Land area | 0.098 | 0.0981 | 0.0001 | TRUE |
Eight of the ten agree exactly at the printed precision. Average temperature and average rainfall are last-digit differences of the same kind as above: the computed values are 0.89382 and 0.55878.
tbl4 <- calibrate_rho(form, bfg2020, compare = w1)
tbl4$variable <- labels[tbl4$variable]
dmp_table4 <- c(
"Average potential agricultural yield" = 118.3,
"Distance from centroid to the coast" = 78.6,
"Centroid Longitude" = 49.9,
"Average temperature" = 37.3,
"Average rainfall" = 29.3,
"Centroid Latitude" = 26.9,
"Distance from centroid to rivers" = 25.0,
"Land area" = 22.5,
"Elevation" = 20.2,
"Distance from centroid to lakes" = 12.1
)
cmp4 <- compare_published(
quantity = tbl4$variable,
published = unname(dmp_table4[tbl4$variable]),
digits = 1,
computed = tbl4$rho
)
knitr::kable(cmp4, caption = "Table 4 column (5): this package vs DMP (2026).")| quantity | published | computed | difference | agrees |
|---|---|---|---|---|
| Average potential agricultural yield | 118.3 | 118.3396 | 0.0396 | TRUE |
| Distance from centroid to the coast | 78.6 | 78.5811 | -0.0189 | TRUE |
| Centroid Longitude | 49.9 | 49.9301 | 0.0301 | TRUE |
| Average temperature | 37.3 | 37.2628 | -0.0372 | TRUE |
| Average rainfall | 29.3 | 29.2864 | -0.0136 | TRUE |
| Centroid Latitude | 26.9 | 26.9299 | 0.0299 | TRUE |
| Distance from centroid to rivers | 25.0 | 24.9589 | -0.0411 | TRUE |
| Land area | 22.5 | 22.4556 | -0.0444 | TRUE |
| Elevation | 20.2 | 20.2516 | 0.0516 | FALSE |
| Distance from centroid to lakes | 12.1 | 12.0645 | -0.0355 | TRUE |
Nine of the ten agree to the precision the paper prints. Elevation is the exception: this package gives 20.2516 where the paper prints 20.2, a gap of 0.0516. That is slightly more than half a unit of the last printed digit, so it is not explained by rounding alone – the paper’s underlying value was nearer 20.24. A discrepancy this size does not move any conclusion in the paper, but it is recorded here rather than smoothed over, because the point of a replication is to show where the numbers meet and where they do not.
Figure 2 of DMP (2026) reports the same analysis for the Cut
Spending on Poor outcome, which is not part of the
bundled 14-variable bfg2020 slice. Once the full
Bazzi-Fiszbein-Gebresilasse replication data is loaded, the calls look
like:
Figure 3 repeats Figure 1 with the state fixed effects calibrated
against rather than merely controlled for. It needs no
additional data, only a different compare set. The paper
reports that the breakdown point drops from about 80% to about 30%, its
illustration that \(\bar r_X\) is only
interpretable relative to the calibration covariates:
fig3 <- regsen_bounds(form, bfg2020, compare = c(w1, "statea"), cbar = 1)
c(`w1 only` = abs(bp_conservative$breakdown), # paper: ~0.80
`w1 + state effects` = abs(fig3$breakdown)) # paper: ~0.30
#> w1 only w1 + state effects
#> 0.8035643 0.2909420The full figure, with both panels next to the published ones, is on the website: see the article Side by side with the published figures.
The paper notes that restricting the unobservable’s effect on the outcome shrinks the identified set considerably. An illustration in that spirit (this is not a figure from the paper):
fy <- regsen_bounds(form, bfg2020, compare = w1, rybar = 2,
rxbar = seq(0, 0.95, length.out = 30))
plot(fy, ylim = c(-1, 5),
title = "rybar = 2 finite-impact constraint")