Decision Tree and Markov Chain Analysis Examples
ClinicoPath Development Team
2025-07-29
Source:vignettes/07-decision-trees.Rmd
07-decision-trees.Rmd
Overview
This vignette provides comprehensive examples explaining both decision tree and Markov chain analysis types implemented in the ClinicoPath jamovi module.
Decision Tree Analysis Example
Scenario: Acute Appendicitis Treatment Decision
Clinical Question: Should a patient with suspected appendicitis receive immediate surgery or conservative treatment?
Creating Example Data
# Create decision tree example data
set.seed(123)
n_patients <- 100
appendicitis_decision_data <- data.frame(
patient_id = 1:n_patients,
# DECISION NODES (Square boxes)
treatment_choice = sample(c("Immediate Surgery", "Conservative Treatment"),
n_patients, replace = TRUE),
# PROBABILITY VARIABLES (for chance nodes - circles)
prob_appendicitis_confirmed = runif(n_patients, 0.7, 0.9), # Probability patient actually has appendicitis
prob_surgery_success = runif(n_patients, 0.95, 0.99), # Surgery success rate
prob_conservative_success = runif(n_patients, 0.6, 0.8), # Conservative treatment success
prob_complications_surgery = runif(n_patients, 0.02, 0.08), # Surgery complications
prob_complications_conservative = runif(n_patients, 0.15, 0.25), # Conservative complications
# COST VARIABLES (outcomes)
cost_surgery = rnorm(n_patients, 12000, 2000), # Surgery costs
cost_conservative = rnorm(n_patients, 3000, 500), # Conservative treatment costs
cost_complications = rnorm(n_patients, 8000, 1500), # Complication management costs
cost_failed_conservative = rnorm(n_patients, 15000, 2500), # Emergency surgery after failed conservative
# UTILITY VARIABLES (quality of life outcomes)
utility_success = runif(n_patients, 0.95, 1.0), # Full recovery
utility_minor_complications = runif(n_patients, 0.8, 0.9), # Recovery with minor issues
utility_major_complications = runif(n_patients, 0.6, 0.8), # Recovery with major issues
# OUTCOME VARIABLES (terminal nodes - triangles)
clinical_outcome = sample(c("Complete Recovery", "Minor Complications", "Major Complications"),
n_patients, replace = TRUE, prob = c(0.8, 0.15, 0.05))
)
# Display first few rows
head(appendicitis_decision_data, 5)
Decision Tree Structure
The decision tree has the following structure:
-
DECISION NODE (Square): Treatment Choice
- Option A: Immediate Surgery
- Option B: Conservative Treatment
-
CHANCE NODES (Circles): Probabilistic Outcomes
- For Surgery:
- Success (95-99%): Low cost, high utility
- Complications (2-8%): Higher cost, lower utility
- For Conservative:
- Success (60-80%): Low cost, high utility
- Failure (20-40%): Requires emergency surgery
- For Surgery:
-
TERMINAL NODES (Triangles): Final Outcomes
- Each path ends with:
- Cost: $3,000 - $20,000
- Utility: 0.6 - 1.0 QALYs
- Each path ends with:
Expected Value Calculations
# Calculate expected values for decision tree
surgery_expected_cost <- mean(appendicitis_decision_data$cost_surgery +
appendicitis_decision_data$prob_complications_surgery *
appendicitis_decision_data$cost_complications)
conservative_expected_cost <- mean(appendicitis_decision_data$cost_conservative +
(1 - appendicitis_decision_data$prob_conservative_success) *
appendicitis_decision_data$cost_failed_conservative)
surgery_expected_utility <- mean(appendicitis_decision_data$utility_success *
appendicitis_decision_data$prob_surgery_success +
appendicitis_decision_data$utility_minor_complications *
appendicitis_decision_data$prob_complications_surgery)
conservative_expected_utility <- mean(appendicitis_decision_data$utility_success *
appendicitis_decision_data$prob_conservative_success +
appendicitis_decision_data$utility_major_complications *
(1 - appendicitis_decision_data$prob_conservative_success))
# Calculate ICER
incremental_cost <- surgery_expected_cost - conservative_expected_cost
incremental_utility <- surgery_expected_utility - conservative_expected_utility
icer <- incremental_cost / incremental_utility
# Display results
cat("DECISION TREE ANALYSIS RESULTS:\n")
cat("===============================\n")
cat("Surgery Strategy:\n")
cat(" Expected Cost: $", round(surgery_expected_cost, 0), "\n")
cat(" Expected Utility:", round(surgery_expected_utility, 3), "QALYs\n")
cat("\n")
cat("Conservative Strategy:\n")
cat(" Expected Cost: $", round(conservative_expected_cost, 0), "\n")
cat(" Expected Utility:", round(conservative_expected_utility, 3), "QALYs\n")
cat("\n")
cat("Cost-Effectiveness Analysis:\n")
cat(" Incremental Cost: $", round(incremental_cost, 0), "\n")
cat(" Incremental Utility:", round(incremental_utility, 3), "QALYs\n")
cat(" ICER: $", round(icer, 0), "per QALY\n")
if (icer < 50000) {
cat(" ✓ Surgery is cost-effective (ICER < $50,000/QALY)\n")
} else {
cat(" ⚠ Surgery may not be cost-effective (ICER > $50,000/QALY)\n")
}
Markov Chain Analysis Example
Scenario: Chronic Heart Disease Management
Clinical Question: What is the long-term cost-effectiveness of different heart disease management strategies?
Creating Markov Data
# Create Markov chain example data
set.seed(456)
n_strategies <- 150
# First create basic structure
heart_disease_markov_data <- data.frame(
strategy_id = 1:n_strategies,
# DECISION VARIABLES
management_strategy = sample(c("Standard Care", "Intensive Monitoring", "Preventive Surgery"),
n_strategies, replace = TRUE),
patient_risk_category = sample(c("Low Risk", "Moderate Risk", "High Risk"),
n_strategies, replace = TRUE, prob = c(0.4, 0.4, 0.2))
)
# Add transition probabilities
heart_disease_markov_data$prob_asymp_to_symp <- case_when(
heart_disease_markov_data$management_strategy == "Standard Care" ~ runif(n_strategies, 0.08, 0.12),
heart_disease_markov_data$management_strategy == "Intensive Monitoring" ~ runif(n_strategies, 0.05, 0.08),
heart_disease_markov_data$management_strategy == "Preventive Surgery" ~ runif(n_strategies, 0.02, 0.05)
)
heart_disease_markov_data$prob_asymp_to_death <- runif(n_strategies, 0.01, 0.02)
# From Symptomatic state
heart_disease_markov_data$prob_symp_to_hf <- case_when(
heart_disease_markov_data$management_strategy == "Standard Care" ~ runif(n_strategies, 0.15, 0.25),
heart_disease_markov_data$management_strategy == "Intensive Monitoring" ~ runif(n_strategies, 0.10, 0.18),
heart_disease_markov_data$management_strategy == "Preventive Surgery" ~ runif(n_strategies, 0.05, 0.12)
)
heart_disease_markov_data$prob_symp_to_death <- runif(n_strategies, 0.02, 0.04)
# From Heart Failure state
heart_disease_markov_data$prob_hf_to_death <- runif(n_strategies, 0.12, 0.20)
# STATE-SPECIFIC ANNUAL COSTS
heart_disease_markov_data$cost_asymptomatic <- case_when(
heart_disease_markov_data$management_strategy == "Standard Care" ~ rnorm(n_strategies, 2000, 300),
heart_disease_markov_data$management_strategy == "Intensive Monitoring" ~ rnorm(n_strategies, 4000, 500),
heart_disease_markov_data$management_strategy == "Preventive Surgery" ~ rnorm(n_strategies, 8000, 1000)
)
heart_disease_markov_data$cost_symptomatic <- rnorm(n_strategies, 12000, 2000)
heart_disease_markov_data$cost_heart_failure <- rnorm(n_strategies, 35000, 5000)
heart_disease_markov_data$cost_death <- rep(0, n_strategies) # No ongoing costs after death
# STATE-SPECIFIC ANNUAL UTILITIES (Quality of Life)
heart_disease_markov_data$utility_asymptomatic <- runif(n_strategies, 0.90, 0.95)
heart_disease_markov_data$utility_symptomatic <- runif(n_strategies, 0.70, 0.80)
heart_disease_markov_data$utility_heart_failure <- runif(n_strategies, 0.45, 0.60)
heart_disease_markov_data$utility_death <- rep(0, n_strategies) # No utility after death
# Display first few rows
head(heart_disease_markov_data, 5)
Markov Chain Structure
HEALTH STATES (Markov States):
- Asymptomatic Heart Disease
- Symptomatic Heart Disease
- Heart Failure
- Death (Absorbing State)
TRANSITION PATHWAYS:
Asymptomatic → Symptomatic → Heart Failure → Death
↘ Death ↘ Death
Transition Matrix Example
# Create example transition matrix for Standard Care
states <- c("Asymptomatic", "Symptomatic", "Heart Failure", "Death")
trans_matrix_standard <- matrix(0, nrow = 4, ncol = 4)
rownames(trans_matrix_standard) <- states
colnames(trans_matrix_standard) <- states
# Fill transition matrix with average probabilities for Standard Care
standard_data <- heart_disease_markov_data[heart_disease_markov_data$management_strategy == "Standard Care", ]
trans_matrix_standard[1, 1] <- 1 - mean(standard_data$prob_asymp_to_symp) - mean(standard_data$prob_asymp_to_death) # Stay asymptomatic
trans_matrix_standard[1, 2] <- mean(standard_data$prob_asymp_to_symp) # Asymp to symptomatic
trans_matrix_standard[1, 4] <- mean(standard_data$prob_asymp_to_death) # Asymp to death
trans_matrix_standard[2, 2] <- 1 - mean(standard_data$prob_symp_to_hf) - mean(standard_data$prob_symp_to_death) # Stay symptomatic
trans_matrix_standard[2, 3] <- mean(standard_data$prob_symp_to_hf) # Symp to heart failure
trans_matrix_standard[2, 4] <- mean(standard_data$prob_symp_to_death) # Symp to death
trans_matrix_standard[3, 3] <- 1 - mean(standard_data$prob_hf_to_death) # Stay in heart failure
trans_matrix_standard[3, 4] <- mean(standard_data$prob_hf_to_death) # HF to death
trans_matrix_standard[4, 4] <- 1.0 # Death is absorbing
cat("EXAMPLE TRANSITION MATRIX (Standard Care):\n")
cat("=========================================\n")
print(round(trans_matrix_standard, 3))
cat("\nRow sums (should equal 1.0):", round(rowSums(trans_matrix_standard), 3), "\n")
Markov Cohort Simulation
# Run Markov cohort simulation
num_cycles <- 20
cohort_trace <- matrix(0, nrow = num_cycles + 1, ncol = 4)
colnames(cohort_trace) <- states
# Initial distribution: Everyone starts asymptomatic
cohort_trace[1, 1] <- 1.0
# Run Markov simulation
for (cycle in 2:(num_cycles + 1)) {
cohort_trace[cycle, ] <- cohort_trace[cycle - 1, ] %*% trans_matrix_standard
}
# Create summary table
trace_df <- data.frame(
Year = 0:num_cycles,
Asymptomatic = round(cohort_trace[, 1] * 100, 1),
Symptomatic = round(cohort_trace[, 2] * 100, 1),
Heart_Failure = round(cohort_trace[, 3] * 100, 1),
Death = round(cohort_trace[, 4] * 100, 1)
)
# Show key time points
key_years <- c(1, 6, 11, 16, 21) # 0, 5, 10, 15, 20 years
cat("Population distribution over time:\n")
print(trace_df[key_years, ])
Cost-Effectiveness Calculation
# Calculate costs and utilities for Standard Care
state_costs <- c(mean(standard_data$cost_asymptomatic),
mean(standard_data$cost_symptomatic),
mean(standard_data$cost_heart_failure),
0) # Death
state_utilities <- c(mean(standard_data$utility_asymptomatic),
mean(standard_data$utility_symptomatic),
mean(standard_data$utility_heart_failure),
0) # Death
names(state_costs) <- states
names(state_utilities) <- states
cat("Annual costs per state:\n")
print(round(state_costs, 0))
cat("\nAnnual utilities per state:\n")
print(round(state_utilities, 3))
# Calculate cumulative discounted costs and utilities
discount_rate <- 0.03
cumulative_costs <- rep(0, num_cycles + 1)
cumulative_utilities <- rep(0, num_cycles + 1)
for (cycle in 2:(num_cycles + 1)) {
# Calculate cycle costs and utilities
cycle_cost <- sum(cohort_trace[cycle, ] * state_costs)
cycle_utility <- sum(cohort_trace[cycle, ] * state_utilities)
# Apply discounting
discount_factor <- (1 + discount_rate)^(-(cycle - 1))
cumulative_costs[cycle] <- cumulative_costs[cycle - 1] + cycle_cost * discount_factor
cumulative_utilities[cycle] <- cumulative_utilities[cycle - 1] + cycle_utility * discount_factor
}
# Final results
final_cost <- cumulative_costs[num_cycles + 1]
final_qalys <- cumulative_utilities[num_cycles + 1]
cat("\nFINAL 20-YEAR RESULTS (Standard Care):\n")
cat("Total Lifetime Cost: $", round(final_cost, 0), "\n")
cat("Total Lifetime QALYs:", round(final_qalys, 2), "\n")
cat("Cost per QALY: $", round(final_cost / final_qalys, 0), "\n")
Markov Chain Interpretation
Markov chains are ideal for:
- Chronic diseases with multiple stages
- Long-term cost-effectiveness analysis (years to lifetime)
- Disease progression modeling
- Comparing interventions with different timing effects
- Policy decisions affecting population health
- When disease states change over time
- Recurring decisions or ongoing treatments
When to Use Each Method
Comparison Table
comparison_table <- data.frame(
Aspect = c("Time Horizon", "Disease Type", "Decision Complexity", "Outcomes",
"Costs", "Best For", "Data Requirements", "Computational Needs"),
Decision_Tree = c("Short-term (days-months)", "Acute conditions", "Simple (2-3 options)",
"One-time outcomes", "One-time costs", "Treatment selection",
"Probabilities, costs, utilities", "Low"),
Markov_Chain = c("Long-term (years-lifetime)", "Chronic conditions", "Complex strategies",
"Recurring outcomes", "Ongoing costs", "Disease management",
"Transition probabilities, state costs", "Higher")
)
print(comparison_table)
Example Applications
DECISION TREES are best for:
- Emergency treatment decisions (appendicitis, trauma)
- Surgical vs non-surgical interventions
- Diagnostic test decisions
- Vaccination decisions
- One-time screening decisions
MARKOV CHAINS are best for:
- Chronic disease management (diabetes, heart disease)
- Cancer progression and treatment
- Addiction treatment programs
- Preventive intervention policies
- Healthcare resource planning
- Long-term pharmaceutical studies
Summary
This vignette demonstrates practical applications of both decision tree and Markov chain methods for medical decision analysis and cost-effectiveness research. The generated datasets can be used with the ClinicoPath jamovi module to perform these analyses interactively.