What is dynamic uptake?

Dynamic uptake refers to the modeling of multiple series of payoffs/cashflows over time, rather than the modeling of just one series of payoffs/cashflows at a time. This may arise, for example, in considering the treatment costs of multiple cohorts of patients beginning their treatment courses at different times. There are also analogies to ‘run-off triangles’ used by general insurance actuaries to model the emergence of insurance claims over time following claim events.

This package is designed to be able to calculate presente values with dynamic uptake/multiple cohorts. This can be coupled with considerations of dynamic pricing, as covered in vignette("Dynamic Pricing").

Using dynamicpv

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

Let us work with the same cashflow as in vignette("Dynamic Pricing"), but assume all costs/prices are constant in real terms. The discount rate is 3 % per year, real.

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

# (Real) discount rate of 3\% per timestep (year)
disc <- 0.03

# Discounting factor - used for base R calculations later
vt1 <- (1 + disc)^(-1 * (0:4))

Non-dynamic uptake

First let us consider non-dynamic uptake, as covered in vignette("Dynamic Pricing").

Calculation of NPV from a simple cashflow and given discount rate
Time Cashflow 1 Discount factor Product
tt ctc_t vt=(1+i)tv^t = (1+i)^{-t}
1 110 1 110
2 120 0.971 117
3 130 0.943 123
4 140 0.915 128
5 150 0.888 133
Total 650 610

In this case, there is a single cashflow series starting at timestep 1. This time, we will use the uptakes argument for dynamicpv::dynpv() - although this is unnecessary since its default is also 1.

# Uptake vector is simple
uptakes1 <- 1

# NPV calculation
pv1 <- dynpv(payoffs=cashflow, uptakes=uptakes1, discrate=disc)
pv1$results$total
#> [1] 610.4352

The result is unchanged from the vignette("Dynamic Pricing"), where we also showed the function could be verified with some simple vector arithmetic.

Dynamic uptake, constant in time (unweighted)

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.

Calculation of NPV from a simple cashflow and given discount rate
Time Cashflow 1 Cashflow 2 Cashflow 3 Cashflow 4 Cashflow 5 Cashflow Sum Discount factor Product
tt ctc_t ct1c_{t-1} ct2c_{t-2} ct3c_{t-3} ct4c_{t-4} vt=(1+i)tv^t = (1+i)^{-t}
1 110 - - - - 110 1 110
2 120 110 - - - 230 0.971 223
3 130 120 110 - - 360 0.943 339
4 140 130 120 110 - 500 0.915 458
5 150 140 130 120 110 650 0.888 578
Total 650 1,708

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

# Uptake vector is simple
uptakes2 <- rep(1, 5)
uptakes2
#> [1] 1 1 1 1 1

# NPV calculation
pv2 <- dynpv(payoffs=cashflow, uptakes=uptakes2, discrate=disc)
pv2$results
#> $ncoh
#> [1] 5
#> 
#> $uptake
#> [1] 5
#> 
#> $calc
#> # A tibble: 15 × 9
#>        j     k     l     t    uj    pk     R     v    pv
#>    <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#>  1     1     1     0     1     1   110     1 1     110  
#>  2     1     2     0     2     1   120     1 0.971 117. 
#>  3     1     3     0     3     1   130     1 0.943 123. 
#>  4     1     4     0     4     1   140     1 0.915 128. 
#>  5     1     5     0     5     1   150     1 0.888 133. 
#>  6     2     1     0     2     1   110     1 0.971 107. 
#>  7     2     2     0     3     1   120     1 0.943 113. 
#>  8     2     3     0     4     1   130     1 0.915 119. 
#>  9     2     4     0     5     1   140     1 0.888 124. 
#> 10     3     1     0     3     1   110     1 0.943 104. 
#> 11     3     2     0     4     1   120     1 0.915 110. 
#> 12     3     3     0     5     1   130     1 0.888 116. 
#> 13     4     1     0     4     1   110     1 0.915 101. 
#> 14     4     2     0     5     1   120     1 0.888 107. 
#> 15     5     1     0     5     1   110     1 0.888  97.7
#> 
#> $cohpv
#> # A tibble: 5 × 3
#>       j tzero   spv
#>   <int> <dbl> <dbl>
#> 1     1     0 610. 
#> 2     2     0 463. 
#> 3     3     0 329. 
#> 4     4     0 207. 
#> 5     5     0  97.7
#> 
#> $total
#> [1] 1707.723
#> 
#> $mean
#> [1] 341.5446

There are 5 patients with a total NPV of 1,708, which equates to 342 on average per patient. This can also be calculated in base R.

sum(vt1 * cumsum(cashflow))
#> [1] 1707.723

