Skip to content

Commit a37ad9a

Browse files
committed
push; local in progress
1 parent 79afc4c commit a37ad9a

File tree

12 files changed

+136
-66
lines changed

12 files changed

+136
-66
lines changed

R/cpp11.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ r_proj_mvn <- function(eta, l, x, eps) {
2828
.Call(`_seine_r_proj_mvn`, eta, l, x, eps)
2929
}
3030

31-
r_proj_mvns <- function(eta, l, x, eps) {
32-
.Call(`_seine_r_proj_mvns`, eta, l, x, eps)
31+
r_proj_local <- function(eta, e_cov, r_cov, x, eps) {
32+
.Call(`_seine_r_proj_local`, eta, e_cov, r_cov, x, eps)
3333
}

R/ei_est.R

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
#' ei_est(riesz = rr, data = spec) # Weighted (Riesz) estimate
4949
#' est = ei_est(regr = m, riesz = rr, data = spec) # Double/debiased ML estimate
5050
#' as.matrix(est)
51-
#' as.matrix(est, se=TRUE)
51+
#' as.matrix(est, "std.error")
5252
#' vcov(est)[1:4, 1:4]
5353
#'
5454
#' est = ei_est(m, rr, data = spec, subset = (state == "Alabama"))
@@ -69,8 +69,8 @@ ei_est = function(regr=NULL, riesz=NULL, data, total, subset=NULL,
6969
n = nrow(y)
7070
n_y = ncol(y)
7171

72+
subset = eval_tidy(enquo(subset), data)
7273
if (!is.null(subset)) {
73-
subset = eval_tidy(enquo(subset), data)
7474
if (is.logical(subset)) {
7575
subset = 1*subset
7676
} else if (is.numeric(subset)) {
@@ -179,8 +179,6 @@ est_check_riesz = function(riesz, data, weights, n, regr) {
179179
cli_abort("The number of weights in {.arg riesz} ({nrow(riesz)}) must
180180
match the number of observations ({n}).", call=parent.frame())
181181
}
182-
# TODO check
183-
# riesz = riesz * weights
184182
riesz = scale_cols(riesz, 1 / colMeans(riesz))
185183
riesz
186184
}
@@ -339,17 +337,28 @@ ei_wrap_model <- function(x, data, predictors = NULL, outcome = NULL, ...) {
339337

340338
# Types --------
341339

342-
#' @describeIn ei_est Format estimates or standard errors as a matrix. Does not
343-
#' work if the object has been sorted.
340+
#' @describeIn ei_est Format estimates, standard errors, or other columns as a matrix.
344341
#' @param x,object An object of class `ei_est`
345-
#' @param se If `TRUE`, return standard errors instead of estimates.
342+
#' @param which Which column of `ei_est` to convert to a matrix. For example,
343+
#' pass `which="std.error"` to return standard errors instead of estimates.
344+
#' Partial matching supported.
346345
#' @param ... Additional arguments (ignored)
347346
#' @export
348-
as.matrix.ei_est <- function(x, se=FALSE, ...) {
349-
y = if (isFALSE(se)) x$estimate else x$std.error
347+
as.matrix.ei_est <- function(x, which="estimate", ...) {
348+
nms = setdiff(names(x), c("predictor", "outcome"))
349+
col = nms[pmatch(which, nms)]
350+
if (is.na(col)) {
351+
cli_abort(c("Unknown column {.val {which}} in {.cls ei_est} object.",
352+
">"="Available columns: {.val {nms}}"))
353+
}
354+
y = x[[col]]
355+
350356
xlab = unique(x$predictor)
351357
ylab = unique(x$outcome)
352-
matrix(y, nrow=length(xlab), ncol=length(ylab), dimnames=list(xlab, ylab))
358+
out = matrix(nrow=length(xlab), ncol=length(ylab), dimnames=list(xlab, ylab))
359+
out[cbind(x$predictor, x$outcome)] = y
360+
names(dimnames(out)) = c("predictor", "outcome")
361+
out
353362
}
354363

355364
#' @describeIn ei_est Extract full covariance matrix of estimates

R/ei_local.R

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#' Projects predictions from a fitted regression model onto the accounting
44
#' constraint using a provided residual covariance matrix. This ensures that
55
#' each set of local estimates satisfies the accounting identity. Local
6-
#' estimates will be truncated to variable bounds.
6+
#' estimates may be truncated to variable bounds.
77
#'
88
#' Local estimates are produced independently for each outcome variable.
99
#' Truncation to bounds, if used, will in general lead to estimates that do
@@ -38,7 +38,7 @@
3838
#' corresponds to the observation index in the input. It has class
3939
#' `ei_est_local`, supporting several methods.
4040
#'
41-
#' @examples
41+
#' @examples \dontrun{
4242
#' data(elec_1968)
4343
#'
4444
#' spec = ei_spec(elec_1968, vap_white:vap_other, pres_dem_hum:pres_abs,
@@ -48,6 +48,7 @@
4848
#'
4949
#' ei_est_local(m, spec, conf_level = 0.95)
5050
#' suppressWarnings(ei_est_local(m, spec, bounds=c(0.01, 0.2)))
51+
#' }
5152
#' @export
5253
ei_est_local = function(regr, data, r_cov=NULL, bounds=NULL, conf_level=FALSE, unimodal=TRUE) {
5354
y = est_check_outcome(regr, data, NULL)
@@ -57,7 +58,7 @@ ei_est_local = function(regr, data, r_cov=NULL, bounds=NULL, conf_level=FALSE, u
5758
cli_warn("Local confidence intervals do not yet incorporate prediction uncertainty.",
5859
.frequency="regularly", .frequency_id="ei_est_local_temp")
5960

60-
rl = est_check_regr(regr, data, n, NULL, n_y)
61+
rl = est_check_regr(regr, data, n, NULL, n_y, sd = TRUE)
6162
n_x = length(rl$preds)
6263
if (inherits(regr, "ei_wrapped") && !isFALSE(conf_level)) {
6364
cli_warn("Local confidence intervals with wrapped model objects
@@ -75,18 +76,20 @@ ei_est_local = function(regr, data, r_cov=NULL, bounds=NULL, conf_level=FALSE, u
7576
if (!is.list(r_cov)) {
7677
r_cov = lapply(seq_len(n_y), function(i) r_cov)
7778
}
78-
r_cov = lapply(r_cov, function(r) {
79+
# r_cov = lapply(r_cov, function(r) {
80+
for (r in r_cov) {
7981
if (!is.matrix(r) || nrow(r) != ncol(r) || nrow(r) != n_x) {
8082
cli_abort("Invalid {.arg r_cov} found.")
8183
}
84+
}
85+
# t(chol(r))
86+
# })
8287

83-
t(chol(r))
84-
})
85-
88+
# browser()
8689
ests = list()
8790
for (k in seq_len(n_y)) {
8891
eta = vapply(rl$preds, function(p) p[, k], numeric(n))
89-
proj = r_proj_mvns(eta, r_cov[[k]], rl$x, y[, k] - rl$yhat[, k])
92+
proj = r_proj_mvn(eta, r_cov[[k]], rl$x, y[, k] - rl$yhat[, k])
9093

9194
ests[[k]] = tibble::new_tibble(list(
9295
.row = rep(seq_len(n), n_x),

R/ei_sens.R

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@
2121
#' amount of bias.
2222
#' @param confounding The confounding parameter (\eqn{rho}), which must be
2323
#' between 0 and 1 (the adversarial worst-case).
24+
#' @param expand_ci If `TRUE` and confidence intervals are present in `est`,
25+
#' expand the width of the intervals in each direction by the calculated bias
26+
#' bound.
2427
#'
2528
#' @returns A data frame of the same format as `est`, but with additional
2629
#' columns: `c_outcome` and `c_predictor`, matching all combinations of those
2730
#' arguments, and `bias_bound`, containing the bound on the amount of bias.
28-
#' The data frame has additional class `ei_est`, which supports a [plot()]
29-
#' method.
31+
#' The data frame has additional class `ei_sens`, which supports a
32+
#' [plot.ei_sens()] method.
3033
#'
3134
#' @references
3235
#' Chernozhukov, V., Cinelli, C., Newey, W., Sharma, A., & Syrgkanis, V. (2022).
@@ -48,9 +51,14 @@
4851
#' # Riesz representer to cause bias of 0.4?
4952
#' ei_sens(est, c_outcome=1, bias_bound=0.4)
5053
#'
54+
#' # Update confidence intervals and extract as matrix
55+
#' est = ei_est(m, rr, spec, conf_level=0.95)
56+
#' sens = ei_sens(est, c_outcome=0.5, c_predictor=0.2)
57+
#' as.matrix(sens, "conf.high")
58+
#'
5159
#' @export
5260
ei_sens <- function(est, c_outcome=seq(0, 1, 0.01)^2, c_predictor=seq(0, 1, 0.01)^2,
53-
bias_bound=NULL, confounding=1) {
61+
bias_bound=NULL, confounding=1, expand_ci=TRUE) {
5462
is_01 <- function(value, arg) {
5563
if (!is.numeric(value) || all(value < 0) || all(value > 1)) {
5664
cli_abort("{.arg {arg}} must be between 0 and 1.",
@@ -67,12 +75,12 @@ ei_sens <- function(est, c_outcome=seq(0, 1, 0.01)^2, c_predictor=seq(0, 1, 0.01
6775
}
6876

6977
idx = as.matrix(as.data.frame(est[, c("predictor", "outcome")]))
70-
sens_s = attr(est, "sens_s")[idx]
78+
sens_s = attr(est, "sens_s")
7179
bounds_inf = attr(est, "bounds_inf")
7280
if (is.null(bias_bound)) {
7381
cc = expand.grid(c_outcome=c_outcome, c_predictor=c_predictor)
7482
est = merge(est, cc)
75-
est$bias_bound = sens_s * confounding *
83+
est$bias_bound = sens_s[idx] * confounding *
7684
sqrt(est$c_outcome * est$c_predictor / (1 - est$c_predictor))
7785
} else {
7886
if (!is.numeric(bias_bound)) {
@@ -83,11 +91,17 @@ ei_sens <- function(est, c_outcome=seq(0, 1, 0.01)^2, c_predictor=seq(0, 1, 0.01
8391
cc = expand.grid(c_outcome=c_outcome, c_predictor=1, bias_bound=abs(bias_bound))
8492
est = merge(est, cc)
8593

86-
cp = est$bias_bound^2 / (sens_s^2 * confounding^2 * est$c_outcome)
94+
cp = est$bias_bound^2 / (sens_s[idx]^2 * confounding^2 * est$c_outcome)
8795
est$c_predictor = cp / (1 + cp)
8896
}
8997

90-
tibble::new_tibble(est, bounds_inf=bounds_inf, class="ei_sens")
98+
if (isTRUE(expand_ci) && all(c("conf.low", "conf.high") %in% names(est))) {
99+
est$conf.low = est$conf.low - est$bias_bound
100+
est$conf.high = est$conf.high + est$bias_bound
101+
}
102+
103+
tibble::new_tibble(est, bounds_inf=bounds_inf, sens_s=sens_s,
104+
class=c("ei_sens", "ei_est"))
91105
}
92106

93107
#' Robustness values for ecological inference
@@ -118,6 +132,9 @@ ei_sens <- function(est, c_outcome=seq(0, 1, 0.01)^2, c_predictor=seq(0, 1, 0.01
118132
#' y_avg = weighted.mean(elec_1968$pres_ind_wal, elec_1968$pres_total)
119133
#' ei_sens_rv(est, estimate - y_avg)
120134
#'
135+
#' # Extract as matrix
136+
#' as.matrix(ei_sens_rv(est, 0.2), "rv")
137+
#'
121138
#' @export
122139
ei_sens_rv <- function(est, bias_bound, confounding=1) {
123140
bias_bound = eval_tidy(enquo(bias_bound), est)
@@ -126,7 +143,7 @@ ei_sens_rv <- function(est, bias_bound, confounding=1) {
126143
}
127144

128145
idx = as.matrix(as.data.frame(est[, c("predictor", "outcome")]))
129-
a = bias_bound^2 / attr(sens, "S")[idx]^2 / confounding^2
146+
a = bias_bound^2 / attr(est, "sens_s")[idx]^2 / confounding^2
130147
est$rv = 0.5 * (-a + sqrt(a^2 + 4*a))
131148

132149
est
@@ -139,8 +156,15 @@ ei_sens_rv <- function(est, bias_bound, confounding=1) {
139156
#' @param x An [ei_sens] object
140157
#' @param y An outcome variable, as a character vector. Defaults to first.
141158
#' @param predictor A predictor variable to plot, as a character vector. Defaults to first.
159+
#' @param bounds A vector `c(min, max)` of bounds for the outcome, which will
160+
#' affect the contours which are plotted. In general, truncation will lead to
161+
#' violations of the accounting identity. If `bounds = NULL` (the default),
162+
#' they will be inferred from the outcome variable: if it is contained within
163+
#' \eqn{[0, 1]}, for instance, then the bounds will be `c(0, 1)`. Setting
164+
#' `bounds = FALSE` forces unbounded estimates.
165+
#' @param plot_se A vector of multiples of the standard error to plot as contours.
166+
#' @param ... Additional arguments passed on to [contour()]
142167
#' @param lwd A scaling factor for the contour line widths
143-
#' @param ... Ignored
144168
#'
145169
#' @references
146170
#' Chernozhukov, V., Cinelli, C., Newey, W., Sharma, A., & Syrgkanis, V. (2022).
@@ -154,12 +178,13 @@ ei_sens_rv <- function(est, bias_bound, confounding=1) {
154178
#' total = pres_total, covariates = c(state, pop_urban, farm))
155179
#' m = ei_ridge(spec)
156180
#' rr = ei_riesz(spec, penalty = m$penalty)
157-
#' sens = ei_sens(seq(0, 1, 0.01), seq(0, 1, 0.01), m, rr, spec)
181+
#' est = ei_est(m, rr, spec)
182+
#' sens = ei_sens(est)
158183
#'
159184
#' plot(sens)
160185
#'
161186
#' @export
162-
plot.ei_sens <- function(x, y=NULL, predictor=NULL, bounds=NULL, lwd=1, ...) {
187+
plot.ei_sens <- function(x, y=NULL, predictor=NULL, bounds=NULL, plot_se=1:3, ..., lwd=1) {
163188
if (is.null(y)) y = x$outcome[1]
164189
if (is.null(predictor)) predictor = x$predictor[1]
165190

@@ -198,41 +223,43 @@ plot.ei_sens <- function(x, y=NULL, predictor=NULL, bounds=NULL, lwd=1, ...) {
198223

199224
n_om = 3 # orders of magnitude
200225
breaks = diff(bounds) * (10^-seq_len(n_om) %x% c(2:4, 6:9))
201-
oldmar = par()$mar
202-
par(mar = c(4.2, 5.2, 3, 1.1))
203-
contour(
226+
oldmar = graphics::par()$mar
227+
graphics::par(mar = c(4.2, 5.2, 3, 1.1))
228+
graphics::contour(
204229
cx, cy, cz, levels=breaks, drawlabels=FALSE, col="#bbb", lwd=lwd,
205230
xlab = bquote(1 - {R^2}[alpha ~ "~" ~ alpha[s]]),
206231
ylab = bquote({R^2}[.(y) ~ "~ confounder |" ~
207232
.(paste(preds, collapse = " + ")) ~ " + covariates" ]),
208233
main = paste0("Sensitivity bounds for E[", y, " | ", predictor, "]"),
209234
xaxs="i", yaxs="i", cex.lab=1.5
210235
)
211-
grid()
236+
graphics::grid()
212237

213238
breaks = c(10^-seq_len(n_om) %x% c(1, 5), 1)
214239
labels = as.character(breaks)
215-
special = c(abs(bounds - x$estimate[1]), x$std.error[1] * 1:3)
240+
special = c(abs(bounds - x$estimate[1]), x$std.error[1] * plot_se)
216241
dists = apply(abs(outer(special, breaks, `/`) - 1), 2, min)
217242
labels[dists < 0.05] = ""
218-
contour(
243+
graphics::contour(
219244
cx, cy, cz, levels=breaks, labels=labels,
220245
lwd = lwd * c(rep(c(1.6, 1.0), n_om), 1.6),
221246
labcex=0.8, col = "#444", add=TRUE, method="edge"
222247
)
223-
contour(
248+
graphics::contour(
224249
cx, cy, cz, lwd=2*lwd, labcex=1.0, col="#a42",
225250
levels = abs(bounds - x$estimate[1]),
226251
labels = paste("Estimate =", bounds),
227252
add=TRUE, method="edge"
228253
)
229-
contour(
230-
cx, cy, cz, lwd=2*lwd, lty="dashed", labcex=1.0, col="#46b",
231-
levels = x$std.error[1] * 1:3,
232-
labels = paste("\u00b1", 1:3, "SE"),
233-
add=TRUE, method="edge"
234-
)
235-
par(mar = oldmar)
254+
if (length(plot_se) > 0) {
255+
graphics::contour(
256+
cx, cy, cz, lwd=2*lwd, lty="dashed", labcex=1.0, col="#46b",
257+
levels = x$std.error[1] * plot_se,
258+
labels = paste("\u00b1", plot_se, "SE"),
259+
add=TRUE, method="edge"
260+
)
261+
}
262+
graphics::par(mar = oldmar)
236263
}
237264

238265

_pkgdown.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ reference:
1818
desc: Estimate global and local quantities and perform sensitivity analyses
1919
contents:
2020
- ei_est
21-
- ei_wrap_model
21+
- ei_est_local
22+
- ei_sens
23+
- ei_sens_rv
24+
- plot.ei_sens
2225
- title: Ecological modeling
2326
desc: Fit regression and weighting models to aggregate data to use in inference
2427
contents:
@@ -28,6 +31,7 @@ reference:
2831
- ridge-methods
2932
- weights.ei_riesz
3033
- ei_ridge_impl
34+
- ei_wrap_model
3135
- title: Preprocessing
3236
desc: Functions to set up EI problems and tidy data
3337
contents:

man/ei_est.Rd

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/ei_est_local.Rd

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)