Decision Trees, Markov Cohorts and Probabilistic Sensitivity Analysis: How a Model Turns Evidence into a Decision

Health EconomicsClinical Epidemiology ResearchMethodology and Research Design
Decision Trees, Markov Cohorts and Probabilistic Sensitivity Analysis: How a Model Turns Evidence into a Decision
On this page

อ่านฉบับภาษาไทย (Thai version)

Abstract

A district hospital committee has been told that a new preventive treatment, C, gives 6.00 quality-adjusted life years (QALYs) per patient over a lifetime for 250,000 THB. Usual care gives 5.00 for 100,000 THB. The trial behind C lasted one year. This article opens the model that turned that year into a lifetime. A decision tree prices the first year: each outcome's chance times its cost or health, added up. One year alone makes C cost close to a million baht per QALY, because its benefit comes later. So the model follows 1,000 people who move once a year between Well, Sick and Dead until all have died, which gives the committee's numbers. Changing one input at a time shows that the trial's risk ratio decides the answer. Rerunning the model 1,000 times with its two least certain inputs drawn at random puts C ahead in 542 of the 1,000 runs, 54.2%: a share, not one number.


Visual summary: read down the card for the key idea, then the five numbered takeaways: the first year as a tree, the lifetime totals, the risk ratio of 0.59 that C must stay below, and 542 of 1,000 runs with C ahead, so the answer is a share, not one number.

Where do lifetime numbers come from?

A district hospital committee must decide whether to fund C, a new preventive treatment for adults with a chronic condition, or keep usual care, A. The analysts' report says that over a lifetime A costs 100,000 THB (Thai baht) per patient and gives 5.00 QALYs. C costs 250,000 THB and gives 6.00. A QALY, a quality-adjusted life year, is a year of life weighted by its quality, from 1 for full health to 0 for death QALY, DALY and the Utilities Behind Them: What Is Actually Being Measured.

So C buys 1.00 extra QALY for 150,000 THB more, a price of 150,000 THB per extra QALY, under the committee's limit of 160,000 THB. The committee's limit, 160,000 THB per QALY, is also called the threshold.

Value the extra QALY at the threshold and subtract the extra cost: 160,000 × 1.00 − 150,000 = 10,000 THB. That is the incremental net monetary benefit (INMB). Positive means the extra cost is worth paying. We call this 10,000 THB C's lead; the full lesson is in the article on the ICER and net benefit.

The trial behind C followed patients for one year. On usual care 13 of 60 had the complication the treatment prevents, 21.67%, against 8 of 66 on C, 12.12%.

So a committee member asks two questions. Where do lifetime numbers come from, when nobody was watched for a lifetime? And the trial's risk ratio of 0.56 could, by its 95% confidence interval, lie anywhere from 0.25 to 1.26: how sure is C's lead of 10,000 THB?

Both questions have the same answer, a model. A common shortcut treats a model's output as one fixed number. This article shows where the number comes from and how far it can move. It uses a decision tree for the first year, one group of patients followed year by year for the lifetime, and the model rerun with its least certain inputs drawn at random.

The first year as a decision tree

Read first on this site. These posts teach in full how to build a decision tree step by step and when a tree is the right structure. This section keeps only the committee's first year.

Start with what the trial saw: one year. Within it, three things can happen to a patient: they stay well, they have the complication, or they die of another cause. A life table for people of this age shows that 6.5% of them die of other causes each year, whatever the treatment. On usual care 21.67% have the complication, so 71.83% stay well; on C 12.12% have it, so 81.38% stay well.

Each outcome has a cost and a health value for the year. A study of patients' preferences gives each kind of year a quality weight, its utility: 0.80 QALYs for a year lived well and 0.50 for a year with the complication. On C a well year also carries the drug, at its price of 37,800 THB a year, and the hospital's cost list prices a year of complication care at 16,900 THB. A death adds no cost and no health, and costs that both options share are left out, so a well year on usual care costs 0.

To get one patient's expected cost, multiply each outcome's cost by its chance and add them up. On usual care that is 21.67% × 16,900 = 3,662 THB, since the other two branches cost nothing. On C it is 81.38% × 37,800 + 12.12% × 16,900 = 32,810 THB. Health works the same way: 71.83% × 0.80 + 21.67% × 0.50 = 0.683 QALYs on usual care, and 0.712 on C.

This drawing is a decision tree [6]: a square for the choice, a circle where chance takes over, and a payoff at the end of each branch. Working from the ends back to the circles is called rolling back the tree, and its rule fits on one line, where p is a branch's chance and V its payoff, a cost or QALYs:

$$E[V]=\sum_k p_k\,V_k$$

Follow each branch from the square on the left: the percentages are the chances, and the ends carry each outcome's cost and QALYs for year 1. Each circle shows its branches added up, so C costs 32,810 THB against 3,662 THB and gives 0.712 QALYs against 0.683.

Why one year is not enough

Read the tree as the whole answer and C looks hopeless. In year 1 it costs 29,148 THB more per patient and adds 0.029 QALYs. That is close to a million baht for each extra QALY, more than six times the committee's limit.

The tree is not wrong; it is too short. C pays off through the complications it prevents, and a complication does not end with the year: the patient stays sick, needs care every year after, and dies sooner. A tree would need a new fork for every year of every patient's life, so the drawing would grow with each year it covers. A neater way is to stop following one patient down every branch and follow a whole group instead.

