|
40 | 40 | #' \item `SE`: Standard Error (Note: For S, this is SE of log(S)). |
41 | 41 | #' \item `CI_low`: Lower confidence interval bound. |
42 | 42 | #' \item `CI_upp`: Upper confidence interval bound. |
| 43 | +#' \item `Scale`: The ratio scale the measures were computed on, inferred |
| 44 | +#' from the model: "OR (logistic)", "HR (Cox)", "RR (log-binomial)", |
| 45 | +#' "RR (Poisson)", or "ratio". |
43 | 46 | #' } |
44 | 47 | #' Returns an empty tibble if errors occur. |
45 | 48 | #' |
| 49 | +#' @details |
| 50 | +#' RERI, AP, and the synergy index S are additive-interaction measures defined on |
| 51 | +#' the \emph{risk-ratio} scale. When the model reports odds ratios (logistic) or |
| 52 | +#' hazard ratios (Cox), these measures only approximate their risk-ratio |
| 53 | +#' counterparts when the outcome is rare; for a common outcome the odds ratio |
| 54 | +#' overstates the risk ratio and RERI/AP/S can be biased. The `Scale` column makes |
| 55 | +#' the operating scale explicit. To estimate risk-ratio-based interaction |
| 56 | +#' directly for a common outcome, fit a log-binomial or Poisson working model. |
| 57 | +#' |
46 | 58 | #' @seealso \code{\link{addint}} |
47 | 59 | #' |
48 | 60 | #' @importFrom stats coef |
|
55 | 67 | #' |
56 | 68 | #' @examples |
57 | 69 | #' \donttest{ |
58 | | -#' # --- Load required libraries for the example --- |
59 | | -#' # Ensure the 'addint' function is defined or loaded from the package |
60 | | -#' # source("R/addint.R") # If running interactively before package build |
61 | | -#' |
62 | | -#' if (requireNamespace("survey", quietly = TRUE) && |
63 | | -#' requireNamespace("NHANES", quietly = TRUE) && |
64 | | -#' requireNamespace("dplyr", quietly = TRUE) && |
65 | | -#' requireNamespace("tidyr", quietly = TRUE) && |
66 | | -#' requireNamespace("msm", quietly = TRUE)) { |
67 | | -#' |
68 | | -#' library(survey) |
69 | | -#' library(NHANES) |
70 | | -#' library(dplyr) |
71 | | -#' library(tidyr) |
72 | | -#' library(msm) |
73 | | -#' |
74 | | -#' # --- 1. Data Preparation (NHANES Example) --- |
75 | | -#' data(NHANESraw) |
76 | | -#' |
77 | | -#' vars_needed <- c("Age", "Race1", "BPSysAve", "BMI", "ObeseStatus", "Hypertension_130", |
78 | | -#' "SDMVPSU", "SDMVSTRA", "WTMEC2YR") |
79 | | -#' |
80 | | -#' nhanes_adults_processed <- NHANESraw %>% |
81 | | -#' filter(Age >= 20) %>% |
82 | | -#' mutate( |
83 | | -#' ObeseStatus = factor(ifelse(BMI >= 30, "Obese", "Not Obese"), |
84 | | -#' levels = c("Not Obese", "Obese")), |
85 | | -#' Hypertension_130 = factor(ifelse(BPSysAve >= 130, "Yes", "No"), |
86 | | -#' levels = c("No", "Yes")), |
87 | | -#' Race1 = relevel(as.factor(Race1), ref = "White") |
88 | | -#' ) %>% |
89 | | -#' select(all_of(vars_needed)) %>% |
90 | | -#' drop_na() |
| 70 | +#' data(nhanes_mortality, package = "svyTable1") |
| 71 | +#' nhanes_mortality$htn01 <- as.numeric(nhanes_mortality$htn == "Yes") |
91 | 72 | #' |
92 | | -#' adult_design_binary <- svydesign( |
93 | | -#' id = ~SDMVPSU, strata = ~SDMVSTRA, weights = ~WTMEC2YR, |
94 | | -#' nest = TRUE, data = nhanes_adults_processed |
95 | | -#' ) |
| 73 | +#' design <- survey::svydesign( |
| 74 | +#' id = ~psu, strata = ~strata, weights = ~survey_weight, |
| 75 | +#' nest = TRUE, data = nhanes_mortality |
| 76 | +#' ) |
96 | 77 | #' |
97 | | -#' # --- 2. Fit Interaction Term Model --- |
98 | | -#' interaction_model_logit <- svyglm( |
99 | | -#' Hypertension_130 ~ Race1 * ObeseStatus + Age, |
100 | | -#' design = adult_design_binary, family = quasibinomial() |
101 | | -#' ) |
| 78 | +#' # Saturated interaction model of sex and insulin use. |
| 79 | +#' interaction_model <- survey::svyglm( |
| 80 | +#' htn01 ~ sex * insulin + age, |
| 81 | +#' design = design, family = quasibinomial() |
| 82 | +#' ) |
102 | 83 | #' |
103 | | -#' # --- 3. Calculate all additive interactions --- |
104 | | -#' all_interactions_table <- addintlist( |
105 | | -#' model = interaction_model_logit, |
106 | | -#' factor1_name = "Race1", |
107 | | -#' factor2_name = "ObeseStatus", |
108 | | -#' measures = "all" |
109 | | -#' ) |
110 | | -#' |
111 | | -#' # Print the results table |
112 | | -#' print(all_interactions_table, n=50) |
113 | | -#' |
114 | | -#' } else { |
115 | | -#' print("Required packages (survey, NHANES, dplyr, tidyr, msm) not found.") |
116 | | -#' } |
| 84 | +#' # RERI, AP and S for every factor-level combination. The Scale column flags |
| 85 | +#' # that these are OR-based (see Details for the rare-outcome caveat). |
| 86 | +#' addintlist( |
| 87 | +#' model = interaction_model, |
| 88 | +#' factor1_name = "sex", |
| 89 | +#' factor2_name = "insulin", |
| 90 | +#' measures = "all" |
| 91 | +#' ) |
117 | 92 | #' } |
118 | 93 | addintlist <- function(model, |
119 | 94 | factor1_name, |
@@ -307,10 +282,29 @@ addintlist <- function(model, |
307 | 282 | } |
308 | 283 |
|
309 | 284 | # --- Final structure --- |
| 285 | + # The Scale column makes explicit which ratio measure RERI/AP/S were built |
| 286 | + # from (OR for logistic, HR for Cox, RR for log-binomial/Poisson), since these |
| 287 | + # additive-interaction measures are only RR-scale-correct when the outcome is |
| 288 | + # rare (see Details). |
310 | 289 | final_table <- final_table %>% |
311 | | - # Use .data pronoun |
| 290 | + dplyr::mutate(Scale = .effect_measure_scale(model)) %>% |
312 | 291 | dplyr::relocate("Factor1", "Level1", "Factor2", "Level2", |
313 | | - "Measure", "Estimate", "SE", "CI_low", "CI_upp") |
| 292 | + "Measure", "Estimate", "SE", "CI_low", "CI_upp", "Scale") |
314 | 293 |
|
315 | 294 | return(final_table) |
316 | 295 | } |
| 296 | + |
| 297 | +# Internal: infer the ratio scale that RERI/AP/S were computed on, from the |
| 298 | +# fitted model's class/family. Not exported. |
| 299 | +.effect_measure_scale <- function(model) { |
| 300 | + cl <- class(model)[1] |
| 301 | + if (cl %in% c("svycoxph", "coxph")) return("HR (Cox)") |
| 302 | + fam <- tryCatch(stats::family(model)$family, error = function(e) NA_character_) |
| 303 | + link <- tryCatch(stats::family(model)$link, error = function(e) NA_character_) |
| 304 | + if (!is.na(fam)) { |
| 305 | + if (grepl("binomial", fam) && identical(link, "logit")) return("OR (logistic)") |
| 306 | + if (grepl("binomial", fam) && identical(link, "log")) return("RR (log-binomial)") |
| 307 | + if (grepl("poisson", fam)) return("RR (Poisson)") |
| 308 | + } |
| 309 | + "ratio" |
| 310 | +} |
0 commit comments