1
+ # Copyright (c) 2023 Merck & Co., Inc., Rahway, NJ, USA and its affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This file is part of the psm3mkv program.
5
+ #
6
+ # psm3mkv is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ # ==================================================================
20
+ # Functions relating to comparing likelihoods of parametric and splines PSMs
21
+ # - compare_psm_likes()
22
+ # - compare_psm_likes_par()
23
+ # - compare_psm_likes_spl()
24
+ # ==================================================================
25
+
1
26
# ' Compare likelihoods of PSMs
2
27
# '
3
28
# ' Compare the total log-likelihood values for the patient-level dataset after fitting PSM-simple and PSM-complex models to each combination of endpoint distributions
6
31
# ' @importFrom rlang .data
7
32
# ' @return List containing
8
33
# ' - `res`: Dataset of calculation results for each model
9
- # ' - `ind_aic`: Set of statistical distributions for TTP, PFS and OS which individually fit each endpoint with the best (lowest) AIC
10
- # ' - `ind_bic`: Set of statistical distributions for TTP, PFS and OS which individually fit each endpoint with the best (lowest) BIC
11
- # ' - `jt_aic`: Set of statistical distributions for TTP, PFS and OS which overall fit a PSM with the best (lowest) AIC
12
- # ' - `jt_bic`: Set of statistical distributions for TTP, PFS and OS which overall fit a PSM with the best (lowest) BIC
34
+ # ' - `best`: Tibble indicating which is the best fitting model individually or jointly, to each endpoint, according to AIC or BIC
13
35
# ' @export
14
36
# ' @examples
15
37
# ' # Fit parametric distributions to a dataset
16
38
# ' bosonc <- create_dummydata("flexbosms")
17
39
# ' parfits <- fit_ends_mods_par(bosonc)
40
+ # ' splfits <- fit_ends_mods_spl(bosonc)
18
41
# ' # Present comparison of likelihood calculations
42
+ # ' \donttest{
19
43
# ' compare_psm_likes(bosonc, parfits)
44
+ # ' compare_psm_likes(bosonc, splfits)
45
+ # ' }
20
46
compare_psm_likes <- function (ptdata , fitslist , cuttime = 0 ) {
47
+ # Determine whether fit is a spline, then call the right function
48
+ if (fitslist $ pfs [[1 ]]$ result $ dlist $ name == " survspline" ) {
49
+ compare_psm_likes_spl(ptdata , fitslist , cuttime )
50
+ } else {
51
+ compare_psm_likes_par(ptdata , fitslist , cuttime )
52
+ }
53
+ }
54
+
55
+ # Compare likelihoods of PSMs for parametric models
56
+ compare_psm_likes_par <- function (ptdata , fitslist , cuttime = 0 ) {
21
57
# Check that fitslist is a list of 6 endpoints
22
58
if (length(fitslist )!= 6 ) {stop(" The list provided to fitslist must contain all 6 endpoints" )}
23
59
# Create local variables
24
60
eps <- ndists <- aic_indbest <- bic_indbest <- bests <- res <- thisfit <- aic_jtbest <- bic_jtbest <- NULL
25
61
ll <- rank_aic <- ttp_meth <- pfs_dist <- os_dist <- rank_bic <- NULL
26
- # TTP, PFS and OS are endpoints 1, 3 and 4
27
62
eps <- c(1 , 3 , 4 )
28
- # Number of distributions for each endpoint
29
- ndists <- eps | >
30
- purrr :: map_vec(~ length(fitslist [[.x ]]))
31
- # Best fits for each endpoint - AIC
32
- aic_indbest <- eps | >
33
- purrr :: map_vec(~ find_bestfit(fitslist [[.x ]], crit = " aic" )$ fit $ dlist $ name )
34
- # Best fits for each endpoint - BIC
35
- bic_indbest <- eps | >
36
- purrr :: map_vec(~ find_bestfit(fitslist [[.x ]], crit = " bic" )$ fit $ dlist $ name )
37
- # Join as a tibble
38
- bests <- rbind(aic_indbest , bic_indbest )
63
+ # Best fits by AIC or BIC
64
+ fits_aic <- eps | >
65
+ purrr :: map(~ find_bestfit(fitslist [[.x ]], crit = " aic" )$ fit )
66
+ fits_bic <- eps | >
67
+ purrr :: map(~ find_bestfit(fitslist [[.x ]], crit = " bic" )$ fit )
68
+ fits_all <- c(fits_aic , fits_bic )
69
+ # Pull out names/distributions
70
+ fits_names <- seq(2 * length(eps )) | >
71
+ purrr :: map_vec(~ fits_all [[.x ]]$ dlist $ name )
72
+ # Summarize parametric results in a table
39
73
bests <- tibble :: tibble(
40
- ttp_meth = bests [, 1 ],
41
- pfs_dist = bests [, 2 ],
42
- os_dist = bests [, 3 ],
74
+ ttp_meth = fits_names [c( 1 , 4 ) ],
75
+ pfs_dist = fits_names [c( 2 , 5 ) ],
76
+ os_dist = fits_names [c( 3 , 6 ) ],
43
77
meth = " ind" ,
44
78
ic = c(" aic" , " bic" )
45
79
)
46
80
# Create results table for each model combination
81
+ ndists <- eps | >
82
+ purrr :: map_vec(~ length(fitslist [[.x ]]))
47
83
res <- tibble :: tibble(
48
84
id = 1 : (ndists [3 ]* ndists [2 ]* (ndists [1 ]+ 1 )),
49
85
ttp_meth = NA ,
50
86
pfs_dist = NA ,
51
87
os_dist = NA ,
52
88
ll = NA ,
53
89
npar = NA ,
54
- npts = fitslist $ os [[ 1 ]] $ result $ N
90
+ npts = NA
55
91
)
56
92
# Create a safe calculation of the PSM-simple likelihood (returns NA on error)
57
93
slike_simple <- purrr :: possibly(
58
94
~ calc_likes_psm_simple(
59
95
ptdata = ptdata ,
60
96
dpam = .x ,
61
- cuttime = cuttime )$ ll [ 2 ] ,
97
+ cuttime = cuttime ),
62
98
otherwise = NA )
63
99
# Create a safe calculation of the PSM-complex likelihood (returns NA on error)
64
100
slike_complex <- purrr :: possibly(
65
101
~ calc_likes_psm_complex(
66
102
ptdata = ptdata ,
67
103
dpam = .x ,
68
- cuttime = cuttime )$ ll [ 2 ] ,
104
+ cuttime = cuttime ),
69
105
otherwise = NA )
70
106
# Compute results for PSM-simple models
71
107
message(" Calculating PSM simple" )
@@ -78,8 +114,12 @@ compare_psm_likes <- function(ptdata, fitslist, cuttime=0) {
78
114
res $ ttp_meth [resrow ] <- " simple"
79
115
res $ pfs_dist [resrow ] <- thisfit $ pfs $ dlist $ name
80
116
res $ os_dist [resrow ] <- thisfit $ os $ dlist $ name
81
- res $ ll [resrow ] <- slike_simple(thisfit )
82
- res $ npar [resrow ] <- thisfit $ pfs $ npars + thisfit $ os $ npars + 1
117
+ psmsimple <- slike_simple(thisfit )
118
+ if (is.na(psmsimple )[1 ]== FALSE ) {
119
+ res $ ll [resrow ] <- psmsimple $ ll [2 ]
120
+ res $ npar [resrow ] <- thisfit $ pfs $ npars + thisfit $ os $ npars + 1
121
+ res $ npts [resrow ] <- psmsimple $ npts [2 ]
122
+ }
83
123
}
84
124
}
85
125
# Compute results for PSM-complex models
@@ -95,8 +135,12 @@ compare_psm_likes <- function(ptdata, fitslist, cuttime=0) {
95
135
res $ ttp_meth [resrow ] <- thisfit $ ttp $ dlist $ name
96
136
res $ pfs_dist [resrow ] <- thisfit $ pfs $ dlist $ name
97
137
res $ os_dist [resrow ] <- thisfit $ os $ dlist $ name
98
- res $ ll [resrow ] <- slike_complex(thisfit )
99
- res $ npar [resrow ] <- thisfit $ ttp $ npars + thisfit $ pfs $ npars + thisfit $ os $ npars
138
+ psmcomplex <- slike_complex(thisfit )
139
+ if (is.na(psmcomplex )[1 ]== FALSE ) {
140
+ res $ ll [resrow ] <- psmcomplex $ ll [2 ]
141
+ res $ npar [resrow ] <- thisfit $ ttp $ npars + thisfit $ pfs $ npars + thisfit $ os $ npars
142
+ res $ npts [resrow ] <- psmcomplex $ npts [2 ]
143
+ }
100
144
}
101
145
}
102
146
}
@@ -114,8 +158,8 @@ compare_psm_likes <- function(ptdata, fitslist, cuttime=0) {
114
158
best_bic = 0
115
159
)
116
160
# Identify best AIC and best BIC model
117
- res $ best_aic [res $ ttp_meth == aic_indbest [1 ] & res $ pfs_dist == aic_indbest [ 2 ] & res $ os_dist == aic_indbest [ 3 ]] <- 1
118
- res $ best_bic [res $ ttp_meth == bic_indbest [ 1 ] & res $ pfs_dist == bic_indbest [2 ] & res $ os_dist == bic_indbest [ 3 ]] <- 1
161
+ res $ best_aic [res $ ttp_meth == bests $ ttp_meth [1 ] & res $ pfs_dist == bests $ pfs_dist [ 1 ] & res $ os_dist == bests $ os_dist [ 1 ]] <- 1
162
+ res $ best_bic [res $ ttp_meth == bests $ ttp_meth [ 2 ] & res $ pfs_dist == bests $ pfs_dist [2 ] & res $ os_dist == bests $ os_dist [ 2 ]] <- 1
119
163
# Identify best distributions for overall AIC and BIC
120
164
aic_jtbest <- res | >
121
165
dplyr :: filter(rank_aic == 1 ) | >
@@ -124,7 +168,146 @@ compare_psm_likes <- function(ptdata, fitslist, cuttime=0) {
124
168
bic_jtbest <- res | >
125
169
dplyr :: filter(rank_bic == 1 ) | >
126
170
dplyr :: select(ttp_meth , pfs_dist , os_dist ) | >
171
+ dplyr :: mutate(meth = " joint" , ic = " bic" )
172
+ # Join together
173
+ bests <- bests | >
174
+ tibble :: add_row(aic_jtbest ) | >
175
+ tibble :: add_row(bic_jtbest )
176
+ # Return
177
+ return (list (results = res , bests = bests ))
178
+ }
179
+
180
+ # Compare likelihoods of PSMs for spline models
181
+ compare_psm_likes_spl <- function (ptdata , fitslist , cuttime = 0 ) {
182
+ # Check that fitslist is a list of 6 endpoints
183
+ if (length(fitslist )!= 6 ) {stop(" The list provided to fitslist must contain all 6 endpoints" )}
184
+ # Create local variables
185
+ eps <- ndists <- aic_indbest <- bic_indbest <- bests <- res <- thisfit <- aic_jtbest <- bic_jtbest <- NULL
186
+ ll <- rank_aic <- ttp_meth <- ttp_knots <- pfs_scales <- pfs_knots <- os_scales <- os_knots <- rank_bic <- NULL
187
+ # Best fits by AIC or BIC
188
+ eps <- c(1 , 3 , 4 )
189
+ fits_aic <- eps | >
190
+ purrr :: map(~ find_bestfit(fitslist [[.x ]], crit = " aic" )$ fit )
191
+ fits_bic <- eps | >
192
+ purrr :: map(~ find_bestfit(fitslist [[.x ]], crit = " bic" )$ fit )
193
+ fits_all <- c(fits_aic , fits_bic )
194
+ # Pull out scales and knots
195
+ fits_scales <- seq(2 * length(eps )) | >
196
+ purrr :: map_vec(~ fits_all [[.x ]]$ aux $ scale )
197
+ fits_knots <- seq(2 * length(eps )) | >
198
+ purrr :: map_vec(~ length(fits_all [[.x ]]$ aux $ knots ))
199
+ # Summarize parametric results in a table
200
+ bests <- tibble :: tibble(
201
+ ttp_meth = fits_scales [c(1 , 4 )],
202
+ ttp_knots = fits_knots [c(1 , 4 )],
203
+ pfs_scales = fits_scales [c(2 , 5 )],
204
+ pfs_knots = fits_knots [c(2 , 5 )],
205
+ os_scales = fits_scales [c(3 , 6 )],
206
+ os_knots = fits_knots [c(3 , 6 )],
207
+ meth = " ind" ,
208
+ ic = c(" aic" , " bic" )
209
+ )
210
+ # Create results table for each model combination
211
+ ndists <- eps | >
212
+ purrr :: map_vec(~ length(fitslist [[.x ]]))
213
+ res <- tibble :: tibble(
214
+ id = 1 : (ndists [3 ]* ndists [2 ]* (ndists [1 ]+ 1 )),
215
+ ttp_meth = NA ,
216
+ ttp_knots = NA ,
217
+ pfs_scales = NA ,
218
+ pfs_knots = NA ,
219
+ os_scales = NA ,
220
+ os_knots = NA ,
221
+ ll = NA ,
222
+ npar = NA ,
223
+ npts = NA
224
+ )
225
+ # Create a safe calculation of the PSM-simple likelihood (returns NA on error)
226
+ slike_simple <- purrr :: possibly(
227
+ ~ calc_likes_psm_simple(
228
+ ptdata = ptdata ,
229
+ dpam = .x ,
230
+ cuttime = cuttime ),
231
+ otherwise = NA )
232
+ # Create a safe calculation of the PSM-complex likelihood (returns NA on error)
233
+ slike_complex <- purrr :: possibly(
234
+ ~ calc_likes_psm_complex(
235
+ ptdata = ptdata ,
236
+ dpam = .x ,
237
+ cuttime = cuttime ),
238
+ otherwise = NA )
239
+ # Compute results for PSM-simple models
240
+ message(" Calculating PSM simple" )
241
+ thisfit <- list (ttp = NA , pfs = NA , os = NA )
242
+ for (p in 1 : ndists [2 ]) {
243
+ thisfit $ pfs <- fitslist $ pfs [[p ]]$ result
244
+ for (o in 1 : ndists [3 ]) {
245
+ thisfit $ os <- fitslist $ os [[o ]]$ result
246
+ resrow <- (p - 1 )* ndists [3 ] + o
247
+ res $ ttp_meth [resrow ] <- " simple"
248
+ res $ ttp_knots [resrow ] <- 0
249
+ res $ pfs_scales [resrow ] <- thisfit $ pfs $ aux $ scale
250
+ res $ pfs_knots [resrow ] <- length(thisfit $ pfs $ aux $ knots )
251
+ res $ os_scales [resrow ] <- thisfit $ os $ aux $ scale
252
+ res $ os_knots [resrow ] <- length(thisfit $ os $ aux $ knots )
253
+ psmsimple <- slike_simple(thisfit )
254
+ if (is.na(psmsimple )[1 ]== FALSE ) {
255
+ res $ ll [resrow ] <- psmsimple $ ll [2 ]
256
+ res $ npar [resrow ] <- thisfit $ pfs $ npars + thisfit $ os $ npars + 1
257
+ res $ npts [resrow ] <- psmsimple $ npts [2 ]
258
+ }
259
+ }
260
+ }
261
+ # Compute results for PSM-complex models
262
+ message(" Calculating PSM complex" )
263
+ thisfit <- list (ttp = NA , pfs = NA , os = NA )
264
+ for (t in 1 : ndists [1 ]) {
265
+ thisfit $ ttp <- fitslist $ ttp [[t ]]$ result
266
+ for (p in 1 : ndists [2 ]) {
267
+ thisfit $ pfs <- fitslist $ pfs [[p ]]$ result
268
+ for (o in 1 : ndists [3 ]) {
269
+ thisfit $ os <- fitslist $ os [[o ]]$ result
270
+ resrow <- t * ndists [3 ]* ndists [2 ] + (p - 1 )* ndists [3 ] + o
271
+ res $ ttp_meth [resrow ] <- thisfit $ ttp $ aux $ scale
272
+ res $ ttp_knots [resrow ] <- length(thisfit $ ttp $ aux $ knots )
273
+ res $ pfs_scales [resrow ] <- thisfit $ pfs $ aux $ scale
274
+ res $ pfs_knots [resrow ] <- length(thisfit $ pfs $ aux $ knots )
275
+ res $ os_scales [resrow ] <- thisfit $ os $ aux $ scale
276
+ res $ os_knots [resrow ] <- length(thisfit $ os $ aux $ knots )
277
+ psmcomplex <- slike_complex(thisfit )
278
+ if (is.na(psmcomplex )[1 ]== FALSE ) {
279
+ res $ ll [resrow ] <- psmcomplex $ ll [2 ]
280
+ res $ npar [resrow ] <- thisfit $ ttp $ npars + thisfit $ pfs $ npars + thisfit $ os $ npars
281
+ res $ npts [resrow ] <- psmcomplex $ npts [2 ]
282
+ }
283
+ }
284
+ }
285
+ }
286
+ # Set log-likelihood values to NA if if cannot be calculated (=-Inf)
287
+ res $ ll [res $ ll == - Inf ] <- NA
288
+ # Add AIC and BIC, with ranks
289
+ message(" Wrapping up" )
290
+ res <- res | >
291
+ dplyr :: mutate(
292
+ aic = 2 * .data $ npar - 2 * ll ,
293
+ bic = .data $ npar * log(.data $ npts )- 2 * ll ,
294
+ rank_aic = rank(.data $ aic ),
295
+ rank_bic = rank(.data $ bic ),
296
+ best_aic = 0 ,
297
+ best_bic = 0
298
+ )
299
+ # Identify best AIC and best BIC model
300
+ res $ best_aic [res $ ttp_meth == bests $ ttp_meth [1 ] & res $ ttp_knots == bests $ ttp_knots [1 ] & res $ pfs_scales == bests $ pfs_scales [1 ] & res $ pfs_knots == bests $ pfs_knots [1 ] & res $ os_scales == bests $ os_scales [1 ] & res $ os_knots == bests $ os_knots [1 ]] <- 1
301
+ res $ best_bic [res $ ttp_meth == bests $ ttp_meth [2 ] & res $ ttp_knots == bests $ ttp_knots [2 ] & res $ pfs_scales == bests $ pfs_scales [2 ] & res $ pfs_knots == bests $ pfs_knots [2 ] & res $ os_scales == bests $ os_scales [2 ] & res $ os_knots == bests $ os_knots [2 ]] <- 1
302
+ # Identify best distributions for overall AIC and BIC
303
+ aic_jtbest <- res | >
304
+ dplyr :: filter(rank_aic == 1 ) | >
305
+ dplyr :: select(ttp_meth , ttp_knots , pfs_scales , pfs_knots , os_scales , os_knots ) | >
127
306
dplyr :: mutate(meth = " joint" , ic = " aic" )
307
+ bic_jtbest <- res | >
308
+ dplyr :: filter(rank_bic == 1 ) | >
309
+ dplyr :: select(ttp_meth , ttp_knots , pfs_scales , pfs_knots , os_scales , os_knots ) | >
310
+ dplyr :: mutate(meth = " joint" , ic = " bic" )
128
311
# Join together
129
312
bests <- bests | >
130
313
tibble :: add_row(aic_jtbest ) | >
0 commit comments