Data : ajouter table FU dans grstat_example()#98
Data : ajouter table FU dans grstat_example()#98charlottebargain wants to merge 3 commits intomainfrom
grstat_example()#98Conversation
La table fu possède trois colonnes : `subjid` (l'identifiant patient), `fu_date` (date de dernier statut connu) et `fu_status` (dernier statut connu). Le statut est déterminé en fonction du bras de traitement (`arm` dans la table `enrolres` et du statut de progression de la tumeur connu dans la table `rc`.
Exemple :
db = grstat_example()
data_km = db$enrolres %>%
left_join(db$fu, by = "subjid") %>%
mutate(time_OS = as.numeric(fu_date - date_inclusion)/365.25,
event_OS = 1*(fu_status == "Dead"))
km = survival::survfit(survival::Surv(time_OS, event_OS) ~ arm, data = data_km)
p = ggsurvfit::ggsurvfit(km) +
ggsurvfit::add_censor_mark() +
ggsurvfit::add_confidence_interval() +
ggsurvfit::add_risktable()
print(p)
DanChaltiel
left a comment
There was a problem hiding this comment.
Trop génial Charlotote, c'est vraiment du super code !
Je t'ai fait mes commentaires, mais évidemment ce n'est que mon avis et tout est toujours discutable (via les commentaires ou sinon on en discute quand tu veux).
J'ai vu que tu avais mis l'exemple dans le commentaire de ton commit, il faut plutôt que tu le mettes dans le 1er post en haut de la PR.
J'ai testé, c'est super pour l'OS, mais pour la PFS ça risque d'être plus compliqué à paramétrer :-(
Je vais essayer de faire une microappli Shiny pour explorer tout ça quand j'ai un moment.
| if (!all(c("subjid", "date_inclusion") %in% names(enrolres))) { | ||
| stop("`enrolres` must contain columns `subjid` and `date_inclusion`.") | ||
| } |
There was a problem hiding this comment.
Très bien la programmation défensive :-)
Vu que c'est fréquent, j'avais ajouté un helper pour simplifier : assert_names_exists()
Cela dit, vu que c'est une fonction interne, pas besoin de trop t'embêter : "l'utilisateur" qui donne enrolres et recist c'est la fonction grstat_example() donc on est tranquille.
| enrolres = enrolres %>% arrange(subjid) | ||
| recist = recist %>% arrange(subjid) |
There was a problem hiding this comment.
Elles ne sont pas arrangées par défaut ?
Si c'est le cas il faut qu'on corrige dans les autres fonctions
| } | ||
|
|
||
| rcresp_levels = levels(recist$rcresp) | ||
| if (!"Progressive disease" %in% rcresp_levels) { stop("One level of `rcresp_levels` must be 'Progressive disease'. Found: ", paste(rcresp_levels, collapse = ", ")) } |
There was a problem hiding this comment.
Tu es sûr de ton check ?
Si je comprends bien il faut que la fonction plante si aucun patient n'est progressif ?
PS: Tu pourrais simplifier ta condition en if(!any(recist$rcresp=="Progressive disease"))
| progression_status = recist %>% | ||
| select(subjid, rcresp) %>% | ||
| summarise(status = any(rcresp == "Progressive disease"), .by = 'subjid') %>% | ||
| mutate(status = as_factor(if_else(!is.na(status), true = "Progressive disease", false = "No progression"))) |
There was a problem hiding this comment.
[FACULTATIF]
Ça marche très bien comme ça, mais je te conseille plutôt d'utiliser des vecteurs logiques autant que possible.
Une colonne event_prog=!is.na(status) sera plus facile à gérer par la suite.
Et rien ne t'empêche de faire une autre colonne status_prog = factor(ifelse(event_prog, "Progressive disease", "No progression"))
Si on regarde ton algorithme global, tu crées un logical avec is.na, tu le transformes en factor, puis plus loin tu retransformes ton factor en logical avec ==.
| enrolres = enrolres %>% arrange(subjid) | ||
| recist = recist %>% arrange(subjid) | ||
|
|
||
| prog_status = .progression_status(recist) |
There was a problem hiding this comment.
Comme .progression_status() retourne une df, il faudrait plutôt appeler le résultat data_prog
| rtn$datetime_extraction = structure(1704067200, class = c("POSIXct", "POSIXt"), | ||
| tzone = "Europe/Paris") | ||
|
|
||
| rtn = .remove_post_event(rtn) |
There was a problem hiding this comment.
plus joli en pipe après le imap ?
| #' @importFrom lubridate ymd as_date days | ||
| #' @importFrom stats rexp | ||
| #' @importFrom forcats as_factor fct_recode | ||
| example_fu = function(enrolres, recist, seed, lambda_censor = 0.3, lambda_control = 0.2, beta_arm = -0.6, beta_prog_status = 0.5, ...){ |
There was a problem hiding this comment.
Bienvenue dans la galère des noms de variables 😅
Vu qu'on utilise ... pour passer les arguments, on n'appelle jamais example_fu (non exporté).
Chaque argument est dispatché à sa fonction :
grstat_example(beta_arm=-0.7, beta_prog_status=0.4, rc_coef_treatement=4)
Du coup, il faudrait préfixer tes arguments par fu_ pour qu'on comprenne bien que beta_arm vaut pour la survie et pas pour la progression.
| if(is.null(prog_status)) {stop("`prog_status` is NULL.")} | ||
|
|
||
| if(!is.factor(arm)) arm = as_factor(arm) | ||
| if(!is.factor(prog_status)) prog_status = mutate(prog_status, status = as_factor(status)) |
There was a problem hiding this comment.
Je crois que prog_status est forcément un factor à ce niveau
| #' @return list of data frames with enrolment, adverse event, recist evaluation and follow-up information with recist evaluation truncated with last follow-up date | ||
| #' @keywords internal | ||
| #' @importFrom dplyr left_join select filter | ||
| .remove_post_event = function(rtn){ |
There was a problem hiding this comment.
Pas mal du tout !
Idem, pas forcément besoin de check vu qu'on contrôle complètement la fonction.
Du coup ça pose la question : est-ce qu'on ne devrait pas enlever aussi les AE ?
| .default = rc_coef_treatement) | ||
| percent_change_per_month = percent_change_per_month * coef | ||
| subj_delai = 42 * seq(rc_num_timepoints) + runif(rc_num_timepoints, -7, 7) | ||
| subj_delai = 15 * seq(rc_num_timepoints) + runif(rc_num_timepoints, -7, 7) |
There was a problem hiding this comment.
Oui, c'est beaucoup mieux avec 15 !
Est-ce que tu pourrais en faire un argument ?
Comme promis, l'appli pour tweaker tes paramètres en temps réel. - Le prompt IA, si ça t'intéresse de voir comment j'ai fait : https://chatgpt.com/share/69242598-61fc-8003-b30f-72e50d93da0a - J'ai mis des données bidon pour la PFS, je te laisse cuisiner get_plot() - rc_delay n'est pas fonctionnel tant qu'il n'existe pas dans `example_rc()` (cf commentaire dans ma PR), mais on en aura sans doute besoin pour avoir une PFS jolie. Tu peux évidemment le renommer, je n'y ai pas beaucoup réfléchi. - j'ai un tout petit peu modifié `grstat_example()` pour que l'appel soit plus robuste car j'avais une erreur incompréhensible. [skip_ci]
TODO : ajouter un exemple