The following R script processes the results from 1000 PSA runs. It takes in the PSA results(res_no_intervention_parallel, res_Empower_Health_parallel and res_Usual_Care_parallel) dataframes and performs subsequent analyses. The script also generates a plot of the simulation results.
The script begins by loading the necessary R libraries:
## Warning: package 'ggplot2' was built under R version 4.4.3
## Warning: package 'readr' was built under R version 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## âś” dplyr 1.1.4 âś” readr 2.1.6
## âś” forcats 1.0.1 âś” stringr 1.6.0
## âś” ggplot2 4.0.2 âś” tibble 3.3.0
## âś” lubridate 1.9.4 âś” tidyr 1.3.1
## âś” purrr 1.2.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## âś– dplyr::filter() masks stats::filter()
## âś– dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
##
## Attaching package: 'reshape2'
##
## The following object is masked from 'package:tidyr':
##
## smiths
library(ggplot2) # For plotting
library(dampack) # For processing PSA results
library(scales) # For formatting axes in plots##
## Attaching package: 'scales'
##
## The following object is masked from 'package:purrr':
##
## discard
##
## The following object is masked from 'package:readr':
##
## col_factor
# Import PSA datasets
load('/Users/jamesoguta/Documents/James Oguta/My PhD Folder-2023-2025/Trainings/KenyaCVDModel/data/psa_results_no_trt_2.rda')
load('/Users/jamesoguta/Documents/James Oguta/My PhD Folder-2023-2025/Trainings/KenyaCVDModel/data/psa_results_Emp_Health_2.rda')
load('/Users/jamesoguta/Documents/James Oguta/My PhD Folder-2023-2025/Trainings/KenyaCVDModel/data/psa_results_Usual_Care_2.rda')psa_wide <- reshape(psa_results,
timevar = "strategy",
idvar = "sim",
direction = "wide")
head(psa_wide)## sim mean_Dcosts.No_Intervention mean_Ddalys.No_Intervention
## 1 1 967.3705 7.399176
## 2 2 884.9262 7.435233
## 3 3 907.1375 7.424413
## 4 4 1007.9599 7.382568
## 5 5 973.2876 7.397506
## 6 6 951.3212 7.405980
## mean_Dcosts.Empower_Health mean_Ddalys.Empower_Health mean_Dcosts.Usual_Care
## 1 4851.712 7.298338 4283.573
## 2 4783.454 7.320417 4188.935
## 3 4797.463 7.315476 4213.083
## 4 4887.848 7.287305 4333.161
## 5 4865.832 7.293878 4297.776
## 6 4842.831 7.301330 4268.467
## mean_Ddalys.Usual_Care
## 1 7.409644
## 2 7.441969
## 3 7.432341
## 4 7.394076
## 5 7.405577
## 6 7.414809
# Save the psa_wide dataset as an .rda file
save(psa_wide, file = "/Users/jamesoguta/Documents/James Oguta/My PhD Folder-2023-2025/Trainings/KenyaCVDModel/data/psa_wide.rda")
# Save the psa_wide dataset as a .csv file
write.csv(psa_wide, file = "/Users/jamesoguta/Documents/James Oguta/My PhD Folder-2023-2025/Trainings/KenyaCVDModel/data/psa_results_1st.csv", row.names = FALSE)data <- read.csv("/Users/jamesoguta/Documents/James Oguta/My PhD Folder-2023-2025/Trainings/KenyaCVDModel/data/psa_results_1st.csv")
# Set Kenya GDP per capita
gdp_per_capita <- 2000
# Define GDP-based WTP thresholds
wtp_thresholds_gdp <- c(
gdp_per_capita * 0.5, # 0.5x GDP = $1,000
gdp_per_capita * 1, # 1x GDP = $2,000
gdp_per_capita * 3 # 3x GDP = $6,000
)
# Define maximum WTP for plots (up to $20,000 as requested)
max_wtp <- 10000
# Define WTP grid for analysis (0 to max_wtp)
wtp_grid <- seq(0, max_wtp, by = 500)
# View structure
str(data)## 'data.frame': 1000 obs. of 7 variables:
## $ sim : int 1 2 3 4 5 6 7 8 9 10 ...
## $ mean_Dcosts.No_Intervention: num 967 885 907 1008 973 ...
## $ mean_Ddalys.No_Intervention: num 7.4 7.44 7.42 7.38 7.4 ...
## $ mean_Dcosts.Empower_Health : num 4852 4783 4797 4888 4866 ...
## $ mean_Ddalys.Empower_Health : num 7.3 7.32 7.32 7.29 7.29 ...
## $ mean_Dcosts.Usual_Care : num 4284 4189 4213 4333 4298 ...
## $ mean_Ddalys.Usual_Care : num 7.41 7.44 7.43 7.39 7.41 ...
# Calculate means and standard deviations for the two interventions
summary_stats <- data %>%
summarise(
# Means
mean_cost_empower = mean(mean_Dcosts.Empower_Health),
mean_cost_usual = mean(mean_Dcosts.Usual_Care),
mean_dalys_empower = mean(mean_Ddalys.Empower_Health),
mean_dalys_usual = mean(mean_Ddalys.Usual_Care),
# Standard deviations
sd_cost_empower = sd(mean_Dcosts.Empower_Health),
sd_cost_usual = sd(mean_Dcosts.Usual_Care),
sd_dalys_empower = sd(mean_Ddalys.Empower_Health),
sd_dalys_usual = sd(mean_Ddalys.Usual_Care)
)
print("Summary Statistics:")## [1] "Summary Statistics:"
## mean_cost_empower mean_cost_usual mean_dalys_empower mean_dalys_usual
## 1 4848.937 4278.653 7.299519 7.411795
## sd_cost_empower sd_cost_usual sd_dalys_empower sd_dalys_usual
## 1 53.87085 71.59616 0.01686957 0.02313703
## [1] "Kenya GDP per capita: $ 2000"
print(paste("GDP-based thresholds: 0.5x = $", wtp_thresholds_gdp[1],
", 1x = $", wtp_thresholds_gdp[2],
", 3x = $", wtp_thresholds_gdp[3]))## [1] "GDP-based thresholds: 0.5x = $ 1000 , 1x = $ 2000 , 3x = $ 6000"
# Calculate incremental differences
incremental <- data %>%
mutate(
# Cost difference (Empower Health - Usual Care)
inc_cost = mean_Dcosts.Empower_Health - mean_Dcosts.Usual_Care,
# DALY difference (negative means DALYs averted)
inc_dalys = mean_Ddalys.Empower_Health - mean_Ddalys.Usual_Care,
# DALYs averted (positive means better outcome)
dalys_averted = mean_Ddalys.Usual_Care - mean_Ddalys.Empower_Health,
# ICER
icer = inc_cost / dalys_averted
)
# Summary of incremental differences and ICER
icer_summary <- incremental %>%
summarise(
mean_inc_cost = mean(inc_cost),
sd_inc_cost = sd(inc_cost),
ci_inc_cost_lower = quantile(inc_cost, 0.025),
ci_inc_cost_upper = quantile(inc_cost, 0.975),
mean_inc_dalys = mean(inc_dalys),
sd_inc_dalys = sd(inc_dalys),
ci_inc_dalys_lower = quantile(inc_dalys, 0.025),
ci_inc_dalys_upper = quantile(inc_dalys, 0.975),
mean_dalys_averted = mean(dalys_averted),
sd_dalys_averted = sd(dalys_averted),
ci_dalys_averted_lower = quantile(dalys_averted, 0.025),
ci_dalys_averted_upper = quantile(dalys_averted, 0.975),
mean_icer = mean(icer, na.rm = TRUE),
median_icer = median(icer, na.rm = TRUE),
icer_lower = quantile(icer, 0.025, na.rm = TRUE),
icer_upper = quantile(icer, 0.975, na.rm = TRUE)
)
print("ICER Summary (Empower Health vs Usual Care):")## [1] "ICER Summary (Empower Health vs Usual Care):"
## mean_inc_cost sd_inc_cost ci_inc_cost_lower ci_inc_cost_upper mean_inc_dalys
## 1 570.284 17.83834 534.1923 602.8531 -0.1122763
## sd_inc_dalys ci_inc_dalys_lower ci_inc_dalys_upper mean_dalys_averted
## 1 0.006324055 -0.1243319 -0.09978764 0.1122763
## sd_dalys_averted ci_dalys_averted_lower ci_dalys_averted_upper mean_icer
## 1 0.006324055 0.09978764 0.1243319 5086.519
## median_icer icer_lower icer_upper
## 1 5080.567 4849.276 5353.795
# Paired t-tests
ttest_cost <- t.test(data$mean_Dcosts.Empower_Health,
data$mean_Dcosts.Usual_Care, paired = TRUE)
ttest_dalys <- t.test(data$mean_Ddalys.Empower_Health,
data$mean_Ddalys.Usual_Care, paired = TRUE)
print("Paired t-test for Costs:")## [1] "Paired t-test for Costs:"
##
## Paired t-test
##
## data: data$mean_Dcosts.Empower_Health and data$mean_Dcosts.Usual_Care
## t = 1011, df = 999, p-value < 2.2e-16
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
## 569.1770 571.3909
## sample estimates:
## mean difference
## 570.284
## [1] "Paired t-test for DALYs:"
##
## Paired t-test
##
## data: data$mean_Ddalys.Empower_Health and data$mean_Ddalys.Usual_Care
## t = -561.43, df = 999, p-value < 2.2e-16
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
## -0.1126687 -0.1118838
## sample estimates:
## mean difference
## -0.1122763
# Use boxplots to visualize the distribution of costs and DALYs for each intervention
cost_dalys_long <- data %>%
pivot_longer(cols = c(mean_Dcosts.Empower_Health, mean_Dcosts.Usual_Care, mean_Ddalys.Empower_Health, mean_Ddalys.Usual_Care),
names_to = "variable", values_to = "value") %>%
mutate(
intervention = case_when(
variable %in% c("mean_Dcosts.Empower_Health", "mean_Ddalys.Empower_Health") ~ "Empower Health",
variable %in% c("mean_Dcosts.Usual_Care", "mean_Ddalys.Usual_Care") ~ "Usual Care"
),
measure = case_when(
variable %in% c("mean_Dcosts.Empower_Health", "mean_Dcosts.Usual_Care") ~ "Cost",
variable %in% c("mean_Ddalys.Empower_Health", "mean_Ddalys.Usual_Care") ~ "DALYs"
)
)
ggplot(cost_dalys_long, aes(x = intervention, y = value, fill = intervention)) +
geom_boxplot() +
facet_wrap(~ measure, scales = "free_y") +
labs(title = "Distribution of Mean Costs and DALYs by Intervention",
x = "Intervention",
y = "Value") +
theme_minimal() +
scale_fill_manual(values = c("Empower Health" = "blue", "Usual Care" = "green")) +
theme(legend.position = "none")
# Plot cost-effectiveness plane for mean costs and DALYs
# Mean values
mean_uc_dalys <- mean(data$mean_Ddalys.Usual_Care, na.rm = TRUE)
mean_uc_costs <- mean(data$mean_Dcosts.Usual_Care, na.rm = TRUE)
mean_eh_dalys <- mean(data$mean_Ddalys.Empower_Health, na.rm = TRUE)
mean_eh_costs <- mean(data$mean_Dcosts.Empower_Health, na.rm = TRUE)
# Cost-effectiveness scatter plot with legend
ce_scatter <- ggplot() +
geom_point(
data = data,
aes(
x = mean_Ddalys.Usual_Care,
y = mean_Dcosts.Usual_Care,
color = "Usual Care"
),
alpha = 0.3, size = 1
) +
geom_point(
data = data,
aes(
x = mean_Ddalys.Empower_Health,
y = mean_Dcosts.Empower_Health,
color = "Empower Health"
),
alpha = 0.3, size = 1
) +
geom_point(
aes(
x = mean_uc_dalys,
y = mean_uc_costs,
color = "Usual Care"
),
size = 4, shape = 18
) +
geom_point(
aes(
x = mean_eh_dalys,
y = mean_eh_costs,
color = "Empower Health"
),
size = 4, shape = 18
) +
labs(
title = "Cost-Effectiveness Scatter Plot (Costs vs DALYs)",
x = "DALYs",
y = "Costs (USD)",
color = "Intervention"
) +
scale_color_manual(
values = c(
"Usual Care" = "blue",
"Empower Health" = "green"
)
) +
scale_x_continuous(labels = number_format(accuracy = 0.001)) +
scale_y_continuous(labels = dollar_format()) +
theme_minimal() +
theme(legend.position = "bottom")
print(ce_scatter)
# Plot CE Plane for ICERs
# Optional helpers for dynamic annotation placement
x_pos <- max(incremental$dalys_averted, na.rm = TRUE) * 0.7
y_max <- max(incremental$inc_cost, na.rm = TRUE)
icer_scatter <- ggplot(incremental, aes(x = dalys_averted, y = inc_cost)) +
geom_point(alpha = 0.3, color = "darkgreen", size = 1) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray50", linewidth = 0.6) +
geom_vline(xintercept = 0, linetype = "dashed", color = "gray50", linewidth = 0.6) +
# GDP-based WTP lines
geom_abline(
intercept = 0, slope = wtp_thresholds_gdp[1],
linetype = "dotted", color = "orange", alpha = 0.7, linewidth = 1
) +
geom_abline(
intercept = 0, slope = wtp_thresholds_gdp[2],
linetype = "dotted", color = "blue", alpha = 0.7, linewidth = 1
) +
geom_abline(
intercept = 0, slope = wtp_thresholds_gdp[3],
linetype = "dotted", color = "red", alpha = 0.7, linewidth = 1
) +
# Mean point
geom_point(
x = mean(incremental$dalys_averted, na.rm = TRUE),
y = mean(incremental$inc_cost, na.rm = TRUE),
color = "black", size = 4, shape = 18
) +
# Annotations
annotate(
"text", x = x_pos, y = wtp_thresholds_gdp[1] * x_pos,
label = paste0("0.5x GDP = $", format(wtp_thresholds_gdp[1], big.mark = ",")),
angle = 20, vjust = -0.5, size = 3, color = "orange"
) +
annotate(
"text", x = x_pos, y = wtp_thresholds_gdp[2] * x_pos,
label = paste0("1x GDP = $", format(wtp_thresholds_gdp[2], big.mark = ",")),
angle = 20, vjust = -0.5, size = 3, color = "blue"
) +
annotate(
"text", x = x_pos, y = wtp_thresholds_gdp[3] * x_pos,
label = paste0("3x GDP = $", format(wtp_thresholds_gdp[3], big.mark = ",")),
angle = 20, vjust = -0.5, size = 3, color = "red"
) +
labs(
title = "Incremental Cost-Effectiveness Scatter Plot",
subtitle = paste0(
"Empower Health vs Usual Care | Kenya GDP thresholds: 0.5x = $",
format(wtp_thresholds_gdp[1], big.mark = ","),
", 1x = $", format(wtp_thresholds_gdp[2], big.mark = ","),
", 3x = $", format(wtp_thresholds_gdp[3], big.mark = ",")
),
x = "Incremental DALYs Averted (Empower Health better →)",
y = "Incremental Cost (USD) (Empower Health more expensive →)"
) +
scale_x_continuous(labels = number_format(accuracy = 0.001)) +
scale_y_continuous(labels = dollar_format()) +
theme_minimal()
print(icer_scatter)
# Plot CEAC for the two interventions
# 3. Cost-Effectiveness Acceptability Curve with GDP thresholds
# Function to calculate Net Monetary Benefit (NMB)
calculate_nmb <- function(cost, dalys, wtp) {
return(wtp * (7.8 - dalys) - cost) # Using 7.8 as reference (max DALYs in dataset)
}
# Calculate NMB at different WTP thresholds
nmb_data <- data %>%
crossing(wtp = wtp_grid) %>%
mutate(
nmb_usual = calculate_nmb(mean_Dcosts.Usual_Care,
mean_Ddalys.Usual_Care, wtp),
nmb_empower = calculate_nmb(mean_Dcosts.Empower_Health,
mean_Ddalys.Empower_Health, wtp),
nmb_diff = nmb_empower - nmb_usual
)
# Calculate probability of being cost-effective at each threshold
ce_acceptability <- nmb_data %>%
group_by(wtp) %>%
summarise(
prob_usual = mean(nmb_usual > nmb_empower),
prob_empower = mean(nmb_empower > nmb_usual),
prob_equal = mean(nmb_empower == nmb_usual)
) %>%
pivot_longer(cols = c(prob_usual, prob_empower),
names_to = "intervention",
values_to = "probability") %>%
mutate(
intervention = case_when(
intervention == "prob_usual" ~ "Usual Care",
intervention == "prob_empower" ~ "Empower Health"
)
)
ceac_plot <- ggplot(ce_acceptability, aes(x = wtp, y = probability, color = intervention)) +
geom_line(size = 1.2) +
geom_ribbon(aes(ymin = 0, ymax = probability, fill = intervention),
alpha = 0.1, show.legend = FALSE) +
# Add vertical lines for GDP thresholds
geom_vline(xintercept = wtp_thresholds_gdp[1], linetype = "dashed",
color = "orange", alpha = 0.7, size = 0.8) +
geom_vline(xintercept = wtp_thresholds_gdp[2], linetype = "dashed",
color = "blue", alpha = 0.7, size = 0.8) +
geom_vline(xintercept = wtp_thresholds_gdp[3], linetype = "dashed",
color = "red", alpha = 0.7, size = 0.8) +
# Add labels for GDP thresholds
annotate("text", x = wtp_thresholds_gdp[1] + 300, y = 0.95,
label = paste0("0.5x GDP\n$", format(wtp_thresholds_gdp[1], big.mark = ",")),
size = 3, color = "orange") +
annotate("text", x = wtp_thresholds_gdp[2] + 300, y = 0.95,
label = paste0("1x GDP\n$", format(wtp_thresholds_gdp[2], big.mark = ",")),
size = 3, color = "blue") +
annotate("text", x = wtp_thresholds_gdp[3] + 300, y = 0.95,
label = paste0("3x GDP\n$", format(wtp_thresholds_gdp[3], big.mark = ",")),
size = 3, color = "red") +
labs(
title = "Cost-Effectiveness Acceptability Curve",
subtitle = paste0("Kenya GDP per capita = $", gdp_per_capita,
" | Thresholds: 0.5x, 1x, and 3x GDP"),
x = "Willingness-to-Pay per DALY Averted (USD)",
y = "Probability of Cost-Effectiveness",
color = "Intervention"
) +
scale_x_continuous(labels = scales::dollar_format(),
limits = c(0, max_wtp),
breaks = seq(0, max_wtp, by = 2000)) +
scale_y_continuous(labels = scales::percent_format(), limits = c(0, 1)) +
theme_minimal() +
theme(legend.position = "bottom")## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# NMB Plot with GDP thresholds
# 4. Net Monetary Benefit Plot with GDP thresholds
nmb_summary <- nmb_data %>%
group_by(wtp) %>%
summarise(
nmb_usual_mean = mean(nmb_usual),
nmb_usual_lower = quantile(nmb_usual, 0.025),
nmb_usual_upper = quantile(nmb_usual, 0.975),
nmb_empower_mean = mean(nmb_empower),
nmb_empower_lower = quantile(nmb_empower, 0.025),
nmb_empower_upper = quantile(nmb_empower, 0.975)
) %>%
pivot_longer(cols = -wtp,
names_to = c("intervention", "statistic"),
names_pattern = "nmb_(.*)_(.*)") %>%
pivot_wider(names_from = statistic, values_from = value)
nmb_plot <- ggplot(nmb_summary, aes(x = wtp, y = mean, color = intervention, fill = intervention)) +
geom_line(size = 1.2) +
geom_ribbon(aes(ymin = lower, ymax = upper), alpha = 0.2, color = NA) +
# Add vertical lines for GDP thresholds
geom_vline(xintercept = wtp_thresholds_gdp[1], linetype = "dashed",
color = "orange", alpha = 0.7, size = 0.8) +
geom_vline(xintercept = wtp_thresholds_gdp[2], linetype = "dashed",
color = "blue", alpha = 0.7, size = 0.8) +
geom_vline(xintercept = wtp_thresholds_gdp[3], linetype = "dashed",
color = "red", alpha = 0.7, size = 0.8) +
labs(
title = "Net Monetary Benefit by Willingness-to-Pay Threshold",
subtitle = paste0("Empower Health vs Usual Care | Kenya GDP thresholds (0.5x, 1x, 3x = $",
wtp_thresholds_gdp[1], ", $", wtp_thresholds_gdp[2], ", $",
wtp_thresholds_gdp[3], ")"),
x = "Willingness-to-Pay per DALY Averted (USD)",
y = "Net Monetary Benefit (USD)",
color = "Intervention",
fill = "Intervention"
) +
scale_x_continuous(labels = scales::dollar_format(),
limits = c(0, max_wtp),
breaks = seq(0, max_wtp, by = 2000)) +
scale_y_continuous(labels = scales::dollar_format()) +
theme_minimal() +
theme(legend.position = "bottom") +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray50")
print(nmb_plot)
# Bootstrap confidence intervals for ICER
# 6. Sensitivity Analysis: Bootstrap confidence intervals for ICER
set.seed(123)
n_bootstrap <- 5000
bootstrap_icers <- function(data, n_bootstrap) {
boot_icers <- numeric(n_bootstrap)
for (i in 1:n_bootstrap) {
boot_sample <- data[sample(1:nrow(data), nrow(data), replace = TRUE), ]
# Calculate ICER for bootstrap sample
dalys_averted <- mean(boot_sample$mean_Ddalys.Usual_Care - boot_sample$mean_Ddalys.Empower_Health)
cost_diff <- mean(boot_sample$mean_Dcosts.Empower_Health - boot_sample$mean_Dcosts.Usual_Care)
boot_icers[i] <- cost_diff / dalys_averted
}
return(boot_icers)
}
boot_icers <- bootstrap_icers(data, n_bootstrap)
# Summary of bootstrap ICERs
boot_icer_summary <- data.frame(
mean_icer = mean(boot_icers, na.rm = TRUE),
median_icer = median(boot_icers, na.rm = TRUE),
lower_95 = quantile(boot_icers, 0.025, na.rm = TRUE),
upper_95 = quantile(boot_icers, 0.975, na.rm = TRUE),
prob_cost_effective_at_0.5x_GDP = mean(boot_icers < wtp_thresholds_gdp[1], na.rm = TRUE),
prob_cost_effective_at_1x_GDP = mean(boot_icers < wtp_thresholds_gdp[2], na.rm = TRUE),
prob_cost_effective_at_3x_GDP = mean(boot_icers < wtp_thresholds_gdp[3], na.rm = TRUE)
)
print("Bootstrap ICER Confidence Intervals:")## [1] "Bootstrap ICER Confidence Intervals:"
## mean_icer median_icer lower_95 upper_95 prob_cost_effective_at_0.5x_GDP
## 2.5% 5079.28 5079.209 5071.376 5087.157 0
## prob_cost_effective_at_1x_GDP prob_cost_effective_at_3x_GDP
## 2.5% 0 1
mean_dalys <- mean(incremental$dalys_averted, na.rm = TRUE)
mean_cost <- mean(incremental$inc_cost, na.rm = TRUE)
xmax <- max(abs(incremental$dalys_averted), na.rm = TRUE) * 1.5
ymax <- max(abs(incremental$inc_cost), na.rm = TRUE) * 1.5
mean_icer <- mean(incremental$inc_cost / incremental$dalys_averted, na.rm = TRUE)
ce_plane <- ggplot(incremental,
aes(x = dalys_averted, y = inc_cost)) +
geom_point(alpha = 0.3, color = "darkgreen", size = 1) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
# Add GDP thresholds
geom_abline(intercept = 0, slope = wtp_thresholds_gdp[1],
linetype = "dotted", color = "orange", alpha = 0.7, size = 1) +
geom_abline(intercept = 0, slope = wtp_thresholds_gdp[2],
linetype = "dotted", color = "blue", alpha = 0.7, size = 1) +
geom_abline(intercept = 0, slope = wtp_thresholds_gdp[3],
linetype = "dotted", color = "red", alpha = 0.7, size = 1) +
# Mean incremental point
geom_point(x = mean_dalys, y = mean_cost,
color = "black", size = 4, shape = 18) +
# Mean ICER label
annotate("text",
x = xmax * 0.55,
y = mean_icer * xmax * 0.55,
label = paste0("Mean ICER = ", dollar(mean_icer), "/DALY averted"),
color = "black", size = 3, hjust = 0, vjust = -0.5) +
# Add quadrants
annotate("rect", xmin = 0, xmax = Inf, ymin = 0, ymax = Inf,
alpha = 0.1, fill = "lightgreen") +
annotate("rect", xmin = -Inf, xmax = 0, ymin = 0, ymax = Inf,
alpha = 0.1, fill = "lightyellow") +
annotate("rect", xmin = -Inf, xmax = 0, ymin = -Inf, ymax = 0,
alpha = 0.1, fill = "lightcoral") +
annotate("rect", xmin = 0, xmax = Inf, ymin = -Inf, ymax = 0,
alpha = 0.1, fill = "lightblue") +
# Add labels
annotate("text", x = 0.04, y = 400, label = "Quadrant I\nCostly, Effective",
size = 3, color = "darkgreen") +
annotate("text", x = -0.02, y = 400, label = "Quadrant II\nCostly, Ineffective",
size = 3, color = "darkred") +
annotate("text", x = -0.02, y = -400, label = "Quadrant III\nCost-saving, Ineffective",
size = 3, color = "darkred") +
annotate("text", x = 0.04, y = -400, label = "Quadrant IV\nCost-saving, Effective",
size = 3, color = "darkgreen") +
labs(
title = "Cost-Effectiveness Plane with Quadrants",
subtitle = paste0("Empower Health vs Usual Care | Kenya GDP thresholds (0.5x, 1x, 3x = $",
wtp_thresholds_gdp[1], ", $", wtp_thresholds_gdp[2], ", $",
wtp_thresholds_gdp[3], ")"),
x = "Incremental DALYs Averted (Empower Health better →)",
y = "Incremental Cost (USD) (Empower Health more expensive →)"
) +
scale_x_continuous(labels = scales::number_format(accuracy = 0.001)) +
scale_y_continuous(labels = scales::dollar_format()) +
theme_minimal()
print(ce_plane)
# EVPI
# 7. Expected Value of Perfect Information (EVPI)
calculate_evpi <- function(data, wtp_grid) {
evpi_results <- data.frame(wtp = wtp_grid, evpi = NA)
for (i in seq_along(wtp_grid)) {
wtp <- wtp_grid[i]
# Calculate NMB for each intervention at this WTP
nmb_data <- data %>%
mutate(
nmb_usual = wtp * (7.8 - mean_Ddalys.Usual_Care) - mean_Dcosts.Usual_Care,
nmb_empower = wtp * (7.8 - mean_Ddalys.Empower_Health) - mean_Dcosts.Empower_Health
) %>%
rowwise() %>%
mutate(
max_nmb = max(nmb_usual, nmb_empower)
)
# EVPI = Expected value with perfect information - Expected value with current information
current_expected_value <- max(mean(nmb_data$nmb_usual), mean(nmb_data$nmb_empower))
expected_value_perfect_info <- mean(nmb_data$max_nmb)
evpi_results$evpi[i] <- expected_value_perfect_info - current_expected_value
}
return(evpi_results)
}
# Calculate EVPI using the same grid
evpi_results <- calculate_evpi(data, wtp_grid)
# EVPI Plot with GDP thresholds
evpi_plot <- ggplot(evpi_results, aes(x = wtp, y = evpi)) +
geom_line(size = 1.2, color = "darkblue") +
geom_ribbon(aes(ymin = 0, ymax = evpi), alpha = 0.3, fill = "darkblue") +
# Add vertical lines for GDP thresholds
geom_vline(xintercept = wtp_thresholds_gdp[1], linetype = "dashed",
color = "orange", alpha = 0.7, size = 0.8) +
geom_vline(xintercept = wtp_thresholds_gdp[2], linetype = "dashed",
color = "blue", alpha = 0.7, size = 0.8) +
geom_vline(xintercept = wtp_thresholds_gdp[3], linetype = "dashed",
color = "red", alpha = 0.7, size = 0.8) +
# Add labels
annotate("text", x = wtp_thresholds_gdp[1] + 300, y = max(evpi_results$evpi) * 0.9,
label = paste0("0.5x GDP\n$", format(wtp_thresholds_gdp[1], big.mark = ",")),
size = 3, color = "orange") +
annotate("text", x = wtp_thresholds_gdp[2] + 300, y = max(evpi_results$evpi) * 0.8,
label = paste0("1x GDP\n$", format(wtp_thresholds_gdp[2], big.mark = ",")),
size = 3, color = "blue") +
annotate("text", x = wtp_thresholds_gdp[3] + 300, y = max(evpi_results$evpi) * 0.7,
label = paste0("3x GDP\n$", format(wtp_thresholds_gdp[3], big.mark = ",")),
size = 3, color = "red") +
labs(
title = "Expected Value of Perfect Information (EVPI)",
subtitle = paste0("Kenya GDP per capita = $", gdp_per_capita,
" | Thresholds: 0.5x, 1x, and 3x GDP"),
x = "Willingness-to-Pay per DALY Averted (USD)",
y = "EVPI (USD per patient)"
) +
scale_x_continuous(labels = scales::dollar_format(),
limits = c(0, max_wtp),
breaks = seq(0, max_wtp, by = 2000)) +
scale_y_continuous(labels = scales::dollar_format()) +
theme_minimal() +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray50")
print(evpi_plot)
# Summary table with GDP thresholds
summary_table <- data.frame(
Metric = c("Kenya GDP per capita",
"0.5x GDP Threshold", "1x GDP Threshold", "3x GDP Threshold",
"Mean Cost - Empower Health", "Mean Cost - Usual Care",
"Mean DALYs - Empower Health", "Mean DALYs - Usual Care",
"Mean Incremental Cost", "Mean DALYs Averted",
"Mean ICER", "Median ICER",
"95% CI ICER (Bootstrap)",
paste0("Probability ICER < 0.5x GDP ($", format(wtp_thresholds_gdp[1], big.mark = ","), ")"),
paste0("Probability ICER < 1x GDP ($", format(wtp_thresholds_gdp[2], big.mark = ","), ")"),
paste0("Probability ICER < 3x GDP ($", format(wtp_thresholds_gdp[3], big.mark = ","), ")"),
paste0("EVPI at 0.5x GDP ($", format(wtp_thresholds_gdp[1], big.mark = ","), ")"),
paste0("EVPI at 1x GDP ($", format(wtp_thresholds_gdp[2], big.mark = ","), ")"),
paste0("EVPI at 3x GDP ($", format(wtp_thresholds_gdp[3], big.mark = ","), ")")),
Value = c(
sprintf("$%s", format(gdp_per_capita, big.mark = ",")),
sprintf("$%s", format(wtp_thresholds_gdp[1], big.mark = ",")),
sprintf("$%s", format(wtp_thresholds_gdp[2], big.mark = ",")),
sprintf("$%s", format(wtp_thresholds_gdp[3], big.mark = ",")),
sprintf("$%s", format(round(mean(data$mean_Dcosts.Empower_Health), 2), big.mark = ",")),
sprintf("$%s", format(round(mean(data$mean_Dcosts.Usual_Care), 2), big.mark = ",")),
round(mean(data$mean_Ddalys.Empower_Health), 4),
round(mean(data$mean_Ddalys.Usual_Care), 4),
sprintf("$%s", format(round(icer_summary$mean_inc_cost, 2), big.mark = ",")),
round(icer_summary$mean_dalys_averted, 4),
sprintf("$%s", format(round(icer_summary$mean_icer, 0), big.mark = ",")),
sprintf("$%s", format(round(icer_summary$median_icer, 0), big.mark = ",")),
sprintf("$%s - $%s",
format(round(boot_icer_summary$lower_95, 0), big.mark = ","),
format(round(boot_icer_summary$upper_95, 0), big.mark = ",")),
sprintf("%.1f%%", boot_icer_summary$prob_cost_effective_at_0.5x_GDP * 100),
sprintf("%.1f%%", boot_icer_summary$prob_cost_effective_at_1x_GDP * 100),
sprintf("%.1f%%", boot_icer_summary$prob_cost_effective_at_3x_GDP * 100),
sprintf("$%s", format(round(evpi_results$evpi[evpi_results$wtp == wtp_thresholds_gdp[1]], 2), big.mark = ",")),
sprintf("$%s", format(round(evpi_results$evpi[evpi_results$wtp == wtp_thresholds_gdp[2]], 2), big.mark = ",")),
sprintf("$%s", format(round(evpi_results$evpi[evpi_results$wtp == wtp_thresholds_gdp[3]], 2), big.mark = ","))
)
)
print("Summary Table - Empower Health vs Usual Care with Kenya GDP Thresholds:")## [1] "Summary Table - Empower Health vs Usual Care with Kenya GDP Thresholds:"
## Metric Value
## 1 Kenya GDP per capita $2,000
## 2 0.5x GDP Threshold $1,000
## 3 1x GDP Threshold $2,000
## 4 3x GDP Threshold $6,000
## 5 Mean Cost - Empower Health $4,848.94
## 6 Mean Cost - Usual Care $4,278.65
## 7 Mean DALYs - Empower Health 7.2995
## 8 Mean DALYs - Usual Care 7.4118
## 9 Mean Incremental Cost $570.28
## 10 Mean DALYs Averted 0.1123
## 11 Mean ICER $5,087
## 12 Median ICER $5,081
## 13 95% CI ICER (Bootstrap) $5,071 - $5,087
## 14 Probability ICER < 0.5x GDP ($1,000) 0.0%
## 15 Probability ICER < 1x GDP ($2,000) 0.0%
## 16 Probability ICER < 3x GDP ($6,000) 100.0%
## 17 EVPI at 0.5x GDP ($1,000) $0
## 18 EVPI at 1x GDP ($2,000) $0
## 19 EVPI at 3x GDP ($6,000) $0
# Save plots
# ggsave("cost_effectiveness_scatter.png", plot = ce_scatter, width = 8, height = 6)
# ggsave("icer_scatter.png", plot = icer_scatter, width = 8, height = 6)
# ggsave("ceac_plot.png", plot = ceac_plot, width = 8, height = 6)
# ggsave("nmb_plot.png", plot = nmb_plot, width = 8, height = 6)
# ggsave("evpi_plot.png", plot = evpi_plot, width = 8, height = 6)
# ggsave("ce_plane.png", plot = ce_plane, width = 8, height = 6)