Net Present Value

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)

# Numeric notation in this document
# With thanks to https://stackoverflow.com/questions/18965637/set-global-thousand-separator-on-knitr/18967590#18967590
knitr::knit_hooks$set(
    inline = function(x) {
        if(!is.numeric(x)){
            x
        } else if(x<100) {
            prettyNum(round(x, 3), big.mark=",")
        } else {
            prettyNum(round(x, 0), big.mark=",")
        }
    }
)

Introduction

This vignette describes how to use the dynamicpv package to derive (Net) Present Values (NPVs) that account for dynamic (or static) pricing, and dynamic (or static) uptake. We consider:

First let us load the packages we will use for this vignette.

library(dynamicpv)

NPV with static pricing and static uptake

The NPV of a cashflow $c_t$ in $t$ years time, assuming prices constant in real terms and an annual discount rate of $i_{real}$ is calculated as follows:

$$NPV = \frac{c_t}{(1+i_{real})^{t}}$$

It is overkill rather to use dynamicpv to calculate the NPV of a cashflow that is constant in real terms, since there are simpler routes to doing this. Nevertheless, let us start there, since it helps understand functionality. We use a simple cashflow and $i=3\%$.

# A simple cashflow
cashflow <- c(110, 120, 130, 140, 150)

# (Real) discount rate of 3\% per timestep (year)
disc <- 0.03
vt1 <- (1 + disc)^(-1 * (0:4))

The calculation can be presented in a table as follows.

| Time | Cashflow | Discount factor | Product | |------|----------|--------------------|---------| | $t$ | $c_t$ | $v^t = (1+i)^{1-t}$ | | | 1 | r cashflow[1] | r vt1[1] | r cashflow[1] * vt1[1] | | 2 | r cashflow[2] | r vt1[2] | r cashflow[2] * vt1[2] | | 3 | r cashflow[3] | r vt1[3] | r cashflow[3] * vt1[3] | | 4 | r cashflow[4] | r vt1[4] | r cashflow[4] * vt1[4] | | 5 | r cashflow[5] | r vt1[5] | r cashflow[5] * vt1[5] | | Total | r sum(cashflow) | | r sum(vt1 * cashflow) |

: Calculation of NPV from a simple cashflow and given discount rate

# Run dynpv calculation with full output
pv1 <- dynpv(payoffs=cashflow, discrate=disc)

summary(pv1)

The present value of this cashflow is r total(pv1). The same result could have been found more easily than invoking this package.

sum(vt1 * cashflow)

NPV with dynamic pricing and static uptake

Cashflows may not in general be expected to be constant in real terms. Accordingly it can be preferable to explicitly estimate the cashflow in nominal terms, $c'_t = c_t \cdot R_t$, and discount with a nominal discount rate that includes expected inflation.

