| Title: | Robust Causal Mediation Analysis Under Differential Misclassification |
|---|---|
| Description: | Provides tools for conducting sensitivity analysis for causal mediation effects when the exposure or mediator is measured with differential misclassification (e.g., recall bias, outcome-dependent measurement error). Unlike existing measurement error correction methods that assume non-differential error or require validation data, 'medrobust' derives partial identification bounds that remain valid without gold-standard measurements. The package implements methods developed in Tofighi (2025) for partial identification bounds for Natural Direct Effects (NDE) and Natural Indirect Effects (NIE), data-driven falsification via testable implications, sensitivity analysis over user-specified ranges of misclassification parameters, diagnostic tools and publication-quality visualizations, bootstrap inference for confidence intervals via percentile and bias-corrected and accelerated (BCa) methods, and synthetic data generation for power analysis and methods research. The package handles both mediator misclassification and exposure misclassification within a unified framework. The partial identification approach builds on Manski (2003, ISBN:978-0387004549); confidence intervals for the partially identified bounds use the construction of Imbens and Manski (2004) <doi:10.1111/j.1468-0262.2004.00549.x>. |
| Authors: | Davood Tofighi [aut, cre, cph] (ORCID: <https://orcid.org/0000-0001-8523-7776>) |
| Maintainer: | Davood Tofighi <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.4.4 |
| Built: | 2026-10-26 08:24:21 UTC |
| Source: | https://github.com/Data-Wise/medrobust |
Create sensitivity_region object from list
as_sensitivity_region(region_list)as_sensitivity_region(region_list)
region_list |
List with sn0_range, sp0_range, psi_sn_range, psi_sp_range |
sensitivity_region S7 object
as_sensitivity_region(list( sn0_range = c(0.80, 0.95), sp0_range = c(0.85, 0.99), psi_sn_range = c(1, 2), psi_sp_range = c(1, 1) ))as_sensitivity_region(list( sn0_range = c(0.80, 0.95), sp0_range = c(0.85, 0.99), psi_sn_range = c(1, 2), psi_sp_range = c(1, 1) ))
as.data.frame() on a medrobust_bounds object (as returned by
bound_ne) returns a one-row data frame of the bounds and fit summary,
convenient for tabulating or combining results across analyses.
This is an S7 method for the base generic; call it as as.data.frame(bounds).
x |
A |
... |
Ignored. |
A one-row data.frame with columns NIE_lower, NIE_upper,
NDE_lower, NDE_upper, NIE_width, NDE_width (upper minus lower),
effect_scale, misclassified_variable, n_compatible, n_evaluated,
and falsified_proportion. If the object carries bootstrap results, it
also has percentile-interval columns for each bound endpoint
(NIE_lower_ci_lower, NIE_lower_ci_upper, ..., NDE_upper_ci_upper)
plus bootstrap_method and bootstrap_n_reps. Analytic (Imbens-Manski)
intervals are not included; use x@analytic_ci.
as.list() on a sensitivity region object (as returned by
sensitivity_region) returns its four parameter ranges as a
plain named list, the same shape accepted by the sensitivity_region
argument of bound_ne.
This is an S7 method for the base generic; call it as as.list(region).
x |
A sensitivity region object created by |
... |
Ignored. |
A named list with elements sn0_range, sp0_range,
psi_sn_range, and psi_sp_range, each a length-2 numeric
vector c(lower, upper).
Stores bootstrap inference results for partial identification bounds.
bootstrap_results( method = character(0), n_reps = integer(0), n_failed = 0L, confidence_level = integer(0), nie_lower_ci = integer(0), nie_upper_ci = integer(0), nde_lower_ci = integer(0), nde_upper_ci = integer(0), boot_nie_lower = integer(0), boot_nie_upper = integer(0), boot_nde_lower = integer(0), boot_nde_upper = integer(0), z0 = NULL, acceleration = NULL )bootstrap_results( method = character(0), n_reps = integer(0), n_failed = 0L, confidence_level = integer(0), nie_lower_ci = integer(0), nie_upper_ci = integer(0), nde_lower_ci = integer(0), nde_upper_ci = integer(0), boot_nie_lower = integer(0), boot_nie_upper = integer(0), boot_nde_lower = integer(0), boot_nde_upper = integer(0), z0 = NULL, acceleration = NULL )
method |
Character: "percentile" or "bca" |
n_reps |
Number of bootstrap replications |
n_failed |
Number of failed bootstrap samples |
confidence_level |
Confidence level (e.g., 0.95) |
nie_lower_ci |
Confidence interval for NIE lower bound |
nie_upper_ci |
Confidence interval for NIE upper bound |
nde_lower_ci |
Confidence interval for NDE lower bound |
nde_upper_ci |
Confidence interval for NDE upper bound |
boot_nie_lower |
Bootstrap samples for NIE lower bound |
boot_nie_upper |
Bootstrap samples for NIE upper bound |
boot_nde_lower |
Bootstrap samples for NDE lower bound |
boot_nde_upper |
Bootstrap samples for NDE upper bound |
z0 |
BCa bias-correction parameter |
acceleration |
BCa acceleration parameter |
An S7 object of class bootstrap_results holding bootstrap
confidence intervals and the replicate samples for the lower and upper
bound endpoints of the natural direct and indirect effects.
Compute the mean, median, and range of bound widths from bootstrap samples.
bootstrap_width_summary(bootstrap_results, effect = "NIE")bootstrap_width_summary(bootstrap_results, effect = "NIE")
bootstrap_results |
The |
effect |
Character string: "NIE" or "NDE" |
List with width statistics
Computes a confidence interval for the partial-identification set returned by
bound_ne(). The raw estimated bound is consistent but
is not a confidence set: when the identified set is narrow relative to the
sampling uncertainty of its endpoints, it under-covers the true effect at small
samples. bound_ci() widens the endpoints by their standard errors using the
Imbens & Manski (2004) construction, restoring approximately nominal coverage of
the true effect.
bound_ci( bounds, data, exposure, mediator, outcome, confounders, misclassified_variable = c("exposure", "mediator"), n_boot = 200L, level = 0.95, seed = NULL )bound_ci( bounds, data, exposure, mediator, outcome, confounders, misclassified_variable = c("exposure", "mediator"), n_boot = 200L, level = 0.95, seed = NULL )
bounds |
A fitted |
data |
The data frame passed to |
exposure, mediator, outcome, confounders
|
Column names, as in |
misclassified_variable |
Either |
n_boot |
Number of resamples for the endpoint standard errors (default 200). |
level |
Confidence level (default 0.95). |
seed |
Optional integer seed for reproducibility. |
Endpoint standard errors are obtained by re-evaluating the effect at the fixed minimizing/maximizing sensitivity parameter on resampled data (one evaluation per resample, with no grid search), which is far cheaper than a full bootstrap of the whole grid.
A named list with elements NIE and NDE, each a numeric vector with
lower, upper (the point bounds), se_lower, se_upper (endpoint SEs), and
ci_lower, ci_upper (the Imbens-Manski confidence interval).
Imbens, G. W. and Manski, C. F. (2004). Confidence Intervals for Partially Identified Parameters. Econometrica, 72(6), 1845-1857.
bound_ne()
sim <- simulate_dm_data( n = 2000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) bounds <- bound_ne( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", sensitivity_region = list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5) ), n_grid = 10, verbose = FALSE ) # Widen the raw bounds [L, U] into Imbens-Manski confidence intervals ci <- bound_ci( bounds, data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", n_boot = 50, seed = 1 ) ci$NDEsim <- simulate_dm_data( n = 2000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) bounds <- bound_ne( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", sensitivity_region = list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5) ), n_grid = 10, verbose = FALSE ) # Widen the raw bounds [L, U] into Imbens-Manski confidence intervals ci <- bound_ci( bounds, data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", n_boot = 50, seed = 1 ) ci$NDE
Computes partial identification bounds for the Natural Direct Effect (NDE) and Natural Indirect Effect (NIE) when either the exposure or mediator is subject to differential misclassification. The method does not require validation data.
bound_ne( data, exposure, mediator, outcome, confounders = NULL, misclassified_variable = c("exposure", "mediator"), sensitivity_region = NULL, n_grid = 50, effect_scale = c("OR", "RR", "RD"), confidence_level = 0.95, ci_method = c("none", "analytic"), ci_n_boot = 200L, bootstrap = FALSE, bootstrap_reps = 1000, bootstrap_method = c("percentile", "bca"), parallel = FALSE, n_cores = NULL, cache = FALSE, cache_dir = NULL, verbose = TRUE, stratify_by = NULL, use_adaptive_grid = TRUE, grid_method = c("lhs", "auto", "regular", "adaptive", "sobol", "binary") )bound_ne( data, exposure, mediator, outcome, confounders = NULL, misclassified_variable = c("exposure", "mediator"), sensitivity_region = NULL, n_grid = 50, effect_scale = c("OR", "RR", "RD"), confidence_level = 0.95, ci_method = c("none", "analytic"), ci_n_boot = 200L, bootstrap = FALSE, bootstrap_reps = 1000, bootstrap_method = c("percentile", "bca"), parallel = FALSE, n_cores = NULL, cache = FALSE, cache_dir = NULL, verbose = TRUE, stratify_by = NULL, use_adaptive_grid = TRUE, grid_method = c("lhs", "auto", "regular", "adaptive", "sobol", "binary") )
data |
A data frame containing the observed variables. |
exposure |
Character string. Name of the exposure variable (A or A*). |
mediator |
Character string. Name of the mediator variable (M or M*). |
outcome |
Character string. Name of the outcome variable (Y). |
confounders |
Character vector. Names of confounding variables. |
misclassified_variable |
Character string. Either "exposure" or "mediator" to indicate which variable is subject to misclassification. |
sensitivity_region |
A named list defining the sensitivity region Theta_Psi.
Should contain: |
n_grid |
Integer. Number of grid points per parameter dimension. Default is 50. |
effect_scale |
Character string. Scale for reporting effects: "OR" (odds ratio), "RR" (risk ratio), or "RD" (risk difference). Default is "OR". |
confidence_level |
Numeric. Confidence level for confidence intervals. Default is 0.95. |
ci_method |
Character. |
ci_n_boot |
Integer. Resamples for the analytic CI endpoint SEs. Default 200. |
bootstrap |
Logical. Whether to compute bootstrap confidence intervals. Default is FALSE. |
bootstrap_reps |
Integer. Number of bootstrap replicates if bootstrap=TRUE. Default is 1000. |
bootstrap_method |
Character string. Bootstrap CI method: "percentile" (default) or "bca" (bias-corrected and accelerated). The percentile method is faster and adequate for most applications. BCa provides second-order accurate intervals but requires jackknife estimation, which is computationally intensive for large datasets. |
parallel |
Logical. Whether to use parallel processing. Default is FALSE. |
n_cores |
Integer. Number of cores for parallel processing. If NULL, uses detectCores() - 1. Default is NULL. |
cache |
Logical. Whether to cache intermediate results. Default is FALSE. |
cache_dir |
Character string. Directory for cache files. If NULL, uses temp directory. |
verbose |
Logical. Whether to print progress messages. Default is TRUE. |
stratify_by |
Character vector. Additional variables to stratify by (advanced use). |
use_adaptive_grid |
Logical. Whether to use adaptive grid refinement for large grids (n_grid >= 20). This dramatically reduces computation time by focusing on compatible regions. Default is TRUE. |
grid_method |
Character string specifying grid search algorithm:
|
This function implements the partial identification approach described in Tofighi, D. (2025). The method derives bounds on causal mediation effects by specifying a plausible range for misclassification parameters and using testable implications to rule out empirically inconsistent values.
The sensitivity region Theta_Psi is defined by four parameters:
sn0: Baseline sensitivity (probability of correct classification
when Y=0)
sp0: Baseline specificity
psi_sn: Differential sensitivity parameter (odds ratio)
psi_sp: Differential specificity parameter (odds ratio)
Non-differential misclassification corresponds to psi_sn = psi_sp = 1.
An object of class medrobust_bounds containing:
NIE_lower |
Lower bound for Natural Indirect Effect |
NIE_upper |
Upper bound for Natural Indirect Effect |
NDE_lower |
Lower bound for Natural Direct Effect |
NDE_upper |
Upper bound for Natural Direct Effect |
compatible_sets |
Data frame of compatible parameter sets |
falsified_proportion |
Proportion of sensitivity region falsified |
effect_scale |
Scale used for reporting |
n_evaluated |
Number of parameter sets evaluated |
evaluated_sets |
Every evaluated parameter set with a logical
|
n_compatible |
Number of compatible parameter sets |
computation_time |
Time taken for computation |
call |
Original function call |
data_summary |
Summary statistics of the data |
bootstrap_results |
Bootstrap results if bootstrap=TRUE |
Tofighi, D. (2025). Partial Identification of Causal Mediation Effects Under Differential Misclassification. Biostatistics.
McKay, M. D., Beckman, R. J., & Conover, W. J. (1979). A comparison of three methods for selecting values of input variables in the analysis of output from a computer code. Technometrics, 21(2), 239-245.
Sobol', I. M. (1967). On the distribution of points in a cube and the approximate evaluation of integrals. USSR Computational Mathematics and Mathematical Physics, 7(4), 86-112.
check_compatibility, sensitivity_plot,
falsification_summary
# Simulate data with a known mediator-misclassification mechanism sim <- simulate_dm_data( n = 8000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) # Sensitivity region containing the (here non-differential) truth sens_region <- list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5) ) # Compute partial-identification bounds for mediator misclassification. # The raw bound [L, U] is consistent but is NOT a confidence set; at finite n # it can under-cover the truth, so we add Imbens-Manski confidence intervals # in the same fit via ci_method = "analytic". set.seed(1) bounds <- bound_ne( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", sensitivity_region = sens_region, n_grid = 10, ci_method = "analytic", ci_n_boot = 50 ) # View results print(bounds) summary(bounds) bounds@analytic_ci$NDE # raw [L, U] plus Imbens-Manski confidence interval # Visualize sensitivity_plot(bounds, param = "psi_sn")# Simulate data with a known mediator-misclassification mechanism sim <- simulate_dm_data( n = 8000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) # Sensitivity region containing the (here non-differential) truth sens_region <- list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5) ) # Compute partial-identification bounds for mediator misclassification. # The raw bound [L, U] is consistent but is NOT a confidence set; at finite n # it can under-cover the truth, so we add Imbens-Manski confidence intervals # in the same fit via ci_method = "analytic". set.seed(1) bounds <- bound_ne( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", sensitivity_region = sens_region, n_grid = 10, ci_method = "analytic", ci_n_boot = 50 ) # View results print(bounds) summary(bounds) bounds@analytic_ci$NDE # raw [L, U] plus Imbens-Manski confidence interval # Visualize sensitivity_plot(bounds, param = "psi_sn")
Tests whether a specific set of misclassification parameters is compatible with the observed data by checking testable implications. Returns detailed diagnostics about which constraints are satisfied or violated.
check_compatibility( data, exposure, mediator, outcome, confounders = NULL, psi, misclassified_variable = c("exposure", "mediator"), return_details = TRUE, tolerance = 1e-06 )check_compatibility( data, exposure, mediator, outcome, confounders = NULL, psi, misclassified_variable = c("exposure", "mediator"), return_details = TRUE, tolerance = 1e-06 )
data |
Data frame containing the observed variables |
exposure |
Character string. Name of exposure variable |
mediator |
Character string. Name of mediator variable |
outcome |
Character string. Name of outcome variable |
confounders |
Character vector. Names of confounding variables |
psi |
Named list containing misclassification parameters:
|
misclassified_variable |
Character string. Either "exposure" or "mediator" |
return_details |
Logical. If TRUE, returns detailed stratum-level diagnostics. Default is TRUE. |
tolerance |
Numeric. Tolerance for numerical precision when checking
constraints. Default is 1e-6. On the mediator path, |
This function implements the testable implications derived in Propositions 4.1 and 5.1 of the paper. For mediator misclassification, it checks whether the observed data can be explained by any true causal parameters given the specified misclassification mechanism. For exposure misclassification, it checks the likelihood ratio constraints on observed probabilities.
The function is useful for:
Testing specific hypotheses about misclassification parameters
Understanding which constraints are most informative
Debugging sensitivity analyses
Generating diagnostic plots
A list with class compatibility_test containing:
compatible |
Logical. TRUE if parameters are compatible |
psi |
The tested parameter set |
n_constraints_total |
Total number of testable constraints |
n_constraints_satisfied |
Number of satisfied constraints |
n_constraints_violated |
Number of violated constraints |
violated_constraints |
Data frame of violated constraints (if any) |
implied_probabilities |
Solved true probabilities (if compatible) |
stratum_details |
Stratum-level diagnostics (if return_details=TRUE) |
bound_ne, falsification_summary
data("heals_data") # Test non-differential misclassification with high accuracy test_ndm <- check_compatibility( data = heals_data, exposure = "A_star", mediator = "M", outcome = "Y", confounders = c("age", "male", "smoking", "bmi"), psi = list(sn0 = 0.90, sp0 = 0.90, psi_sn = 1.0, psi_sp = 1.0), misclassified_variable = "exposure" ) print(test_ndm) # Test strong differential misclassification test_strong_dm <- check_compatibility( data = heals_data, exposure = "A_star", mediator = "M", outcome = "Y", confounders = c("age", "male", "smoking", "bmi"), psi = list(sn0 = 0.85, sp0 = 0.85, psi_sn = 3.0, psi_sp = 1.0), misclassified_variable = "exposure" ) print(test_strong_dm)data("heals_data") # Test non-differential misclassification with high accuracy test_ndm <- check_compatibility( data = heals_data, exposure = "A_star", mediator = "M", outcome = "Y", confounders = c("age", "male", "smoking", "bmi"), psi = list(sn0 = 0.90, sp0 = 0.90, psi_sn = 1.0, psi_sp = 1.0), misclassified_variable = "exposure" ) print(test_ndm) # Test strong differential misclassification test_strong_dm <- check_compatibility( data = heals_data, exposure = "A_star", mediator = "M", outcome = "Y", confounders = c("age", "male", "smoking", "bmi"), psi = list(sn0 = 0.85, sp0 = 0.85, psi_sn = 3.0, psi_sp = 1.0), misclassified_variable = "exposure" ) print(test_strong_dm)
Compare partial identification bounds from multiple analyses (e.g., different sensitivity regions, different datasets, different assumptions).
compare_bounds(bounds_list, labels = NULL)compare_bounds(bounds_list, labels = NULL)
bounds_list |
A named list of |
labels |
Optional character vector of labels for each analysis. If NULL, uses names from bounds_list. |
A data frame comparing the bounds, and optionally a plot.
# Compare bounds under different sensitivity assumptions sim <- simulate_dm_data( n = 500, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) args0 <- list( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", n_grid = 10 ) set.seed(1) bounds1 <- do.call(bound_ne, c(args0, list(sensitivity_region = list( sn0_range = c(0.82, 0.97), sp0_range = c(0.82, 0.97), psi_sn_range = c(0.85, 1.3), psi_sp_range = c(0.85, 1.3))))) bounds2 <- do.call(bound_ne, c(args0, list(sensitivity_region = list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5))))) comparison <- compare_bounds( list(conservative = bounds1, liberal = bounds2) ) print(comparison)# Compare bounds under different sensitivity assumptions sim <- simulate_dm_data( n = 500, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) args0 <- list( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", n_grid = 10 ) set.seed(1) bounds1 <- do.call(bound_ne, c(args0, list(sensitivity_region = list( sn0_range = c(0.82, 0.97), sp0_range = c(0.82, 0.97), psi_sn_range = c(0.85, 1.3), psi_sp_range = c(0.85, 1.3))))) bounds2 <- do.call(bound_ne, c(args0, list(sensitivity_region = list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5))))) comparison <- compare_bounds( list(conservative = bounds1, liberal = bounds2) ) print(comparison)
S7 class for storing results of compatibility tests for specific misclassification parameter values.
compatible |
Logical indicating if parameters are compatible with data |
psi |
List of misclassification parameters tested |
sn1 |
Sensitivity when Y=1 (implied from psi) |
sp1 |
Specificity when Y=1 (implied from psi) |
n_constraints_total |
Total number of testable constraints |
n_constraints_satisfied |
Number of constraints satisfied |
n_constraints_violated |
Number of constraints violated |
violated_constraints |
Data frame with details of violated constraints |
implied_probabilities |
List of implied probability distributions, or
|
stratum_details |
List with stratum-specific details, or |
misclassified_variable |
Character: "exposure" or "mediator" |
reason |
Character describing reason for incompatibility (if any) |
An S7 object of class compatibility_test holding the outcome of the
data-compatibility (falsification) test, including the satisfied and
violated testable constraints.
Estimate standard errors for the lower and upper bounds using the bootstrap distribution.
compute_bound_se(bootstrap_results)compute_bound_se(bootstrap_results)
bootstrap_results |
The |
Named vector of standard errors
Extract the compatible parameter sets from a bounds analysis.
extract_bounds(bounds_object)extract_bounds(bounds_object)
bounds_object |
An object of class |
A data frame containing compatible parameter sets
sim <- simulate_dm_data( n = 2000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) bounds <- bound_ne( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", sensitivity_region = list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5) ), n_grid = 10, verbose = FALSE ) head(extract_bounds(bounds))sim <- simulate_dm_data( n = 2000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) bounds <- bound_ne( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", sensitivity_region = list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5) ), n_grid = 10, verbose = FALSE ) head(extract_bounds(bounds))
Extract the parameter sets that the grid search evaluated and the data
falsified. Works for every grid_method, because bound_ne
records each evaluated set with its verdict.
extract_falsified_region(bounds_object)extract_falsified_region(bounds_object)
bounds_object |
An object of class |
A data frame with columns sn0, sp0, psi_sn and
psi_sp, one row per falsified parameter set.
Provides a detailed summary of which regions of the sensitivity parameter space are empirically falsified by the data. Helps understand which assumptions about misclassification are most constrained by the observed data.
falsification_summary( bounds_object, by_parameter = TRUE, n_bins = 10, plot = TRUE )falsification_summary( bounds_object, by_parameter = TRUE, n_bins = 10, plot = TRUE )
bounds_object |
An object of class |
by_parameter |
Logical. If TRUE, breaks down falsification by each parameter dimension. Default is TRUE. |
n_bins |
Integer. Number of bins for discretizing parameters when computing falsification rates. Default is 10. |
plot |
Logical. If TRUE, generates visualization of falsification patterns. Default is TRUE. |
This function uses every parameter set bound_ne() evaluated (its
evaluated_sets property) to show which regions of the sensitivity
space are ruled out by the testable implications. The falsification rate of a
bin is the share of the sets evaluated in that bin that the data falsified;
a bin in which no set was evaluated has rate NA.
High falsification rates indicate that the data are informative about that
particular parameter.
The falsification analysis is useful for:
Understanding which misclassification assumptions are most constrained
Identifying whether bounds are wide due to weak data vs. wide sensitivity region
Guiding choice of sensitivity parameters for future studies
Assessing whether additional data would meaningfully narrow bounds
A list of class falsification_summary containing:
overall |
Overall falsification rate |
by_parameter |
Parameter-specific falsification rates (if by_parameter=TRUE) |
joint_falsification |
2D falsification patterns |
most_constrained |
Parameters that are most falsified |
least_constrained |
Parameters that are least falsified |
plot |
ggplot2 object (if plot=TRUE) |
# Compute bounds first (see ?bound_ne) sim <- simulate_dm_data( n = 8000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) set.seed(1) bounds <- bound_ne( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", sensitivity_region = list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5) ), n_grid = 10 ) falsif <- falsification_summary(bounds) print(falsif) # View falsification plot falsif@plot# Compute bounds first (see ?bound_ne) sim <- simulate_dm_data( n = 8000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) set.seed(1) bounds <- bound_ne( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", sensitivity_region = list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5) ), n_grid = 10 ) falsif <- falsification_summary(bounds) print(falsif) # View falsification plot falsif@plot
Format an effect estimate as a string for tables/reports.
format_effect(estimate, effect_scale = "OR", digits = 2, ci = NULL)format_effect(estimate, effect_scale = "OR", digits = 2, ci = NULL)
estimate |
Numeric effect estimate (or bounds) |
effect_scale |
Character string: "OR", "RR", or "RD" |
digits |
Integer: number of decimal places |
ci |
Optional: confidence interval (length 2 vector) |
Character string
format_effect(1.48) format_effect(1.48, ci = c(1.21, 1.81)) # point estimate with a CI format_effect(c(1.12, 1.37)) # bounds [L, U] format_effect(0.031, effect_scale = "RD", digits = 3)format_effect(1.48) format_effect(1.48, ci = c(1.21, 1.81)) # point estimate with a CI format_effect(c(1.12, 1.37)) # bounds [L, U] format_effect(0.031, effect_scale = "RD", digits = 3)
A random sample of U.S. live births used to illustrate partial-identification bounds when a binary mediator is differentially misclassified, without validation data. The mediator—gestational hypertension as recorded on the birth certificate—is a textbook differentially misclassified variable: its birth-certificate sensitivity is poor and reporting accuracy plausibly depends on the outcome (preterm birth). This is the substantive triad analyzed by the closest prior point-identification method (Hochstedler Webb and Wells, 2025).
gesthtngesthtn
A data frame with 5,000 observations and 4 binary variables:
Binary. Advanced maternal age (1 = age >= 35 years, 0 = age < 35).
Binary. Recorded (surrogate) gestational hypertension from the birth certificate (1 = Yes, 0 = No). A misclassified measurement of the true mediator; the true status is not observed.
Binary. Preterm birth (1 = gestational age < 37 weeks, 0 = term).
Binary. Parity (1 = one or more prior live births, 0 = none).
## Source data
A fixed-seed (20260614) random sample of 5,000 from 3,662,426 complete cases in
the 2021 U.S. National Center for Health Statistics (NCHS) Natality
(birth-certificate) file, public-domain U.S.-government data obtained via the
NBER mirror. Variables were derived as above from mager, rf_ghype,
combgest, and priorlive; see
system.file("scripts", "prepare_gesthtn.R", package = "medrobust").
## Misclassification Validation of the 2003 revised birth certificate against the medical record (Dietz et al., 2015) found the gestational-hypertension item to have poor sensitivity (< 70%) and excellent specificity (> 90%); differential (outcome-dependent) sensitivity is plausible. These motivate a sensitivity region with baseline sensitivity in roughly [0.50, 0.70], specificity in [0.90, 0.99], and a differential-sensitivity odds ratio at or above 1.
NCHS Natality 2021 (U.S. birth certificates), via the NBER mirror https://data.nber.org/nvss/natality/csv/2021/natality2021us.csv. Misclassification context: Dietz et al. (2015), Public Health Reports 130(1):60-70, doi:10.1177/003335491513000108. Prior art on this application: Hochstedler Webb and Wells (2025), Statistical Methods in Medical Research 34(5):1037-1059, doi:10.1177/09622802251316970.
data("gesthtn") table(gesthtn$M_star, gesthtn$Y) # Partial-identification bounds for the (differentially misclassified) mediator; # see ?bound_ne and the article # https://data-wise.github.io/medrobust/articles/gesthtn-bounds.html region <- sensitivity_region( sn0_range = c(0.50, 0.70), sp0_range = c(0.90, 0.99), psi_sn_range = c(1.0, 3.0), psi_sp_range = c(1.0, 1.0) )data("gesthtn") table(gesthtn$M_star, gesthtn$Y) # Partial-identification bounds for the (differentially misclassified) mediator; # see ?bound_ne and the article # https://data-wise.github.io/medrobust/articles/gesthtn-bounds.html region <- sensitivity_region( sn0_range = c(0.50, 0.70), sp0_range = c(0.90, 0.99), psi_sn_range = c(1.0, 3.0), psi_sp_range = c(1.0, 1.0) )
A synthetic dataset based on the Health Effects of Arsenic Longitudinal Study (HEALS) that demonstrates differential measurement error in exposure assessment. The dataset includes both observed (misclassified) and true (ground truth) arsenic exposure, allowing for validation of sensitivity analysis methods.
heals_dataheals_data
A data frame with 450 observations and 9 variables:
Integer. Unique subject identifier (1 to 450).
Binary. Cardiovascular disease status (0 = Absent, 1 = Present).
Binary. Elevated inflammation marker (sVCAM-1) (0 = Low, 1 = High).
Binary. Self-reported arsenic exposure (0 = No, 1 = Yes). Subject to severe differential recall bias.
Binary. True arsenic exposure status (0 = No, 1 = Yes). Included for validation; not available in real studies.
Numeric. Age in years (range: 18-75, mean ~42.8).
Binary. Sex (0 = Female, 1 = Male).
Binary. Ever smoker status (0 = Never, 1 = Ever).
Numeric. Body mass index in kg/m² (mean ~19.7).
## Data Generation The data were generated to mimic the HEALS cohort using published effect sizes and demographic profiles from:
Ahsan et al. (2006) - Baseline demographics
Chen et al. (2007) - Arsenic-inflammation relationship
Argos et al. (2010) - Arsenic-CVD relationship (HR ~ 1.92)
## Measurement Error Model Differential misclassification was introduced with:
Cases (Y=1): Sensitivity = 0.90, Specificity = 0.55 (over-reporting)
Controls (Y=0): Sensitivity = 0.60, Specificity = 0.90
This creates severe recall bias where CVD cases over-report arsenic exposure, inflating the naive effect estimate by approximately 70
## True vs. Naive Effects
True Natural Direct Effect: OR ~ 1.68
Naive NDE (using A_star): OR ~ 2.85
Bias amplification: ~70
Synthetic data generated using methods described in the article "Synthetic HEALS Data: Ground Truth with Differential Measurement Error" on the package website: https://data-wise.github.io/medrobust/articles/heals-synthetic-data.html.
# Load the data data("heals_data") str(heals_data) # Examine prevalence table(heals_data$A_star, heals_data$Y) # Compare naive vs. true effect naive_mod <- glm(Y ~ A_star + M + age + male + smoking + bmi, data = heals_data, family = binomial) true_mod <- glm(Y ~ A_true + M + age + male + smoking + bmi, data = heals_data, family = binomial) cat("Naive NDE:", round(exp(coef(naive_mod)["A_star"]), 2), "\n") cat("True NDE:", round(exp(coef(true_mod)["A_true"]), 2), "\n") # To compute partial-identification bounds for the (potentially # misclassified) exposure A_star, pass this data to bound_ne(); # see ?bound_ne for a complete, runnable example.# Load the data data("heals_data") str(heals_data) # Examine prevalence table(heals_data$A_star, heals_data$Y) # Compare naive vs. true effect naive_mod <- glm(Y ~ A_star + M + age + male + smoking + bmi, data = heals_data, family = binomial) true_mod <- glm(Y ~ A_true + M + age + male + smoking + bmi, data = heals_data, family = binomial) cat("Naive NDE:", round(exp(coef(naive_mod)["A_star"]), 2), "\n") cat("True NDE:", round(exp(coef(true_mod)["A_true"]), 2), "\n") # To compute partial-identification bounds for the (potentially # misclassified) exposure A_star, pass this data to bound_ne(); # see ?bound_ne for a complete, runnable example.
S7 class for storing partial identification bounds for natural direct and indirect effects under differential misclassification.
NIE_lower |
Lower bound for Natural Indirect Effect |
NIE_upper |
Upper bound for Natural Indirect Effect |
NDE_lower |
Lower bound for Natural Direct Effect |
NDE_upper |
Upper bound for Natural Direct Effect |
compatible_sets |
Data frame of parameter sets compatible with data |
n_compatible |
Number of compatible parameter sets |
n_evaluated |
Total number of parameter sets evaluated |
falsified_proportion |
Proportion of parameter space falsified |
effect_scale |
Character: "OR", "RR", or "RD" |
misclassified_variable |
Character: "exposure" or "mediator" |
sensitivity_region |
Sensitivity region specification |
naive_estimates |
List of naive effect estimates |
bootstrap_results |
Bootstrap inference results (if computed) |
data_summary |
Summary statistics from the data |
evaluated_sets |
Data frame of every parameter set the grid search
evaluated ( |
call |
The function call that created this object |
An S7 object of class medrobust_bounds containing the partial
identification bounds for the natural direct and indirect effects, the
parameter sets compatible with the data, and the associated inference and
diagnostic summaries.
Low-level constructor for falsification_summary S7 objects. Most users should use the falsification_summary() function which analyzes bounds.
new_falsification_summary( overall, n_evaluated, n_compatible, n_falsified, by_parameter = NULL, joint_falsification = NULL, most_constrained = character(0), least_constrained = character(0), plot = NULL )new_falsification_summary( overall, n_evaluated, n_compatible, n_falsified, by_parameter = NULL, joint_falsification = NULL, most_constrained = character(0), least_constrained = character(0), plot = NULL )
overall |
Overall falsification rate |
n_evaluated |
Number of parameter sets evaluated |
n_compatible |
Number of compatible parameter sets |
n_falsified |
Number of falsified parameter sets |
by_parameter |
Parameter-specific falsification (optional) |
joint_falsification |
Joint falsification patterns (optional) |
most_constrained |
Most constrained parameters (optional) |
least_constrained |
Least constrained parameters (optional) |
plot |
ggplot2 object (optional) |
A falsification_summary S7 object
new_falsification_summary( overall = 0.25, n_evaluated = 100L, n_compatible = 75L, n_falsified = 25L )new_falsification_summary( overall = 0.25, n_evaluated = 100L, n_compatible = 75L, n_falsified = 25L )
A pooled sample of U.S. adults from NHANES used to illustrate
partial-identification bounds when a binary exposure is differentially
misclassified, without validation data. The exposure—self-reported physical
inactivity—is a textbook differentially misclassified variable: it has no
biomarker, relies on participant recall and report, and reporting accuracy
plausibly depends on health status (and thus on the outcome). The mediator,
laboratory-measured systemic inflammation (hs-CRP), is by contrast measured
without error. nhanes_pa is the exposure-side mirror image of
gesthtn (which misclassifies the mediator), completing the
mediator/exposure symmetry of the package's worked examples.
nhanes_panhanes_pa
A data frame with approximately 9,906 observations and 6 binary
variables (every column is an integer in {0, 1}):
Binary. Self-reported physical inactivity (1 = inactive, 0 = active). A misclassified (surrogate) measurement of the true exposure; "inactive" means leisure-time aerobic activity below the 2018 Physical Activity Guidelines threshold of 150 moderate-equivalent minutes per week (vigorous minutes counted double). The true activity status is not observed.
Binary. Elevated systemic inflammation, hs-CRP >= 3 mg/L (1 = elevated, 0 = not). Laboratory-measured, treated as error-free.
Binary. Prevalent cardiovascular disease (1 = any of coronary heart disease, angina, myocardial infarction, or stroke; 0 = none).
Binary. Older age (1 = age >= 50 years, 0 = age < 50).
Binary. Female sex (1 = female, 0 = male).
Binary. Obesity (1 = BMI >= 30, 0 = BMI < 30).
## Source data
Pooled NHANES 2015–2016 (_I) and 2017–2018 (_J) cycles, adults
aged 20 and older, restricted to complete cases on the six modeled binaries.
Unlike gesthtn (a fixed-seed random sample drawn from millions of
births), nhanes_pa ships the full complete-case sample with
no random sampling: the data are tiny (six binary columns), and the
complete-case construction is deterministic, so the dataset is reproducible by
construction with no seed. The eight C1 x C2 x C3 strata are
well-populated (each roughly 843–1,588), which keeps
bound_ne(confounders = c("C1", "C2", "C3")) feasible across the
differential-sensitivity sweep. Variables were derived from the PAQ
(physical-activity questionnaire), HSCRP (high-sensitivity C-reactive protein),
MCQ (medical conditions), DEMO (demographics), and BMX (body measures) files;
see system.file("scripts", "prepare_nhanes_pa.R", package = "medrobust").
## Misclassification
The misclassified variable here is a self-reported exposure in a
cross-sectional survey, so the relevant mechanism is differential
reporting: sensitivity and specificity for self-reported inactivity may
depend on the prevalent outcome (e.g., respondents with cardiovascular disease
may report activity differently from healthy respondents). This is
outcome-dependent reporting error, not case-control recall bias—there
is no retrospective recall of past exposure conditional on diagnosis. With no
validation data, an analyst can posit plausible accuracy (e.g., baseline
sensitivity and specificity each in [0.80, 0.95]) and sweep a
differential-sensitivity odds ratio () at or above 1 to probe
robustness.
CDC National Health and Nutrition Examination Survey (NHANES), public-domain U.S.-government data for cycles 2015–2016 and 2017–2018, obtained via the nhanesA package (https://cran.r-project.org/package=nhanesA). Survey home: https://www.cdc.gov/nchs/nhanes/. Activity threshold: 2018 Physical Activity Guidelines for Americans (150 moderate-equivalent min/week).
data("nhanes_pa") table(A_star = nhanes_pa$A_star, Y = nhanes_pa$Y) # Partial-identification bounds for the (differentially misclassified) exposure; # see ?bound_ne and the article # https://data-wise.github.io/medrobust/articles/nhanes_pa-bounds.html region <- sensitivity_region( sn0_range = c(0.80, 0.95), sp0_range = c(0.80, 0.95), psi_sn_range = c(1.0, 2.0), psi_sp_range = c(1.0, 1.0) )data("nhanes_pa") table(A_star = nhanes_pa$A_star, Y = nhanes_pa$Y) # Partial-identification bounds for the (differentially misclassified) exposure; # see ?bound_ne and the article # https://data-wise.github.io/medrobust/articles/nhanes_pa-bounds.html region <- sensitivity_region( sn0_range = c(0.80, 0.95), sp0_range = c(0.80, 0.95), psi_sn_range = c(1.0, 2.0), psi_sp_range = c(1.0, 1.0) )
Visualize the bootstrap distribution of bounds.
plot_bootstrap_distribution( bootstrap_results, effect = "NIE", original_bounds = NULL )plot_bootstrap_distribution( bootstrap_results, effect = "NIE", original_bounds = NULL )
bootstrap_results |
The |
effect |
Character string: "NIE" or "NDE" |
original_bounds |
Original bound estimates (optional) |
ggplot2 object
Conducts simulation-based power analysis to determine the sample size needed to achieve a target bound width or to rule out the null hypothesis with high probability.
power_analysis( true_params, dm_params, sensitivity_region, misclass_type = c("exposure", "mediator"), sample_sizes = seq(100, 1000, by = 100), target_width = NULL, target_power = 0.8, alpha = 0.05, effect = c("NIE", "NDE"), n_sim = 100, n_grid = 30, confounders = 1, parallel = TRUE, n_cores = NULL, verbose = TRUE, seed = 12345 )power_analysis( true_params, dm_params, sensitivity_region, misclass_type = c("exposure", "mediator"), sample_sizes = seq(100, 1000, by = 100), target_width = NULL, target_power = 0.8, alpha = 0.05, effect = c("NIE", "NDE"), n_sim = 100, n_grid = 30, confounders = 1, parallel = TRUE, n_cores = NULL, verbose = TRUE, seed = 12345 )
true_params |
Named list of true causal parameters (see |
dm_params |
Named list of misclassification parameters |
sensitivity_region |
Named list defining Theta_Psi for bound_ne |
misclass_type |
Character string. "exposure" or "mediator" |
sample_sizes |
Integer vector. Sample sizes to evaluate. Default is seq(100, 1000, by = 100). |
target_width |
Numeric. Target bound width. If specified, finds minimum sample size to achieve this width with high probability. Default is NULL. |
target_power |
Numeric. Target power for rejecting null. Default is 0.80. |
alpha |
Numeric. Significance level. Default is 0.05. |
effect |
Character string. Which effect to power for: "NIE" or "NDE". Default is "NIE". |
n_sim |
Integer. Number of simulation replicates per sample size. Default is 100. |
n_grid |
Integer. Grid resolution for bound_ne. Default is 30. |
confounders |
Integer. Number of confounders. Default is 1. |
parallel |
Logical. Use parallel processing? Default is TRUE. |
n_cores |
Integer. Number of cores. Default is NULL (auto-detect). |
verbose |
Logical. Print progress? Default is TRUE. |
seed |
Integer. Random seed. Default is 12345. |
An S7 object of class power_analysis_result containing:
power_curve |
Data frame with power by sample size |
true_effect |
True effect value |
target_power |
Target power level |
target_width |
Target bound width (if specified) |
recommended_n_power |
Recommended sample size for target power |
recommended_n_width |
Recommended sample size for target width |
simulation_params |
Parameters used for simulation |
# A power analysis runs many bootstrap-bound replications across several # sample sizes, so it is computationally intensive; it is wrapped in # \dontrun{} to keep checks fast. Reduce sample_sizes / n_sim to run quickly. ## Not run: # Power analysis for exposure DM with moderate effects power_result <- power_analysis( true_params = list(beta_AM = 0.405, theta_AY = 0.405, theta_MY = 0.405), dm_params = list(sn0 = 0.85, sp0 = 0.85, psi_sn = 1.5, psi_sp = 1.0), sensitivity_region = list( sn0_range = c(0.80, 0.90), sp0_range = c(0.80, 0.90), psi_sn_range = c(1.0, 2.0), psi_sp_range = c(1.0, 1.0) ), misclass_type = "exposure", sample_sizes = c(200, 400, 600, 800, 1000), target_width = 0.3, target_power = 0.80, n_sim = 100 ) print(power_result) plot(power_result) ## End(Not run)# A power analysis runs many bootstrap-bound replications across several # sample sizes, so it is computationally intensive; it is wrapped in # \dontrun{} to keep checks fast. Reduce sample_sizes / n_sim to run quickly. ## Not run: # Power analysis for exposure DM with moderate effects power_result <- power_analysis( true_params = list(beta_AM = 0.405, theta_AY = 0.405, theta_MY = 0.405), dm_params = list(sn0 = 0.85, sp0 = 0.85, psi_sn = 1.5, psi_sp = 1.0), sensitivity_region = list( sn0_range = c(0.80, 0.90), sp0_range = c(0.80, 0.90), psi_sn_range = c(1.0, 2.0), psi_sp_range = c(1.0, 1.0) ), misclass_type = "exposure", sample_sizes = c(200, 400, 600, 800, 1000), target_width = 0.3, target_power = 0.80, n_sim = 100 ) print(power_result) plot(power_result) ## End(Not run)
S7 class for storing power analysis results for partial identification bounds.
power_curve |
Data frame with power and width by sample size |
true_effect |
True effect value used in simulations |
target_power |
Target power level for sample size recommendations |
target_width |
Target bound width for sample size recommendations |
recommended_n_power |
Recommended sample size to achieve target power |
recommended_n_width |
Recommended sample size to achieve target width |
simulation_params |
List of simulation parameters used |
An S7 object of class power_analysis_result holding the power and
bound-width curves across sample sizes and the recommended sample sizes for
the target power and width.
Prints a formatted report of a falsification test returned by
check_compatibility. This is an S7 method for the base
generic; call it as print(x).
x |
An object of class |
... |
Additional arguments (currently unused). |
Invisibly returns the input object.
check_compatibility, summary.compatibility_test
Generate publication-quality visualizations of partial identification bounds as a function of sensitivity parameters.
sensitivity_plot( bounds_object, param = "psi_sn", effect = c("both", "NIE", "NDE"), plot_type = c("bounds", "heatmap", "contour"), show_naive = TRUE, show_null = TRUE, color_scheme = "default", theme = "bw", ... )sensitivity_plot( bounds_object, param = "psi_sn", effect = c("both", "NIE", "NDE"), plot_type = c("bounds", "heatmap", "contour"), show_naive = TRUE, show_null = TRUE, color_scheme = "default", theme = "bw", ... )
bounds_object |
An object of class |
param |
Character vector specifying which parameter(s) to plot on the x-axis. Options: "psi_sn", "psi_sp", "sn0", "sp0". Can specify multiple for faceted plots. |
effect |
Character string: "NIE", "NDE", or "both" (default). |
plot_type |
Character string specifying plot type:
|
show_naive |
Logical indicating whether to overlay the naive estimate (assuming no misclassification). Default is TRUE. |
show_null |
Logical indicating whether to show horizontal line at null value (effect = 1 for OR/RR, effect = 0 for RD). Default is TRUE. |
color_scheme |
Character string specifying color palette: "default", "viridis", "colorblind", "grayscale". |
theme |
Character string for ggplot2 theme: "bw" (default), "minimal", "classic", "void". |
... |
Additional arguments passed to ggplot2 functions. |
The function creates different plot types to visualize sensitivity analysis:
Bounds plot: Shows how the lower and upper bounds vary as a function of one sensitivity parameter, with other parameters held fixed or averaged.
Heatmap: Shows the width of the identification region (upper - lower) as a function of two sensitivity parameters simultaneously.
Contour plot: Shows contour lines of the bounds in 2D parameter space.
A ggplot2 object that can be further customized or saved.
# Compute bounds to plot (see ?bound_ne) sim <- simulate_dm_data( n = 8000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) set.seed(1) bounds <- bound_ne( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", sensitivity_region = list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5) ), n_grid = 10 ) # Basic bounds plot sensitivity_plot(bounds, param = "psi_sn", effect = "NIE") # Show both effects sensitivity_plot(bounds, param = "psi_sn", effect = "both") # Customize the returned ggplot object p <- sensitivity_plot(bounds, param = "psi_sn") + ggplot2::labs(title = "My Custom Title") + ggplot2::theme(legend.position = "bottom") p# Compute bounds to plot (see ?bound_ne) sim <- simulate_dm_data( n = 8000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) set.seed(1) bounds <- bound_ne( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", sensitivity_region = list( sn0_range = c(0.80, 0.99), sp0_range = c(0.80, 0.99), psi_sn_range = c(0.8, 1.5), psi_sp_range = c(0.8, 1.5) ), n_grid = 10 ) # Basic bounds plot sensitivity_plot(bounds, param = "psi_sn", effect = "NIE") # Show both effects sensitivity_plot(bounds, param = "psi_sn", effect = "both") # Customize the returned ggplot object p <- sensitivity_plot(bounds, param = "psi_sn") + ggplot2::labs(title = "My Custom Title") + ggplot2::theme(legend.position = "bottom") p
Constructor for sensitivity_region S7 objects. Issues a warning if the region may be non-informative (Sn + Sp <= 1).
sensitivity_region(sn0_range, sp0_range, psi_sn_range, psi_sp_range)sensitivity_region(sn0_range, sp0_range, psi_sn_range, psi_sp_range)
sn0_range |
Numeric vector of length 2: [min, max] for baseline sensitivity |
sp0_range |
Numeric vector of length 2: [min, max] for baseline specificity |
psi_sn_range |
Numeric vector of length 2: [min, max] for sensitivity OR |
psi_sp_range |
Numeric vector of length 2: [min, max] for specificity OR |
A sensitivity_region S7 object
region <- sensitivity_region( sn0_range = c(0.80, 0.95), sp0_range = c(0.85, 0.99), psi_sn_range = c(1, 2), psi_sp_range = c(1, 1) ) regionregion <- sensitivity_region( sn0_range = c(0.80, 0.95), sp0_range = c(0.85, 0.99), psi_sn_range = c(1, 2), psi_sp_range = c(1, 1) ) region
Generates synthetic data with known differential misclassification for power analysis, methods validation, and simulation studies. Allows control over true causal parameters and misclassification mechanisms.
simulate_dm_data( n = 500, true_params = list(beta_AM = 0.405, theta_AY = 0.405, theta_MY = 0.405), dm_params = list(sn0 = 0.85, sp0 = 0.85, psi_sn = 1.5, psi_sp = 1), misclass_type = c("exposure", "mediator"), confounders = 1, confounder_params = list(type = "binary", effect_on_A = 0.3, effect_on_M = 0.3, effect_on_Y = 0.3), effect_modification = FALSE, interaction_coef = 0, seed = NULL, return_truth = TRUE, return_params = TRUE )simulate_dm_data( n = 500, true_params = list(beta_AM = 0.405, theta_AY = 0.405, theta_MY = 0.405), dm_params = list(sn0 = 0.85, sp0 = 0.85, psi_sn = 1.5, psi_sp = 1), misclass_type = c("exposure", "mediator"), confounders = 1, confounder_params = list(type = "binary", effect_on_A = 0.3, effect_on_M = 0.3, effect_on_Y = 0.3), effect_modification = FALSE, interaction_coef = 0, seed = NULL, return_truth = TRUE, return_params = TRUE )
n |
Integer. Sample size. Default is 500. |
true_params |
Named list of true causal parameters:
|
dm_params |
Named list of misclassification parameters:
|
misclass_type |
Character string. Either "exposure" or "mediator" to indicate which variable is misclassified. Default is "exposure". |
confounders |
Integer. Number of confounding variables to include. Default is 1. Set to 0 for no confounders. |
confounder_params |
Named list controlling confounder generation:
|
effect_modification |
Logical. Should there be effect modification (interaction between A and M on Y)? Default is FALSE. |
interaction_coef |
Numeric. Interaction coefficient if effect_modification=TRUE. Default is 0. |
seed |
Integer. Random seed for reproducibility. If NULL, no seed is set. |
return_truth |
Logical. Should true (unobserved) values be returned? Default is TRUE. |
return_params |
Logical. Should true causal effects be calculated and returned? Default is TRUE. |
An S7 object of class simulated_dm_data containing:
observed |
Data frame with observed (potentially misclassified) variables |
truth |
Data frame with true (unobserved) values (if return_truth=TRUE) |
true_effects |
List of true causal effects (if return_params=TRUE) |
generation_params |
Parameters used for data generation |
misclassification_applied |
Summary of applied misclassification |
# Basic simulation with moderate effects and mild DM sim_data <- simulate_dm_data( n = 500, true_params = list( beta_AM = 0.405, # OR = 1.5 theta_AY = 0.405, # OR = 1.5 theta_MY = 0.405 # OR = 1.5 ), dm_params = list( sn0 = 0.85, sp0 = 0.85, psi_sn = 1.5, psi_sp = 1.0 ), misclass_type = "exposure", seed = 12345 ) # Use in analysis set.seed(1) bounds <- bound_ne( data = sim_data@observed, exposure = "A_star", mediator = "M", outcome = "Y", confounders = "C1", misclassified_variable = "exposure", sensitivity_region = list( sn0_range = c(0.80, 0.90), sp0_range = c(0.80, 0.90), psi_sn_range = c(1.0, 2.0), psi_sp_range = c(1.0, 1.0) ), n_grid = 10 )# Basic simulation with moderate effects and mild DM sim_data <- simulate_dm_data( n = 500, true_params = list( beta_AM = 0.405, # OR = 1.5 theta_AY = 0.405, # OR = 1.5 theta_MY = 0.405 # OR = 1.5 ), dm_params = list( sn0 = 0.85, sp0 = 0.85, psi_sn = 1.5, psi_sp = 1.0 ), misclass_type = "exposure", seed = 12345 ) # Use in analysis set.seed(1) bounds <- bound_ne( data = sim_data@observed, exposure = "A_star", mediator = "M", outcome = "Y", confounders = "C1", misclassified_variable = "exposure", sensitivity_region = list( sn0_range = c(0.80, 0.90), sp0_range = c(0.80, 0.90), psi_sn_range = c(1.0, 2.0), psi_sp_range = c(1.0, 1.0) ), n_grid = 10 )
S7 class for storing simulated data with known differential misclassification, used for power analysis and methods validation.
observed |
Data frame with observed (potentially misclassified) variables |
truth |
Data frame with true (unobserved) values |
true_effects |
List of true causal effects |
generation_params |
List of parameters used to generate the data |
misclassification_applied |
List of misclassification parameters applied |
An S7 object of class simulated_dm_data holding the simulated
observed data together with the true (unobserved) values, the true causal
effects, and the data-generating parameters.
Prints a detailed summary of a falsification test returned by
check_compatibility. This is an S7 method for the base
generic; call it as summary(object).
object |
An object of class |
... |
Additional arguments (currently unused). |
Invisibly returns the input object.
check_compatibility, print.compatibility_test
Test multiple hypotheses about misclassification parameters simultaneously.
test_multiple_hypotheses( data, exposure, mediator, outcome, confounders, psi_list, misclassified_variable )test_multiple_hypotheses( data, exposure, mediator, outcome, confounders, psi_list, misclassified_variable )
data |
Data frame |
exposure |
Character string |
mediator |
Character string |
outcome |
Character string |
confounders |
Character vector |
psi_list |
List of parameter sets to test, each a list with |
misclassified_variable |
Character string |
Data frame with test results for each hypothesis
sim <- simulate_dm_data( n = 2000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) # psi_list must be named: the names label the rows of the result test_multiple_hypotheses( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", psi_list = list( accurate = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), poor = list(sn0 = 0.6, sp0 = 0.6, psi_sn = 1, psi_sp = 1) ) )sim <- simulate_dm_data( n = 2000, true_params = list(beta_AM = log(2.5), theta_AY = log(1.5), theta_MY = log(2.5)), dm_params = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), misclass_type = "mediator", confounders = 1, seed = 1 ) # psi_list must be named: the names label the rows of the result test_multiple_hypotheses( data = sim@observed, exposure = "A", mediator = "M_star", outcome = "Y", confounders = "C1", misclassified_variable = "mediator", psi_list = list( accurate = list(sn0 = 0.9, sp0 = 0.9, psi_sn = 1, psi_sp = 1), poor = list(sn0 = 0.6, sp0 = 0.6, psi_sn = 1, psi_sp = 1) ) )