Skip to content

Commit 91a9bd7

Browse files
authored
Merge pull request #21 from dom-muston/main
Documentation changes and improve uptake method
2 parents e2ae62b + e2b9eee commit 91a9bd7

File tree

17 files changed

+290
-112
lines changed

17 files changed

+290
-112
lines changed

.Rbuildignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
^pkg\.lock$
1313
^pkgdown$
1414
^sticker\.png$
15+
^_pkgdown\.yml$

R/dynamic.R

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,64 @@
1616
# You should have received a copy of the GNU General Public License
1717
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1818

19-
#' Calculate present value for a payoff with dynamic pricing and dynamic uptake
19+
#' Present values with dynamic pricing and dynamic uptake
20+
#'
21+
#' Calculate present value for a payoff with dynamic (lifecycle) pricing and dynamic uptake (stacked cohorts).
2022
#'
21-
#' Present value of a payoff affected by dynamic pricing, with uptake across multiple cohorts (dynamic uptake)
23+
#' Suppose payoffs in relation to patients receiving treatment (such as costs or health outcomes) occur over timesteps \eqn{t=1, ..., T}. Let us partition time as follows.
24+
#'
25+
#' - Suppose \eqn{j=1,...,T} indexes the time at which the patient begins treatment.
26+
#' - Suppose \eqn{k=1,...,T} indexes time since initiating treatment.
27+
#'
28+
#' In general, \eqn{t=j+k-1}, and we are interested in the set of \eqn{(j,k)} such that \eqn{1 \leq t \leq T}.
29+
#'
30+
#' For example, \eqn{t=3} comprises:
31+
#'
32+
#' - patients who are in the third timestep of treatment that began in timestep 1: (j,k)=(1,3);
33+
#' - patients who are in the second timestep of treatment that began in timestep 2, (j,k)=(2,2); and
34+
#' - patients who are in the first timestep of treatment that began in timestep 3, (j,k)=(3,1)
35+
#'
36+
#' The [Present Value](https://en.wikipedia.org/wiki/Present_value) of a cashflow \eqn{p_k} for the \eqn{u_j} patients who began treatment at time \eqn{j} and who are in their \eqn{k}th timestep of treatment is as follows
37+
#' \deqn{PV(j,k,l) = u_j \cdot p_k \cdot R_{j+k+l-1} \cdot (1+i)^{2-j-k}}
38+
#' where \eqn{i} is the risk-free discount rate per timestep, \eqn{p_k} is the cashflow amount in today’s money, and \eqn{p_k \cdot R_{j+k+l-1}} is the nominal amount of the cashflow at the time it is incurred, allowing for an offset of \eqn{l = tzero}.
39+
#'
40+
#' The total present value, \eqn{TPV(l)}, is therefore the sum over all \eqn{j} and \eqn{k} within the time horizon \eqn{T}, namely:
41+
#' \deqn{TPV(l) = \sum_{j=1}^{T} \sum_{k=1}^{T-j+1} PV(j,k, l) \\
42+
#' \;
43+
#' = \sum_{j=1}^{T} \sum_{k=1}^{T-j+1} u_j \cdot p_k \cdot R_{l+j+k-1} \cdot (1+i)^{2-j-k}}
44+
#'
45+
#' This function calculates \eqn{PV(j,k,l)} for all values of \eqn{j}, \eqn{k} and \eqn{l}, and returns this in a tibble.
2246
#' @param uptakes Vector of patient uptake over time
2347
#' @param horizon Time horizon for the calculation (length must be less than or equal to the length of payoffs)
2448
#' @param tzero Time at the date of calculation, to be used in lookup in prices vector
2549
#' @param payoffs Vector of payoffs of interest (numeric vector)
2650
#' @param prices Vector of price indices through the time horizon of interest
2751
#' @param discrate Discount rate per timestep, corresponding to price index
2852
#' @returns A tibble of class "dynpv" with the following columns:
29-
#' - `j`: todo
30-
#' - `k`: todo
31-
#' - `l`: todo
32-
#' - `t`: todo
33-
#' - `uj`: todo
34-
#' - `pk`: todo
35-
#' - `R`: todo
36-
#' - `v`: todo
37-
#' - `pv`: todo
53+
#' - `j`: Time at which patients began treatment
54+
#' - `k`: Time since patients began treatment
55+
#' - `l`: Time offset for the price index (from `tzero`)
56+
#' - `t`: Equals \eqn{j+k-1}
57+
#' - `uj`: Uptake of patients beginning treatment at time \eqn{j} (from `uptakes`)
58+
#' - `pk`: Cashflow amount in today's money in respect of patients at time \eqn{k} since starting treatment (from `payoffs`)
59+
#' - `R`: Index of prices over time \eqn{l+t} (from `prices`)
60+
#' - `v`: Discounting factors, \eqn{(1+i)^{1-t}}, where `i` is the discount rate per timestep
61+
#' - `pv`: Present value, \eqn{PV(j,k,l)}
3862
#' @export
3963
#' @examples
40-
#' library(dplyr)
64+
#' # Simple example
65+
#' pv1 <- dynpv(
66+
#' uptakes = (1:10), # 1 patient uptakes in timestep 1, 2 patients in timestep 2, etc
67+
#' tzero = c(0,5), # Calculations are performed with prices at times 0 and 5
68+
#' payoffs = 90 + 10*(1:10), # Payoff vector of length 10 = (100, 110, ..., 190)
69+
#' prices = 1.02^((1:15)-1), # Prices increase at 2\% per timestep in future
70+
#' discrate = 0.05 # The nominal discount rate is 5\% per timestep;
71+
#' # the real discount rate per timestep is 3\% (=5\% - 3\%)
72+
#' )
73+
#' pv1
74+
#' summary(pv1)
75+
#'
76+
#' # More complex example, using cashflow output from a heemod model
4177
#'
4278
#' # Obtain dataset
4379
#' democe <- get_dynfields(
@@ -48,19 +84,17 @@
4884
#'
4985
#' # Obtain short payoff vector of interest
5086
#' payoffs <- democe |>
51-
#' filter(int=="new", model_time<11) |>
52-
#' mutate(cost_oth = cost_total - cost_daq_new)
53-
#' Nt <- nrow(payoffs)
87+
#' dplyr::filter(int=="new", model_time<11) |>
88+
#' dplyr::mutate(cost_oth = cost_total - cost_daq_new)
5489
#'
5590
#' # Example calculation
56-
#' pv <- dynpv(
57-
#' uptakes = rep(1, Nt),
91+
#' pv2 <- dynpv(
92+
#' uptakes = rep(1, nrow(payoffs)),
5893
#' payoffs = payoffs$cost_oth,
59-
#' prices = 1 + (0:(Nt-1))*0.05,
94+
#' prices = 1.05^(0:(nrow(payoffs)-1)),
6095
#' discrate = 0.08
6196
#' )
62-
#' pv
63-
#' summary(pv)
97+
#' summary(pv2)
6498
dynpv <- function(
6599
uptakes = 1,
66100
payoffs,

R/oop.R

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#' from `e2`. Total uptake is the uptake from `e1` plus `mult` times the uptake
5353
#' from `e2`. Take care of this when using `$mean` of the summed object.
5454
#'
55+
#' @seealso [dynpv()], [futurepv()]
5556
#' @returns S3 object of class "dynpv"
5657
#'
5758
#' @export
@@ -62,9 +63,9 @@ addprod <- function(e1, e2, mult) {
6263
xdata <- e1 |> mutate(dpvno="x")
6364
ydata <- e2 |> mutate(dpvno="y")
6465
# Check that j, k and l vectors align
65-
if (length(xdata$j) != length(ydata$j)) {warning("Uptake vectors differ in length after trimming")}
66-
stopifnot(max(xdata$k) == max(ydata$k))
67-
stopifnot(max(xdata$l) == max(ydata$l))
66+
if (length(xdata$j) != length(ydata$j)) {warning("Uptake vectors differ in length")}
67+
stopifnot("Length of payoffs vectors differ" = max(xdata$k) == max(ydata$k))
68+
stopifnot("Length of time-offset vectors (tzero) differ" = max(xdata$l) == max(ydata$l))
6869
# Combine data
6970
jdata <- bind_rows(xdata, ydata) |>
7071
# Spread
@@ -85,44 +86,57 @@ addprod <- function(e1, e2, mult) {
8586
return(jdata)
8687
}
8788

88-
#' Number of cohorts of uptaking patients (ncoh)
89-
#'
89+
#' Number of cohorts of uptaking patients
90+
#'
91+
#' Number of cohorts of uptaking patients, calculated as the length of the `uptakes` input to [dynpv()]
92+
#'
9093
#' @param df Tibble of class "dynpv" created by [dynpv()] or [futurepv()]
91-
#'
94+
#' @seealso [dynpv()], [futurepv()]
9295
#' @return A number
9396
#'
9497
#' @export
9598
ncoh <- function(df) max(df$k)
9699

97-
#' Number of times (unique values of tzero) at which calculations are performed
100+
#' Number of times at which present value calculations are performed
98101
#'
102+
#' Number of times at which present value calculations are performed, calculated as the length of the `tzero` input to [dynpv()]
103+
#'
99104
#' @inherit ncoh params return
100-
#'
105+
#' @seealso [dynpv()], [futurepv()]
101106
#' @export
102107
ntimes <- function(df) length(unique(df$l))
103108

104-
#' Total number of uptaking patients (uptake)
109+
#' Total number of uptaking patients
105110
#'
111+
#' Total number of uptaking patients, calculated as the sum of the `uptake` input to [dynpv()], or \eqn{\sum_{j=1}^T u_j}
112+
#'
106113
#' @inherit ncoh params
107-
#'
114+
#' @seealso [dynpv()], [futurepv()]
108115
#' @return A number or tibble
109116
#'
110117
#' @export
111118
uptake <- function(df) {
112119
# Avoid no visible binding note
113-
uj <- sd <- j <- l <- tzero <- NULL
120+
uj <- sd <- j <- NULL
114121
tempout1 <- df |>
115-
summarize(mean=mean(uj), sd=sd(uj), .by=c(j, l)) |>
116-
rename(tzero = l) |>
117-
summarize(uptake=sum(mean), .by=tzero)
118-
result <- if (nrow(tempout1)==1) tempout1$uptake else tempout1
119-
return(result)
122+
summarize(mean=mean(uj), sd=sd(uj), .by=c(j)) |>
123+
summarize(uptake=sum(mean))
124+
return(tempout1$uptake)
120125
}
121126

122-
#' Tibble of summarized calculation results for each uptake cohort (sum_by_coh)
127+
#' Present value for each uptake cohort and calculation time
123128
#'
129+
#' Calculates the sum of the Present Value by uptake cohort (`j`) and time at which the calculation is performed (`tzero` input to [dynpv()])
130+
#'
131+
#' The [Present Value](https://en.wikipedia.org/wiki/Present_value) of a cashflow \eqn{p_k} for the \eqn{u_j} patients who began treatment at time \eqn{j} and who are in their \eqn{k}th timestep of treatment is as follows
132+
#' \deqn{PV(j,k,l) = u_j \cdot p_k \cdot R_{j+k+l-1} \cdot (1+i)^{2-j-k}}
133+
#' where \eqn{i} is the risk-free discount rate per timestep, \eqn{p_k} is the cashflow amount in today’s money, and \eqn{p_k \cdot R_{j+k+l-1}} is the nominal amount of the cashflow at the time it is incurred, allowing for an offset of \eqn{l = tzero}.
134+
#'
135+
#' This method returns \eqn{\sum_{k=1}^{T-j+1} PV(j,k,l)} for each value of \eqn{j} and \eqn{l}, where \eqn{T} is the time horizon of the calculation.
136+
#'
124137
#' @inherit uptake params return
125-
#'
138+
#' @seealso [dynpv()], [futurepv()]
139+
#' @return A number or tibble
126140
#' @export
127141
sum_by_coh <- function(df) {
128142
# Avoid no visible binding note
@@ -135,10 +149,22 @@ sum_by_coh <- function(df) {
135149
return(result)
136150
}
137151

138-
#' Total present value (total)
152+
#' Total present value
153+
#'
154+
#' Sum of the Present Value, by time at which the calculation is performed (`tzero` input to [dynpv()])
155+
#'
156+
#' The [Present Value](https://en.wikipedia.org/wiki/Present_value) of a cashflow \eqn{p_k} for the \eqn{u_j} patients who began treatment at time \eqn{j} and who are in their \eqn{k}th timestep of treatment is as follows
157+
#' \deqn{PV(j,k,l) = u_j \cdot p_k \cdot R_{j+k+l-1} \cdot (1+i)^{2-j-k}}
158+
#' where \eqn{i} is the risk-free discount rate per timestep, \eqn{p_k} is the cashflow amount in today’s money, and \eqn{p_k \cdot R_{j+k+l-1}} is the nominal amount of the cashflow at the time it is incurred, allowing for an offset of \eqn{l = tzero}.
159+
#'
160+
#' The total present value by time at which the calculation is performed, \eqn{TPV(l)}, is therefore the sum of \eqn{PV(j,k,l)} over all \eqn{j} and \eqn{k} within the time horizon \eqn{T}, namely:
161+
#' \deqn{TPV(l) = \sum_{j=1}^{T} \sum_{k=1}^{T-j+1} PV(j,k, l) \\
162+
#' \;
163+
#' = \sum_{j=1}^{T} \sum_{k=1}^{T-j+1} u_j \cdot p_k \cdot R_{l+j+k-1} \cdot (1+i)^{2-j-k}}
139164
#'
140165
#' @inherit uptake params return
141-
#'
166+
#' @seealso [dynpv()], [futurepv()]
167+
#' @return A number or tibble
142168
#' @export
143169
total <- function(df) {
144170
# Avoid no visible binding note
@@ -150,13 +176,17 @@ total <- function(df) {
150176
return(result)
151177
}
152178

153-
#' Average present value per uptaking patient (mean=total/uptake)
179+
#' Mean present value per uptaking patient
180+
#'
181+
#' Mean of the Present Value per uptaking patient, by time at which the calculation is performed (`tzero` input to [dynpv()]).
182+
#'
183+
#' This is equal to [total()] divided by [uptake()].
154184
#'
155185
#' @param x Tibble of class "dynpv" created by [dynpv()] or [futurepv()]
156186
#' @param ... Currently unused
157187
#'
158188
#' @inherit uptake return
159-
#'
189+
#' @seealso [dynpv()], [futurepv()]
160190
#' @export
161191
mean.dynpv <- function(x, ...) {
162192
# Avoid no visible binding note
@@ -166,9 +196,12 @@ mean.dynpv <- function(x, ...) {
166196
if (length(total)==1) {
167197
total / uptake
168198
} else {
169-
left_join(total, uptake, join_by(tzero)) |>
170-
mutate(mean = total / uptake) |>
171-
select(-total, -uptake)
199+
total |>
200+
mutate(
201+
uptake = uptake,
202+
mean = total / uptake
203+
) |>
204+
select(-total, -uptake)
172205
}
173206
}
174207

@@ -178,13 +211,13 @@ mean.dynpv <- function(x, ...) {
178211
#' @param ... Currently unused
179212
#'
180213
#' @return A list of class "dynpv_summary" with the following elements:
181-
#' - `ncoh`: Number of cohorts of uptaking patients
182-
#' - `ntimes`: Number of times (unique values of tzero) at which calculations are performed
183-
#' - `uptake`: Total number of uptaking patients
184-
#' - `sum_by_coh`: Tibble of summarized calculation results for each uptake cohort
185-
#' - `total`: Total present value
186-
#' - `mean`: Average present value per uptaking patient (=total/uptake)
187-
#'
214+
#' - `ncoh`: Number of cohorts of uptaking patients, from [ncoh()]
215+
#' - `ntimes`: Number of times at which present value calculations are performed, from [ntimes()]
216+
#' - `uptake`: Total number of uptaking patients, from [uptake()]
217+
#' - `sum_by_coh`: Present value for each uptake cohort and calculation time, from [sum_by_coh()]
218+
#' - `total`: Total present value, from [total()]
219+
#' - `mean`: Mean present value per uptaking patient, from [mean()], equal to `total`/`uptake`.
220+
#' @seealso [dynpv()], [futurepv()]
188221
#' @export
189222
summary.dynpv <- function(object, ...) {
190223
structure(
@@ -205,19 +238,19 @@ print.dynpv_summary <- function(x, ...) {
205238
cat("Summary of Dynamic Pricing and Uptake\n")
206239
cat(" Number of cohorts: ", x$ncoh, "\n")
207240
cat(" Number of times: ", x$ntimes, "\n")
241+
cat(" Total uptake: ", x$uptake, "\n")
208242
# Output depends on whether $ntimes>1
209243
if (x$ntimes>1) {
210244
# Avoid no visible binding note
211245
tzero <- NULL
212246
# Create a tibble
213-
tib <- x$uptake |>
214-
left_join(x$total, join_by(tzero)) |>
215-
left_join(x$mean, join_by(tzero))
216-
cat("\n Uptake, total and mean present values by timepoint: \n")
247+
tib <- x$total |>
248+
left_join(x$mean, join_by(tzero)) |>
249+
select(tzero, total, mean)
250+
cat("\n Total and mean present values by timepoint: \n")
217251
print(tib)
218252
}
219253
else {
220-
cat(" Total uptake: ", x$uptake, "\n")
221254
cat(" Total present value: ", x$total, "\n")
222255
cat(" Mean present value: ", x$mean, "\n")
223256
}

README.Rmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ knitr::opts_chunk$set(
2323

2424
The goal of *dynamicpv* is to evaluate present values and cost-effectiveness with dynamic pricing and uptake.
2525

26-
Through the `dynamicpv::dynpv()` function, the package provides calculations of the present values of costs, life years, QALYs or other payoffs allowing for dynamic uptake (also known as multiple cohorts) and dynamic pricing (also known as life-cycle). The starting point is a conventional cohort cost-effectiveness model, such as one computed using the [heemod](https://cran.r-project.org/package=heemod) package.
26+
Through the [dynpv()](https://msdllcpapers.github.io/dynacem/reference/dynpv.html) function, the package provides calculations of the present values of costs, life years, QALYs or other payoffs allowing for dynamic uptake (also known as multiple cohorts) and dynamic pricing (also known as life-cycle). The starting point is a conventional cohort cost-effectiveness model, such as one computed using the [heemod](https://cran.r-project.org/package=heemod) package.
2727

2828
## Installation
2929

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ coverage](https://codecov.io/gh/MSDLLCpapers/dynacem/graph/badge.svg)](https://a
1515
The goal of *dynamicpv* is to evaluate present values and
1616
cost-effectiveness with dynamic pricing and uptake.
1717

18-
Through the `dynamicpv::dynpv()` function, the package provides
19-
calculations of the present values of costs, life years, QALYs or other
20-
payoffs allowing for dynamic uptake (also known as multiple cohorts) and
21-
dynamic pricing (also known as life-cycle). The starting point is a
22-
conventional cohort cost-effectiveness model, such as one computed using
23-
the [heemod](https://cran.r-project.org/package=heemod) package.
18+
Through the
19+
[dynpv()](https://msdllcpapers.github.io/dynacem/reference/dynpv.html)
20+
function, the package provides calculations of the present values of
21+
costs, life years, QALYs or other payoffs allowing for dynamic uptake
22+
(also known as multiple cohorts) and dynamic pricing (also known as
23+
life-cycle). The starting point is a conventional cohort
24+
cost-effectiveness model, such as one computed using the
25+
[heemod](https://cran.r-project.org/package=heemod) package.
2426

2527
## Installation
2628

_pkgdown.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
url: https://MSDLLCpapers.github.io/dynacem/
2+
template:
3+
bootstrap: 5
4+
5+
reference:
6+
- title: Key functions and datasets
7+
- contents:
8+
- dynpv
9+
- futurepv
10+
- oncpsm
11+
- title: Methods to apply to dynpv S3 objects
12+
- contents:
13+
- "`+.dynpv`"
14+
- "`-.dynpv`"
15+
- mean.dynpv
16+
- ncoh
17+
- ntimes
18+
- sum_by_coh
19+
- summary.dynpv
20+
- total
21+
- uptake
22+
- title: Helper functions
23+
- contents:
24+
- addprod
25+
- get_dynfields
26+
- get_param_value
27+
- trim_vec
28+

man/addprod.Rd

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)