R code decision_tree.R
# The first year as a decision tree: each branch's chance times its cost or health, added up.
f <- function(x, d = 0) formatC(x, format = "f", digits = d, big.mark = ",")
p_sick <- c(A = 13 / 60, C = 8 / 66)            # complication within the year, from the trial
p_well <- 1 - p_sick - 0.065                     # 6.5% die of other causes; the rest stay well
cost <- p_well * c(0, 37800) + p_sick * 16900    # C's drug while well; a year of complication care
qaly <- p_well * 0.80 + p_sick * 0.50            # a death adds no cost and no health
cat("Stay well (%): A", f(100 * p_well[1], 2), " C", f(100 * p_well[2], 2), "\n")
cat("Complication (%): A", f(100 * p_sick[1], 2), " C", f(100 * p_sick[2], 2), "\n")
cat("Risk ratio, C over A:", f(p_sick[2] / p_sick[1], 2), "\n")
cat("Expected cost per patient (THB): A", f(cost[1]), " C", f(cost[2]), "\n")
cat("Expected QALYs per patient: A", f(qaly[1], 3), " C", f(qaly[2], 3), "\n")
cat("C minus A: cost (THB)", f(diff(cost)), " QALYs", f(diff(qaly), 3), "\n")
Output of the run decision_tree.log
Stay well (%): A 71.83  C 81.38
Complication (%): A 21.67  C 12.12
Risk ratio, C over A: 0.56
Expected cost per patient (THB): A 3,662  C 32,810
Expected QALYs per patient: A 0.683  C 0.712
C minus A: cost (THB) 29,148  QALYs 0.029
The tree's arithmetic for one patient in year 1, usual care A and C.

One group, three states, one step a year

Read first on this site. This post teaches in full how a share for one step comes from a rate, and why the length of the step stays in the calculation. The full lesson on moving a whole group between states is linked under the model's name below; this section keeps only the committee's group of 1,000.

Put 1,000 people in one of three states, Well, Sick or Dead, and once a year move a fixed share of each state to another; the shares are the tree's chances. From Well, 21.67% fall sick on usual care, 6.5% die of another cause and 71.83% stay. From Sick, 13% die each year, the yearly share of deaths in a cohort study of patients followed after the complication, and 87% stay sick. Nobody recovers, and nobody leaves Dead.

These shares are chances per one-year step, not rates. A rate counts events per person-time. A share for one step is derived from it with the length of the step kept in the calculation. The life table's 6.5% a year, for example, matches a death rate of 0.0672 per person-year, because $1-e^{-0.0672}=0.065$.

The same one-year step decides how the risk ratio is used. The trial's shares can be used as they are only because the trial and the step both last one year. For the same reason its risk ratio of 0.56, a ratio of one-year risks, multiplies usual care's one-year share; a hazard ratio would be applied to the rate instead.

After year 1 on usual care the group holds 718.3 Well, 216.7 Sick and 65.0 Dead: the tree's three branches, now as counts. Fractions of a person are fine, because these are expected counts, the average over many such groups. Year 2 starts from those counts, and the steps below show how 216.7 Sick become 344.1.

Follow the arrows out of Well: each year 21.67% fall sick on usual care and 12.12% on C, and 6.5% die of another cause. From Sick, 13% die each year. The box on the right shows the 1,000 people after year 1, so the group's first year matches the tree's.

The usual-care group's first two years, step by step

1,000 people on usual care, counted at the end of each year. Each step uses only the counts from the step before.

  1. Year 1

    \[ 1000\times 71.83\%=718.3,\ 1000\times 21.67\%=216.7,\ 1000\times 6.5\%=65.0 \]

    All 1,000 start Well, so year 1 splits them as the tree did.

  2. Year 2, Sick

    \[ 216.7\times 87\%+718.3\times 21.67\%=188.5+155.6=344.1 \]

    The sick who stay sick, plus the well who fall sick this year.

  3. Year 2, Well and Dead

    \[ 718.3\times 71.83\%=516.0,\ 65.0+718.3\times 6.5\%+216.7\times 13\%=139.9 \]

    The well who stay well; and the dead so far, plus this year's deaths from both states.

  4. QALYs per person

    \[ \frac{718.3\times 0.80+216.7\times 0.50}{1000}=0.683,\ \frac{516.0\times 0.80+344.1\times 0.50}{1000}=0.585 \]

    Each year's count in each state times its utility, shared over the 1,000.

Result: Year 1 earns 0.683 QALYs per person, the same as the tree, and year 2 earns 0.585, because fewer people are well. The model keeps stepping like this until everyone has died.

R code first_years.R
# 1,000 people start Well and move once a year by the shares in each row; counted at year end.
f <- function(x, d = 1) formatC(x, format = "f", digits = d, big.mark = ",")
p_sick <- 13 / 60                         # usual care: the trial's one-year complication risk
move <- rbind(c(1 - p_sick - 0.065, p_sick, 0.065),  # from Well: stay, fall sick, die
              c(0, 0.87, 0.13),                    # from Sick: stay sick, die
              c(0, 0, 1))                          # Dead stays dead
