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).
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
5260ei_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
122139ei_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(" \u 00b1" , 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(" \u 00b1" , plot_se , " SE" ),
259+ add = TRUE , method = " edge"
260+ )
261+ }
262+ graphics :: par(mar = oldmar )
236263}
237264
238265
0 commit comments