Skip to main content

real-world-evidence

Real-world evidence analysis in R, including target trial emulation, propensity scores, external controls, and bias analysis.

설치로 이동

소스 정보

저장소
choxos/BiostatAgent
최근 소스 활동
2026년 5월 27일 20:44
감지된 SKILL.md 언어
영어
스타
11
포크
1

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
real-world-evidence
description
Real-world evidence analysis in R, including target trial emulation, propensity scores, external controls, and bias analysis.
# Real-World Evidence Analysis in R ## Overview Methods for analyzing real-world data (RWD) to generate real-world evidence (RWE). Covers target trial emulation, comparative effectiveness research, propensity score methods for observational data, external control arms, bias quantification, and sensitivity analysis for unmeasured confounding. ## Target Trial Emulation ### Conceptual Framework ```r # Target trial emulation framework # Specify the target trial protocol, then emulate using observational data # Key elements to specify: # 1. Eligibility criteria # 2. Treatment strategies # 3. Assignment procedures # 4. Follow-up period # 5. Outcome # 6. Causal contrast (ITT, per-protocol, etc.) # 7. Analysis plan ``` ### Using TrialEmulation Package ```r library(TrialEmulation) # Prepare data for target trial emulation # Data should be in long format with time-varying covariates # Example: Clone-censor-weight approach trial_data <- initiators( data = rwd, id = "patient_id", period = "period", treatment = "treatment", outcome = "outcome", eligible = "eligible", outcome_cov = ~ age + sex + comorbidity, model_var = "assigned_treatment", switch_d_cov = ~ time_since_start + lag_outcome, first_period = 1, last_period = 52, use_censor_weights = TRUE ) # Fit the model fit <- trial_msm( trial_data, outcome_cov = ~ assigned_treatment * poly(follow_up, 2), model_var = "assigned_treatment", include_followup_time = TRUE, include_trial_period = TRUE, use_sample_weights = TRUE, analysis_weights = "asis" ) summary(fit) ``` ### Sequential Trial Emulation ```r # Emulate multiple trials starting at different time points # Pool results across trials # Step 1: Identify eligible patients at each time point rwd_expanded <- rwd |> group_by(patient_id) |> mutate( # Check eligibility at each visit eligible = check_eligibility(age, lab_values, prior_treatment), # Time zero for each potential trial trial_start = if_else(eligible & treatment_initiated, visit_date, NA) ) |> ungroup() # Step 2: Clone patients for each trial they're eligible for # Step 3: Apply artificial censoring per assigned strategy # Step 4: Weight for selection and artificial censoring # Step 5: Pool and analyze ``` ## Propensity Score Methods for RWE ### Propensity Score Estimation ```r library(MatchIt) library(WeightIt) library(cobalt) # Estimate propensity scores ps_model <- glm( treatment ~ age + sex + bmi + smoking + diabetes + prior_hosp + baseline_egfr + acei_use, family = binomial, data = rwd ) rwd$ps <- predict(ps_model, type = "response") # Check positivity (overlap) ggplot(rwd, aes(x = ps, fill = factor(treatment))) + geom_density(alpha = 0.5) + labs(x = "Propensity Score", fill = "Treatment") + theme_bw() ``` ### Inverse Probability Weighting (IPW) ```r library(WeightIt) # ATT weights (Average Treatment Effect on the Treated) weights_att <- weightit( treatment ~ age + sex + bmi + smoking + diabetes + prior_hosp + baseline_egfr, data = rwd, method = "ps", estimand = "ATT" ) # ATE weights (Average Treatment Effect) weights_ate <- weightit( treatment ~ age + sex + bmi + smoking + diabetes, data = rwd, method = "ps", estimand = "ATE" ) # Check balance library(cobalt) bal.tab(weights_att, stats = c("m", "v", "ks")) love.plot(weights_att, threshold = 0.1) # Weighted analysis library(survey) design <- svydesign(ids = ~1, weights = ~weights_att$weights, data = rwd) fit_weighted <- svyglm(outcome ~ treatment, design = design, family = binomial) summary(fit_weighted) ``` ### Overlap Weighting ```r library(PSweight) # Overlap weights (target population with equipoise) ow_result <- PSweight( ps.formula = treatment ~ age + sex + bmi + smoking + diabetes, data = rwd, yname = "outcome", weight = "overlap" ) summary(ow_result) ``` ### Propensity Score Matching ```r library(MatchIt) # 1:1 nearest neighbor matching match_nn <- matchit( treatment ~ age + sex + bmi + smoking + diabetes + baseline_egfr, data = rwd, method = "nearest", distance = "glm", caliper = 0.2, ratio = 1, replace = FALSE ) summary(match_nn) plot(match_nn, type = "jitter") # Get matched data matched_data <- match.data(match_nn) # Analyze matched data fit_matched <- glm(outcome ~ treatment, family = binomial, data = matched_data, weights = weights) ``` ### Doubly Robust Estimation ```r library(AIPW) # Doubly robust estimator aipw_result <- AIPW$new( Y = rwd$outcome, A = rwd$treatment, W = rwd |> select(age, sex, bmi, smoking, diabetes, baseline_egfr), Q.SL.library = c("SL.glm", "SL.ranger", "SL.xgboost"), g.SL.library = c("SL.glm", "SL.ranger"), k_split = 5, verbose = FALSE ) aipw_result$fit()$summary() # Risk difference and risk ratio aipw_result$summary() ``` ## External Control Arms ### Historical Controls Integration ```r # Combine trial data with external controls # Step 1: Identify comparable external controls external_controls <- rwd |> filter( # Match eligibility criteria age >= 18 & age <= 75, egfr >= 30, no_prior_treatment == TRUE ) # Step 2: Propensity score matching/weighting library(MatchIt) combined <- bind_rows( trial_data |> mutate(source = "trial", treatment = treatment), external_controls |> mutate(source = "external", treatment = 0) ) # Match external controls to trial control arm characteristics match_ext <- matchit( (source == "trial" & treatment == 0) ~ age + sex + stage + biomarker, data = combined |> filter(treatment == 0 | source == "external"), method = "nearest", caliper = 0.2 ) # Step 3: Analysis with matched external controls matched_combined <- match.data(match_ext) ``` ### Propensity Score Integration ```r library(WeightIt) # Weight external controls to match trial population weights_ext <- weightit( source == "trial" ~ age + sex + stage + biomarker + region, data = combined, method = "ebal", # Entropy balancing estimand = "ATT" ) # Check balance bal.tab(weights_ext) # Weighted analysis library(survival) fit_ext <- coxph( Surv(time, event) ~ treatment + source, data = combined, weights = weights_ext$weights ) ``` ## Inverse Probability of Censoring Weighting ```r # Handle informative censoring in RWD library(ipw) # Model censoring probability cens_model <- glm( censored ~ time_period + treatment + age + prior_event, family = binomial, data = rwd_long ) # Calculate IPCW rwd_long <- rwd_long |> group_by(patient_id) |> mutate( p_uncensored = 1 - predict(cens_model, type = "response"), ipcw = cumprod(p_uncensored) ) |> ungroup() # Stabilized weights rwd_long <- rwd_long |> mutate( sw = ps_weight * ipcw / mean(ps_weight * ipcw, na.rm = TRUE) ) # Weighted analysis with stabilized weights library(survey) design_ipcw <- svydesign(ids = ~patient_id, weights = ~sw, data = rwd_long) ``` ## Time-Varying Confounding and MSMs ```r library(ipw) # Marginal Structural Model for time-varying treatment # Step 1: Estimate time-varying treatment weights temp_weights <- ipwtm( exposure = treatment, family = "binomial", link = "logit", numerator = ~ 1, denominator = ~ age + cd4 + viral_load + prior_oi, id = patient_id, timevar = visit, type = "first", data = rwd_long ) # Step 2: Estimate censoring weights temp_cens <- ipwtm( exposure = censored, family = "binomial", link = "logit", numerator = ~ 1, denominator = ~ age + cd4 + viral_load + treatment, id = patient_id, timevar = visit, type = "first", data = rwd_long ) # Step 3: Combine weights rwd_long$msm_weight <- temp_weights$ipw.weights * temp_cens$ipw.weights # Step 4: Truncate extreme weights rwd_long$msm_weight_trunc <- pmin(rwd_long$msm_weight, quantile(rwd_long$msm_weight, 0.99)) # Step 5: Fit MSM library(geepack) msm_fit <- geeglm( outcome ~ treatment + time, id = patient_id, weights = msm_weight_trunc, corstr = "independence", family = binomial, data = rwd_long ) ``` ## Adjusted Survival Curves ```r library(adjustedCurves) # G-computation adjusted survival curves adj_surv <- adjustedsurv( data = rwd, variable = "treatment", ev_time = "time", event = "event", method = "direct", outcome_model = coxph(Surv(time, event) ~ treatment + age + sex + stage, data = rwd, x = TRUE), conf_int = TRUE, bootstrap = TRUE, n_boot = 500 ) # Plot plot(adj_surv) + labs(x = "Time (months)", y = "Survival Probability", title = "Adjusted Survival Curves") # IPTW-adjusted survival adj_surv_iptw <- adjustedsurv( data = rwd, variable = "treatment", ev_time = "time", event = "event", method = "iptw_km", treatment_model = glm(treatment ~ age + sex + stage, family = binomial, data = rwd), conf_int = TRUE ) ``` ## Sensitivity Analysis for Unmeasured Confounding ### E-values ```r library(EValue) # E-value for point estimate and confidence interval evalue_result <- evalues.RR( est = 1.8, # Observed risk ratio lo = 1.3, # Lower CI bound hi = 2.5 # Upper CI bound ) print(evalue_result) # Interpretation: An unmeasured confounder would need to be # associated with both treatment and outcome by RR of [E-value] # to explain away the observed association # Visualize bias_plot(evalue_result) ``` ### Tip Analysis ```r library(tipr) # Tipping point analysis tip_result <- tip_coef( effect_observed = log(1.8), # Observed log(RR) exposure_confounder_effect = seq(1, 3, 0.1), confounder_outcome_effect = seq(1, 3, 0.1), verbose = TRUE ) # Visualize tipping points plot(tip_result) # Identify combinations that would tip result tip_result |> filter(adjusted_effect < 0) # Where effect becomes null/protective ``` ### Quantitative Bias Analysis ```r library(episensr) # Probabilistic bias analysis for unmeasured confounding set.seed(123) # Observed data obs_table <- matrix(c(100, 50, 80, 120), nrow = 2) # Bias parameters (distributions) pba_result <- probsens.conf( obs_table, reps = 10000, seca.parms = list("trapezoidal", c(0.7, 0.8, 0.9, 0.95)), # Sensitivity spca.parms = list("trapezoidal", c(0.8, 0.85, 0.95, 1.0)) # Specificity ) summary(pba_result) ``` ## Data Quality Assessment ```r # RWD quality checks # 1. Completeness completeness <- rwd |> summarise(across(everything(), ~mean(!is.na(.)) * 100)) |> pivot_longer(everything(), names_to = "variable", values_to = "pct_complete") # 2. Plausibility plausibility_checks <- rwd |> summarise( age_range_ok = all(age >= 0 & age <= 120), dates_consistent = all(discharge_date >= admission_date, na.rm = TRUE), lab_in_range = all(egfr >= 0 & egfr <= 200, na.rm = TRUE) ) # 3. Temporal patterns rwd |> count(year = year(index_date)) |> ggplot(aes(year, n)) + geom_col() + labs(title = "Patient Enrollment Over Time") # 4. Treatment patterns rwd |> group_by(treatment) |> summarise( n = n(), mean_age = mean(age),
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기