The prior tutorials have focused on constructing economic models to simulate disease progression, costs, and quality-adjusted life-years (QALYs). While incremental cost-effectiveness ratios (ICERs) have been computed and probabilistic sensitivity analysis (PSA) has been employed, we have not yet formalized cost-effectiveness analysis (CEA) or represented decision uncertainty.
In this analysis we will perform a CEA given the output of model from the “Semi-Markov Multi-state Model” tutorial. We will use the CEA functions from hesim
to summarize decision uncertainty and ggplot2
for visualization. The CEA will be peformed for a single target population, but you can review the hesim
tutorial on CEA and the references therein for an example of CEA in the context of multiple subgroups.
library("hesim")
library("ggplot2")
library("magrittr")
theme_set(theme_minimal()) # Set ggplot2 theme
CEA is based on estimating the net monetary benefit (NMB). For a given parameter set \(\theta\), the NMB with treatment \(j\) is computed as the difference between the monetized health gains from an intervention less costs, or,
\[ \begin{aligned} NMB(j,\theta) = e_{j}(\theta)\cdot k- c_{j}(\theta), \end{aligned} \]
where \(e_{j}\) and \(c_{j}\) are measures of health outcomes (e.g. QALYs) and costs using treatment \(j\) respectively, and \(k\) is a decision makers willingness to pay (WTP) per unit of health outcomes. The optimal treatment is the one that maximizes the expected NMB,
\[ \begin{aligned} j^{*} = \text{argmax}_j E_{\theta} \left[NMB(j,\theta)\right]. \end{aligned} \]
For a pairwise comparison, treatment \(1\) is preferred to treatment \(0\) if the expected incremental net monetary benefit (INMB) is positive; that is, if \(E_\theta \left[INMB\right] > 0\) where the INMB is given by
\[ \begin{aligned} INMB(\theta) = NMB(j = 1, \theta) - NMB(j = 0, \theta). \end{aligned} \]
Treatments can be compared in an equivalent manner using the incremental cost-effectiveness ratio (ICER). The most common case occurs when a new treatment is more effective and more costly so that treatment \(1\) is preferred to treatment \(0\) if the ICER is less than the WTP threshold \(k\),
\[ \begin{aligned} k > \frac{E_\theta[c_{1} - c_{0}]}{E_\theta[e_{1} - e_{0}]} = ICER. \end{aligned} \] There are three additional cases. Treatment \(1\) is considered to dominate treatment 0 if it is more effective and less costly. Treatment \(1\) is dominated by treatment \(0\) if it is less effective and more costly. Finally, treatment \(1\) is preferred to treatment \(0\) if it is less effective and less costly when \(k < ICER\).
In practice, the distribution of \(\theta\) is simulated from the output of the PSA. Specifically, For each treatment strategy, a PSA produces \(m\) random draws from the distribution of health outcomes and costs,
\[ \begin{aligned} e_{j} &= [e_{j}^1, e_{j}^2, \dots, e_{j}^m] \\ c_{j} &= [c_{j}^1, c_{j}^2, \dots, c_{j}^m]. \end{aligned} \]
CEA can be performed using the hesim::cea()
and hesim::cea_pw()
functions. cea()
summarizes results by taking into account each treatment strategy in the analysis, while cea_pw()
summarizes “pairwise” results in which each treatment is compared to a comparator. Both functions return a number of outputs for summarizing a PSA.
The functions are generic functions, meaning that their implementation depends on the class of their input. The default method works on a generic data frame object like we simulated in the non-hesim
Markov cohort modeling tutorial.
markov_ce <- readRDS("markov-cohort-ce_sim.rds")
markov_ce
## sample strategy dqalys dcosts
## 1: 1 New 22.46453 424193.11
## 2: 1 SOC 19.79403 171692.25
## 3: 2 New 24.63744 411513.48
## 4: 2 SOC 21.75544 139341.69
## 5: 3 New 21.94022 582726.30
## ---
## 1996: 998 SOC 20.36152 74070.17
## 1997: 999 New 24.32977 547848.32
## 1998: 999 SOC 22.45038 296303.99
## 1999: 1000 New 22.92601 469550.31
## 2000: 1000 SOC 20.85917 212142.96
However, when a simulation has been conducted with hesim
, it is typically more convenient to use the hesim::ce
object generated from the economic model (as in the hesim
Markov cohort modeling tutorial).
markov_hesim_ce <- readRDS("markov-cohort-hesim-ce_sim.rds")
hesim_dat <- readRDS("markov-cohort-hesim_data.rds")
markov_hesim_ce
## $costs
## category dr sample strategy_id costs grp_id
## 1: treatment 0.03 1 1 53080.88 1
## 2: treatment 0.03 1 2 332405.01 1
## 3: treatment 0.03 2 1 51515.33 1
## 4: treatment 0.03 2 2 330330.38 1
## 5: treatment 0.03 3 1 49739.19 1
## ---
## 5996: total 0.03 998 2 636329.42 1
## 5997: total 0.03 999 1 72277.30 1
## 5998: total 0.03 999 2 348862.87 1
## 5999: total 0.03 1000 1 98696.43 1
## 6000: total 0.03 1000 2 359055.98 1
##
## $qalys
## dr sample strategy_id qalys grp_id
## 1: 0.03 1 1 22.51374 1
## 2: 0.03 1 2 24.33262 1
## 3: 0.03 2 1 21.82532 1
## 4: 0.03 2 2 24.30805 1
## 5: 0.03 3 1 20.86320 1
## ---
## 1996: 0.03 998 2 23.22127 1
## 1997: 0.03 999 1 20.53312 1
## 1998: 0.03 999 2 23.04054 1
## 1999: 0.03 1000 1 19.77835 1
## 2000: 0.03 1000 2 22.06166 1
##
## attr(,"class")
## [1] "ce"
We focus our illustration on the case where the input is a hesim::ce
object.
wtp <- seq(0, 250000, 500) # Willingness to pay per QALY
cea_pw_out <- cea_pw(markov_hesim_ce,
comparator = 1, # Comparator is SOC (ID = 1)
dr_qalys = 0.03, dr_costs = 0.03,
wtp)
cea_out <- cea(markov_hesim_ce,
dr_qalys = 0.03, dr_costs = 0.03,
k = wtp)
For completeness, we note that results would be nearly identical using the default method.
cea_pw_out2 <- cea_pw(markov_ce, comparator = "SOC",
k = wtp,
sample = "sample", strategy = "strategy",
e = "dqalys", c = "dcosts")
cea_pw_out$summary
## strategy_id grp_id ie_mean ie_lower ie_upper ic_mean ic_lower ic_upper
## 1: 2 1 2.046011 1.045688 3.073314 257392.6 200710.6 288832.9
## icer
## 1: 125802.2
cea_pw_out2$summary
## strategy grp ie_mean ie_lower ie_upper ic_mean ic_lower ic_upper icer
## 1: New 1 2.092274 1.038125 3.141643 261594.3 212237.7 291653.5 125028.7
The output of cea_pw()
can be used to create an ICER table with icer()
, which can, in turn, be formatted for pretty printing with format()
. A WTP threshold is needed to compute the INMB and is set with the wtp
argument. Estimates of incremental QALYs and incremental costs are computed by averaging over PSA samples. Confidence intervals (CIs) are computed using the quantiles of the PSA and 95% CIs are displayed by default.
labs <- get_labels(hesim_dat)
icer(cea_pw_out, wtp = 50000, labels = labs) %>%
format()
## Outcome New
## 1: Incremental QALYs 2.05 (1.05, 3.07)
## 2: Incremental costs 257,393 (200,711, 288,833)
## 3: Incremental NMB -155,092 (-210,411, -75,154)
## 4: ICER 125,802
Note that get_labels()
was used to create labels for the identification variables in the hesim_data()
object. This is useful because it can be passed to summary functions (e.g., icer()
) as well as plotting functions (as will be shown below).
There are a number of measures that are typically used to represent decision uncertainty including cost-effectiveness planes, cost-effectiveness acceptability curves (CEACs), cost-effectiveness acceptability frontiers (CEAFs), and the expected value of perfect information (EVPI). ggplot2
graphics can be quickly generated with hesim
using the functions plot_ceplane()
, plot_ceac()
, plot_ceaf()
, and plot_evpi()
. Users who desire more control over plotting may also create custom plots using the output of cea()
and cea_pw()
as demonstrated below.
The cost-effectiveness plane plots the incremental effectiveness of a treatment strategy (relative to a comparator) against the incremental cost of the treatment strategy. The plot is useful because it demonstrates both the uncertainty and the magnitude of the estimates. Each point on the plot is from a particular random draw from the PSA.
Data for plotting a cost-effectiveness plane comes from the delta
output generated from the cea_pw()
function, which, for each sampled parameter set and treatment strategy, estimates differences in costs and QALYs relative to the comparator.
head(cea_pw_out$delta)
## sample strategy_id grp_id ie ic
## 1: 1 2 1 1.818878 250483.6
## 2: 2 2 1 2.482730 244696.0
## 3: 3 2 1 1.820730 259581.0
## 4: 4 2 1 1.094451 281016.0
## 5: 5 2 1 2.188967 258238.0
## 6: 6 2 1 2.661375 247998.2
The dotted line in the plot is the WTP line, with slope equal to the desire value of \(k\) (in this case $100,000). For a chosen \(k\), points below the line are cost-effective while those above it are not.
plot_ceplane(cea_pw_out, k = 100000, labels = labs)
A useful summary measure for quantifying uncertainty is the probability that each treatment strategy is the most cost effective, which is estimated from simulation output as the proportion of simulation draws that each strategy has the highest NMB.
plot_ceac(cea_out, labels = labs)
The probability that the new treatment is the most cost-effective is increasing in WTP.
The difference between this plot and the one above is that it compares each strategy to a single comparator rather than considering all strategies simultaneously. Since there are only two treatment strategies the distinction is not meaningful, but it can be important when there are 3 or more treatment strategies.
plot_ceac(cea_pw_out, labels = labs)
One drawback of the CEAC is that the probability of being cost-effective cannot be used to determine the optimal treatment option. Instead, if a decision-makers objective is to maximize health gain, then decisions should be based on the expected NMB. The cost-effectiveness acceptability frontier (CEAF), which plots the probability that the optimal treatment strategy (i.e., the strategy with the highest expected NMB) is cost-effective, is appropriate in this context.
plot_ceaf(cea_out, labels = labs)
A limitation of the prior measures are that they ignore the magnitude of cost or QALY gains. A measure which combines the probability of being most effective with the magnitude of the expected NMB is the EVPI, which, intuitively, provides an estimate of the amount that a decision maker would be willing to pay to collect additional data and completely eliminate uncertainty. Mathematically, the EVPI is defined as the difference between the maximum expected NMB given perfect information and the maximum expected NMB given current information. In other words, we calculate the NMB for the optimal treatment strategy for each random draw of the parameters and compare that to the NMB for the treatment strategy that is optimal when averaging across all parameters,
\[ \begin{aligned} EVPI &= E_\theta \left[max_j NMB(j, \theta)\right] - max_j E_\theta \left [ NMB(j, \theta)\right]. \\ \end{aligned} \]
The cea()
function performs the EVPI calculation across all simulation draws from the PSA and for a number of WTP values \(k\). A plot by group of the the EVPI for different values of \(k\) is shown below. The kink in the plot represents the value of \(k\) where the optimal strategy changes.
plot_evpi(cea_out)
ggplot2
As noted above, it is often desirable to have full control over plotting. For this reason, the output of cea()
and cea_pw()
contain all data required to represent decision uncertainty: the delta
element returned by cea_pw()
can be used to create a cost-effectiveness plane, the mce
element returned by cea()
can be used to create a CEAC for simultaneous comparisons and a CEAF, the ceac
element returned by cea_pw()
can be used to create a CEAC for pairwise comparisons, and the evpi
element returned by cea()
can be used to plot the EVPI.
We demonstrate each case below, while leveraging a couple of helper functions.
strategy_factor <- function (x) {
factor(x, levels = 1:2, labels = c("SOC", "New"))
}
format_dollar <- function(x) {
paste0("$", formatC(x, format = "d", big.mark = ","))
}
ylim <- max(cea_pw_out$delta[, ic]) * 1.1
xlim <- ceiling(max(cea_pw_out$delta[, ie]) * 1.1)
ggplot(cea_pw_out$delta,
aes(x = ie, y = ic, col = strategy_factor(strategy_id))) +
geom_jitter(size = .5) +
xlab("Incremental QALYs") +
ylab("Incremental cost") +
scale_y_continuous(limits = c(-ylim, ylim),
labels = format_dollar) +
scale_x_continuous(limits = c(-xlim, xlim), breaks = seq(-6, 6, 2)) +
theme(legend.position = "bottom") +
scale_colour_discrete(name = "Strategy") +
geom_abline(slope = 100000, linetype = "dashed") +
geom_hline(yintercept = 0) +
geom_vline(xintercept = 0)
ggplot(cea_out$mce,
aes(x = k, y = prob, col = strategy_factor(strategy_id))) +
geom_line() +
xlab("Willingness to pay") +
ylab("Probability most cost-effective") +
scale_x_continuous(breaks = seq(0, max(wtp), length.out = 6),
label = format_dollar) +
theme(legend.position = "bottom") +
scale_colour_discrete(name = "Strategy")
ggplot(cea_pw_out$ceac,
aes(x = k, y = prob, col = strategy_factor(strategy_id))) +
geom_line() +
xlab("Willingness to pay") +
ylab("Probability most cost-effective") +
scale_x_continuous(breaks = seq(0, max(wtp), length.out = 6),
label = format_dollar) +
theme(legend.position = "bottom") +
scale_colour_discrete(name = "Strategy")
ggplot(cea_out$mce[best == 1],
aes(x = k, y = prob, col = strategy_factor(strategy_id))) +
geom_line() +
xlab("Willingness to pay") +
ylab("Probability most cost-effective") +
scale_x_continuous(breaks = seq(0, max(wtp), length.out = 6),
label = format_dollar) +
theme(legend.position = "bottom") +
scale_colour_discrete(name = "Strategy")
ggplot(cea_out$evpi, aes(x = k, y = evpi)) +
geom_line() +
xlab("Willingness to pay") +
ylab("Expected value of perfect information") +
scale_x_continuous(breaks = seq(0, max(wtp), length.out = 6),
label = format_dollar) +
scale_y_continuous(label = format_dollar) +
theme(legend.position = "bottom")