Skip to content

Data : ajouter table FU dans grstat_example()#98

Draft
charlottebargain wants to merge 3 commits intomainfrom
84-survival-data-simulation
Draft

Data : ajouter table FU dans grstat_example()#98
charlottebargain wants to merge 3 commits intomainfrom
84-survival-data-simulation

Conversation

@charlottebargain
Copy link
Contributor

TODO : ajouter un exemple

@charlottebargain charlottebargain linked an issue Nov 5, 2025 that may be closed by this pull request
7 tasks
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)
Copy link
Member

@DanChaltiel DanChaltiel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +323 to +325
if (!all(c("subjid", "date_inclusion") %in% names(enrolres))) {
stop("`enrolres` must contain columns `subjid` and `date_inclusion`.")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +335 to +336
enrolres = enrolres %>% arrange(subjid)
recist = recist %>% arrange(subjid)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = ", ")) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, ...){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Data : ajouter table FU dans grstat_example()

2 participants