Clinical Research Visualization with jvisr
Fit-for-Purpose Clinical Visualizations Using the visR Package
ClinicoPath
last-modified
Source:vignettes/jjstatsplot-35-jvisr-comprehensive.Rmd
jjstatsplot-35-jvisr-comprehensive.Rmd
Introduction
The jvisr
function provides professional clinical
research visualizations using the visR package with sensible defaults
based on graphical principles. It’s designed specifically for clinical
and medical research applications, supporting multiple analysis types
including survival analysis, cumulative incidence, patient flow charts,
and clinical summaries.
What is visR?
visR is an R package that provides a grammar of graphics implementation focused on the visualization needs of clinical development and medical research. It offers:
- Fit-for-purpose plots: Designed specifically for clinical research contexts
- Regulatory compliance: Follows industry standards for clinical trial reporting
- CDISC integration: Native support for CDISC ADaM data standards
- Consistent styling: Professional, publication-ready visualizations
- Statistical rigor: Proper handling of censoring, competing risks, and confidence intervals
Key Features of jvisr
- Multiple Analysis Types: Kaplan-Meier curves, cumulative incidence, Table One summaries, attrition flowcharts, and risk tables
- CDISC Support: Native support for CDISC ADaM ADTTE data format
- Advanced Customization: Extensive options for themes, colors, labels, and statistical displays
- Performance Optimized: Intelligent caching for large clinical datasets
- Clinical Focus: Designed specifically for medical and clinical research applications
Installation and Setup
# Load required libraries
library(ClinicoPath)
## Warning: replacing previous import 'dplyr::as_data_frame' by
## 'igraph::as_data_frame' when loading 'ClinicoPath'
## Warning: replacing previous import 'DiagrammeR::count_automorphisms' by
## 'igraph::count_automorphisms' when loading 'ClinicoPath'
## Warning: replacing previous import 'dplyr::groups' by 'igraph::groups' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'DiagrammeR::get_edge_ids' by
## 'igraph::get_edge_ids' when loading 'ClinicoPath'
## Warning: replacing previous import 'dplyr::union' by 'igraph::union' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'dplyr::select' by 'jmvcore::select' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'igraph::union' by 'lubridate::union' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'igraph::%--%' by 'lubridate::%--%' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::tnr' by 'mlr3measures::tnr' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::precision' by
## 'mlr3measures::precision' when loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::tn' by 'mlr3measures::tn' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::fnr' by 'mlr3measures::fnr' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::tp' by 'mlr3measures::tp' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::npv' by 'mlr3measures::npv' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::ppv' by 'mlr3measures::ppv' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::auc' by 'mlr3measures::auc' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::tpr' by 'mlr3measures::tpr' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::fn' by 'mlr3measures::fn' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::fp' by 'mlr3measures::fp' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::fpr' by 'mlr3measures::fpr' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::recall' by
## 'mlr3measures::recall' when loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::specificity' by
## 'mlr3measures::specificity' when loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::sensitivity' by
## 'mlr3measures::sensitivity' when loading 'ClinicoPath'
## Warning: replacing previous import 'igraph::as_data_frame' by
## 'tibble::as_data_frame' when loading 'ClinicoPath'
## Warning: replacing previous import 'igraph::crossing' by 'tidyr::crossing' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'magrittr::extract' by 'tidyr::extract' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'mlr3measures::sensitivity' by
## 'caret::sensitivity' when loading 'ClinicoPath'
## Warning: replacing previous import 'mlr3measures::specificity' by
## 'caret::specificity' when loading 'ClinicoPath'
## Registered S3 methods overwritten by 'useful':
## method from
## autoplot.acf ggfortify
## fortify.acf ggfortify
## fortify.kmeans ggfortify
## fortify.ts ggfortify
## Warning: replacing previous import 'jmvcore::select' by 'dplyr::select' when
## loading 'ClinicoPath'
## Registered S3 methods overwritten by 'ggpp':
## method from
## heightDetails.titleGrob ggplot2
## widthDetails.titleGrob ggplot2
## Warning: replacing previous import 'DataExplorer::plot_histogram' by
## 'grafify::plot_histogram' when loading 'ClinicoPath'
## Warning: replacing previous import 'dplyr::select' by 'jmvcore::select' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'mlr3measures::auc' by 'pROC::auc' when
## loading 'ClinicoPath'
## Warning: replacing previous import 'cutpointr::roc' by 'pROC::roc' when loading
## 'ClinicoPath'
## Warning: replacing previous import 'tibble::view' by 'summarytools::view' when
## loading 'ClinicoPath'
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
##
## Attaching package: 'survival'
## The following object is masked from 'package:ClinicoPath':
##
## colon
# Set options for better output
options(digits = 3)
knitr::opts_chunk$set(
fig.width = 12,
fig.height = 8,
dpi = 300,
echo = TRUE,
eval = FALSE,
out.width = "100%"
)
# Check if packages are available
if (!requireNamespace("survival", quietly = TRUE)) {
message("Note: survival package required for clinical analysis")
}
if (!requireNamespace("visR", quietly = TRUE)) {
message("Note: visR package provides enhanced clinical visualizations")
}
## Note: visR package provides enhanced clinical visualizations
Data Preparation
Let’s create realistic clinical datasets for different types of analysis:
Clinical Trial Data
# Create clinical trial dataset
set.seed(42)
clinical_trial <- data.frame(
# Patient identifiers
patient_id = paste0("PT_", sprintf("%03d", 1:300)),
# Treatment groups
treatment_arm = factor(sample(c("Placebo", "Low_Dose", "High_Dose"),
300, replace = TRUE, prob = c(0.35, 0.35, 0.3))),
study_site = factor(sample(c("Site_A", "Site_B", "Site_C"), 300, replace = TRUE)),
# Patient characteristics
age = round(rnorm(300, 58, 15)),
gender = factor(sample(c("Male", "Female"), 300, replace = TRUE)),
baseline_severity = factor(sample(c("Mild", "Moderate", "Severe"),
300, replace = TRUE, prob = c(0.4, 0.4, 0.2))),
# CDISC ADaM ADTTE variables
AVAL = rexp(300, 0.1), # Analysis Value (time to event)
CNSR = rbinom(300, 1, 0.4), # Censor indicator (1=censored, 0=event)
PARAM = factor(rep("Overall Survival", 300)),
PARAMCD = factor(rep("OS", 300)),
# Standard survival variables
time_to_event = rexp(300, 0.1),
event_indicator = rbinom(300, 1, 0.6),
# Biomarkers
biomarker_positive = factor(sample(c("Positive", "Negative"), 300, replace = TRUE, prob = c(0.4, 0.6))),
pdl1_expression = pmax(0, pmin(100, rnorm(300, 25, 15))),
# Quality of life
baseline_qol = round(pmax(0, pmin(100, rnorm(300, 70, 15)))),
qol_change = rnorm(300, 5, 12)
) %>%
mutate(
# Create realistic dose-response relationships
time_to_event = case_when(
treatment_arm == "Placebo" ~ rexp(300, 0.12),
treatment_arm == "Low_Dose" ~ rexp(300, 0.08),
treatment_arm == "High_Dose" ~ rexp(300, 0.06)
) + rnorm(300, 0, 2),
# Ensure positive survival times
time_to_event = pmax(0.1, time_to_event),
# AVAL should match time_to_event for OS endpoint
AVAL = time_to_event,
# Event indicators should be consistent
event_indicator = ifelse(CNSR == 1, 0, 1), # Convert CNSR to event indicator
# Add realistic biomarker effects
time_to_event = time_to_event * ifelse(biomarker_positive == "Positive", 1.3, 1.0),
# Age effects
age = pmax(18, pmin(85, age))
)
# Display data structure
str(clinical_trial)
Real-World Evidence Data
# Create real-world evidence dataset
rwe_data <- data.frame(
patient_id = paste0("RWE_", sprintf("%04d", 1:500)),
# More heterogeneous treatment patterns
treatment_line = factor(sample(1:4, 500, replace = TRUE, prob = c(0.4, 0.3, 0.2, 0.1))),
treatment_class = factor(sample(c("Chemotherapy", "Immunotherapy", "Targeted", "Combination"),
500, replace = TRUE, prob = c(0.3, 0.25, 0.25, 0.2))),
# Healthcare system variables
hospital_type = factor(sample(c("Academic", "Community", "Cancer_Center"),
500, replace = TRUE, prob = c(0.3, 0.5, 0.2))),
insurance_type = factor(sample(c("Private", "Medicare", "Medicaid"),
500, replace = TRUE, prob = c(0.5, 0.35, 0.15))),
# More complex patient population
age = round(rnorm(500, 65, 18)),
comorbidity_count = rpois(500, 2.5),
# Survival outcomes
time_to_death = rweibull(500, 1.1, 20),
death_observed = rbinom(500, 1, 0.45),
time_to_progression = rweibull(500, 1.5, 8),
progression_observed = rbinom(500, 1, 0.75)
) %>%
mutate(
# Create line-of-therapy effects
time_to_death = case_when(
treatment_line == 1 ~ rweibull(500, 1.3, 25),
treatment_line == 2 ~ rweibull(500, 1.2, 18),
treatment_line == 3 ~ rweibull(500, 1.1, 12),
treatment_line == 4 ~ rweibull(500, 1.0, 8)
),
time_to_death = pmax(0.5, time_to_death),
age = pmax(18, pmin(95, age))
)
str(rwe_data)
Basic Usage
Kaplan-Meier Survival Analysis
The most common clinical visualization is the Kaplan-Meier survival curve:
# Basic Kaplan-Meier analysis
km_basic <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator"
)
# The result contains the clinical visualization
print(names(km_basic))
Stratified Analysis
Compare survival across treatment groups:
# Stratified Kaplan-Meier analysis
km_stratified <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm"
)
Understanding the Clinical Output
A clinical Kaplan-Meier plot shows: - Survival curves: Probability of survival over time - Confidence intervals: Uncertainty bounds (default: 95%) - Risk tables: Numbers at risk at each time point - P-values: Statistical comparison between groups (log-rank test) - Median survival: Time when 50% of patients have experienced the event
CDISC Format Support
For regulatory submissions and clinical trials following CDISC standards:
# CDISC ADaM ADTTE format
cdisc_analysis <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
cdisc_format = TRUE,
aval_var = "AVAL",
cnsr_var = "CNSR",
strata_var = "treatment_arm"
)
The CDISC format uses: - AVAL: Analysis Value (time to event) - CNSR: Censor indicator (1=censored, 0=event) - PARAM/PARAMCD: Parameter descriptions and codes
Advanced Clinical Visualizations
Cumulative Incidence Analysis
For competing risks scenarios:
# Cumulative incidence analysis
cuminc_analysis <- jvisr(
data = clinical_trial,
analysis_type = "cuminc",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm"
)
Cumulative incidence plots are essential when: - Multiple types of events can occur - Events prevent observation of the primary endpoint - Competing risks are present (death vs. disease progression)
Table One Summaries
Generate comprehensive baseline characteristics tables:
# Table One clinical summary
tableone_analysis <- jvisr(
data = clinical_trial,
analysis_type = "tableone"
)
Table One summaries include: - Demographics: Age, gender, race/ethnicity distributions - Clinical characteristics: Disease stage, performance status, biomarkers - Treatment assignment: Randomization and stratification factors - Baseline assessments: Laboratory values, quality of life scores
Attrition Flowcharts
Visualize patient flow through clinical studies:
# Patient attrition flowchart
attrition_analysis <- jvisr(
data = clinical_trial,
analysis_type = "attrition"
)
Attrition charts show: - Enrollment: Total patients screened and enrolled - Randomization: Treatment assignment and stratification - Follow-up: Patients completing study procedures - Analysis populations: ITT, per-protocol, safety populations
Risk Tables
Detailed risk table analysis:
# Risk table analysis
risktable_analysis <- jvisr(
data = clinical_trial,
analysis_type = "risktable",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm"
)
Customization Options
Confidence Intervals and Statistical Display
Control statistical elements:
# Enhanced statistical display
enhanced_km <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
confidence_interval = TRUE,
p_value = TRUE,
quantiles = TRUE,
risk_table = TRUE
)
Statistical options include: - Confidence intervals: Show uncertainty bounds around survival estimates - P-values: Log-rank test for group comparisons - Quantiles: Median and quartile survival times - Risk tables: Numbers at risk, events, and survival probabilities
Function Scale Options
Different survival function transformations:
# Survival probability (default)
surv_prob <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
fun_type = "surv"
)
# Event probability (1 - survival)
event_prob <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
fun_type = "event"
)
# Cumulative hazard
cumhaz <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
fun_type = "cumhaz"
)
# Log cumulative hazard
cloglog <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
fun_type = "cloglog"
)
Function scales help with: - Survival: Standard clinical interpretation - Event: Focus on event occurrence - Cumulative hazard: Assess hazard function shape - Log cumulative hazard: Evaluate proportional hazards assumption
Professional Themes and Styling
Apply clinical research themes:
# visR clinical theme (default)
visr_theme <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
theme_style = "visr"
)
# Classic statistical graphics
classic_theme <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
theme_style = "classic"
)
# Minimal clean style
minimal_theme <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
theme_style = "minimal"
)
# Clean black and white
clean_theme <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
theme_style = "clean"
)
Color Palettes for Clinical Research
Choose appropriate color schemes:
# ColorBrewer Set1 (good for group comparisons)
set1_colors <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
color_palette = "Set1"
)
# ColorBrewer Set2 (softer colors)
set2_colors <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
color_palette = "Set2"
)
# Dark2 (high contrast)
dark2_colors <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
color_palette = "Dark2"
)
# Paired colors (for before/after comparisons)
paired_colors <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
color_palette = "Paired"
)
Custom Labels and Annotations
Add clinical context with proper labeling:
# Comprehensive labeling
labeled_analysis <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
title = "Phase III Clinical Trial: Overall Survival Analysis",
time_label = "Time from Randomization",
time_units = "months",
survival_label = "Overall Survival Probability",
confidence_interval = TRUE,
risk_table = TRUE,
p_value = TRUE
)
Professional labeling includes: - Descriptive titles: Study phase, endpoint, population - Axis labels: Clear, clinically meaningful descriptions - Time units: Specify measurement scale (days, months, years) - Statistical annotations: P-values, confidence levels, sample sizes
Real-World Applications
Multi-arm Clinical Trial Analysis
Primary Efficacy Endpoint
# Primary efficacy analysis
primary_efficacy <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
title = "Primary Efficacy Analysis: Overall Survival",
time_label = "Time from Randomization",
time_units = "months",
survival_label = "Overall Survival Probability",
confidence_interval = TRUE,
risk_table = TRUE,
p_value = TRUE,
quantiles = TRUE,
theme_style = "visr"
)
Biomarker Subgroup Analysis
# Biomarker-stratified analysis
biomarker_analysis <- jvisr(
data = clinical_trial[clinical_trial$biomarker_positive == "Positive", ],
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
title = "Biomarker-Positive Population: Overall Survival",
time_label = "Time from Randomization",
time_units = "months",
survival_label = "Overall Survival Probability",
confidence_interval = TRUE,
risk_table = TRUE,
p_value = TRUE,
theme_style = "visr"
)
Safety Population Analysis
# Safety population (all treated patients)
safety_analysis <- jvisr(
data = clinical_trial,
analysis_type = "tableone"
)
Real-World Evidence Study
Treatment Effectiveness in Clinical Practice
# Real-world effectiveness analysis
rwe_effectiveness <- jvisr(
data = rwe_data,
analysis_type = "kaplan_meier",
time_var = "time_to_death",
event_var = "death_observed",
strata_var = "treatment_class",
title = "Real-World Evidence: Treatment Effectiveness",
time_label = "Time from Treatment Initiation",
time_units = "months",
survival_label = "Overall Survival Probability",
confidence_interval = TRUE,
risk_table = TRUE,
theme_style = "classic"
)
Line of Therapy Analysis
# Treatment line analysis
line_therapy <- jvisr(
data = rwe_data,
analysis_type = "kaplan_meier",
time_var = "time_to_death",
event_var = "death_observed",
strata_var = "treatment_line",
title = "Survival by Line of Therapy",
time_label = "Time from Treatment Start",
time_units = "months",
survival_label = "Overall Survival Probability",
confidence_interval = TRUE,
risk_table = TRUE,
theme_style = "minimal"
)
Healthcare Setting Comparison
# Healthcare setting analysis
setting_analysis <- jvisr(
data = rwe_data,
analysis_type = "kaplan_meier",
time_var = "time_to_death",
event_var = "death_observed",
strata_var = "hospital_type",
title = "Survival by Healthcare Setting",
time_label = "Time from Diagnosis",
time_units = "months",
survival_label = "Overall Survival Probability",
confidence_interval = TRUE,
risk_table = TRUE,
theme_style = "clean"
)
Regulatory Submission Analysis
CDISC-Compliant Analysis
# Regulatory submission analysis
regulatory_analysis <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
cdisc_format = TRUE,
aval_var = "AVAL",
cnsr_var = "CNSR",
strata_var = "treatment_arm",
title = "CDISC ADaM ADTTE Analysis: Overall Survival",
time_label = "Analysis Value (AVAL)",
time_units = "months",
survival_label = "Survival Probability",
confidence_interval = TRUE,
risk_table = TRUE,
p_value = TRUE,
quantiles = TRUE,
theme_style = "classic",
color_palette = "Set1"
)
Advanced Techniques
Missing Data Handling
Handle missing data appropriately in clinical analyses:
# Create data with missing values
missing_data <- clinical_trial
missing_data$time_to_event[sample(1:300, 30)] <- NA
missing_data$treatment_arm[sample(1:300, 10)] <- NA
# Analysis with missing data
missing_analysis <- jvisr(
data = missing_data,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm"
)
Missing data considerations: - Complete case analysis: Default approach, removes missing observations - Multiple imputation: Consider for sensitivity analyses - Pattern analysis: Understand missingness mechanisms - Regulatory guidance: Follow ICH E9(R1) principles
Large Dataset Optimization
The function automatically optimizes performance for large datasets:
# Create large dataset
large_data <- do.call(rbind, replicate(5, clinical_trial, simplify = FALSE))
large_data$patient_id <- paste0("LG_", sprintf("%04d", 1:nrow(large_data)))
# Performance timing
system.time({
large_analysis <- jvisr(
data = large_data,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm"
)
})
# Second run should be faster due to caching
system.time({
large_analysis2 <- jvisr(
data = large_data,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm"
)
})
Performance features: - Intelligent caching: Automatic caching of prepared data and plots - Change detection: Cache invalidation when data or options change - Memory efficiency: Optimized data preparation and processing - Parallel processing: Efficient handling of stratified analyses
Summary and Interpretation Control
Control what outputs are displayed:
# Summary only (no interpretation)
summary_only <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
show_summary = TRUE,
show_interpretation = FALSE
)
# Interpretation only (no summary table)
interpretation_only <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
show_summary = FALSE,
show_interpretation = TRUE
)
# Plot only (minimal output)
plot_only <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
show_summary = FALSE,
show_interpretation = FALSE
)
Best Practices for Clinical Research
Study Design Considerations
Use appropriate visualizations based on study design:
# Create study design guide
study_guide <- data.frame(
Study_Type = c(
"Phase I dose escalation",
"Phase II single arm",
"Phase III randomized",
"Real-world evidence",
"Biomarker study",
"Competing risks"
),
Recommended_Analysis = c(
"Safety run-in with dose-response curves",
"Single-arm Kaplan-Meier with historical control",
"Stratified Kaplan-Meier with treatment comparison",
"Propensity-matched survival analysis",
"Biomarker-stratified subgroup analysis",
"Cumulative incidence with competing events"
),
Key_Options = c(
"Show individual patient data, dose escalation",
"Include confidence intervals, median survival",
"P-values, risk tables, stratification factors",
"Adjusted analysis, multiple confounders",
"Subgroup analysis, interaction tests",
"Competing events, cause-specific hazards"
)
)
knitr::kable(study_guide, caption = "Clinical Study Design Guidelines")
Statistical Interpretation
Survival Curve Interpretation
# Create interpretation guide
interpretation_guide <- data.frame(
Element = c("Curve shape", "Confidence intervals", "Median survival", "Log-rank p-value", "Risk table", "Hazard ratio"),
Clinical_Meaning = c(
"Constant vs. changing hazard over time",
"Precision of survival estimates at each timepoint",
"Time when 50% of patients experience event",
"Statistical significance of treatment difference",
"Number of patients contributing to estimate",
"Relative treatment effect (not shown in KM plot)"
),
Regulatory_Importance = c(
"Proportional hazards assumption validation",
"Required for regulatory submissions",
"Primary endpoint for many oncology trials",
"Determines statistical significance claim",
"Transparency of data underlying curves",
"Effect size for sample size calculations"
)
)
knitr::kable(interpretation_guide, caption = "Clinical Interpretation Guide")
Quality Control Guidelines
Data Quality Checks
# Data quality checks for clinical analysis
qc_checks <- function(data, time_var, event_var) {
checks <- list()
# Check for missing data
checks$missing_time <- sum(is.na(data[[time_var]]))
checks$missing_event <- sum(is.na(data[[event_var]]))
# Check data ranges
checks$negative_times <- sum(data[[time_var]] <= 0, na.rm = TRUE)
checks$event_range <- range(data[[event_var]], na.rm = TRUE)
# Check follow-up
checks$median_followup <- median(data[[time_var]], na.rm = TRUE)
checks$max_followup <- max(data[[time_var]], na.rm = TRUE)
# Check event rates
checks$overall_event_rate <- mean(data[[event_var]], na.rm = TRUE)
return(checks)
}
# Run quality control
qc_results <- qc_checks(clinical_trial, "time_to_event", "event_indicator")
print(qc_results)
Publication Requirements
# Publication-ready analysis
publication_km <- jvisr(
data = clinical_trial,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm",
title = "Overall Survival by Treatment Arm",
time_label = "Time from Randomization",
time_units = "months",
survival_label = "Overall Survival Probability",
confidence_interval = TRUE,
risk_table = TRUE,
p_value = TRUE,
quantiles = TRUE,
theme_style = "classic",
color_palette = "Set1"
)
Publication considerations: - High resolution:
Ensure plots are 300+ DPI for print - Color
accessibility: Use colorblind-friendly palettes
- Font sizes: Readable at publication scale -
Statistical rigor: Include all required statistical
elements - CONSORT compliance: Follow reporting
guidelines
Error Handling and Troubleshooting
Common Issues
Data Validation
# Check data requirements
validate_clinical_data <- function(data, time_var, event_var) {
errors <- c()
if (!is.numeric(data[[time_var]])) {
errors <- c(errors, "Time variable must be numeric")
}
if (!all(data[[event_var]] %in% c(0, 1, NA))) {
errors <- c(errors, "Event variable must be 0/1 coded")
}
if (any(data[[time_var]] <= 0, na.rm = TRUE)) {
errors <- c(errors, "Time variable must be positive")
}
if (length(unique(data[[event_var]])) < 2) {
errors <- c(errors, "Need both events and censored observations")
}
return(errors)
}
# Validate our data
validation_errors <- validate_clinical_data(clinical_trial, "time_to_event", "event_indicator")
if (length(validation_errors) > 0) {
cat("Data validation errors:\\n")
for (error in validation_errors) {
cat("-", error, "\\n")
}
} else {
cat("Data validation passed\\n")
}
Edge Cases
# Small sample size
small_data <- clinical_trial[1:20, ]
small_analysis <- jvisr(
data = small_data,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm"
)
# Single group
single_group_data <- clinical_trial[clinical_trial$treatment_arm == "Placebo", ]
single_group_analysis <- jvisr(
data = single_group_data,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator"
)
# All events censored
all_censored_data <- clinical_trial
all_censored_data$event_indicator <- 0
censored_analysis <- jvisr(
data = all_censored_data,
analysis_type = "kaplan_meier",
time_var = "time_to_event",
event_var = "event_indicator",
strata_var = "treatment_arm"
)
Performance Optimization
# Performance optimization tips
performance_tips <- data.frame(
Scenario = c(
"Large dataset (>1000 patients)",
"Many strata (>5 groups)",
"Frequent re-analysis",
"Regulatory submission"
),
Recommendation = c(
"Use caching, avoid unnecessary re-computation",
"Consider grouping categories, focus on key comparisons",
"Cache results, minimize data changes",
"Use CDISC format, validate data thoroughly"
),
Alternative = c(
"Sample for exploration, full analysis for final",
"Hierarchical analysis, primary vs secondary",
"Batch processing, automated workflows",
"Pre-specified analysis plan, locked datasets"
)
)
knitr::kable(performance_tips, caption = "Performance Optimization Guide")
Integration with Clinical Workflows
Reproducible Clinical Analysis
# Set clinical analysis parameters
clinical_params <- list(
primary_endpoint = "time_to_event",
event_indicator = "event_indicator",
treatment_var = "treatment_arm",
stratification_factors = c("baseline_severity", "biomarker_positive"),
alpha_level = 0.05,
analysis_populations = c("ITT", "PP", "Safety"),
theme = "classic"
)
# Systematic clinical analysis function
run_clinical_analysis <- function(data, params) {
results <- list()
# Primary efficacy analysis
results$primary <- jvisr(
data = data,
analysis_type = "kaplan_meier",
time_var = params$primary_endpoint,
event_var = params$event_indicator,
strata_var = params$treatment_var,
confidence_interval = TRUE,
risk_table = TRUE,
p_value = TRUE,
theme_style = params$theme
)
# Subgroup analyses
for (factor in params$stratification_factors[1:2]) { # Limit for example
results[[paste0("subgroup_", factor)]] <- jvisr(
data = data,
analysis_type = "kaplan_meier",
time_var = params$primary_endpoint,
event_var = params$event_indicator,
strata_var = factor,
confidence_interval = TRUE,
theme_style = params$theme
)
}
return(results)
}
# Run systematic analysis
clinical_results <- run_clinical_analysis(clinical_trial, clinical_params)
Clinical Study Report Integration
# Generate summary statistics for CSR
generate_csr_summary <- function(data, time_var, event_var, strata_var) {
summary_stats <- data %>%
group_by(.data[[strata_var]]) %>%
summarise(
N = n(),
Events = sum(.data[[event_var]], na.rm = TRUE),
Event_Rate = round(mean(.data[[event_var]], na.rm = TRUE), 3),
Median_Followup = round(median(.data[[time_var]], na.rm = TRUE), 1),
Max_Followup = round(max(.data[[time_var]], na.rm = TRUE), 1),
.groups = "drop"
)
return(summary_stats)
}
# Generate CSR summary
csr_summary <- generate_csr_summary(clinical_trial, "time_to_event", "event_indicator", "treatment_arm")
knitr::kable(csr_summary, caption = "Clinical Study Report Summary Statistics")
Summary
The jvisr
function provides a comprehensive solution for
clinical research visualization. Key advantages include:
Core Clinical Capabilities
- Multiple Analysis Types: Kaplan-Meier curves, cumulative incidence, Table One summaries, attrition charts, and risk tables
- CDISC Integration: Native support for regulatory-standard data formats
- Statistical Rigor: Proper handling of censoring, confidence intervals, and hypothesis testing
- Professional Output: Publication and regulatory-ready visualizations
Performance and Reliability
- Intelligent Caching: Automatic optimization for repeated analyses and large clinical datasets
- Data Validation: Built-in checks for clinical data quality and completeness
- Error Handling: Graceful handling of edge cases and missing data
- Scalability: Efficient processing from small pilot studies to large registration trials
Clinical Research Applications
- Clinical Trials: Phase I-III studies with proper statistical methodology
- Real-World Evidence: Observational studies with complex treatment patterns
- Biomarker Research: Precision medicine and patient stratification
- Regulatory Submissions: CDISC-compliant analyses for regulatory review
Integration Benefits
- Workflow Integration: Seamless integration with clinical data management systems
- Reproducible Research: Standardized approaches for consistent reporting
- Quality Control: Built-in validation and error checking
- Documentation: Comprehensive output with clinical interpretation