utility <- c(0.80, 0.50, 0)
year1 <- c(1000, 0, 0) %*% move; year2 <- year1 %*% move
cat("Year 1, Well Sick Dead:", f(year1), "\n")
cat("Year 2, Well Sick Dead:", f(year2), "\n")
cat("Year 2 Sick = still sick + newly sick:", f(year1[2] * 0.87), "+", f(year1[1] * p_sick), "\n")
cat("QALYs per person, years 1, 2:", f(c(year1 %*% utility, year2 %*% utility) / 1000, 3), "\n")
cat("Death rate behind the 6.5% yearly share (per person-year):", f(-log(1 - 0.065), 4), "\n")
Output of the run first_years.log
Year 1, Well Sick Dead: 718.3 216.7 65.0
Year 2, Well Sick Dead: 516.0 344.1 139.9
Year 2 Sick = still sick + newly sick: 188.5 + 155.6
QALYs per person, years 1, 2: 0.683 0.585
Death rate behind the 6.5% yearly share (per person-year): 0.0672
The usual-care group's first two years, from the yearly shares.

The name in the title: a Markov cohort

This kind of model carries the name in this article's title: a Markov model follows a group of people between a few health states, one cycle at a time. In the series' own example, of 1,000 healthy people, 800 stay healthy, 150 fall sick and 50 die in the first cycle. The full lesson is in Building a Markov Cohort Model Step by Step: Follow the Cohort Through Time.

Its defining rule is the one used above: where a person goes next depends only on the state they are in now, not on how they got there [1, 2]. The group it follows is called a cohort and each step a cycle, so the committee's model is a Markov cohort with a one-year cycle.

An overview on this site sets decision trees and these models side by side:

R code markov_recap.R
# The series' own example of one cycle: 1,000 people start healthy and move by one row of shares.
start <- 1000
row <- c(healthy = 0.80, sick = 0.15, dead = 0.05)   # from Healthy: stay, fall sick, die
cat("People who start healthy:", start, "\n")
cat("Still healthy after the first cycle:", start * row[["healthy"]], "\n")
cat("Fallen sick in the first cycle:", start * row[["sick"]], "\n")
cat("Died in the first cycle:", start * row[["dead"]], "\n")
Output of the run markov_recap.log
People who start healthy: 1000
Still healthy after the first cycle: 800
Fallen sick in the first cycle: 150
Died in the first cycle: 50
The series' own example of one cycle: the numbers quoted in the paragraph above.

A lifetime, added up

Keep stepping for 100 years, until everyone has died, and add up the years each person spends in each state. On usual care that is 2.55 years Well and 5.92 years Sick per person; on C it is 4.37 years Well and 5.01 Sick.

A shortcut gives the same years by hand. Someone in Well stays there 71.83 ÷ (21.67 + 6.5) = 2.55 years on average: the yearly chance of staying divided by the yearly chance of leaving. Of those who leave Well, the share 21.67 ÷ (21.67 + 6.5) fall sick; each is counted Sick at the end of that year and then stays 87 ÷ 13 more years, so 21.67 ÷ (21.67 + 6.5) × (1 + 87 ÷ 13) = 5.92. On C the same steps give 81.38 ÷ (12.12 + 6.5) = 4.37 and 5.01.

Now value those years. Usual care gives 2.55 × 0.80 + 5.92 × 0.50 = 5.00 QALYs and costs 5.92 × 16,900 = 100,000 THB to the nearest 1,000. C gives 4.37 × 0.80 + 5.01 × 0.50 = 6.00 QALYs. It costs 4.37 × 37,800 + 5.01 × 16,900 = 250,000 THB to the nearest 1,000.

These are the committee's numbers. C's extra QALY comes from more years spent well, and C costs more because each of those years carries the drug. The lead is 160,000 × 1.00 − 150,000 = 10,000 THB.

Per patient over a lifetime, from 1,000 people followed until all have died. All inputs are illustrative.
Usual care, ANew treatment, C
Years Well2.554.37
Years Sick5.925.01
QALYs5.006.00
Cost, nearest 1,000 THB100,000250,000
R code lifetime.R
# Run each option's group of 1,000 for 100 years, until all have died, and add up the years.
f <- function(x, d = 0) formatC(x, format = "f", digits = d, big.mark = ",")
years_in <- function(p_sick, m = c(1000, 0, 0), total = 0) {   # m: Well, Sick, Dead at the start
  move <- rbind(c(1 - p_sick - 0.065, p_sick, 0.065), c(0, 0.87, 0.13), c(0, 0, 1))
  for (t in 1:100) { m <- m %*% move; total <- total + m }; total / 1000 }  # years per person