$$ 1+i_{nom} = (1+i_{real})(1+r_g) $$ $$NPV = \frac{c't}{(1+i{nom})^t} = \frac{c_t \cdot R_t}{(1+i_{nom})^t} $$

Prices that increase regularly

In this example, let us assume that prices of the resource underlying the cashflow increases at 1 \% per year, but that general price inflation is 2.5 \% per year. The real discount rate is unchanged at 3 \% per year, so the nominal discount rate is therefore approximately 5.5 \% per year.

First, we derive a price index to handle the changing price of the resource underlying the cashflow. Then we derive the nominal discount rate, incorporating the general rate of price inflation in the economy. We then combine these inputs with dynpv() to calculate the NPV.

# Set up price index
pinfl <- 0.01
pindex <- (1+pinfl)^(0:4)
pindex

# Nominal discount rate
nomdisc <- (1+disc)*1.025-1

# Calculate present value
pv2 <- dynpv(payoffs=cashflow, prices=pindex, discrate=nomdisc)

summary(pv2)

The NPV of r total(pv2) could still have been calculated using base R functions.

# Compare with more base calculations
vt2 <- (1+nomdisc)^(-1 * (0:4))
sum(vt2 * cashflow * pindex)

Prices that increase irregularly

Where dynamicpv is most relevant is when the price index is more irregular, more dynamic. For example, one might anticipate a sudden future change in a price of a certain resource (e.g. drug price reduction on loss of exclusivity). This can be accommodated in the price index. In this simple example, let us assume that the underlying price of the resource reduces to half its original value from the fourth year.

# Revise the price index to be 0.5 from year 4
pindex[4:5] <- 0.5
pindex

# Calculate present value
pv3 <- dynpv(payoffs=cashflow, prices=pindex, discrate=nomdisc)

summary(pv3)

With this dynamic pricing change, the NPV has reduced by r total(pv2) - total(pv3), from r total(pv2) to r total(pv3). The corresponding base R code is shown below.

# Compare with more base calculations
sum(vt2 * cashflow * pindex)

NPV with static pricing and dynamic uptake

Uptake is constant over time

Suppose we have one new patient each year, and we wish to calculate the total NPV in a time horizon of 5 years. Now we have a payoff triangle as follows.

| Time | Cashflow 1 | Cashflow 2 | Cashflow 3 | Cashflow 4 | Cashflow 5 | Cashflow Sum | Discount factor | Product | |------|----------|--------------------|---------|----|-----|----|----|----| | $t$ | $c_t$ | $c_{t-1}$ | $c_{t-2}$ | $c_{t-3}$ | $c_{t-4}$ | | $v^t = (1+i)^{1-t}$ | | | 1 | r cashflow[1] | - | - | - | - | r cashflow[1] |r vt1[1] | r cashflow[1] * vt1[1] | | 2 | r cashflow[2] | r cashflow[1] | - | - | - | r sum(cashflow[1:2]) |r vt1[2] | r sum(cashflow[1:2]) * vt1[2] | | 3 | r cashflow[3] | r cashflow[2] |r cashflow[1] | - | - | r sum(cashflow[1:3]) | r vt1[3] | r sum(cashflow[1:3]) * vt1[3] | | 4 | r cashflow[4] | r cashflow[3] | r cashflow[2] |r cashflow[1] | - | r sum(cashflow[1:4]) | r vt1[4] | r sum(cashflow[1:4]) * vt1[4] | | 5 | r cashflow[5] | r cashflow[4] | r cashflow[3] | r cashflow[2] |r cashflow[1] | r sum(cashflow[1:5]) | r vt1[5] | r sum(cashflow[1:5]) * vt1[5] | | Total | r sum(cashflow) | | | | | | | r sum(vt1 * cumsum(cashflow)) |

: Calculation of NPV from a simple cashflow and given discount rate

With dynamicpv::dynpv(), we just update the uptakes argument.

# Uptake vector is (1, 1, 1, 1, 1)
uptakes1 <- rep(1, 5)

# NPV calculation
pv4 <- dynpv(payoffs=cashflow, uptakes=uptakes1, discrate=disc)

summary(pv4)

There are r sum(uptakes1) patients with a total NPV of r total(pv4), which equates to r mean(pv4) on average per patient. This can also be calculated in base R.

sum(vt1 * cumsum(cashflow))

Uptake varies over time

In general, uptake will not be constant in time. Due to factors such as varying epidemiology (prevalence and incidence), prior treatment pathways (patient testing, patient diagnostics, prior treatment usage), as well as the specific uptake of the treatment of interest (patient share), uptake will also be highly variable over time. In this setting, the total and mean NPVs must reflect weightings applicable to each cashflow.

For example, suppose the number of patients receiving treatment increases by one each year. The weighting given to cashflow 1 would then be $1/(1+2+3+4+5)=6.67$ \%.

# Uptake vector is (1, 2, 3, 4, 5)
uptakes2 <- 1:5

# NPV calculation
pv5 <- dynpv(payoffs=cashflow, uptakes=uptakes2, discrate=disc)

summary(pv5)

There are r sum(uptakes2) patients with a total NPV of r total(pv5), which equates to r mean(pv5) on average per patient. Verifying this result in base R also becomes more complicated.

# Verifying total NPV is now more complicated
checkpv <- rep(0, 5)
for (i in 1:5) {
  checkpv[i] <- sum(cashflow[1:i] * uptakes2[i:1] * vt1[i])
}
sum(checkpv)

NPV with dynamic pricing and dynamic uptake

The package becomes most powerful when considering both dynamic uptake and dynamic pricing. A simple call to dynamicpv::dynpv() replaces what would be rather more complicated with base functions, even in this toy example.

# NPV calculation
pv6 <- dynpv(payoffs=cashflow, uptakes=uptakes2, prices=pindex, discrate=nomdisc)

# Calculation results
pv6

# Total present value = sum of the pv column
sum(pv6$pv)

summary(pv6)

There are r sum(uptakes2) patients with a total NPV of r total(pv6), which equates to r mean(pv6) on average per patient.



Try the dynamicpv package in your browser

Any scripts or data that you put into this service are public.

dynamicpv documentation built on Jan. 16, 2026, 1:07 a.m.