Dynamic uptake, not constant in 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.671/(1+2+3+4+5)=6.67 %.

# Uptake vector is simple
uptakes3 <- 1:5

# NPV calculation
pv3 <- dynpv(payoffs=cashflow, uptakes=uptakes3, discrate=disc)
pv3$results
#> $ncoh
#> [1] 5
#> 
#> $uptake
#> [1] 15
#> 
#> $calc
#> # A tibble: 15 × 9
#>        j     k     l     t    uj    pk     R     v    pv
#>    <int> <int> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl>
#>  1     1     1     0     1     1   110     1 1      110 
#>  2     1     2     0     2     1   120     1 0.971  117.
#>  3     1     3     0     3     1   130     1 0.943  123.
#>  4     1     4     0     4     1   140     1 0.915  128.
#>  5     1     5     0     5     1   150     1 0.888  133.
#>  6     2     1     0     2     2   110     1 0.971  214.
#>  7     2     2     0     3     2   120     1 0.943  226.
#>  8     2     3     0     4     2   130     1 0.915  238.
#>  9     2     4     0     5     2   140     1 0.888  249.
#> 10     3     1     0     3     3   110     1 0.943  311.
#> 11     3     2     0     4     3   120     1 0.915  329.
#> 12     3     3     0     5     3   130     1 0.888  347.
#> 13     4     1     0     4     4   110     1 0.915  403.
#> 14     4     2     0     5     4   120     1 0.888  426.
#> 15     5     1     0     5     5   110     1 0.888  489.
#> 
#> $cohpv
#> # A tibble: 5 × 3
#>       j tzero   spv
#>   <int> <dbl> <dbl>
#> 1     1     0  610.
#> 2     2     0  927.
#> 3     3     0  987.
#> 4     4     0  829.
#> 5     5     0  489.
#> 
#> $total
#> [1] 3841.785
#> 
#> $mean
#> [1] 256.119

There are 15 patients with a total NPV of 3,842, which equates to 256 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] * uptakes3[i:1] * vt1[i])
}
sum(checkpv)
#> [1] 3841.785

Dynamic pricing and 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.

# Price index
pinfl <- 0.01
pindex <- (1+pinfl)^(0:4)
pindex[4:5] <- 0.5

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

# NPV calculation
pv4 <- dynpv(payoffs=cashflow, uptakes=uptakes3, prices=pindex, discrate=nomdisc)
pv4$results
#> $ncoh
#> [1] 5
#> 
#> $uptake
#> [1] 15
#> 
#> $calc
#> # A tibble: 15 × 9
#>        j     k     l     t    uj    pk     R     v    pv
#>    <int> <int> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl>
#>  1     1     1     0     1     1   110  1    1     110  
#>  2     1     2     0     2     1   120  1.01 0.961 117. 
#>  3     1     3     0     3     1   130  1.02 0.924 123. 
#>  4     1     4     0     4     1   140  0.5  0.888  62.2
#>  5     1     5     0     5     1   150  0.5  0.854  64.0
#>  6     2     1     0     2     2   110  1.01 0.961 214. 
#>  7     2     2     0     3     2   120  1.02 0.924 226. 
#>  8     2     3     0     4     2   130  0.5  0.888 115. 
#>  9     2     4     0     5     2   140  0.5  0.854 120. 
#> 10     3     1     0     3     3   110  1.02 0.924 311. 
#> 11     3     2     0     4     3   120  0.5  0.888 160. 
#> 12     3     3     0     5     3   130  0.5  0.854 166. 
#> 13     4     1     0     4     4   110  0.5  0.888 195. 
#> 14     4     2     0     5     4   120  0.5  0.854 205. 
#> 15     5     1     0     5     5   110  0.5  0.854 235. 
#> 
#> $cohpv
#> # A tibble: 5 × 3
#>       j tzero   spv
#>   <int> <dbl> <dbl>
#> 1     1     0  475.
#> 2     2     0  675.
#> 3     3     0  637.
#> 4     4     0  400.
#> 5     5     0  235.
#> 
#> $total
#> [1] 2422.633
#> 
#> $mean
#> [1] 161.5089

There are 15 patients with a total NPV of 2,423, which equates to 162 on average per patient.

Summary

  • Dynamic uptake occurs if the same cashflow series may begin at several different times, as can occur when modeling costs of treatment to populations over time (where uptake of that treatment varies in time) or the run-off of insurance claims. This can complicate calculations of NPV.
  • The dynamicpv::dynpv() function can be used to calculate NPVs for arbitrary vectors of cashflows, at a given discount rate and a given expected uptake over time.
  • In this way, the function can accommodate both dynamic uptake and dynamic pricing, as seen in the vignette("Dynamic Pricing").