a <- years_in(13 / 60); c_ <- years_in(8 / 66)       # A usual care; C, from the trial's risks
qaly <- c(sum(a * c(0.80, 0.50, 0)), sum(c_ * c(0.80, 0.50, 0)))
cost <- c(sum(a * c(0, 16900, 0)), sum(c_ * c(37800, 16900, 0)))
cat("Well and sick years per person: A", f(a[1:2], 2), " C", f(c_[1:2], 2), "\n")
cat("QALYs: A", f(qaly[1], 2), " C", f(qaly[2], 2), "\n")
cat("Cost, nearest 1,000 THB: A", f(round(cost[1], -3)), " C", f(round(cost[2], -3)), "\n")
cat("C minus A: cost", f(round(diff(cost), -3)), " QALYs", f(diff(qaly), 2), "\n")
cat("Lead of C at 160,000 per QALY (THB):", f(round(160000 * diff(qaly) - diff(cost), -3)), "\n")
Output of the run lifetime.log
Well and sick years per person: A 2.55 5.92  C 4.37 5.01
QALYs: A 5.00  C 6.00
Cost, nearest 1,000 THB: A 100,000  C 250,000
C minus A: cost 150,000  QALYs 1.00
Lead of C at 160,000 per QALY (THB): 10,000
Both groups stepped for 100 years and added up: years, QALYs, cost and C's lead.
The controls start at usual care, A: the cards read 0.683 and 0.585 QALYs for years 1 and 2, and 5.00 over 60 years. Drag Well to Sick down from usual care's 21.67% to C's 12.12%, and watch the green Well band last longer and the total climb to 6.00, because people stay well for more years. The two white dots mark the Well counts after years 1 and 2; the explorer stops at 60 years because by then almost everyone has died. Leave the last control, how people are counted, on the end of each year for now; the section 'What the model forgets' explains what the other two do.

What the model forgets

Read first on this site. These posts teach in full when a model must remember how long someone has been sick, and which count earns each year. This section keeps only what both rules do to the committee's numbers.

The rule that only the present state matters is also the model's main limit. Here a patient in their first year with the complication and one in their tenth both die at the same 13% each year. If the first year after a complication were the dangerous one, the model would need to split Sick in two: a first sick year that everyone leaves after one step, which modellers call a tunnel state, and later sick years. Another way is to follow patients one at a time, each with their own history [2].

Counting everyone at the end of each year is a choice too. A person who dies during year 1 earns nothing for it. C's drug, which all 1,000 start on the first day, is charged only to those still well at the year's end.

The usual refinement counts each year at the average of its start and end, as if people moved halfway through it (called the half-cycle correction) [3]. In the series' own example, a cycle's healthy year is weighted 1.0 and a sick year 0.6 (that example's weights, not the committee's 0.80 and 0.50), and 800 of 1,000 people stay healthy while 150 fall sick in the first cycle.

Counted at the end, that is 0.800 × 1.0 + 0.150 × 0.6 = 0.890 QALYs per person. Counted at the average of start and end, it is (1.000 + 0.800) ÷ 2 × 1.0 + 0.150 ÷ 2 × 0.6 = 0.945. The full lesson is in Building a Markov Cohort Model Step by Step: Follow the Cohort Through Time.

Counting each year at the average of its start and end would have added half a year more in Well for everyone. Both options would gain 0.40 QALYs (half of 0.80), so C's extra stays 1.00 QALY. But C would also pay for half a year more of its drug, 0.5 × 37,800 = 18,900 THB.

C's lead of 10,000 THB would then become 10,000 − 18,900 = −8,900, about 9,000 THB behind. Its cost per extra QALY would rise from 150,000 to 168,900 THB, above the 160,000 limit. So if the committee had counted this way, the verdict would flip.

The committee counted at the year's end, so the lead is 10,000 THB, the cost is 150,000 THB per QALY, and every run in this article keeps that count. The counting rule is an input to report with the result; the explorer above lets you switch to the start of each year or the average of start and end.

R code half_cycle.R
# The series' own example, first cycle: 1,000 start healthy; 800 stay healthy, 150 fall sick.
f <- function(x, d) formatC(x, format = "f", digits = d)
w <- c(healthy = 1.0, sick = 0.6)                  # that example's weights, not the committee's
start <- c(1000, 0) / 1000; end <- c(800, 150) / 1000   # shares at the cycle's start and end
cat("Weights of a healthy and a sick year:", f(w, 1), "\n")
cat("Shares at the start of the cycle, healthy and sick:", f(start, 3), "\n")
cat("Shares at the end of the cycle, healthy and sick:", f(end, 3), "\n")
cat("QALYs per person, counted at the end:", f(sum(end * w), 3), "\n")
cat("QALYs per person, average of start and end count:", f(sum((start + end) / 2 * w), 3), "\n")
Output of the run half_cycle.log
Weights of a healthy and a sick year: 1.0 0.6
Shares at the start of the cycle, healthy and sick: 1.000 0.000
Shares at the end of the cycle, healthy and sick: 0.800 0.150
QALYs per person, counted at the end: 0.890
QALYs per person, average of start and end count: 0.945
The series' own example of the first cycle, counted at its end and at the average of its start and end: the numbers quoted in the paragraphs above.
R code counting.R
# Count each year at its end, or at the average of its start and end; all 1,000 start Well.
f <- function(x, d = 0) formatC(x, format = "f", digits = d, big.mark = ",")
yrs <- function(p) c((1 - p - 0.065) / (p + 0.065), p / (p + 0.065) / 0.13)  # Well, Sick years
a <- yrs(13 / 60); c_ <- yrs(8 / 66)                          # counted at year ends
dq <- round(sum((c_ - a) * c(0.80, 0.50)), 2)                  # C's extra QALYs, 1.00
dc <- round(c_[1] * 37800 + (c_[2] - a[2]) * 16900, -3); extra <- 0.5 * 37800  # THB
lead <- 160000 * dq - dc - c(0, extra)            # C's lead: year ends, average count
cat("QALYs added to each option by the average count:", f(0.5 * 0.80, 2), "\n")
cat("Drug cost added to C by the average count (THB):", f(extra), "\n")
cat("Lead of C (THB), year ends and average:", f(lead), " nearest 1,000:", f(round(lead, -3)), "\n")
cat("Cost per extra QALY (THB), year ends and average:", f(dc / dq), f((dc + extra) / dq), "\n")
Output of the run counting.log
QALYs added to each option by the average count: 0.40
Drug cost added to C by the average count (THB): 18,900
Lead of C (THB), year ends and average: 10,000 -8,900  nearest 1,000: 10,000 -9,000
Cost per extra QALY (THB), year ends and average: 150,000 168,900
C's lead and cost per extra QALY with each year counted at its end or at the average of its start and end: the numbers quoted in the paragraphs above.

Where every input comes from

Each number the model uses comes from a different place, as the table shows, and the model's job is to join them. In this teaching example every input is illustrative. In a real model each one would carry its source, its value and how uncertain it is, in a table like this one.

The two ways out of Well come from different sources, the trial and the life table, yet they share the same patients. Someone who dies of another cause can no longer have the complication. Turning each into a yearly share on its own and adding the two can overstate how many leave Well each year. A fuller model therefore adds the two rates first and then splits the one exit between them, as Rates, Probabilities and Transition Probabilities: Keep Time in the Calculation shows.

The drug is taken only while a patient is well; after a complication, complication care replaces it. Every year also counts the same however far ahead it lies, to match the committee's figures; the costing article shows how future years are usually shrunk.

Where each input of the model comes from. All values are illustrative.
InputValueWhere it comes from
Complication each year, usual care21.67%the trial: 13 of 60
Complication each year, C12.12%the trial: 8 of 66, a risk ratio of 0.56
Death from another cause each year6.5%a life table for this age
Death each year once sick13%a cohort study after the complication
Utility of a year Well, Sick0.80, 0.50a study of patients' preferences
Sick care a year16,900 THBa hospital cost list
C's drug a year, while well37,800 THBthe drug's price

Check the model before using it

Read first on this site. This post teaches in full how to test a model's equations and code, and how to judge whether it is the right model. This section keeps only the checks on the committee's model.

Before trusting the model, check it in two ways. First, was it built right? Every row of shares adds up to 100%, nobody comes back from Dead, and the first year gives back what it was built from: 216.7 of 1,000 fall sick on usual care, the trial's 21.67%, and 12.12% on C. Modellers call these checks verification.

Second, is it the right model for these patients? The years lived can be compared with a life table, and the model's forecasts with data it was not built on, such as patients followed for longer than the trial. That is validation, from face validity (does the structure make clinical sense?) to external validity (does it predict data it was not built on?) [2].

Which input could flip the answer?

Read first on this site. This post teaches sensitivity analysis for health economic evaluation in full, one input at a time and all inputs together. This section keeps only the inputs of the committee's model.

A lead of 10,000 THB on a lifetime cost of 250,000 THB is small. One way to see how fragile it is: change one input at a time to a low and a high value. Keep the rest at their usual values, and run the model again [4].

At the ends of the trial's interval, a risk ratio of 0.25 gives C a lead of 188,000 THB, and 1.26 leaves it 131,000 THB behind. A drug price of 30,000 or 45,000 THB a year moves the lead to 44,000 or −21,000 THB. A utility of 0.40 or 0.60 for a sick year moves it to 25,000 or −4,000 THB. A sick-care cost of 11,000 or 24,000 THB a year gives 5,000 or 17,000 THB: a dearer complication helps C, because C prevents it.

Drawn as bars, widest at the top, these results make a tornado diagram. The bars show the lead in baht rather than a cost per QALY, because a ratio swings wildly, and can change sign, when the extra QALYs come close to zero. Only the risk ratio swings the answer far to both sides.

The lead is exactly zero at a risk ratio of 0.59, the switching value. C stays ahead only while the true risk ratio is below it, yet the trial cannot rule out anything from 0.25 to 1.26.

Compare the widths of the bars: each shows how far one input moves C's lead between its low and high value. Only the risk ratio bar reaches far into both the orange and the green side of the dashed zero line. So the trial's effect, not the prices or the utilities, decides whether C is worth funding.
R code one_way.R
# Move one input at a time to a low and a high value; C's lead (THB) at 160,000 THB per QALY.
f <- function(x) formatC(round(x, -3), format = "f", digits = 0, big.mark = ",")
lead <- function(rr = 8 / 66 / (13 / 60), drug = 37800, sick = 16900, u_sick = 0.50) {
  yrs <- function(p) c((1 - p - 0.065) / (p + 0.065), p / (p + 0.065) / 0.13)  # well, sick years
  a <- yrs(13 / 60); c_ <- yrs(13 / 60 * rr)
  160000 * sum((c_ - a) * c(0.80, u_sick)) - c_[1] * drug - (c_[2] - a[2]) * sick }
cat("Usual values:", f(lead()), "\n")
cat("Risk ratio 0.25 and 1.26:", f(lead(rr = 0.25)), "and", f(lead(rr = 1.26)), "\n")
cat("Drug 30,000 and 45,000:", f(lead(drug = 30000)), "and", f(lead(drug = 45000)), "\n")
cat("Sick care 11,000 and 24,000:", f(lead(sick = 11000)), "and", f(lead(sick = 24000)), "\n")
cat("Sick utility 0.40 and 0.60:", f(lead(u_sick = 0.40)), "and", f(lead(u_sick = 0.60)), "\n")
cat("Risk ratio at a zero lead:", round(uniroot(function(r) lead(rr = r), c(0.3, 1))$root, 2), "\n")
Output of the run one_way.log
Usual values: 10,000
Risk ratio 0.25 and 1.26: 188,000 and -131,000
Drug 30,000 and 45,000: 44,000 and -21,000
Sick care 11,000 and 24,000: 5,000 and 17,000
Sick utility 0.40 and 0.60: 25,000 and -4,000
Risk ratio at a zero lead: 0.59
C's lead with one input at a time at a low and a high value, and the risk ratio at a zero lead.

Run the model 1,000 times

Changing one input at a time misses what happens when several are off at once, and it treats 1.26 as if it were as likely as 0.56. The trial says values near 0.56 are likely and values near 1.26 are possible but rare. So run the model many times, and each time draw the uncertain inputs at random, each value turning up as often as the evidence makes it plausible. A full analysis draws every uncertain input this way [4]; here we draw the two we are least sure of, the risk ratio and the yearly cost of sick care, and hold the rest at their usual values.

For the risk ratio, the draws are centred on 0.56 and spread so that 95 in 100 land inside the trial's own interval, 0.25 to 1.26. About 1 in 40 fall below it, 1 in 40 above, and none below zero, as a risk ratio cannot be negative. For the yearly cost of sick care, the draws centre on 16,900 THB, never fall below zero and stretch further up than down, because a few patients cost far more than the rest. About 19 in 20 land between 11,000 and 24,000 THB.

The first run drew a risk ratio of 0.69 and a sick-care cost of 23,016 THB, and C came out 28,793 THB behind. The second drew 0.36 and 19,712 THB, and C led by 111,378 THB. The third drew 0.59, close to the risk ratio at which C's lead vanishes, and 21,313 THB: C led by only 2,244 THB, helped by the dearer sick care it prevents. In every run the Well row still adds up to 100%, because the share who stay well is whatever is left after the drawn complication share and the 6.5% who die of other causes.

Over 1,000 runs, C comes out ahead in 542 of them, 54.2%. That share answers the committee's second question: on the evidence so far, C is the better buy in a little more than half of the plausible worlds. The runs keep the committee's year-end count.

The method you have just seen has a name and a full lesson on this site. That post teaches in full how each uncertain input gets a curve of plausible values, how many runs are enough and how the runs are summed up. This section kept only the two inputs of the committee's model that we are least sure of.

Each dot on the left is one run; look at which side of the dashed line it falls. Green dots under the line are the 54.2% of runs in which C's extra QALYs are worth more than its extra cost. Orange dots left of 0 are runs in which C gives less health. The curve on the right repeats that count at every price per QALY, so it shows how the answer depends on the committee's limit.

A share and an average, not one number

The right-hand chart repeats the count of runs with C ahead at every price per QALY the committee might accept, so it rises as the limit rises. Even at the highest prices on its right, it stops short of 100%: in some runs C adds little health, or none, while still costing far more, so no price makes it worthwhile [5].

The decision itself rests on the average lead across the runs, 14,054 THB, which is higher than the 10,000 THB from the usual inputs. The lead bends with the risk ratio: low draws help C more than high draws hurt it. So fund the option with the highest average lead, which here is C at 14,054 THB, and report the share beside it. The average weighs how much C wins or loses by, not only how often.

Never average the 1,000 costs per QALY instead. In runs where C adds almost no health that ratio shoots to huge positive or negative values, while the lead stays an ordinary sum of money.

1,000 runs are a sample too: in 19 of 20 repeats with fresh draws, the share would land within about 3.1 points of 54.2%. Fixing the starting number of the random draws, called the seed, makes the same 1,000 runs come out every time, so anyone can check them.

R code reruns.R
# Rerun the model 1,000 times; each run draws the risk ratio and the yearly cost of sick care.
f <- function(x, d = 0) formatC(x, format = "f", digits = d, big.mark = ",")
set.seed(2026); n <- 1000
rr <- exp(rnorm(n, log(8 / 66 / (13 / 60)), sqrt(1 / 8 - 1 / 66 + 1 / 13 - 1 / 60)))  # the trial
care <- rgamma(n, shape = 25, scale = 16900 / 25)       # centred on 16,900 THB; never below zero
well_years <- function(p) (1 - p - 0.065) / (p + 0.065)  # years in Well, counted at year ends
sick_years <- function(p) p / (p + 0.065) / 0.13         # share who fall sick x 1 / 0.13 years
p <- 13 / 60 * rr; a <- 13 / 60                           # C's yearly risk in each run; A's
d_well <- well_years(p) - well_years(a); d_sick <- sick_years(p) - sick_years(a)
lead <- 160000 * (0.80 * d_well + 0.50 * d_sick) - 37800 * well_years(p) - care * d_sick
cat("Runs 1-3: risk ratio", f(rr[1:3], 2), " sick care", f(care[1:3]), " lead", f(lead[1:3]), "\n")
w <- mean(lead > 0); cat("C ahead in", sum(lead > 0), "of", n, "runs:", f(100 * w, 1), "%\n")
cat("Average lead:", f(mean(lead)), " give or take:", f(196 * sqrt(w * (1 - w) / n), 1), "points\n")
Output of the run reruns.log
Runs 1-3: risk ratio 0.69 0.36 0.59  sick care 23,016 19,712 21,313  lead -28,793 111,378 2,244
C ahead in 542 of 1000 runs: 54.2 %
Average lead: 14,054  give or take: 3.1 points
1,000 runs with the risk ratio and the sick-care cost drawn at random. The output shows the first three runs, the count and share of runs with C ahead, the average lead and how much the share could wobble.

The name in the title: probabilistic sensitivity analysis

This is the method in the title: probabilistic sensitivity analysis (PSA) reruns the model many times with every uncertain input drawn from its distribution. The result is a share of runs rather than one number, and it shows how sensitive the answer is to inputs drawn by chance [4]. The series' own example is a different one from the committee's. There, an extra net benefit that averages 10,000 THB with a standard deviation of 40,000 THB gives about 599 of 1,000 runs in favour of the treatment.

A cost-effectiveness acceptability curve (CEAC) shows, at each threshold, the share of simulation runs in which a treatment is the best buy. In the series' example it reads 0.60 at 160,000 THB per QALY, because about 599 of 1,000 runs favour the treatment. The committee's curve, on the right of the figure above, reads 54.2% there [5]. The same lesson teaches the method and the curve in full: Probabilistic Sensitivity Analysis: From Parameter Distributions to CEAC.

R code psa_recap.R
# The series' own example of 1,000 reruns: the extra net benefit changes from run to run.
f <- function(x, d = 0) formatC(x, format = "f", digits = d, big.mark = ",")
gain_mean <- 10000; gain_sd <- 40000; runs <- 1000    # THB, THB, reruns of the model
share <- pnorm(gain_mean / gain_sd)                    # share of runs with an extra gain above 0
cat("Extra net benefit, average (THB):", f(gain_mean), "\n")
cat("Extra net benefit, standard deviation (THB):", f(gain_sd), "\n")
cat("Runs that favour the treatment, of", f(runs), "runs:", round(share * runs), "\n")
cat("Height of the acceptability curve at 160,000 THB per QALY:", f(share, 2), "\n")
Output of the run psa_recap.log
Extra net benefit, average (THB): 10,000
Extra net benefit, standard deviation (THB): 40,000
Runs that favour the treatment, of 1,000 runs: 599
Height of the acceptability curve at 160,000 THB per QALY: 0.60
The series' own example of 1,000 reruns: the numbers quoted in the paragraphs above.

What 1,000 runs cannot fix

Every run uses the same model and the same trial. Suppose the trial was biased, for instance because sicker patients ended up on usual care. Then every draw of the risk ratio is centred on the wrong value, and 1,000 runs give a precise picture of the wrong answer Chance, Bias and Confounding: Why an Association Is Not Yet a Treatment Effect.

If the structure is wrong, for instance if the first sick year is the dangerous one, no amount of drawing fixes it: build the other structure and compare the answers. So report the inputs with their sources, the structure, the one-at-a-time results and the share from the runs together, so that a reader can check each part.

Three ways a model misleads

  • Stopping when the trial stops

    Year 1 alone prices C at close to a million baht per QALY, because its benefit comes later.

    Fix: Run the model for as long as the consequences last, here until everyone has died.

  • Stopping at one input at a time

    The one-at-a-time bars give a risk ratio of 1.26 and one of 0.56 equal weight, and never move two inputs together.

    Fix: Draw the uncertain inputs together, each as often as it is plausible, and run the model many times.

  • Expecting the runs to fix bias

    The runs only spread the trial's own uncertainty; a biased risk ratio stays biased in every run.

    Fix: Judge bias and structure separately, and test other structures side by side.

Terms as we use them

Decision tree (ต้นไม้การตัดสินใจ)
A drawing of a choice (square), the chances that follow it (circles) and each end's payoff.
Rolling back (การคำนวณย้อนกลับ)
Multiplying each branch's payoff by its chance and adding them up, from the ends back to the choice; year 1 of usual care rolls back to 3,662 THB.
Expected value (ค่าคาดหมาย)
The average payoff over all branches, weighted by their chances.
Health state (สถานะสุขภาพ)
One of the boxes a person can be in: Well, Sick or Dead.
Cohort and cycle (กลุ่มประชากรและรอบ)
The group of 1,000 people the model follows, and one step of the model, here one year.
Expected count (จำนวนคาดหมาย)
The average number in a state over many such groups, so it can be a fraction such as 216.7.
Tunnel state (สถานะอุโมงค์)
A state that everyone leaves after one step, such as a first sick year, so the model can remember how long someone has been sick.
Verification and validation (การตรวจสอบและการประเมินความสมเหตุสมผล)
Verification checks that the model was built right, such as its first year giving back the trial's 21.67% and 12.12%; validation checks that it is the right model for these patients, against data it was not built on.
One input at a time (เปลี่ยนข้อมูลนำเข้าทีละตัว)
Rerunning the model with one input at a low or high value and the rest at their usual values.
Tornado diagram (แผนภาพทอร์นาโด)
One bar per input, widest at the top, showing how far each moves the result.
Switching value (ค่าที่ทำให้คำตอบพลิก)
The value of an input at which two options tie; a risk ratio of 0.59 here.
Run and seed (การรันและ seed)
One pass of the model with one set of drawn inputs; the seed is the starting number of the random draws, so the same seed gives the same runs.

References

  1. Sonnenberg FA, Beck JR. Markov models in medical decision making: a practical guide. Med Decis Making. 1993;13(4):322-338. doi:10.1177/0272989X9301300409
  2. Siebert U, Alagoz O, Bayoumi AM, Jahn B, Owens DK, Cohen DJ, Kuntz KM. State-transition modeling: a report of the ISPOR-SMDM Modeling Good Research Practices Task Force-3. Med Decis Making. 2012;32(5):690-700. doi:10.1177/0272989X12455463
  3. Naimark DMJ, Bott M, Krahn M. The half-cycle correction explained: two alternative pedagogical approaches. Med Decis Making. 2008;28(5):706-712. doi:10.1177/0272989X08315241
  4. Briggs AH, Weinstein MC, Fenwick EAL, Karnon J, Sculpher MJ, Paltiel AD. Model parameter estimation and uncertainty: a report of the ISPOR-SMDM Modeling Good Research Practices Task Force-6. Value Health. 2012;15(6):835-842. doi:10.1016/j.jval.2012.04.014
  5. Fenwick E, Claxton K, Sculpher M. Representing uncertainty: the role of cost-effectiveness acceptability curves. Health Econ. 2001;10(8):779-787. doi:10.1002/hec.635
  6. Briggs A, Claxton K, Sculpher M. Decision Modelling for Health Economic Evaluation. Oxford: Oxford University Press; 2006. ISBN 9780198526629

The series, in learning order

  1. Health Economics Series Guide: Read the Evidence Before the Costs
  2. Incidence, Prevalence and the Right Denominator: The Epidemiology Beneath Cost-of-Illness and Markov Models
  3. Design, Model and Estimand: What Each Study Design Can Actually Estimate
  4. Risk Ratio, Odds Ratio, Risk Difference and NNT: One 2×2 Table, Four Answers
  5. The Hazard Ratio Is Not a Risk Ratio: Proportional Hazards, Restricted Mean Survival Time and Model Inputs
  6. Chance, Bias and Confounding: Why an Association Is Not Yet a Treatment Effect
  7. Beyond the Evidence Pyramid: GRADE Certainty and Choosing Effectiveness Inputs for Economic Models
  8. From Sensitivity to Cost per Case Detected: Predictive Values, Prevalence and the Economics of Screening
  9. Whose Cost, in Which Year? Perspective, Opportunity Cost, Inflation and Discounting
  10. QALY, DALY and the Utilities Behind Them: What Is Actually Being Measured
  11. ICER, Dominance and the Cost-Effectiveness Plane: Reading the Thai Threshold of 160,000 Baht per QALY
  12. Decision Trees, Markov Cohorts and Probabilistic Sensitivity Analysis: How a Model Turns Evidence into a Decision (this article)
  13. Cost-Effective Is Not Affordable, and Efficient Is Not Fair: Budget Impact and Equity
  14. Parameter Sourcing, Reference Cost Lists and Utility Catalogues When Local Data Are Sparse
  15. Early Health Technology Assessment, Headroom Pricing and Benefit-Package Decisions
  16. Communicating Health Economic Evidence: From Model Outputs to Policy Briefs

Key takeaways

  • A decision tree multiplies each branch's chance by its payoff and adds them up: usual care's first year is 3,662 THB and 0.683 QALYs per patient.
  • One year is too short when the benefit comes later: year 1 alone prices C at close to a million baht per QALY.
  • Following 1,000 people between Well, Sick and Dead once a year until all have died gives 5.00 against 6.00 QALYs, and 100,000 against 250,000 THB, counted at year ends.
  • Change one input at a time to find the one that flips the answer: here the risk ratio, which must stay below 0.59 for C to lead.
  • Run the model 1,000 times with its two least certain inputs drawn at random: C leads in 542 runs, 54.2%, with an average lead of 14,054 THB, so report a share and an average, not one number.

Related in the wiki: Incidence, Prevalence and the Right Denominator: The Epidemiology Beneath Cost-of-Illness and Markov Models · The Hazard Ratio Is Not a Risk Ratio: Proportional Hazards, Restricted Mean Survival Time and Model Inputs · Beyond the Evidence Pyramid: GRADE Certainty and Choosing Effectiveness Inputs for Economic Models · ICER, Dominance and the Cost-Effectiveness Plane: Reading the Thai Threshold of 160,000 Baht per QALY · Chance, Bias and Confounding: Why an Association Is Not Yet a Treatment Effect · Whose Cost, in Which Year? Perspective, Opportunity Cost, Inflation and Discounting · QALY, DALY and the Utilities Behind Them: What Is Actually Being Measured

0
Message for International and Thai ReadersUnderstanding My Medical Context in ThailandRead more →Message for International and Thai ReadersUnderstanding My Broader Content Beyond MedicineRead more →

Comments

No comments yet. Be the first to share your thoughts.

Sign in to comment