diff --git a/analyse-R.html b/analyse-R.html index 78431514..d8b5e21f 100644 --- a/analyse-R.html +++ b/analyse-R.html @@ -200,7 +200,7 @@

Introduction
à l’analyse d’enquêtes
avec R et RStudio

-Dernière mise à jour : 26 octobre 2021 +Dernière mise à jour : 6 décembre 2021

DOI: 10.5281/zenodo.2081968

@@ -278,7 +278,7 @@

Analyser

Statistiques avancées

-

Effets d’interaction dans un modèle Multicolinéarité dans la régression Quel type de modèles choisir ? Analyse de survie Analyse de séquences Trajectoires de soins : un exemple de données longitudinales Analyse de réseaux Analyse spatiale

+

Tableaux statistiques avancés avec gtsummary Effets d’interaction dans un modèle Multicolinéarité dans la régression Quel type de modèles choisir ? Analyse de survie Analyse de séquences Trajectoires de soins : un exemple de données longitudinales Analyse de réseaux Analyse spatiale

@@ -307,7 +307,7 @@

Approfondir

Divers

-

Mettre en forme des nombres Couleurs et Palettes Annotations mathématiques Calculer un âge Diagramme de Lexis

+

Mettre en forme des nombres avec scales Couleurs et Palettes Annotations mathématiques Calculer un âge Diagramme de Lexis

@@ -5738,7 +5738,7 @@

Renommer des variables

[13] "hard.rock" "lecture.bd" "peche.chasse" [16] "cuisine" "bricol" "cinema" [19] "sport" "heures.tv" -

Si l’on utilise dplyr (voir chapitre dédié), on peut facilement renommer une variable avec rename{.pkg=“dplyr} :

+

Si l’on utilise dplyr (voir chapitre dédié), on peut facilement renommer une variable avec rename :

library(dplyr, quietly = TRUE)

 Attachement du package : 'dplyr'
@@ -7328,6 +7328,7 @@

Manipulations avancées avec data.table

  • Enchaîner les opérations
  • Réorganiser un tableau
  • +
  • Ressources en ligne
  • @@ -7475,6 +7476,16 @@

    Réorganiser un tableau

    L’extension data.table fournie également deux fonctions, melt et dcast, dédiée à la réorganisation d’un tableau de données (respectivement wide-to-long reshaping et long-to-wide reshaping).

    Pour plus de détails, voir la vignette dédiée sur le site de l’extension : https://rdatatable.gitlab.io/data.table/articles/datatable-reshape.html

    +
    +

    Ressources en ligne

    + +
    @@ -19955,6 +19966,7 @@

    Identifier les variables ayant un effet significatif

    {"columns":[{"label":[""],"name":["_rn_"],"type":[""],"align":["left"]},{"label":["LR Chisq"],"name":[1],"type":["dbl"],"align":["right"]},{"label":["Df"],"name":[2],"type":["dbl"],"align":["right"]},{"label":["Pr(>Chisq)"],"name":[3],"type":["dbl"],"align":["right"]}],"data":[{"1":"17.309362","2":"1","3":"3.176182e-05","_rn_":"sexe"},{"1":"52.802551","2":"3","3":"2.019958e-11","_rn_":"grpage"},{"1":"123.826250","2":"4","3":"8.132287e-26","_rn_":"etud"},{"1":"4.231856","2":"5","3":"5.165401e-01","_rn_":"relig"},{"1":"13.438488","2":"1","3":"2.465142e-04","_rn_":"heures.tv"}],"options":{"columns":{"min":{},"max":[10]},"rows":{"min":[10],"max":[10]},"pages":{}}}
    +

    Si l’on a recours à tbl_regression, on peut facilement ajouter les p-valeurs globales avec add_global_p.

    reg %>%
       tbl_regression(exponentiate = TRUE) %>%
    @@ -28398,59 +28410,160 @@ 

    CAH avec l’extension FactoMineR

    -
    +
    -

    Effets d’interaction dans un modèle

    +

    Tableaux statistiques avancés avec gtsummary

    -
    -

    Ce chapitre est évoqué dans le webin-R #07 (régression logistique partie 2) sur YouTube.

    +

    L’extension gtsummary a déjà été abordée dans d’autres chapitres, notamment via les fonctions tbl_summary et tbl_svysummary dans le chapitre sur la statistique bivariée ou la fonction tbl_regression dans le chapitre sur la régression logistique.

    +

    Dans ce chapitre, nous allons explorer plus en profondeur les différentes options offertes gtsummary pour la réalisation de tableaux statistiques prêts à être publiés.

    +

    Les personnes anglophones pourront également se référer à l’excellent site de documentation du package : https://www.danieldsjoberg.com/gtsummary/

    +
    library(gtsummary)
    +
    +

    Remarques sur les types de variables et les sélecteurs associés

    +

    gtsummary permets de réaliser des tableaux statistiques combinant plusieurs variables, l’affichage des résultats pouvant dépendre du type de variables.

    +

    Par défaut, gtsummary considère qu’une variable est catégorielle s’il s’agit d’un facteur, d’une variable textuelle ou d’une variable numérique ayant moins de 10 valeurs différentes.

    +

    Une variable sera considérée comme dichotomique (variable catégorielle à seulement deux modalités) s’il s’agit d’un vecteur logique (TRUE/FALSE), d’une variable textuelle codée yes/no ou d’une variable numérique codée 0/1.

    +

    Dans les autres cas, une variable numérique sera considérée comme continue.

    +
    +

    Si vous utilisez des vecteurs labellisés (voir le chapitre dédié), vous devez les convertir, en amont, en facteur ou en variables numériques. Voir l’extension labelled et les fonctions to_factor, unlabelled et unclass.

    +
    +

    Nous verrons plus loin qu’il est possible de forcer le type d’une variable et l’existence d’autres types de variables.

    +

    gtsummary fournit des sélecteurs qui pourront être utilisés dans les options des différentes fonctions, en particulier all_continuous pour les variables continues, all_dichotolous pour les variables dichotomiques et all_categorical pour les variables catégorielles (incluant les variables dichotomiques, utiliser all_categorical(dichotomous = FALSE) pour sélectionner les variables catégorielles en excluant les variables dichotomiques).

    +

    Dans le cadre des tableaux présentant les résultats d’un modèle statistique, il existe en plus d’autres sélecteurs pour sélectionner certains termes spécifiques : all_intercepts (pour sélectionner seulement le ou les intercepts du modèle), all_interaction pour les termes d’interactions entre plusieurs variables, all_contrasts pour sélectionner les variables catégorielles codées avec un contraste particulier.

    +
    +
    +

    Remarques sur la syntaxe des options

    +

    De nombreuses options des fonctions de gtsummary peuvent s’appliquer seulement à une ou certaines variables. Pour ces options-là, gtsummary attends une formule de la forme variables concernées ~ valeur de l'option ou bien une liste de formules ayant cette forme.

    +

    Par exemple, pour modifier l’étiquette associée à une certaine variable, on peut utiliser l’option label de tbl_svysummary.

    +
    tbl_summary(trial, label = age ~ "Âge")
    +tbl_summary(trial, label = list(age ~ "Âge", trt ~ "Traitement"))
    +

    gtsummary est très flexible sur la manière d’indiquer la ou les variables concernées. Il peut s’agir du nom de la variable, d’une chaîne de caractères contenant le nom de la variable, ou d’un vecteur contenant le nom de la variable. Les syntaxes ci-dessous sont ainsi équivalentes.

    +
    tbl_summary(trial, label = age ~ "Âge")
    +tbl_summary(trial, label = "age" ~ "Âge")
    +v <- "age"
    +tbl_summary(trial, label = v ~ "Âge")
    +tbl_summary(trial, label = vars(age) ~ "Âge")
    +

    Pour appliquer le même changement à plusieurs variables, plusieurs syntaxes sont acceptées pour lister plusieurs variables.

    +
    tbl_summary(trial, label = c("age", "trt") ~ "Une même étiquette")
    +tbl_summary(trial, label = c(age, trt) ~ "Une même étiquette")
    +tbl_summary(trial, label = vars(age, trt) ~ "Une même étiquette")
    +

    Il est également possible d’utiliser la syntaxe tidyselect et les sélecteurs de tidyselect comme everything, starts_with, contains ou all_of. Ces différents sélecteurs peuvent être combinés au sein d’un c() ou de vars().

    +
    tbl_summary(trial, label = everything() ~ "Une même étiquette")
    +tbl_summary(trial, label = starts_with("a") ~ "Une même étiquette")
    +tbl_summary(trial, label = c(everything(), -age, -trt) ~ "Une même étiquette")
    +tbl_summary(trial, label = age:trt ~ "Une même étiquette")
    +

    Bien sûr, il est possible d’utiliser les sélecteurs propres à gtsummary.

    +
    tbl_summary(trial, label = all_continuous() ~ "Une même étiquette")
    +tbl_summary(trial, label = list(
    +  all_continuous() ~ "Variable continue",
    +  all_dichotomous() ~ "Variable dichotomique",
    +  all_categorical(dichotomous = FALSE) ~ "Variable catégorielle"
    +))
    +

    Enfin, si l’on ne précise rien à gauche du ~, ce sera considéré comme équivalent à everything(). Les deux syntaxes ci-dessous sont donc équivalentes.

    +
    tbl_summary(trial, label = ~"Une même étiquette")
    +tbl_summary(trial, label = everything() ~ "Une même étiquette")
    +
    +
    +

    Thèmes

    +

    gtsummary fournit plusieurs fonctions préfixées theme_gtsummary_*() permettant de modifier l’affichage par défaut des tableaux.

    +

    La fonction theme_gtsummary_journal permets d’adopter les standards de certaines grandes revues scientifiques telles que JAMA (Journal of the American Medical Association), The Lancet ou encore le NEJM (New England Journal of Medicine).

    +

    Par défaut, tbl_summary utilise la médiane et l’intervalle interquartile pour les variables continues. Si on applique theme_gtsummary_mean_sd, la moyenne et l’écart-type seront utilisés par défaut.

    +

    La fonction theme_gtsummary_language permet de modifier la langue utilisée par défaut dans les tableaux. Les options decimal.mark et big.mark permettent de définir respectivement le séparateur de décimales et le séparateur des milliers. Ainsi, pour présenter un tableau en français, on appliquera en début de script :

    +
    theme_gtsummary_language(language = "fr", decimal.mark = ",", big.mark = " ")
    +
    Setting theme `language: fr`
    -

    Dans un modèle statistique classique, on fait l’hypothèse implicite que chaque variable explicative est indépendante des autres. Cependant, cela ne se vérifie pas toujours. Par exemple, l’effet de l’âge peut varier en fonction du sexe. Il est dès lors nécessaire de prendre en compte dans son modèle les effets d’interaction1.

    -
    -

    Exemple d’interaction

    -

    Reprenons le modèle que nous avons utilisé dans le chapitre sur la régression logistique.

    -
    library(questionr)
    -data(hdv2003)
    -d <- hdv2003
    -d$sexe <- relevel(d$sexe, "Femme")
    -d$grpage <- cut(d$age, c(16, 25, 45, 65, 99), right = FALSE, include.lowest = TRUE)
    -d$etud <- d$nivetud
    -levels(d$etud) <- c(
    -  "Primaire", "Primaire", "Primaire",
    -  "Secondaire", "Secondaire", "Technique/Professionnel",
    -  "Technique/Professionnel", "Supérieur"
    -)
    -d$etud <- addNAstr(d$etud, "Manquant")
    -library(labelled)
    -var_label(d$sport) <- "Pratique du sport ?"
    -var_label(d$sexe) <- "Sexe"
    -var_label(d$grpage) <- "Groupe d'âges"
    -var_label(d$etud) <- "Niveau d'étude"
    -var_label(d$relig) <- "Pratique religieuse"
    -var_label(d$heures.tv) <- "Nombre d'heures passées devant la télévision par jour"
    -

    Nous avions alors exploré les facteurs associés au fait de pratiquer du sport.

    -
    mod <- glm(sport ~ sexe + grpage + etud + heures.tv + relig, data = d, family = binomial())
    -library(gtsummary)
    -tbl_regression(mod, exponentiate = TRUE)
    -
    +
    +

    Statistiques descriptives avec tbl_summary()

    +

    La fonction tbl_summary permets de réaliser des tris à plats de plusieurs variables, éventuellement croisés selon une variable catégorielle.

    +

    On lui passe en entrée un tableaux de données (data.frame) et par défaut toutes les variables sont résumées.

    +
    trial %>%
    +  tbl_summary()
    +
    + + + + + + + + + + + + + - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 + +Ensemble (effectif total: 200)1 +
    Age46 (37 – 59)48 (39 – 56)47 (38 – 57)
    -Binaire
    (oui / non)
    Logistique (logit)OR
    (odds ratio)
    stats::glm (family = binomial("logit"))survey::svyglm
    (family = quasibinomial("logit"))
    lme4::glmer (family = binomial("logit"))geepack::geeglm (family = binomial("logit"))
    Manquant7411
     Probitpas interprétable directementstats::glm (family = binomial("probit"))survey::svyglm
    (family = quasibinomial("probit"))
    lme4::glmer (family = binomial("probit"))geepack::geeglm (family = binomial("probit"))
    T Stage
     Log binomialRR
    (risques relatifs)
    PR
    (prevalence ratio)
    stats::glm (family = binomial("log"))survey::svyglm
    (family = quasibinomial("log"))
    lme4::glmer (family = binomial("log"))geepack::geeglm (family = binomial("log"))
    T128 (29%)25 (25%)53 (26%)
    -Catégorielle ordinale
    (3 modalités ou plus)
    Régression logistique ordinaleOR
    (odds ratio)
    -ordinal::clm()

    VGAM::vglm (family = cumulative(parallel = TRUE))

    MASS::polr() -
    -svrepmisc::svyclm()

    svyVGAM::svy_vglm (family = cumulative(parallel = TRUE))

    survey::svyolr() -
    ordinal::clmm() -geepack::ordgee()

    multgee::ordLORgee() -
    T225 (26%)29 (28%)54 (27%)
    -Catégorielle nominale
    (3 modalités ou plus)
    Régression logistique multinomialeOR
    (odds ratio)
    -nnet::multinom()

    VGAM::vglm (family = multinomial) -
    -svrepmisc::svymultinom()

    svyVGAM::svy_vglm (family = multinomial) -
     multgee::nomLORgee()
    T322 (22%)21 (21%)43 (22%)
    -Survie
    (time to event)
    CoxHR
    (hazard ratio)
    survival::coxph()survey::svycoxph() -survival::coxph() avec un terme frailty

    coxme::coxme() -
    -survival::coxph() avec l’option cluster -
    T423 (23%)27 (26%)50 (25%)
     Modèle à temps discret binomial cloglogHR
    (hazard ratio)
    stats::glm (family = binomial("cloglog"))survey::svyglm
    (family = quasibinomial("cloglog"))
    lme4::glmer (family = binomial("cloglog"))geepack::geeglm (family = binomial("cloglog"))
    Tumor Response28 (29%)33 (34%)61 (32%)
     Accelerated failure time (AFT)HR
    (hazard ratio)
    survival::survreg()survey::svysurvreg() -survival::survreg() avec un terme frailty - -survival::survreg() avec l’option cluster -
    Comptage avec surreprésentation de zérosZero-inflated PoissonOR & RRpscl::zeroinfl()sjstats::svyglm.zip()glmmTMB::glmmTMB(family = poisson) 
     Zero-inflated negtaive binomialOR & RRpscl::zeroinfl (dist = "negbin")sjstats::svyglm.zip (dist = "negbin")  
    Manquant347
    +

    + 1 + + + Médiane (EI); n (%) +

    +
    -

    * Voir aussi gee::gee comme alternative à geepack::geeglm

    +
    +

    Statistiques affichées (statistic, percent, sort)

    +

    Le paramètre statistic permets de sélectionner les statistiques à afficher pour chaque variable. On indiquera une chaîne de caractères dont les différentes statistiques seront indiquées entre accolades ({}).

    +

    Pour une variable continue, on pourra utiliser {median} pour la médiane, {mean} pour la moyenne, {sd} pour l’écart type, {var} pour la variance, {min} pour le minimum, {max} pour le maximum, ou encore {p##} (en remplacant ## par un nombre entier entre 00 et 100) pour le percentile correspondant (par exemple p25 et p75 pour le premier et le troisième quartile). Utilisez all_continous pour sélectionner toutes les variables continues.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(age, marker),
    +    statistic = all_continuous() ~ "Moy. : {mean} [min-max : {min} - {max}]"
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    AgeMoy. : 47 [min-max : 6 - 83]
    Manquant11
    Marker Level (ng/mL)Moy. : 0,92 [min-max : 0,00 - 3,87]
    Manquant10
    +

    + 1 + + + Moy. : Moyenne [min-max : Étendue] +

    +
    -

    Pour représenter ces rapports de risque, on peut ici encore avoir recours à la fonction ggcoef_model de l’extension GGally.

    -
    library(GGally, quietly = TRUE)
    -
    
    -Attachement du package : 'GGally'
    -
    L'objet suivant est masqué depuis 'package:JLutils':
    +

    Il est possible d’afficher des statistiques différentes pour chaque variable.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(age, marker),
    +    statistic = list(
    +      age ~ "Méd. : {median} [{p25} - {p75}]",
    +      marker ~ "Moy. : {mean} ({sd})"
    +    )
    +  )
    +
    + -
    -

    Ce chapitre est évoqué dans le webin-R #17 (trajectoires de soins : un exemple de données longitudinales 1 : description des données) sur YouTube.

    -

    Ce chapitre est évoqué dans le webin-R #18 (trajectoires de soins : un exemple de données longitudinales 2 : analyse de survie) sur YouTube.

    -

    Ce chapitre est évoqué dans le webin-R #19 (trajectoires de soins : un exemple de données longitudinales 3 : analyse de séquences) sur YouTube.

    -

    Ce chapitre est évoqué dans le webin-R #20 (trajectoires de soins : un exemple de données longitudinales 4 : régression logistique multinomiale & modèles mixtes à classe latente) sur YouTube.

    -

    Ce chapitre est évoqué dans le webin-R #21 (trajectoires de soins : un exemple de données longitudinales 5 : modèle à observations répétée, régression logistique ordinale GEE & analyse de survie multi-états) sur YouTube.

    -
    -
    -

    Une version de ce chapitre ustilisant l’extension data.table plutôt que dplyr est également disponible.

    -
    -

    Dans ce chapitre, nous allons aborder plusieurs méthodes d’analyse à partir d’un jeu de données longitudinales. Tout d’abord, importons les données dans R avec la commande suivante :

    -
    load(url("http://larmarange.github.io/analyse-R/data/care_trajectories.RData"))
    -
    class(care_trajectories)
    -
    [1] "data.table" "data.frame"
    -

    Nous obtenons un objet appelé care_trajectories. La fonction class nous montre qu’il s’agit d’un tableau de données au format data.table (voir le chapitre dédié). Chargeons le tidyverse et utilisons as_tibble pour convertir le tableau de données.

    -
    library(tidyverse, quietly = TRUE)
    -care_trajectories <- as_tibble(care_trajectories)
    -
    -

    Première description des données

    -

    Jetons un premier regard aux données.

    -
    head(care_trajectories)
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    idmonthcare_statussexageeducationwealthdistance_clinic
    30D11221
    31D11221
    32D11221
    33D11221
    34D11221
    35D11221
    -

    Il apparaît que les données sont dans un format long et tidy (voir le chapitre sur tidyr pour une présentation du concept de tidy data), avec une ligne par individu et par pas de temps. Il apparait également que les données sont stockées sous formes de vecteurs labellisés (voir le chapitre dédié aux vecteurs labellisés). Nous aurons donc besoin de l’extension labelled.

    -
    library(labelled)
    -

    Pour une description des variables, on pourra avoir recours à describe de questionr.

    -
    library(questionr)
    -describe(care_trajectories, freq.n.max = 10)
    -
    [49365 obs. x 8 variables] tbl_df tbl data.frame
    +#tckkcwlbeu .gt_from_md > :last-child {
    +  margin-bottom: 0;
    +}
     
    -$id: patient identifier
    -integer: 3 3 3 3 3 3 9 9 13 13 ...
    -min: 3 - max: 9998 - NAs: 0 (0%) - 2929 unique values
    +#tckkcwlbeu .gt_row {
    +  padding-top: 8px;
    +  padding-bottom: 8px;
    +  padding-left: 5px;
    +  padding-right: 5px;
    +  margin: 10px;
    +  border-top-style: solid;
    +  border-top-width: 1px;
    +  border-top-color: #D3D3D3;
    +  border-left-style: none;
    +  border-left-width: 1px;
    +  border-left-color: #D3D3D3;
    +  border-right-style: none;
    +  border-right-width: 1px;
    +  border-right-color: #D3D3D3;
    +  vertical-align: middle;
    +  overflow-x: hidden;
    +}
     
    -$month: month(s) since diagnosis
    -numeric: 0 1 2 3 4 5 0 1 0 1 ...
    -min: 0 - max: 50 - NAs: 0 (0%) - 51 unique values
    +#tckkcwlbeu .gt_stub {
    +  color: #333333;
    +  background-color: #FFFFFF;
    +  font-size: 100%;
    +  font-weight: initial;
    +  text-transform: inherit;
    +  border-right-style: solid;
    +  border-right-width: 2px;
    +  border-right-color: #D3D3D3;
    +  padding-left: 12px;
    +}
     
    -$care_status: care status
    -labelled character: "D" "D" "D" "D" "D" "D" "D" "D" "D" "D" ...
    -NAs: 0 (0%) - 4 unique values
    -4 value labels: [D] diagnosed, but not in care [C] in care, but not on treatment [T] on treatment, but infection not suppressed [S] on treatment and suppressed infection
    +#tckkcwlbeu .gt_summary_row {
    +  color: #333333;
    +  background-color: #FFFFFF;
    +  text-transform: inherit;
    +  padding-top: 8px;
    +  padding-bottom: 8px;
    +  padding-left: 5px;
    +  padding-right: 5px;
    +}
     
    -                                                   n     %
    -[D] diagnosed, but not in care                 25374  51.4
    -[C] in care, but not on treatment               5886  11.9
    -[T] on treatment, but infection not suppressed  4596   9.3
    -[S] on treatment and suppressed infection      13509  27.4
    -Total                                          49365 100.0
    +#tckkcwlbeu .gt_first_summary_row {
    +  padding-top: 8px;
    +  padding-bottom: 8px;
    +  padding-left: 5px;
    +  padding-right: 5px;
    +  border-top-style: solid;
    +  border-top-width: 2px;
    +  border-top-color: #D3D3D3;
    +}
     
    -$sex: sex
    -labelled double: 1 1 1 1 1 1 1 1 0 0 ...
    -min: 0 - max: 1 - NAs: 0 (0%) - 2 unique values
    -2 value labels: [0] male [1] female
    +#tckkcwlbeu .gt_grand_summary_row {
    +  color: #333333;
    +  background-color: #FFFFFF;
    +  text-transform: inherit;
    +  padding-top: 8px;
    +  padding-bottom: 8px;
    +  padding-left: 5px;
    +  padding-right: 5px;
    +}
     
    -               n   %
    -[0] male   17781  36
    -[1] female 31584  64
    -Total      49365 100
    +#tckkcwlbeu .gt_first_grand_summary_row {
    +  padding-top: 8px;
    +  padding-bottom: 8px;
    +  padding-left: 5px;
    +  padding-right: 5px;
    +  border-top-style: double;
    +  border-top-width: 6px;
    +  border-top-color: #D3D3D3;
    +}
     
    -$age: age group
    -labelled double: 1 1 1 1 1 1 2 2 2 2 ...
    -min: 1 - max: 3 - NAs: 0 (0%) - 3 unique values
    -3 value labels: [1] 16-29 [2] 30-59 [3] 60+
    +#tckkcwlbeu .gt_striped {
    +  background-color: rgba(128, 128, 128, 0.05);
    +}
     
    -              n     %
    -[1] 16-29 16911  34.3
    -[2] 30-59 29365  59.5
    -[3] 60+    3089   6.3
    -Total     49365 100.0
    +#tckkcwlbeu .gt_table_body {
    +  border-top-style: solid;
    +  border-top-width: 2px;
    +  border-top-color: #D3D3D3;
    +  border-bottom-style: solid;
    +  border-bottom-width: 2px;
    +  border-bottom-color: #D3D3D3;
    +}
     
    -$education: education level
    -labelled double: 2 2 2 2 2 2 3 3 2 2 ...
    -min: 1 - max: 3 - NAs: 0 (0%) - 3 unique values
    -3 value labels: [1] primary [2] secondary [3] higher
    +#tckkcwlbeu .gt_footnotes {
    +  color: #333333;
    +  background-color: #FFFFFF;
    +  border-bottom-style: none;
    +  border-bottom-width: 2px;
    +  border-bottom-color: #D3D3D3;
    +  border-left-style: none;
    +  border-left-width: 2px;
    +  border-left-color: #D3D3D3;
    +  border-right-style: none;
    +  border-right-width: 2px;
    +  border-right-color: #D3D3D3;
    +}
     
    -                  n     %
    -[1] primary   10417  21.1
    -[2] secondary 19024  38.5
    -[3] higher    19924  40.4
    -Total         49365 100.0
    +#tckkcwlbeu .gt_footnote {
    +  margin: 0px;
    +  font-size: 90%;
    +  padding: 4px;
    +}
     
    -$wealth: wealth group (assets score)
    -labelled double: 2 2 2 2 2 2 2 2 1 1 ...
    -min: 1 - max: 3 - NAs: 0 (0%) - 3 unique values
    -3 value labels: [1] low [2] middle [3] high
    +#tckkcwlbeu .gt_sourcenotes {
    +  color: #333333;
    +  background-color: #FFFFFF;
    +  border-bottom-style: none;
    +  border-bottom-width: 2px;
    +  border-bottom-color: #D3D3D3;
    +  border-left-style: none;
    +  border-left-width: 2px;
    +  border-left-color: #D3D3D3;
    +  border-right-style: none;
    +  border-right-width: 2px;
    +  border-right-color: #D3D3D3;
    +}
     
    -               n     %
    -[1] low    15432  31.3
    -[2] middle 20769  42.1
    -[3] high   13164  26.7
    -Total      49365 100.0
    +#tckkcwlbeu .gt_sourcenote {
    +  font-size: 90%;
    +  padding: 4px;
    +}
     
    -$distance_clinic: distance to nearest clinic
    -labelled double: 1 1 1 1 1 1 2 2 2 2 ...
    -min: 1 - max: 2 - NAs: 0 (0%) - 2 unique values
    -2 value labels: [1] less than 10 km [2] 10 km or more
    +#tckkcwlbeu .gt_left {
    +  text-align: left;
    +}
     
    -                        n     %
    -[1] less than 10 km 26804  54.3
    -[2] 10 km or more   22561  45.7
    -Total               49365 100.0
    -

    Nous pouvons également utiliser look_for de labelled.

    -
    care_trajectories %>% look_for()
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +#tckkcwlbeu .gt_center { + text-align: center; +} + +#tckkcwlbeu .gt_right { + text-align: right; + font-variant-numeric: tabular-nums; +} + +#tckkcwlbeu .gt_font_normal { + font-weight: normal; +} + +#tckkcwlbeu .gt_font_bold { + font-weight: bold; +} + +#tckkcwlbeu .gt_font_italic { + font-style: italic; +} + +#tckkcwlbeu .gt_super { + font-size: 65%; +} + +#tckkcwlbeu .gt_footnote_marks { + font-style: italic; + font-weight: normal; + font-size: 65%; +} + +
    posvariablelabelcol_typelevelsvalue_labels
    1idpatient identifierintNULLNULL
    2monthmonth(s) since diagnosisdblNULLNULL
    3care_statuscare statuschr+lblNULLD, C, T, S
    + + + + + + + + - - - - - - - + + + - - - - - - - + + + - - - - - - - + + + - - - - - - - + + + + +
    Caractéristique +N = 2001 +
    AgeMéd. : 47 [38 - 57]
    4sexsexdbl+lblNULL0, 1
    Manquant11
    5ageage groupdbl+lblNULL1, 2, 3
    Marker Level (ng/mL)Moy. : 0,92 (0,86)
    6educationeducation leveldbl+lblNULL1, 2, 3
    Manquant10
    7wealthwealth group (assets score)dbl+lblNULL1, 2, 3
    +

    + 1 + + + Méd. : Médiane [EI]; Moy. : Moyenne (ET) +

    +
    +
    +

    Pour les variables continues, il est également possible d’indiquer le nom d’une fonction personnalisée qui prends un vecteur et renvoie une valeur résumé. Par exemple, pour afficher la moyenne des carrés :

    +
    moy_carres <- function(x) {
    +  mean(x^2, na.rm = TRUE)
    +}
    +trial %>%
    +  tbl_summary(
    +    include = marker,
    +    statistic = ~"MC : {moy_carres}"
    +  )
    +
    + + + + + + + + + + - - - - - - - + + + + + +
    Caractéristique +N = 2001 +
    Marker Level (ng/mL)MC : 1,57
    8distance_clinicdistance to nearest clinicdbl+lblNULL1, 2
    Manquant10
    +

    + 1 + + + MC : moy_carres +

    +
    -

    Dans cette étude, on a suivi des patients à partir du moment où ils ont été diagnostiqués pour une pathologie grave et chronique et on a suivi leurs parcours de soins chaque mois à partir du diagnostic. La variable status contient le statut dans les soins de chaque individu pour chaque mois de suivi :

    -
      -
    • -D : s’il n’est pas actuellement suivi dans une clinique, soit que la personne n’est pas encore entrée en clinique après le diagnostic, soit qu’elle a quitté la clinique et qu’elle est donc sortie des soins ;
    • -
    • -C : indique que le patient est entré en soins (il est suivi dans une clinique) mais il n’a pas encore commencé le traitement, ou bien il a arrêté le traitement mais est toujours suivi en clinique ;
    • -
    • -T : la personne est sous traitement mais l’infections n’est pas supprimée ou contrôlée, soit que le traitement n’a pas encore eu le temps de faire effet, soit qu’il n’est plus efficace ;
    • -
    • -S : la personne est suivie en clinique, sous traitement et son infection est supprimée / contrôlée, indiquant que le traitement est efficace et produit son effet. Cette étape ultime du parcours de soins est celle dans laquelle on souhaite maintenir les individus le plus longtemps possible.
    • -
    -

    Il est important de noter que nous avons ici des statuts hiérarchiquement ordonnés (D < C < T < S), ce qui aura son importance pour les choix méthodologiques que nous aurons à faire.

    -

    Nous disposons également d’autres variables (âge, sexe, niveau d’éducation…) qui sont ici dépendantes du temps, c’est-à-dire que le cas échéant, elles peuvent varier d’un mois à l’autre en cas de changement.

    -

    Avant de démarrer les analyses, françisons certaines de ces variables.

    -
    var_label(care_trajectories$sex) <- "Sexe"
    -val_labels(care_trajectories$sex) <- c(homme = 0, femme = 1)
    -var_label(care_trajectories$age) <- "Âge"
    -var_label(care_trajectories$education) <- "Education"
    -val_labels(care_trajectories$education) <- c(
    -  primaire = 1,
    -  secondaire = 2,
    -  supérieur = 3
    -)
    -

    Le fichier contient 49365 lignes, ce qui ne veut pas dire qu’il y a ce nombre d’invidus suivis au cours du temps, puisque plusieurs lignes correspondent à un même individu. On peut obtenir le nombre d’individus différents assez facilement avec la commande :

    -
    length(unique(care_trajectories$id))
    -
    [1] 2929
    -

    Précision : dans ce fichier, tous les individus ne sont pas suivis pendant la même durée, car ils n’ont pas tous été diagnostiqués au même moment. Cependant, il n’y a pas de trous dans le suivi (ce qui serait le cas si certains individus sortaient de l’observation pendant quelques mois puis re-rentraient dans la cohorte de suivi).

    -

    Avant d’aller plus avant, il nous faut avoir une idée du nombre d’individus observé au cours du temps, ce que l’on peut obtenir avec :

    -
    ggplot(care_trajectories) +
    -  aes(x = month) +
    -  geom_bar()
    -

    -

    Améliorons ce graphique en y ajoutant la distribution selon le statut dans les soins chaque mois, en améliorant l’axe du temps (tous les 6 mois est plus facile à lire) et en y ajoutant un titre et des étiquettes appropriées. Afin de disposer d’une palette de couleurs à fort contraste, nous allons utiliser l’extension viridis. Enfin, nous allons utiliser une petite astuce pour indiquer les effectifs sur l’axe horizontal. Au passage, nous allons également franciser les étiquettes de la variable care_status avec val_labels (notez aussi le recours à to_factor dans aes qui nous permet de transformer à la volée la variable en facteur, format attendu par ggplot2 pour les variables catégorielles). On se référera au chapitre dédié à ggplot2 pour plus de détails sur les différentes fonctions de cette extension graphique.

    -
    library(viridis)
    -n <- care_trajectories %>%
    -  filter(month %in% (0:8*6)) %>%
    -  group_by(month) %>%
    -  count() %>%
    -  pluck("n")
    -etiquettes <- paste0("M", 0:8*6, "\n(n=", n, ")")
    -val_labels(care_trajectories$care_status) <- c(
    -  "diagnostiqué, mais pas suivi" = "D",
    -  "suivi, mais pas sous traitement" = "C",
    -  "sous traitement, mais infection non contrôlée" = "T",
    -  "sous traitement et infection contrôlée" = "S"
    -)
    -ggplot(care_trajectories) +
    -  aes(x = month, fill = to_factor(care_status)) +
    -  geom_bar(color = "gray50", width = 1) +
    -  scale_x_continuous(breaks = 0:8*6, labels = etiquettes) +
    -  ggtitle("Distribution du statut dans les soins chaque mois") +
    -  xlab("") + ylab("") +
    -  theme_light() +
    -  theme(legend.position = "bottom") +
    -  labs(fill = "Statut dans les soins") + 
    -  scale_fill_viridis(discrete = TRUE, direction = -1) +
    -  guides(fill = guide_legend(nrow = 2))
    -

    -

    On s’aperçoit qu’une majorité des personnes suivies ne l’ont été que peu de temps, avec une décroissance rapide des effectifs.

    -
    -

    Évolution de la cascade de soins au cours du temps

    -

    On nomme communément cascade de soins la proportion d’individus dans chaque statut à un moment du temps donné. On peut facilement obtenir celle-ci à partir du code du graphique précédent en ajoutant l’option position = fill à geom_bar.

    -
    ggplot(care_trajectories) +
    -  aes(x = month, fill = to_factor(care_status)) +
    -  geom_bar(color = "gray50", width = 1, position = "fill") +
    -  scale_x_continuous(breaks = 0:8*6, labels = etiquettes) +
    -  scale_y_continuous(labels = scales::percent) +
    -  ggtitle("Cascade des soins observée, selon le temps depuis le diagnostic") +
    -  xlab("") + ylab("") +
    -  theme_light() +
    -  theme(legend.position = "bottom") +
    -  labs(fill = "Statut dans les soins") + 
    -  scale_fill_viridis(discrete = TRUE, direction = -1) +
    -  guides(fill = guide_legend(nrow = 2))
    -

    -

    Les effectifs sont très faibles au-delà de 36 mois et il serait préférable de couper la cascade au-delà de M36, ce que l’on peut faire aisément ne gardant que les lignes correspondantes de care_trajectories.

    -
    casc_obs <- ggplot(care_trajectories %>% filter(month <= 36)) +
    -  aes(x = month, fill = to_factor(care_status)) +
    -  geom_bar(color = "gray50", width = 1, position = "fill") +
    -  scale_x_continuous(breaks = 0:8*6, labels = etiquettes) +
    -  scale_y_continuous(labels = scales::percent) +
    -  ggtitle("Cascade des soins observée, selon le temps depuis le diagnostic") +
    -  xlab("") + ylab("") +
    -  theme_light() +
    -  theme(legend.position = "bottom") +
    -  labs(fill = "Statut dans les soins") + 
    -  scale_fill_viridis(discrete = TRUE, direction = -1) +
    -  guides(fill = guide_legend(nrow = 2))
    -casc_obs
    -

    -
    -
    -

    Analyse de survie classique

    -

    L’analyse de survie constitue l’approche statistique la plus fréquente pour appréhender des données biographiques. Dans sa version classique, l’analyse de survie modélise le temps mis pour vivre un événement particulier à partir d’un événement origine.

    -

    Dans notre exemple, l’événement d’origine commun à tous les individus est le diagnostic VIH. Les personnes suivies peuvent vivre trois événements principaux :

    -
      -
    • entrée en soins (passage de D à C) ;
    • -
    • initiation du traitement (passage de C à T) ;
    • -
    • contrôle de l’infection (passage de T à S).
    • -
    -

    En toute rigueur, il faudrait également considérer les transitions inverses (sortie de soins, arrêt du traitement, échec virologique). De même, il est possible que certains aient vécus plusieurs transitions successives (entrée en soin, initiation du traitement, sortie de soins et arrêt du traitement, nouvelle entrée en soins…).

    -

    Pour le moment, contentons-nous de regarder la première entrée en soins, la première initiation du traitement et la première atteinte du contrôle de l’infection et de calculer la date de ces trois événements, dans un fichier ind contenant une ligne par individu.

    -
    ind <- care_trajectories %>% filter(month == 0)
    -ind$diagnostic <- 0
    -ind <- ind %>%
    -  left_join(
    -    care_trajectories %>%
    -      filter(care_status %in% c("C", "T", "S")) %>%
    -      group_by(id) %>%
    -      dplyr::summarise(entree_soins = min(month)),
    -    by = "id"
    -  ) %>%
    -  left_join(
    -    care_trajectories %>%
    -      filter(care_status %in% c("T", "S")) %>%
    -      group_by(id) %>%
    -      dplyr::summarise(initiation_tt = min(month)),
    -    by = "id"
    -  ) %>%
    -  left_join(
    -    care_trajectories %>%
    -      filter(care_status == "S") %>%
    -      group_by(id) %>%
    -      dplyr::summarise(controle = min(month)),
    -    by = "id"
    -  )
    -

    Il nous faut également la durée de suivi par individu.

    -
    ind <- ind %>%
    -  left_join(
    -    care_trajectories %>%
    -      group_by(id) %>%
    -      dplyr::summarise(suivi = max(month)),
    -    by = "id"
    -  )
    -

    Pour faciliter la suite des analyses, nous allons nous créer une petite fonction qui, en fonction de la date d’origine et la date d’événement retenues, calculera la courbe de Kaplan-Meier correspondante (voir le chapitre sur l’analyse de suivie pour le calcul de la courbe de survie et celui dédié à l’écriture de fonctions).

    -
    km <- function(date_origine, date_evenement, nom) {
    -  library(survival)
    -  
    -  # ne garder que les observations avec date d'origine
    -  tmp <- ind[!is.na(ind[[date_origine]]), ]
    -  
    -  # pre-remplir la variable time avec duree de suivi
    -  # depuis date d'origine
    -  tmp$time <- tmp$suivi - tmp[[date_origine]]
    -  # et considérer que l'événement n'a pas été vécu
    -  tmp$event <- FALSE
    -  
    -  # si date_evement documentée, événement vécu
    -  tmp[!is.na(tmp[[date_evenement]]), ]$event <- TRUE
    -  tmp[tmp$event == TRUE, ]$time <- 
    -    tmp[tmp$event == TRUE, ][[date_evenement]] -
    -    tmp[tmp$event == TRUE, ][[date_origine]]
    -  
    -  kaplan <- survfit(Surv(time, event) ~ 1, data = tmp)
    -  res <- broom::tidy(kaplan, conf.int = TRUE)
    -  res$nom <- nom
    -  res
    -}
    -

    Une première approche consiste à regarder la survenue de chacun des trois événements mentions plus haut en fonction du temps depuis le diagnostic.

    -
    depuis_diag <- dplyr::bind_rows(
    -  km("diagnostic", "entree_soins", "entrée en soins"),
    -  km("diagnostic", "initiation_tt", "initiation du traitement"),
    -  km("diagnostic", "controle", "contrôle de l'infection")
    -)
    -
    -g_diag <- ggplot(data = depuis_diag) +
    -  aes(x = time, y = 1 - estimate, 
    -      color = as_factor(nom), fill = as_factor(nom),
    -      ymin = 1 - conf.high, ymax = 1 - conf.low) +
    -  geom_ribbon(alpha = .25, mapping = aes(color = NULL)) +
    -  geom_line(size = 1) +
    -  theme_classic() +
    -  theme(
    -    legend.position = "bottom",
    -    panel.grid.major.y = element_line(colour = "grey")
    -  ) +
    -  scale_x_continuous(breaks = 0:6*6, limits = c(0, 36)) +
    -  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
    -  xlab("mois depuis le diagnostic") + 
    -  ylab("") + labs(color = "", fill = "")
    -g_diag
    -

    -

    Ce graphique ressemble à la cascade des soins observée que nous avions calculée plus haut, à quelques différences près :

    -
      -
    • avec la méthode de Kaplan-Meier, la censure à droite, i.e. le fait que tous les individus n’ont pas la même durée de suivi, est correctement prise en compte et la courbe est corrigée en conséquence ;
    • -
    • par contre, les transitions inverses ne sont pas considérées : lorsqu’un individu a atteint une étape, on ne regarde pas s’il en ressort.
    • -
    -

    Une autre manière d’appréhender nos trajectoires est de considérer le temps requis pour atteindre une étape une fois la précédente étape atteinte. Ce qu’on obtient facilement en adaptant légèrement notre code précédent.

    -
    depuis_prec <- dplyr::bind_rows(
    -  km("diagnostic", "entree_soins", "entrée en soins"),
    -  km("entree_soins", "initiation_tt", "initiation du traitement"),
    -  km("initiation_tt", "controle", "contrôle de l'infection")
    -)
    -
    -g_prec <- ggplot(data = depuis_prec) +
    -  aes(x = time, y = 1 - estimate, 
    -      color = as_factor(nom), fill = as_factor(nom),
    -      ymin = 1 - conf.high, ymax = 1 - conf.low) +
    -  geom_ribbon(alpha = .25, mapping = aes(color = NULL)) +
    -  geom_line(size = 1) +
    -  theme_classic() +
    -  theme(
    -    legend.position = "bottom",
    -    panel.grid.major.y = element_line(colour = "grey")
    -  ) +
    -  scale_x_continuous(breaks = 0:6*6, limits = c(0, 36)) +
    -  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
    -  xlab("mois depuis l'étape précédente") + 
    -  ylab("") + labs(color = "", fill = "")
    -
    -g_prec
    -

    -

    Attention : cette représentation graphique peut éventuellement prêter à confusion dans sa lecture car l’échelle de temps n’est pas tout à fait la même pour chaque courbe, dans la mesure où la date d’origine diffère pour chacune. Dès lors, il peut être plus pertinent de présenter chaque courbe l’une à côté de l’autre.

    -
    g_prec + facet_grid(~ as_factor(nom))
    -

    -

    Ici, on voit plus clairement que l’étape où il y a le plus de perdition est celle de l’entrée en soins, moins des trois quarts des personnes diagnostiquées étant venu en clinique au mois une fois dans les trois ans suivant le diagnostic. Par contre, l’initiation du traitement une fois entré en clinique et le contrôle de l’infection une fois le traitement initié sont beaucoup plus rapide.

    -

    Pour aller plus loin avec les outils de l’analyse de survie classique, il serait possible de faire des analyses bivariées (Kaplan-Meier) ou multivariées (Cox) pour chacune de ces étapes. Cependant, il serait plus intéressant de trouver une approache statistique permettant de considérer dans un même modèle l’ensemble des transitions possibles.

    +

    Pour une variable catégorielle, les statistiques possibles sont {n} le nombre d’observations, {N} le nombre total d’observations, et {p} le pourcentage correspondant. Utilisez all_categorical pour sélectionner toutes les variables catégorielles.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(stage, response),
    +    statistic = all_categorical() ~ "{p} % ({n}/{N})"
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    T Stage
    T126 % (53/200)
    T227 % (54/200)
    T322 % (43/200)
    T425 % (50/200)
    Tumor Response32 % (61/193)
    Manquant7
    +

    + 1 + + + % % (n/N) +

    +
    -
    -

    Une première analyse de séquences sur l’ensemble du fichier

    -

    L’analyse de séquences permet d’appréhender l’ensemble de la trajectoire de soins à travers la succession des états dans lesquels se trouvent les patients observés.

    -

    Nous allons donc réaliser une analyse de séquences (voir le chapitre dédié) sur l’ensemble de notre fichier. Pour cela, il va falloir préalable que nous transformions nos donnée actuellement dans un format long en un tableau large, c’est-à-dire avec une ligne par individu et une variable différentes par pas de temps. On peut réaliser cela facilement avec pivot_wider de tidyr (voir le chapitre dédié à tidyr).

    -
    library(tidyr)
    -large <- care_trajectories %>%
    -  dplyr::select(id, m = month, care_status) %>%
    -  pivot_wider(names_from = m, values_from = care_status, names_prefix = "m") 
    -head(large)
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    idm0m1m2m3m4m5m6m7m8m9m10m11m12m13m14m15m16m17m18m19m20m21m22m23m24m25m26m27m28m29m30m31m32m33m34m35m36m37m38m39m40m41m42m43m44m45m46m47m48m49m50
    3DDDDDDNANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANA
    9DDNANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANA
    13DDDDDDDDNANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANA
    15DDDDTTTCDDDDDDDCCCCCCCCCTTTTTTTTTNANANANANANANANANANANANANANANANANANA
    18DDSSSSSSSSSSSSSSSSSNANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANA
    21DDDDDDNANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANA
    -

    On utilise seqdef de TraMineR pour créer nos séquences, avec les arguments alphabet pour forcer l’ordre de l’alphabet, states pour spécifier des étiquettes courtes à chaque état et cpal pour indiquer le code couleur de chaque état (et être raccord avec nos graphiques précédents).

    -
    library(TraMineR, quietly = TRUE)
    -seq_all <- seqdef(
    -  large %>% dplyr::select(m0:m50),
    -  id = large$id,
    -  alphabet = c("D", "C", "T", "S"),
    -  states = c("diagnostiqué", "en soins", "sous traitement", "inf. contrôlée"),
    -  cpal = viridis(4, direction = -1)
    -)
    -
     [>] found missing values ('NA') in sequence data
    -
     [>] preparing 2929 sequences
    -
     [>] coding void elements with '%' and missing values with '*'
    -
     [>] state coding:
    -
           [alphabet]  [label]         [long label] 
    -
         1  D           diagnostiqué    diagnostiqué
    -
         2  C           en soins        en soins
    -
         3  T           sous traitement sous traitement
    -
         4  S           inf. contrôlée  inf. contrôlée
    -
     [>] 2929 sequences in the data set
    -
     [>] min/max sequence length: 1/51
    -

    On peut retrouver la cascade de soins avec seqdplot.

    -
    seqdplot(seq_all, legend.prop = .25)
    -

    -

    Nous allons maintenant calculer une matrice des distances entre individus par optimal matching. Dans le cas présent, nos différents status sont hiérarchiquement ordonnés. Il n’est donc pas raisonnable de penser que les coûts sont constants entre les différents statuts, puisqu’en un sens, passer directement de D à T peut être considéré comme être passé d’abord de D à C puis de C à D. Nous allons donc faire une matrice de coûts hiérarchisée. seqcost nous permets de produire une matrice de coûts constants, que nous allons ensuite modifier manuellement. Pour le coût indel, le plus simple est de considérer la moitié du coût de substitution maximum.

    -
    couts <- seqcost(seq_all, method = "CONSTANT")
    -
     [>] creating 4x4 substitution-cost matrix using 2 as constant value
    -
    couts
    -
    $indel
    -[1] 1
    +

    Il est possible, pour une variable catégorielle, de trier les modalités de la plus fréquente à la moins fréquente avec le paramètre sort.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(stage, response),
    +    sort = all_categorical() ~ "frequency"
    +  )
    +
    + - - - - - - - - - - - - - + + + + - - - - - + - - - - - - + + - - - - - - + + - - - - - - + + - - - - - - + + - - - - - - + + + + + + + + +
    Caractéristique - typo_pam - Total
    62385410
    Caractéristique +N = 2001 +
    typo_cahT Stage
    15224039565T254 (27%)
    2033303336T153 (26%)
    312035913276T450 (25%)
    42305112140T343 (22%)
    Total5462103941671 317Tumor Response61 (32%)
    Manquant7
    +

    + 1 + + + n (%) +

    +
    -

    Regardons les tapis de séquence des deux typologies.

    -
    large_m18$ordre_cmd <- cmdscale(as.dist(dist_m18), k = 1)
    -seqIplot(seq_m18, group = large_m18$typo_cah, sortv = large_m18$ordre_cmd)
    -

    -
    seqIplot(seq_m18, group = large_m18$typo_pam, sortv = large_m18$ordre_cmd)
    -

    -

    Comme on le voit les deux typologies obtenues sont très proches. Suivant le cas, à vous de choisir celle qui semble la plus pertinente d’un point de vue sociologique. Il existe également divers indicateurs statisques pour mesurer la qualité d’une partition (voir le manuel de la librairie WeightedCluster de Matthias Studer). Ils peuvent être calculés avec la fonction wcClusterQuality. Comparons les deux typologies obtenues.

    -
    tab <- tibble(
    -  stat = names(wcClusterQuality(dist_m18, large_m18$typo_cah)$stats),
    -  cah = wcClusterQuality(dist_m18, large_m18$typo_cah)$stats,
    -  pam = wcClusterQuality(dist_m18, large_m18$typo_pam)$stats
    -)
    -gt::gt(tab) %>% gt::fmt_number(2:3, decimals = 2, sep_mark = " ")
    -
    +

    Lorsqu’une variable by est définie, on peut utiliser percent pour indiquer le type de pourcentages : en ligne avec "row", en colonne avec "column" et "cell" pour les pourcentages totaux.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(stage, response),
    +    by = grade,
    +    statistic = all_categorical() ~ "{p} % ({n}/{N})",
    +    percent = "row"
    +  ) %>%
    +  add_overall(last = TRUE)
    +
    - - - + + + + + - - - - - - - - - - - - - - - - - - + + + + + - - - + + + + + - - - + + + + + - - - + + + + + - - - + + + + + - - - + + + + + - - - + + + + + + + +
    statcahpamCaractéristique +I, N = 681 + +II, N = 681 + +III, N = 641 + +Total, N = 2001 +
    PBC0.700.71
    HG0.930.95
    HGSD0.930.95
    ASW0.530.57T Stage
    ASWw0.530.57T132 % (17/53)43 % (23/53)25 % (13/53)100 % (53/53)
    CH938.971 023.12T233 % (18/54)31 % (17/54)35 % (19/54)100 % (54/54)
    R20.680.70T342 % (18/43)26 % (11/43)33 % (14/43)100 % (43/43)
    CHsq3 351.213 980.60T430 % (15/50)34 % (17/50)36 % (18/50)100 % (50/50)
    R2sq0.880.90Tumor Response34 % (21/61)31 % (19/61)34 % (21/61)100 % (61/61)
    HC0.030.02Manquant1517
    +

    + 1 + + + % % (n/N) +

    +
    -

    Selon ces indicateurs calculés, l’approche PAM obtiendrait une partition légèrement de meilleure qualité que celle obtenuepar CAH.

    -

    L’extension WeightedCluster fournie aussi une fonction wcSilhouetteObs permettant de mesurer la silhouette de chaque séquence. Plus cette métrique est élevée et proche de 1, plus la séquence est proche du centre de classe et caractéristique de la classe. On peut utiliser cette métrique pour classer les séquences sur le tapis de séquences.

    -
    large_m18$sil <- wcSilhouetteObs(dist_m18, large_m18$typo_pam)
    -seqIplot(seq_m18, group = large_m18$typo_pam, sortv = large_m18$sil)
    -

    -

    Nous voyons émerger quatre groupes distincts :

    -
      -
    • les rapides, qui entrent en soins et initient le traitement dès les premiers mois suivant le diagnostic ;
    • -
    • les lents, qui entrent en soins et initient le traitement plus tardivement, après M6 ;
    • -
    • les inaboutis, qui entrent en soins mais n’initient pas le traitement ;
    • -
    • les hors soins, qui ne sont pas entrés en soins où n’y sont pas restés.
    • -
    -

    Le graphique obtenu avec seqIplot affiche visuellement chaque groupe avec la même hauteur, pouvant laisser accroire que chaque groupe a le même poids dans l’échantillon. Pour produire une représentation graphique des tapis de séquences plus correcte, où chaque la hauteur de chaque groupe correspondrait à son poids dans l’échantillon, nous allons passer par ggplot2. Un tapis de séquences peut-être vu comme un raster et dès lors représenté avec geom_raster. Pour travailler avec ggplot2, nos données doivent être au format tidy, c’est-à-dire avec une ligne par point d’observation, soit une ligne par personne et par jour. Nous allons donc repartir du fichier care_trajectories. Le mois d’observation indiquera la position en abscisse. Quant à la position en ordonnée, il faudra que nous la calculions, séparément pour chaque groupe, afin d’éviter des lignes vides dans le graphique.

    -
    # nommer les groupes
    -large_m18$groupe <- factor(
    -  large_m18$typo_pam,
    -  c(85, 23, 410, 6),
    -  c("Rapides", "Lents", "Inaboutis", "Hors soins")
    -)
    -
    -# calculer le rang des individus dans chaque groupe
    -large_m18 <- large_m18 %>%
    -  group_by(groupe) %>%
    -  arrange(ordre_cmd) %>%
    -  mutate(rang_cmd = rank(ordre_cmd, ties.method = "first"))
    -
    -# créer un fichier long
    -long_m18 <- care_trajectories %>%
    -  filter(id %in% large_m18$id & month <= 18) %>%
    -  left_join(
    -    large_m18 %>% dplyr::select(id, groupe, rang_cmd),
    -    by = "id"
    -  )
    -
    -long_m18$care_statusF <- to_factor(long_m18$care_status)
    -
    -# calculer les effectifs par groupe
    -tmp <- large_m18 %>% 
    -  dplyr::count(groupe) %>%
    -  mutate(groupe_n = paste0(groupe, "\n(n=", n, ")"))
    -
    -long_m18 <- long_m18 %>%
    -  left_join(
    -    tmp %>% dplyr::select(groupe, groupe_n),
    -    by = "groupe"
    -  )
    -
    # graphique des tapis de séquences
    -ggplot(long_m18) +
    -  aes(x = month, y = rang_cmd, fill = care_statusF) +
    -  geom_raster() +
    -  facet_grid(groupe_n ~ ., space = "free", scales = "free") +
    -  scale_x_continuous(breaks = 0:6*3, labels = paste0("M", 0:6*3)) +
    -  scale_y_continuous(breaks = 0:5*100, minor_breaks = NULL) +
    -  xlab("") + ylab("") +
    -  theme_light() +
    -  theme(legend.position = "bottom") +
    -  labs(fill = "Statut dans les soins") + 
    -  scale_fill_viridis(discrete = TRUE, direction = -1) +
    -  guides(fill = guide_legend(nrow = 2))
    -

    -
    -
    -

    Facteurs associés à l’appartenance à chaque groupe

    -

    Une fois les différents groupes de trajectoires identifiés, il est courant de vouloir regarder si certains facteurs influencent l’appartenance à un groupe plutôt qu’un autre. Dans nos données d’exemple, nous nous intéresserons aux variables suivantes : sexe, groupe d’âges et niveau d’éducation.

    -

    Ces différentes variables sont renseignées pour chaque mois dans le tableau de données care_trajectories, en tenant compte des éventuels changements au cours du temps. Ici, notre analyse est menée au niveau individuel. Nous allons donc récupérer la valeur de ces différentes variables au moment du diagnostic, à savoir à M0.

    -
    large_m18 <- large_m18 %>%
    -  left_join(
    -    care_trajectories %>%
    -      dplyr::filter(month == 0) %>%
    -      dplyr::select(id, sex, age, education),
    -    by = "id"
    -  )
    -

    La fonction tbl_summary de l’extension gtsummary permets de produire aisément une série de tableaux croisés. À noter le recours à add_p() pour ajouter les p-valeurs au test du Chi².1 La fonction add_overall permets d’ajouter une colonne avec l’ensemble de l’échantillon. Notez que nous avons utiliser unlabelled de l’extension labelled pour convertir en amont les vecteurs labellisés en facteurs.

    -
    library(gtsummary)
    -large_m18 %>%
    -  ungroup() %>%
    -  unlabelled() %>% 
    -  tbl_summary(by = "groupe", include = c("groupe", "sex", "age", "education")) %>%
    -  add_p() %>%
    -  add_overall(last = TRUE)
    -
    +

    Pour toutes les variables (catégorielles et continues), les statistiques suivantes sont également disponibles : {N_obs} le nombre total d’observations, {N_miss} le nombre d’observations manquantes (NA), {N_nonmiss} le nombre d’observations non manquantes, {p_miss} le pourcentage d’observations manquantes (i.e. N_miss / N_obs) et {p_nonmiss} le pourcentage d’observations non manquantes (i.e. N_nonmiss / N_obs).

    +
    +
    +

    Affichage du nom des statistiques (add_stat_label)

    +

    Lorsque l’on affiche de multiples statistiques, la liste des statistiques est regroupée dans une note de tableau qui peut vite devenir un peu confuse.

    +
    tbl <- trial %>%
    +  tbl_summary(
    +    include = c(age, marker, grade),
    +    by = trt,
    +    statistic = list(
    +      age ~ "{median} [{p25} - {p75}]",
    +      marker ~ "{mean} ({sd})"
    +    )
    +  )
    +tbl
    +
    + + + + + + + + + + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
    Caractéristique +Drug A, N = 98 +Drug B, N = 102
    Age, Médiane [EI]46 [37 - 59]48 [39 - 56]
    monthF42,79<0,0012,53,1
    Manquant74
    monthF53,82<0,0013,44,3
    Marker Level (ng/mL), Moyenne (ET)1,02 (0,89)0,82 (0,83)
    monthF64,69<0,0014,15,4
    Manquant64
    monthF75,63<0,0014,96,4
    Grade, n (%)
    monthF86,40<0,0015,67,3
    I35 (36%)33 (32%)
    monthF97,02<0,0016,18,1
    II32 (33%)36 (35%)
    monthF107,49<0,0016,58,6
    III31 (32%)33 (32%)
    monthF117,96<0,0016,99,2
    monthF128,42<0,0017,39,8
    monthF139,31<0,0018,010,8
    monthF1410,11<0,0018,611,8
    monthF1510,74<0,0019,212,6
    monthF1611,46<0,0019,813,4
    monthF1711,43<0,0019,713,4
    monthF1811,53<0,0019,813,6
    monthF1911,50<0,0019,713,6
    monthF2011,70<0,0019,714,1
    monthF2111,67<0,0019,614,2
    monthF2212,60<0,00110,315,4
    monthF2312,78<0,00110,415,7
    monthF2412,76<0,00110,415,7
    monthF2513,11<0,00110,616,2
    monthF2613,60<0,00111,016,9
    monthF2714,40<0,00111,617,9
    monthF2815,65<0,00112,519,6
    monthF2914,89<0,00111,918,7
    +
    +
    tbl %>% add_stat_label(location = "column")
    +
    + + + + + + + + + + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + +
    CaractéristiqueStatistique +Drug A, N = 98 +Drug B, N = 102
    AgeMédiane [EI]46 [37 - 59]48 [39 - 56]
    monthF3014,98<0,00111,918,9
    Manquantn74
    monthF3114,02<0,00111,017,9
    Marker Level (ng/mL)Moyenne (ET)1,02 (0,89)0,82 (0,83)
    monthF3214,27<0,00111,018,5
    Manquantn64
    monthF3314,41<0,00111,018,9
    Grade
    monthF3414,73<0,00111,019,7
    In (%)35 (36%)33 (32%)
    monthF3516,17<0,00111,822,1
    IIn (%)32 (33%)36 (35%)
    monthF3616,07<0,00111,422,6
    IIIn (%)31 (32%)33 (32%)
    -

    On peut facilement représenter tout cela graphiquement. On va supprimer les termes seuils, grâce à str_detect de stringr. On notera le recours à fct_inorder de forcats pour conserver l’ordre des termes selon leur ordre d’apparition.

    -
    res <- res[!str_detect(res$term, "Inter"),]
    -res$term <- fct_inorder(res$term)
    -res$term <- fct_recode(res$term,
    -               "femme vs. homme" = "sexFfemme",
    -               "âge : 30-59 vs. 16-29" = "ageF30-59",
    -               "âge : 60+ vs 16-29" = "ageF60+",
    -               "éducation : secondaire vs. primaire" = "educationFsecondaire",
    -               "éducation : supérieure vs. primaire" = "educationFsupérieur",
    -               "M2" = "monthF2",
    -               "M3" = "monthF3",
    -               "M4" = "monthF4",
    -               "M5" = "monthF5",
    -               "M6" = "monthF6",
    -               "M7" = "monthF7",
    -               "M8" = "monthF8",
    -               "M9" = "monthF9",
    -               "M10" = "monthF10",
    -               "M11" = "monthF11",
    -               "M12" = "monthF12",
    -               "M13" = "monthF13",
    -               "M14" = "monthF14",
    -               "M15" = "monthF15",
    -               "M16" = "monthF16",
    -               "M17" = "monthF17",
    -               "M18" = "monthF18",
    -               "M19" = "monthF19",
    -               "M20" = "monthF20",
    -               "M21" = "monthF21",
    -               "M22" = "monthF22",
    -               "M23" = "monthF23",
    -               "M24" = "monthF24",
    -               "M25" = "monthF25",
    -               "M26" = "monthF26",
    -               "M27" = "monthF27",
    -               "M28" = "monthF28",
    -               "M29" = "monthF29",
    -               "M30" = "monthF30",
    -               "M31" = "monthF31",
    -               "M32" = "monthF32",
    -               "M33" = "monthF33",
    -               "M34" = "monthF34",
    -               "M35" = "monthF35",
    -               "M36" = "monthF36")
    -res$variable <- c("sexe", "âge", "âge", "éducation", "éducation", rep("mois", 35))
    -
    -res %>% GGally::ggcoef_plot(y = "term", facet_row = "variable", colour = "variable")
    -

    -

    Sur ce graphique, on visualise bien l’évolution temporelle traduite par les odds ratios associés à chaque mois, ainsi que les effets globaux de nos covariables : les femmes ont une meilleure progression dans la cascade de soins que les hommes, de même que les plus éduqués et les plus âgés.

    -
    -

    Modèle de survie multi-états

    -

    Depuis la fin du XXe siècle, de nombreux développements ont réalisés pour étendre les modèles de survie à des processus multi-états. Ces modèles permettent de considérer une grande variété de processus. Plusieurs implémentations existent dans R3. Ici, nous allons utiliser l’extension msm qui repose sur des modèles de Markov multi-états et peut prendre en compte des co-variables dans le modèle.

    -

    En premier lieu, pour cette extension, ils nous faut disposer des données sous une forme longue, c’est-à-dire avec une ligne par individu et point d’observation dans le temps, ce qui est déjà le cas du fichier care_trajectories. Les différents status possibles doivent également être codés sous la forme de nombres entiers croissants (ici 1 correspondra à D, 2 à C, 3 à T et 4 à S).

    -
    library(msm)
    -care_trajectories$status <- as.integer(to_factor(care_trajectories$care_status))
    -care_trajectories <- care_trajectories %>%
    -  arrange(id, month)
    -

    Par ailleurs, nous n’allons conserver dans l’analyse que les individus avec au moins deux points d’observation, ici ceux observés au moins jusqu’à un mois.

    -
    ct <- care_trajectories %>%
    -  filter(id %in% (care_trajectories %>% filter(month == 1) %>% pluck("id")))
    -

    La fonction statetable.msm permet de calculer le nombre et le type de transitions observées dans les données.

    -
    statetable.msm(status, id, data = ct)
    - - - - - - - - - - - - - - - + +
    +

    Forcer le type de variable (type, value)

    +

    Comme abordé plus haut, gtsummary détermine automatiquement le type de chaque variable. Par défaut, la variabe age est traitée comme variable continue, death comme dichotomique (seule la valeur 1 est affichée) et grade comme variable catégorielle.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(grade, age, death)
    +  )
    +
    + +
    from/to1234
    1219161168467247
    + + + + + + + + - - - - - - + + + - - - - - - + + + - - - - - - + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    Grade
    25014323597210
    I68 (34%)
    3261413489770
    II68 (34%)
    4332304312275
    III64 (32%)
    Age47 (38 – 57)
    Manquant11
    Patient Died112 (56%)
    +

    + 1 + + + n (%); Médiane (EI) +

    +
    -

    Il faut ensuite définir les transitions possibles dans le modèle en faisant une matrice carrée. On indiquera 0 si la transition n’est pas possible, une valeur positive sinon.

    -
    tr <- rbind(
    -  c(0, 1, 0, 0), # de 1 : vers 2
    -  c(1, 0, 1, 0), # de 2 : vers 1 ou vers 3
    -  c(0, 1, 0, 1), # de 3 : vers 2 ou vers 4
    -  c(0, 0, 1, 0)  # de 4 : vers 3
    -)
    -

    Dans notre matrice de transitions, nous n’avons pas défini de transition directe entre le statut 1 et le statut 3, alors que de telles transitions sont pourtant observées dans notre fichier. En fait, nous pouvons considérer qu’une transition de 1 vers 3 correspond en fait à deux transitions successives, de 1 vers 2 puis de 2 vers 3. La fonction msm s’aura identifier d’elle-mêmes ces doubles, voire triples, transitions.

    -

    On peut facilement représenter notre matrice de transition sous forme de schéma à l’aide de l’excellente extension DiagrammeR.

    -
    library(DiagrammeR)
    -mermaid("
    -graph TD
    -1[diagnostiqué, mais pas suivi]
    -2[suivi, mais pas sous traitement]
    -3[sous traitement, mais infection non contrôlée]
    -4[sous traitement et infection contrôlée]
    -
    -1--entrée<br />en soins-->2
    -2--initiation<br />traitement-->3
    -2--sortie<br />de soins-->1
    -3--contrôle<br />infection-->4
    -3--arrêt<br />traitement-->2
    -4--échec<br/>virologique-->3
    -", height = 300)
    -
    -

    Il ne nous reste plus qu’à spécifier notre modèle. L’option obstype = 1 indique à msm que nos données correspondent à des snapshots à certains moments donnés (ici tous les mois) et donc que les transitions d’un état à un autre ont eu lieu entre nos points d’observation. Les types 2 et 3 correspondent à des dates de transition exactes (voir l’aide la fonction pour plus de détails).

    -
    ms_mod <- msm(
    -  status ~ month, subject = id, data = ct, qmatrix = tr, obstype = 1
    -)
    -

    En exécutant cette commande, vous risquez d’obtenir le message d’erreur suivant :

    -
    Error in Ccall.msm(params, do.what = "lik", ...) : numerical overflow in calculating likelihood
    -

    Cela est dû à un problème d’échelle dans l’optimisation du modèle qui génère des nombres plus grands que ce que peux gérer l’ordinateur. Il peut être résolu de la manière suivante. Tout d’abord, on reexécute le modèle avec l’option control = list(trace = TRUE).

    -
    ms_mod <- msm(
    -  status ~ month, subject = id, data = ct, qmatrix = tr, obstype = 1, 
    -  control = list(trace = TRUE)
    -)
    -

    On obtient le message suivant :

    -
    initial  value 74796.800445 
    -Error in Ccall.msm(params, do.what = "lik", ...) : numerical overflow in calculating likelihood
    -

    Ce qui importe de retenir, c’est la valeur initiale du paramètre d’optimisation avant que l’erreur ne se produise. On va l’utiliser (ou une valeur proche) comme paramètre d’échelle pour l’optimation avec l’option fnscale :

    -
    ms_mod <- msm(
    -  status ~ month, subject = id, data = ct, qmatrix = tr, obstype = 1, 
    -  control = list(fnscale = 75000, trace = TRUE)
    -)
    -
    initial  value 0.997291 
    -iter  10 value 0.520302
    -iter  20 value 0.497598
    -iter  30 value 0.497487
    -final  value 0.497484 
    -converged
    -Used 39 function and 37 gradient evaluations
    -

    On peut comparer la prévalence dans chaque état au cours du temps telle que modélisée par le modèle avec les valeurs observées avec la fonction plot.prevalence.msm.

    -
    plot.prevalence.msm(ms_mod)
    -

    -

    Par défaut, msm considère que les intensités de transition d’un état à un autre sont constantes au cours du temps. Or, dans notre example, il apparait que les prévalences observées varient différemment pendant les premiers mois après le diagnostic. Nous allons donc recalculer le modèle en spécifiant avec le paramètre pci que nous souhaitons considérer des intensités de transition différentes pour les trois premiers mois, la première année, la seconde année et après la seconde année. Comme il faudra plus d’itérations pour faire converger notre modèle, nous avons également augmenter la valeur du paramètre maxit (100 par défaut).

    -
    ms_mod <- msm(
    -  status ~ month, subject = id, data = ct, qmatrix = tr, obstype = 1, 
    -  pci = c(3, 12, 24),
    -  control = list(fnscale = 75000, trace = TRUE, maxit = 500)
    -)
    -
    -

    Là encore le temps de calcul peut être assez long. Vous pouvez récupérer les résultats du modèle avec la commande :

    -
    load(url("https://github.com/larmarange/analyse-R/blob/gh-pages/data/trajectoires_ms_mod.RData"))
    -

    Comparons à nouveau les prévalences estimées avec les prévalences observées.

    -
    plot.prevalence.msm(ms_mod)
    -

    -

    Comme on peut le voir, l’ajustement entre les deux a été amélioré. Les prévalences elles-mêmes peuvent s’obtenir avec prevalence.msm.

    -
    prevalence.msm(ms_mod)
    -
    $Observed
    -   State 1 State 2 State 3 State 4 Total
    -0     2799      24       0       7  2830
    -5     1551     292     297     460  2600
    -10     958     257     210     623  2048
    -15     578     168     104     603  1453
    -20     283      87      65     327   762
    -25     192      63      39     267   561
    -30     147      41      23     245   456
    -35      69      17      12     136   234
    -40      14       2       2      27    45
    -45       7       0       0      12    19
    -50       0       0       0       1     1
    +

    Il est cependant possible de forcer un certain type avec l’argument type. Précision, lorsque l’on force une variable en dichotomique, il faut indiquer avec value la valeur à afficher (les autres sont alors masquées).

    +
    trial %>%
    +  tbl_summary(
    +    include = c(grade, age, death),
    +    type = list(
    +      grade ~ "dichotomous",
    +      age ~ "categorical",
    +      death ~ "categorical"
    +    ),
    +    value = grade ~ "III",
    +    label = grade ~ "Grade III"
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    Grade III64 (32%)
    Age
    61 (0,5%)
    91 (0,5%)
    101 (0,5%)
    171 (0,5%)
    192 (1,1%)
    201 (0,5%)
    212 (1,1%)
    231 (0,5%)
    252 (1,1%)
    262 (1,1%)
    271 (0,5%)
    282 (1,1%)
    301 (0,5%)
    317 (3,7%)
    322 (1,1%)
    332 (1,1%)
    346 (3,2%)
    352 (1,1%)
    365 (2,6%)
    374 (2,1%)
    387 (3,7%)
    395 (2,6%)
    402 (1,1%)
    413 (1,6%)
    424 (2,1%)
    437 (3,7%)
    446 (3,2%)
    456 (3,2%)
    463 (1,6%)
    477 (3,7%)
    487 (3,7%)
    496 (3,2%)
    504 (2,1%)
    516 (3,2%)
    524 (2,1%)
    536 (3,2%)
    545 (2,6%)
    552 (1,1%)
    563 (1,6%)
    575 (2,6%)
    583 (1,6%)
    591 (0,5%)
    604 (2,1%)
    615 (2,6%)
    621 (0,5%)
    634 (2,1%)
    641 (0,5%)
    653 (1,6%)
    664 (2,1%)
    674 (2,1%)
    683 (1,6%)
    692 (1,1%)
    701 (0,5%)
    713 (1,6%)
    741 (0,5%)
    751 (0,5%)
    762 (1,1%)
    781 (0,5%)
    831 (0,5%)
    Manquant11
    Patient Died
    088 (44%)
    1112 (56%)
    +

    + 1 + + + n (%) +

    +
    -
    -

    Accolades de comparaison (bracket)

    -

    La géométrie geom_braket de l’extension ggpubr permets d’ajouter sur un graphique des accolades de comparaison entre groupes.

    -
    library(ggpubr)
    -df <- ToothGrowth
    -df$dose <- factor(df$dose)
    -
    -ggplot(df) +
    -  aes(x = dose, y = len) +
    -  geom_boxplot() +
    -  geom_bracket(
    -    xmin = "0.5", xmax = "1", y.position = 30,
    -    label = "t-test, p < 0.05"
    -  )
    -

    -
    ggplot(df) +
    -  aes(x = dose, y = len) +
    -  geom_boxplot() +
    -  geom_bracket(
    -    xmin = c("0.5", "1"),
    -    xmax = c("1", "2"),
    -    y.position = c(30, 35),
    -    label = c("***", "**"),
    -    tip.length = 0.01
    -  )
    -

    -

    Plus d’informations : https://rpkgs.datanovia.com/ggpubr/

    -
    -

    Diagramme en crêtes (ridges)

    -

    L’extension ggridges fournit une géométrie geom_density_ridges_gradient pour la création de diagramme en crêtes.

    -
    library(ggridges)
    -ggplot(lincoln_weather, aes(x = `Mean Temperature [F]`, y = Month, fill = stat(x))) +
    -  geom_density_ridges_gradient(scale = 3, rel_min_height = 0.01) +
    -  scale_fill_viridis_c(name = "Temp. [F]", option = "C") +
    -  labs(title = "Temperatures in Lincoln NE in 2016") +
    -  theme_ridges()
    -
    Picking joint bandwidth of 3.37
    -

    -

    Plus d’informations : https://wilkelab.org/ggridges/

    -
    -
    -

    Graphique en gaufres (waffle)

    -

    L’extension waffle propose geom_waffle pour des graphiques dits en gaufres.

    -

    ATTENTION : elle s’installe avec la commande install.packages("waffle", repos = "https://cinc.rud.is").

    -
    library(waffle)
    -xdf <- data.frame(
    -  parts = factor(rep(month.abb[1:3], 3), levels = month.abb[1:3]),
    -  vals = c(10, 20, 30, 6, 14, 40, 30, 20, 10),
    -  fct = c(rep("Thing 1", 3), rep("Thing 2", 3), rep("Thing 3", 3))
    -)
    -
    -ggplot(xdf) +
    -  aes(fill = parts, values = vals) +
    -  geom_waffle() +
    -  facet_wrap(~fct) +
    -  scale_fill_manual(
    -    name = NULL,
    -    values = c("#a40000", "#c68958", "#ae6056"),
    -    labels = c("Fruit", "Sammich", "Pizza")
    -  ) +
    -  coord_equal() +
    -  theme_minimal() +
    -  theme_enhance_waffle()
    -

    -

    Plus d’informations : https://github.com/hrbrmstr/waffle

    -
    -
    -

    Graphique en mosaïque (mosaic plot)

    -

    L’extension ggmosaic permets de réaliser des graphiques en mosaïque avec geom_mosaic.

    -
    library(ggmosaic)
    -data(titanic)
    -
    -ggplot(data = titanic) +
    -  geom_mosaic(aes(x = product(Class), fill = Survived))
    -

    -

    Plus d’informations : https://cran.r-project.org/web/packages/ggmosaic/vignettes/ggmosaic.html

    -
    -
    -

    Représentations graphiques des distributions et de l’incertitude

    -

    L’extension ggdist propose plusieurs géométries permettant de représenter l’incertitude autour de certaines mesures.

    -

    -
    -
    -

    Graphique de pirates : alternative aux boîtes à moustache (pirat plot)

    -

    Cette représentation alternative aux boîtes à moustache s’obtient avec la géométrie geom_pirate de l’extension ggpirate1.

    -
    library(ggplot2)
    -library(ggpirate)
    -ggplot(mpg, aes(x = class, y = cty)) +
    -  geom_pirate(aes(colour = class, fill = class)) +
    -  theme_bw()
    -

    -

    Pour plus d’informations : https://github.com/mikabr/ggpirate

    -
    -
    -

    Nuages de pluie (raincloud plots)

    -

    Il existe encore d’autres approches graphiques pour mieux rendre compte visuellement différentes distrubutions comme les raincloud plots. Pour en savoir plus, on pourra se référer à un billet de blog en anglais de Cédric Scherer ou encore à l’extension raincloudplots.

    -

    -
    -
    -

    Demi-géométries

    -

    L’extension gghalves propose des demi-géométries qui peuvent être combinées entre elles.

    -
    library(gghalves)
    -ggplot(iris, aes(x = Species, y = Sepal.Width)) +
    -  geom_half_boxplot(fill = "lightblue") +
    -  geom_half_violin(side = "r", fill = "navajowhite")
    -

    -
    -
    -

    Annotations avec des formes gémoétriques

    -

    L’extension ggforce fournie plusieurs géométries permettant d’annoter les points d’un nuage : geom_mark_circle, geom_mark_ellipse, geom_mark_rect et geom_mark_hull.

    -
    library(ggforce)
    -ggplot(iris, aes(Petal.Length, Petal.Width)) +
    -  geom_mark_rect(aes(fill = Species, label = Species)) +
    -  geom_point()
    -

    -
    ggplot(iris, aes(Petal.Length, Petal.Width)) +
    -  geom_mark_hull(aes(fill = Species, label = Species)) +
    -  geom_point()
    -

    -
    -
    -
    -

    Axes, légende et facettes

    -
    -

    Texte mis en forme

    -

    L’extension ggtext permet d’utiliser du markdown et du HTML pour une mise en forme avancée de texte (axes, titres, légendes…).

    -
    library(ggtext)
    -
    -library(tidyverse)
    -library(ggtext)
    -library(glue)
    -
    -data <- tibble(
    -  bactname = c("Staphylococcaceae", "Moraxella", "Streptococcus", "Acinetobacter"),
    -  OTUname = c("OTU 1", "OTU 2", "OTU 3", "OTU 4"),
    -  value = c(-0.5, 0.5, 2, 3)
    -)
    -
    -data %>%
    -  mutate(
    -    color = c("#009E73", "#D55E00", "#0072B2", "#000000"),
    -    name = glue("<i style='color:{color}'>{bactname}</i> ({OTUname})"),
    -    name = fct_reorder(name, value)
    -  ) %>%
    -  ggplot(aes(value, name, fill = color)) +
    -  geom_col(alpha = 0.5) +
    -  scale_fill_identity() +
    -  labs(caption = "Example posted on **stackoverflow.com**<br>(using made-up data)") +
    -  theme(
    -    axis.text.y = element_markdown(),
    -    plot.caption = element_markdown(lineheight = 1.2)
    -  )
    -

    -
    ggplot(mtcars, aes(disp, mpg)) +
    -  geom_point() +
    -  labs(
    -    title = "<b>Fuel economy vs. engine displacement</b><br>
    -    <span style = 'font-size:10pt'>Lorem ipsum *dolor sit amet,*
    -    consectetur adipiscing elit, **sed do eiusmod tempor incididunt** ut
    -    labore et dolore magna aliqua. <span style = 'color:red;'>Ut enim
    -    ad minim veniam,</span> quis nostrud exercitation ullamco laboris nisi
    -    ut aliquip ex ea commodo consequat.</span>",
    -    x = "displacement (in<sup>3</sup>)",
    -    y = "Miles per gallon (mpg)<br><span style = 'font-size:8pt'>A measure of
    -    the car's fuel efficiency.</span>"
    -  ) +
    -  theme(
    -    plot.title.position = "plot",
    -    plot.title = element_textbox_simple(
    -      size = 13,
    -      lineheight = 1,
    -      padding = margin(5.5, 5.5, 5.5, 5.5),
    -      margin = margin(0, 0, 5.5, 0),
    -      fill = "cornsilk"
    -    ),
    -    axis.title.x = element_textbox_simple(
    -      width = NULL,
    -      padding = margin(4, 4, 4, 4),
    -      margin = margin(4, 0, 0, 0),
    -      linetype = 1,
    -      r = grid::unit(8, "pt"),
    -      fill = "azure1"
    -    ),
    -    axis.title.y = element_textbox_simple(
    -      hjust = 0,
    -      orientation = "left-rotated",
    -      minwidth = unit(1, "in"),
    -      maxwidth = unit(2, "in"),
    -      padding = margin(4, 4, 2, 4),
    -      margin = margin(0, 0, 2, 0),
    -      fill = "lightsteelblue1"
    -    )
    -  )
    -

    -
    -
    -

    Axes limités -

    -

    coord_capped_cart et coord_capped_flip de l’extension lemon permet de limiter le dessin des axes au minimum et au maximum. Voir l’exemple ci-dessous.

    -
    library(ggplot2)
    -library(lemon)
    -p <- ggplot(mtcars) +
    -  aes(x = cyl, y = mpg) +
    -  geom_point() +
    -  theme_classic() +
    -  ggtitle("Axes classiques")
    -pcapped <- p +
    -  coord_capped_cart(bottom = "both", left = "both") +
    -  ggtitle("Axes limités")
    -cowplot::plot_grid(p, pcapped, nrow = 1)
    -

    -

    Une autre possibilité est d’avoir recours à la fonction guide_axis_truncated de l’extension ggh4x.

    -
    library(ggh4x)
    -ggplot(mtcars) +
    -  aes(x = cyl, y = mpg) +
    -  geom_point() +
    -  theme_classic() +
    -  scale_y_continuous(breaks = c(15, 20, 25, 30)) +
    -  guides(
    -    x = guide_axis_truncated(trunc_lower = 5, trunc_upper = 7),
    -    y = guide_axis_truncated()
    -  )
    -

    -
    -
    -

    Répéter les étiquettes des axes sur des facettes

    -

    Lorsque l’on réalise des facettes, les étiquettes des axes ne sont pas répétées.

    -
    library(ggplot2)
    -ggplot(mpg) +
    -  aes(displ, cty) +
    -  geom_point() +
    -  facet_wrap(~cyl)
    -

    -

    L’extension lemon propose facet_rep_grid et facet_rep_wrap qui répètent les axes sur chaque facette.

    -
    library(lemon)
    -ggplot(mpg) +
    -  aes(displ, cty) +
    -  geom_point() +
    -  facet_rep_wrap(~cyl, repeat.tick.labels = TRUE)
    -

    -
    -
    -

    Encoches mineures sur les axes

    -

    Par défaut, des encoches (ticks) sont dessinées sur les axes uniquement pour la grille principale (major breaks). La fonction guide_axis_minor de l’extension ggh4x permet de rajouter des encoches aux points de la grille mineure (minor breaks).

    -
    p <- ggplot(mtcars) +
    -  aes(x = cyl, y = mpg) +
    -  geom_point() +
    -  theme_classic()
    -p
    -

    -
    library(ggh4x)
    -p +
    -  scale_x_continuous(
    -    minor_breaks = 16:32 / 4,
    -    guide = guide_axis_minor()
    -  ) +
    -  # to control relative length of minor ticks
    -  theme(ggh4x.axis.ticks.length.minor = rel(.8))
    -

    -
    -
    -

    Relations imbriquées

    -

    La fonction guide_axis_nested de l’extension ggh4x permet d’afficher sur un axe des relations imbriquées.

    -
    library(ggh4x)
    -df <- data.frame(
    -  item = c("Coffee", "Tea", "Apple", "Pear", "Car"),
    -  type = c("Drink", "Drink", "Fruit", "Fruit", ""),
    -  amount = c(5, 1, 2, 3, 1),
    -  stringsAsFactors = FALSE
    -)
    -
    -ggplot(df) +
    -  aes(x = interaction(item, type), y = amount) +
    -  geom_col() +
    -  guides(x = guide_axis_nested())
    -

    -
    -
    -

    Combinaison de variables sur l’axe des X

    -

    L’extension ggupset fournie une fonction scale_x_upset permettant de représenter des combinaisons de variables sur l’axe des x, combinaisons de variables stockées sous forme d’une colonne de type liste. Pour plus d’informations, voir https://github.com/const-ae/ggupset.

    -
    library(ggupset)
    -library(tidyverse, quietly = TRUE)
    -tidy_movies %>%
    -  distinct(title, year, length, .keep_all = TRUE) %>%
    -  ggplot(aes(x = Genres)) +
    -  geom_bar() +
    -  scale_x_upset(n_intersections = 20)
    -
    Warning: Removed 100 rows containing non-finite values
    -(stat_count).
    -

    ### Zoom sur un axe

    -

    L’extension ggforce propose une fonction facet_zoom permettant de zoomer une partie d’un axe.

    -
    library(ggforce)
    -ggplot(iris, aes(Petal.Length, Petal.Width, colour = Species)) +
    -  geom_point() +
    -  facet_zoom(x = Species == "versicolor")
    -

    -
    -
    -

    Des facettes paginées (diviser en plusieurs sous-graphiques)

    -

    Les fonctions facet_wrap_paginate et facet_grid_paginate de ggforce permet de découper facilement un graphique en facettes en plusieurs pages.

    -
    ggplot(diamonds) +
    -  geom_point(aes(carat, price), alpha = 0.1) +
    -  facet_wrap_paginate(~ cut:clarity, ncol = 3, nrow = 3, page = 1)
    -

    -
    ggplot(diamonds) +
    -  geom_point(aes(carat, price), alpha = 0.1) +
    -  facet_wrap_paginate(~ cut:clarity, ncol = 3, nrow = 3, page = 2)
    -

    -
    ggplot(diamonds) +
    -  geom_point(aes(carat, price), alpha = 0.1) +
    -  facet_wrap_paginate(~ cut:clarity, ncol = 3, nrow = 3, page = 3)
    -

    -
    -
    -
    -

    Cartes

    -

    Voir le chapitre dédié.

    -
    -
    -

    Graphiques complexes

    -
    -

    Graphiques divergents

    -

    L’extension ggcharts fournit plusieurs fonctions de haut niveau pour faciliter la réalisation de graphiques divergents en barres (diverging_bar_chart), en sucettes (diverging_lollipop_chart) voire même une pyramide des âges (pyramid_chart).

    -
    library(ggcharts)
    -data(mtcars)
    -mtcars_z <- dplyr::transmute(
    -  .data = mtcars,
    -  model = row.names(mtcars),
    -  hpz = scale(hp)
    -)
    -
    -diverging_bar_chart(data = mtcars_z, x = model, y = hpz)
    -

    -
    diverging_lollipop_chart(
    -  data = mtcars_z,
    -  x = model,
    -  y = hpz,
    -  lollipop_colors = c("#006400", "#b32134"),
    -  text_color = c("#006400", "#b32134")
    -)
    -

    -
    -
    -

    Pyramides des âges

    -

    Plussieurs solutions sont disponible pour réaliser une pyramide des âges.

    -

    Tout d’abord, ggcharts propose une fonction pyramid_chart.

    -
    library(ggcharts)
    -data("popch")
    -pyramid_chart(data = popch, x = age, y = pop, group = sex)
    -
    Warning: `expand_scale()` is deprecated; use `expansion()`
    -instead.
    +
    +

    Afficher des statistiques sur plusieurs lignes (continuous2)

    +

    Pour les variables continues, gtsummary a introduit un type de variable "continuous2", qui doit être attribué manuellement via type, et qui permets d’afficher plusieurs lignes de statistiques (en indiquant plusieurs chaînes de caractères dans statistic). À noter le sélecteur dédié all_continuous2.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(age, marker, ttdeath),
    +    type = c(age, marker) ~ "continuous2",
    +    statistic = all_continuous2() ~ c("{median} ({p25} - {p75}", "{mean} ({sd})", "{min} - {max}")
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CaractéristiqueN = 200
    Age
    Médiane (EI47 (38 - 57
    Moyenne (ET)47 (14)
    Étendue6 - 83
    Manquant11
    Marker Level (ng/mL)
    Médiane (EI0,64 (0,22 - 1,39
    Moyenne (ET)0,92 (0,86)
    Étendue0,00 - 3,87
    Manquant10
    Months to Death/Censor22,4 (16,0 – 24,0)
    -
    -
    -
      -
    1. Voir http://www.cookbook-r.com/Graphs/Multiple_graphs_on_one_page_(ggplot2)/.

    2. -
    3. lemon fournit également diverses fonctions pour manipuler des graphiques ggplot2, comme par exemple la possibilité de répéter les axes quand on utilise des facettes.

    4. -
    +
    +

    Mise en forme des statistiques (digits)

    +

    L’argument digits permet de spécifier comment mettre en forme les différentes statistiques. Le plus simple est d’indiquer le nombre de décimales à afficher. Il est important de tenir compte que plusieurs statistiques peuvent être affichées pour une même variable. On peut alors indiquer une valeur différente pour chaque statistique.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(age, stage),
    +    by = trt,
    +    digits = list(
    +      all_continuous() ~ 1,
    +      all_categorical() ~ c(0, 1)
    +    )
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 +
    Age46,0 (37,0 – 59,0)48,0 (39,0 – 56,0)
    Manquant74
    T Stage
    T128 (28,6%)25 (24,5%)
    T225 (25,5%)29 (28,4%)
    T322 (22,4%)21 (20,6%)
    T423 (23,5%)27 (26,5%)
    +

    + 1 + + + Médiane (EI); n (%) +

    +
    +
    +

    Au lieu d’un nombre de décimales, on peut indiquer plutôt une fonction à appliquer pour mettre en forme le résultat. Par exemple, gtsummary fournit les fonctions suivantes : style_number{data-pkg=“gtsummary} pour les nombres de manière générale, style_percent{data-pkg=”gtsummary} pour les pourcentages (les valeurs sont multipliées par 100, mais le symbole % n’est pas ajouté), style_pvalue{data-pkg=“gtsummary} pour les p-valeurs, style_sigfig{data-pkg=”gtsummary} qui n’affiche (par défaut) que deux chiffres significatifs, ou encore style_ratio{data-pkg=“gtsummary} qui est une variante de style_sigfig pour les ratios (comme les odds ratios) que l’on compare à 1.

    +

    Il faiut bien noter que ce qui est attendu par digits, c’est une fonction et non le résultat d’une fonction. On indiquera donc le nom de la fonction sans parenthèse.

    +
    trial %>%
    +  tbl_summary(include = marker)
    +
    + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    Marker Level (ng/mL)0,64 (0,22 – 1,39)
    Manquant10
    +

    + 1 + + + Médiane (EI) +

    +
    +
    +
    trial %>%
    +  tbl_summary(
    +    include = marker,
    +    digits = all_continuous() ~ c(style_percent, style_pvalue, style_ratio)
    +  )
    +
    + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    Marker Level (ng/mL)64 (0,2 – 1,39)
    Manquant10
    +

    + 1 + + + Médiane (EI) +

    +
    +
    +

    Comme digits attends à recevoir une fonction (et non le résultat) d’une fonction, on ne peut pas passer directement des arguments aux fonctions style_*() de gtsummary. Pour cela il faut créer une fonction à la levée :

    +
    trial %>%
    +  tbl_summary(
    +    include = marker,
    +    statistic = ~"{mean} pour 100",
    +    digits = ~ function(x) {
    +      style_percent(x, digits = 1)
    +    }
    +  )
    +
    + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    Marker Level (ng/mL)91,6 pour 100
    Manquant10
    +

    + 1 + + + Moyenne pour 100 +

    +
    +
    +

    À noter dans l’exemple précédent que les fonctions style_*() de gtsummary tiennent compte du thème défini (ici la virgule comme séparateur de décimale).

    +

    Pour une mise en forme plus avancée des nombres, il faut se tourner vers l’extension scales (choir le chapitre dédié). ATTENTION : les fonctions de scales n’héritent pas des paramètres du thème gtsummary actif. Il faut donc personnaliser le séparateur de décimal dans l’appel à la fonction.

    +
    trial %>%
    +  tbl_summary(
    +    include = marker,
    +    statistic = ~"{mean}",
    +    digits = ~ scales::label_number(accuracy = .01, suffix = " ng/mL", decimal.mark = ",")
    +  )
    +
    + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    Marker Level (ng/mL)0,92 ng/mL
    Manquant10
    +

    + 1 + + + Moyenne +

    +
    +
    +
    +
    +

    Données manquantes (missing, missing_text)

    +

    Le paramètre missing permets d’indiquer s’il faut afficher le nombre d’observations manquantes (c’est-à-dire égales à NA) : "ifany" (valeur par défaut) affiche ce nombre seulement s’il y en a, "no" masque ce nombre et "always" force l’affichage de ce nombre même s’il n’y pas de valeur manquante. Le paramètre missing_text permets de personnaliser le texte affiché.

    +
    trial %>%
    +  tbl_summary(include = c(trt, age))
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    Chemotherapy Treatment
    Drug A98 (49%)
    Drug B102 (51%)
    Age47 (38 – 57)
    Manquant11
    +

    + 1 + + + n (%); Médiane (EI) +

    +
    +
    +
    trial %>%
    +  tbl_summary(
    +    include = c(trt, age),
    +    missing = "always",
    +    missing_text = "Nbre observations manquantes"
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    Chemotherapy Treatment
    Drug A98 (49%)
    Drug B102 (51%)
    Nbre observations manquantes0
    Age47 (38 – 57)
    Nbre observations manquantes11
    +

    + 1 + + + n (%); Médiane (EI) +

    +
    +
    +

    Il est à noter, pour les variables catégorielles, que les valeurs manquantes ne sont jamais pris en compte pour le calcul des pourcentages. Pour les inclure dans le calcul, il faut les transformer en valeurs explicites, par exemple avec fct_explicit_na de forcats.

    +
    trial %>%
    +  tbl_summary(include = response, type = response ~ "categorical")
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    Tumor Response
    0132 (68%)
    161 (32%)
    Manquant7
    +

    + 1 + + + n (%) +

    +
    +
    +
    trial %>%
    +  mutate(response = response %>% as.factor() %>% forcats::fct_explicit_na(na_level = "non observé")) %>%
    +  tbl_summary(include = response)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    response
    0132 (66%)
    161 (30%)
    non observé7 (3,5%)
    +

    + 1 + + + n (%) +

    +
    +
    +
    +
    +

    Étiquettes des variables (label)

    +

    gtsummary, par défaut, prends en compte les étiquettes de variables, si elles existent, et sinon utilisera le nom de chaque variable dans le tableau. Pour rappel, les étiquettes de variables peuvent être manipulées avec l’extension labelled et les fonctions var_label et set_variable_labels.

    +

    Il est aussi possible d’utiliser l’option label de tbl_summary pour indiquer des étiquettes personnalisées.

    +
    iris %>%
    +  labelled::set_variable_labels(
    +    Petal.Length = "Longueur du pétale",
    +    Petal.Width = "Largeur du pétale"
    +  ) %>%
    +  tbl_summary(label = Species ~ "Espèce")
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 1501 +
    Sepal.Length5,80 (5,10 – 6,40)
    Sepal.Width3,00 (2,80 – 3,30)
    Longueur du pétale4,35 (1,60 – 5,10)
    Largeur du pétale1,30 (0,30 – 1,80)
    Espèce
    setosa50 (33%)
    versicolor50 (33%)
    virginica50 (33%)
    +

    + 1 + + + Médiane (EI); n (%) +

    +
    +
    +

    Pour modifier les modalités d’une variable catégorielle, il faut modifier en amont les niveaux du facteur correspondant.

    +
    +
    +

    Afficher les effectifs (add_n)

    +

    La fonction add_n permets d’ajouter une colonne avec le nombre d’observations (non manquantes par défaut). Plusieurs options permettent de personnaliser le résultat : col_label pour modifier l’intitulé de la colonne; statistic pour personnaliser la ou les statistiques affichées (la liste des statistiques disponibles est disponible dans le fichier d’aide add_n.tbl_summary); last pour la positition de la colonne; footnote pour l’ajout d’une note de tableau.

    +
    trial %>%
    +  tbl_summary(include = c(age, marker)) %>%
    +  add_n()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CaractéristiqueN +N = 2001 +
    Age18947 (38 – 57)
    Manquant11
    Marker Level (ng/mL)1900,64 (0,22 – 1,39)
    Manquant10
    +

    + 1 + + + Médiane (EI) +

    +
    +
    +
    trial %>%
    +  tbl_summary(
    +    include = c(age, marker),
    +    by = trt,
    +    missing = "no"
    +  ) %>%
    +  add_n(
    +    statistic = "{n}/{N}",
    +    col_label = "**Effectifs** (observés / total)",
    +    last = TRUE,
    +    footnote = TRUE
    +  ) %>%
    +  add_overall(last = TRUE)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 + +Effectifs (observés / total)2 + +Total, N = 2001 +
    Age46 (37 – 59)48 (39 – 56)189/20047 (38 – 57)
    Marker Level (ng/mL)0,84 (0,24 – 1,57)0,52 (0,19 – 1,20)190/2000,64 (0,22 – 1,39)
    +

    + 1 + + + Médiane (EI) +

    +

    + 2 + + + N non manquant/N total +

    +
    +
    +
    +
    +

    Mise en forme du tableau (bold_labels, italicize_levels)

    +

    Les fonctions bold_labels, bold_levels, italicize_labels et italicize_levels permettent d’afficher les étiquettes de variables et les modalités des variables catégorielles en gras ou en italique.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(marker, grade, stage),
    +    by = trt
    +  ) %>%
    +  bold_labels() %>%
    +  italicize_levels()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 +
    Marker Level (ng/mL)0,84 (0,24 – 1,57)0,52 (0,19 – 1,20)
    Manquant64
    Grade
    I35 (36%)33 (32%)
    II32 (33%)36 (35%)
    III31 (32%)33 (32%)
    T Stage
    T128 (29%)25 (25%)
    T225 (26%)29 (28%)
    T322 (22%)21 (21%)
    T423 (23%)27 (26%)
    +

    + 1 + + + Médiane (EI); n (%) +

    +
    +
    +
    +
    +

    Modifer en-têtes et notes (modify_header, modify_spanning_header, modify_footnote)

    +

    La fonction modify_header permet de modifier les en-têtes des colonnes, modify_spanning_header d’ajouter un chapeau regroupant plusieurs colonnes et modify_footnote. On doit indiquer une formule ou une liste de formules indiquant les colonnes concernées et la modification souhaitée.

    +

    Il faut néanmoins connaître le nom interne des différentes colonnes. Ceux-ci peuvent âtre affichés avec la fonction show_header_names :

    +
    tbl <- trial %>%
    +  tbl_summary(
    +    include = c(age, grade),
    +    by = trt
    +  ) %>%
    +  add_overall() %>%
    +  add_p()
    +tbl
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Total, N = 2001 + +Drug A, N = 981 + +Drug B, N = 1021 + +p-valeur2 +
    Age47 (38 – 57)46 (37 – 59)48 (39 – 56)0,7
    Manquant1174
    Grade0,9
    I68 (34%)35 (36%)33 (32%)
    II68 (34%)32 (33%)36 (35%)
    III64 (32%)31 (32%)33 (32%)
    +

    + 1 + + + Médiane (EI); n (%) +

    +

    + 2 + + + test de Wilcoxon-Mann-Whitney; test du khi-deux d'indépendance +

    +
    +
    +
    show_header_names(tbl)
    +
    i As a usage guide, the code below re-creates the current column headers.
    +
    modify_header(update = list(
    +  label ~ "**Caractéristique**",
    +  stat_0 ~ "**Total**, N = 200",
    +  stat_1 ~ "**Drug A**, N = 98",
    +  stat_2 ~ "**Drug B**, N = 102",
    +  p.value ~ "**p-valeur**"
    +))
    +
    
    +
    +Column Name   Column Header       
    +------------  --------------------
    +label         **Caractéristique** 
    +stat_0        **Total**, N = 200  
    +stat_1        **Drug A**, N = 98  
    +stat_2        **Drug B**, N = 102 
    +p.value       **p-valeur**        
    +

    label est la colonne affichant le nom des variables, stat_0 la colonne totale crée par add_overall (ou la colonne unique de statistiques en l’absence de paramètre by) et p.value la colonne crée par add_p. Lorsqu’il y a un paramètre by, des colonnes nommées stat_1, stat_2, etc. sont crées pour chaque valeur de by. La fonction all_stat_cols permets de sélectionner toutes les colonnes dont le nom commence par stat_. On peut également utiliser all_stat_cols(stat_0 = FALSE) sélectionner toutes les colonnes associées à by mais pas celle crée par add_overall.

    +

    Dans les étiquettes, on peut utiliser des doubles étoiles (**) pour indiquer du gras et des tirets simples (_) pour de l’italique (il s’agit de codes markdown). On peut utliser {N} pour afficher le nombre total d’observations. Pour les colonnes associées à by, {level}, {n} et {p} correspondent respectivement au niveau du facteur, au nombre d’observations et à la proportion de ce facteur dans l’échantillon total. La valeur NA peut être utilisée pour supprimer les notes associées aux colonnes concernées.

    +
    tbl %>%
    +  modify_header(
    +    list(
    +      label ~ "**Variable**",
    +      all_stat_cols(stat_0 = FALSE) ~ "_{level}_ (n={n}, {style_percent(p)}%)",
    +      stat_0 ~ "**TOTAL** (n={N})",
    +      p.value ~ "**Test de comparaison** (p-valeur)"
    +    )
    +  ) %>%
    +  modify_footnote(everything() ~ NA) %>%
    +  modify_spanning_header(all_stat_cols() ~ "**Traitement**")
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Variable + Traitement + +Test de comparaison (p-valeur)
    +TOTAL (n=200) +Drug A (n=98, 49%) +Drug B (n=102, 51%)
    Age47 (38 – 57)46 (37 – 59)48 (39 – 56)0,7
    Manquant1174
    Grade0,9
    I68 (34%)35 (36%)33 (32%)
    II68 (34%)32 (33%)36 (35%)
    III64 (32%)31 (32%)33 (32%)
    +
    +
    +
    +

    Tests de comparaisons (add_p, separate_p_footnotes)

    +

    Lorsqu’une variable by est définie, la fonction add_p permets d’ajouter des tests de comparaisons entre les groupes et d’afficher les p-valeurs.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(trt, marker, age, response, stage),
    +    by = trt
    +  ) %>%
    +  add_p()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 + +p-valeur2 +
    Marker Level (ng/mL)0,84 (0,24 – 1,57)0,52 (0,19 – 1,20)0,085
    Manquant64
    Age46 (37 – 59)48 (39 – 56)0,7
    Manquant74
    Tumor Response28 (29%)33 (34%)0,5
    Manquant34
    T Stage0,9
    T128 (29%)25 (25%)
    T225 (26%)29 (28%)
    T322 (22%)21 (21%)
    T423 (23%)27 (26%)
    +

    + 1 + + + Médiane (EI); n (%) +

    +

    + 2 + + + test de Wilcoxon-Mann-Whitney; test du khi-deux d'indépendance +

    +
    +
    +

    Par défaut, pour les variables continues, un test de Kruskal-Wallis calculé avec la fonction kruskal.test est utilisé lorsqu’il y a trois groupes ou plus, et un test de Wilcoxon-Mann-Whitney calculé avec wilcox.test (test de comparaison des rangs) lorsqu’il n’y a que deux groupes.

    +

    Si l’on affiche des moyennes, il serait plus juste d’utiliser un test t de Student (test de compairaison des moyennes) calculé avec t.test.

    +

    Pour les variables catégorielles, un test du Chi² calculé avec chisq.test est utilisé par défaut lorsque les effectifs théoriques sont supérieurs à 5, sinon un test de Fosher calculé avec fisher.test est utilisé.

    +

    D’autres tests sont disponibles et sont détaillés dans le fichier d’aide add_p.tbl_summary.

    +

    Le paramètre test permets de spécifier pour chaque variable le type de tests à utiliser. La fonction separate_p_footnotes peut être utilisée pour créer une note de tableau différente pour chaque test. Le paramètre pvalue_fun permet d’indiquer une fonction personnalisée pour la mise en forme des p-valeurs.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(trt, marker, age, response, stage),
    +    statistic = age ~ "{mean} ({sd})",
    +    by = trt
    +  ) %>%
    +  add_stat_label() %>%
    +  add_p(
    +    test = list(
    +      response ~ "fisher.test",
    +      age ~ "t.test"
    +    ),
    +    pvalue_fun = scales::label_pvalue(accuracy = .0001)
    +  ) %>%
    +  separate_p_footnotes()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 98 +Drug B, N = 102p-valeur
    Marker Level (ng/mL), Médiane (EI)0,84 (0,24 – 1,57)0,52 (0,19 – 1,20)0.08471 +
    Manquant64
    Age, Moyenne (ET)47 (15)47 (14)0.83442 +
    Manquant74
    Tumor Response, n (%)28 (29%)33 (34%)0.54033 +
    Manquant34
    T Stage, n (%)0.86624 +
    T128 (29%)25 (25%)
    T225 (26%)29 (28%)
    T322 (22%)21 (21%)
    T423 (23%)27 (26%)
    +

    + 1 + + + Wilcoxon rank sum test +

    +

    + 2 + + + Welch Two Sample t-test +

    +

    + 3 + + + Fisher's exact test +

    +

    + 4 + + + Pearson's Chi-squared test +

    +
    +
    +
    +
    +

    Intervalles de confiance (add_ci)

    +

    La fonction add_ci permets d’ajouter des intervalles de confiance dans des colonnes additionnelles. ATTENTION : par défaut, pour les variables continues, cela calcule les intervalles de confiance d’une moyenne et non d’une médiane. Le type d’intervalle peut être modifié avec method (par exemple "wilcox.test" pour l’intervalle de confiance d’une médiane). statistic permet de personnaliser la présentation de l’intervalle. conf.level permets de changer le niveau de confiance. style_fun permets de modifier la fonction de formatage des

    +
    trial %>%
    +  tbl_summary(
    +    include = c(age, stage),
    +    by = trt,
    +    statistic = all_continuous() ~ "{mean}"
    +  ) %>%
    +  add_overall() %>%
    +  add_ci()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Total, N = 2001 + +95% CI2 + +Drug A, N = 981 + +95% CI2 + +Drug B, N = 1021 + +95% CI2 +
    Age474744, 504745, 50
    Manquant1174
    T Stage
    T153 (26%)21%, 33%28 (29%)20%, 39%25 (25%)17%, 34%
    T254 (27%)21%, 34%25 (26%)17%, 35%29 (28%)20%, 38%
    T343 (22%)16%, 28%22 (22%)15%, 32%21 (21%)13%, 30%
    T450 (25%)19%, 32%23 (23%)16%, 33%27 (26%)18%, 36%
    +

    + 1 + + + Moyenne; n (%) +

    +

    + 2 + + + IC = intervalle de confiance +

    +
    +
    +
    trial %>%
    +  tbl_summary(
    +    include = c(age, marker),
    +    statistic = ~"{median}"
    +  ) %>%
    +  add_ci(
    +    method = ~"wilcox.test",
    +    statistic = ~"entre {conf.low} et {conf.high}",
    +    conf.level = .9,
    +    style_fun = ~ scales::label_number(accuracy = .01, decimal.mark = ",")
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 + +90% CI2 +
    Age47entre 45,50 et 49,00
    Manquant11
    Marker Level (ng/mL)0,64entre 0,71 et 0,94
    Manquant10
    +

    + 1 + + + Médiane +

    +

    + 2 + + + IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Différences entre groupes (add_difference)

    +

    Si la variable spécfiée dans by a exactement 2 niveaux, il est possible de calculer la différence entre deux moyennes (variable continue) ou entre deux proportions (variables dichotomiques uniquement, pas les variables catégorielles), d’afficher l’intervalle de confiance de cette différence et la p-valeur associée (la différence est-elle significativement différente de 0) avec add_difference.

    +
    trial %>%
    +  tbl_summary(
    +    include = c(age, marker, response),
    +    by = trt,
    +    statistic = list(
    +      all_continuous() ~ "{mean}",
    +      all_categorical() ~ "{p}%"
    +    ),
    +    digits = list(
    +      all_continuous() ~ 2,
    +      all_categorical() ~ 1
    +    )
    +  ) %>%
    +  add_difference()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 + +Difference2 + +95% IC2,3 + +p-valeur2 +
    Age47,0147,45-0,44-4,6 – 3,70,8
    Manquant74
    Marker Level (ng/mL)1,020,820,20-0,05 – 0,440,12
    Manquant64
    Tumor Response29,5%33,7%-4,2%-18% – 9,9%0,6
    Manquant34
    +

    + 1 + + + Moyenne; % +

    +

    + 2 + + + test de Student; Two sample test for equality of proportions +

    +

    + 3 + + + IC = intervalle de confiance +

    +
    +
    +

    D’autres options sont disponibles (comme la possibilité de calculer des différences ajustées sur d’autres variables) et sont explicitées dans le fichier d’aide de add_difference.

    +
    +
    +
    +

    Tableau croisé avec tbl_cross()

    +

    La fonction tbl_cross est une variation de tbl_summary permettant de croiser deux variables spécfiées avec les arguments row et col. Le type de pourcentage peut-être précisé avec l’arguement percent. Il est possible d’ajouter le résultat d’un test du Chi² avec add_p.tbl_cross.

    +
    trial %>%
    +  tbl_cross(
    +    row = grade,
    +    col = trt,
    +    percent = "row"
    +  ) %>%
    +  add_p(source_note = TRUE)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique + Chemotherapy Treatment + Total
    Drug ADrug B
    Grade
    I35 (51%)33 (49%)68 (100%)
    II32 (47%)36 (53%)68 (100%)
    III31 (48%)33 (52%)64 (100%)
    Total98 (49%)102 (51%)200 (100%)
    Pearson's Chi-squared test, p=0,9
    +
    +
    +
    +

    Données pondérées et tbl_svysummary()

    +

    La fonction tbl_svysummary est similaire à tbl_summary à l’exception qu’elle prend en entrée un objet de type survey défini avec l’extension homonyme. Cela permet de définir une pondération des observations et un plan d’échantillonnage complexe. Les options de tbl_svysummary sont similaires et il est possible d’utiliser les autres fonctions de gtsummary telles que add_overall, add_p, add_n, add_stat_label, etc.

    +

    Il faut noter que les tests statistiques disponibles ne sont pas les mêmes et sont détaillés dans le fichier d’aide de add_p.tbl_svysummary.

    +
    Titanic %>%
    +  as.data.frame() %>%
    +  survey::svydesign(~1, data = ., weights = ~Freq) %>%
    +  tbl_svysummary(
    +    by = Survived,
    +    percent = "row"
    +  ) %>%
    +  add_stat_label(location = "column") %>%
    +  add_n() %>%
    +  add_overall(last = TRUE) %>%
    +  add_p() %>%
    +  separate_p_footnotes()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CaractéristiqueNStatistique +No, N = 1 490 +Yes, N = 711 +Total, N = 2 2011 +p-valeur
    Class2 2010,72 +
    1stn (%)122 (38%)203 (62%)325 (100%)
    2ndn (%)167 (59%)118 (41%)285 (100%)
    3rdn (%)528 (75%)178 (25%)706 (100%)
    Crewn (%)673 (76%)212 (24%)885 (100%)
    Sex2 2010,0482 +
    Malen (%)1 364 (79%)367 (21%)1 731 (100%)
    Femalen (%)126 (27%)344 (73%)470 (100%)
    Age2 2010,42 +
    Childn (%)52 (48%)57 (52%)109 (100%)
    Adultn (%)1 438 (69%)654 (31%)2 092 (100%)
    +

    + 1 + + + n (%) +

    +

    + 2 + + + chi-squared test with Rao & Scott's second-order correction +

    +
    +
    +
    +
    +

    Statistiques personnalisées avec tbl_continous() et tbl_custom_summary()

    +
    +

    tbl_continuous()

    +

    La fonction tbl_continuous permets de résumer une variable continue en fonction de deux ou plusieurs variables catégorielles.

    +

    Par exemple, pour afficher l’âge moyen de plusieurs sous-groupes :

    +
    trial %>%
    +  tbl_continuous(
    +    variable = age,
    +    statistic = ~"{mean}",
    +    include = c(stage, grade),
    +    by = trt,
    +    digits = ~1
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 +
    T Stage
    T144,149,5
    T250,246,4
    T348,850,0
    T445,344,3
    Grade
    I45,946,4
    II44,650,3
    III51,045,7
    +

    + 1 + + + Moyenne +

    +
    +
    +
    +
    +

    tbl_custom_summary()

    +

    La fonction tbl_custom_summary permets encore plus de personnalisation que tbl_continuous.

    +

    Comme précédemment, un tableau va être créé avec les paramètres include et by. On doit également fournir via stat_fns une fonction personnalisée qui va recevoir un sous tableau de données (obtenu en croisant include et by), contenant toutes les variables du fichier, et qui renverra des statistiques personnalisées que l’on affichera avec statistic. La fonction peut-être différente pour chaque variable.

    +

    Il est également possible d’utiliser quelques fonctions dédiées fournies directement par gtsummary.

    +

    À noter que l’option overall_raw permets d’afficher une ligne total, overall_raw_label de personnaliser l’étiquette de cette ligne et overall_raw_last de choisir si on souhaite l’afficher en début ou en fin de tableau.

    +
    +

    tbl_custom_summary() & continuous_summary()

    +

    La fonction continuous_summary permet de reproduire avec tbl_custom_summary le fonctionnement de tbl_continuous. continuous_summary prend un seul argument (le nom d’une variable du fichier). Les statistiques à afficher sont directement précisées avec statistic.

    +

    Ainsi, pour afficher l’âge moyen (avec l’écart-type) en fonction des variables trt, grade et stage :

    +
    trial %>%
    +  tbl_custom_summary(
    +    include = c("grade", "stage"),
    +    by = "trt",
    +    stat_fns = ~ continuous_summary("age"),
    +    statistic = ~"{mean} ({sd})",
    +    overall_row = TRUE,
    +    digits = ~1
    +  ) %>%
    +  add_overall() %>%
    +  modify_footnote(
    +    update = all_stat_cols() ~ "Âge moyen (ET)"
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Total, N = 2001 + +Drug A, N = 981 + +Drug B, N = 1021 +
    Total47,2 (14,3)47,0 (14,7)47,4 (14,0)
    Grade
    I46,2 (15,2)45,9 (16,0)46,4 (14,6)
    II47,5 (13,7)44,6 (14,8)50,3 (12,1)
    III48,1 (14,1)51,0 (12,6)45,7 (15,0)
    T Stage
    T146,8 (14,7)44,1 (14,7)49,5 (14,5)
    T248,1 (12,6)50,2 (12,8)46,4 (12,4)
    T349,4 (14,1)48,8 (14,0)50,0 (14,5)
    T444,8 (16,0)45,3 (17,3)44,3 (15,0)
    +

    + 1 + + + Âge moyen (ET) +

    +
    +
    +

    Astuce : la fonction modify_footnote peut être utilisée pour mettre à jour la note de tableau.

    +
    +
    +

    tbl_custom_summary() & proportion_summary()

    +

    La fonction proportion_summary permets de calculer une proportion (et son intervalle de confiance). Elle prends en entrée la variable à partir de laquelle calculer la proportion et le ou les valeurs à inclure dans cette proportion. Il faut préciser l’affichage souhaité avec statistic et la mise enforme avec digits.

    +

    Par exemple, pour afficher la proportion de personnes étant à l’étape “T3” ou “T4” (variable stage) :

    +
    trial %>%
    +  tbl_custom_summary(
    +    include = c("grade", "trt"),
    +    stat_fns = ~ proportion_summary(variable = "stage", value = "T3"),
    +    statistic = ~"{prop}% [{conf.low}-{conf.high}]",
    +    digits = ~ scales::label_percent(accuracy = .1, decimal.mark = ",", suffix = "")
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +
    Grade
    I26,5% [16,8-38,8]
    II16,2% [8,7-27,5]
    III21,9% [12,9-34,3]
    Chemotherapy Treatment
    Drug A22,4% [14,9-32,2]
    Drug B20,6% [13,5-30,0]
    +

    + 1 + + + prop% [conf.low-conf.high] +

    +
    +
    +
    +
    +

    tbl_custom_summary() & ratio_summary()

    +

    La fonction ratio_summary calcule le ratio entre deux variables. Elle peut ainsi être utilisée pour produire un tableau d’incidence (nombre de cas / exposition exprimée en personnes-années). On lui indique le nom de la variable à prendre en compte pour le numérateur et celui de la variable pour le dénominateur. Pour chaque sous-groupe, la fonction renvoie {num} (somme de la variable définie pour le numérateur), {denom} (somme de la variable définie pour le dénominateur) et {ratio} (i.e. {num}/{denom}). Si {num} est un nombre entier, l’intervalle de confiance de {ratio} est calculé à l’aide de la fonction poisson.test et accessible via {conf.high} et {conf.high}.

    +
    trial %>%
    +  tbl_custom_summary(
    +    include = c("stage", "grade"),
    +    by = "trt",
    +    stat_fns = ~ ratio_summary("response", "ttdeath"),
    +    statistic = ~"{ratio} [{conf.low}; {conf.high}] ({num}/{denom})",
    +    digits = ~ c(3, 2, 2, 0, 0),
    +    overall_row = TRUE,
    +    overall_row_label = "Total"
    +  ) %>%
    +  bold_labels() %>%
    +  modify_footnote(
    +    update = all_stat_cols() ~ "Ratio [95% CI] (n/N)"
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 +
    Total0,014 [0,01; 0,02] (28/1 983)0,017 [0,01; 0,02] (33/1 942)
    T Stage
    T10,012 [0,00; 0,02] (7/583)0,021 [0,01; 0,04] (11/522)
    T20,011 [0,00; 0,02] (6/528)0,012 [0,01; 0,03] (7/560)
    T30,019 [0,01; 0,04] (8/426)0,016 [0,01; 0,03] (7/425)
    T40,016 [0,01; 0,03] (7/445)0,018 [0,01; 0,04] (8/434)
    Grade
    I0,011 [0,00; 0,02] (8/734)0,019 [0,01; 0,03] (13/690)
    II0,011 [0,00; 0,02] (7/651)0,019 [0,01; 0,03] (12/645)
    III0,022 [0,01; 0,04] (13/598)0,013 [0,01; 0,03] (8/607)
    +

    + 1 + + + Ratio [95% CI] (n/N) +

    +
    +
    +
    +
    +

    tbl_custom_summary() & écriture d’une fonction personnalisée

    +

    Il est également possible, et c’est là toute la puissance de tbl_custom_summary, de définir une fonction personnelle et de la passer via stat_fns.

    +

    Cette fonction sera appellée pour chaque cellule du tableau, chaque cellule étant calculée indépendamment.

    +

    Une telle fonction recevra les arguments suivants : - data est un tableau de données contenant un sous-ensemble des données transmises à tbl_custom_summary, plus précisément le sous-ensemble défini par la valeur courante de variable et de by. Il faut noter que les valeurs manquantes (NA) de variable sont également exclues de data. - full_data est le tableau de données complet transmis à tbl_custom_summary. - variable est une valeur textuelle contenant le nom de la variable sur laquelle porte le calcul en cours. - by est une valeur textuelle contenant le nom de la variable by s’il y en a une, NULL sinon. - type est une valeur textuelle indiquant le type de variable (continuous, categorical, …). - stat_display est une valeur textuelle indiquant les statistiques qui seront affichées (i.e. la valeur indiquée dans l’argument statistic de tbl_custom_summary).

    +

    La plupart du temps, une fonction personnalisée n’aura pas besoin de tous ces éléments. C’est pourquoi il est recommandé d’inclure ... dans la définition de la fonction, par exemple ma_fonction <- function(data, ...){}.

    +

    La fonction devra impérativement renvoyé un tibble composé d’une seule ligne et avec une colonne par statistique calculée, le nom de la colonne correspondant avec la statistique demandée dans statistic.

    +

    Voyons un premier exemple, avec une fonction calculant la somme de marker et l’âge moyen.

    +
    ma_fonction <- function(data, ...) {
    +  marker_sum <- sum(data$marker, na.rm = TRUE)
    +  mean_age <- mean(data$age, na.rm = TRUE)
    +  dplyr::tibble(
    +    marker_sum = marker_sum,
    +    mean_age = mean_age
    +  )
    +}
    +
    +ma_fonction(trial)
    +
    + +
    +

    Construisons un tableau à partir de cette dernière.

    +
    trial %>%
    +  tbl_custom_summary(
    +    include = c(stage, grade),
    +    by = trt,
    +    stat_fns = ~ma_fonction,
    +    statistic = ~"A: {mean_age} - M: {marker_sum}",
    +    digits = everything() ~ c(1, 0),
    +    overall_row = TRUE
    +  ) %>%
    +  add_overall(last = TRUE) %>%
    +  modify_footnote(
    +    update = all_stat_cols() ~ "A: âge moyen - M: somme de marker"
    +  ) %>%
    +  bold_labels()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 + +Total, N = 2001 +
    TotalA: 47,0 - M: 94A: 47,4 - M: 80A: 47,2 - M: 174
    T Stage
    T1A: 44,1 - M: 19A: 49,5 - M: 15A: 46,8 - M: 35
    T2A: 50,2 - M: 29A: 46,4 - M: 29A: 48,1 - M: 59
    T3A: 48,8 - M: 21A: 50,0 - M: 18A: 49,4 - M: 39
    T4A: 45,3 - M: 24A: 44,3 - M: 18A: 44,8 - M: 42
    Grade
    IA: 45,9 - M: 39A: 46,4 - M: 31A: 46,2 - M: 70
    IIA: 44,6 - M: 24A: 50,3 - M: 19A: 47,5 - M: 43
    IIIA: 51,0 - M: 30A: 45,7 - M: 30A: 48,1 - M: 61
    +

    + 1 + + + A: âge moyen - M: somme de marker +

    +
    +
    +

    Dans notre second exemple, nous souhaitons calculer la moyenne et l’intervalle de confiance de la variable affichée en ligne. Cette fois-ci, la variable en cours n’est pas connue à l’avance mais son nom est accessible via l’argument variable. On peut donc y accéder avec la syntaxe data[[variable]].

    +
    mean_ci <- function(data, variable, ...) {
    +  test <- t.test(data[[variable]])
    +  dplyr::tibble(
    +    mean = test$estimate,
    +    conf.low = test$conf.int[1],
    +    conf.high = test$conf.int[2]
    +  )
    +}
    +
    +trial %>%
    +  tbl_custom_summary(
    +    include = c("marker", "ttdeath"),
    +    by = "trt",
    +    stat_fns = ~mean_ci,
    +    statistic = ~"{mean} [{conf.low}; {conf.high}]"
    +  ) %>%
    +  add_overall(last = TRUE) %>%
    +  modify_footnote(
    +    update = all_stat_cols() ~ "moyenne [IC 95%"
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 + +Total, N = 2001 +
    Marker Level (ng/mL)1,02 [0,83; 1,20]0,82 [0,65; 0,99]0,92 [0,79; 1,04]
    Manquant6410
    Months to Death/Censor20,2 [19,2; 21,2]19,0 [18,0; 20,1]19,6 [18,9; 20,4]
    +

    + 1 + + + moyenne [IC 95% +

    +
    +
    +

    Allons un peu plus loin avec notre troisième exemple. Nous nous intéressons non seulement à la moyenne de la variable marker pour une sous-catégorie donnée, mais également si cette moyenne est supérieure ou inférieuree à la grande moyenne (toutes catégories confondues). Nous aurons donc besoin de l’ensemble du jeu de données avec full_data. Cet exemple nous permets également de voir qu’il est possible de renvoyer une statistique textuelle.

    +
    diff_to_great_mean <- function(data, full_data, ...) {
    +  mean <- mean(data$marker, na.rm = TRUE)
    +  great_mean <- mean(full_data$marker, na.rm = TRUE)
    +  diff <- mean - great_mean
    +  dplyr::tibble(
    +    mean = mean,
    +    great_mean = great_mean,
    +    diff = diff,
    +    level = ifelse(diff > 0, "haut", "bas")
    +  )
    +}
    +
    +trial %>%
    +  tbl_custom_summary(
    +    include = c("grade", "stage"),
    +    by = "trt",
    +    stat_fns = ~diff_to_great_mean,
    +    statistic = ~"{mean} ({level}, diff: {diff})",
    +    digits = ~ list(1, as.character, 1),
    +    overall_row = TRUE
    +  ) %>%
    +  bold_labels()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 +
    Total1,0 (haut, diff: 0,1)0,8 (bas, diff: -0,1)
    Grade
    I1,2 (haut, diff: 0,2)1,0 (haut, diff: 0,1)
    II0,9 (bas, diff: -0,1)0,5 (bas, diff: -0,4)
    III1,0 (haut, diff: 0,1)1,0 (haut, diff: 0,1)
    T Stage
    T10,7 (bas, diff: -0,2)0,7 (bas, diff: -0,3)
    T21,2 (haut, diff: 0,3)1,0 (haut, diff: 0,1)
    T31,1 (haut, diff: 0,1)0,9 (haut, diff: 0,0)
    T41,1 (haut, diff: 0,2)0,7 (bas, diff: -0,2)
    +

    + 1 + + + Moyenne (level, diff: diff) +

    +
    +
    +
    +

    Il n’existe pas encore de fonction tbl_custom_svysummary acceptant un objet survey en entrée, mais une telle fonction devrait être disponible dans une future version de gtsummary.

    +
    +
    +
    +
    +

    add_stat()

    +

    D’un usage plus avancé, add_stat permets de rajouter une colonne de statistiques personnalisées à un objet gtsummary existant. Le calcul ne se fait pas ici cellule par cellule mais variable par variable.

    +

    On pourra se référer à l’aide la fonction pour des exemples d’utilisation.

    +
    +
    +
    +

    Résultats d’un modèle avec tbl_regression()

    +

    Déjà abordé dans le chapitre sur la régression logistique, tbl_regression permets d’afficher les coefficients d’un modèle statistique, avec les intervalles de confiance et les p-valeurs.

    +

    tbl_regression utilise de manière sous-jacente l’extension broom.helpers et est donc compatible avec tous les types de modèles compatibles.

    +
    mod <- glm(
    +  response ~ grade * age + trt,
    +  data = trial,
    +  family = binomial
    +)
    +mod %>% tbl_regression()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +log(OR)1 + +95% IC1 +p-valeur
    Grade
    I
    II0,45-2,4 – 3,30,8
    III0,32-2,4 – 3,10,8
    Age0,02-0,01 – 0,060,2
    Chemotherapy Treatment
    Drug A
    Drug B0,13-0,50 – 0,770,7
    Grade * Age
    II * Age-0,01-0,07 – 0,040,7
    III * Age-0,01-0,06 – 0,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +

    Afficher seulement certains coefficients (include)

    +

    Le paramètre include permets de choisir les variables / termes à afficher.

    +
    mod %>%
    +  tbl_regression(include = c(trt, all_interaction()))
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +log(OR)1 + +95% IC1 +p-valeur
    Chemotherapy Treatment
    Drug A
    Drug B0,13-0,50 – 0,770,7
    Grade * Age
    II * Age-0,01-0,07 – 0,040,7
    III * Age-0,01-0,06 – 0,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Étiquettes des variables (label)

    +

    On peut personnaliser les étiquettes des variables avec label.

    +
    mod %>%
    +  tbl_regression(label = list(
    +    trt ~ "Traitement",
    +    "grade:age" ~ "Interaction entre grade et âge"
    +  ))
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +log(OR)1 + +95% IC1 +p-valeur
    Grade
    I
    II0,45-2,4 – 3,30,8
    III0,32-2,4 – 3,10,8
    Age0,02-0,01 – 0,060,2
    Traitement
    Drug A
    Drug B0,13-0,50 – 0,770,7
    Interaction entre grade et âge
    II * Age-0,01-0,07 – 0,040,7
    III * Age-0,01-0,06 – 0,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Exponentiation des coefficients (exponentiate)

    +

    Pour une régression logistique, il est d’usage d’afficher l’exponentiel des coefficients, ce que l’on peut faire en indiquant exponentiate = TRUE.

    +
    mod %>%
    +  tbl_regression(exponentiate = TRUE)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +OR1 + +95% IC1 +p-valeur
    Grade
    I
    II1,570,09 – 26,30,8
    III1,380,09 – 21,50,8
    Age1,030,99 – 1,070,2
    Chemotherapy Treatment
    Drug A
    Drug B1,140,60 – 2,160,7
    Grade * Age
    II * Age0,990,93 – 1,040,7
    III * Age0,990,94 – 1,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Changer l’intitulé des colonnes

    +

    Comme pour tout tableau gtsummary, l’intitulé des colonnes peut être modifié avec modify_header. On pourra avoir recours à show_header_names pour connaître le nom de chaque colonne.

    +
    tbl <- mod %>% tbl_regression(exponentiate = TRUE)
    +show_header_names(tbl)
    +
    i As a usage guide, the code below re-creates the current column headers.
    +
    modify_header(update = list(
    +  label ~ "**Caractéristique**",
    +  estimate ~ "**OR**",
    +  ci ~ "**95% IC**",
    +  p.value ~ "**p-valeur**"
    +))
    +
    
    +
    +Column Name   Column Header       
    +------------  --------------------
    +label         **Caractéristique** 
    +estimate      **OR**              
    +ci            **95% IC**          
    +p.value       **p-valeur**        
    +
    tbl %>%
    +  modify_header(estimate ~ "**Odds Ratio**")
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Odds Ratio1 + +95% IC1 +p-valeur
    Grade
    I
    II1,570,09 – 26,30,8
    III1,380,09 – 21,50,8
    Age1,030,99 – 1,070,2
    Chemotherapy Treatment
    Drug A
    Drug B1,140,60 – 2,160,7
    Grade * Age
    II * Age0,990,93 – 1,040,7
    III * Age0,990,94 – 1,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Afficher des étoiles de signification (add_significance_stars)

    +

    La fonction add_significance_stars ajoute des étoiles de significativité à côté des coefficients. Les options hide_ci, hide_p et hide_se permettent de masquer/afficher les intervalles de confiance, les p-valeurs et les écarts-types.

    +
    lm(time ~ ph.ecog + sex, survival::lung) %>%
    +  tbl_regression() %>%
    +  add_significance_stars(
    +    hide_ci = FALSE,
    +    hide_p = FALSE,
    +    hide_se = TRUE
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Beta1 + +95% IC2 +p-valeur
    ph.ecog-58**-96 – -210,003
    sex52-2,5 – 1070,061
    +

    + 1 + + + *p<0.05; **p<0.01; ***p<0.001 +

    +

    + 2 + + + IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Variables dichotomiques sur une ligne (show_single_row)

    +

    L’argument show_single_row permet d’indiquer une liste de variables dichotomiques que l’on souhaite afficher sur une seule ligne (la modalité de référence étant alors masquée). Il est possible d’indiquer all_dichotomous() pour appliquer cette option à toutes les variables dichotomiques.

    +
    mod %>%
    +  tbl_regression(show_single_row = trt)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +log(OR)1 + +95% IC1 +p-valeur
    Grade
    I
    II0,45-2,4 – 3,30,8
    III0,32-2,4 – 3,10,8
    Age0,02-0,01 – 0,060,2
    Chemotherapy Treatment0,13-0,50 – 0,770,7
    Grade * Age
    II * Age-0,01-0,07 – 0,040,7
    III * Age-0,01-0,06 – 0,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Afficher l’intercept (intercept)

    +

    Par défaut, l’intercept n’est pas affiché. Mais on peut forcer son affichage avec intercept = TRUE.

    +
    mod %>%
    +  tbl_regression(intercept = TRUE)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +log(OR)1 + +95% IC1 +p-valeur
    (Intercept)-2,0-4,0 – -0,220,038
    Grade
    I
    II0,45-2,4 – 3,30,8
    III0,32-2,4 – 3,10,8
    Age0,02-0,01 – 0,060,2
    Chemotherapy Treatment
    Drug A
    Drug B0,13-0,50 – 0,770,7
    Grade * Age
    II * Age-0,01-0,07 – 0,040,7
    III * Age-0,01-0,06 – 0,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Mise en forme des coefficients (estimate_fun, pvalue_fun)

    +

    L’argument estimate_fun permet de fournir une fonction qui sera utilisée pour mettre en forme les coefficients (et les intervalles de confiance) et pvalue_fun pour une fonction utilisée pour les p-valeurs. Voir le chapitre dédié à la mise en forme des nombres.

    +
    mod %>%
    +  tbl_regression(
    +    estimate_fun = scales::label_number(accuracy = .001, decimal.mark = ","),
    +    pvalue_fun = scales::label_pvalue(accuracy = .001, decimal.mark = ",", add_p = TRUE)
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +log(OR)1 + +95% IC1 +p-valeur
    Grade
    I
    II0,453-2,359 – 3,271p=0,750
    III0,318-2,417 – 3,066p=0,818
    Age0,025-0,010 – 0,063p=0,183
    Chemotherapy Treatment
    Drug A
    Drug B0,131-0,505 – 0,771p=0,687
    Grade * Age
    II * Age-0,013-0,069 – 0,043p=0,654
    III * Age-0,006-0,061 – 0,047p=0,813
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Afficher les coefficients pour les références (add_estimate_to_reference_rows)

    +

    L’option add_estimate_to_reference_rows = TRUE ajoute la valeur du coefficient pour les modalités de références.

    +
    mod %>%
    +  tbl_regression(add_estimate_to_reference_rows = TRUE, exponentiate = TRUE)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +OR1 + +95% IC1 +p-valeur
    Grade
    I1,00
    II1,570,09 – 26,30,8
    III1,380,09 – 21,50,8
    Age1,030,99 – 1,070,2
    Chemotherapy Treatment
    Drug A1,00
    Drug B1,140,60 – 2,160,7
    Grade * Age
    II * Age0,990,93 – 1,040,7
    III * Age0,990,94 – 1,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    P-valeurs globales (add_global_p)

    +

    La fonction add_global_p calcule une p-valeur globale pour chaque variable. On ajoutera keep = TRUE pour conserver les p-valeurs individuelles de chaque coefficient.

    +

    Note : par défaut, les p-valeurs globales calculées sont du type III. Voir la note dédiée aux p-valeurs globales dans le chapitre sur la régression logistique.

    +
    mod %>%
    +  tbl_regression() %>%
    +  add_global_p(keep = TRUE)
    +
    add_global_p: Global p-values for variable(s) `add_global_p(include = c("grade",
    +"age", "trt", "grade:age"))` were calculated with
    +  `car::Anova(x$model_obj, type = "III")`
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +log(OR)1 + +95% IC1 +p-valeur
    Grade>0,9
    I
    II0,45-2,4 – 3,30,8
    III0,32-2,4 – 3,10,8
    Age0,02-0,01 – 0,060,2
    Chemotherapy Treatment0,7
    Drug A
    Drug B0,13-0,50 – 0,770,7
    Grade * Age>0,9
    II * Age-0,01-0,07 – 0,040,7
    III * Age-0,01-0,06 – 0,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Ajouter les VIF (add_vif)

    +

    Dans le chapitre sur la multicolinéarité, nous avons abordé les facteurs d’inflation de la variance (FIV) ou variance inflation factor (VIF) en anglais. Ils peuvent être facilement calculés avec add_vif.

    +
    mod %>%
    +  tbl_regression() %>%
    +  add_vif()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +log(OR)1 + +95% IC1 +p-valeur +GVIF1 + +Adjusted GVIF2,1 +
    Grade1743,6
    I
    II0,45-2,4 – 3,30,8
    III0,32-2,4 – 3,10,8
    Age0,02-0,01 – 0,060,22,61,6
    Chemotherapy Treatment1,01,0
    Drug A
    Drug B0,13-0,50 – 0,770,7
    Grade * Age1993,8
    II * Age-0,01-0,07 – 0,040,7
    III * Age-0,01-0,06 – 0,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance, GVIF = Generalized Variance Inflation Factor +

    +

    + 2 + + + GVIF^[1/(2*df)] +

    +
    +
    +
    +
    +

    Représenter graphiquement le modèle (plot)

    +

    Nous avons déjà abordé dans d’autres chapitres la fonction ggcoef_model de GGally pour la représentation graphiques des coefficients. Pour un graphique rapide, on peut appliquer plot() à un tableau généré avec tbl_regression pour produire rapidement un graphique des coefficients.

    +
    mod %>%
    +  tbl_regression(exponentiate = TRUE) %>%
    +  plot()
    +
    Registered S3 method overwritten by 'GGally':
    +  method from   
    +  +.gg   ggplot2
    +

    +

    Cependant, si l’on souhaite plus d’options de personnalisation, on utilisera directement ggcoef_model de GGally.

    +
    mod %>%
    +  GGally::ggcoef_model(exponentiate = TRUE)
    +

    +
    +
    +

    Afficher des statistiques globales du modèle (add_glance_table, add_glance_source_note)

    +

    La méthode glance de broom permets de calculer des statistiques globales sur un modèle (comme le R² ou l’AIC, les statistiques calculées dépendant de chaque modèle).

    +
    mod %>% broom::glance()
    +
    + +
    +

    Ces statistiques globales peuvent être ajoutées au tableau avec add_glance_table ou en notes avec add_glance_source_note. Le paramètre include permets de choisir les éléments à afficher parmi les colonnes du tableau générés par glance.

    +
    mod %>%
    +  tbl_regression() %>%
    +  add_glance_table()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +log(OR)1 + +95% IC1 +p-valeur
    Grade
    I
    II0,45-2,4 – 3,30,8
    III0,32-2,4 – 3,10,8
    Age0,02-0,01 – 0,060,2
    Chemotherapy Treatment
    Drug A
    Drug B0,13-0,50 – 0,770,7
    Grade * Age
    II * Age-0,01-0,07 – 0,040,7
    III * Age-0,01-0,06 – 0,050,8
    déviance nulle229
    degrés de liberté du modèle nul182
    Log-likelihood-113
    AIC239
    BIC262
    Deviance225
    degrés de liberté des résidus176
    No. Obs.183
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    mod %>%
    +  tbl_regression() %>%
    +  add_glance_source_note(include = c("nobs", "AIC"))
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +log(OR)1 + +95% IC1 +p-valeur
    Grade
    I
    II0,45-2,4 – 3,30,8
    III0,32-2,4 – 3,10,8
    Age0,02-0,01 – 0,060,2
    Chemotherapy Treatment
    Drug A
    Drug B0,13-0,50 – 0,770,7
    Grade * Age
    II * Age-0,01-0,07 – 0,040,7
    III * Age-0,01-0,06 – 0,050,8
    No. Obs. = 183; AIC = 239
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +
    +

    Combiner des tableaux

    +
    +

    tbl_stack() & tbl_merge()

    +

    La fonction tbl_stack permets de coller deux (ou plus) tableaux l’un au-dessus de l’autre tandis que tbl_merge les placera côte-à-côte, en s’assurant qu’une même variable sera bien affichée sur la même ligne.

    +
    t1 <-
    +  glm(response ~ trt, trial, family = binomial) %>%
    +  tbl_regression(exponentiate = TRUE)
    +
    +t2 <-
    +  glm(response ~ grade + trt + stage + marker, trial, family = binomial) %>%
    +  tbl_regression(exponentiate = TRUE)
    +
    +tbl_stack(
    +  list(t1, t2),
    +  group_header = c("Modèle bivarié", "Modèle multivarié")
    +)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +OR1 + +95% IC1 +p-valeur
    Modèle bivarié
    Chemotherapy Treatment
    Drug A
    Drug B1,210,66 – 2,240,5
    Modèle multivarié
    Grade
    I
    II1,180,52 – 2,660,7
    III1,140,52 – 2,500,7
    Chemotherapy Treatment
    Drug A
    Drug B1,480,78 – 2,860,2
    T Stage
    T1
    T20,460,18 – 1,140,10
    T31,040,41 – 2,61>0,9
    T40,690,28 – 1,640,4
    Marker Level (ng/mL)1,471,00 – 2,150,048
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    tbl_merge(
    +  list(t1, t2),
    +  tab_spanner = c("Modèle bivarié", "Modèle multivarié")
    +)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique + Modèle bivarié + + Modèle multivarié +
    +OR1 + +95% IC1 +p-valeur +OR1 + +95% IC1 +p-valeur
    Chemotherapy Treatment
    Drug A
    Drug B1,210,66 – 2,240,51,480,78 – 2,860,2
    Grade
    I
    II1,180,52 – 2,660,7
    III1,140,52 – 2,500,7
    T Stage
    T1
    T20,460,18 – 1,140,10
    T31,040,41 – 2,61>0,9
    T40,690,28 – 1,640,4
    Marker Level (ng/mL)1,471,00 – 2,150,048
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    tbl_strata()

    +

    La fonction tbl_strata permet de calculer un tableau gtsummary pour chaque modalité d’une variable catégorielle définie via strata, puis de combiner les tableaux entre eux. Le paramètre .tbl_fun indique la fonction à utiliser pour le calcul du tableau. On peut utiliser la syntaxe rapide d’écritude de fonction propre au tidyverse en indiquant une formule (qui commence par ~) et en utilisant .x pour indiquer où passer le sous-ensemble de données.

    +

    Par défaut les sous-tableaux produits sont combinés avec tbl_merge.

    +
    trial %>%
    +  select(age, grade, stage, trt) %>%
    +  mutate(grade = paste("Grade", grade)) %>%
    +  tbl_strata(
    +    strata = grade,
    +    .tbl_fun =
    +      ~ .x %>%
    +        tbl_summary(by = trt, missing = "no") %>%
    +        add_n()
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique + Grade I + + Grade II + + Grade III +
    N +Drug A, N = 351 + +Drug B, N = 331 +N +Drug A, N = 321 + +Drug B, N = 361 +N +Drug A, N = 311 + +Drug B, N = 331 +
    Age6646 (36 – 60)48 (42 – 55)6244 (31 – 54)50 (43 – 57)6152 (42 – 60)45 (36 – 52)
    T Stage686864
    T18 (23%)9 (27%)14 (44%)9 (25%)6 (19%)7 (21%)
    T28 (23%)10 (30%)8 (25%)9 (25%)9 (29%)10 (30%)
    T311 (31%)7 (21%)5 (16%)6 (17%)6 (19%)8 (24%)
    T48 (23%)7 (21%)5 (16%)12 (33%)10 (32%)8 (24%)
    +

    + 1 + + + Médiane (EI); n (%) +

    +
    +
    +

    Pour les combiner avec tbl_stack, on indiquera .combine_with = "tbl_stack".

    +
    trial %>%
    +  select(age, grade, stage, trt) %>%
    +  mutate(grade = paste("Grade", grade)) %>%
    +  tbl_strata(
    +    strata = grade,
    +    .tbl_fun =
    +      ~ .x %>%
    +        tbl_summary(by = trt, missing = "no") %>%
    +        add_n(),
    +    .combine_with = "tbl_stack"
    +  )
    +
    i Column headers among stacked tables differ. Headers from the first table are
    +used. Use `quiet = TRUE` to supress this message.
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CaractéristiqueN +Drug A, N = 351 + +Drug B, N = 331 +
    Grade I
    Age6646 (36 – 60)48 (42 – 55)
    T Stage68
    T18 (23%)9 (27%)
    T28 (23%)10 (30%)
    T311 (31%)7 (21%)
    T48 (23%)7 (21%)
    Grade II
    Age6244 (31 – 54)50 (43 – 57)
    T Stage68
    T114 (44%)9 (25%)
    T28 (25%)9 (25%)
    T35 (16%)6 (17%)
    T45 (16%)12 (33%)
    Grade III
    Age6152 (42 – 60)45 (36 – 52)
    T Stage64
    T16 (19%)7 (21%)
    T29 (29%)10 (30%)
    T36 (19%)8 (24%)
    T410 (32%)8 (24%)
    +

    + 1 + + + Médiane (EI); n (%) +

    +
    +
    +
    +
    +

    tbl_split()

    +

    Lorsqu’un tableau est trop long et qu’on souhaite le couper en plusieurs tableaux, on pourra utiliser tbl_spit en indiquant le nom des variables après lesquelles le tableau doit être coupé.

    +
    trial %>%
    +  tbl_summary() %>%
    +  tbl_split(variables = c(marker, grade))
    +
    +
    +
    +

    Régressions univariées multiples avec tbl_uvregression()

    +

    La fonction tbl_uvregression est utile pour réaliser plusieurs régressions univariées. Il faut lui passer un tableau ne contenant que la variable à expliquer et les variables explicatives. La variable à expliquer sera indiquée avec y. L’argument method indique la fonction à utiliser pour le calcul des modèles univariés, par exemple glm pour une régression logistique ordinale. On pourra indiquer des paramètres à transmettre à cette fonction avec method.args, par exemple list(family = binomial) dans le cadre d’une régreession logistique binaire.

    +
    tbl_uni <- tbl_uvregression(
    +  trial %>% select(response, age, grade, stage),
    +  method = glm,
    +  y = response,
    +  method.args = list(family = binomial),
    +  exponentiate = TRUE,
    +  hide_n = TRUE
    +)
    +tbl_uni
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +OR1 + +95% IC1 +p-valeur
    Age1,021,00 – 1,040,10
    Grade
    I
    II0,950,45 – 2,000,9
    III1,100,52 – 2,290,8
    T Stage
    T1
    T20,630,27 – 1,460,3
    T31,130,48 – 2,680,8
    T40,830,36 – 1,920,7
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +

    On peut facilement présenter côte-à-côte l’analyse descriptive, l’analyse bivariée et l’analyse multivariée avec tbl_merge.

    +
    tbl_desc <- trial %>%
    +  tbl_summary(
    +    by = response,
    +    include = c(age, grade, stage)
    +  )
    +
    7 observations missing `response` have been removed. To include these observations, use `forcats::fct_explicit_na()` on `response` column before passing to `tbl_summary()`.
    +
    tbl_multi <- trial %>%
    +  glm(
    +    response ~ age + grade + stage,
    +    data = .,
    +    family = binomial
    +  ) %>%
    +  tbl_regression(exponentiate = TRUE)
    +
    +tbl_merge(
    +  list(tbl_desc, tbl_uni, tbl_multi),
    +  tab_spanner = c("**Analyse descriptive**", "**Modèles bivariés**", "**Modèle multivarié**")
    +)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique + Analyse descriptive + + Modèles bivariés + + Modèle multivarié +
    +0, N = 1321 + +1, N = 611 + +OR2 + +95% IC2 +p-valeur +OR2 + +95% IC2 +p-valeur
    Age46 (36 – 55)49 (43 – 59)1,021,00 – 1,040,101,021,00 – 1,040,092
    Manquant73
    Grade
    I46 (35%)21 (34%)
    II44 (33%)19 (31%)0,950,45 – 2,000,90,840,38 – 1,850,7
    III42 (32%)21 (34%)1,100,52 – 2,290,81,050,49 – 2,25>0,9
    T Stage
    T134 (26%)18 (30%)
    T239 (30%)13 (21%)0,630,27 – 1,460,30,570,23 – 1,340,2
    T325 (19%)15 (25%)1,130,48 – 2,680,80,910,37 – 2,220,8
    T434 (26%)15 (25%)0,830,36 – 1,920,70,760,31 – 1,850,6
    +

    + 1 + + + Médiane (EI); n (%) +

    +

    + 2 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Tables de survie avec tbl_survfit()

    +

    L’analyse de survie et les courbes de Kaplan-Meier sont abordées dans un chapitre dédié. La fonction tbl_survfit permets de représenter la probabilité encore en vie à différents points de temps définis avec times.

    +
    library(survival)
    +km <- survfit(Surv(ttdeath, death) ~ trt, trial)
    +survminer::ggsurvplot(km)
    +

    +
    km %>%
    +  tbl_survfit(
    +    times = c(0, 6, 12, 18, 24),
    +    label_header = "**Mois {time}**"
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CaractéristiqueMois 0Mois 6Mois 12Mois 18Mois 24
    Chemotherapy Treatment
    Drug A100% (100% – 100%)99% (97% – 100%)91% (85% – 97%)70% (62% – 80%)47% (38% – 58%)
    Drug B100% (100% – 100%)99% (97% – 100%)86% (80% – 93%)60% (51% – 70%)41% (33% – 52%)
    +
    +

    On peut alternativement représenter la proportion ayant vécu l’évènement avec reverse = TRUE.

    +
    km %>%
    +  tbl_survfit(
    +    times = c(6, 12),
    +    reverse = TRUE
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    CaractéristiqueTemps 6Temps 12
    Chemotherapy Treatment
    Drug A1,0% (0% – 3,0%)9,2% (3,3% – 15%)
    Drug B1,0% (0% – 2,9%)14% (6,8% – 20%)
    +
    +

    Au lieu d’indiquer des points de temps, on peut indiquer des quantiles avec probs et représenter le temps requis pour atteindre ces quantiles.

    +
    km %>%
    +  tbl_survfit(probs = c(.25, .5, .75))
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique25% Percentile50% Percentile75% Percentile
    Chemotherapy Treatment
    Drug A17 (16 – 20)24 (21 – —)— (— – —)
    Drug B15 (13 – 17)21 (18 – —)— (— – —)
    +
    +

    Il est également possible de passer une liste d’objets survfit.

    +
    list(
    +  survfit(Surv(ttdeath, death) ~ 1, trial),
    +  survfit(Surv(ttdeath, death) ~ trt, trial),
    +  survfit(Surv(ttdeath, death) ~ grade, trial)
    +) %>%
    +  tbl_survfit(
    +    times = c(6, 12, 18),
    +    label_header = "**Mois {time}**"
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CaractéristiqueMois 6Mois 12Mois 18
    Total99% (98% – 100%)88% (84% – 93%)65% (59% – 72%)
    Chemotherapy Treatment
    Drug A99% (97% – 100%)91% (85% – 97%)70% (62% – 80%)
    Drug B99% (97% – 100%)86% (80% – 93%)60% (51% – 70%)
    Grade
    I100% (100% – 100%)97% (93% – 100%)75% (65% – 86%)
    II100% (100% – 100%)82% (74% – 92%)60% (50% – 73%)
    III97% (93% – 100%)86% (78% – 95%)59% (48% – 73%)
    +
    +

    Dernière possibilité, il est possible de passer un tableau de données et d’indiquer les variables à analyser. Les tables de survie seront alors calculées à la volée.

    +
    trial %>%
    +  tbl_survfit(
    +    y = Surv(ttdeath, death),
    +    include = c(trt, grade, stage),
    +    probs = 0.5,
    +    label_header = "**Survie médiane en mois** (IC 95%)",
    +    estimate_fun = scales::label_number(accuracy = .1)
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Survie médiane en mois (IC 95%)
    Chemotherapy Treatment
    Drug A23.5 (21.2 – —)
    Drug B21.2 (18.2 – —)
    Grade
    I— (22.1 – —)
    II22.2 (18.0 – —)
    III19.7 (17.6 – 23.2)
    T Stage
    T1— (22.7 – —)
    T2— (20.1 – —)
    T322.9 (18.3 – —)
    T417.2 (15.6 – 22.4)
    +
    +
    +
    +

    Exporter un tableau

    +

    Les tableaux produits par gtsummary peuvent être rendus avec plusieurs moteurs de tableaux, grace aux fonctions as_flex_table, as_hux_table, as_kable_extra, et as_kable. Ils peuvent même être convertis en tableaux de données avec , as_tibble.

    +

    Dans un document R Markdown, gtsummary utilisera le moteur de tableaux le plus adapté selon la sortie (HTML, PDF ou Word).

    +
    +

    Formats d’export d’un tableau gtsummary

    +
    +
    tbl <- trial %>%
    +  tbl_summary(
    +    include = c(age, grade),
    +    by = trt
    +  ) %>%
    +  add_p()
    +
    +tbl %>% as_gt()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Drug A, N = 981 + +Drug B, N = 1021 + +p-valeur2 +
    Age46 (37 – 59)48 (39 – 56)0,7
    Manquant74
    Grade0,9
    I35 (36%)33 (32%)
    II32 (33%)36 (35%)
    III31 (32%)33 (32%)
    +

    + 1 + + + Médiane (EI); n (%) +

    +

    + 2 + + + test de Wilcoxon-Mann-Whitney; test du khi-deux d'indépendance +

    +
    +
    +
    tbl %>% as_flex_table()
    +
    +
    tbl %>% as_hux_table()
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Caractéristique

    +
    +

    Drug A, N = 98

    +
    +

    Drug B, N = 102

    +
    +

    p-valeur

    +
    Age46 (37 – 59)48 (39 – 56)0,7
    Manquant74
    Grade0,9
    I35 (36%)33 (32%)
    II32 (33%)36 (35%)
    III31 (32%)33 (32%)
    Médiane (EI); n (%)
    test de Wilcoxon-Mann-Whitney; test du khi-deux d'indépendance
    +
    tbl %>% as_kable_extra()
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +Caractéristique + +Drug A, N = 98 + +Drug B, N = 102 + +p-valeur +
    +Age + +46 (37 – 59) + +48 (39 – 56) + +0,7 +
    +Manquant + +7 + +4 + +
    +Grade + + + +0,9 +
    +I + +35 (36%) + +33 (32%) + +
    +II + +32 (33%) + +36 (35%) + +
    +III + +31 (32%) + +33 (32%) + +
    +1 Médiane (EI); n (%) +
    +2 test de Wilcoxon-Mann-Whitney; test du khi-deux d’indépendance +
    +
    tbl %>% as_tibble()
    +
    # A tibble: 6 x 4
    +  `**Caractéristique**` `**Drug A**, N = ~ `**Drug B**, N =~
    +  <chr>                 <chr>              <chr>            
    +1 Age                   46 (37 – 59)       48 (39 – 56)     
    +2 Manquant              7                  4                
    +3 Grade                 <NA>               <NA>             
    +4 I                     35 (36%)           33 (32%)         
    +5 II                    32 (33%)           36 (35%)         
    +6 III                   31 (32%)           33 (32%)         
    +# ... with 1 more variable: **p-valeur** <chr>
    +

    En dehors d’un fichier R markdown, pour exporter un tableau dans un fichier HTML, TeX ou RTF, on pourra utiliser gtsave de gt.

    +
    tbl %>%
    +  as_gt() %>%
    +  gt::gtsave(filename = ".") # use extensions .html .tex .ltx .rtf
    +

    Pour exporter un tableau dans un fichier Word, on pourra avoir recours à save_as_docx de flextable.

    +
    tbl %>%
    +  as_flex_table() %>%
    +  flextable::save_as_docx()
    +
    +
    +

    Plus d’options avec bstfun

    +

    L’extension bstfun est une petite sœur de gtsummary, développée par la même équipe. Cette extension n’est pas disponible sur CRAN mais seulement sur GitHub et elle permet, entre autres, de tester certaines fonctionnalités avant leur éventuelle intégration dans gtsummary.

    +

    Cette extension n’étant disponible que sur GitHub, elle s’installe avec la commande ci-après. ATTENTION : sous Windows, vous aurez besoin d’avoir installer en amont l’outil R Tools disponible sur https://cran.r-project.org/bin/windows/Rtools/.

    +
    devtools::install_github("ddsjoberg/bstfun")
    +
    +

    tbl_likert()

    +

    En sciences sociales, il est fréquent de mesurer des connaissances ou des opinions selon une échelle de Likert. Dans cette situation, nous avons alors plusieurs variables catégorielles partageant les mêmes modalités.

    +

    Prenons les données utilisées dans le chapitre Exemples de graphiques avancés.

    +
    load(url("https://larmarange.github.io/analyse-R/data/connaissances.RData"))
    +
    library(labelled)
    +
    Warning: le package 'labelled' a été compilé avec la version
    +R 4.1.2
    +
    quest %>% lookfor("conn")
    +
     pos variable label col_type values
    + 2   conn_a   —     fct      oui   
    +                             non   
    +                             NSP   
    + 3   conn_b   —     fct      oui   
    +                             non   
    +                             NSP   
    + 4   conn_c   —     fct      oui   
    +                             non   
    +                             NSP   
    + 5   conn_d   —     fct      oui   
    +                             non   
    +                             NSP   
    + 6   conn_e   —     fct      oui   
    +                             non   
    +                             NSP   
    + 7   conn_f   —     fct      oui   
    +                             non   
    +                             NSP   
    + 8   conn_g   —     fct      oui   
    +                             non   
    +                             NSP   
    +

    Nous avons une série de 8 variables avec les mêmes modalités (Oui, Non et NSP). Un tri à plat peut-être un peu fastidieux à lire.

    +
    quest %>%
    +  tbl_summary(include = starts_with("conn_"))
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 5001 +
    conn_a
    oui36 (7,5%)
    non442 (92%)
    NSP1 (0,2%)
    Manquant21
    conn_b
    oui500 (100%)
    non0 (0%)
    NSP0 (0%)
    conn_c
    oui495 (99%)
    non2 (0,4%)
    NSP3 (0,6%)
    conn_d
    oui138 (35%)
    non244 (62%)
    NSP14 (3,5%)
    Manquant104
    conn_e
    oui458 (95%)
    non19 (3,9%)
    NSP6 (1,2%)
    Manquant17
    conn_f
    oui452 (92%)
    non31 (6,3%)
    NSP7 (1,4%)
    Manquant10
    conn_g
    oui469 (96%)
    non15 (3,1%)
    NSP4 (0,8%)
    Manquant12
    +

    + 1 + + + n (%) +

    +
    +
    +

    La fonction tbl_likert de bstfun est plus adaptée pour présenter ce type de données.

    +
    library(bstfun)
    +
    
    +Attachement du package : 'bstfun'
    +
    Les objets suivants sont masqués depuis 'package:gtsummary':
    +
    +    tbl_split, trial
    +
    quest %>%
    +  tbl_likert(
    +    include = starts_with("conn_"),
    +    statistic = ~"{p}%"
    +  ) %>%
    +  add_n()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CaractéristiqueN +oui1 + +non1 + +NSP1 +
    conn_a4797,5%92%0,2%
    conn_b500100%0%0%
    conn_c50099%0,4%0,6%
    conn_d39635%62%3,5%
    conn_e48395%3,9%1,2%
    conn_f49092%6,3%1,4%
    conn_g48896%3,1%0,8%
    +

    + 1 + + + % +

    +
    +
    +
    +
    +

    Ajouter un graphique de tendances (add_sparkline)

    +

    La fonction add_sparkline ajoute une représentation graphique de la distribution d’une variable continue.

    +
    trial %>%
    +  tbl_summary(include = c(age, marker)) %>%
    +  add_sparkline(column_header = "**Distribution**")
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +N = 2001 +Distribution
    Age, yrs47 (38 – 57) + +
    Manquant11
    Marker Level, ng/mL0,64 (0,22 – 1,39) + +
    Manquant10
    +

    + 1 + + + Médiane (EI) +

    +
    +
    +
    +
    +

    Représentation graphique des coefficients dans le tableau (add_inline_forest_plot)

    +

    La fonction add_inline_forest_plot ajoute aux tableaux représentant les coefficients d’un modèle une représentation graphique de ces coefficients et de leur intervalle de confiance.

    +
    mod %>%
    +  tbl_regression(exponentiate = TRUE) %>%
    +  add_inline_forest_plot()
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CaractéristiqueForest Plot +OR1 + +95% IC1 +p-valeur
    Grade
    I
    II + +1,570,09 – 26,30,8
    III + +1,380,09 – 21,50,8
    Age + +1,030,99 – 1,070,2
    Chemotherapy Treatment
    Drug A
    Drug B + +1,140,60 – 2,160,7
    Grade * Age
    II * Age + +0,990,93 – 1,040,7
    III * Age + +0,990,94 – 1,050,8
    +

    + 1 + + + OR = rapport de cotes, IC = intervalle de confiance +

    +
    +
    +
    +
    +

    Forest plot (as_forest_plot)

    +

    La fonction as_forest_plot permets d’afficher un graphique des coefficients utilisant la fonction forestplot de l’extension homonyme forestplot à partir d’un tableau construit avec tbl_regression.

    +
    mod %>%
    +  tbl_regression() %>%
    +  as_forest_plot()
    +

    +
    +
    + +
    +
    + + + +

    Effets d’interaction dans un modèle

    + +
    + + + +
    +

    Ce chapitre est évoqué dans le webin-R #07 (régression logistique partie 2) sur YouTube.

    +
    +

    Dans un modèle statistique classique, on fait l’hypothèse implicite que chaque variable explicative est indépendante des autres. Cependant, cela ne se vérifie pas toujours. Par exemple, l’effet de l’âge peut varier en fonction du sexe. Il est dès lors nécessaire de prendre en compte dans son modèle les effets d’interaction1.

    +
    +

    Exemple d’interaction

    +

    Reprenons le modèle que nous avons utilisé dans le chapitre sur la régression logistique.

    +
    library(questionr)
    +data(hdv2003)
    +d <- hdv2003
    +d$sexe <- relevel(d$sexe, "Femme")
    +d$grpage <- cut(d$age, c(16, 25, 45, 65, 99), right = FALSE, include.lowest = TRUE)
    +d$etud <- d$nivetud
    +levels(d$etud) <- c(
    +  "Primaire", "Primaire", "Primaire",
    +  "Secondaire", "Secondaire", "Technique/Professionnel",
    +  "Technique/Professionnel", "Supérieur"
    +)
    +d$etud <- addNAstr(d$etud, "Manquant")
    +library(labelled)
    +var_label(d$sport) <- "Pratique du sport ?"
    +var_label(d$sexe) <- "Sexe"
    +var_label(d$grpage) <- "Groupe d'âges"
    +var_label(d$etud) <- "Niveau d'étude"
    +var_label(d$relig) <- "Pratique religieuse"
    +var_label(d$heures.tv) <- "Nombre d'heures passées devant la télévision par jour"
    +

    Nous avions alors exploré les facteurs associés au fait de pratiquer du sport.

    +
    mod <- glm(sport ~ sexe + grpage + etud + heures.tv + relig, data = d, family = binomial())
    +library(gtsummary)
    +tbl_regression(mod, exponentiate = TRUE)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +OR1 + +95% CI1 +p-valeur
    Sexe
    Femme
    Homme1,551,26 – 1,91<0,001
    Groupe d'âges
    [16,25)
    [25,45)0,660,42 – 1,030,065
    [45,65)0,340,21 – 0,54<0,001
    [65,99]0,250,15 – 0,43<0,001
    Niveau d'étude
    Primaire
    Secondaire2,591,77 – 3,83<0,001
    Technique/Professionnel2,861,98 – 4,17<0,001
    Supérieur6,634,55 – 9,80<0,001
    Manquant8,594,53 – 16,6<0,001
    Nombre d'heures passées devant la télévision par jour0,890,83 – 0,95<0,001
    Pratique religieuse
    Pratiquant regulier
    Pratiquant occasionnel0,980,68 – 1,42>0,9
    Appartenance sans pratique0,990,71 – 1,40>0,9
    Ni croyance ni appartenance0,810,55 – 1,180,3
    Rejet0,680,39 – 1,190,2
    NSP ou NVPR0,920,40 – 2,020,8
    +

    + 1 + + + OR = rapport de cotes, CI = intervalle de confiance +

    +
    +
    +

    Selon les résultats de notre modèle, les hommes pratiquent plus un sport que les femmes et la pratique du sport diminue avec l’âge. Pour représenter les effets différentes variables, on peut avoir recours à la fonction allEffects de l’extension effects.

    +
    library(effects)
    +plot(allEffects(mod))
    +
    +Représentation graphique des effets du modèle +

    Cependant, l’effet de l’âge est-il le même selon le sexe ? Nous allons donc introduire une interaction entre l’âge et le sexe dans notre modèle, ce qui sera représenté par sexe * grpage dans l’équation du modèle.

    +
    mod2 <- glm(sport ~ sexe * grpage + etud + heures.tv + relig, data = d, family = binomial())
    +tbl_regression(mod2, exponentiate = TRUE)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +OR1 + +95% CI1 +p-valeur
    Sexe
    Femme
    Homme5,202,46 – 11,7<0,001
    Groupe d'âges
    [16,25)
    [25,45)1,020,59 – 1,80>0,9
    [45,65)0,610,34 – 1,100,10
    [65,99]0,460,23 – 0,920,027
    Niveau d'étude
    Primaire
    Secondaire2,601,77 – 3,85<0,001
    Technique/Professionnel2,881,99 – 4,21<0,001
    Supérieur6,774,64 – 10,0<0,001
    Manquant9,024,68 – 17,8<0,001
    Nombre d'heures passées devant la télévision par jour0,890,83 – 0,95<0,001
    Pratique religieuse
    Pratiquant regulier
    Pratiquant occasionnel0,990,68 – 1,43>0,9
    Appartenance sans pratique1,020,73 – 1,440,9
    Ni croyance ni appartenance0,830,57 – 1,220,3
    Rejet0,680,38 – 1,190,2
    NSP ou NVPR0,930,40 – 2,040,9
    Sexe * Groupe d'âges
    Homme * [25,45)0,310,13 – 0,700,006
    Homme * [45,65)0,240,10 – 0,54<0,001
    Homme * [65,99]0,230,09 – 0,590,003
    +

    + 1 + + + OR = rapport de cotes, CI = intervalle de confiance +

    +
    +
    +

    Commençons par regarder les effets du modèle.

    +
    plot(allEffects(mod2))
    +
    +Représentation graphique des effets du modèle avec interaction entre le sexe et le groupe d’âge +

    Sur ce graphique, on voit que l’effet de l’âge sur la pratique d’un sport est surtout marqué chez les hommes. Chez les femmes, le même effet est observé, mais dans une moindre mesure et seulement à partir de 45 ans.

    +

    On peut tester si l’ajout de l’interaction améliore significativement le modèle avec anova.

    +
    anova(mod2, test = "Chisq")
    +
    + +
    +

    Jetons maintenant un oeil aux coefficients du modèle. Pour rendre les choses plus visuelles, nous aurons recours à ggcoef_model de l’extension GGally.

    +
    library(GGally)
    +ggcoef_model(mod2, exponentiate = TRUE)
    +
    +Représentation graphique des coefficients du modèle avec interaction entre le sexe et le groupe d’âge +

    Concernant l’âge et le sexe, nous avons trois séries de coefficients : trois coefficients (grpage[25,45), grpage[45,65) et grpage[65,99]) qui correspondent à l’effet global de la variable âge, un coefficient (sexeHomme)pour l’effet global du sexe et trois coefficients qui sont des moficateurs de l’effet d’âge pour les hommes (grpage[25,45), grpage[45,65) et grpage[65,99]).

    +

    Pour bien interpréter ces coefficients, il faut toujours avoir en tête les modalités choisies comme référence pour chaque variable. Supposons une femme de 60 ans, dont toutes lautres variables correspondent aux modalités de référence (c’est donc une pratiquante régulière, de niveau primaire, qui ne regarde pas la télévision). Regardons ce que prédit le modèle quant à sa probabilité de faire du sport au travers d’une représentation graphique

    +
    library(breakDown)
    +library(ggplot2)
    +logit <- function(x) exp(x) / (1 + exp(x))
    +nouvelle_observation <- d[1, ]
    +nouvelle_observation$sexe[1] <- "Femme"
    +nouvelle_observation$grpage[1] <- "[45,65)"
    +nouvelle_observation$etud[1] <- "Primaire"
    +nouvelle_observation$relig[1] <- "Pratiquant regulier"
    +nouvelle_observation$heures.tv[1] <- 0
    +plot(
    +  broken(mod2, nouvelle_observation, predict.function = betas),
    +  trans = logit
    +) + ylim(0, 1) + ylab("Probabilité de faire du sport")
    +
    Scale for 'y' is already present. Adding another scale
    +for 'y', which will replace the existing scale.
    +
    +Représentation graphique de l’estimation de la probabilité de faire du sport pour une femme de 60 ans +

    En premier lieu, l’intercept s’applique et permet de déterminer la probabilité de base de faire du sport (si toutes les variables sont à leur valeur de référence). Femme étant la modalité de référence pour la variable sexe, cela ne modifie pas le calcul de la probabilité de faire du sport. Par contre, il y a une modification induite par la modalité 45-65 de la variable grpage.

    +

    Regardons maintenant la situation d’un homme de 20 ans.

    +
    nouvelle_observation$sexe[1] <- "Homme"
    +nouvelle_observation$grpage[1] <- "[16,25)"
    +plot(
    +  broken(mod2, nouvelle_observation, predict.function = betas),
    +  trans = logit
    +) + ylim(0, 1) + ylab("Probabilité de faire du sport")
    +
    Scale for 'y' is already present. Adding another scale
    +for 'y', which will replace the existing scale.
    +
    +Représentation graphique de l’estimation de la probabilité de faire du sport pour un homme de 20 ans +

    Nous sommes à la modalité de référence pour l’âge par contre il y a un effet important du sexe. Le coefficient associé globalement à la variable sexe correspond donc à l’effet du sexe à la modalité de référence du groupe d’âges.

    +

    La situation est différente pour un homme de 60 ans.

    +
    nouvelle_observation$grpage[1] <- "[45,65)"
    +plot(
    +  broken(mod2, nouvelle_observation, predict.function = betas),
    +  trans = logit
    +) + ylim(0, 1) + ylab("Probabilité de faire du sport")
    +
    Scale for 'y' is already present. Adding another scale
    +for 'y', which will replace the existing scale.
    +
    +Représentation graphique de l’estimation de la probabilité de faire du sport pour un homme de 60 ans +

    Cette fois-ci, il y a plusieurs modifications d’effet. On applique en effet à la fois le coefficient sexe = Homme (effet du sexe pour les 15-24 ans), le coefficient grpage = [45-65) qui est l’effet de l’âge pour les femmes de 45-64 ans et le coefficient sexe:grpage = Homme:[45-65) qui indique l’effet spécifique qui s’applique aux hommes de 45-64, d’une part par rapport aux femmes du même et d’autre part par rapport aux hommes de 16-24 ans. L’effet des coefficients d’interaction doivent donc être interprétés par rapport aux autres coefficients du modèle qui s’appliquent, en tenant compte des modalités de référence.

    +

    Il est cependant possible d’écrire le même modèle différemment. En effet, sexe * grpage dans la formule du modèle est équivalent à l’écriture sexe + grpage + sexe:grpage, c’est-à-dire à modéliser un coefficient global pour chaque variable plus un des coefficients d’interaction. On aurait pu demander juste des coefficients d’interaction, en ne mettant que sexe:grpage.

    +
    mod3 <- glm(sport ~ sexe:grpage + etud + heures.tv + relig, data = d, family = binomial())
    +tbl_regression(mod3, exponentiate = TRUE)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +OR1 + +95% CI1 +p-valeur
    Niveau d'étude
    Primaire
    Secondaire2,601,77 – 3,85<0,001
    Technique/Professionnel2,881,99 – 4,21<0,001
    Supérieur6,774,64 – 10,0<0,001
    Manquant9,024,68 – 17,8<0,001
    Nombre d'heures passées devant la télévision par jour0,890,83 – 0,95<0,001
    Pratique religieuse
    Pratiquant regulier
    Pratiquant occasionnel0,990,68 – 1,43>0,9
    Appartenance sans pratique1,020,73 – 1,440,9
    Ni croyance ni appartenance0,830,57 – 1,220,3
    Rejet0,680,38 – 1,190,2
    NSP ou NVPR0,930,40 – 2,040,9
    Sexe * Groupe d'âges
    Femme * [16,25)1,820,92 – 3,580,083
    Homme * [16,25)9,464,34 – 21,8<0,001
    Femme * [25,45)1,861,17 – 3,000,009
    Homme * [25,45)3,021,87 – 4,96<0,001
    Femme * [45,65)1,110,69 – 1,800,7
    Homme * [45,65)1,350,84 – 2,210,2
    Femme * [65,99]0,840,47 – 1,500,6
    Homme * [65,99]
    +

    + 1 + + + OR = rapport de cotes, CI = intervalle de confiance +

    +
    +
    +

    Au sens strict, ce modèle explique tout autant le phénomène étudié que le modèle précédent. On peut le vérifier facilement avec anova.

    +
    anova(mod2, mod3, test = "Chisq")
    +
    + +
    +

    De même, les effets modélisés sont les mêmes.

    +
    plot(allEffects(mod3))
    +
    +Représentation graphique des effets du modèle avec interaction simple entre le sexe et le groupe d’âge +

    Par contre, regardons d’un peu plus près les coefficients de ce nouveau modèle. Nous allons voir que leur interprétation est légèrement différente.

    +
    ggcoef_model(mod3, exponentiate = TRUE)
    +
    +Représentation graphique des coefficients du modèle avec interaction simple entre le sexe et le groupe d’âge +

    Cette fois-ci, il n’y a plus de coefficients globaux pour la variable sexe ni pour grpage mais des coefficients pour chaque combinaison de ces deux variables.

    +
    plot(
    +  broken(mod3, nouvelle_observation, predict.function = betas),
    +  trans = logit
    +) + ylim(0, 1) + ylab("Probabilité de faire du sport")
    +
    Scale for 'y' is already present. Adding another scale
    +for 'y', which will replace the existing scale.
    +
    +Représentation graphique de l’estimation de la probabilité de faire du sport pour un homme de 40 ans +

    Cette fois-ci, le coefficient d’interaction fourrnit l’effet global du sexe et de l’âge, et non plus la modification de cette combinaison par rapport aux coefficients globaux. Leur sens est donc différent et il faudra les interpréter en conséquence.

    +
    +
    +

    Un second exemple d’interaction

    +

    Intéressons-nous maintenant à l’interaction entre le sexe et le niveau d’étude. L’effet du niveau d’étude diffère-t-il selon le sexe ?

    +
    mod4 <- glm(sport ~ sexe * etud + grpage + heures.tv + relig, data = d, family = binomial())
    +

    Regardons d’abord les effets.

    +
    plot(allEffects(mod4))
    +
    +Représentation graphique des effets du modèle avec interaction entre le sexe et le niveau d’étude +

    À première vue, l’effet du niveau d’étude semble être le même chez les hommes et chez les femmes. Ceci dit, cela serait peut être plus lisible si l’on superposait les deux sexe sur un même graphique. Nous allons utiliser la fonction ggeffect de l’extension ggeffects qui permets de récupérer les effets calculés avec effect dans un format utilisable avec ggplot2.

    +
    library(ggeffects)
    +plot(ggeffect(mod4, c("etud", "sexe")))
    +
    +Effets du niveau d’étude selon le sexe +

    Cela confirme ce que l’on suppose. Regardons les coefficients du modèle.

    +
    ggcoef_model(mod4, exponentiate = TRUE)
    +
    +Représentation graphique des coefficients du modèle avec interaction simple entre le sexe et le niveau d’étude +
    tbl_regression(mod4, exponentiate = TRUE)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +OR1 + +95% CI1 +p-valeur
    Sexe
    Femme
    Homme1,420,77 – 2,580,3
    Niveau d'étude
    Primaire
    Secondaire2,251,35 – 3,820,002
    Technique/Professionnel3,091,90 – 5,17<0,001
    Supérieur6,594,02 – 11,1<0,001
    Manquant5,212,42 – 11,4<0,001
    Groupe d'âges
    [16,25)
    [25,45)0,660,42 – 1,040,071
    [45,65)0,340,21 – 0,55<0,001
    [65,99]0,250,15 – 0,43<0,001
    Nombre d'heures passées devant la télévision par jour0,890,83 – 0,95<0,001
    Pratique religieuse
    Pratiquant regulier
    Pratiquant occasionnel0,980,67 – 1,420,9
    Appartenance sans pratique1,010,72 – 1,43>0,9
    Ni croyance ni appartenance0,830,56 – 1,210,3
    Rejet0,700,40 – 1,230,2
    NSP ou NVPR0,900,39 – 1,980,8
    Sexe * Niveau d'étude
    Homme * Secondaire1,380,65 – 2,930,4
    Homme * Technique/Professionnel0,870,44 – 1,750,7
    Homme * Supérieur1,010,49 – 2,07>0,9
    Homme * Manquant4,281,32 – 15,80,020
    +

    + 1 + + + OR = rapport de cotes, CI = intervalle de confiance +

    +
    +
    +

    Si les coefficients associés au niveau d’étude sont significatifs, ceux de l’interaction ne le sont pas (sauf sexeHomme:etudManquant) et celui associé au sexe, précédemment significatif ne l’est plus. Testons avec anova si l’interaction est belle et bien significative.

    +
    anova(mod4, test = "Chisq")
    +
    + +
    +

    L’interaction est bien significative mais faiblement. Vu que l’effet du niveau d’étude reste nénamoins très similaire selon le sexe, on peut se demander s’il est pertinent de la conserver.

    +
    +
    +

    Explorer les différentes interactions possibles

    +

    Il peut y avoir de multiples interactions dans un modèle, d’ordre 2 (entre deux variables) ou plus (entre trois variables ou plus). Il est dès lors tentant de tester les multiples interactions possibles de manière itératives afin d’identifier celles à retenir. C’est justement le but de la fonction glmulti de l’extension du même nom. glmulti permets de tester toutes les combinaisons d’interactions d’ordre 2 dans un modèle, en retenant le meilleur modèle à partir d’un critère spécifié (par défaut l’AIC). ATTENTION : le temps de calcul de glmulti peut-être long.

    +
    library(glmulti)
    +glmulti(sport ~ sexe + grpage + etud + heures.tv + relig, data = d, family = binomial())
    +
    Initialization...
    +TASK: Exhaustive screening of candidate set.
    +Fitting...
    +
    +After 50 models:
    +Best model: sport~1+grpage+heures.tv+sexe:heures.tv+grpage:heures.tv+etud:heures.tv
    +Crit= 2284.87861987263
    +Mean crit= 2406.80086471225
    +
    +After 100 models:
    +Best model: sport~1+etud+heures.tv+grpage:heures.tv
    +Crit= 2267.79462883348
    +Mean crit= 2360.46497457747
    +
    +After 150 models:
    +Best model: sport~1+grpage+etud+heures.tv+sexe:heures.tv
    +Crit= 2228.88574082404
    +Mean crit= 2286.60589884071
    +
    +After 200 models:
    +Best model: sport~1+grpage+etud+heures.tv+sexe:heures.tv
    +Crit= 2228.88574082404
    +Mean crit= 2254.99359340075
    +
    +After 250 models:
    +Best model: sport~1+sexe+grpage+etud+heures.tv+etud:sexe+sexe:heures.tv
    +Crit= 2226.00088609349
    +Mean crit= 2241.76611580481
    +
    +After 300 models:
    +Best model: sport~1+sexe+grpage+etud+heures.tv+grpage:sexe+sexe:heures.tv
    +Crit= 2222.67161519005
    +Mean crit= 2234.95020358944
    +

    On voit qu’au bout d’un moment, l’algorithme se statibilise autour d’un modèle comportant une interaction entre le sexe et l’âge d’une part et entre le sexe et le nombre d’heures passées quotidiennement devant la télé. On voit également que la variable religion a été retirée du modèle final.

    +
    best <- glm(sport ~ 1 + sexe + grpage + etud + heures.tv + grpage:sexe + sexe:heures.tv, data = d, family = binomial())
    +odds.ratio(best)
    +
    Waiting for profiling to be done...
    +
    + +
    +
    ggcoef_model(best, exponentiate = TRUE)
    +
    +Représentation graphique des coefficients du modèle avec interaction entre le sexe, le niveau d’étude et le nombre d’heures passées devant la télévision +
    plot(allEffects(best))
    +
    +Représentation graphique des effets du modèle avec interaction entre le sexe, le niveau d’étude et le nombre d’heures passées devant la télévision +
    +
    +
    +

    Pour aller plus loin

    +

    Il y a d’autres extensions dédiées à l’analyse des interactions d’un modèle, de même que de nombreux supports de cours en ligne dédiés à cette question.

    +

    On pourra en particulier se référer à la vignette inclue avec l’extension phia : https://cran.r-project.org/web/packages/phia/vignettes/phia.pdf.

    +
    +
    +
    +
      +
    1. Pour une présentation plus statistique et mathématique des effets d’interaction, on pourra se référer au cours de Jean-François Bickel disponible en ligne.

    2. +
    +
    + +
    +
    + + + +

    Multicolinéarité dans la régression

    + +
    + + + +
    +

    Ce chapitre est évoqué dans le webin-R #07 (régression logistique partie 2) sur YouTube.

    +
    +

    Dans une régression, la multicolinéarité est un problème qui survient lorsque certaines variables de prévision du modèle mesurent le même phénomène. Une multicolinéarité prononcée s’avère problématique, car elle peut augmenter la variance des coefficients de régression et les rendre instables et difficiles à interpréter. Les conséquences de coefficients instables peuvent être les suivantes :

    +
      +
    • les coefficients peuvent sembler non significatifs, même lorsqu’une relation significative existe entre le prédicteur et la réponse ;
    • +
    • les coefficients de prédicteurs fortement corrélés varieront considérablement d’un échantillon à un autre ;
    • +
    • lorsque des termes d’un modèle sont fortement corrélés, la suppression de l’un de ces termes aura une incidence considérable sur les coefficients estimés des autres. Les coefficients des termes fortement corrélés peuvent même présenter le mauvais signe.
    • +
    +

    La multicolinéarité n’a aucune incidence sur l’adéquation de l’ajustement, ni sur la qualité de la prévision. Cependant, les coefficients individuels associés à chaque variable explicative ne peuvent pas être interprétés de façon fiable.

    +
    +

    Définition

    +

    Au sens strict, on parle de multicolinéarité parfaite lorsqu’une des variables explicatives d’un modèle est une combinaison linéraire d’une ou plusieurs autres variables explicatives introduites dans le même modèle. L’absence de multicolinéarité parfaite est une des conditions requises pour pouvoir estimer un modèle linéaire et, par extension, un modèle linéaire généralisé (dont les modèles de régression logistique).

    +

    Dans les faits, une multicolinéarité parfaite n’est quasiment jamais observée. Mais une forte multicolinéarité entre plusieurs variables peut poser problème dans l’estimation et l’interprétation d’un modèle.

    +

    Une erreur fréquente est de confondre multicolinéarité et corrélation. Si des variables colinéaires sont de facto fortement corrélées entre elles, deux variables corrélées ne sont pas forcément colinéaires. En termes non statistiques, il y a colinéarité lorsque deux ou plusieurs variables mesurent la même chose.

    +

    Prenons un exemple. Nous étudions les complications après l’accouchement dans différentes maternités d’un pays en développement. On souhaite mettre dans le modèle, à la fois le milieu de résidence (urbain ou rural) et le fait qu’il y ait ou non un médecin dans la clinique. Or, dans la zone d’enquête, les maternités rurales sont dirigées seulement par des sage-femmes tandis que l’on trouve un médecin dans toutes les maternités urbaines sauf une. Dès lors, dans ce contexte précis, le milieu de résidence prédit presque totalement la présence d’un médecin et on se retrouve face à une multicolinéarité (qui serait même parfaite s’il n’y avait pas une clinique urbaine sans médecin). On ne peut donc distinguer l’effet de la présence d’un médecin de celui du milieu de résidence et il ne faut mettre qu’une seule de ces deux variables dans le modèle, sachant que du point de vue de l’interprétation elle capturera à la fois l’effet de la présence d’un médecin et celui du milieu de résidence.

    +

    Par contre, si dans notre région d’étude, seule la moitié des maternités urbaines disposait d’un médecin, alors le milieu de résidence n’aurait pas été suffisant pour prédire la présence d’un médecin. Certes, les deux variables seraient corrélées mais pas colinéaires. Un autre exemple de corrélation sans colinéarité, c’est la relation entre milieu de résidence et niveau d’instruction. Il y a une corrélation entre ces deux variables, les personnes résidant en ville étant généralement plus instruites. Cependant, il existe également des personnes non instruites en ville et des personnes instruites en milieu rural. Le milieu de résidence n’est donc pas suffisant pour prédire le niveau d’instruction.

    +
    +
    +

    Mesure de la colinéarité

    +

    Il existe différentes mesures de la multicolinéarité. L’extension mctest en fournie plusieurs, mais elle n’est utilisable que si l’ensemble des variables explicatives sont de type numérique.

    +

    L’approche la plus classique consiste à examiner les facteurs d’inflation de la variance (FIV) ou variance inflation factor (VIF) en anglais. Les FIV estimenent de combien la variance d’un coefficient est augmentée en raison d’une relation linéaire avec d’autres prédicteurs. Ainsi, un FIV de 1,8 nous dit que la variance de ce coefficient particulier est supérieure de 80 % à la variance que l’on aurait dû observer si ce facteur n’est absolument pas corrélé aux autres prédicteurs.

    +

    Si tous les FIV sont égaux à 1, il n’existe pas de multicolinéarité, mais si certains FIV sont supérieurs à 1, les prédicteurs sont corrélés. Il n’y a pas de consensus sur la valeur au-delà de laquelle on doit considérer qu’il y a multicolinéarité. Certains auteurs, comme Paul Allison1, disent regarder plus en détail les variables avec un FIV supérieur à 2,5. D’autres ne s’inquiètent qu’à partir de 5. Il n’existe pas de test statistique qui permettrait de dire s’il y a colinéarité ou non2.

    +

    L’extension car fournit une fonction vif permettant de calculer les FIV à partir d’un modèle. Elle implémente même une version généralisée permettant de considérer des facteurs catégoriels et des modèles linéaires généralisés comme la régression logistique.

    +

    Reprenons, pour exemple, un modèle logistique que nous avons déjà abordé dans d’autres chapitres.

    +
    library(questionr)
    +data(hdv2003)
    +d <- hdv2003
    +d$sexe <- relevel(d$sexe, "Femme")
    +d$grpage <- cut(d$age, c(16, 25, 45, 65, 99), right = FALSE, include.lowest = TRUE)
    +d$etud <- d$nivetud
    +levels(d$etud) <- c(
    +  "Primaire", "Primaire", "Primaire", "Secondaire", "Secondaire",
    +  "Technique/Professionnel", "Technique/Professionnel", "Supérieur"
    +)
    +d$etud <- addNAstr(d$etud, "Manquant")
    +mod <- glm(sport ~ sexe + grpage + etud + heures.tv + relig, data = d, family = binomial())
    +

    Le calcul des FIV se fait simplement en passant le modèle à la fonction vif.

    +
    library(car)
    +vif(mod)
    +
               GVIF Df GVIF^(1/(2*Df))
    +sexe      1.048  1           1.024
    +grpage    1.842  3           1.107
    +etud      1.848  4           1.080
    +heures.tv 1.062  1           1.031
    +relig     1.121  5           1.011
    +

    Dans notre exemple, tous les FIV sont proches de 1. Il n’y a donc pas de problème potentiel de colinéarité à explorer.

    +
    +
    +

    La multicolinéarité est-elle toujours un problème ?

    +

    Là encore, il n’y a pas de consensus sur cette question. Certains analystes considèrent que tout modèle où certains prédicteurs seraient colinéaires n’est pas valable. Dans un billet sur internet, Paul Allison évoque quant à lui des situations où la multicolinéarité peut être ignorée en toute sécurité. Le texte ci-dessous est une traduction du billet de Paul Allison.

    +

    1. Les variables avec des FIV élevés sont des variables de contrôle, et les variables d’intérêt n’ont pas de FIV élevés.

    +

    Voici le problème de la multicollinéarité : ce n’est un problème que pour les variables qui sont colinéaires. Il augmente les erreurs-types de leurs coefficients et peut rendre ces coefficients instables de plusieurs façons. Mais tant que les variables colinéaires ne sont utilisées que comme variables de contrôle, et qu’elles ne sont pas colinéaires avec vos variables d’intérêt, il n’y a pas de problème. Les coefficients des variables d’intérêt ne sont pas affectés et la performance des variables de contrôle n’est pas altérée.

    +

    Voici un exemple tiré de ces propres travaux : l’échantillon est constitué de collèges américains, la variable dépendante est le taux d’obtention de diplôme et la variable d’intérêt est un indicateur (factice) pour les secteurs public et privé. Deux variables de contrôle sont les scores moyens au SAT et les scores moyens à l’ACT pour l’entrée en première année. Ces deux variables ont une corrélation supérieure à ,9, ce qui correspond à des FIV d’au moins 5,26 pour chacune d’entre elles. Mais le FIV pour l’indicateur public/privé n’est que de 1,04. Il n’y a donc pas de problème à se préoccuper et il n’est pas nécessaire de supprimer l’un ou l’autre des deux contrôles, à condition que l’on ne cherche pas à interpréter ou comparer l’un par rapport à l’autre les coefficients de ces deux variables de contrôle.

    +

    2. Les FIV élevés sont causés par l’inclusion de puissances ou de produits d’autres variables.

    +

    Si vous spécifiez un modèle de régression avec x et x2, il y a de bonnes chances que ces deux variables soient fortement corrélées. De même, si votre modèle a x, z et xz, x et z sont susceptibles d’être fortement corrélés avec leur produit. Il n’y a pas de quoi s’inquiéter, car la valeur p de xz n’est pas affectée par la multicollinéarité. Ceci est facile à démontrer : vous pouvez réduire considérablement les corrélations en centrant les variables (c’est-à-dire en soustrayant leurs moyennes) avant de créer les puissances ou les produits. Mais la valeur p pour x2 ou pour xz sera exactement la même, que l’on centre ou non. Et tous les résultats pour les autres variables (y compris le R2 mais sans les termes d’ordre inférieur) seront les mêmes dans les deux cas. La multicollinéarité n’a donc pas de conséquences négatives.

    +

    3. Les variables avec des FIV élevés sont des variables indicatrices (factices) qui représentent une variable catégorielle avec trois catégories ou plus.

    +

    Si la proportion de cas dans la catégorie de référence est faible, les variables indicatrices auront nécessairement des FIV élevés, même si la variable catégorielle n’est pas associée à d’autres variables dans le modèle de régression.

    +

    Supposons, par exemple, qu’une variable de l’état matrimonial comporte trois catégories : actuellement marié, jamais marié et anciennement marié. Vous choisissez anciennement marié comme catégorie de référence, avec des variables d’indicateur pour les deux autres. Ce qui se passe, c’est que la corrélation entre ces deux indicateurs devient plus négative à mesure que la fraction de personnes dans la catégorie de référence diminue. Par exemple, si 45 % des personnes ne sont jamais mariées, 45 % sont mariées et 10 % sont anciennement mariées, les valeurs du FIV pour les personnes mariées et les personnes jamais mariées seront d’au moins 3,0.

    +

    Est-ce un problème ? Eh bien, cela signifie que les valeurs p des variables indicatrices peuvent être élevées. Mais le test global selon lequel tous les indicateurs ont des coefficients de zéro n’est pas affecté par des FIV élevés. Et rien d’autre dans la régression n’est affecté. Si vous voulez vraiment éviter des FIV élevés, il suffit de choisir une catégorie de référence avec une plus grande fraction des cas. Cela peut être souhaitable pour éviter les situations où aucun des indicateurs individuels n’est statistiquement significatif, même si l’ensemble des indicateurs est significatif.

    +
    +
    +
    +
      +
    1. https://statisticalhorizons.com/multicollinearity

    2. +
    3. Pour plus de détails, voir ce post de Davig Giles qui explique pourquoi ce n’est pas possible.

    4. +
    +
    + +
    +
    + + + +

    Quel type de modèles choisir ?

    + +
    + + + +
    +

    Ce chapitre est évoqué dans le webin-R #21 (trajectoires de soins : un exemple de données longitudinales 5 : modèle à observations répétée, régression logistique ordinale GEE & analyse de survie multi-états) sur YouTube.

    +
    +
    +

    Tableau synthétique

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Variable à expliquerType de modèleInterprétation des coefficientsModèle de baseÉchantillonnage complexeModèle mixteGEE*
    ContinueLinéairedirectestats::lm() lme4::lmer() 
     Gaussiendirectestats::glm (family = gaussian)survey::svyglm
    (family = gaussian)
    lme4::glmer (family = gaussian)geepack::geeglm (family = gaussian)
    +Comptage
    (nombre d’occurences)
    PoissonRR
    (risques relatifs)
    stats::glm (family = poisson)survey::svyglm
    (family = quasipoisson)
    lme4::glmer (family = poisson)geepack::geeglm (family = poisson)
     Binomial négatifRR
    (risques relatifs)
    MASS::glm.nb() +sjstats::svyglm.nb()

    svrepmisc::svynb() +
    lme4::glmer.nb() 
    Ratio / IncidencePoisson avec offset +IRR
    (incidence rate ratio)
    stats::glm (family = poisson)survey::svyglm
    (family = quasipoisson)
    lme4::glmer (family = poisson)geepack::geeglm (family = poisson)
     Binomial négatif avec offset +IRR
    (incidence rate ratio)
    MASS::glm.nb() +sjstats::svyglm.nb()

    svrepmisc::svynb() +
    lme4::glmer.nb() 
    +Binaire
    (oui / non)
    Logistique (logit)OR
    (odds ratio)
    stats::glm (family = binomial("logit"))survey::svyglm
    (family = quasibinomial("logit"))
    lme4::glmer (family = binomial("logit"))geepack::geeglm (family = binomial("logit"))
     Probitpas interprétable directementstats::glm (family = binomial("probit"))survey::svyglm
    (family = quasibinomial("probit"))
    lme4::glmer (family = binomial("probit"))geepack::geeglm (family = binomial("probit"))
     Log binomialRR
    (risques relatifs)
    PR
    (prevalence ratio)
    stats::glm (family = binomial("log"))survey::svyglm
    (family = quasibinomial("log"))
    lme4::glmer (family = binomial("log"))geepack::geeglm (family = binomial("log"))
    +Catégorielle ordinale
    (3 modalités ou plus)
    Régression logistique ordinaleOR
    (odds ratio)
    +ordinal::clm()

    VGAM::vglm (family = cumulative(parallel = TRUE))

    MASS::polr() +
    +svrepmisc::svyclm()

    svyVGAM::svy_vglm (family = cumulative(parallel = TRUE))

    survey::svyolr() +
    ordinal::clmm() +geepack::ordgee()

    multgee::ordLORgee() +
    +Catégorielle nominale
    (3 modalités ou plus)
    Régression logistique multinomialeOR
    (odds ratio)
    +nnet::multinom()

    VGAM::vglm (family = multinomial) +
    +svrepmisc::svymultinom()

    svyVGAM::svy_vglm (family = multinomial) +
     multgee::nomLORgee()
    +Survie
    (time to event)
    CoxHR
    (hazard ratio)
    survival::coxph()survey::svycoxph() +survival::coxph() avec un terme frailty

    coxme::coxme() +
    +survival::coxph() avec l’option cluster +
     Modèle à temps discret binomial cloglogHR
    (hazard ratio)
    stats::glm (family = binomial("cloglog"))survey::svyglm
    (family = quasibinomial("cloglog"))
    lme4::glmer (family = binomial("cloglog"))geepack::geeglm (family = binomial("cloglog"))
     Accelerated failure time (AFT)HR
    (hazard ratio)
    survival::survreg()survey::svysurvreg() +survival::survreg() avec un terme frailty + +survival::survreg() avec l’option cluster +
    Comptage avec surreprésentation de zérosZero-inflated PoissonOR & RRpscl::zeroinfl()sjstats::svyglm.zip()glmmTMB::glmmTMB(family = poisson) 
     Zero-inflated negtaive binomialOR & RRpscl::zeroinfl (dist = "negbin")sjstats::svyglm.zip (dist = "negbin")  
    +

    * Voir aussi gee::gee comme alternative à geepack::geeglm

    +
    +
    + +
    +
    + + + +

    Analyse de survie

    + +
    + + + +
    +

    Ce chapitre est évoqué dans le webin-R #15 (analyse de survie) sur YouTube.

    +

    Ce chapitre est évoqué dans le webin-R #18 (trajectoires de soins : un exemple de données longitudinales 2 : analyse de survie) sur YouTube.

    +

    Ce chapitre est évoqué dans le webin-R #21 (trajectoires de soins : un exemple de données longitudinales 5 : modèle à observations répétée, régression logistique ordinale GEE & analyse de survie multi-états) sur YouTube.

    +
    +
    +

    Ressources en ligne

    +

    L’extension centrale pour l’analyse de survie est survival.

    +

    Un très bon tutoriel (en anglais et en 3 étapes), introduisant les concepts de l’analyse de survie, des courbes de Kaplan-Meier et des modèles de Cox et leur mise en oeuvre pratique sous R est disponible en ligne :

    + +

    Pour un autre exemple (toujours en anglais) d’analyse de survie avec survival, on pourra se référer à https://rpubs.com/vinubalan/hrsurvival.

    +

    Pour représenter vos résultats avec ggplot2, on pourra avoir recours à l’extension survminer présentée en détails sur son site officiel (en anglais) : http://www.sthda.com/english/rpkgs/survminer/. On pourra également avoir recours à la fonction ggsurv de l’extension GGally présentée à l’adresse http://ggobi.github.io/ggally/#ggallyggsurv.

    +

    A noter, il est possible d’utiliser la fonction step sur un modèle de Cox, pour une sélection pas à pas d’un meilleur modèle basé sur une minimisation de l’AIC (voir le chapitre sur la régression logistique).

    +

    L’excellente extension broom peut également être utilisée sur des modèles de survie (Kaplan-Meier ou Cox) pour en convertir les résultats sous la forme d’un tableau de données.

    +

    Pour approfondir les possibilités offertes par l’extension survival, on pourra également consulter les différentes vignettes fournies avec l’extension (voir https://cran.r-project.org/package=survival).

    +
    +
    +

    Un exemple concret : mortalité infanto-juvénile

    +

    Dans cet exemple, nous allons utiliser le jeu de données fecondite fourni par l’extension questionr. Ce jeu de données comporte trois tableaux de données : menages, femmes et enfants.

    +

    Nous souhaitons étudier ici la survie des enfants entre la naissance et l’âge de 5 ans. Dans un premier temps, nous comparerons la survie des jeunes filles et des jeunes garçons. Dans un second temps, nous procéderons à une analyse multivariée en prenant en compte les variables suivantes :

    +
      +
    • sexe de l’enfant
    • +
    • milieu de résidence
    • +
    • niveau de vie du ménage
    • +
    • structure du ménage
    • +
    • niveau d’éducation de la mère
    • +
    • âge de la mère à la naissance de l’enfant
    • +
    • enfin, une variable un peu plus compliquée, à savoir si le rang de naissance de l’enfant (second, troisième, quatrième, etc.) est supérieur au nombre idéal d’enfants selon la mère.
    • +
    +

    Nous allons préparer les données selon deux approches : soit en utilisant l’extension data.table (voir le chapitre dédié à data.table), soit en utilisant l’extension dplyr (voir le chapitre sur dplyr).

    +

    Chargeons les données en mémoire et listons les variables disponibles.

    +
    library(questionr, quietly = TRUE)
    +data(fecondite)
    +lookfor(menages)
    +
    + +
    +
    lookfor(femmes)
    +
    + +
    +
    lookfor(enfants)
    +
    + +
    +
    +
    +

    Préparation des données avec data.table

    +

    Tout d’abord, regardons sous quel format elles sont stockées.

    +
    class(menages)
    +
    [1] "tbl_df"     "tbl"        "data.frame"
    +
    describe(menages)
    +
    [1814 obs. x 5 variables] tbl_df tbl data.frame
    +
    +$id_menage: Identifiant du ménage
    +numeric: 1 2 3 4 5 6 7 8 9 10 ...
    +min: 1 - max: 1814 - NAs: 0 (0%) - 1814 unique values
    +
    +$taille: Taille du ménage (nombre de membres)
    +numeric: 7 3 6 5 7 6 15 6 5 19 ...
    +min: 1 - max: 31 - NAs: 0 (0%) - 30 unique values
    +
    +$sexe_chef: Sexe du chef de ménage
    +labelled double: 2 1 1 1 1 2 2 2 1 1 ...
    +min: 1 - max: 2 - NAs: 0 (0%) - 2 unique values
    +2 value labels: [1] homme [2] femme
    +
    +$structure: Structure démographique du ménage
    +labelled double: 4 2 5 4 4 4 5 2 5 5 ...
    +min: 1 - max: 5 - NAs: 0 (0%) - 5 unique values
    +6 value labels: [0] pas d'adulte [1] un adulte [2] deux adultes de sexe opposé [3] deux adultes de même sexe [4] trois adultes ou plus avec lien de parenté [5] adultes sans lien de parenté
    +
    +$richesse: Niveau de vie (quintiles)
    +labelled double: 1 2 2 1 1 3 2 5 4 3 ...
    +min: 1 - max: 5 - NAs: 0 (0%) - 5 unique values
    +5 value labels: [1] très pauvre [2] pauvre [3] moyen [4] riche [5] très riche
    +

    Les tableaux de données sont au format tibble (c’est-à-dire sont de la classe tbl_df) et les variables catégorielles sont du type haven_labelled (voir le chapitre sur les vecteurs labellisés). Ce format correspond au format de données si on les avait importées depuis SPSS avec l’extension haven (voir le chapitre sur l’import de données).

    +

    En premier lieu, il nous faut convertir les tableaux de données au format data.table, ce qui peut se faire avec la fonction setDT. Par ailleurs, nous allons également charger en mémoire l’extension labelled pour la gestion des vecteurs labellisés.

    +
    library(labelled)
    +
    Warning: le package 'labelled' a été compilé avec la version
    +R 4.1.2
    +
    library(data.table)
    +setDT(menages)
    +setDT(femmes)
    +setDT(enfants)
    +

    En premier lieu, il nous faut calculer la durée d’observation des enfants, à savoir le temps passé entre la date de naissance (variable du fichier enfants) et la date de passation de l’entretien (fournie par le tableau de données femmes). Pour récupérer des variables du fichier femmes dans le fichier enfants, nous allons procéder à une fusion de table (voir le chapitre dédié). Pour le calcul de la durée d’observation, nous allons utiliser le package lubridate (voir le chapitre calculer un âge et celui sur la gestion des dates). Nous effectuerons l’analyse en mois (puisque l’âge au décès est connu en mois). Dès lors, la durée d’observation sera calculée en mois.

    +
    enfants <- merge(
    +  enfants,
    +  femmes[, .(id_femme, date_entretien)],
    +  by = "id_femme",
    +  all.x = TRUE
    +)
    +
    +# duree observation en mois
    +library(lubridate, quietly = TRUE)
    +enfants[, duree_observation := time_length(interval(date_naissance, date_entretien), unit = "months")]
    +

    ATTENTION : il y 11 enfants soi-disant nés après la date d’enquête ! Quelle que soit l’enquête, il est rare de ne pas observer d’incohérences. Dans le cas présent, il est fort possible que la date d’entretien puisse parfois être erronnée (par exemple si l’enquêteur a inscrit une date sur le questionnaire papier le jour du recensement du ménage mais n’ai pu effectué le questionnaire individuel que plus tard). Nous décidons ici de procéder à une correction en ajoutant un mois aux dates d’entretien problématiques. D’autres approches auraient pu être envisagées, comme par exemple exclure ces observations problématiques. Cependant, cela aurait impacté le calcul du range de naissance pour les autres enfants issus de la même mère. Quoiqu’il en soit, il n’y a pas de réponse unique. À vous de vous adapter au contexte particulier de votre analyse.

    +
    enfants[duree_observation < 0, date_entretien := date_entretien %m+% months(1)]
    +enfants[, duree_observation := time_length(interval(date_naissance, date_entretien), unit = "months")]
    +

    Regardons maintenant comment les âges au décès ont été collectés.

    +
    freq(enfants$age_deces)
    +
    + +
    +

    Les âges au décès sont ici exprimés en mois révolus. Les décès à un mois révolu correspondent à des décès entre 1 et 2 mois exacts. Par ailleurs, les durées d’observation que nous avons calculées avec time_length sont des durées exactes, c’est-à-dire avec la partie décimale. Pour une analyse de survie, on ne peut mélanger des durées exactes et des durées révolues. Trois approches peuvent être envisagées :

    +
      +
    1. faire l’analyse en mois révolus, auquel cas on ne gardera que la partie entière des durées d’observations avec la fonction trunc ;
    2. +
    3. considérer qu’un âge au décès de 3 mois révolus correspond en moyenne à 3,5 mois exacts et donc ajouter 0,5 à tous les âges révolus ;
    4. +
    5. imputer un âge au décès exact en distribuant aléatoirement les décès à 3 mois révolus entre 3 et 4 mois exacts, autrement dit en ajoutant aléatoirement une partie décimale aux âges révolus.
    6. +
    +

    Nous allons ici adopter la troisième approche en considérant que les décès se répartissent de manière uniforme au sein d’un même mois. Nous aurons donc recours à la fonction runif qui permets de générer des valeurs aléatoires entre 0 et 1 selon une distribustion uniforme.

    +
    enfants[, age_deces_impute := age_deces + runif(.N)]
    +

    Pour définir notre objet de survie, il nous faudra deux variables. Une première, temporelle, indiquant la durée à laquelle survient l’évènement étudié (ici le décès) pour ceux ayant vécu l’évènement et la durée d’observation pour ceux n’ayant pas vécu l’évènement (censure à droite). Par ailleurs, une seconde variable indiquant si les individus ont vécu l’évènement (0 pour non, 1 pour oui). Or, ici, la variable survie est codée 0 pour les décès et 1 pour ceux ayant survécu. Pour plus de détails, voir l’aide de la fonction Surv.

    +
    enfants[, deces := 0]
    +enfants[survie == 0, deces := 1]
    +var_label(enfants$deces) <- "Est décédé ?"
    +val_labels(enfants$deces) <- c(non = 0, oui = 1)
    +
    +enfants[, time := duree_observation]
    +enfants[deces == 1, time := age_deces_impute]
    +

    Occupons-nous maintenant des variables explicatives que nous allons inclure dans l’analyse. Tout d’abord, ajoutons à la table enfants les variables nécessaires des tables femmes et menages. Notons qu’il nous faudra importer id_menage de la table femmes pour pouvoir fusionner ensuite la table enfants avec la table menages. Par ailleurs, pour éviter une confusion sur la variable date_naissance, nous renommons à la volée cette variable de la table femmes en date_naissance_mere.

    +
    enfants <- merge(
    +  enfants,
    +  femmes[, .(
    +    id_femme, id_menage, milieu, educ, 
    +    date_naissance_mere = date_naissance, nb_enf_ideal
    +  )],
    +  by = "id_femme",
    +  all.x = TRUE
    +)
    +enfants <- merge(
    +  enfants,
    +  menages[, .(id_menage, structure, richesse)],
    +  by = "id_menage",
    +  all.x = TRUE
    +)
    +

    Les variables catégorielles sont pour l’heure sous formes de vecteurs labellisés. Or, dans un modèle, il est impératif de les convertir en facteurs pour qu’elles soient bien traitées comme des variables catégorielles (autrement elles seraient traitées comme des variables continues). On aura donc recours à la fonction to_factor de l’extension labelled.

    +
    enfants[, sexe := to_factor(sexe)]
    +enfants[, richesse := to_factor(richesse)]
    +

    Regardons plus attentivement, la variable structure.

    +
    freq(enfants$structure)
    +
    + +
    +

    Tout d’abord, la modalité pas d’adulte n’est pas représentée dans l’échantillon. On aura donc recours à l’argument drop_unused_labels pour ne pas conserver cette modalité. Par ailleurs, nous considérons que la situation familiale à partir de laquelle nous voudrons comparer les autres dans notre modèle, donc celle qui doit être considérée comme la modalité de référence, est celle du ménage nucléaire. Cette modalité (deux adultes de sexe opposé) n’étant pas la première, nous aurons recours à la fonction relevel.

    +
    enfants[, structure := to_factor(structure, drop_unused_labels = TRUE)]
    +enfants[, structure := relevel(structure, "deux adultes de sexe opposé")]
    +

    Regardons la variable educ.

    +
    freq(enfants$educ)
    +
    + +
    +

    La modalité supérieur est peu représentée dans notre échantillon. Nous allons la fusionner avec la modalité secondaire (voir la section Regrouper les modalités d’une variable du chapitre Recodage).

    +
    enfants[, educ2 := educ]
    +enfants[educ == 3, educ2 := 2]
    +val_label(enfants$educ2, 2) <- "secondaire ou plus"
    +val_label(enfants$educ2, 3) <- NULL
    +enfants[, educ2 := to_factor(educ2)]
    +freq(enfants$educ2)
    +
    + +
    +

    Calculons maintenant l’âge de la mère à la naissance de l’enfant (voir le chapitre Calculer un âge) et découpons le en groupes d’âges (voir la section Découper une variable numérique en classes du chapitre Recodage).

    +
    enfants[, age_mere_naissance := time_length(
    +  interval(date_naissance_mere, date_naissance), 
    +  unit = "years"
    +  )]
    +
    +enfants$gpage_mere_naissance <- cut(
    +  enfants$age_mere_naissance, 
    +  include.lowest = TRUE, right = FALSE,
    +  breaks=c(13, 20, 30, 50)
    +)
    +levels(enfants$gpage_mere_naissance) <- c(
    +  "19 ou moins", "20-29", "30 et plus"
    +)
    +enfants$gpage_mere_naissance <- relevel(enfants$gpage_mere_naissance, "20-29")
    +freq(enfants$gpage_mere_naissance)
    +
    + +
    +

    Reste à calculer si le rang de naissance de l’enfant est supérieur au nombre idéal d’enfants tel que défini par la mère. On aura recours à la fonction rank appliquée par groupe (ici calculé séparément pour chaque mère). L’argument ties.method permet d’indiquer comment gérer les égalités (ici les naissances multiples, e.g. les jumeaux). Comme nous voulons comparer le rang de l’enfant au nombre idéal d’enfants, nous allons retenir la méthode "max" pour obtenir, dans le cas présent, le nombre total d’enfants déjà nés1. Avant de calculer un rang, il est impératif de trier préalablement le tableau (voir le chapitre Tris).

    +
    setorder(enfants, id_femme, date_naissance)
    +enfants[, rang := rank(date_naissance, ties.method = "max"), by = id_femme]
    +enfants[, rang_apres_ideal := "non"]
    +# note: unclass() requis en raison d'un bug non corrigé dans haven empéchant de comparer haven_labelled_spss et integer
    +enfants[rang > unclass(nb_enf_ideal), rang_apres_ideal := "oui"]
    +enfants[, rang_apres_ideal := factor(rang_apres_ideal)]
    +enfants[, rang_apres_ideal := relevel(rang_apres_ideal, "non")]
    +
    +
    +

    Préparation des données avec dplyr

    +

    Tout d’abord, regardons sous quel format elles sont stockées.

    +
    data(fecondite)
    +class(menages)
    +
    [1] "tbl_df"     "tbl"        "data.frame"
    +
    describe(menages)
    +
    [1814 obs. x 5 variables] tbl_df tbl data.frame
    +
    +$id_menage: Identifiant du ménage
    +numeric: 1 2 3 4 5 6 7 8 9 10 ...
    +min: 1 - max: 1814 - NAs: 0 (0%) - 1814 unique values
    +
    +$taille: Taille du ménage (nombre de membres)
    +numeric: 7 3 6 5 7 6 15 6 5 19 ...
    +min: 1 - max: 31 - NAs: 0 (0%) - 30 unique values
    +
    +$sexe_chef: Sexe du chef de ménage
    +labelled double: 2 1 1 1 1 2 2 2 1 1 ...
    +min: 1 - max: 2 - NAs: 0 (0%) - 2 unique values
    +2 value labels: [1] homme [2] femme
    +
    +$structure: Structure démographique du ménage
    +labelled double: 4 2 5 4 4 4 5 2 5 5 ...
    +min: 1 - max: 5 - NAs: 0 (0%) - 5 unique values
    +6 value labels: [0] pas d'adulte [1] un adulte [2] deux adultes de sexe opposé [3] deux adultes de même sexe [4] trois adultes ou plus avec lien de parenté [5] adultes sans lien de parenté
    +
    +$richesse: Niveau de vie (quintiles)
    +labelled double: 1 2 2 1 1 3 2 5 4 3 ...
    +min: 1 - max: 5 - NAs: 0 (0%) - 5 unique values
    +5 value labels: [1] très pauvre [2] pauvre [3] moyen [4] riche [5] très riche
    +

    Les tableaux de données sont déjà au format tibble (c’est-à-dire sont de la classe tbl_df)2 et les variables catégorielles sont du type labelled (voir le chapitre sur les vecteurs labellisés). Ce format correspond au format de données si on les avait importées depuis SPSS avec l’extension haven (voir le chapitre sur l’import de données).

    +

    Nous allons charger en mémoire l’extension labelled pour la gestion des vecteurs labellisés en plus de dplyr.

    +
    library(dplyr)
    +library(labelled)
    +

    En premier lieu, il nous faut calculer la durée d’observation des enfants, à savoir le temps passé entre la date de naissance (variable du fichier enfants) et la date de passation de l’entretien (fournie par le tableau de données femmes). Pour récupérer des variables du fichier femmes dans le fichier enfants, nous allons procéder à une fusion de table (voir le chapitre dédié). Pour le calcul de la durée d’observation, nous allons utiliser le package lubridate (voir le chapitre calculer un âge et celui sur la gestion des dates). Nous effectuerons l’analyse en mois (puisque l’âge au décès est connu en mois). Dès lors, la durée d’observation sera calculée en mois.

    +

    ATTENTION : les étiquettes de valeurs sont le plus souvent perdus lors des fusions de tables avec dplyr. Pour les récupérer après fusion, nous allons conserver une version originale du tableau de données enfants et utiliser la fonction copy_labels_from de l’extension labelled.

    +
    enfants_original <- enfants
    +
    +library(lubridate)
    +enfants <- enfants %>%
    +  left_join(
    +    femmes %>% select(id_femme, date_entretien),
    +    by = "id_femme"
    +  ) %>%
    +  copy_labels_from(enfants_original) %>%
    +  mutate(duree_observation = time_length(
    +    interval(date_naissance, date_entretien), 
    +    unit = "months"
    +  ))
    +

    ATTENTION : il y 11 enfants soi-disant nés après la date d’enquête ! Quelle que soit l’enquête, il est rare de ne pas observer d’incohérences. Dans le cas présent, il est fort possible que la date d’entretien puisse parfois être erronnée (par exemple si l’enquêteur a inscrit une date sur le questionnaire papier le jour du recensement du ménage mais n’ai pu effectué le questionnaire individuel que plus tard). Nous décidons ici de procéder à une correction en ajoutant un mois aux dates d’entretien problématiques. D’autres approches auraient pu être envisagées, comme par exemple exclure ces observations problématiques. Cependant, cela aurait impacté le calcul du range de naissance pour les autres enfants issus de la même mère. Quoiqu’il en soit, il n’y a pas de réponse unique. À vous de vous adapter au contexte particulier de votre analyse.

    +
    enfants$date_entretien[enfants$duree_observation < 0] <-
    +  enfants$date_entretien[enfants$duree_observation < 0] %m+% months(1)
    +enfants <- enfants %>%
    +  mutate(duree_observation = time_length(
    +    interval(date_naissance, date_entretien), 
    +    unit = "months"
    +  ))
    +

    Regardons maintenant comment les âges au décès ont été collectés.

    +
    freq(enfants$age_deces)
    +
    + +
    +

    Les âges au décès sont ici exprimés en mois révolus. Les décès à un mois révolu correspondent à des décès entre 1 et 2 mois exacts. Par ailleurs, les durées d’observation que nous avons calculées avec time_length sont des durées exactes, c’est-à-dire avec la partie décimale. Pour une analyse de survie, on ne peut mélanger des durées exactes et des durées révolues. Trois approches peuvent être envisagées :

    +
      +
    1. faire l’analyse en mois révolus, auquel cas on ne gardera que la partie entière des durées d’observations avec la fonction trunc ;
    2. +
    3. considérer qu’un âge au décès de 3 mois révolus correspond en moyenne à 3,5 mois exacts et donc ajouter 0,5 à tous les âges révolus ;
    4. +
    5. imputer un âge au décès exact en distribuant aléatoirement les décès à 3 mois révolus entre 3 et 4 mois exacts, autrement dit en ajoutant aléatoirement une partie décimale aux âges révolus.
    6. +
    +

    Nous allons ici adopter la troisième approche en considérant que les décès se répartissent de manière uniforme au sein d’un même mois. Nous aurons donc recours à la fonction runif qui permets de générer des valeurs aléatoires entre 0 et 1 selon une distribustion uniforme.

    +
    enfants <- enfants %>%
    +  dplyr::mutate(age_deces_impute = age_deces + runif(n()))
    +

    Pour définir notre objet de survie, il nous faudra deux variables. Une première, temporelle, indiquant la durée à laquelle survient l’évènement étudié (ici le décès) pour ceux ayant vécu l’évènement et la durée d’observation pour ceux n’ayant pas vécu l’évènement (censure à droite). Par ailleurs, une seconde variable indiquant si les individus ont vécu l’évènement (0 pour non, 1 pour oui). Or, ici, la variable survie est codée 0 pour les décès et 1 pour ceux ayant survécu. Pour plus de détails, voir l’aide de la fonction Surv.

    +
    enfants <- enfants %>%
    +  mutate(deces = if_else(survie == 0, 1, 0)) %>%
    +  set_variable_labels(deces = "Est décédé ?") %>%
    +  set_value_labels(deces = c(non = 0, oui = 1)) %>%
    +  mutate(time = if_else(deces == 1, age_deces_impute, duree_observation))
    +

    Occupons-nous maintenant des variables explicatives que nous allons inclure dans l’analyse. Tout d’abord, ajoutons à la table enfants les variables nécessaires des tables femmes et menages. Notons qu’il nous faudra importer id_menage de la table femmes pour pouvoir fusionner ensuite la table enfants avec la table menages. Par ailleurs, pour éviter une confusion sur la variable date_naissance, nous renommons à la volée cette variable de la table femmes en date_naissance_mere.

    +
    enfants <- enfants %>%
    +  left_join(
    +    select(femmes,
    +      id_femme, id_menage, milieu, educ, 
    +      date_naissance_mere = date_naissance, nb_enf_ideal
    +    ),
    +    by = "id_femme"
    +  ) %>%
    +  left_join(
    +    select(menages, id_menage, structure, richesse),
    +    by = "id_menage"
    +  ) %>%
    +  copy_labels_from(enfants_original) %>%
    +  copy_labels_from(femmes) %>%
    +  copy_labels_from(menages)
    +

    Les variables catégorielles sont pour l’heure sous formes de vecteurs labellisés. Or, dans un modèle, il est impératif de les convertir en facteurs pour qu’elles soient bien traitées comme des variables catégorielles (autrement elles seraient traitées comme des variables continues). On aura donc recours à la fonction to_factor de l’extension labelled.

    +
    enfants <- enfants %>%
    +  mutate(sexe = to_factor(sexe), richesse = to_factor(richesse))
    +

    Regardons plus attentivement, la variable structure.

    +
    freq(enfants$structure)
    +
    + +
    +

    Tout d’abord, la modalité pas d’adulte n’est pas représentée dans l’échantillon. On aura donc recours à l’argument drop_unused_labels pour ne pas conserver cette modalité. Par ailleurs, nous considérons que la situation familiale à partir de laquelle nous voudrons comparer les autres dans notre modèle, donc celle qui doit être considérée comme la modalité de référence, est celle du ménage nucléaire. Cette modalité (deux adultes de sexe opposé) n’étant pas la première, nous aurons recours à la fonction relevel{data-pkg = “stats”}.

    +
    enfants <- enfants %>%
    +  mutate(structure = relevel(
    +    to_factor(structure, drop_unused_labels = TRUE),
    +    "deux adultes de sexe opposé"
    +  ))
    +

    Regardons la variable educ.

    +
    freq(enfants$educ)
    +
    + +
    +

    La modalité supérieur est peu représentée dans notre échantillon. Nous allons la fusionner avec la modalité secondaire (voir la section Regrouper les modalités d’une variable du chapitre Recodage).

    +
    enfants <- enfants %>%
    +  mutate(educ2 = ifelse(educ == 3, 2, educ)) %>%
    +  set_value_labels(educ2 = c(
    +    aucun = 0,
    +    primaire = 1,
    +    "secondaire ou plus" = 2
    +  )) %>%
    +  mutate(educ2 = to_factor(educ2))
    +freq(enfants$educ2)
    +
    + +
    +

    Calculons maintenant l’âge de la mère à la naissance de l’enfant (voir le chapitre Caluler un âge) et découpons le en groupes d’âges (voir la section Découper une variable numérique en classes du chapitre Recodage).

    +
    enfants <- enfants %>%
    +  mutate(
    +    age_mere_naissance = time_length(
    +      interval(date_naissance_mere, date_naissance), 
    +      unit = "years"
    +    ),
    +    gpage_mere_naissance = cut(
    +      age_mere_naissance, 
    +      include.lowest = TRUE, right = FALSE,
    +      breaks=c(13, 20, 30, 50)
    +    )
    +  )
    +  
    +levels(enfants$gpage_mere_naissance) <- c(
    +  "19 ou moins", "20-29", "30 et plus"
    +)
    +enfants$gpage_mere_naissance <- relevel(enfants$gpage_mere_naissance, "20-29")
    +freq(enfants$gpage_mere_naissance)
    +
    + +
    +

    Reste à calculer si le rang de naissance de l’enfant est supérieur au nombre idéal d’enfants tel que défini par la mère. On aura recours à la fonction rank appliquée par groupe (ici calculé séparément pour chaque mère). L’argument ties.method permet d’indiquer comment gérer les égalités (ici les naissances multiples, e.g. les jumeaux). Comme nous voulons comparer le rang de l’enfant au nombre idéal d’enfants, nous allons retenir la méthode "max" pour obtenir, dans le cas présent, le nombre total d’enfants déjà nés3. Avant de calculer un rang, il est impératif de trier préalablement le tableau (voir le chapitre Tris).

    +
    enfants <- enfants %>%
    +  arrange(id_femme, date_naissance) %>%
    +  group_by(id_femme) %>%
    +  mutate(
    +    rang = rank(date_naissance, ties.method = "max"),
    +    rang_apres_ideal = ifelse(rang > as.integer(nb_enf_ideal), "oui", "non"),
    +    rang_apres_ideal = factor(rang_apres_ideal, levels = c("non", "oui"))
    +  )
    +
    +
    +

    Kaplan-Meier

    +

    La courbe de survie de Kaplan-Meier s’obtient avec la fonction survfit de l’extension survival.

    +
    library(survival)
    +km_global <- survfit(Surv(time, deces) ~ 1, data = enfants)
    +km_global
    +
    Call: survfit(formula = Surv(time, deces) ~ 1, data = enfants)
    +
    +        n events median 0.95LCL 0.95UCL
    +[1,] 1584    142     NA      NA      NA
    +

    Pour la représenter, on pourra avoir recours à la fonction ggsurvplot de l’extension survminer.

    +
    library(survminer, quietly = TRUE)
    +
    
    +Attachement du package : 'survminer'
    +
    L'objet suivant est masqué depuis 'package:survival':
    +
    +    myeloma
    +
    ggsurvplot(km_global)
    +
    +Courbe de survie de Kaplan-Meier +

    On peut facilement représenter à la place la courbe cumulée des évènements (l’inverse de la courbe de survie) et la table des effectifs en fonction du temps.

    +
    ggsurvplot(km_global, fun = "event", risk.table = TRUE, surv.scale = "percent")
    +
    +Courbe cumulée des évènements et table des effectifs +

    Pour comparer deux groupes (ici les filles et les garçons), il suffit d’indiquer la variable de comparaison à survfit.

    +
    km_sexe <- survfit(Surv(time, deces) ~ sexe, data = enfants)
    +km_sexe
    +
    Call: survfit(formula = Surv(time, deces) ~ sexe, data = enfants)
    +
    +                n events median 0.95LCL 0.95UCL
    +sexe=masculin 762     94     NA      NA      NA
    +sexe=féminin  822     48     NA      NA      NA
    +

    La fonction survdiff permets de calculer le test du logrank afin de comparer des courbes de survie. La mortalité infanto-juvénile diffère-t-elle significativement selon le sexe de l’enfant ?

    +
    survdiff(Surv(time, deces) ~ sexe, data = enfants)
    +
    Call:
    +survdiff(formula = Surv(time, deces) ~ sexe, data = enfants)
    +
    +                N Observed Expected (O-E)^2/E (O-E)^2/V
    +sexe=masculin 762       94     66.2      11.6      21.8
    +sexe=féminin  822       48     75.8      10.2      21.8
    +
    + Chisq= 21.8  on 1 degrees of freedom, p= 3e-06 
    +

    Une fois encore, on aura recours à ggsurvplot pour représenter les courbes de survie.

    +
    ggsurvplot(km_sexe, conf.int = TRUE, risk.table = TRUE, pval = TRUE, data = enfants)
    +
    +Courbes de Kaplan-Meier selon le sexe +
    +
    +
    +

    Modèle de Cox

    +

    Un modèle de Cox se calcule aisément avec coxph{survival}.

    +
    mod1 <- coxph(
    +  Surv(time, deces) ~ sexe + milieu + richesse + 
    +  structure + educ2 + gpage_mere_naissance + rang_apres_ideal, 
    +  data = enfants
    +)
    +mod1
    +
    Call:
    +coxph(formula = Surv(time, deces) ~ sexe + milieu + richesse + 
    +    structure + educ2 + gpage_mere_naissance + rang_apres_ideal, 
    +    data = enfants)
    +
    +                                                         coef
    +sexeféminin                                         -0.809568
    +milieu                                               0.656241
    +richessepauvre                                      -0.082223
    +richessemoyen                                        0.318645
    +richesseriche                                        0.353483
    +richessetrès riche                                   0.464590
    +structureun adulte                                  -0.150231
    +structuredeux adultes de même sexe                   0.604592
    +structuretrois adultes ou plus avec lien de parenté  0.049430
    +structureadultes sans lien de parenté               -0.131369
    +educ2primaire                                       -0.030251
    +educ2secondaire ou plus                             -0.203903
    +gpage_mere_naissance19 ou moins                     -0.310248
    +gpage_mere_naissance30 et plus                      -0.002586
    +rang_apres_idealoui                                  1.355106
    +                                                    exp(coef)
    +sexeféminin                                          0.445050
    +milieu                                               1.927533
    +richessepauvre                                       0.921066
    +richessemoyen                                        1.375263
    +richesseriche                                        1.424019
    +richessetrès riche                                   1.591361
    +structureun adulte                                   0.860509
    +structuredeux adultes de même sexe                   1.830506
    +structuretrois adultes ou plus avec lien de parenté  1.050672
    +structureadultes sans lien de parenté                0.876895
    +educ2primaire                                        0.970202
    +educ2secondaire ou plus                              0.815541
    +gpage_mere_naissance19 ou moins                      0.733265
    +gpage_mere_naissance30 et plus                       0.997417
    +rang_apres_idealoui                                  3.877173
    +                                                     se(coef)
    +sexeféminin                                          0.177806
    +milieu                                               0.269928
    +richessepauvre                                       0.250417
    +richessemoyen                                        0.247868
    +richesseriche                                        0.298425
    +richessetrès riche                                   0.428583
    +structureun adulte                                   0.600329
    +structuredeux adultes de même sexe                   0.376445
    +structuretrois adultes ou plus avec lien de parenté  0.196666
    +structureadultes sans lien de parenté                0.305478
    +educ2primaire                                        0.205751
    +educ2secondaire ou plus                              0.366889
    +gpage_mere_naissance19 ou moins                      0.268062
    +gpage_mere_naissance30 et plus                       0.191557
    +rang_apres_idealoui                                  0.602401
    +                                                         z
    +sexeféminin                                         -4.553
    +milieu                                               2.431
    +richessepauvre                                      -0.328
    +richessemoyen                                        1.286
    +richesseriche                                        1.184
    +richessetrès riche                                   1.084
    +structureun adulte                                  -0.250
    +structuredeux adultes de même sexe                   1.606
    +structuretrois adultes ou plus avec lien de parenté  0.251
    +structureadultes sans lien de parenté               -0.430
    +educ2primaire                                       -0.147
    +educ2secondaire ou plus                             -0.556
    +gpage_mere_naissance19 ou moins                     -1.157
    +gpage_mere_naissance30 et plus                      -0.014
    +rang_apres_idealoui                                  2.250
    +                                                           p
    +sexeféminin                                         5.29e-06
    +milieu                                                0.0151
    +richessepauvre                                        0.7427
    +richessemoyen                                         0.1986
    +richesseriche                                         0.2362
    +richessetrès riche                                    0.2784
    +structureun adulte                                    0.8024
    +structuredeux adultes de même sexe                    0.1083
    +structuretrois adultes ou plus avec lien de parenté   0.8016
    +structureadultes sans lien de parenté                 0.6672
    +educ2primaire                                         0.8831
    +educ2secondaire ou plus                               0.5784
    +gpage_mere_naissance19 ou moins                       0.2471
    +gpage_mere_naissance30 et plus                        0.9892
    +rang_apres_idealoui                                   0.0245
    +
    +Likelihood ratio test=38.16  on 15 df, p=0.0008546
    +n= 1584, number of events= 142 
    +

    De nombreuses variables ne sont pas significatives. Voyons si nous pouvons, avec la fonction step, améliorer notre modèle par minimisation de l’AIC ou Akaike Information Criterion (voir la section Sélection de modèles du chapitre sur la Régression logistique).

    +
    mod2 <- step(mod1)
    +
    Start:  AIC=2027.07
    +Surv(time, deces) ~ sexe + milieu + richesse + structure + educ2 + 
    +    gpage_mere_naissance + rang_apres_ideal
    +
    +                       Df    AIC
    +- structure             4 2022.0
    +- richesse              4 2022.7
    +- educ2                 2 2023.4
    +- gpage_mere_naissance  2 2024.6
    +<none>                    2027.1
    +- rang_apres_ideal      1 2028.6
    +- milieu                1 2031.3
    +- sexe                  1 2047.1
    +
    +Step:  AIC=2022.01
    +Surv(time, deces) ~ sexe + milieu + richesse + educ2 + gpage_mere_naissance + 
    +    rang_apres_ideal
    +
    +                       Df    AIC
    +- richesse              4 2017.0
    +- educ2                 2 2018.2
    +- gpage_mere_naissance  2 2019.5
    +<none>                    2022.0
    +- rang_apres_ideal      1 2023.4
    +- milieu                1 2025.6
    +- sexe                  1 2042.2
    +
    +Step:  AIC=2017
    +Surv(time, deces) ~ sexe + milieu + educ2 + gpage_mere_naissance + 
    +    rang_apres_ideal
    +
    +                       Df    AIC
    +- educ2                 2 2013.3
    +- gpage_mere_naissance  2 2014.8
    +<none>                    2017.0
    +- rang_apres_ideal      1 2018.0
    +- milieu                1 2018.8
    +- sexe                  1 2037.6
    +
    +Step:  AIC=2013.28
    +Surv(time, deces) ~ sexe + milieu + gpage_mere_naissance + rang_apres_ideal
    +
    +                       Df    AIC
    +- gpage_mere_naissance  2 2011.2
    +<none>                    2013.3
    +- rang_apres_ideal      1 2014.3
    +- milieu                1 2015.7
    +- sexe                  1 2033.8
    +
    +Step:  AIC=2011.17
    +Surv(time, deces) ~ sexe + milieu + rang_apres_ideal
    +
    +                   Df    AIC
    +<none>                2011.2
    +- rang_apres_ideal  1 2012.3
    +- milieu            1 2013.9
    +- sexe              1 2031.6
    +

    On peut obtenir facilement les coefficients du modèle avec l’excellente fonction tidy de l’extension broom. Ne pas oublier de préciser exponentiate = TRUE. En effet, dans le cas d’un modèle de Cox, l’exponentiel des coefficients corresponds au ratio des risques instantannés ou hazard ratio (HR) en anglais.

    +
    library(broom, quietly = TRUE)
    +
    Warning: le package 'broom' a été compilé avec la version R
    +4.1.2
    +
    tidy(mod2, exponentiate = TRUE)
    +
    + +
    +

    Pour représenter ces rapports de risque, on peut ici encore avoir recours à la fonction ggcoef_model de l’extension GGally.

    +
    library(GGally, quietly = TRUE)
    +ggcoef_model(mod2, exponentiate = TRUE)
    +
    +Coefficients du modèle avec ggcoef_model +

    L’extension survminer fournit également une fonction ggforest qui permet de représenter de manière plus esthétique et complète les coefficients d’un modèle de Cox.

    +
    ggforest(mod2)
    +
    +Coefficients du modèle avec ggforest +
    +
    +
    +

    Vérification de la validité du modèle

    +

    Un modèle de Cox n’est valable que sous l’hypothèse de la proportionnalité des risques relatifs. Selon cette hypothèse les résidus de Schoenfeld ne dépendent pas du temps. Cette hypothèse peut être testée avec la fonction cox.zph.

    +
    test <- cox.zph(mod2)
    +test
    +
                     chisq df    p
    +sexe             0.505  1 0.48
    +milieu           0.136  1 0.71
    +rang_apres_ideal 0.163  1 0.69
    +GLOBAL           0.805  3 0.85
    +

    Une valeur de p inférieure à 5 % indique que l’hypothèse n’est pas vérifiée. Il apparaît que p est supérieur à 5 % globalement et pour chaque variable prise individuellement. Notre modèle est donc valide.

    +

    Il est possible de représenter la distribution des résidus de Schoenfeld à l’aide de ggcoxzph de l’extension survminer, afin de voir si leur répartition change au cours du temps.

    +
    ggcoxzph(test)
    +
    Warning in regularize.values(x, y, ties, missing(ties),
    +na.rm = na.rm): collapsing to unique 'x' values
    +
    +Résidus de Schoenfeld +
    +
    +
    +

    Données pondérées

    +

    Si vous utilisez des données pondérées avec un plan d’échantillonnage complexe (voir le chapitre dédié), vous pouvez utilisez les fonctions suivantes de l’extension survey :

    +
      +
    • +svykm pour estimer une courbe de survie de Kaplan-Meier ;
    • +
    • +svycoxph pour un modèle de Cox.
    • +
    +

    Dans les deux cas, pensez à ajouter l’option se = TRUE pour que les erreurs standards soient calculées (et que les intervalles de confiance puissent être générés).

    +
    +
    +
    +
      +
    1. Ici, pour plus de simplicité, nous n’avons pas pris en compte les décès éventuels des enfants de rang inférieur avant la naissance considérée.

    2. +
    3. Si cela n’avait pas été le cas, nous aurions eu recours à la fonction tbl_df.

    4. +
    5. Ici, pour plus de simplicité, nous n’avons pas pris en compte les décès éventuels des enfants de rang inférieur avant la naissance considérée.

    6. +
    +
    + +
    +
    + + + +

    Analyse de séquences

    + +
    + + + +
    +

    Ce chapitre est évoqué dans le webin-R #16 (analyse de séquences) sur YouTube.

    +

    Ce chapitre est évoqué dans le webin-R #19 (trajectoires de soins : un exemple de données longitudinales 3 : analyse de séquences) sur YouTube.

    +
    +
    +

    La version originale de ce chapitre est une reprise, avec l’aimable autorisation de son auteur, d’un article de Nicolas Robette intitulé L’analyse de séquences : une introduction avec le logiciel R et le package TraMineR et publié sur le blog Quanti (http://quanti.hypotheses.org/686/).

    +
    +

    Depuis les années 1980, l’étude quantitative des trajectoires biographiques (life course analysis) a pris une ampleur considérable dans le champ des sciences sociales. Les collectes de données micro-individuelles longitudinales se sont développées, principalement sous la forme de panels ou d’enquêtes rétrospectives. Parallèlement à cette multiplication des données disponibles, la méthodologie statistique a connu de profondes évolutions. L’analyse des biographies (event history analysis) — qui ajoute une dimension diachronique aux modèles économétriques mainstream — s’est rapidement imposée comme l’approche dominante : il s’agit de modéliser la durée des situations ou le risque d’occurrence des événements.

    +
    +

    L’analyse de séquences

    +

    Cependant, ces dernières années ont vu la diffusion d’un large corpus de méthodes descriptives d’analyse de séquences, au sein desquelles l’appariement optimal (optimal matching) occupe une place centrale1. L’objectif principal de ces méthodes est d’identifier — dans la diversité d’un corpus de séquences constituées de séries d’états successifs — les régularités, les ressemblances, puis le plus souvent de construire des typologies de « séquences-types ». L’analyse de séquences constitue donc un moyen de décrire mais aussi de mieux comprendre le déroulement de divers processus.

    +

    La majeure partie des applications de l’analyse de séquences traite de trajectoires biographiques ou de carrières professionnelles. Dans ces cas, chaque trajectoire ou chaque carrière est décrite par une séquence, autrement dit par une suite chronologiquement ordonnée de « moments » élémentaires, chaque moment correspondant à un « état » déterminé de la trajectoire (par exemple, pour les carrières professionnelles : être en emploi, au chômage ou en inactivité). Mais on peut bien sûr imaginer des types de séquences plus originaux : Andrew Abbott2, le sociologue américain qui a introduit l’optimal matching dans les sciences scientifiques ou des séquences de pas de danses traditionnelles.

    +

    En France, les premiers travaux utilisant l’appariement optimal sont ceux de Claire Lemercier3 sur les carrières des membres des institutions consulaires parisiennes au xixe siècle (Lemercier, 2005), et de Laurent Lesnard4 sur les emplois du temps (Lesnard, 2008). Mais dès les années 1980, les chercheurs du Céreq construisaient des typologies de trajectoires d’insertion à l’aide des méthodes d’analyse des données « à la française » (analyse des correspondances, etc.)5. Au final, on dénombre maintenant plus d’une centaine d’articles de sciences sociales contenant ou discutant des techniques empruntées à l’analyse de séquences.

    +

    Pour une présentation des différentes méthodes d’analyse de séquences disponibles et de leur mise en oeuvre pratique, il existe un petit manuel en français, publié en 2011 dernière aux éditions du Ceped (collection « Les clefs pour »6) et disponible en pdf7 (Robette, 2011). De plus, un article récemment publié dans le Bulletin de Méthodologie Sociologique compare de manière systématique les résultats obtenus par les principales méthodes d’analyse de séquences (Robette & Bry, 2012). La conclusion en est qu’avec des données empiriques aussi structurées que celles que l’on utilise en sciences sociales, l’approche est robuste, c’est-à-dire qu’un changement de méthode aura peu d’influence sur les principaux résultats. Cependant, l’article tente aussi de décrire les spécificités de chaque méthode et les différences marginales qu’elles font apparaître, afin de permettre aux chercheurs de mieux adapter leurs choix méthodologiques à leur question de recherche.

    +

    Afin d’illustrer la démarche de l’analyse de séquences, nous allons procéder ici à la description « pas à pas » d’un corpus de carrières professionnelles, issues de l’enquête Biographies et entourage (Ined, 2000)8. Et pour ce faire, on va utiliser le logiciel R, qui propose la solution actuellement la plus complète et la plus puissante en matière d’analyse de séquences. Les méthodes d’analyse de séquences par analyses factorielles ou de correspondances ne nécessitent pas de logiciel spécifique : tous les logiciels de statistiques généralistes peuvent être utilisés (SAS, SPSS, Stata, R, etc.). En revanche, il n’existe pas de fonctions pour l’appariement optimal dans SAS ou SPSS. Certains logiciels gratuits implémentent l’appariement optimal (comme Chesa9 ou TDA10) mais il faut alors recourir à d’autres programmes pour dérouler l’ensemble de l’analyse (classification, représentation graphique). Stata propose le module sq11, qui dispose d’un éventail de fonctions intéressantes. Mais c’est R et le package TraMineR12, développé par des collègues de l’Université de Genève (Gabadinho et al, 2011), qui fournit la solution la plus complète et la plus puissante à ce jour : on y trouve l’appariement optimal mais aussi d’autres algorithmes alternatifs, ainsi que de nombreuses fonctions de description des séquences et de représentation graphique.

    +
    +
    +

    Charger TraMineR et récupérer les données

    +

    Tout d’abord, à quoi ressemblent nos données ? On a reconstruit à partir de l’enquête les carrières de 1000 hommes. Pour chacune, on connaît la position professionnelle chaque année, de l’âge de 14 ans jusqu’à 50 ans. Cette position est codée de la manière suivante : les codes 1 à 6 correspondent aux groupes socioprofessionnels de la nomenclature des PCS de l’INSEE 13 (agriculteurs exploitants ; artisans, commerçants et chefs d’entreprise ; cadres et professions intellectuelles supérieures ; professions intermédiaires ; employés ; ouvriers) ; on y a ajouté « études » (code 7), « inactivité » (code 8) et « service militaire » (code 9). Le fichier de données comporte une ligne par individu et une colonne par année : la variable csp1 correspond à la position à 14 ans, la variable csp2 à la position à 15 ans, etc. Par ailleurs, les enquêtés étant tous nés entre 1930 et 1950, on ajoute à notre base une variable « génération » à trois modalités, prenant les valeurs suivantes : 1=“1930-1938” ; 2=“1939-1945” ; 3=“1946-1950”. Au final, la base est constituée de 500 lignes et de 37 + 1 = 38 colonnes et se présente sous la forme d’un fichier texte au format csv (téléchargeable à http://larmarange.github.io/analyse-R/data/trajpro.csv).

    +

    Une fois R ouvert, on commence par installer les extensions nécessaires à ce programme (opération à ne réaliser que lors de leur première utilisation) et par les charger en mémoire. L’extension TraMineR propose de nombreuses fonctions pour l’analyse de séquences. L’extension cluster comprend un certain nombre de méthodes de classification automatique13.

    +
    library(TraMineR)
    +library(cluster)
    +

    On importe ensuite les données, on recode la variable « génération » pour lui donner des étiquettes plus explicites. On jette également un coup d’oeil à la structure du tableau de données :

    +
    Rows: 1000 Columns: 38
    +
    -- Column specification ------------------------------------
    +Delimiter: ","
    +dbl (38): csp1, csp2, csp3, csp4, csp5, csp6, csp7, csp8...
    +
    
    +i Use `spec()` to retrieve the full column specification for this data.
    +i Specify the column types or set `show_col_types = FALSE` to quiet this message.
    +
    donnees <- read.csv("http://larmarange.github.io/analyse-R/data/trajpro.csv", header = T)
    +
    donnees$generation <- factor(donnees$generation, labels = c("1930-38", "1939-45", "1946-50"))
    +str(donnees)
    +
    spec_tbl_df [1,000 x 38] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
    + $ csp1      : num [1:1000] 1 7 6 7 7 6 7 7 7 6 ...
    + $ csp2      : num [1:1000] 1 7 6 7 7 6 7 7 6 6 ...
    + $ csp3      : num [1:1000] 1 7 6 6 7 6 7 7 6 6 ...
    + $ csp4      : num [1:1000] 1 7 6 6 7 6 7 7 6 6 ...
    + $ csp5      : num [1:1000] 1 7 6 6 7 6 7 7 6 6 ...
    + $ csp6      : num [1:1000] 1 7 6 6 7 6 9 7 6 6 ...
    + $ csp7      : num [1:1000] 6 9 6 6 7 6 9 7 9 6 ...
    + $ csp8      : num [1:1000] 6 9 9 6 7 6 9 7 4 6 ...
    + $ csp9      : num [1:1000] 6 6 9 6 7 6 9 3 4 9 ...
    + $ csp10     : num [1:1000] 6 6 9 6 7 6 4 3 4 9 ...
    + $ csp11     : num [1:1000] 6 6 6 6 3 6 4 3 4 6 ...
    + $ csp12     : num [1:1000] 6 6 6 6 3 6 4 3 4 6 ...
    + $ csp13     : num [1:1000] 6 6 6 6 3 6 4 3 4 6 ...
    + $ csp14     : num [1:1000] 6 4 6 6 3 6 4 3 4 6 ...
    + $ csp15     : num [1:1000] 6 4 6 6 3 6 4 3 4 6 ...
    + $ csp16     : num [1:1000] 6 4 6 6 3 6 6 3 4 6 ...
    + $ csp17     : num [1:1000] 6 4 6 6 3 6 6 3 4 6 ...
    + $ csp18     : num [1:1000] 6 4 6 6 3 6 6 3 4 6 ...
    + $ csp19     : num [1:1000] 6 4 6 6 3 6 6 3 4 6 ...
    + $ csp20     : num [1:1000] 6 4 6 6 3 6 6 3 4 6 ...
    + $ csp21     : num [1:1000] 6 4 6 6 6 6 6 3 4 6 ...
    + $ csp22     : num [1:1000] 6 4 6 6 6 6 6 3 4 4 ...
    + $ csp23     : num [1:1000] 6 4 6 6 6 6 6 3 4 4 ...
    + $ csp24     : num [1:1000] 6 6 6 6 5 6 6 3 4 4 ...
    + $ csp25     : num [1:1000] 6 6 6 6 5 6 6 3 4 4 ...
    + $ csp26     : num [1:1000] 6 6 6 6 5 6 6 3 4 4 ...
    + $ csp27     : num [1:1000] 6 6 6 6 5 6 6 3 4 4 ...
    + $ csp28     : num [1:1000] 6 6 6 6 5 6 6 3 4 4 ...
    + $ csp29     : num [1:1000] 6 6 6 6 5 6 6 3 4 4 ...
    + $ csp30     : num [1:1000] 4 6 6 6 5 6 6 3 4 4 ...
    + $ csp31     : num [1:1000] 4 6 6 6 5 6 6 3 4 4 ...
    + $ csp32     : num [1:1000] 4 6 6 6 5 6 6 3 4 4 ...
    + $ csp33     : num [1:1000] 4 6 6 6 5 6 6 3 4 4 ...
    + $ csp34     : num [1:1000] 4 6 6 6 5 6 6 3 4 4 ...
    + $ csp35     : num [1:1000] 4 6 6 6 5 6 6 3 4 4 ...
    + $ csp36     : num [1:1000] 4 6 6 6 5 6 6 3 4 4 ...
    + $ csp37     : num [1:1000] 4 6 6 6 5 6 6 3 4 4 ...
    + $ generation: Factor w/ 3 levels "1930-38","1939-45",..: 2 1 1 3 2 3 1 1 2 1 ...
    + - attr(*, "spec")=
    +  .. cols(
    +  ..   csp1 = col_double(),
    +  ..   csp2 = col_double(),
    +  ..   csp3 = col_double(),
    +  ..   csp4 = col_double(),
    +  ..   csp5 = col_double(),
    +  ..   csp6 = col_double(),
    +  ..   csp7 = col_double(),
    +  ..   csp8 = col_double(),
    +  ..   csp9 = col_double(),
    +  ..   csp10 = col_double(),
    +  ..   csp11 = col_double(),
    +  ..   csp12 = col_double(),
    +  ..   csp13 = col_double(),
    +  ..   csp14 = col_double(),
    +  ..   csp15 = col_double(),
    +  ..   csp16 = col_double(),
    +  ..   csp17 = col_double(),
    +  ..   csp18 = col_double(),
    +  ..   csp19 = col_double(),
    +  ..   csp20 = col_double(),
    +  ..   csp21 = col_double(),
    +  ..   csp22 = col_double(),
    +  ..   csp23 = col_double(),
    +  ..   csp24 = col_double(),
    +  ..   csp25 = col_double(),
    +  ..   csp26 = col_double(),
    +  ..   csp27 = col_double(),
    +  ..   csp28 = col_double(),
    +  ..   csp29 = col_double(),
    +  ..   csp30 = col_double(),
    +  ..   csp31 = col_double(),
    +  ..   csp32 = col_double(),
    +  ..   csp33 = col_double(),
    +  ..   csp34 = col_double(),
    +  ..   csp35 = col_double(),
    +  ..   csp36 = col_double(),
    +  ..   csp37 = col_double(),
    +  ..   generation = col_double()
    +  .. )
    + - attr(*, "problems")=<externalptr> 
    +

    On a bien 1000 observations et 38 variables. On définit maintenant des labels pour les différents états qui composent les séquences et on crée un objet « séquence » avec seqdef :

    +
    labels <- c("agric", "acce", "cadr", "pint", "empl", "ouvr", "etud", "inact", "smil")
    +seq <- seqdef(donnees[, 1:37], states = labels)
    +
     [>] state coding:
    +
           [alphabet]  [label]  [long label] 
    +
         1  1           agric    agric
    +
         2  2           acce     acce
    +
         3  3           cadr     cadr
    +
         4  4           pint     pint
    +
         5  5           empl     empl
    +
         6  6           ouvr     ouvr
    +
         7  7           etud     etud
    +
         8  8           inact    inact
    +
         9  9           smil     smil
    +
     [>] 1000 sequences in the data set
    +
     [>] min/max sequence length: 37/37
    +
    +
    +

    Appariement optimal et classification

    +

    Ces étapes préalables achevées, on peut comparer les séquences en calculant les dissimilarités entre paires de séquences. On va ici utiliser la méthode la plus répandue, l’appariement optimal (optimal matching). Cette méthode consiste, pour chaque paire de séquences, à compter le nombre minimal de modifications (substitutions, suppressions, insertions) qu’il faut faire subir à l’une des séquences pour obtenir l’autre. On peut considérer que chaque modification est équivalente, mais il est aussi possible de prendre en compte le fait que les « distances » entre les différents états n’ont pas toutes la même « valeur » (par exemple, la distance sociale entre emploi à temps plein et chômage est plus grande qu’entre emploi à temps plein et emploi à temps partiel), en assignant aux différentes modifications des « coûts » distincts. Dans notre exemple, on va créer avec seqsubm une « matrice des coûts de substitution » dans laquelle tous les coûts sont constants et égaux à 214 :

    +
    couts <- seqsubm(seq, method = "CONSTANT", cval = 2)
    +
     [>] creating 9x9 substitution-cost matrix using 2 as constant value
    +

    Ensuite, on calcule la matrice de distances entre les séquences (i.e contenant les « dissimilarités » entre les séquences) avec seqdist, avec un coût d’insertion/suppression (indel) que l’on fixe ici à 1 :

    +
    seq.om <- seqdist(seq, method = "OM", indel = 1, sm = couts)
    +
     [>] 1000 sequences with 9 distinct states
    +
     [>] checking 'sm' (size and triangle inequality)
    +
     [>] 818 distinct  sequences 
    +
     [>] min/max sequence lengths: 37/37
    +
     [>] computing distances using the OM metric
    +
     [>] elapsed time: 1.63 secs
    +
    +

    Ce cas de figure où tous les coûts de substitution sont égaux à 2 et le coût indel égal à 1 correspond à un cas particulier d’optimal matching que l’on appelle la Longuest Common Subsequence ou LCS. Elle peut se calculer directement avec seqdist de la manière suivante :

    +
    seq.om <- seqdist(seq, method = "LCS")
    +
     [>] 1000 sequences with 9 distinct states
    +
     [>] creating a 'sm' with a substitution cost of 2
    +
     [>] creating 9x9 substitution-cost matrix using 2 as constant value
    +
     [>] 818 distinct  sequences 
    +
     [>] min/max sequence lengths: 37/37
    +
     [>] computing distances using the LCS metric
    +
     [>] elapsed time: 1.57 secs
    +

    En l’absence d’hypothèses fortes sur les différents statuts auxquels correspond notre alphabet (données hiérarchisées, croisement de différentes dimensions…), nous vous recommandons d’utiliser prioritairement la métrique LCS pour calculer la distance entre les séquences.

    +

    On pourra trouver un exemple de matrice de coûts hiérarchisée dans le chapitre sur les trajectoires de soins.

    +
    +

    Cette matrice des distances ou des dissimilarités entre séquences peut ensuite être utilisée pour une classification ascendante hiérarchique (CAH), qui permet de regrouper les séquences en un certain nombre de « classes » en fonction de leur proximité :

    +
    seq.dist <- hclust(as.dist(seq.om), method = "ward.D2")
    +

    Avec la fonction plot, il est possible de tracer l’arbre de la classification (dendrogramme).

    +
    plot(as.dendrogram(seq.dist), leaflab = "none")
    +
    +Dendrogramme de la classification des séquences +

    De même, on peut représenter les sauts d’inertie.

    +
    plot(sort(seq.dist$height, decreasing = TRUE)[1:20], type = "s", xlab = "nb de classes", ylab = "inertie")
    +
    +Sauts d’inertie de la classification des séquences +

    L’observation, sur ce dendogramme ou sur la courbe des sauts d’inertie, des sauts d’inertie des dernières étapes de la classification peut servir de guide pour déterminer le nombre de classes que l’on va retenir pour la suite des analyses. Une première inflexion dans la courbe des sauts d’inertie apparaît au niveau d’une partition en 5 classes. On voit aussi une seconde inflexion assez nette à 7 classes. Mais il faut garder en tête le fait que ces outils ne sont que des guides, le choix devant avant tout se faire après différents essais, en fonction de l’intérêt des résultats par rapport à la question de recherche et en arbitrant entre exhaustivité et parcimonie.

    +

    On fait ici le choix d’une partition en 5 classes :

    +
    nbcl <- 5
    +seq.part <- cutree(seq.dist, nbcl)
    +seq.part <- factor(seq.part, labels = paste("classe", 1:nbcl, sep = "."))
    +
    +
    +

    Représentations graphiques

    +

    Pour se faire une première idée de la nature des classes de la typologie, il existe un certain nombre de représentations graphiques. Les chronogrammes (state distribution plots) présentent une série de coupes transversales : pour chaque âge, on a les proportions d’individus de la classe dans les différentes situations (agriculteur, étudiant, etc.). Ce graphique s’obtient avec seqdplot :

    +
    seqdplot(seq, group = seq.part, xtlab = 14:50, border = NA)
    +
    +Chronogrammes +

    Chacune des classes semble caractérisée par un groupe professionnel principal : profession intermédiaire pour la classe 1, ouvrier pour la 2, employé pour la 3, cadre pour la 4 et indépendant pour la 5. Cependant, on aperçoit aussi des « couches » d’autres couleurs, indiquant que l’ensemble des carrières ne sont probablement pas stables.

    +

    Les « tapis » (index plots), obtenus avec seqIplot, permettent de mieux visualiser la dimension individuelle des séquences. Chaque segment horizontal représente une séquence, découpée en sous-segments correspondant aux aux différents états successifs qui composent la séquence.

    +
    seqIplot(seq, group = seq.part, xtlab = 14:50, space = 0, border = NA, yaxis = FALSE)
    +
    +Tapis des séquences triés +

    Il est possible de trier les séquences pour rendre les tapis plus lisibles (on trie ici par multidimensional scaling à l’aide de la fonction cmdscale).

    +
    ordre <- cmdscale(as.dist(seq.om), k = 1)
    +seqIplot(seq, group = seq.part, sortv = ordre, xtlab = 14:50, space = 0, border = NA, yaxis = FALSE)
    +
    +Tapis des séquences triés par multidimensional scaling +

    On voit mieux apparaître ainsi l’hétérogénéité de certaines classes. Les classes 1, 3 et 4, par exemple, semblent regrouper des carrières relativement stables (respectivement de professions intermédiaires, d’employés et de cadres) et des carrières plus « mobiles » commencées comme ouvrier (classes 1 et 3, en orange) ou comme profession intermédiaire (classe 4, en rouge). De même, la majorité des membres de la dernière classe commencent leur carrière dans un groupe professionnel distinct de celui qu’ils occuperont par la suite (indépendants). Ces distinctions apparaissent d’ailleurs si on relance le programme avec un nombre plus élevé de classes (en remplaçant le 5 de la ligne nbcl <- 5 par 7, seconde inflexion de la courbe des sauts d’inertie, et en exécutant de nouveau le programme à partir de cette ligne) : les stables et les mobiles se trouvent alors dans des classes distinctes.

    +

    Le package seqhandbook propose une fonction seq_heatmap permettant de représenter le tapis de l’ensemble des séquences selon l’ordre du dendrogramme.

    +
    library(seqhandbook)
    +seq_heatmap(seq, seq.dist, labCol = 14:50)
    +
    +Tapis des séquences trié selon le dendrogramme +
    +

    Il est possible de reproduire un tapis de séquence avec ggplot2. Outre le fait que cela fournit plus d’options de personnalisation du graphique, cela permets également à ce que la hauteur de chaque classe sur le graphique soit proportionnelle aux nombre d’invidus.

    +

    En premier lieu, on a va ajouter à notre fichier de données des identifiants individuels, la typologie crée et l’ordre obtenu par multidimensional scaling.

    +
    donnees$id <- row.names(donnees)
    +donnees$classe <- seq.part
    +donnees$ordre <- rank(ordre, ties.method = "random")
    +

    Ensuite, il est impératif que nos données soient dans un format long et tidy, c’est-à-dire avec une ligne par individu et par pas de temps. Pour cela on aura recours à la fonction gather (voir le chapitre dédié).

    +
    library(tidyr)
    +long <- donnees %>% gather(csp1:csp37, key = annee, value = csp)
    +

    On va mettre en forme la variable csp sous forme de facteur, récupérer l’année grace à la fonction str_sub de l’extension stringr (voir le chapitre sur la manipulation de texte) et recalculer l’âge.

    +
    long$csp <- factor(long$csp, labels = c("agriculteur", "art./com./chefs", "cadres", "prof. int.", "employés", "ouvriers", "étudiants", "inactifs", "serv. militaire"))
    +library(stringr)
    +long$annee <- as.integer(str_sub(long$annee, 4))
    +long$age <- long$annee + 13
    +

    Il n’y a plus qu’à faire notre graphique grace à geom_raster qui permet de colorier chaque pixel. Techniquement, pour un tapis de séquence, il s’agit de représenter le temps sur l’axe horizontal et les individus sur l’axe vertical. Petite astuce : plutôt que d’utiliser id pour l’axe vertical, nous utilisons ordre afin de trier les observations. Par ailleurs, il est impératif de transformer au passage ordre en facteur afin que ggplot2 puisse recalculer proprement et séparément les axes pour chaque facette15, à condition de ne pas oublier l’option scales = "free_y" dans l’appel à facet_grid. Les autres commandes ont surtout pour vocation d’améliorer le rendu du graphique (voir le chapitre dédié à ggplot2).

    +
    library(ggplot2)
    +ggplot(long) +
    +  aes(x = age, y = factor(ordre), fill = csp) +
    +  geom_raster() +
    +  ylab("") +
    +  scale_y_discrete(label = NULL) +
    +  theme_bw() +
    +  theme(legend.position = "bottom") +
    +  scale_fill_brewer(palette = "Set3") +
    +  facet_grid(classe ~ ., scales = "free_y", space = "free_y") +
    +  scale_x_continuous(limits = c(14, 50), breaks = c(14, 20, 25, 30, 35, 40, 45, 50), expand = c(0, 0))
    +
    Warning: Removed 2000 rows containing missing values
    +(geom_raster).
    +

    +
    +

    La distance des séquences d’une classe au centre de cette classe, obtenue avec disscenter, permet de mesurer plus précisément l’homogénéité des classes. Nous utilisons ici aggregate pour calculer la moyenne par classe :

    +
    aggregate(disscenter(as.dist(seq.om), group = seq.part), list(seq.part), mean)
    +
    + +
    +

    Cela nous confirme que les classes 1, 3 et 5 sont nettement plus hétérogènes que les autres, alors que la classe 2 est la plus homogène.

    +

    D’autres représentations graphiques existent pour poursuivre l’examen de la typologie. On peut visualiser les 10 séquences les plus fréquentes de chaque classe avec seqfplot.

    +
    seqfplot(seq, group = seq.part)
    +
    +Séquences les plus fréquentes de chaque classe +

    On peut aussi visualiser avec seqmsplot l’état modal (celui qui correspond au plus grand nombre de séquences de la classe) à chaque âge.

    +
    seqmsplot(seq, group = seq.part, xtlab = 14:50, main = "classe")
    +
    +Statut modal à chaque âge +

    On peut également représenter avec seqmtplot les durées moyennes passées dans les différents états.

    +
    seqmtplot(seq, group = seq.part)
    +
    +Durée moyenne dans chaque statut +

    La fonction seqrplot cherche à identifier des séquences représentatives de chaque classe. Plusieurs méthodes sont proposées (voir seqrep). La méthode dist cherche à identifier des séquences centrales à chaque classe, c’est-à-dire situées à proximité du centre de la classe. Selon l’hétérogénéité de la classe, plusieurs séquences représentatives peuvent être renvoyées. ATTENTION : il faut être prudent dans l’interprétation de ces séquences centrales de la classe dans la mesure où elles ne rendent pas toujours compte de ce qui se passe dans la classe et où elles peuvent induire en erreur quand la classe est assez hétérogène. Il faut donc les considérer tout en ayant en tête l’ensemble du tapis de séquence pour voir si elles sont effectivement de bonnes candidates.

    +
    seqrplot(seq, group = seq.part, dist.matrix = seq.om, criterion = "dist")
    +
    +Séquences représentatives de chaque classe +

    Enfin, l’entropie transversale décrit l’évolution de l’homogénéité de la classe. Pour un âge donné, une entropie proche de 0 signifie que tous les individus de la classe (ou presque) sont dans la même situation. À l’inverse, l’entropie est de 1 si les individus sont dispersés dans toutes les situations. Ce type de graphique produit par seqHtplot peut être pratique pour localiser les moments de transition, l’insertion professionnelle ou une mobilité sociale ascendante.

    +
    seqHtplot(seq, group = seq.part, xtlab = 14:50)
    +
    +Entropie transversale +
    +
    +
    +

    Distribution de la typologie

    +

    On souhaite maintenant connaître la distribution de la typologie (en effectifs et en pourcentages) :

    +
    library(questionr)
    +freq(seq.part)
    +
    + +
    +

    On poursuit ensuite la description des classes en croisant la typologie avec la variable generation :

    +
    cprop(table(seq.part, donnees$generation))
    +
              
    +seq.part   1930-38 1939-45 1946-50 Ensemble
    +  classe.1  35.6    32.5    40.8    36.6   
    +  classe.2  19.7    18.3    17.0    18.3   
    +  classe.3   6.5    13.9    11.2    10.4   
    +  classe.4  31.8    29.2    27.9    29.6   
    +  classe.5   6.5     6.1     3.0     5.1   
    +  Total    100.0   100.0   100.0   100.0   
    +
    chisq.test(table(seq.part, donnees$generation))
    +
    
    +    Pearson's Chi-squared test
    +
    +data:  table(seq.part, donnees$generation)
    +X-squared = 18.518, df = 8, p-value = 0.01766
    +

    Le lien entre le fait d’avoir un certain type de carrières et la cohorte de naissance est significatif à un seuil de 15 %. On constate par exemple l’augmentation continue de la proportion de carrières de type « professions intermédiaires » (classe 1) et, entre les deux cohortes les plus anciennes, l’augmentation de la part des carrières de type « employés » (classe 3) et la baisse de la part des carrières de type « cadres » (classe 4).

    +

    Bien d’autres analyses sont envisageables : croiser la typologie avec d’autres variables (origine sociale, etc.), construire l’espace des carrières possibles, étudier les interactions entre trajectoires familiales et professionnelles, analyser la variance des dissimilarités entre séquences en fonction de plusieurs variables « explicatives16 »…

    +

    Mais l’exemple proposé est sans doute bien suffisant pour une première introduction !

    +
    +
    +

    Pour aller plus loin

    +

    En premier lieu, la lecture du manuel d’utilisation de TraMineR, intitulé Mining sequence data in R with the TraMineR package: A user’s guide et écrit par Alexis Gabadinho, Gilbert Ritschard, Matthias Studer et Nicolas S. Muller, est fortement conseillée. Ce manuel ne se contente pas de présenter l’extension, mais aborde également la théorie sous-jacente de l’analyse de séquences, les différents formats de données, les différences approches (séquences de statut ou séquences de transitions par exemple), etc.

    +

    Pour une initiation en français, on pourra se référer à l’ouvrage de Nicolas Robette Explorer et décrire les parcours de vie : les typologies de trajectoires sorti en 2011 aux éditions du Ceped.

    +

    L’extension WeightedCluster de Matthias Studer est un excellent complément à TraMineR. Il a également écrit un manuel de la librairie WeightedCluster : un guide pratique pour la création de typologies de trajectoires en sciences sociales avec R.

    +

    Enfin, l’extension TraMineRextras (https://cran.r-project.org/package=TraMineRextras) contient des fonctions complémentaires à TraMineR, plus ou moins en phase de test.

    +
    +
    +

    Bibliographie

    + +
    +
    +
    +
      +
    1. Pour une analyse des conditions sociales de la diffusion de l’analyse de séquences dans le champ des sciences sociales, voir Robette, 2012.

    2. +
    3. http://home.uchicago.edu/~aabbott/

    4. +
    5. http://lemercier.ouvaton.org/document.php?id=62

    6. +
    7. http://laurent.lesnard.free.fr/article.php3?id_article=22

    8. +
    9. Voir par exemple l’article d’Yvette Grelet (2002).

    10. +
    11. http://www.ceped.org/?rubrique57

    12. +
    13. http://nicolas.robette.free.fr/Docs/Robette2011_Manuel_TypoTraj.pdf

    14. +
    15. Pour une analyse plus poussée de ces données, avec deux méthodes différentes, voir Robette & Thibault, 2008. Pour une présentation de l’enquête, voir Lelièvre & Vivier, 2001.

    16. +
    17. http://home.fsw.vu.nl/ch.elzinga/

    18. +
    19. http://steinhaus.stat.ruhr-uni-bochum.de/tda.html

    20. +
    21. http://www.stata-journal.com/article.html?article=st0111

    22. +
    23. http://mephisto.unige.ch/traminer/

    24. +
    25. Pour une présentation plus détaillée, voir le chapitre sur la classification ascendante hiérarchique (CAH).

    26. +
    27. Le fonctionnement de l’algorithme d’appariement optimal — et notamment le choix des coûts — est décrit dans le chapitre 9 du manuel de TraMineR (http://mephisto.unige.ch/pub/TraMineR/doc/TraMineR-Users-Guide.pdf).

    28. +
    29. Essayez le même code mais avec y = ordre au lieu de y = factor(ordre) et vous comprendrez tout l’intérêt de cette astuce.

    30. +
    31. L’articulation entre méthodes « descriptives » et méthodes « explicatives » est un prolongement possible de l’analyse de séquences. Cependant, l’analyse de séquences était envisagée par Abbott comme une alternative à la sociologie quantitative mainstream, i.e le « paradigme des variables » et ses hypothèses implicites souvent difficilement tenables (Abbott, 2001). Une bonne description solidement fondée théoriquement vaut bien des « modèles explicatifs » (Savage, 2009).

    32. +
    +
    + +
    +
    + + + +

    Trajectoires de soins : un exemple de données longitudinales

    + +
    + + + + +
    +

    Ce chapitre est évoqué dans le webin-R #17 (trajectoires de soins : un exemple de données longitudinales 1 : description des données) sur YouTube.

    +

    Ce chapitre est évoqué dans le webin-R #18 (trajectoires de soins : un exemple de données longitudinales 2 : analyse de survie) sur YouTube.

    +

    Ce chapitre est évoqué dans le webin-R #19 (trajectoires de soins : un exemple de données longitudinales 3 : analyse de séquences) sur YouTube.

    +

    Ce chapitre est évoqué dans le webin-R #20 (trajectoires de soins : un exemple de données longitudinales 4 : régression logistique multinomiale & modèles mixtes à classe latente) sur YouTube.

    +

    Ce chapitre est évoqué dans le webin-R #21 (trajectoires de soins : un exemple de données longitudinales 5 : modèle à observations répétée, régression logistique ordinale GEE & analyse de survie multi-états) sur YouTube.

    +
    +
    +

    Une version de ce chapitre ustilisant l’extension data.table plutôt que dplyr est également disponible.

    +
    +

    Dans ce chapitre, nous allons aborder plusieurs méthodes d’analyse à partir d’un jeu de données longitudinales. Tout d’abord, importons les données dans R avec la commande suivante :

    +
    load(url("http://larmarange.github.io/analyse-R/data/care_trajectories.RData"))
    +
    class(care_trajectories)
    +
    [1] "data.table" "data.frame"
    +

    Nous obtenons un objet appelé care_trajectories. La fonction class nous montre qu’il s’agit d’un tableau de données au format data.table (voir le chapitre dédié). Chargeons le tidyverse et utilisons as_tibble pour convertir le tableau de données.

    +
    library(tidyverse, quietly = TRUE)
    +care_trajectories <- as_tibble(care_trajectories)
    +
    +

    Première description des données

    +

    Jetons un premier regard aux données.

    +
    head(care_trajectories)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    idmonthcare_statussexageeducationwealthdistance_clinic
    30D11221
    31D11221
    32D11221
    33D11221
    34D11221
    35D11221
    +

    Il apparaît que les données sont dans un format long et tidy (voir le chapitre sur tidyr pour une présentation du concept de tidy data), avec une ligne par individu et par pas de temps. Il apparait également que les données sont stockées sous formes de vecteurs labellisés (voir le chapitre dédié aux vecteurs labellisés). Nous aurons donc besoin de l’extension labelled.

    +
    library(labelled)
    +

    Pour une description des variables, on pourra avoir recours à describe de questionr.

    +
    library(questionr)
    +describe(care_trajectories, freq.n.max = 10)
    +
    [49365 obs. x 8 variables] tbl_df tbl data.frame
    +
    +$id: patient identifier
    +integer: 3 3 3 3 3 3 9 9 13 13 ...
    +min: 3 - max: 9998 - NAs: 0 (0%) - 2929 unique values
    +
    +$month: month(s) since diagnosis
    +numeric: 0 1 2 3 4 5 0 1 0 1 ...
    +min: 0 - max: 50 - NAs: 0 (0%) - 51 unique values
    +
    +$care_status: care status
    +labelled character: "D" "D" "D" "D" "D" "D" "D" "D" "D" "D" ...
    +NAs: 0 (0%) - 4 unique values
    +4 value labels: [D] diagnosed, but not in care [C] in care, but not on treatment [T] on treatment, but infection not suppressed [S] on treatment and suppressed infection
    +
    +                                                   n     %
    +[D] diagnosed, but not in care                 25374  51.4
    +[C] in care, but not on treatment               5886  11.9
    +[T] on treatment, but infection not suppressed  4596   9.3
    +[S] on treatment and suppressed infection      13509  27.4
    +Total                                          49365 100.0
    +
    +$sex: sex
    +labelled double: 1 1 1 1 1 1 1 1 0 0 ...
    +min: 0 - max: 1 - NAs: 0 (0%) - 2 unique values
    +2 value labels: [0] male [1] female
    +
    +               n   %
    +[0] male   17781  36
    +[1] female 31584  64
    +Total      49365 100
    +
    +$age: age group
    +labelled double: 1 1 1 1 1 1 2 2 2 2 ...
    +min: 1 - max: 3 - NAs: 0 (0%) - 3 unique values
    +3 value labels: [1] 16-29 [2] 30-59 [3] 60+
    +
    +              n     %
    +[1] 16-29 16911  34.3
    +[2] 30-59 29365  59.5
    +[3] 60+    3089   6.3
    +Total     49365 100.0
    +
    +$education: education level
    +labelled double: 2 2 2 2 2 2 3 3 2 2 ...
    +min: 1 - max: 3 - NAs: 0 (0%) - 3 unique values
    +3 value labels: [1] primary [2] secondary [3] higher
    +
    +                  n     %
    +[1] primary   10417  21.1
    +[2] secondary 19024  38.5
    +[3] higher    19924  40.4
    +Total         49365 100.0
    +
    +$wealth: wealth group (assets score)
    +labelled double: 2 2 2 2 2 2 2 2 1 1 ...
    +min: 1 - max: 3 - NAs: 0 (0%) - 3 unique values
    +3 value labels: [1] low [2] middle [3] high
    +
    +               n     %
    +[1] low    15432  31.3
    +[2] middle 20769  42.1
    +[3] high   13164  26.7
    +Total      49365 100.0
    +
    +$distance_clinic: distance to nearest clinic
    +labelled double: 1 1 1 1 1 1 2 2 2 2 ...
    +min: 1 - max: 2 - NAs: 0 (0%) - 2 unique values
    +2 value labels: [1] less than 10 km [2] 10 km or more
    +
    +                        n     %
    +[1] less than 10 km 26804  54.3
    +[2] 10 km or more   22561  45.7
    +Total               49365 100.0
    +

    Nous pouvons également utiliser look_for de labelled.

    +
    care_trajectories %>% look_for()
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    posvariablelabelcol_typelevelsvalue_labels
    1idpatient identifierintNULLNULL
    2monthmonth(s) since diagnosisdblNULLNULL
    3care_statuscare statuschr+lblNULLD, C, T, S
    4sexsexdbl+lblNULL0, 1
    5ageage groupdbl+lblNULL1, 2, 3
    6educationeducation leveldbl+lblNULL1, 2, 3
    7wealthwealth group (assets score)dbl+lblNULL1, 2, 3
    8distance_clinicdistance to nearest clinicdbl+lblNULL1, 2
    +

    Dans cette étude, on a suivi des patients à partir du moment où ils ont été diagnostiqués pour une pathologie grave et chronique et on a suivi leurs parcours de soins chaque mois à partir du diagnostic. La variable status contient le statut dans les soins de chaque individu pour chaque mois de suivi :

    +
      +
    • +D : s’il n’est pas actuellement suivi dans une clinique, soit que la personne n’est pas encore entrée en clinique après le diagnostic, soit qu’elle a quitté la clinique et qu’elle est donc sortie des soins ;
    • +
    • +C : indique que le patient est entré en soins (il est suivi dans une clinique) mais il n’a pas encore commencé le traitement, ou bien il a arrêté le traitement mais est toujours suivi en clinique ;
    • +
    • +T : la personne est sous traitement mais l’infections n’est pas supprimée ou contrôlée, soit que le traitement n’a pas encore eu le temps de faire effet, soit qu’il n’est plus efficace ;
    • +
    • +S : la personne est suivie en clinique, sous traitement et son infection est supprimée / contrôlée, indiquant que le traitement est efficace et produit son effet. Cette étape ultime du parcours de soins est celle dans laquelle on souhaite maintenir les individus le plus longtemps possible.
    • +
    +

    Il est important de noter que nous avons ici des statuts hiérarchiquement ordonnés (D < C < T < S), ce qui aura son importance pour les choix méthodologiques que nous aurons à faire.

    +

    Nous disposons également d’autres variables (âge, sexe, niveau d’éducation…) qui sont ici dépendantes du temps, c’est-à-dire que le cas échéant, elles peuvent varier d’un mois à l’autre en cas de changement.

    +

    Avant de démarrer les analyses, françisons certaines de ces variables.

    +
    var_label(care_trajectories$sex) <- "Sexe"
    +val_labels(care_trajectories$sex) <- c(homme = 0, femme = 1)
    +var_label(care_trajectories$age) <- "Âge"
    +var_label(care_trajectories$education) <- "Education"
    +val_labels(care_trajectories$education) <- c(
    +  primaire = 1,
    +  secondaire = 2,
    +  supérieur = 3
    +)
    +

    Le fichier contient 49365 lignes, ce qui ne veut pas dire qu’il y a ce nombre d’invidus suivis au cours du temps, puisque plusieurs lignes correspondent à un même individu. On peut obtenir le nombre d’individus différents assez facilement avec la commande :

    +
    length(unique(care_trajectories$id))
    +
    [1] 2929
    +

    Précision : dans ce fichier, tous les individus ne sont pas suivis pendant la même durée, car ils n’ont pas tous été diagnostiqués au même moment. Cependant, il n’y a pas de trous dans le suivi (ce qui serait le cas si certains individus sortaient de l’observation pendant quelques mois puis re-rentraient dans la cohorte de suivi).

    +

    Avant d’aller plus avant, il nous faut avoir une idée du nombre d’individus observé au cours du temps, ce que l’on peut obtenir avec :

    +
    ggplot(care_trajectories) +
    +  aes(x = month) +
    +  geom_bar()
    +

    +

    Améliorons ce graphique en y ajoutant la distribution selon le statut dans les soins chaque mois, en améliorant l’axe du temps (tous les 6 mois est plus facile à lire) et en y ajoutant un titre et des étiquettes appropriées. Afin de disposer d’une palette de couleurs à fort contraste, nous allons utiliser l’extension viridis. Enfin, nous allons utiliser une petite astuce pour indiquer les effectifs sur l’axe horizontal. Au passage, nous allons également franciser les étiquettes de la variable care_status avec val_labels (notez aussi le recours à to_factor dans aes qui nous permet de transformer à la volée la variable en facteur, format attendu par ggplot2 pour les variables catégorielles). On se référera au chapitre dédié à ggplot2 pour plus de détails sur les différentes fonctions de cette extension graphique.

    +
    library(viridis)
    +n <- care_trajectories %>%
    +  filter(month %in% (0:8*6)) %>%
    +  group_by(month) %>%
    +  count() %>%
    +  pluck("n")
    +etiquettes <- paste0("M", 0:8*6, "\n(n=", n, ")")
    +val_labels(care_trajectories$care_status) <- c(
    +  "diagnostiqué, mais pas suivi" = "D",
    +  "suivi, mais pas sous traitement" = "C",
    +  "sous traitement, mais infection non contrôlée" = "T",
    +  "sous traitement et infection contrôlée" = "S"
    +)
    +ggplot(care_trajectories) +
    +  aes(x = month, fill = to_factor(care_status)) +
    +  geom_bar(color = "gray50", width = 1) +
    +  scale_x_continuous(breaks = 0:8*6, labels = etiquettes) +
    +  ggtitle("Distribution du statut dans les soins chaque mois") +
    +  xlab("") + ylab("") +
    +  theme_light() +
    +  theme(legend.position = "bottom") +
    +  labs(fill = "Statut dans les soins") + 
    +  scale_fill_viridis(discrete = TRUE, direction = -1) +
    +  guides(fill = guide_legend(nrow = 2))
    +

    +

    On s’aperçoit qu’une majorité des personnes suivies ne l’ont été que peu de temps, avec une décroissance rapide des effectifs.

    +
    +
    +

    Évolution de la cascade de soins au cours du temps

    +

    On nomme communément cascade de soins la proportion d’individus dans chaque statut à un moment du temps donné. On peut facilement obtenir celle-ci à partir du code du graphique précédent en ajoutant l’option position = fill à geom_bar.

    +
    ggplot(care_trajectories) +
    +  aes(x = month, fill = to_factor(care_status)) +
    +  geom_bar(color = "gray50", width = 1, position = "fill") +
    +  scale_x_continuous(breaks = 0:8*6, labels = etiquettes) +
    +  scale_y_continuous(labels = scales::percent) +
    +  ggtitle("Cascade des soins observée, selon le temps depuis le diagnostic") +
    +  xlab("") + ylab("") +
    +  theme_light() +
    +  theme(legend.position = "bottom") +
    +  labs(fill = "Statut dans les soins") + 
    +  scale_fill_viridis(discrete = TRUE, direction = -1) +
    +  guides(fill = guide_legend(nrow = 2))
    +

    +

    Les effectifs sont très faibles au-delà de 36 mois et il serait préférable de couper la cascade au-delà de M36, ce que l’on peut faire aisément ne gardant que les lignes correspondantes de care_trajectories.

    +
    casc_obs <- ggplot(care_trajectories %>% filter(month <= 36)) +
    +  aes(x = month, fill = to_factor(care_status)) +
    +  geom_bar(color = "gray50", width = 1, position = "fill") +
    +  scale_x_continuous(breaks = 0:8*6, labels = etiquettes) +
    +  scale_y_continuous(labels = scales::percent) +
    +  ggtitle("Cascade des soins observée, selon le temps depuis le diagnostic") +
    +  xlab("") + ylab("") +
    +  theme_light() +
    +  theme(legend.position = "bottom") +
    +  labs(fill = "Statut dans les soins") + 
    +  scale_fill_viridis(discrete = TRUE, direction = -1) +
    +  guides(fill = guide_legend(nrow = 2))
    +casc_obs
    +

    +
    +
    +

    Analyse de survie classique

    +

    L’analyse de survie constitue l’approche statistique la plus fréquente pour appréhender des données biographiques. Dans sa version classique, l’analyse de survie modélise le temps mis pour vivre un événement particulier à partir d’un événement origine.

    +

    Dans notre exemple, l’événement d’origine commun à tous les individus est le diagnostic VIH. Les personnes suivies peuvent vivre trois événements principaux :

    +
      +
    • entrée en soins (passage de D à C) ;
    • +
    • initiation du traitement (passage de C à T) ;
    • +
    • contrôle de l’infection (passage de T à S).
    • +
    +

    En toute rigueur, il faudrait également considérer les transitions inverses (sortie de soins, arrêt du traitement, échec virologique). De même, il est possible que certains aient vécus plusieurs transitions successives (entrée en soin, initiation du traitement, sortie de soins et arrêt du traitement, nouvelle entrée en soins…).

    +

    Pour le moment, contentons-nous de regarder la première entrée en soins, la première initiation du traitement et la première atteinte du contrôle de l’infection et de calculer la date de ces trois événements, dans un fichier ind contenant une ligne par individu.

    +
    ind <- care_trajectories %>% filter(month == 0)
    +ind$diagnostic <- 0
    +ind <- ind %>%
    +  left_join(
    +    care_trajectories %>%
    +      filter(care_status %in% c("C", "T", "S")) %>%
    +      group_by(id) %>%
    +      dplyr::summarise(entree_soins = min(month)),
    +    by = "id"
    +  ) %>%
    +  left_join(
    +    care_trajectories %>%
    +      filter(care_status %in% c("T", "S")) %>%
    +      group_by(id) %>%
    +      dplyr::summarise(initiation_tt = min(month)),
    +    by = "id"
    +  ) %>%
    +  left_join(
    +    care_trajectories %>%
    +      filter(care_status == "S") %>%
    +      group_by(id) %>%
    +      dplyr::summarise(controle = min(month)),
    +    by = "id"
    +  )
    +

    Il nous faut également la durée de suivi par individu.

    +
    ind <- ind %>%
    +  left_join(
    +    care_trajectories %>%
    +      group_by(id) %>%
    +      dplyr::summarise(suivi = max(month)),
    +    by = "id"
    +  )
    +

    Pour faciliter la suite des analyses, nous allons nous créer une petite fonction qui, en fonction de la date d’origine et la date d’événement retenues, calculera la courbe de Kaplan-Meier correspondante (voir le chapitre sur l’analyse de suivie pour le calcul de la courbe de survie et celui dédié à l’écriture de fonctions).

    +
    km <- function(date_origine, date_evenement, nom) {
    +  library(survival)
    +  
    +  # ne garder que les observations avec date d'origine
    +  tmp <- ind[!is.na(ind[[date_origine]]), ]
    +  
    +  # pre-remplir la variable time avec duree de suivi
    +  # depuis date d'origine
    +  tmp$time <- tmp$suivi - tmp[[date_origine]]
    +  # et considérer que l'événement n'a pas été vécu
    +  tmp$event <- FALSE
    +  
    +  # si date_evement documentée, événement vécu
    +  tmp[!is.na(tmp[[date_evenement]]), ]$event <- TRUE
    +  tmp[tmp$event == TRUE, ]$time <- 
    +    tmp[tmp$event == TRUE, ][[date_evenement]] -
    +    tmp[tmp$event == TRUE, ][[date_origine]]
    +  
    +  kaplan <- survfit(Surv(time, event) ~ 1, data = tmp)
    +  res <- broom::tidy(kaplan, conf.int = TRUE)
    +  res$nom <- nom
    +  res
    +}
    +

    Une première approche consiste à regarder la survenue de chacun des trois événements mentions plus haut en fonction du temps depuis le diagnostic.

    +
    depuis_diag <- dplyr::bind_rows(
    +  km("diagnostic", "entree_soins", "entrée en soins"),
    +  km("diagnostic", "initiation_tt", "initiation du traitement"),
    +  km("diagnostic", "controle", "contrôle de l'infection")
    +)
    +
    +g_diag <- ggplot(data = depuis_diag) +
    +  aes(x = time, y = 1 - estimate, 
    +      color = as_factor(nom), fill = as_factor(nom),
    +      ymin = 1 - conf.high, ymax = 1 - conf.low) +
    +  geom_ribbon(alpha = .25, mapping = aes(color = NULL)) +
    +  geom_line(size = 1) +
    +  theme_classic() +
    +  theme(
    +    legend.position = "bottom",
    +    panel.grid.major.y = element_line(colour = "grey")
    +  ) +
    +  scale_x_continuous(breaks = 0:6*6, limits = c(0, 36)) +
    +  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
    +  xlab("mois depuis le diagnostic") + 
    +  ylab("") + labs(color = "", fill = "")
    +g_diag
    +

    +

    Ce graphique ressemble à la cascade des soins observée que nous avions calculée plus haut, à quelques différences près :

    +
      +
    • avec la méthode de Kaplan-Meier, la censure à droite, i.e. le fait que tous les individus n’ont pas la même durée de suivi, est correctement prise en compte et la courbe est corrigée en conséquence ;
    • +
    • par contre, les transitions inverses ne sont pas considérées : lorsqu’un individu a atteint une étape, on ne regarde pas s’il en ressort.
    • +
    +

    Une autre manière d’appréhender nos trajectoires est de considérer le temps requis pour atteindre une étape une fois la précédente étape atteinte. Ce qu’on obtient facilement en adaptant légèrement notre code précédent.

    +
    depuis_prec <- dplyr::bind_rows(
    +  km("diagnostic", "entree_soins", "entrée en soins"),
    +  km("entree_soins", "initiation_tt", "initiation du traitement"),
    +  km("initiation_tt", "controle", "contrôle de l'infection")
    +)
    +
    +g_prec <- ggplot(data = depuis_prec) +
    +  aes(x = time, y = 1 - estimate, 
    +      color = as_factor(nom), fill = as_factor(nom),
    +      ymin = 1 - conf.high, ymax = 1 - conf.low) +
    +  geom_ribbon(alpha = .25, mapping = aes(color = NULL)) +
    +  geom_line(size = 1) +
    +  theme_classic() +
    +  theme(
    +    legend.position = "bottom",
    +    panel.grid.major.y = element_line(colour = "grey")
    +  ) +
    +  scale_x_continuous(breaks = 0:6*6, limits = c(0, 36)) +
    +  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
    +  xlab("mois depuis l'étape précédente") + 
    +  ylab("") + labs(color = "", fill = "")
    +
    +g_prec
    +

    +

    Attention : cette représentation graphique peut éventuellement prêter à confusion dans sa lecture car l’échelle de temps n’est pas tout à fait la même pour chaque courbe, dans la mesure où la date d’origine diffère pour chacune. Dès lors, il peut être plus pertinent de présenter chaque courbe l’une à côté de l’autre.

    +
    g_prec + facet_grid(~ as_factor(nom))
    +

    +

    Ici, on voit plus clairement que l’étape où il y a le plus de perdition est celle de l’entrée en soins, moins des trois quarts des personnes diagnostiquées étant venu en clinique au mois une fois dans les trois ans suivant le diagnostic. Par contre, l’initiation du traitement une fois entré en clinique et le contrôle de l’infection une fois le traitement initié sont beaucoup plus rapide.

    +

    Pour aller plus loin avec les outils de l’analyse de survie classique, il serait possible de faire des analyses bivariées (Kaplan-Meier) ou multivariées (Cox) pour chacune de ces étapes. Cependant, il serait plus intéressant de trouver une approache statistique permettant de considérer dans un même modèle l’ensemble des transitions possibles.

    +
    +
    +

    Une première analyse de séquences sur l’ensemble du fichier

    +

    L’analyse de séquences permet d’appréhender l’ensemble de la trajectoire de soins à travers la succession des états dans lesquels se trouvent les patients observés.

    +

    Nous allons donc réaliser une analyse de séquences (voir le chapitre dédié) sur l’ensemble de notre fichier. Pour cela, il va falloir préalable que nous transformions nos donnée actuellement dans un format long en un tableau large, c’est-à-dire avec une ligne par individu et une variable différentes par pas de temps. On peut réaliser cela facilement avec pivot_wider de tidyr (voir le chapitre dédié à tidyr).

    +
    library(tidyr)
    +large <- care_trajectories %>%
    +  dplyr::select(id, m = month, care_status) %>%
    +  pivot_wider(names_from = m, values_from = care_status, names_prefix = "m") 
    +head(large)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    idm0m1m2m3m4m5m6m7m8m9m10m11m12m13m14m15m16m17m18m19m20m21m22m23m24m25m26m27m28m29m30m31m32m33m34m35m36m37m38m39m40m41m42m43m44m45m46m47m48m49m50
    3DDDDDDNANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANA
    9DDNANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANA
    13DDDDDDDDNANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANA
    15DDDDTTTCDDDDDDDCCCCCCCCCTTTTTTTTTNANANANANANANANANANANANANANANANANANA
    18DDSSSSSSSSSSSSSSSSSNANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANA
    21DDDDDDNANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANANA
    +

    On utilise seqdef de TraMineR pour créer nos séquences, avec les arguments alphabet pour forcer l’ordre de l’alphabet, states pour spécifier des étiquettes courtes à chaque état et cpal pour indiquer le code couleur de chaque état (et être raccord avec nos graphiques précédents).

    +
    library(TraMineR, quietly = TRUE)
    +seq_all <- seqdef(
    +  large %>% dplyr::select(m0:m50),
    +  id = large$id,
    +  alphabet = c("D", "C", "T", "S"),
    +  states = c("diagnostiqué", "en soins", "sous traitement", "inf. contrôlée"),
    +  cpal = viridis(4, direction = -1)
    +)
    +
     [>] found missing values ('NA') in sequence data
    +
     [>] preparing 2929 sequences
    +
     [>] coding void elements with '%' and missing values with '*'
    +
     [>] state coding:
    +
           [alphabet]  [label]         [long label] 
    +
         1  D           diagnostiqué    diagnostiqué
    +
         2  C           en soins        en soins
    +
         3  T           sous traitement sous traitement
    +
         4  S           inf. contrôlée  inf. contrôlée
    +
     [>] 2929 sequences in the data set
    +
     [>] min/max sequence length: 1/51
    +

    On peut retrouver la cascade de soins avec seqdplot.

    +
    seqdplot(seq_all, legend.prop = .25)
    +

    +

    Nous allons maintenant calculer une matrice des distances entre individus par optimal matching. Dans le cas présent, nos différents status sont hiérarchiquement ordonnés. Il n’est donc pas raisonnable de penser que les coûts sont constants entre les différents statuts, puisqu’en un sens, passer directement de D à T peut être considéré comme être passé d’abord de D à C puis de C à D. Nous allons donc faire une matrice de coûts hiérarchisée. seqcost nous permets de produire une matrice de coûts constants, que nous allons ensuite modifier manuellement. Pour le coût indel, le plus simple est de considérer la moitié du coût de substitution maximum.

    +
    couts <- seqcost(seq_all, method = "CONSTANT")
    +
     [>] creating 4x4 substitution-cost matrix using 2 as constant value
    +
    couts
    +
    $indel
    +[1] 1
    +
    +$sm
    +                  diagnostiqué-> en soins->
    +diagnostiqué->                 0          2
    +en soins->                     2          0
    +sous traitement->              2          2
    +inf. contrôlée->               2          2
    +                  sous traitement-> inf. contrôlée->
    +diagnostiqué->                    2                2
    +en soins->                        2                2
    +sous traitement->                 0                2
    +inf. contrôlée->                  2                0
    +
    couts$sm[1, ] <- c(0, 1, 2, 3)
    +couts$sm[2, ] <- c(1, 0, 1, 2)
    +couts$sm[3, ] <- c(2, 1, 0, 1)
    +couts$sm[4, ] <- c(3, 2, 1, 0)
    +couts$indel <- max(couts$sm) / 2
    +couts
    +
    $indel
    +[1] 1.5
    +
    +$sm
    +                  diagnostiqué-> en soins->
    +diagnostiqué->                 0          1
    +en soins->                     1          0
    +sous traitement->              2          1
    +inf. contrôlée->               3          2
    +                  sous traitement-> inf. contrôlée->
    +diagnostiqué->                    2                3
    +en soins->                        1                2
    +sous traitement->                 0                1
    +inf. contrôlée->                  1                0
    +
    dist_all <- seqdist(seq_all, method = "OM", sm = couts$sm, indel = couts$indel)
    +
     [>] 2929 sequences with 4 distinct states
    +
     [>] checking 'sm' (size and triangle inequality)
    +
     [>] 1370 distinct  sequences 
    +
     [>] min/max sequence lengths: 1/51
    +
     [>] computing distances using the OM metric
    +
     [>] elapsed time: 1.8 secs
    +

    Calculons le dendrogramme et représentons le avec le tapis de séquence grace à seq_heatmap de l’extension seqhandbook.

    +
    arbre_all <- hclust(as.dist(dist_all), method = "ward.D2")
    +library(seqhandbook, quietly = TRUE)
    +seq_heatmap(seq_all, arbre_all)
    +

    +

    Il apparaît que les différentes séquences sont principalement regroupées en fonction de leur longueur. En effet, pour passer d’une séquence courte à une séquence longue il faut ajouter des statuts pour compléter la séquence ce qui induit de facto une distance élevée (en raison du coût indel). Dès lors, lorsque l’on travaille avec des séquences aux longueurs très disparates, une classification ascendante hiérarchique va produire une typologie de séquences courtes et de séquences longues, ce qui n’est pas forcément ce que l’on recherche.

    +

    Dans notre exemple, nous pouvons considérer que les séquences courtes ne sont pas pertinentes à retenir dans l’analyse car l’observation n’est pas assez longue pour voir le parcours de soins des patients. Une solution consiste à ne retenir que les individus observées au moins n mois et analyser leur trajectoire sur seulement n mois, ce qui permet de n’avoir que des séquences de même longueur. Dès lors, la distance entre deux séquences ne dépendra plus que des différences de parcours. On serait tenté de prendre un n élévé pour avoir ainsi des parcours de soins longs. Mais dans ce cas là, l’analyse ne se fera que sur un tout petit nombre d’individus et on manquera de puissance. Si, à l’inverse, on prends un n petit, nous aurons des effectifs élevés mais les séquences seront peut-être trop courtes pour mettre en évidence la variété des trajectoires. Il faut dès lors trouver un compromis entre ces deux contraintes.

    +

    Si l’on regarde notre premier graphique montrant le nombre d’observations au cours du temps, il apparaît une sorte de point d’inflexion au niveau de M18 avec un brusque décrochage. D’un autre côté, 18 mois offre un minimum de durée d’observations pour espérer voir émerger des trajectoires plus complexes.

    +
    +
    +

    Une seconde analyse de séquences limitées aux 18 premiers mois

    +

    Reprenons notre analyse en se limitant aux individus observés au moins 18 mois (soit 19 status entre M0 et M18) et en se limitant aux 18 premiers mois pour modéliser les séquences. La fonction seqlength permets de récupérer la longueur de chaque séquence.

    +
    large$seq_length <- seqlength(seq_all)
    +large_m18 <- large %>%
    +  dplyr::filter(seq_length >= 19) %>%
    +  dplyr::select(id:m18)
    +  
    +seq_m18 <- seqdef(
    +  large_m18 %>% dplyr::select(m0:m18),
    +  id = large_m18$id,
    +  alphabet = c("D", "C", "T", "S"),
    +  states = c("diagnostiqué", "en soins", "sous traitement", "inf. contrôlée"),
    +  cpal = viridis(4, direction = -1)
    +)
    +
     [>] state coding:
    +
           [alphabet]  [label]         [long label] 
    +
         1  D           diagnostiqué    diagnostiqué
    +
         2  C           en soins        en soins
    +
         3  T           sous traitement sous traitement
    +
         4  S           inf. contrôlée  inf. contrôlée
    +
     [>] 1317 sequences in the data set
    +
     [>] min/max sequence length: 19/19
    +
    dist_m18 <- seqdist(seq_m18, method = "OM", sm = couts$sm, indel = couts$indel)
    +
     [>] 1317 sequences with 4 distinct states
    +
     [>] checking 'sm' (size and triangle inequality)
    +
     [>] 578 distinct  sequences 
    +
     [>] min/max sequence lengths: 19/19
    +
     [>] computing distances using the OM metric
    +
     [>] elapsed time: 0.4 secs
    +
    arbre_m18 <- hclust(as.dist(dist_m18), method = "ward.D2")
    +seq_heatmap(seq_m18, arbre_m18)
    +

    +

    Reste maintenant à décider du nombre de classes à retenir. Encore une fois, c’est un équilibre à trouver entre le niveau de détails voulus et le niveau de simplification requis pour permettre l’analyse.

    +

    Pour faciliter ce choix, on peut avoir recours à la fonction as.seqtree de l’extension WeightedCluster, couplée à la fonction seqtreedisplay. ATTENTION : pour que le graphique puisse être produit, il faut que le logiciel libre GraphViz (https://graphviz.gitlab.io/) soit installé sur votre PC. On peut également installer GraphViz avec le code ci-dessous :

    +
    if (!requireNamespace("BiocManager", quietly = TRUE))
    +    install.packages("BiocManager")
    +BiocManager::install("Rgraphviz")
    +

    La combinaison des deux fonctions va permettre de représenter l’évolution des catégories au fur-et-à-mesure que l’on coupe le dendrogramme plus bas. On peut choisir le type de graphique utilisé avec l’argument type (voir l’aide de seqplot) et le nombre maximum de clusters avec nclust.

    +
    library(WeightedCluster, quietly = TRUE)
    +seqtree_m18 <- as.seqtree(arbre_m18, seqdata = seq_m18, diss = dist_m18, ncluster = 7)
    +seqtreedisplay(seqtree_m18, type="I", border=NA, show.depth=TRUE)
    +
    +

    Représentation graphique produite par seqtreedisplay

    +
    +

    Afin d’éviter de multiplier les sous-groupes, nous n’allons conserver que 4 catégories.

    +
    large_m18$typo_cah <- cutree(arbre_m18, 4)
    +
    +

    Il est aussi possible de se baser sur divers indicateurs statistiques sur la qualité de chaque partition. Pour cela, on pourra par exemple avoir recours à la fonction as.clustrange de l’extension WeightedCluster. Essayez par exemple les commandes ci-après :

    +
    nc <- as.clustrange(arbre_m18, dist_m18)
    +summary(nc, max.rank = 3)
    +plot(nc, norm = "zscore")
    +

    Pour plus d’informations, voir le manuel de la librairie WeightedCluster, chapitre 7.

    +
    +

    On peut représenter le découpage du dendrogramme avec fviz_dend fournie par factoextra

    +
    library(factoextra)
    +fviz_dend(arbre_m18, k = 4, show_labels = FALSE, rect = TRUE)
    +

    +

    Comme expliqué par Matthias Studer dans le manuel de la librairie WeightedCluster, plusieurs critiques peuvent être adressées aux procédures hiérarchiques, en particulier le fait que la fusion de deux groupes se fait en maximisant un critère local.

    +

    L’algorithme PAM pour Partitioning Around Medoids suit une autre logique que les algorithmes hiérarchiques et vise à obtenir la meilleure partition d’un ensemble de données en un nombre prédéfini de groupes. Il a l’avantage de maximiser un critère global et non uniquement un critère local. Par contre, le nombre de classes doit être fixé à l’avance.

    +

    Ayant décidé de retenir 4 classes au regard de notre classification ascendante hiérarchique, nous pouvons voir si l’algorithme PAM permets d’améliorer nos 4 classes. Nous allons utiliser la fonction wcKMedoids de l’extension WeightedCluster en lui indiquant comme partition initiale celle obtenue avec la classigication hiérarchique.

    +
    pam_m18 <- wcKMedoids(dist_m18, k = 4, initialclust = arbre_m18)
    +large_m18$typo_pam <- pam_m18$clustering
    +

    Un tableau croisé nous permets de voir que les deux typologies restent proches.

    +
    library(gtsummary)
    +large_m18 %>% 
    +  tbl_cross(row = typo_cah, col = typo_pam)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique + typo_pam + Total
    62385410
    typo_cah
    15224039565
    2033303336
    312035913276
    42305112140
    Total5462103941671 317
    +
    +

    Regardons les tapis de séquence des deux typologies.

    +
    large_m18$ordre_cmd <- cmdscale(as.dist(dist_m18), k = 1)
    +seqIplot(seq_m18, group = large_m18$typo_cah, sortv = large_m18$ordre_cmd)
    +

    +
    seqIplot(seq_m18, group = large_m18$typo_pam, sortv = large_m18$ordre_cmd)
    +

    +

    Comme on le voit les deux typologies obtenues sont très proches. Suivant le cas, à vous de choisir celle qui semble la plus pertinente d’un point de vue sociologique. Il existe également divers indicateurs statisques pour mesurer la qualité d’une partition (voir le manuel de la librairie WeightedCluster de Matthias Studer). Ils peuvent être calculés avec la fonction wcClusterQuality. Comparons les deux typologies obtenues.

    +
    tab <- tibble(
    +  stat = names(wcClusterQuality(dist_m18, large_m18$typo_cah)$stats),
    +  cah = wcClusterQuality(dist_m18, large_m18$typo_cah)$stats,
    +  pam = wcClusterQuality(dist_m18, large_m18$typo_pam)$stats
    +)
    +gt::gt(tab) %>% gt::fmt_number(2:3, decimals = 2, sep_mark = " ")
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    statcahpam
    PBC0.700.71
    HG0.930.95
    HGSD0.930.95
    ASW0.530.57
    ASWw0.530.57
    CH938.971 023.12
    R20.680.70
    CHsq3 351.213 980.60
    R2sq0.880.90
    HC0.030.02
    +
    +

    Selon ces indicateurs calculés, l’approche PAM obtiendrait une partition légèrement de meilleure qualité que celle obtenuepar CAH.

    +

    L’extension WeightedCluster fournie aussi une fonction wcSilhouetteObs permettant de mesurer la silhouette de chaque séquence. Plus cette métrique est élevée et proche de 1, plus la séquence est proche du centre de classe et caractéristique de la classe. On peut utiliser cette métrique pour classer les séquences sur le tapis de séquences.

    +
    large_m18$sil <- wcSilhouetteObs(dist_m18, large_m18$typo_pam)
    +seqIplot(seq_m18, group = large_m18$typo_pam, sortv = large_m18$sil)
    +

    +

    Nous voyons émerger quatre groupes distincts :

    +
      +
    • les rapides, qui entrent en soins et initient le traitement dès les premiers mois suivant le diagnostic ;
    • +
    • les lents, qui entrent en soins et initient le traitement plus tardivement, après M6 ;
    • +
    • les inaboutis, qui entrent en soins mais n’initient pas le traitement ;
    • +
    • les hors soins, qui ne sont pas entrés en soins où n’y sont pas restés.
    • +
    +

    Le graphique obtenu avec seqIplot affiche visuellement chaque groupe avec la même hauteur, pouvant laisser accroire que chaque groupe a le même poids dans l’échantillon. Pour produire une représentation graphique des tapis de séquences plus correcte, où chaque la hauteur de chaque groupe correspondrait à son poids dans l’échantillon, nous allons passer par ggplot2. Un tapis de séquences peut-être vu comme un raster et dès lors représenté avec geom_raster. Pour travailler avec ggplot2, nos données doivent être au format tidy, c’est-à-dire avec une ligne par point d’observation, soit une ligne par personne et par jour. Nous allons donc repartir du fichier care_trajectories. Le mois d’observation indiquera la position en abscisse. Quant à la position en ordonnée, il faudra que nous la calculions, séparément pour chaque groupe, afin d’éviter des lignes vides dans le graphique.

    +
    # nommer les groupes
    +large_m18$groupe <- factor(
    +  large_m18$typo_pam,
    +  c(85, 23, 410, 6),
    +  c("Rapides", "Lents", "Inaboutis", "Hors soins")
    +)
    +
    +# calculer le rang des individus dans chaque groupe
    +large_m18 <- large_m18 %>%
    +  group_by(groupe) %>%
    +  arrange(ordre_cmd) %>%
    +  mutate(rang_cmd = rank(ordre_cmd, ties.method = "first"))
    +
    +# créer un fichier long
    +long_m18 <- care_trajectories %>%
    +  filter(id %in% large_m18$id & month <= 18) %>%
    +  left_join(
    +    large_m18 %>% dplyr::select(id, groupe, rang_cmd),
    +    by = "id"
    +  )
    +
    +long_m18$care_statusF <- to_factor(long_m18$care_status)
    +
    +# calculer les effectifs par groupe
    +tmp <- large_m18 %>% 
    +  dplyr::count(groupe) %>%
    +  mutate(groupe_n = paste0(groupe, "\n(n=", n, ")"))
    +
    +long_m18 <- long_m18 %>%
    +  left_join(
    +    tmp %>% dplyr::select(groupe, groupe_n),
    +    by = "groupe"
    +  )
    +
    # graphique des tapis de séquences
    +ggplot(long_m18) +
    +  aes(x = month, y = rang_cmd, fill = care_statusF) +
    +  geom_raster() +
    +  facet_grid(groupe_n ~ ., space = "free", scales = "free") +
    +  scale_x_continuous(breaks = 0:6*3, labels = paste0("M", 0:6*3)) +
    +  scale_y_continuous(breaks = 0:5*100, minor_breaks = NULL) +
    +  xlab("") + ylab("") +
    +  theme_light() +
    +  theme(legend.position = "bottom") +
    +  labs(fill = "Statut dans les soins") + 
    +  scale_fill_viridis(discrete = TRUE, direction = -1) +
    +  guides(fill = guide_legend(nrow = 2))
    +

    +
    +
    +

    Facteurs associés à l’appartenance à chaque groupe

    +

    Une fois les différents groupes de trajectoires identifiés, il est courant de vouloir regarder si certains facteurs influencent l’appartenance à un groupe plutôt qu’un autre. Dans nos données d’exemple, nous nous intéresserons aux variables suivantes : sexe, groupe d’âges et niveau d’éducation.

    +

    Ces différentes variables sont renseignées pour chaque mois dans le tableau de données care_trajectories, en tenant compte des éventuels changements au cours du temps. Ici, notre analyse est menée au niveau individuel. Nous allons donc récupérer la valeur de ces différentes variables au moment du diagnostic, à savoir à M0.

    +
    large_m18 <- large_m18 %>%
    +  left_join(
    +    care_trajectories %>%
    +      dplyr::filter(month == 0) %>%
    +      dplyr::select(id, sex, age, education),
    +    by = "id"
    +  )
    +

    La fonction tbl_summary de l’extension gtsummary permets de produire aisément une série de tableaux croisés. À noter le recours à add_p() pour ajouter les p-valeurs au test du Chi².1 La fonction add_overall permets d’ajouter une colonne avec l’ensemble de l’échantillon. Notez que nous avons utiliser unlabelled de l’extension labelled pour convertir en amont les vecteurs labellisés en facteurs.

    +
    library(gtsummary)
    +large_m18 %>%
    +  ungroup() %>%
    +  unlabelled() %>% 
    +  tbl_summary(by = "groupe", include = c("groupe", "sex", "age", "education")) %>%
    +  add_p() %>%
    +  add_overall(last = TRUE)
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Rapides, N = 3941 + +Lents, N = 2101 + +Inaboutis, N = 1671 + +Hors soins, N = 5461 + +p-valeur2 + +Total, N = 1 3171 +
    Sexe<0,001
    homme104 (26%)61 (29%)42 (25%)243 (45%)450 (34%)
    femme290 (74%)149 (71%)125 (75%)303 (55%)867 (66%)
    Âge<0,001
    16-2996 (24%)71 (34%)62 (37%)235 (43%)464 (35%)
    30-59269 (68%)128 (61%)91 (54%)281 (51%)769 (58%)
    60+29 (7,4%)11 (5,2%)14 (8,4%)30 (5,5%)84 (6,4%)
    Education0,002
    primaire63 (16%)47 (22%)30 (18%)123 (23%)263 (20%)
    secondaire126 (32%)83 (40%)61 (37%)212 (39%)482 (37%)
    supérieur205 (52%)80 (38%)76 (46%)211 (39%)572 (43%)
    +

    + 1 + + + n (%) +

    +

    + 2 + + + test du khi-deux d'indépendance +

    +
    +
    +

    Une manière de présenter ces mêmes données de manière plus visuelle consiste à réaliser un diagramme en barres cumulées (voir le chapitre sur les graphiques bivariés). Ici, nous utilisons une boucle for (voir le chapitre sur les structures conditionnelles) pour calculer les différents tableaux croisés et les fusionner avec bind_rows (voir la section concaténation de tables du chapitre dédié à dplyr). Nous en profitions également pour calculer le test du Chi² et l’afficher avec le nom de la variable sur le graphique.

    +

    Note : pour afficher les proportions sur le graphique, aurons recours recours à la statistique stat_prop de l’extension GGally.

    +
    res <- tibble()
    +explanatory <- c(
    +  "sex" = "Sexe", 
    +  "age" = "Âge",
    +  "education" = "Education"
    +)
    +for (v in names(explanatory)) {
    +  tmp <- tibble::as_tibble(table(large_m18$groupe, to_factor(large_m18[[v]])), .name_repair = "unique")
    +  names(tmp) <- c("groupe", "level", "n")
    +  test <- chisq.test(large_m18$groupe, to_factor(large_m18[[v]]))
    +  tmp$var <- paste0(
    +    explanatory[v],
    +    "\n",
    +    scales::pvalue(test$p.value, add_p = TRUE)
    +  )
    +  res <- bind_rows(res, tmp)
    +}
    +
    +
    +# stat_prop() a besoin d'un facteur
    +res$level <- factor(res$level)
    +
    +library(GGally)
    +ggplot(res) +
    +  aes(x = level, fill = groupe, weight = n) +
    +  geom_bar(position = "fill") +
    +  geom_text(
    +    aes(by = level, label = scales::percent(..prop.., accuracy = 1)), 
    +    stat = "prop", position = position_fill(.5)
    +  ) +
    +  facet_grid(var ~ ., scales = "free", space = "free") +
    +  scale_y_continuous(labels = scales::percent, breaks = 0:5/5) +
    +  coord_flip() +
    +  theme(legend.position = "bottom") +
    +  xlab("") + ylab("") + labs(fill = "")
    +

    Un graphique similaire peut s’obtenir très facilement en ayant recours à la fonction ggbivariate de l’extension GGally2.

    +
    library(GGally)
    +ggbivariate(
    +  large_m18 %>% ungroup() %>% unlabelled(), 
    +  outcome = "groupe", 
    +  explanatory = c("sex", "age", "education"),
    +  columnLabelsY = c("Sex", "Âge", "Éducation")
    +) + labs(fill = "")
    +

    +

    Pour mieux visualiser les relations entre les variables, on peut avoir recours à la fonction ggtable de l’extension GGally qui permets de représenter les résidus du Chi².

    +
    library(GGally)
    +ggtable(
    +  large_m18 %>% ungroup() %>% unlabelled(), 
    +  columnsX = "groupe", 
    +  columnsY = c("sex", "age", "education"),
    +  cells = "col.prop",
    +  fill = "std.resid",
    +  columnLabelsX = "Type de trajectoire", 
    +  columnLabelsY = c("Sex", "Âge", "Éducation"),
    +  legend = 1
    +) + 
    +  labs(fill = "Résidus standardizés du Chi²") +
    +  theme(legend.position = "bottom")
    +

    +

    On considère qu’une cellule est surreprésentée si son résidu standardisé du Chi² est supérieur à 2 ou 3, et qu’elle est sous-représentée si le résidu est inférieur à -2 ou -3.

    +

    On peut ainsi noter que les hommes, les jeunes et ceux vivant à plus de 10 kilomètres d’une clinique sont plus souvent dans le groupe Hors soins. Inversement, les femmes et les plus éduqués sont plus souvent dans le groupe des Rapides. Résultat inattendu, les ménages les plus riches sont moins souvent dans les groupes ayant initiés un traitement (Rapides et Lents), ce résultat pouvant s’expliquer en partie par le fait que les plus aisés peuvent plus facilement accéder à des soins dans le secteur privé, et donc faussement apparaître Hors soins, car seuls les soins reçus dans le secteur public ont été mesurés dans cette étude.

    +

    Pour affiner les résultats, on aura recours à un modèle multivarié en exécutant une régression logistique multinomiale avec multinom de l’extension nnet (pour plus de détails, voir le chapitre dédié).

    +
    library(nnet)
    +large_m18 <- large_m18 %>% ungroup()
    +large_m18$groupe2 <- relevel(large_m18$groupe, "Hors soins")
    +regm <- multinom(
    +  groupe2 ~ sex + age + education, 
    +  data = to_factor(large_m18)
    +)
    +
    library(GGally)
    +ggcoef_multinom(regm, exponentiate = TRUE)
    +

    +

    Nous pouvons représenter les effets des variables du modèle avec la fonction ggeffect de ggeffects.

    +
    library(ggeffects)
    +cowplot::plot_grid(plotlist = plot(ggeffect(regm)), ncol = 3)
    +

    +
    +
    +

    Modèle mixte à classes latentes

    +

    Un autre type d’approche envisageable pour identifier des classes de trajectoires est celle des modèles mixtes à classes latentes. Ce type de modèles peut prendre en compte une grande variété d’indicateurs, continus, binaires ou ordinaux. On peut y intégrer des co-variables et il n’est pas nécessaire de disposer du même nombre d’observations par individu.

    +

    Nous n’aborderons que brièvement ici ce type de modèles complexes. Sous R, ils peuvent être réalisés via l’extension lcmm et sa fonction homonyme lcmm.

    +

    Commençons par préparer les données.

    +
    care_trajectories <- care_trajectories %>%
    +  mutate(
    +    num_status = as.integer(to_factor(care_status)),
    +    sexF = to_factor(sex),
    +    ageF = to_factor(age),
    +    educationF = to_factor(education)
    +  )
    +

    Ici, nous allons modéliser le statut dans les soins en fonction du temps. Il faut indiquer au modèle, via le paramètre ng le nombre de groupes ou classes latentes souhaité. Ici, nous avons retenu 4 en lien avec les résultats de notre analyse de séquences. L’argument link = "thresholds" permets d’indiquer que notre variable d’intérêt est ordinale. Les modèles lcmm peuvent également prendre en compte des variables continues.

    +
    library(lcmm)
    +mod4 <-lcmm(
    +  num_status ~ month, random = ~ month, subject = 'id', 
    +  mixture = ~ month, ng = 4, idiag = TRUE, data = care_trajectories, 
    +  link = "thresholds"
    +)
    +
    +

    Attention : le temps de calcul de ce type de modèle peut être long (plusieurs heures dans notre exemple), suivant le nombre de paramètres, le nombre d’observations et la puissance de votre machine.

    +

    Si vous souhaitez récupérer directement les résultats du modèle, vous pouvez exécuter la commande suivante :

    +
    library(lcmm)
    +load(url("https://github.com/larmarange/analyse-R/raw/gh-pages/data/trajectoires_mod4_lcmm.RData"))
    +
    +

    Voyons comment se présentent les résultats.

    +
    summary(mod4)
    +
    General latent class mixed model 
    +     fitted by maximum likelihood method 
    + 
    +lcmm(fixed = num_status ~ month, mixture = ~month, random = ~month, 
    +    subject = "id", ng = 4, idiag = TRUE, link = "thresholds", 
    +    data = care_trajectories)
    + 
    +Statistical Model: 
    +     Dataset: care_trajectories 
    +     Number of subjects: 2929 
    +     Number of observations: 49365 
    +     Number of latent classes: 4 
    +     Number of parameters: 15  
    +     Link function: thresholds  
    + 
    +Iteration process: 
    +     Convergence criteria satisfied 
    +     Number of iterations:  52 
    +     Convergence criteria: parameters= 5.2e-10 
    +                         : likelihood= 5.4e-07 
    +                         : second derivatives= 3.5e-07 
    + 
    +Goodness-of-fit statistics: 
    +     maximum log-likelihood: -26612.74  
    +     AIC: 53255.48  
    +     BIC: 53345.22  
    + 
    +     Discrete posterior log-likelihood: -26612.74  
    +     Discrete AIC: 53255.48  
    + 
    +     Mean discrete AIC per subject: 9.0911  
    +     Mean UACV per subject: 9.1045  
    +     Mean discrete LL per subject: -9.0859  
    + 
    +Maximum Likelihood Estimates: 
    + 
    +Fixed effects in the class-membership model:
    +(the class of reference is the last class) 
    +
    +                     coef      Se    Wald p-value
    +intercept class1  0.00832 0.04503   0.185 0.85334
    +intercept class2 -0.42002 0.12990  -3.233 0.00122
    +intercept class3 -0.02992 0.09675  -0.309 0.75712
    +
    +Fixed effects in the longitudinal model:
    +
    +                                     coef      Se    Wald
    +intercept class1 (not estimated)        0                
    +intercept class2                 -1.49825 0.08163 -18.355
    +intercept class3                 -0.41418 0.05228  -7.923
    +intercept class4                 -2.50344 0.09211 -27.179
    +month class1                      0.21757 0.00332  65.447
    +month class2                      0.01032 0.00506   2.039
    +month class3                      0.17720 0.00338  52.373
    +month class4                      0.00449 0.00512   0.876
    +                                 p-value
    +intercept class1 (not estimated)        
    +intercept class2                 0.00000
    +intercept class3                 0.00000
    +intercept class4                 0.00000
    +month class1                     0.00000
    +month class2                     0.04142
    +month class3                     0.00000
    +month class4                     0.38090
    +
    +
    +Variance-covariance matrix of the random-effects:
    +          intercept   month
    +intercept   4.39542        
    +month       0.00000 0.12653
    +
    +Residual standard error (not estimated) = 1
    +
    +Parameters of the link function:
    +
    +                  coef      Se    Wald p-value
    +thresh. parm1  0.94803 0.04753  19.945 0.00000
    +thresh. parm2  1.10727 0.00652 169.922 0.00000
    +thresh. parm3  1.08980 0.00727 149.816 0.00000
    +

    On dispose d’un AIC et d’un BIC. Ainsi, une stratégie possible pour déterminer le nombre de classes consiste à calculer un modèle différent pour chaque nombre de classes envisagé puis à retenir le modèle ayant le plus faible AIC ou BIC.

    +

    Pour chaque observation, le modèle a calculé la probabilité qu’elle appartienne à chacune des 4 classes identifiées. La fonction postprob fournit des statistiques sur cette classification.

    +
    postprob(mod4)
    +
     
    +Posterior classification: 
    +  class1 class2 class3  class4
    +N 844.00 153.00 424.00 1508.00
    +%  28.82   5.22  14.48   51.49
    + 
    +Posterior classification table: 
    +     --> mean of posterior probabilities in each class 
    +        prob1  prob2  prob3  prob4
    +class1 0.5945 0.0735 0.2476 0.0844
    +class2 0.0924 0.7257 0.0881 0.0938
    +class3 0.1576 0.1103 0.6561 0.0760
    +class4 0.1522 0.2052 0.1865 0.4561
    + 
    +Posterior probabilities above a threshold (%): 
    +         class1 class2 class3 class4
    +prob>0.7  23.58  54.90  38.68   6.90
    +prob>0.8  15.88  48.37  30.19   4.97
    +prob>0.9  11.26  33.99  21.93   3.51
    + 
    +

    Les classes et les probabilités d’appartenance à chacune sont disponibles aisément.

    +
    head(mod4$pprob)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    idclassprob1prob2prob3prob4
    340.16866290.22185690.20162420.4078560
    940.21237590.20519870.23290090.3495245
    1340.15902130.22602050.18796100.4269972
    1510.99921060.00000000.00000040.0007890
    1810.56163440.04729770.34788220.0431858
    2140.16866290.22185690.20162420.4078560
    +

    Récupérons la classe dans notre fichier de données.

    +
    care_trajectories <- care_trajectories %>%
    +  left_join(
    +    mod4$pprob %>% dplyr::select(id, mod4_class = class),
    +    by = "id"
    +  )
    +

    Améliorons les intitulés des classes et ajoutons le nombre d’individu par classes.

    +
    n_par_classe <- table(mod4$pprob$class)
    +n_par_classe
    + + + + + + + + + + + + + +
    1234
    8441534241508
    +
    care_trajectories$mod4_class2 <- factor(
    +  care_trajectories$mod4_class,
    +  levels = 1:4,
    +  labels = paste0(
    +    "Classe ",
    +    1:4,
    +    " (n=",
    +    n_par_classe,
    +    ")"
    +  )
    +)
    +

    Représentons la cascade observée dans chacune de ces classes.

    +
    care_trajectories$care_statusF <- to_factor(care_trajectories$care_status)
    +ggplot(care_trajectories %>% filter(month <= 36)) +
    +  aes(x = month, fill = care_statusF) +
    +  geom_bar(color = "gray50", width = 1, position = "fill") +
    +  scale_x_continuous(breaks = 0:6*6, labels = paste0("M", 0:6*6)) +
    +  scale_y_continuous(labels = scales::percent) +
    +  xlab("") + ylab("") +
    +  theme_light() +
    +  theme(legend.position = "bottom") +
    +  labs(fill = "Statut dans les soins") + 
    +  scale_fill_viridis(discrete = TRUE, direction = -1) +
    +  guides(fill = guide_legend(nrow = 2)) +
    +  facet_grid(~ mod4_class2)
    +

    +

    Une manière alternative de présenter les classes consiste à représenter chaque mois, non pas la distribution dans chaque état, mais un état moyen en considérant que le statut dans les soins peut être assimilé à un score allant de 1 à 4.

    +

    Les valeurs moyennes seront calculées à la volée grace à la statistique stat_weighted_mean de GGally.

    +
    library(GGally)
    +ggplot(care_trajectories %>% filter(month <= 36)) + 
    +  aes(x = month, y = num_status, color = mod4_class2) +
    +  geom_line(stat = "weighted_mean", size = 1.5) +
    +  scale_x_continuous(breaks = 0:6*6, labels = paste0("M", 0:6*6)) +
    +  scale_y_continuous(
    +    breaks = 1:4, limits = c(1, 4),
    +    labels = c("diagnostiqué", "suivi", "sous traitement", "contrôlé")
    +  ) +
    +  xlab("") + ylab("Statut moyen") + labs(color = "") +
    +  theme_classic() +
    +  theme(
    +    legend.position = "bottom", 
    +    panel.grid.major = element_line(colour = "grey80")
    +  )
    +

    +

    Il faut cependant rester vigilant, dans la mesure où ce type de représentation synthétique peut masquer la diversité des trajectoires sous-jacentes, notamment en termes de durée ou d’enchaînement des événements. Même si cela est peut-être plus difficile à lire, il est toujours bon de regarder les tapis de séquences.

    +
    care_trajectories <- care_trajectories %>%
    +  group_by(mod4_class) %>%
    +  mutate(tmp_rang = fct_infreq(factor(id))) %>%
    +  ungroup()
    +
    ggplot(care_trajectories %>% filter(month <= 36)) +
    +  aes(x = month, y = tmp_rang, fill = care_statusF) +
    +  geom_raster() +
    +  facet_grid(mod4_class2 ~ ., space = "free", scales = "free") +
    +  scale_x_continuous(breaks = 0:6*6, labels = paste0("M", 0:6*6), expand = c(0, 0)) +
    +  scale_y_discrete(labels = NULL) +
    +  xlab("") + ylab("") +
    +  theme_minimal() +
    +  theme(legend.position = "bottom") +
    +  labs(fill = "Statut dans les soins") + 
    +  scale_fill_viridis(discrete = TRUE, direction = -1) +
    +  guides(fill = guide_legend(nrow = 2))
    +

    +
    +
    +

    Modèle à observations répétées

    +

    Pour prendre en considération l’ensemble des observations présentes (sans se limiter aux individus observés au moins sur une certaine période), nous pouvons avoir recours à un modèle à observations répétées.

    +

    Il s’agit de modèles classiques sauf qu’au lieu de considérer une ligne par individu, nous allons intégrer dans le modèle une ligne par individu et par pas de temps. Dans la mesure où nous avons plusieurs observations pour une même personne, cela doit être pris en compte par l’ajout d’un effet aléatoire dans le cadre d’un modèle mixte ou en ayant recours à un (voir le chapitre sur les modèles à effets aléatoires).

    +

    Vue la nature de notre variable d’intérêt (plusieurs modalités ordonnées), nous aurons recours à une régression logistique ordinale (voir le chapitre dédié). Pour un modèle mixte ordinal on peut utiliser la fonction clmm de l’extension ordinal. Pour un modèle GEE ordinal, citons ordgee de l’extension geepack ou encore ordLORgee de multgee. Il importe également que la dimension temporelle soit inclue dans les variables du modèle.

    +

    Ici, nous allons utiliser ordgee. Il nous faut tout d’abord transformer notre variable d’intérêt en un facteur ordonné.

    +
    care_trajectories$care_statusF <- care_trajectories$care_status %>%
    +  to_factor(ordered = TRUE)
    +

    Nous allons transformer nos variables explicatives en facteurs. Pour le temps, dans la mesure où sont effet n’est pas forcément linéaire, nous allons l’intégrer en tant que variable catégorielle. Par contre, comme nous n’avons que très peu d’observations individuelles après 3 ans, nous ne prendrons en compte que les observations des 36 premiers mois. Nous allons aussi retirer les observations à M0 puisqu’à ce moment précis tous les individus sont dans la même situation (diagnostiqués mais pas en soins.)

    +
    ct36 <- care_trajectories %>%
    +  filter(month > 0 & month <= 36) %>%
    +  mutate(
    +    sexF = to_factor(sex),
    +    ageF = to_factor(age),
    +    educationF = to_factor(education),
    +    monthF = to_factor(month)
    +  )
    +

    Calculons notre modèle.

    +
    library(geepack)
    +mod_td <- ordgee(
    +  care_statusF ~ sexF + ageF + educationF + monthF,
    +  data = ct36,
    +  id = ct36$id
    +)
    +

    Les coefficients du modèle s’obtiennent avec summary. Malheureusement, il n’existe pas de tieder pour ce type de modèle. Nous allons donc procéder manuellement.

    +
    res <- summary(mod_td)$mean
    +res$term <- rownames(res)
    +head(res)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    estimatesan.sewaldpterm
    Inter:diagnostiqué, mais pas suivi-2.85698480.1272376504.178990.0000000Inter:diagnostiqué, mais pas suivi
    Inter:suivi, mais pas sous traitement-3.42478220.1289357705.537050.0000000Inter:suivi, mais pas sous traitement
    Inter:sous traitement, mais infection non contrôlée-3.94640560.1320344893.365220.0000000Inter:sous traitement, mais infection non contrôlée
    sexFfemme0.74197220.086777773.106950.0000000sexFfemme
    ageF30-590.55766090.089003139.258220.0000000ageF30-59
    ageF60+0.71470370.193024313.709700.0002133ageF60+
    +

    Les intervalles de confiance à 95% ne sont pas déjà calculés. Faisons-le donc nous même.

    +
    mult <- stats::qnorm((1 + .95) / 2)
    +res$conf.low <- res$estimate - mult * res$san.se
    +res$conf.high <- res$estimate + mult * res$san.se
    +

    Enfin, nous souhaitons disposer des odds ratios et non des coefficients bruts. Il faut avoir recours à la fonction exp (exponentielle).

    +
    res$estimate <- exp(res$estimate)
    +res$conf.low <- exp(res$conf.low)
    +res$conf.high <- exp(res$conf.high)
    +

    Préparons un tableau avec les résultats. Pour le rendre plus lisible, nous allons mettre en forme les odds ratios avec une seule décimale. Améliorer le rendu des p-values et nous allons utiliser la virgule comme séparateur de décimal, comme il se doit. Nous aurons recours aux fonctions number et pvalue de l’extension scales (voir le chapitre sur la mise en forme des nombres).

    +
    tab <- res %>% 
    +  mutate (
    +    estimate = scales::number(estimate, accuracy = .01, decimal.mark = ","),
    +    p = scales::pvalue(p, decimal.mark = ","),
    +    conf.low = scales::number(conf.low, accuracy = .1, decimal.mark = ","),
    +    conf.high = scales::number(conf.high, accuracy = .1, decimal.mark = ",")
    +  ) %>%
    +  dplyr::select(
    +    Facteur = term, OR = estimate, "p-value" = p, 
    +    "IC 95% bas" = conf.low, "IC 95% haut" = conf.high
    +  )
    +
    knitr::kable(tab, row.names = FALSE, align = "lrrrr")
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FacteurORp-valueIC 95% basIC 95% haut
    Inter:diagnostiqué, mais pas suivi0,06<0,0010,00,1
    Inter:suivi, mais pas sous traitement0,03<0,0010,00,0
    Inter:sous traitement, mais infection non contrôlée0,02<0,0010,00,0
    sexFfemme2,10<0,0011,82,5
    ageF30-591,75<0,0011,52,1
    ageF60+2,04<0,0011,43,0
    educationFsecondaire1,170,1091,01,4
    educationFsupérieur1,400,0021,11,7
    monthF21,060,1471,01,1
    monthF31,36<0,0011,21,5
    monthF42,79<0,0012,53,1
    monthF53,82<0,0013,44,3
    monthF64,69<0,0014,15,4
    monthF75,63<0,0014,96,4
    monthF86,40<0,0015,67,3
    monthF97,02<0,0016,18,1
    monthF107,49<0,0016,58,6
    monthF117,96<0,0016,99,2
    monthF128,42<0,0017,39,8
    monthF139,31<0,0018,010,8
    monthF1410,11<0,0018,611,8
    monthF1510,74<0,0019,212,6
    monthF1611,46<0,0019,813,4
    monthF1711,43<0,0019,713,4
    monthF1811,53<0,0019,813,6
    monthF1911,50<0,0019,713,6
    monthF2011,70<0,0019,714,1
    monthF2111,67<0,0019,614,2
    monthF2212,60<0,00110,315,4
    monthF2312,78<0,00110,415,7
    monthF2412,76<0,00110,415,7
    monthF2513,11<0,00110,616,2
    monthF2613,60<0,00111,016,9
    monthF2714,40<0,00111,617,9
    monthF2815,65<0,00112,519,6
    monthF2914,89<0,00111,918,7
    monthF3014,98<0,00111,918,9
    monthF3114,02<0,00111,017,9
    monthF3214,27<0,00111,018,5
    monthF3314,41<0,00111,018,9
    monthF3414,73<0,00111,019,7
    monthF3516,17<0,00111,822,1
    monthF3616,07<0,00111,422,6
    +

    On peut facilement représenter tout cela graphiquement. On va supprimer les termes seuils, grâce à str_detect de stringr. On notera le recours à fct_inorder de forcats pour conserver l’ordre des termes selon leur ordre d’apparition.

    +
    res <- res[!str_detect(res$term, "Inter"),]
    +res$term <- fct_inorder(res$term)
    +res$term <- fct_recode(res$term,
    +               "femme vs. homme" = "sexFfemme",
    +               "âge : 30-59 vs. 16-29" = "ageF30-59",
    +               "âge : 60+ vs 16-29" = "ageF60+",
    +               "éducation : secondaire vs. primaire" = "educationFsecondaire",
    +               "éducation : supérieure vs. primaire" = "educationFsupérieur",
    +               "M2" = "monthF2",
    +               "M3" = "monthF3",
    +               "M4" = "monthF4",
    +               "M5" = "monthF5",
    +               "M6" = "monthF6",
    +               "M7" = "monthF7",
    +               "M8" = "monthF8",
    +               "M9" = "monthF9",
    +               "M10" = "monthF10",
    +               "M11" = "monthF11",
    +               "M12" = "monthF12",
    +               "M13" = "monthF13",
    +               "M14" = "monthF14",
    +               "M15" = "monthF15",
    +               "M16" = "monthF16",
    +               "M17" = "monthF17",
    +               "M18" = "monthF18",
    +               "M19" = "monthF19",
    +               "M20" = "monthF20",
    +               "M21" = "monthF21",
    +               "M22" = "monthF22",
    +               "M23" = "monthF23",
    +               "M24" = "monthF24",
    +               "M25" = "monthF25",
    +               "M26" = "monthF26",
    +               "M27" = "monthF27",
    +               "M28" = "monthF28",
    +               "M29" = "monthF29",
    +               "M30" = "monthF30",
    +               "M31" = "monthF31",
    +               "M32" = "monthF32",
    +               "M33" = "monthF33",
    +               "M34" = "monthF34",
    +               "M35" = "monthF35",
    +               "M36" = "monthF36")
    +res$variable <- c("sexe", "âge", "âge", "éducation", "éducation", rep("mois", 35))
    +
    +res %>% GGally::ggcoef_plot(y = "term", facet_row = "variable", colour = "variable")
    +

    +

    Sur ce graphique, on visualise bien l’évolution temporelle traduite par les odds ratios associés à chaque mois, ainsi que les effets globaux de nos covariables : les femmes ont une meilleure progression dans la cascade de soins que les hommes, de même que les plus éduqués et les plus âgés.

    +
    +
    +

    Modèle de survie multi-états

    +

    Depuis la fin du XXe siècle, de nombreux développements ont réalisés pour étendre les modèles de survie à des processus multi-états. Ces modèles permettent de considérer une grande variété de processus. Plusieurs implémentations existent dans R3. Ici, nous allons utiliser l’extension msm qui repose sur des modèles de Markov multi-états et peut prendre en compte des co-variables dans le modèle.

    +

    En premier lieu, pour cette extension, ils nous faut disposer des données sous une forme longue, c’est-à-dire avec une ligne par individu et point d’observation dans le temps, ce qui est déjà le cas du fichier care_trajectories. Les différents status possibles doivent également être codés sous la forme de nombres entiers croissants (ici 1 correspondra à D, 2 à C, 3 à T et 4 à S).

    +
    library(msm)
    +care_trajectories$status <- as.integer(to_factor(care_trajectories$care_status))
    +care_trajectories <- care_trajectories %>%
    +  arrange(id, month)
    +

    Par ailleurs, nous n’allons conserver dans l’analyse que les individus avec au moins deux points d’observation, ici ceux observés au moins jusqu’à un mois.

    +
    ct <- care_trajectories %>%
    +  filter(id %in% (care_trajectories %>% filter(month == 1) %>% pluck("id")))
    +

    La fonction statetable.msm permet de calculer le nombre et le type de transitions observées dans les données.

    +
    statetable.msm(status, id, data = ct)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    from/to1234
    1219161168467247
    25014323597210
    3261413489770
    4332304312275
    +

    Il faut ensuite définir les transitions possibles dans le modèle en faisant une matrice carrée. On indiquera 0 si la transition n’est pas possible, une valeur positive sinon.

    +
    tr <- rbind(
    +  c(0, 1, 0, 0), # de 1 : vers 2
    +  c(1, 0, 1, 0), # de 2 : vers 1 ou vers 3
    +  c(0, 1, 0, 1), # de 3 : vers 2 ou vers 4
    +  c(0, 0, 1, 0)  # de 4 : vers 3
    +)
    +

    Dans notre matrice de transitions, nous n’avons pas défini de transition directe entre le statut 1 et le statut 3, alors que de telles transitions sont pourtant observées dans notre fichier. En fait, nous pouvons considérer qu’une transition de 1 vers 3 correspond en fait à deux transitions successives, de 1 vers 2 puis de 2 vers 3. La fonction msm s’aura identifier d’elle-mêmes ces doubles, voire triples, transitions.

    +

    On peut facilement représenter notre matrice de transition sous forme de schéma à l’aide de l’excellente extension DiagrammeR.

    +
    library(DiagrammeR)
    +mermaid("
    +graph TD
    +1[diagnostiqué, mais pas suivi]
    +2[suivi, mais pas sous traitement]
    +3[sous traitement, mais infection non contrôlée]
    +4[sous traitement et infection contrôlée]
    +
    +1--entrée<br />en soins-->2
    +2--initiation<br />traitement-->3
    +2--sortie<br />de soins-->1
    +3--contrôle<br />infection-->4
    +3--arrêt<br />traitement-->2
    +4--échec<br/>virologique-->3
    +", height = 300)
    +
    +

    Il ne nous reste plus qu’à spécifier notre modèle. L’option obstype = 1 indique à msm que nos données correspondent à des snapshots à certains moments donnés (ici tous les mois) et donc que les transitions d’un état à un autre ont eu lieu entre nos points d’observation. Les types 2 et 3 correspondent à des dates de transition exactes (voir l’aide la fonction pour plus de détails).

    +
    ms_mod <- msm(
    +  status ~ month, subject = id, data = ct, qmatrix = tr, obstype = 1
    +)
    +

    En exécutant cette commande, vous risquez d’obtenir le message d’erreur suivant :

    +
    Error in Ccall.msm(params, do.what = "lik", ...) : numerical overflow in calculating likelihood
    +

    Cela est dû à un problème d’échelle dans l’optimisation du modèle qui génère des nombres plus grands que ce que peux gérer l’ordinateur. Il peut être résolu de la manière suivante. Tout d’abord, on reexécute le modèle avec l’option control = list(trace = TRUE).

    +
    ms_mod <- msm(
    +  status ~ month, subject = id, data = ct, qmatrix = tr, obstype = 1, 
    +  control = list(trace = TRUE)
    +)
    +

    On obtient le message suivant :

    +
    initial  value 74796.800445 
    +Error in Ccall.msm(params, do.what = "lik", ...) : numerical overflow in calculating likelihood
    +

    Ce qui importe de retenir, c’est la valeur initiale du paramètre d’optimisation avant que l’erreur ne se produise. On va l’utiliser (ou une valeur proche) comme paramètre d’échelle pour l’optimation avec l’option fnscale :

    +
    ms_mod <- msm(
    +  status ~ month, subject = id, data = ct, qmatrix = tr, obstype = 1, 
    +  control = list(fnscale = 75000, trace = TRUE)
    +)
    +
    initial  value 0.997291 
    +iter  10 value 0.520302
    +iter  20 value 0.497598
    +iter  30 value 0.497487
    +final  value 0.497484 
    +converged
    +Used 39 function and 37 gradient evaluations
    +

    On peut comparer la prévalence dans chaque état au cours du temps telle que modélisée par le modèle avec les valeurs observées avec la fonction plot.prevalence.msm.

    +
    plot.prevalence.msm(ms_mod)
    +

    +

    Par défaut, msm considère que les intensités de transition d’un état à un autre sont constantes au cours du temps. Or, dans notre example, il apparait que les prévalences observées varient différemment pendant les premiers mois après le diagnostic. Nous allons donc recalculer le modèle en spécifiant avec le paramètre pci que nous souhaitons considérer des intensités de transition différentes pour les trois premiers mois, la première année, la seconde année et après la seconde année. Comme il faudra plus d’itérations pour faire converger notre modèle, nous avons également augmenter la valeur du paramètre maxit (100 par défaut).

    +
    ms_mod <- msm(
    +  status ~ month, subject = id, data = ct, qmatrix = tr, obstype = 1, 
    +  pci = c(3, 12, 24),
    +  control = list(fnscale = 75000, trace = TRUE, maxit = 500)
    +)
    +
    +

    Là encore le temps de calcul peut être assez long. Vous pouvez récupérer les résultats du modèle avec la commande :

    +
    load(url("https://github.com/larmarange/analyse-R/blob/gh-pages/data/trajectoires_ms_mod.RData"))
    +
    +

    Comparons à nouveau les prévalences estimées avec les prévalences observées.

    +
    plot.prevalence.msm(ms_mod)
    +

    +

    Comme on peut le voir, l’ajustement entre les deux a été amélioré. Les prévalences elles-mêmes peuvent s’obtenir avec prevalence.msm.

    +
    prevalence.msm(ms_mod)
    +
    $Observed
    +   State 1 State 2 State 3 State 4 Total
    +0     2799      24       0       7  2830
    +5     1551     292     297     460  2600
    +10     958     257     210     623  2048
    +15     578     168     104     603  1453
    +20     283      87      65     327   762
    +25     192      63      39     267   561
    +30     147      41      23     245   456
    +35      69      17      12     136   234
    +40      14       2       2      27    45
    +45       7       0       0      12    19
    +50       0       0       0       1     1
    +
    +$Expected
    +        State 1      State 2      State 3     State 4 Total
    +0  2799.0000000  24.00000000   0.00000000   7.0000000  2830
    +5  1436.9517812 424.94481382 358.54921455 379.5541904  2600
    +10 1017.4844830 258.18412962 191.08425368 581.2471337  2048
    +15  667.5131754 168.10101158  98.36234055 519.0234724  1453
    +20  331.9787922  81.73411456  46.82691929 301.4601739   762
    +25  233.1379082  55.54030396  32.92027747 239.4015104   561
    +30  184.4057933  40.32826279  24.41822743 206.8477165   456
    +35   91.3576268  19.57230370  12.12106094 110.9490085   234
    +40   16.9423183   3.63904521   2.30424907  22.1143875    45
    +45    6.9062654   1.49869230   0.96896925   9.6260731    19
    +50    0.3515898   0.07725341   0.05092087   0.5202359     1
    +
    +$`Observed percentages`
    +    State 1    State 2   State 3     State 4
    +0  98.90459  0.8480565  0.000000   0.2473498
    +5  59.65385 11.2307692 11.423077  17.6923077
    +10 46.77734 12.5488281 10.253906  30.4199219
    +15 39.77977 11.5622849  7.157605  41.5003441
    +20 37.13911 11.4173228  8.530184  42.9133858
    +25 34.22460 11.2299465  6.951872  47.5935829
    +30 32.23684  8.9912281  5.043860  53.7280702
    +35 29.48718  7.2649573  5.128205  58.1196581
    +40 31.11111  4.4444444  4.444444  60.0000000
    +45 36.84211  0.0000000  0.000000  63.1578947
    +50  0.00000  0.0000000  0.000000 100.0000000
    +
    +$`Expected percentages`
    +    State 1    State 2   State 3    State 4
    +0  98.90459  0.8480565  0.000000  0.2473498
    +5  55.26738 16.3440313 13.790354 14.5982381
    +10 49.68186 12.6066470  9.330286 28.3812077
    +15 45.94034 11.5692369  6.769604 35.7208171
    +20 43.56677 10.7262618  6.145265 39.5617026
    +25 41.55756  9.9002324  5.868142 42.6740660
    +30 40.43987  8.8439173  5.354874 45.3613413
    +35 39.04172  8.3642323  5.179941 47.4141062
    +40 37.64960  8.0867671  5.120553 49.1430833
    +45 36.34877  7.8878542  5.099838 50.6635426
    +50 35.15898  7.7253409  5.092087 52.0235901
    +

    Ceci dit, le format dans lequel sont renvoyées les prévalences n’est que peu pratique pour les exploiter ensuite, par exemple avec ggplot2. L’extension JLutils fournit une fonction expérimentale tidy.prevalence.msm4 permettant de transformer ce résultat dans un format tidy. JLutils est seulement disponible sur GitHub. On l’installera donc (ou on la mettra à jour) avec la commande devtools::install_github("larmarange/JLutils").

    +
    library(JLutils)
    +prev <- tidy.prevalence.msm(prevalence.msm(ms_mod, times = 0:36))
    +head(prev)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    timestatusobservedexpectedobserved.percentageexpected.percentage
    0State 127992799.00098.9045998.90459
    1State 119422329.47968.6219182.31374
    2State 117661905.41963.5480468.56490
    3State 116511552.66460.7655557.14627
    4State 115891500.04359.5800556.24458
    5State 115511436.95259.6538555.26738
    +
    prev$status <- to_factor(prev$status)
    +casc_status <- c(
    +  "diagnostiqué, mais pas suivi",
    +  "suivi, mais pas sous traitement",
    +  "sous traitement, mais infection non contrôlée",
    +  "sous traitement et infection contrôlée"
    +)
    +levels(prev$status) <- casc_status
    +

    Il est alors ensuite facile de produire le graphique de la cascade de soins, estimée par le modèle, que l’on pourra mettre en comparaison de la cascade observée que nous avions calculé tout à l’heure.

    +
    casc_est <- ggplot(prev) +
    +  aes(x = time, fill = status, weight = expected) +
    +  geom_bar(color = "gray50", width = 1, position = "fill") +
    +  scale_x_continuous(breaks = 0:6*6, labels = paste0("M", 0:6*6)) +
    +  scale_y_continuous(labels = scales::percent) +
    +  ggtitle("Cascade des soins estimée, selon le temps depuis le diagnostic") +
    +  xlab("") + ylab("") +
    +  theme_light() +
    +  theme(legend.position = "bottom") +
    +  labs(fill = "Statut dans les soins") + 
    +  scale_fill_viridis(discrete = TRUE, direction = -1) +
    +  guides(fill = guide_legend(nrow = 2))
    +cowplot::plot_grid(casc_obs, casc_est, ncol = 1)
    +

    +

    Comme on peut le voir, dans le modèle, l’évolution est plus lissée comparativement aux données brutes observées.

    +

    Un des intérêts de msm est la possibilité d’y intégrer des covariables, permettant ainsi de calculer un modèle multivarié. Les variables peuvent être dépendantes du temps, puisqu’il suffit de renseigner leur valeur à chaque point d’observation.

    +
    ct$sex <- to_factor(ct$sex)
    +ct$age <- to_factor(ct$age)
    +ct$education <- to_factor(ct$education)
    +
    ms_mod_mult <- msm(
    +  status ~ month, subject = id, data = ct, qmatrix = tr, obstype = 1, 
    +  pci = c(3, 12, 24),
    +  control = list(fnscale = 75000, trace = TRUE, maxit = 500),
    +  covariates = ~ sex + age + education
    +)
    +
    +

    Là encore le temps de calcul peut être assez long. Vous pouvez récupérer les résultats du modèle avec la commande :

    +
    load(url("https://github.com/larmarange/analyse-R/raw/gh-pages/data/trajectoires_ms_mod_mult.RData"))
    +
    +

    Les risques relatifs, ou hazard ratios en anglais, associés à chaque covariable et à chaque transition s’obtiennent avec hazard.msm. Une fois encore, le format de sortie n’est pas le plus adapté pour un traitement graphique, mais on pourra avoir recours à tidy.hazard.msm de JLutils.

    +
    hr <- tidy.hazard.msm(hazard.msm(ms_mod_mult))
    +head(hr)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    termtransitionfromtoestimateconf.lowconf.high
    sexfemmeState 1 - State 2State 1State 21.88552321.70596122.083985
    sexfemmeState 2 - State 1State 2State 10.92456600.76559971.116540
    sexfemmeState 2 - State 3State 2State 30.90159130.80444581.010468
    sexfemmeState 3 - State 2State 3State 21.09598100.88549661.356498
    sexfemmeState 3 - State 4State 3State 41.39854431.23009801.590057
    sexfemmeState 4 - State 3State 4State 30.86101440.66789181.109979
    +

    On va recoder certaines étiquettes en vue de faire un graphique des résultats.

    +
    hr$type <- case_when(
    +  hr$transition == c("State 2 - State 1") ~ "Transition descendante",
    +  hr$transition == c("State 3 - State 2") ~ "Transition descendante",
    +  hr$transition == c("State 4 - State 3") ~ "Transition descendante",
    +  TRUE ~"Transition ascendante"
    +)
    +
    +hr$term <- hr$term %>%
    +  fct_inorder() %>%
    +  fct_recode(
    +    "femme vs. homme" = "sexfemme",
    +    "30-59 vs. 16-29" = "age30-59",
    +    "60+ vs. 16-29" = "age60+",
    +    "éduc. secondaire vs. primaire" = "educationsecondaire",
    +    "éduc. supérieure vs. primaire" = "educationsupérieur"
    +  )
    +
    +hr$transition <- hr$transition %>%
    +  fct_inorder() %>%
    +  fct_recode(
    +    "entrée en soins" = "State 1 - State 2",
    +    "sortie de soins" = "State 2 - State 1",
    +    "initiation traitement" = "State 2 - State 3",
    +    "arrêt traitement" = "State 3 - State 2",
    +    "contrôle infection" = "State 3 - State 4",
    +    "échec virologique" = "State 4 - State 3"
    +  )
    +
    +head(hr)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    termtransitionfromtoestimateconf.lowconf.hightype
    femme vs. hommeentrée en soinsState 1State 21.88552321.70596122.083985Transition ascendante
    femme vs. hommesortie de soinsState 2State 10.92456600.76559971.116540Transition descendante
    femme vs. hommeinitiation traitementState 2State 30.90159130.80444581.010468Transition ascendante
    femme vs. hommearrêt traitementState 3State 21.09598100.88549661.356498Transition descendante
    femme vs. hommecontrôle infectionState 3State 41.39854431.23009801.590057Transition ascendante
    femme vs. hommeéchec virologiqueState 4State 30.86101440.66789181.109979Transition descendante
    +

    Vu le nombre de coefficients (un risque relatif par covariable et par transition), on va organiser le graphique en distinguant les transitions acsendantes et les transitions descendantes, d’une part, et en regroupant les risques relatifs d’une même covariable, d’autre part. Pour alléger le graphique, nous allons également retirer la covariable timeperiod créé par l’argument pci, en ayant recours à str_detect de l’extension stringr pour repérer les lignes en questions (voir le chapitre sur la manipulation de texte).

    +
    ggplot(data = hr %>% filter(!str_detect(term, "time"))) +
    +  aes(
    +    x = fct_rev(term), y = estimate, color = fct_rev(transition),
    +    ymin = conf.low, ymax = conf.high 
    +  ) +
    +  geom_hline(yintercept = 1, color = "gray25", linetype = "dotted") +
    +  geom_errorbar(position = position_dodge(0.5), width = 0) +
    +  geom_point(position = position_dodge(0.5)) + 
    +  scale_y_log10() + 
    +  facet_grid(~ type) +
    +  coord_flip() +
    +  theme_classic() +
    +  theme(legend.position = "bottom") +
    +  ylab("risque relatif") + xlab("") +
    +  labs(color = "Transition") +
    +  scale_color_brewer(palette = "Paired") +
    +  guides(color = guide_legend(reverse = TRUE))
    +

    +

    Ce modèle de survie multi-états permet de mettre en évidence des effets différenciés selon la transition considérée. Par exemple, les femmes sont plus rapides en matière d’entrée en soins et de contrôle de l’infection (une fois le traitement initié) mais plus lentes à démarrer le traitement (une fois entrées en soins). Par contre, le sexe ne semble pas jouer sur les transitions descendantes (échec virologique, arrête du traitement ou sortie de soins).

    +
    +
    +
    +
      +
    1. Pour plus de détails sur les tableaux croisés et le test du Chi², voir le chapitre sur la statisque bivariée et le chapitre sur les tests de comparaison.

    2. +
    3. À noter, ggbivariate accepte également des variables continues.

    4. +
    5. Comme par exemple msSurv pour une estimation non-paramétrique.

    6. +
    7. avant l’éventuelle intégration diretement dans msm d’un tidier officiel.

    8. +
    +
    + +
    +
    + + + +

    Analyse de réseaux

    + +
    + + +

    Un bon tutoriel pour s’initier à la visualisation des réseaux avec R, Network visualization with R de Katherine Ognyanova, est disponible en ligne : http://kateto.net/network-visualization.

    +

    On pourra poursuivre avec différents billets de blog publiés par François Briatte sur https://politbistro.hypotheses.org/. Enfin, François Briatte a également recensé de nombreuses ressources en ligne sur l’analyse de réseau dans son An awesome list of network analysis resources (http://f.briatte.org/r/awesome-network-analysis-list).

    + +
    +
    + + + +

    Analyse spatiale

    + +
    + + +

    Il est tout à fait possible de réaliser des analyses spatiales sous R. Historiquement, l’extension principale pour la gestion des objets spatiaux sous R est l’extension sp. Depuis quelques années, une nouvelle extension sf s’est développée. Alors, faut-il plutôt apprendre sp ou sf ? Chris Brown tente de répondre à cette question dans son billet Should I learn sf or sp for spatial R programming?. Ces deux extensions ont leurs avantages et inconvénients. Du fait que sp est plus ancienne, elle est compatible avec plus d’autres extensions. De l’autre côté, sf peut s’avérer plus simple pour le néophyte. Dans tous les cas, l’extension raster sera un bon complément pour gérer les données de type raster.

    +

    Pour une présentation détaillée (en anglais) de l’analyse spatiale sous R, on pourra se référer à l’ouvrage Geocomputation with R de Robin Lovelace, Jakub Nowosad et Jannes Muenchow, consultable en ligne (https://geocompr.robinlovelace.net/). Cette ouvrage privilégie plutôt l’extension sf.

    +

    On pourra également se référer aux différentes vignettes inclues par leurs extensions et consultables en ligne sur :

    + +

    Pour la réalisation de cartes avec R, on pourra se référer au chapitre dédié.

    +

    Enfin, le site Awesome R fournit une sélection d’extensions dédiées à l’analyse spatiale.

    + +
    +
    + + + +

    ggplot2 et la grammaire des graphiques

    + +
    + + + +
    +

    Ce chapitre est en cours d’écriture.

    +
    +

    On pourra se référer au chapitre 5 Graphiques du support de cours d’Ewen Gallic intitulé Logiciel R et programmation (http://egallic.fr/Enseignement/R/m1_stat_eco_logiciel_R.pdf), en complément des deux chapitres introductifs d’analyse-R : introduction à ggplot2 et graphiques bivariés avec ggplot2.

    +
    +

    Grammaire des graphiques de ggplot2

    +
    +
    +

    Ouvrages

    +
      +
    • +ggplot2: Elegant Graphics for Data Analysis (Use R!) d’Hadley Wickham 1 +
    • +
    • +R Graphics Cookbook de Winston Chang
    • +
    +
    +
    +

    Assistants pour ggplot2

    +

    Plusieurs extensions propose une assistance visuelle pour l’utilisation de ggplot2 via des add-ins dans RStudio :

    +
      +
    • +esquisse pour explorer visuellement des données et produire des graphiques de base avec ggplot2 (plus d’informations sur https://github.com/dreamRs/esquisse) ;
    • +
    • +ggplotAssist pour générer des graphiques ggplot2 étape par étape (plus d’informations sur https://github.com/cardiomoon/ggplotAssist/) ;
    • +
    • +ggThemeAssist pour éditer le thème d’un graphique déjà réalisé.
    • +
    +
    +
    +
    +
      +
    1. Une version PDF est disponible sur http://moderngraphics11.pbworks.com/f/ggplot2-Book09hWickham.pdf.

    2. +
    +
    + +
    +
    + + + +

    Étendre ggplot2

    + +
    + + + +
    +

    Ce chapitre est évoqué dans le webin-R #13 (exemples de graphiques avancés) sur YouTube.

    +

    Ce chapitre est évoqué dans le webin-R #14 (exemples de graphiques avancés - 2) sur YouTube.

    +
    +

    De nombreuses extensions permettent d’étendre les possibilités graphiques de ggplot2. Certaines ont déjà été abordées dans les différents chapitres d’analyse-R. Le présent chapitre ne se veut pas exhaustif et ne présente qu’une sélection choisie d’extensions.

    +

    Le site ggplot2 extensions (https://exts.ggplot2.tidyverse.org/gallery/) recense diverses extensions pour ggplot2.

    +

    Pour une présentation des fonctions de base et des concepts de ggplot2, on pourra se référer au chapitre dédié ainsi qu’au deux chapitres introductifs : introduction à ggplot2 et graphiques bivariés avec ggplot2.

    +

    Pour trouver l’inspiration et des exemples de code, rien ne vaut l’excellent site https://www.r-graph-gallery.com/.

    +
    +

    Nouvelles géométries

    +
    +

    Étiquettes non superposées

    +

    Lorsque l’on affiche des étiquettes de texte, ces dernières peuvent se supperposer lorsqu’elles sont proches. Les géométries geom_text_repel et geom_label_repel de l’extension ggrepel prennent en compte la position des différentes étiquettes pour éviter qu’elles ne se chevauchent.

    +
    library(ggplot2)
    +library(ggrepel)
    +library(ggrepel)
    +
    +dat <- subset(mtcars, wt > 2.75 & wt < 3.45)
    +dat$car <- rownames(dat)
    +p <- ggplot(dat) +
    +  aes(wt, mpg, label = car) +
    +  geom_point(color = "red")
    +
    +p1 <- p + geom_text() +
    +  labs(title = "geom_text()")
    +p2 <- p + geom_text_repel() +
    +  labs(title = "geom_text_repel()")
    +
    +cowplot::plot_grid(p1, p2, nrow = 1)
    +

    +

    Pour plus d’informations : https://ggrepel.slowkow.com/

    +
    +
    +

    Graphiques en sucettes (lollipop)

    +

    L’extension ggalt propose une géométrie geom_lollipop permettant de réaliser des graphiques dit en sucettes.

    +
    df <- read.csv(text = "category,pct
    +Other,0.09
    +South Asian/South Asian Americans,0.12
    +Interngenerational/Generational,0.21
    +S Asian/Asian Americans,0.25
    +Muslim Observance,0.29
    +Africa/Pan Africa/African Americans,0.34
    +Gender Equity,0.34
    +Disability Advocacy,0.49
    +European/European Americans,0.52
    +Veteran,0.54
    +Pacific Islander/Pacific Islander Americans,0.59
    +Non-Traditional Students,0.61
    +Religious Equity,0.64
    +Caribbean/Caribbean Americans,0.67
    +Latino/Latina,0.69
    +Middle Eastern Heritages and Traditions,0.73
    +Trans-racial Adoptee/Parent,0.76
    +LBGTQ/Ally,0.79
    +Mixed Race,0.80
    +Jewish Heritage/Observance,0.85
    +International Students,0.87", stringsAsFactors = FALSE, sep = ",", header = TRUE)
    +
    +library(ggplot2)
    +library(ggalt)
    +library(scales)
    +
    +ggplot(df) +
    +  aes(y = reorder(category, pct), x = pct) +
    +  geom_lollipop(point.colour = "steelblue", point.size = 2, horizontal = TRUE) +
    +  scale_x_continuous(expand = c(0, 0), labels = percent, breaks = seq(0, 1, by = 0.2), limits = c(0, 1)) +
    +  labs(
    +    x = NULL, y = NULL,
    +    title = "SUNY Cortland Multicultural Alumni survey results",
    +    subtitle = "Ranked by race, ethnicity, home land and orientation\namong the top areas of concern",
    +    caption = "Data from http://stephanieevergreen.com/lollipop/"
    +  ) +
    +  theme_minimal()
    +

    +
    +
    +

    Graphique d’haltères (dumbbell)

    +

    L’extension ggalt propose une géométrie geom_dumbbell permettant de réaliser des graphiques dit en haltères.

    +
    library(ggalt)
    +df <- data.frame(
    +  trt = LETTERS[1:5],
    +  l = c(20, 40, 10, 30, 50),
    +  r = c(70, 50, 30, 60, 80)
    +)
    +
    +ggplot(df) +
    +  aes(y = trt, x = l, xend = r) +
    +  geom_dumbbell(
    +    size = 3,
    +    color = "#e3e2e1",
    +    colour_x = "#5b8124",
    +    colour_xend = "#bad744"
    +  ) +
    +  labs(x = NULL, y = NULL) +
    +  theme_minimal()
    +

    +
    +
    +

    Points reliés (pointpath)

    +

    L’extension ggh4x propose une géométrie geom_pointpath pour afficher des points reliés entre eux par un tiret. La géométrie geom_pointpath est également proposée par l’extension lemon.

    +
    library(ggh4x)
    +ggplot(pressure) +
    +  aes(x = temperature, y = pressure) +
    +  geom_pointpath()
    +

    +
    +
    +

    Accolades de comparaison (bracket)

    +

    La géométrie geom_braket de l’extension ggpubr permets d’ajouter sur un graphique des accolades de comparaison entre groupes.

    +
    library(ggpubr)
    +df <- ToothGrowth
    +df$dose <- factor(df$dose)
    +
    +ggplot(df) +
    +  aes(x = dose, y = len) +
    +  geom_boxplot() +
    +  geom_bracket(
    +    xmin = "0.5", xmax = "1", y.position = 30,
    +    label = "t-test, p < 0.05"
    +  )
    +

    +
    ggplot(df) +
    +  aes(x = dose, y = len) +
    +  geom_boxplot() +
    +  geom_bracket(
    +    xmin = c("0.5", "1"),
    +    xmax = c("1", "2"),
    +    y.position = c(30, 35),
    +    label = c("***", "**"),
    +    tip.length = 0.01
    +  )
    +

    +

    Plus d’informations : https://rpkgs.datanovia.com/ggpubr/

    +
    +
    +

    Diagramme en crêtes (ridges)

    +

    L’extension ggridges fournit une géométrie geom_density_ridges_gradient pour la création de diagramme en crêtes.

    +
    library(ggridges)
    +ggplot(lincoln_weather, aes(x = `Mean Temperature [F]`, y = Month, fill = stat(x))) +
    +  geom_density_ridges_gradient(scale = 3, rel_min_height = 0.01) +
    +  scale_fill_viridis_c(name = "Temp. [F]", option = "C") +
    +  labs(title = "Temperatures in Lincoln NE in 2016") +
    +  theme_ridges()
    +
    Picking joint bandwidth of 3.37
    +

    +

    Plus d’informations : https://wilkelab.org/ggridges/

    +
    +
    +

    Graphique en gaufres (waffle)

    +

    L’extension waffle propose geom_waffle pour des graphiques dits en gaufres.

    +

    ATTENTION : elle s’installe avec la commande install.packages("waffle", repos = "https://cinc.rud.is").

    +
    library(waffle)
    +xdf <- data.frame(
    +  parts = factor(rep(month.abb[1:3], 3), levels = month.abb[1:3]),
    +  vals = c(10, 20, 30, 6, 14, 40, 30, 20, 10),
    +  fct = c(rep("Thing 1", 3), rep("Thing 2", 3), rep("Thing 3", 3))
    +)
    +
    +ggplot(xdf) +
    +  aes(fill = parts, values = vals) +
    +  geom_waffle() +
    +  facet_wrap(~fct) +
    +  scale_fill_manual(
    +    name = NULL,
    +    values = c("#a40000", "#c68958", "#ae6056"),
    +    labels = c("Fruit", "Sammich", "Pizza")
    +  ) +
    +  coord_equal() +
    +  theme_minimal() +
    +  theme_enhance_waffle()
    +

    +

    Plus d’informations : https://github.com/hrbrmstr/waffle

    +
    +
    +

    Graphique en mosaïque (mosaic plot)

    +

    L’extension ggmosaic permets de réaliser des graphiques en mosaïque avec geom_mosaic.

    +
    library(ggmosaic)
    +data(titanic)
    +
    +ggplot(data = titanic) +
    +  geom_mosaic(aes(x = product(Class), fill = Survived))
    +

    +

    Plus d’informations : https://cran.r-project.org/web/packages/ggmosaic/vignettes/ggmosaic.html

    +
    +
    +

    Représentations graphiques des distributions et de l’incertitude

    +

    L’extension ggdist propose plusieurs géométries permettant de représenter l’incertitude autour de certaines mesures.

    +

    +
    +
    +

    Graphique de pirates : alternative aux boîtes à moustache (pirat plot)

    +

    Cette représentation alternative aux boîtes à moustache s’obtient avec la géométrie geom_pirate de l’extension ggpirate1.

    +
    library(ggplot2)
    +library(ggpirate)
    +ggplot(mpg, aes(x = class, y = cty)) +
    +  geom_pirate(aes(colour = class, fill = class)) +
    +  theme_bw()
    +

    +

    Pour plus d’informations : https://github.com/mikabr/ggpirate

    +
    +
    +

    Nuages de pluie (raincloud plots)

    +

    Il existe encore d’autres approches graphiques pour mieux rendre compte visuellement différentes distrubutions comme les raincloud plots. Pour en savoir plus, on pourra se référer à un billet de blog en anglais de Cédric Scherer ou encore à l’extension raincloudplots.

    +

    +
    +
    +

    Demi-géométries

    +

    L’extension gghalves propose des demi-géométries qui peuvent être combinées entre elles.

    +
    library(gghalves)
    +ggplot(iris, aes(x = Species, y = Sepal.Width)) +
    +  geom_half_boxplot(fill = "lightblue") +
    +  geom_half_violin(side = "r", fill = "navajowhite")
    +

    +
    +
    +

    Annotations avec des formes gémoétriques

    +

    L’extension ggforce fournie plusieurs géométries permettant d’annoter les points d’un nuage : geom_mark_circle, geom_mark_ellipse, geom_mark_rect et geom_mark_hull.

    +
    library(ggforce)
    +ggplot(iris, aes(Petal.Length, Petal.Width)) +
    +  geom_mark_rect(aes(fill = Species, label = Species)) +
    +  geom_point()
    +

    +
    ggplot(iris, aes(Petal.Length, Petal.Width)) +
    +  geom_mark_hull(aes(fill = Species, label = Species)) +
    +  geom_point()
    +

    +
    +
    +
    +

    Axes, légende et facettes

    +
    +

    Texte mis en forme

    +

    L’extension ggtext permet d’utiliser du markdown et du HTML pour une mise en forme avancée de texte (axes, titres, légendes…).

    +
    library(ggtext)
    +
    +library(tidyverse)
    +library(ggtext)
    +library(glue)
    +
    +data <- tibble(
    +  bactname = c("Staphylococcaceae", "Moraxella", "Streptococcus", "Acinetobacter"),
    +  OTUname = c("OTU 1", "OTU 2", "OTU 3", "OTU 4"),
    +  value = c(-0.5, 0.5, 2, 3)
    +)
    +
    +data %>%
    +  mutate(
    +    color = c("#009E73", "#D55E00", "#0072B2", "#000000"),
    +    name = glue("<i style='color:{color}'>{bactname}</i> ({OTUname})"),
    +    name = fct_reorder(name, value)
    +  ) %>%
    +  ggplot(aes(value, name, fill = color)) +
    +  geom_col(alpha = 0.5) +
    +  scale_fill_identity() +
    +  labs(caption = "Example posted on **stackoverflow.com**<br>(using made-up data)") +
    +  theme(
    +    axis.text.y = element_markdown(),
    +    plot.caption = element_markdown(lineheight = 1.2)
    +  )
    +

    +
    ggplot(mtcars, aes(disp, mpg)) +
    +  geom_point() +
    +  labs(
    +    title = "<b>Fuel economy vs. engine displacement</b><br>
    +    <span style = 'font-size:10pt'>Lorem ipsum *dolor sit amet,*
    +    consectetur adipiscing elit, **sed do eiusmod tempor incididunt** ut
    +    labore et dolore magna aliqua. <span style = 'color:red;'>Ut enim
    +    ad minim veniam,</span> quis nostrud exercitation ullamco laboris nisi
    +    ut aliquip ex ea commodo consequat.</span>",
    +    x = "displacement (in<sup>3</sup>)",
    +    y = "Miles per gallon (mpg)<br><span style = 'font-size:8pt'>A measure of
    +    the car's fuel efficiency.</span>"
    +  ) +
    +  theme(
    +    plot.title.position = "plot",
    +    plot.title = element_textbox_simple(
    +      size = 13,
    +      lineheight = 1,
    +      padding = margin(5.5, 5.5, 5.5, 5.5),
    +      margin = margin(0, 0, 5.5, 0),
    +      fill = "cornsilk"
    +    ),
    +    axis.title.x = element_textbox_simple(
    +      width = NULL,
    +      padding = margin(4, 4, 4, 4),
    +      margin = margin(4, 0, 0, 0),
    +      linetype = 1,
    +      r = grid::unit(8, "pt"),
    +      fill = "azure1"
    +    ),
    +    axis.title.y = element_textbox_simple(
    +      hjust = 0,
    +      orientation = "left-rotated",
    +      minwidth = unit(1, "in"),
    +      maxwidth = unit(2, "in"),
    +      padding = margin(4, 4, 2, 4),
    +      margin = margin(0, 0, 2, 0),
    +      fill = "lightsteelblue1"
    +    )
    +  )
    +

    +
    +
    +

    Axes limités +

    +

    coord_capped_cart et coord_capped_flip de l’extension lemon permet de limiter le dessin des axes au minimum et au maximum. Voir l’exemple ci-dessous.

    +
    library(ggplot2)
    +library(lemon)
    +p <- ggplot(mtcars) +
    +  aes(x = cyl, y = mpg) +
    +  geom_point() +
    +  theme_classic() +
    +  ggtitle("Axes classiques")
    +pcapped <- p +
    +  coord_capped_cart(bottom = "both", left = "both") +
    +  ggtitle("Axes limités")
    +cowplot::plot_grid(p, pcapped, nrow = 1)
    +

    +

    Une autre possibilité est d’avoir recours à la fonction guide_axis_truncated de l’extension ggh4x.

    +
    library(ggh4x)
    +ggplot(mtcars) +
    +  aes(x = cyl, y = mpg) +
    +  geom_point() +
    +  theme_classic() +
    +  scale_y_continuous(breaks = c(15, 20, 25, 30)) +
    +  guides(
    +    x = guide_axis_truncated(trunc_lower = 5, trunc_upper = 7),
    +    y = guide_axis_truncated()
    +  )
    +

    +
    +
    +

    Répéter les étiquettes des axes sur des facettes

    +

    Lorsque l’on réalise des facettes, les étiquettes des axes ne sont pas répétées.

    +
    library(ggplot2)
    +ggplot(mpg) +
    +  aes(displ, cty) +
    +  geom_point() +
    +  facet_wrap(~cyl)
    +

    +

    L’extension lemon propose facet_rep_grid et facet_rep_wrap qui répètent les axes sur chaque facette.

    +
    library(lemon)
    +ggplot(mpg) +
    +  aes(displ, cty) +
    +  geom_point() +
    +  facet_rep_wrap(~cyl, repeat.tick.labels = TRUE)
    +

    +
    +
    +

    Encoches mineures sur les axes

    +

    Par défaut, des encoches (ticks) sont dessinées sur les axes uniquement pour la grille principale (major breaks). La fonction guide_axis_minor de l’extension ggh4x permet de rajouter des encoches aux points de la grille mineure (minor breaks).

    +
    p <- ggplot(mtcars) +
    +  aes(x = cyl, y = mpg) +
    +  geom_point() +
    +  theme_classic()
    +p
    +

    +
    library(ggh4x)
    +p +
    +  scale_x_continuous(
    +    minor_breaks = 16:32 / 4,
    +    guide = guide_axis_minor()
    +  ) +
    +  # to control relative length of minor ticks
    +  theme(ggh4x.axis.ticks.length.minor = rel(.8))
    +

    +
    +
    +

    Relations imbriquées

    +

    La fonction guide_axis_nested de l’extension ggh4x permet d’afficher sur un axe des relations imbriquées.

    +
    library(ggh4x)
    +df <- data.frame(
    +  item = c("Coffee", "Tea", "Apple", "Pear", "Car"),
    +  type = c("Drink", "Drink", "Fruit", "Fruit", ""),
    +  amount = c(5, 1, 2, 3, 1),
    +  stringsAsFactors = FALSE
    +)
    +
    +ggplot(df) +
    +  aes(x = interaction(item, type), y = amount) +
    +  geom_col() +
    +  guides(x = guide_axis_nested())
    +

    +
    +
    +

    Combinaison de variables sur l’axe des X

    +

    L’extension ggupset fournie une fonction scale_x_upset permettant de représenter des combinaisons de variables sur l’axe des x, combinaisons de variables stockées sous forme d’une colonne de type liste. Pour plus d’informations, voir https://github.com/const-ae/ggupset.

    +
    library(ggupset)
    +library(tidyverse, quietly = TRUE)
    +tidy_movies %>%
    +  distinct(title, year, length, .keep_all = TRUE) %>%
    +  ggplot(aes(x = Genres)) +
    +  geom_bar() +
    +  scale_x_upset(n_intersections = 20)
    +
    Warning: Removed 100 rows containing non-finite values
    +(stat_count).
    +

    ### Zoom sur un axe

    +

    L’extension ggforce propose une fonction facet_zoom permettant de zoomer une partie d’un axe.

    +
    library(ggforce)
    +ggplot(iris, aes(Petal.Length, Petal.Width, colour = Species)) +
    +  geom_point() +
    +  facet_zoom(x = Species == "versicolor")
    +

    +
    +
    +

    Des facettes paginées (diviser en plusieurs sous-graphiques)

    +

    Les fonctions facet_wrap_paginate et facet_grid_paginate de ggforce permet de découper facilement un graphique en facettes en plusieurs pages.

    +
    ggplot(diamonds) +
    +  geom_point(aes(carat, price), alpha = 0.1) +
    +  facet_wrap_paginate(~ cut:clarity, ncol = 3, nrow = 3, page = 1)
    +

    +
    ggplot(diamonds) +
    +  geom_point(aes(carat, price), alpha = 0.1) +
    +  facet_wrap_paginate(~ cut:clarity, ncol = 3, nrow = 3, page = 2)
    +

    +
    ggplot(diamonds) +
    +  geom_point(aes(carat, price), alpha = 0.1) +
    +  facet_wrap_paginate(~ cut:clarity, ncol = 3, nrow = 3, page = 3)
    +

    +
    +
    +
    +

    Cartes

    +

    Voir le chapitre dédié.

    +
    +
    +

    Graphiques complexes

    +
    +

    Graphiques divergents

    +

    L’extension ggcharts fournit plusieurs fonctions de haut niveau pour faciliter la réalisation de graphiques divergents en barres (diverging_bar_chart), en sucettes (diverging_lollipop_chart) voire même une pyramide des âges (pyramid_chart).

    +
    library(ggcharts)
    +data(mtcars)
    +mtcars_z <- dplyr::transmute(
    +  .data = mtcars,
    +  model = row.names(mtcars),
    +  hpz = scale(hp)
    +)
    +
    +diverging_bar_chart(data = mtcars_z, x = model, y = hpz)
    +

    +
    diverging_lollipop_chart(
    +  data = mtcars_z,
    +  x = model,
    +  y = hpz,
    +  lollipop_colors = c("#006400", "#b32134"),
    +  text_color = c("#006400", "#b32134")
    +)
    +

    +
    +
    +

    Pyramides des âges

    +

    Plussieurs solutions sont disponible pour réaliser une pyramide des âges.

    +

    Tout d’abord, ggcharts propose une fonction pyramid_chart.

    +
    library(ggcharts)
    +data("popch")
    +pyramid_chart(data = popch, x = age, y = pop, group = sex)
    +
    Warning: `expand_scale()` is deprecated; use `expansion()`
    +instead.
    +
    +Warning: `expand_scale()` is deprecated; use `expansion()`
    +instead.
    +
    +Warning: `expand_scale()` is deprecated; use `expansion()`
    +instead.
    +
    +Warning: `expand_scale()` is deprecated; use `expansion()`
    +instead.
    +

    +

    L’extension apyramid dédiée aux pyramides des âges fournit une fonction age_pyramid.

    +
    library(apyramid)
    +data(us_2018)
    +age_pyramid(us_2018, age_group = age, split_by = gender, count = count)
    +

    +

    Pour aller plus loin, notamment avec ggplot2, on pourra se référer (en anglais), au chapitre dédié du Epidemiologist R Handbook, par exemple pour savoir comme réaliser deux pyramides superposées :

    +

    +
    +
    +

    Graphiques interactifs

    +

    Voir le chapitre dédie.

    +
    +
    +

    Graphiques animés

    +

    L’extension gganimate permets de réaliser des graphiques animés.

    +

    Voici un exemple :

    +
    library(ggplot2)
    +library(gganimate)
    +library(gapminder)
    +
    +ggplot(gapminder, aes(gdpPercap, lifeExp, size = pop, colour = country)) +
    +  geom_point(alpha = 0.7, show.legend = FALSE) +
    +  scale_colour_manual(values = country_colors) +
    +  scale_size(range = c(2, 12)) +
    +  scale_x_log10() +
    +  facet_wrap(~continent) +
    +  # Here comes the gganimate specific bits
    +  labs(title = "Year: {frame_time}", x = "GDP per capita", y = "life expectancy") +
    +  transition_time(year) +
    +  ease_aes("linear")
    +

    +

    Voir le site de l’extension (https://gganimate.com/) pour la documentation et des tutoriels. Il est conseillé d’installer également l’extension gifski avec gganimate.

    +
    +
    +

    Surligner certaines données

    +

    L’extension gghighlight fournit une fonction gghiglight qui permets de surligner les données qui remplissent des conditions spécifiées.

    +
    d <- purrr::map_dfr(
    +  letters,
    +  ~ data.frame(
    +    idx = 1:400,
    +    value = cumsum(runif(400, -1, 1)),
    +    type = .,
    +    flag = sample(c(TRUE, FALSE), size = 400, replace = TRUE),
    +    stringsAsFactors = FALSE
    +  )
    +)
    +
    +ggplot(d) +
    +  aes(x = idx, y = value, colour = type) +
    +  geom_line()
    +

    +
    library(gghighlight)
    +ggplot(d) +
    +  aes(x = idx, y = value, colour = type) +
    +  geom_line() +
    +  gghighlight(max(value) > 20)
    +
    label_key: type
    +

    +
    ggplot(iris, aes(Sepal.Length, fill = Species)) +
    +  geom_histogram() +
    +  gghighlight() +
    +  facet_wrap(~Species)
    +

    +
    +
    +
    +

    Thèmes et couleurs

    +
    +

    Palettes de couleurs

    +

    Voir le chapitre Couleurs et palettes pour une sélection d’extensions proposant des palettes de couleurs additionnelles.

    +
    +
    +

    hrbrthemes

    +

    L’extension hrbrthemes fournit plusieurs thèmes graphiques pour ggplot2. Un exemple ci-dessous. Pour plus d’informations, voir https://github.com/hrbrmstr/hrbrthemes.

    +
    library(ggplot2)
    +library(hrbrthemes)
    +ggplot(mtcars, aes(mpg, wt)) +
    +  geom_point(aes(color = factor(carb))) +
    +  labs(
    +    x = "Fuel efficiency (mpg)", y = "Weight (tons)",
    +    title = "Seminal ggplot2 scatterplot example",
    +    subtitle = "A plot that is only useful for demonstration purposes",
    +    caption = "Brought to you by the letter 'g'"
    +  ) +
    +  scale_color_ipsum() +
    +  theme_ipsum_rc()
    +

    +
    +
    +

    ggthemes

    +

    ggthemes propose une vingtaine de thèmes différentes présentés sur le site de l’extension : https://jrnold.github.io/ggthemes/.

    +

    Voir ci-dessous un exemple du thème theme_tufte inspiré d’Edward Tufte.

    +
    library(ggplot2)
    +library(ggthemes)
    +
    +p <- ggplot(mtcars, aes(x = wt, y = mpg)) +
    +  geom_point() +
    +  scale_x_continuous(breaks = extended_range_breaks()(mtcars$wt)) +
    +  scale_y_continuous(breaks = extended_range_breaks()(mtcars$mpg)) +
    +  ggtitle("Cars")
    +
    +p + geom_rangeframe() +
    +  theme_tufte()
    +

    +
    p + geom_rug() +
    +  theme_tufte(ticks = FALSE)
    +

    +
    +
    +
    +

    Combiner plusieurs graphiques

    +

    Voir le chapitre dédié.

    +
    +
    +
    +
      +
    1. Cette extension n’étant pas sur CRAN, on l’installera avec la commande devtools::install_github("mikabr/ggpirate").

    2. +
    +
    + +
    +
    + + + +

    Combiner plusieurs graphiques

    + +
    + + + +
    +

    Ce chapitre est évoqué dans le webin-R #14 (exemples de graphiques avancés - 2) sur YouTube.

    +
    +

    Vous savez réaliser des graphiques avec ggplot2 ? Il est très facile de combiner plusieurs graphiques en un seul.

    +

    Commençons par créer quelques graphiques avec ggplot2.

    +
    library(ggplot2)
    +p1 <- ggplot(mtcars, aes(wt, mpg)) +
    +  geom_point()
    +p2 <- ggplot(mtcars, aes(factor(cyl))) +
    +  geom_bar()
    +p3 <- ggplot(mtcars, aes(factor(cyl), mpg)) +
    +  geom_violin()
    +p4 <- ggplot(mtcars, aes(factor(cyl), mpg)) +
    +  geom_boxplot()
    +
    +

    plot_grid (cowplot)

    +

    L’extension cowplot propose une fonction plot_grid. Son usage est expliqué en détail dans les vignettes dédiées inclues avec l’extension : https://wilkelab.org/cowplot/.

    +
    library(cowplot)
    +# simple grid
    +plot_grid(p1, p2, p3, p4)
    +

    +
    # simple grid with labels and aligned plots
    +plot_grid(p1, p2, p3, p4, labels = c("A", "B", "C", "D"), align = "hv")
    +

    +
    # manually setting the number of rows, auto-generate upper-case labels
    +plot_grid(p1, p2, p3, nrow = 3, labels = "AUTO", label_size = 12, align = "v")
    +

    +
    # making rows and columns of different widths/heights
    +plot_grid(p1, p2, p3, p4, align = "hv", rel_heights = c(2, 1), rel_widths = c(1, 2))
    +

    +

    On notera en passant que le chargement de cowplot modifie le style par défaut des graphiques ggplot2. Voir https://cran.r-project.org/web/packages/cowplot/vignettes/introduction.html.

    +
    +
    +

    patchwork

    +

    Citons également l’extension patchwork, disponible sur GitHub (https://github.com/thomasp85/patchwork) qui propose une syntaxe un petit peu différente, par addition de graphiques.

    +
    library(patchwork)
    +p1 + (p2 + p3) + p4 + plot_layout(ncol = 1)
    +

    +
    +
    +

    multiplot (JLutils)

    +

    Dans son ouvrage Cookbook for R, Winston Chang propose une fonction multiplot pour combiner plusieurs graphiques1

    +

    L’extension JLutils disponible sur GitHub propose une version améliorée de cette fonction.

    +

    Pour installer JLutils si non disponible sur votre PC, copier/coller le code ci-dessous.

    +
    if (!require(devtools)) {
    +  install.packages("devtools")
    +  library(devtools)
    +}
    +install_github("larmarange/JLutils")
    +

    Parce que quelques exemples valent mieux qu’un long discours.

    +
    library(JLutils)
    +multiplot(p1, p2, p3, p4)
    +

    +
    multiplot(p1, p2, p3, p4, cols = 2)
    +

    +
    multiplot(p1, p2, p3, layout = matrix(c(1, 2, 3, 3), nrow = 2))
    +

    +
    multiplot(p1, p2, p3, layout = matrix(c(1, 2, 3, 3), nrow = 2, byrow = TRUE))
    +

    +
    multiplot(p1, p2, p3, layout = matrix(c(1, 2, 3, 3), nrow = 2, byrow = TRUE), heights = c(3, 1))
    +

    +
    +
    +

    Légende partagée entre plusieurs graphiques

    +

    JLutils et cowplot fournissent tous deux une fonction get_legend permettant d’extraire la légende d’un graphique puis de l’utiliser avec multiplot ou plot_grid.

    +

    Créons quelques graphiques.

    +
    dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
    +p1 <- qplot(carat, price, data = dsamp, colour = clarity) +
    +  theme(plot.margin = unit(c(6, 0, 6, 0), "pt"))
    +p2 <- qplot(depth, price, data = dsamp, colour = clarity) +
    +  theme(plot.margin = unit(c(6, 0, 6, 0), "pt")) + ylab("")
    +p3 <- qplot(color, price, data = dsamp, colour = clarity) +
    +  theme(plot.margin = unit(c(6, 0, 6, 0), "pt")) + ylab("")
    +

    Récupérons la légende du premier graphique graphique puis supprimons là dans les trois graphiques.

    +
    leg <- get_legend(p1)
    +p1 <- p1 + theme(legend.position = "none")
    +p2 <- p2 + theme(legend.position = "none")
    +p3 <- p3 + theme(legend.position = "none")
    +

    Combinons le tout.

    +
    multiplot(p1, p2, p3, leg, cols = 2)
    +

    +
    plot_grid(p1, p2, p3, leg, ncol = 2)
    +

    +

    Enfin, citons également la fonction grid_arrange_shared_legend de l’extension lemon2.

    +
    +
    +
    +
      +
    1. Voir http://www.cookbook-r.com/Graphs/Multiple_graphs_on_one_page_(ggplot2)/.

    2. +
    3. lemon fournit également diverses fonctions pour manipuler des graphiques ggplot2, comme par exemple la possibilité de répéter les axes quand on utilise des facettes.

    4. +
    +
    + +
    +
    + + + +

    Exemples de graphiques avancés

    + +
    + + + +
    +

    Ce chapitre est évoqué dans le webin-R #13 (exemples de graphiques avancés) sur YouTube.

    +

    Ce chapitre est évoqué dans le webin-R #14 (exemples de graphiques avancés - 2) sur YouTube.

    +
    +

    Dans ce chapitre, nous présentons plusieurs graphiques avec une mise en forme avancée et détaillons pas à pas leur création.

    +

    Chargeons quelques extensions de base.

    +
    library(tidyverse)
    +
    +

    Questions de connaissance

    +

    Pour ce premier exemple, supposons que nous avons réalisé une petite enquête auprès de 500 étudiants pour mesurer leur connaissance du logiciel R. Commençons par charger les données ici purement fictionnelles).

    +
    load(url("https://larmarange.github.io/analyse-R/data/connaissances.RData"))
    +

    Nous avons maintenant un objet quest en mémoire. Regardons rapidement la forme des données.

    +
    glimpse(quest)
    +
    Rows: 500
    +Columns: 15
    +$ id       <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13~
    +$ conn_a   <fct> non, non, non, non, NA, non, non, non, no~
    +$ conn_b   <fct> oui, oui, oui, oui, oui, oui, oui, oui, o~
    +$ conn_c   <fct> oui, oui, oui, oui, oui, oui, oui, oui, o~
    +$ conn_d   <fct> non, non, non, NA, NA, NA, NA, NA, non, o~
    +$ conn_e   <fct> oui, oui, oui, oui, NA, oui, oui, oui, NA~
    +$ conn_f   <fct> oui, non, oui, NA, NA, oui, oui, oui, oui~
    +$ conn_g   <fct> non, oui, oui, oui, oui, oui, oui, oui, o~
    +$ source_a <fct> oui, oui, oui, oui, oui, oui, oui, oui, n~
    +$ source_b <fct> oui, non, non, non, non, oui, non, oui, n~
    +$ source_c <fct> oui, non, oui, oui, non, non, non, non, n~
    +$ source_d <fct> oui, non, non, oui, non, oui, oui, oui, o~
    +$ source_e <fct> oui, non, non, oui, oui, non, oui, non, o~
    +$ source_f <fct> non, non, non, non, non, non, non, non, n~
    +$ source_g <fct> non, non, non, non, non, non, non, non, n~
    +
    summary(quest$conn_a)
    +
     oui  non  NSP NA's 
    +  36  442    1   21 
    +

    Sept affirmations ont été soumises aux étudiants (variables conn_a à conn_g) et il leur a été demandé, pour chacune, s’il pensait qu’elle était juste. Les réponses possibles étaient “oui”, “non” et “NSP” (ne sait pas).

    +
    library(questionr)
    +freq.na(quest)
    +
             missing  %
    +conn_d       104 21
    +conn_a        21  4
    +conn_e        17  3
    +conn_g        12  2
    +conn_f        10  2
    +id             0  0
    +conn_b         0  0
    +conn_c         0  0
    +source_a       0  0
    +source_b       0  0
    +source_c       0  0
    +source_d       0  0
    +source_e       0  0
    +source_f       0  0
    +source_g       0  0
    +

    On peut également noter que pour certaines questions il y a plusieurs valeurs manquantes (jusqu’à 104).

    +

    Nous souhaiterions représenter les réponses sous la forme d’un graphique en barres cumulées. Cependant, un tel graphique ne peut pour le moment être réalisé car les réponses sont stockées dans 7 variables différentes. Pour réaliser le graphique, il nous faut un tableau de données avec une colonne qui contiendrait le nom de la question et une colonne avec les réponses. Cela peut se faire facilement avec la fonction pivot_longer de l’extension tidyr (voir le chapitre dédié).

    +
    conn <- quest %>%
    +  select(starts_with("conn_")) %>%
    +  pivot_longer(
    +    cols = starts_with("conn_"),
    +    names_to = "question",
    +    values_to = "reponse"
    +  )
    +glimpse(conn)
    +
    Rows: 3,500
    +Columns: 2
    +$ question <chr> "conn_a", "conn_b", "conn_c", "conn_d", "~
    +$ reponse  <fct> non, oui, oui, non, oui, oui, non, non, o~

    Nous pouvons maintenant réaliser une première ébauche avec geom_bar et position = "fill".

    ggplot(conn) +
       aes(x = question, fill = reponse) +
    @@ -37144,3770 +70017,5429 @@ 

    Code final du graphique

    -
    -

    Évolutions temporelles

    -

    Pour cet exemple, nous allons utiliser des données fictives. Dans les années 1950, au sortir de la guerre, la Syldavie et la Bordurie (deux pays fictifs inventés par Hergé) ont décidé de mettre un programme de distribution de bandes dessinées de Tintin dans des écoles et des collèges afin de motiver les élèves à lire plus. Nous disposons des rapports d’activités des différents agents du programme qui indident, par mois et par type d’établissement, le nombre d’élèves rencontrés et le nombre de bandes dessinées distribués.

    -

    Commençons par importer les données, qui sont au format Excel. Nous utiliserons donc la fonction read_excel de l’extension readxl. Profitons-en pour charger également en mémoire le tidyverse ainsi que l’extension lubridate qui nous servira pour la gestion des dates.

    -
    library(readxl)
    -library(tidyverse)
    -library(lubridate)
    -url <- "https://larmarange.github.io/analyse-R/data/bandes_dessinees_tintin.xlsx"
    -destfile <- "bandes_dessinees_tintin.xlsx"
    -curl::curl_download(url, destfile)
    -bd <- read_excel(destfile)
    -
    glimpse(bd)
    -
    Rows: 4,331
    -Columns: 6
    -$ annee              <dbl> 1950, 1950, 1951, 1951, 1951, 1~
    -$ mois               <dbl> 9, 10, 4, 2, 4, 5, 5, 3, 2, 5, ~
    -$ pays               <chr> "Bordurie", "Bordurie", "Bordur~
    -$ type_etablissement <chr> "école primaire", "école primai~
    -$ eleves_rencontres  <dbl> 2, 3, 6, 16, 1, 5, 6, 3, 5, 3, ~
    -$ bd_distribuees     <dbl> 3, 3, 6, 16, 1, 5, 12, 4, 6, 5,~
    -
    -

    Bandes dessinées distribuées par mois

    -

    En premier lieu, nous souhaiterions représenter le nombre de bandes dessinées distribuées par mois. Comme on peut le voir, nous n’avons pas directement une variable date, l’année et le mois étant indiqués dans deux variables différentes. Nous allons donc commencer par créer une telle variable, qui correspondra au premier jour du mois.

    -

    Nous allons utiliser la fonction paste0 qui permets de concaténer du texte pour créer des chaines de texte au format ANNEE-MOIS-JOUR puis transformer cela en objet Date avec ymd de l’extension lubridate.

    -
    bd$date <- paste0(bd$annee, "-", bd$mois, "-01") %>% ymd()
    -summary(bd$date)
    -
            Min.      1st Qu.       Median         Mean 
    -"1949-12-01" "1950-07-01" "1950-12-01" "1950-11-13" 
    -     3rd Qu.         Max. 
    -"1951-04-01" "1951-07-01" 
    -

    Nous pouvons maintenant commencer à réaliser notre graphique. Nous allons compter le nombre de BD distribuées par mois et représenter cela sous forme d’un graphique en barres. geom_bar utilise la statistique stat_count pour compter le nombre d’observations. Cependant, le nombre de BD distribuées varie d’une ligne du fichier à l’autre. Nous allons donc utiliser l’esthétique weight pour pondérer le calcul et obtenir le total de bandes dessinées distribuées. Nous allons également utiliser l’esthétique fill pour distinguer les écoles primaires et les collèges.

    -
    p <- ggplot(bd) +
    -  aes(x = date, weight = bd_distribuees, fill = type_etablissement) +
    -  geom_bar()
    -p
    -

    -

    Avec facet_grid nous pouvons comparer la Syldavie et la Bordurie.

    -
    p + facet_grid(cols = vars(pays))
    -

    -

    Essayons de faire un peu mieux en répétant l’axe des y sur le graphique de droite, ce qui peut être fait avec la fonction facet_rep_grid de l’extension lemon.

    -
    library(lemon, quietly = TRUE)
    -p + facet_rep_grid(cols = vars(pays))
    -

    -

    Cela a répété les encoches (ticks). Pour répéter également les étiquettes, il nous faut ajouter l’option repeat.tick.labels = TRUE.

    -
    p <- p + facet_rep_grid(cols = vars(pays), repeat.tick.labels = TRUE)
    -p
    -

    -

    Allégeons un peu le graphique (voir exemple précédent). Pour cela, nous allons utiliser le thème theme_clean fourni par ggthemes.

    -
    library(ggthemes)
    -p <- p +
    -  xlab("") + ylab("") + labs(fill = "") +
    -  ggtitle("Bandes dessinées distribuées par mois") +
    -  theme_clean() +
    -  theme(
    -    legend.position = "bottom"
    -  )
    -p
    -

    -

    De même, nous allons utiliser une palette de couleurs adaptées aux personnes daltoniennes, en s’appuyant sur scale_fill_bright disponible avec khroma.

    -
    library(khroma)
    -p <- p + scale_fill_bright()
    -p
    -

    -

    Reste à améliorer la présentation de l’axe des x avec scale_x_date{data-pkg=“ggplot2}. L’option date_labels permet de personnaliser l’affichage des dates via des raccourcis décrits dans l’aide de la fonction strptime. Ainsi, %b correspond au mois écrit textuellement de manière abrégée et %Y à l’année sur 4 chiffres. \n est utilisé pour indiquer un retour à la ligne. L’option date_breaks permet d’indiquer de manière facilitée la durée attendue entre deux étiquettes, ici trois mois.

    -
    p <- p + scale_x_date(
    -  date_labels = "%b\n%Y",
    -  date_breaks = "3 months"
    -)
    -p
    -

    -

    Pour améliorer le graphique, nous aimerions rajouter une encoche sous chaque mois. Ceci est rendu possible avec la fonction guide_axis_minor de ggh4x.

    -
    library(ggh4x)
    -p <- p + scale_x_date(
    -  date_labels = "%b\n%Y",
    -  date_breaks = "3 months",
    -  date_minor_breaks = "1 month",
    -  guide = guide_axis_minor()
    -)
    -
    Scale for 'x' is already present. Adding another scale
    -for 'x', which will replace the existing scale.
    -
    p
    -

    -

    Le code complet de ce graphique est donc :

    -
    library(ggthemes)
    -library(khroma)
    -library(ggh4x)
    -library(lemon)
    -ggplot(bd) +
    -  aes(x = date, weight = bd_distribuees, fill = type_etablissement) +
    -  geom_bar() +
    -  facet_rep_grid(cols = vars(pays), repeat.tick.labels = TRUE) +
    -  xlab("") +
    -  ylab("") +
    -  labs(fill = "") +
    -  ggtitle("Bandes dessinées distribuées par mois") +
    -  scale_x_date(
    -    date_labels = "%b\n%Y",
    -    date_breaks = "3 months",
    -    date_minor_breaks = "1 month",
    -    guide = guide_axis_minor()
    -  ) +
    -  scale_fill_bright() +
    -  theme_clean() +
    -  theme(
    -    legend.position = "bottom"
    -  )
    -

    +
    +

    Évolutions temporelles

    +

    Pour cet exemple, nous allons utiliser des données fictives. Dans les années 1950, au sortir de la guerre, la Syldavie et la Bordurie (deux pays fictifs inventés par Hergé) ont décidé de mettre un programme de distribution de bandes dessinées de Tintin dans des écoles et des collèges afin de motiver les élèves à lire plus. Nous disposons des rapports d’activités des différents agents du programme qui indident, par mois et par type d’établissement, le nombre d’élèves rencontrés et le nombre de bandes dessinées distribués.

    +

    Commençons par importer les données, qui sont au format Excel. Nous utiliserons donc la fonction read_excel de l’extension readxl. Profitons-en pour charger également en mémoire le tidyverse ainsi que l’extension lubridate qui nous servira pour la gestion des dates.

    +
    library(readxl)
    +library(tidyverse)
    +library(lubridate)
    +url <- "https://larmarange.github.io/analyse-R/data/bandes_dessinees_tintin.xlsx"
    +destfile <- "bandes_dessinees_tintin.xlsx"
    +curl::curl_download(url, destfile)
    +bd <- read_excel(destfile)
    +
    glimpse(bd)
    +
    Rows: 4,331
    +Columns: 6
    +$ annee              <dbl> 1950, 1950, 1951, 1951, 1951, 1~
    +$ mois               <dbl> 9, 10, 4, 2, 4, 5, 5, 3, 2, 5, ~
    +$ pays               <chr> "Bordurie", "Bordurie", "Bordur~
    +$ type_etablissement <chr> "école primaire", "école primai~
    +$ eleves_rencontres  <dbl> 2, 3, 6, 16, 1, 5, 6, 3, 5, 3, ~
    +$ bd_distribuees     <dbl> 3, 3, 6, 16, 1, 5, 12, 4, 6, 5,~
    +
    +

    Bandes dessinées distribuées par mois

    +

    En premier lieu, nous souhaiterions représenter le nombre de bandes dessinées distribuées par mois. Comme on peut le voir, nous n’avons pas directement une variable date, l’année et le mois étant indiqués dans deux variables différentes. Nous allons donc commencer par créer une telle variable, qui correspondra au premier jour du mois.

    +

    Nous allons utiliser la fonction paste0 qui permets de concaténer du texte pour créer des chaines de texte au format ANNEE-MOIS-JOUR puis transformer cela en objet Date avec ymd de l’extension lubridate.

    +
    bd$date <- paste0(bd$annee, "-", bd$mois, "-01") %>% ymd()
    +summary(bd$date)
    +
            Min.      1st Qu.       Median         Mean 
    +"1949-12-01" "1950-07-01" "1950-12-01" "1950-11-13" 
    +     3rd Qu.         Max. 
    +"1951-04-01" "1951-07-01" 
    +

    Nous pouvons maintenant commencer à réaliser notre graphique. Nous allons compter le nombre de BD distribuées par mois et représenter cela sous forme d’un graphique en barres. geom_bar utilise la statistique stat_count pour compter le nombre d’observations. Cependant, le nombre de BD distribuées varie d’une ligne du fichier à l’autre. Nous allons donc utiliser l’esthétique weight pour pondérer le calcul et obtenir le total de bandes dessinées distribuées. Nous allons également utiliser l’esthétique fill pour distinguer les écoles primaires et les collèges.

    +
    p <- ggplot(bd) +
    +  aes(x = date, weight = bd_distribuees, fill = type_etablissement) +
    +  geom_bar()
    +p
    +

    +

    Avec facet_grid nous pouvons comparer la Syldavie et la Bordurie.

    +
    p + facet_grid(cols = vars(pays))
    +

    +

    Essayons de faire un peu mieux en répétant l’axe des y sur le graphique de droite, ce qui peut être fait avec la fonction facet_rep_grid de l’extension lemon.

    +
    library(lemon, quietly = TRUE)
    +p + facet_rep_grid(cols = vars(pays))
    +

    +

    Cela a répété les encoches (ticks). Pour répéter également les étiquettes, il nous faut ajouter l’option repeat.tick.labels = TRUE.

    +
    p <- p + facet_rep_grid(cols = vars(pays), repeat.tick.labels = TRUE)
    +p
    +

    +

    Allégeons un peu le graphique (voir exemple précédent). Pour cela, nous allons utiliser le thème theme_clean fourni par ggthemes.

    +
    library(ggthemes)
    +p <- p +
    +  xlab("") + ylab("") + labs(fill = "") +
    +  ggtitle("Bandes dessinées distribuées par mois") +
    +  theme_clean() +
    +  theme(
    +    legend.position = "bottom"
    +  )
    +p
    +

    +

    De même, nous allons utiliser une palette de couleurs adaptées aux personnes daltoniennes, en s’appuyant sur scale_fill_bright disponible avec khroma.

    +
    library(khroma)
    +p <- p + scale_fill_bright()
    +p
    +

    +

    Reste à améliorer la présentation de l’axe des x avec scale_x_date{data-pkg=“ggplot2}. L’option date_labels permet de personnaliser l’affichage des dates via des raccourcis décrits dans l’aide de la fonction strptime. Ainsi, %b correspond au mois écrit textuellement de manière abrégée et %Y à l’année sur 4 chiffres. \n est utilisé pour indiquer un retour à la ligne. L’option date_breaks permet d’indiquer de manière facilitée la durée attendue entre deux étiquettes, ici trois mois.

    +
    p <- p + scale_x_date(
    +  date_labels = "%b\n%Y",
    +  date_breaks = "3 months"
    +)
    +p
    +

    +

    Pour améliorer le graphique, nous aimerions rajouter une encoche sous chaque mois. Ceci est rendu possible avec la fonction guide_axis_minor de ggh4x.

    +
    library(ggh4x)
    +p <- p + scale_x_date(
    +  date_labels = "%b\n%Y",
    +  date_breaks = "3 months",
    +  date_minor_breaks = "1 month",
    +  guide = guide_axis_minor()
    +)
    +
    Scale for 'x' is already present. Adding another scale
    +for 'x', which will replace the existing scale.
    +
    p
    +

    +

    Le code complet de ce graphique est donc :

    +
    library(ggthemes)
    +library(khroma)
    +library(ggh4x)
    +library(lemon)
    +ggplot(bd) +
    +  aes(x = date, weight = bd_distribuees, fill = type_etablissement) +
    +  geom_bar() +
    +  facet_rep_grid(cols = vars(pays), repeat.tick.labels = TRUE) +
    +  xlab("") +
    +  ylab("") +
    +  labs(fill = "") +
    +  ggtitle("Bandes dessinées distribuées par mois") +
    +  scale_x_date(
    +    date_labels = "%b\n%Y",
    +    date_breaks = "3 months",
    +    date_minor_breaks = "1 month",
    +    guide = guide_axis_minor()
    +  ) +
    +  scale_fill_bright() +
    +  theme_clean() +
    +  theme(
    +    legend.position = "bottom"
    +  )
    +

    +
    +
    +

    Évolution du nombre moyen de bandes dessinées remises par élève

    +

    Nous disposons dans les rapports d’activités du nombre de bandes dessinées distribuées et du nombre d’élèves rencontrés. Nous pouvons donc regarder comment à évaluer au cours du temps le nombre moyen de bandes dessinées distribuées par élève, à savoir l’évolution du ratio bd_distribuees / eleves_rencontres.

    +
    ggplot(bd) +
    +  aes(x = date, y = bd_distribuees / eleves_rencontres, colour = type_etablissement) +
    +  geom_point()
    +

    +

    Comme on le voit sur la figure précédente, si l’on représente directement cet indicateur, nous obtenons plusieurs points pour chaque date. En effet, nous avons plusieurs observations par date et nous obtenons donc un point pour chacune de ces observations. Nous souhaiterions avoir la moyenne pour chaque date. Cela est possible en ayant recours à stat_weighted_mean de l’extension GGally. Pour que le calcul soit correct, il faut réaliser une moyenne pondérée par le dénominateur, à savoir ici le nombre d’élèves.

    +
    library(GGally)
    +ggplot(bd) +
    +  aes(
    +    x = date, y = bd_distribuees / eleves_rencontres,
    +    weight = eleves_rencontres, colour = type_etablissement
    +  ) +
    +  geom_point(stat = "weighted_mean")
    +

    +

    Le graphique sera plus lisible si nous représentons des lignes.

    +
    library(GGally)
    +ggplot(bd) +
    +  aes(
    +    x = date, y = bd_distribuees / eleves_rencontres,
    +    weight = eleves_rencontres, colour = type_etablissement
    +  ) +
    +  geom_line(stat = "weighted_mean")
    +

    +

    Pour améliorer la comparaison, il serait pertinent d’ajouter les intervalles de confiance sur le graphique. Cela peut être réalisé par exemple avec des rubans de couleur. Mais pour cela, il va falloir avoir recours à quelques astuces pour permettre le calcul de ces intervalles de confiance à la volée.

    +

    Tout d’abord, pour calculer l’intervalle de confiance d’un ratio de nombres entiers, nous pouvons utiliser un test de Poisson avec poisson.test auquel nous devons transmettre le numérateur et le dénominateur. Par exemple, pour 48 bandes dessinées distribuées à 27 élèves :

    +
    test <- poisson.test(48, 27)
    +test
    +
    
    +    Exact Poisson test
    +
    +data:  48 time base: 27
    +number of events = 48, time base = 27, p-value =
    +0.0002246
    +alternative hypothesis: true event rate is not equal to 1
    +95 percent confidence interval:
    + 1.310793 2.357075
    +sample estimates:
    +event rate 
    +  1.777778 
    +
    str(test)
    +
    List of 9
    + $ statistic  : Named num 48
    +  ..- attr(*, "names")= chr "number of events"
    + $ parameter  : Named num 27
    +  ..- attr(*, "names")= chr "time base"
    + $ p.value    : num 0.000225
    + $ conf.int   : num [1:2] 1.31 2.36
    +  ..- attr(*, "conf.level")= num 0.95
    + $ estimate   : Named num 1.78
    +  ..- attr(*, "names")= chr "event rate"
    + $ null.value : Named num 1
    +  ..- attr(*, "names")= chr "event rate"
    + $ alternative: chr "two.sided"
    + $ method     : chr "Exact Poisson test"
    + $ data.name  : chr "48 time base: 27"
    + - attr(*, "class")= chr "htest"
    +

    Comme nous le montre str, poisson.test renvoie une liste avec différents éléments et l’intervalle de confiance est disponible dans le sous-objet "conf.int". Il est possible d’y accéder avec l’opérateur $ ou bien avec la fonction pluck de l’extension purrr chargée par défaut avec le tidyverse.

    +
    test$conf.int
    +
    [1] 1.310793 2.357075
    +attr(,"conf.level")
    +[1] 0.95
    +
    test %>% pluck("conf.int")
    +
    [1] 1.310793 2.357075
    +attr(,"conf.level")
    +[1] 0.95
    +

    Pour obtenir la borne inférieure de l’intervalle de confiance, il nous faut maintenant extraire le premier élément.

    +
    test$conf.int[1]
    +
    [1] 1.310793
    +
    test %>% pluck("conf.int", 1)
    +
    [1] 1.310793
    +

    Nous avons trouvé la manière de calculer la borne inférieure de l’intervalle de confiance pour une ligne d’observations. Mais comment procéder pour plusieurs observations. Supposons que nous ayons 4 observations :

    +
    num <- c(50, 25, 32, 48)
    +denom <- c(25, 17, 25, 31)
    +

    Si nous passons ces vecteurs de données à poisson.test, nous obtenons un message d’erreur car poisson.test ne permets de calculer qu’un seul test à la fois.

    +
    poisson.test(num, denom)
    +
    Error in poisson.test(num, denom) : le cas k > 2 n'est pas implémenté
    +

    Dans notre cas, nous souhaitons exécuter poisson.test ligne à ligne. Cela peut être réalisée avec la famille de fonctions map fournies par l’extension purrr. Plus précisément, dans notre situation, nous allons avoir recours à map2 qui permet de fournir deux vecteurs en entrée et d’appliquer une fonction ligne à ligne. On passera à map2 soit une fonction à utiliser telle quelle ou bien une formule décrivant une fonction personnalisée à la levée, ce que nous allons faire. Dans cette formule décrivant le calcul à effectuer, on utilisera .x pour se référer à l’élément du premier vecteur et à .y pour se référer au deuxième vecteur.

    +
    map2(num, denom, ~ poisson.test(.x, .y) %>% pluck("conf.int", 1))
    +
    [[1]]
    +[1] 1.484439
    +
    +[[2]]
    +[1] 0.9516872
    +
    +[[3]]
    +[1] 0.8755191
    +
    +[[4]]
    +[1] 1.141658
    +

    Le résultat obtenu est une liste de 4 éléments, chaque élément contenu la borne inférieur des intervalles. Nous préférerions que la fonction nous retourne un vecteur de valeurs numériques (double) plutôt qu’une liste. Nous allons donc utiliser map2_dbl plutôt que map2.

    +
    map2_dbl(num, denom, ~ poisson.test(.x, .y) %>% pluck("conf.int", 1))
    +
    [1] 1.4844385 0.9516872 0.8755191 1.1416584
    +

    Nous y sommes presque. Il nous reste plus qu’à avoir recours à after_stat pour avoir accès aux numérateurs et dénominateurs calculés par stat_weighted_mean.

    +
    ggplot(bd) +
    +  aes(
    +    x = date, y = bd_distribuees / eleves_rencontres, weight = eleves_rencontres,
    +    ymin = map2_dbl(after_stat(numerator), after_stat(denominator), ~ poisson.test(.x, .y) %>% pluck("conf.int", 1)),
    +    ymax = map2_dbl(after_stat(numerator), after_stat(denominator), ~ poisson.test(.x, .y) %>% pluck("conf.int", 2)),
    +    colour = type_etablissement, fill = type_etablissement
    +  ) +
    +  geom_ribbon(stat = "weighted_mean", alpha = .5) +
    +  geom_line(stat = "weighted_mean")
    +

    +

    Faisons quelques ajustements pour une meilleur lisibilité : effaçons les limites supérieures et inférieures des rubans avec color = "transparent", augmentons la transparence des rubans avec alpha = .2 et épaississons les courbes principales avec size = 1.

    +
    p <- ggplot(bd) +
    +  aes(
    +    x = date, y = bd_distribuees / eleves_rencontres, weight = eleves_rencontres,
    +    ymin = map2_dbl(after_stat(numerator), after_stat(denominator), ~ poisson.test(.x, .y) %>% pluck("conf.int", 1)),
    +    ymax = map2_dbl(after_stat(numerator), after_stat(denominator), ~ poisson.test(.x, .y) %>% pluck("conf.int", 2)),
    +    colour = type_etablissement, fill = type_etablissement
    +  ) +
    +  geom_ribbon(stat = "weighted_mean", alpha = .25, color = "transparent") +
    +  geom_line(stat = "weighted_mean", size = 1)
    +p
    +

    +

    Comme tous les calculs sont réalisés à la volée, il est possible de définir simplement et rapidement des facettes pour séparer les résultats par pays.

    +
    p <- p + facet_rep_grid(cols = vars(pays), repeat.tick.labels = TRUE)
    +p
    +

    +

    Faisons un peu d’habillage :

    +
    p <- p +
    +  xlab("") + ylab("") + labs(fill = "", colour = "") +
    +  ggtitle(
    +    "Nombre moyen de bandes dessinées distribuées par élève",
    +    subtitle = "par mois, type d'établissement et pays"
    +  ) +
    +  scale_x_date(
    +    date_labels = "%b\n%Y",
    +    date_breaks = "3 months",
    +    date_minor_breaks = "1 month",
    +    guide = guide_axis_minor()
    +  ) +
    +  scale_fill_bright() +
    +  scale_colour_bright() +
    +  theme_clean() +
    +  theme(
    +    legend.position = "bottom"
    +  )
    +p
    +

    +

    Voilà !

    +

    Le code final de notre graphique est donc :

    +
    library(ggthemes)
    +library(khroma)
    +library(ggh4x)
    +library(GGally)
    +library(lemon)
    +ggplot(bd) +
    +  aes(
    +    x = date, y = bd_distribuees / eleves_rencontres, weight = eleves_rencontres,
    +    ymin = map2_dbl(after_stat(numerator), after_stat(denominator), ~ poisson.test(.x, .y) %>% pluck("conf.int", 1)),
    +    ymax = map2_dbl(after_stat(numerator), after_stat(denominator), ~ poisson.test(.x, .y) %>% pluck("conf.int", 2)),
    +    colour = type_etablissement, fill = type_etablissement
    +  ) +
    +  geom_ribbon(stat = "weighted_mean", alpha = .25, color = "transparent") +
    +  geom_line(stat = "weighted_mean", size = 1) +
    +  facet_rep_grid(cols = vars(pays), repeat.tick.labels = TRUE) +
    +  xlab("") +
    +  ylab("") +
    +  labs(fill = "", colour = "") +
    +  ggtitle(
    +    "Nombre moyen de bandes dessinées distribuées par élève",
    +    subtitle = "par mois, type d'établissement et pays"
    +  ) +
    +  scale_x_date(
    +    date_labels = "%b\n%Y",
    +    date_breaks = "3 months",
    +    date_minor_breaks = "1 month",
    +    guide = guide_axis_minor()
    +  ) +
    +  scale_fill_bright() +
    +  scale_colour_bright() +
    +  theme_clean() +
    +  theme(
    +    legend.position = "bottom"
    +  )
    +

    +
    +
    +
    +

    Pour aller plus loin

    +

    On pourra jeter un œil au chapitre Étendre ggplot2.

    +
    + +
    +
    + + + +

    Graphiques interactifs

    + +
    + + + +
    +

    ggplotly (plotly)

    +

    Vous savez réaliser des graphiques avec ggplot2 ? Vous savez faire un graphique interactif. Rien de plus facile avec la fonction ggplotly de l’extension plotly.

    +

    Créons un graphique.

    +
    library(ggplot2)
    +p <- ggplot(iris) +
    +  aes(x = Petal.Width, y = Sepal.Length, color = Species) +
    +  geom_point()
    +

    Voici son rendu classique avec ggplot2.

    +
    p
    +

    +

    Et si on passe notre graphique à ggplotly. (N’hésitez pas à faire passer le curseur de votre souris sur le graphique.)

    +
    library(plotly)
    +ggplotly(p)
    +
    +

    Une documentation complète sur ggplotly est disponible sur https://plot.ly/ggplot2/.

    +
    +
    +

    ggvis

    +

    Il existe également une extension ggvis dédiée aux graphiques interactifs. Sa documentation complète est disponible sur https://ggvis.rstudio.com/.

    +
    + +
    +
    + + + +

    lattice : graphiques et formules

    + +
    + + + +

    Bien que l’on ait fait le choix de présenter principalement l’extension ggplot2 plutôt que l’extension lattice, celle-ci reste un excellent choix pour la visualisation, notamment, de panels et de séries temporelles. On trouve de très beaux exemples d’utilisation de lattice en ligne, mais un peu moins de documentation, et beaucoup moins d’extensions, que pour ggplot2.

    +

    On peut trouver en ligne un support de cours détaillé (en anglais) de Deepayan Sarkar (https://www.isid.ac.in/~deepayan/R-tutorials/labs/04_lattice_lab.pdf), également l’auteur de l’ouvrage Lattice: Multivariate Data Visualization with R (http://lmdvr.r-forge.r-project.org/).

    +
    +

    Les bases des graphiques lattice +

    +

    R dispose de deux principaux systèmes graphiques : un système de base contrôlé par le package graphics et le système grid, sur lequel se basent à la fois les packages lattice et ggplot2. Ce système fournit les mêmes fonctionnalités de base que graphics mais offre une gestion de l’arrangement des objets graphiques plus développée, et surtout la possibilité d’utiliser ce que l’on appelle des graphiques en treillis. De plus, les graphiques peuvent être mis à jour très simplement, disposent de thèmes de couleur pré-définis, et offrent un certain degré d’interactivité, avec ou sans le package plotly. Enfin, la syntaxe est plus homogène et grandement simplifié, grâce à l’usage de formules.

    +
    +

    De l’intérêt des formules R

    +

    Voici par exemple comment afficher la courbe de densité (i.e., la version continue et “lissée” d’un histogramme) de deux séries d’observations définies par les niveaux du facteur supp dans le data frame ToothGrowth, disponible dans les exemples de base de R. Notons que l’on souhaite également faire apparaître les distributions univariées, un peu à l’image de ce que fournit rug. Or cette fonction ne permet pas d’exploiter une variable de groupement, donc il sera nécessaire de gérer tout cela manuellement. Voici les instructions permettant de générer le graphique désiré :

    +
    plot(density(ToothGrowth$len[ToothGrowth$supp == "OJ"]), main = "", xlab = "len", las = 1, lwd = 2, col = "coral")
    +lines(density(ToothGrowth$len[ToothGrowth$supp == "VC"]), lwd = 2, col = "cornflowerblue")
    +points(
    +  x = ToothGrowth$len[ToothGrowth$supp == "OJ"],
    +  y = runif(length(ToothGrowth$len[ToothGrowth$supp == "OJ"]),
    +    min = -0.001, max = 0.001
    +  ), col = "coral"
    +)
    +points(
    +  x = ToothGrowth$len[ToothGrowth$supp == "VC"],
    +  y = runif(length(ToothGrowth$len[ToothGrowth$supp == "VC"]),
    +    min = -0.001, max = 0.001
    +  ), col = "cornflowerblue"
    +)
    +legend("top", levels(ToothGrowth$supp), col = c("coral", "cornflowerblue"), lty = 1, bty = "n")
    +
    +Courbes de densité avec les graphiques de base +

    Il y a plusieurs points à retenir dans les instructions ci-dessus : (1) il est nécessaire de définir les deux courbes de densité et les deux distributions univariées, en prenant garde à bien indiquer comment sélectionner les observations (OJ ou VC) en préfixant systématiquement le nom des variables par le nom du data frame ; (2) la définition des couleurs se fait manuellement et si l’on souhaite changer de thème de couleur, il faudra mettre à jour l’ensemble des instructions, en prenant garde à ce que la lgende reste synchronisée avec les courbes de densité et les nuages de points ; et, bien entendu, (3) il est nécessaire de gérer soi-même la légende, ce qui signifie se rappeler les couleurs et l’ordre des niveaux du facteur considéré, ainsi que les axes graphiques dans le cas où l’on souhaite les maintenir coordonnés sur plusieurs panneaux graphiques.

    +

    Voici le même graphique avec lattice :

    +
    library(lattice)
    +densityplot(~len, data = ToothGrowth, group = supp, auto.key = TRUE)
    +
    +Courbes de densité avec le package lattice +

    Avec ggplot2, cela donnerait :

    +
    library(ggplot2)
    +ggplot(data = ToothGrowth, aes(x = len, color = supp)) +
    +  geom_line(stat = "density") +
    +  geom_rug()
    +
    +Courbes de densité avec le package ggplot2 +

    Clairement, on gagne en nombre d’instructions à taper dans la console et en clarté d’expression également, grâce notamment à l’usage de formules permettant de décrire la relation entre chacune des variables utilisées pour construire la représentation graphique.

    +
    +
    +

    Les formules R

    +

    Les formules utilisées dans le système lattice sont presque identiques à celles retouvées dans les modèles d’analyse de variance (aov) ou de régression (lm). En réalité, la notation par formule qu’utilise R est celle proposée par Wilkinson et coll. dans les années 70 pour schématiser la relation entre plusieurs variables dans un plan d’expérience. Plus spécifiquement, l’idée revient à exprimer une relation fonctionnelle, symbolise´e par l’opérateur ~, entre une variable re´ponse y et une ou plusieurs variables explicatives. Disons, pour simplifier, que y est une variable numérique, de même que x, et que a et b sont des variables catégorielles (des facteurs dans le langage R). Voici les principales relations auxquelles on peut s’intéresser dans un modèle statistique linéaire :

    +
      +
    • +y ~ x : régression linéaire simple,
    • +
    • +y ~ x + 0 : idem avec suppression du terme d’ordonnée à l’origine,
    • +
    • +y ~ a + b : ANOVA avec deux effets principaux,
    • +
    • +y ~ a * b : idem avec interaction (équivalent à 1 + a + b + a:b),
    • +
    • +y ~ a / b : idem en considérant une relation d’emboîtement (équivalent à 1 + a + b + a %in% b).
    • +
    +

    Un exemple typique d’utilisation pour un modèle d’ANOVA à trois facteurs est donné ci-dessous :

    +
    fm <- y ~ a * b * c # modèle de base (A, B, C, AB, AC, BC, ABC)
    +mod1 <- aov(fm, data = dfrm) # estimation des parame`tres du mode`le
    +update(mod1, . ~ . - a:b:c) # suppression de l'interaction ABC
    +

    Quant on y réfléchit un peu, les relations ci-dessus peuvent très bien s’appliquer au cas de la composition graphique : y ~ x signifie dans ce cas que l’on souhaite représenter l’évolution de y en fonction de x. En d’autres termes, on s’intéresse à un nuage de dispersion. Le package lattice ajoute les notations suivantes :

    +
      +
    • +~ x : dans le cas où l’on ne décrit qu’une seule variable (i.e., sa distribution),
    • +
    • +a | b : dans le cas où l’on considère la variable a, conditionnellement à la variable b, c’est-à-dire les niveaux de a pour chacun des niveaux de b (ce qui revient à l’interaction a:b citée ci-dessus).
    • +
    +

    Cette dernière notation se révèlera être très utile dans le cas des représentations graphiques conditionnelles, par exemple lorsque l’on souhaite afficher la distribution d’une variable numérique dans différents groupes d’individus définis par les niveaux d’une variable catégorielle, ou lorsque l’on souhaite surligner d’une couleur différentes les points d’un diagramme de dispersion selon la valeur prise par une troisième variable.

    +

    Les formules R sont omniprésentes dans les modèles statistiques, dans les graphiques, mais également dans certaines commandes d’agrégation. Au bout du compte, avec une même formule il est possible de calculer des moyennes de groupes, réaliser une ANOVA et construire la représentation graphique associée. En voici un exemple :

    +
    fm <- len ~ supp
    +m <- aggregate(fm, data = ToothGrowth, mean)
    +summary(aov(fm, data = ToothGrowth))
    +bwplot(fm, data = ToothGrowth)
    +
    +
    +
    +

    Principaux types de graphiques avec lattice (et ggplot2)

    +

    Même si le package lattice fournit moins de commandes que ggplot2, il n’en demeure pas moins qu’il est tout aussi facile de réaliser des représentations graphiques simples en un tour de main. Voici quelques exemples de représentations graphiques uni- et bivariées. Les données d’illustration sont les mêmes que celles utilisées plus haut (ToothGrowth): il s’agit d’une expérience de biologie dans laquelle on s’intéresse à la croissance des odontoblastes de cochons d’inde quantifiée par leur longueur (variable len) lorsqu’on administre à ces derniers de la vitamine C soit sous forme d’acide ascorbique soit sous forme de jus d’orange (supp, OJ = jus d’orange), à différentes doses (dose, en mg).

    +
    +

    Histogramme

    +

    Un histogramme d’effectifs se construit avec histogram. Puisqu’il s’agit de décrire une seule variable, ou sa distribution plus précisément, la formule à employer ne contient pas de variable à gauche du symbole ~ et l’on se contente d’écrire la variable à résumer à droite dans la formule :

    +
    histogram(~len, data = ToothGrowth, type = "count")
    +
    +Histogramme d’effectifs +

    L’option type = "count" permet de forcer la représentation sous forme d’effectifs puisque, par défaut, c’est la densité qui est représentée. La formulation équivalente sous ggplot2 serait :

    +
    ggplot(data = ToothGrowth, aes(x = len)) +
    +  geom_histogram(binwidth = 5)
    +

    (Ou alors qplot(x = len, data = ToothGrowth, geom = "histogram", binwidth = 5).)

    +

    En ajoutant une facette pour tenir compte de la variable supp, cela donne :

    +
    histogram(~ len | supp, data = ToothGrowth, breaks = seq(0, 40, by = 5))
    +
    +Histogramme d’effectifs conditionné sur une variable catégorielle +

    Avec ggplot2, les facettes sont gérées grâce aux commandes facet_grid et facet_wrap.

    +
    +
    +

    Courbe de densité

    +

    Une courbe de densité se construit à l’aide de densityplot et la syntaxe est strictement identique à celle de histogram, à l’option type= près.

    +
    densityplot(~len, data = ToothGrowth, plot.points = FALSE, from = 0, to = 40)
    +
    +Courbe de densité +

    Il est possible de régler le paramètre de lissage à l’aide de l’option bw= : des valeurs plus élevées résultent en une courbe beaucoup plus lissée (essayez avec bw = 10 !) et donc beaucoup moins sensible aux variations locales de la densité.

    +

    À ce stade, on peut en profiter pour discuter les options de conditionnement sur une variable catégorielle et la manière de gérer la présentation graphique : dans le cas d’un histogramme, il est délicat de superposer deux distributions ou plus sur le même graphique, même en ajoutant de la transparence, d’où l’idée de représenter les distributions dans des panneaux graphiques séparés. C’est ce qu’on a réalisé en indiquant que l’on souhaitait décrire la variable len conditionnellement aux valeurs prises par supp (~ len | supp). Dans ce cas, l’opérateur | invoque une facette et un decoupage en autant de panneaux graphiques qu’il y a de valeurs uniques dans la variable supp. Une autre approche consiste à utiliser l’option groups=, et dans ce cas les différentes distributions seront affichées dans le même panneau graphique. Dans le cas d’une courbe de densité, cela revient à les superposer sur la même fenêtre graphique, avec un système de coordonnées unique. Les deux options de conditionnement peuvent être combinées naturellement.

    +

    Voici un exemple de graphique conditionnel un peu plus élaboré :

    +
    densityplot(~len,
    +  data = ToothGrowth, groups = supp, auto.key = TRUE, xlab = "len",
    +  par.settings = list(superpose.line = list(col = c("coral", "cornflowerblue")))
    +)
    +
    +Courbe de densité conditionnelle +

    Au passage, on en a profité pour modifier le thème de couleur. Notez qu’en utilisant par.settings=, lattice se charge de coordonner les couleurs de la légende (auto.key = TRUE) avec celle des éléments graphiques correspondants.

    +

    L’équivalent sous ggplot2 revient à peu près à l’instruction suivante :

    +
    ggplot(data = ToothGrowth) +
    +  aes(x = len, colour = supp) +
    +  geom_line(stat = "density") +
    +  expand_limits(x = c(0, 40)) +
    +  scale_colour_manual("", values = c("coral", "cornflowerblue")) +
    +  theme_bw()
    +

    +
    +
    +

    Diagramme en barres

    +

    Les diagrammes en barres peuvent avantageusement être remplacés par des diagrammes en points, tels que les diagrammes de Cleveland (cf. plus loin), mais en attendant voici comment en réaliser un à l’aide de barchart à partir de données agrégées :

    +
    library(latticeExtra, quietly = TRUE)
    +m <- aggregate(len ~ supp + dose, data = ToothGrowth, mean)
    +barchart(len ~ dose, data = m, groups = supp, horizontal = FALSE, auto.key = TRUE, par.settings = ggplot2like())
    +
    +Diagramme en barres +

    Notons que par.settings= permet non seulement de fournir des options additionnelles pour contrôler le rendu des éléments graphiques (couleur, type de ligne ou de symboles, etc.) mais également d’utiliser des thèmes graphiques disponibles dans le package latticeExtra.

    +
    +
    +

    Diagramme de type boîtes à moustaches

    +

    Les diagrammes en forme de boîtes à moustaches sont obtenus à l’aide de la commande bwplot. Voici un exemple d’utilisation :

    +
    bwplot(len ~ supp, data = ToothGrowth, pch = "|")
    +
    +Diagramme en forme de boîtes à moustaches +

    L’option pch= permet de contrôler la manière dont la médiane est figurée dans la boîte. Par défaut il s’agit d’un simple point, mais si l’on souhaite utiliser les représentations plus classiques, telles que celles trouvées dans boxplot ou geom_boxplot, il suffit de suivre l’exemple ci-dessus. Notons que dans le cas de cette représentation graphique, le conditionnement sur la variable supp est d’emblée réalisé par l’utilisation d’une formule invoquant la variable de conditionnement à droite de l’opérateur ~.

    +
    +
    +

    Diagramme en points

    +

    Le même type de représentation graphique peut être obtenu en utilisant directement les données individuelles, et non leur résumé en cinq points (tel que fournit par summary et exploité par bwplot). Dans ce cas, il s’agit de la commande dotplot, qui permet de construire des diagrammes de Cleveland (moyenne ou effectif total calculé pour une variable en fonction des niveaux d’une autre variable) ou, dans le cas où la variable à résumer consiste en une série de mesures individuelles numériques, des diagrammes de dispersion. Voici une illustration pour ce dernier cas de figure :

    +
    dotplot(len ~ supp, ToothGrowth, jitter.x = TRUE)
    +
    +Diagramme en points +
    +
    +
    +

    Diagramme de dispersion

    +

    Enfin, un diagramme de dispersion est construit à l’aide de la commande xyplot.

    +
    xyplot(len ~ dose, ToothGrowth, type = c("p", "smooth"))
    +
    +Diagramme de dispersion +

    Même si l’exemple ne s’y prête guère, on en a profité pour ajouter une courbe lowess de régression afin d’indiquer la tendance de covariation entre les deux variables numériques. L’aide en ligne pour xyplot n’est pas très utile dans ce cas, et il faut en fait aller regarder les options de personnalisation disponibles dans la sous-fonction correspondante : panel.xyplot.

    +
    +
    + +
    +
    + + + +

    Cartes

    + +
    + + +
    +

    Pour une présentation de l’analyse spatiale sous R, se référer au chapitre dédié.

    +
    +

    Il existe de multiple approches pour réaliser des cartes sous R, y compris avec ggplot2, mais également de manière native avec les extensions sp et sf. Il existe également des extensions apportant des fonctionalités additionnelles comme ggmap, mapview ou encore tmap.

    +

    Pour une introduction succincte en français, on pourra se référer à la section 5.4 du spport de cours Logiciel R et programmation d’Ewan Gallic.

    +

    Voir également l’excellente présentation Données géospatiales et cartographie avec R de Nicolas Roelandt : https://roelandtn.frama.io/slides/2090628_meetup_Raddict_datageo.html.

    +

    En complément (en anglais), la vignette Plotting Simple Features de l’extension sf ou encore le chapitre Making maps with R de l’ouvrage Geocomputation with R de Robin Lovelace, Jakub Nowosad et Jannes Muenchow.

    +

    On pourra également se référer à l’excellent package cartography et à son site dédié : https://github.com/riatelab/cartography/ (pour une présentation en vidéo et en français : https://youtu.be/OI3_AOg6pfc).

    + +
    +
    + + + +

    Autres extensions graphiques

    + +
    + + + + +

    Pour trouver l’inspiration et des exemples de code, rien ne vaut l’excellent site https://www.r-graph-gallery.com/.

    +
    +

    GGally

    +

    L’extension GGally, déjà abordée dans d’autres chapitres, fournit plusieurs fonctions graphiques d’exploration des résultats d’un modèle ou des relations entre variables.

    +
    reg <- lm(Sepal.Length ~ Sepal.Width + Petal.Length + Petal.Width, data = iris)
    +library(GGally)
    +ggcoef(reg)
    +

    +
    data(tips, package = "reshape")
    +ggpairs(tips)
    +

    +

    Plus d’information : https://ggobi.github.io/ggally/

    +
    +
    +

    ggpubr

    +

    L’extension ggpubr fournit plusieurs fonctions pour produire clés en main différents graphiques bivariés avec une mise en forme allégée.

    +
    library(ggpubr)
    +data("ToothGrowth")
    +df <- ToothGrowth
    +ggboxplot(df,
    +  x = "dose", y = "len",
    +  color = "dose", palette = c("#00AFBB", "#E7B800", "#FC4E07"),
    +  add = "jitter", shape = "dose"
    +)
    +

    +
    data("mtcars")
    +dfm <- mtcars
    +# Convert the cyl variable to a factor
    +dfm$cyl <- as.factor(dfm$cyl)
    +# Add the name colums
    +dfm$name <- rownames(dfm)
    +# Calculate the z-score of the mpg data
    +dfm$mpg_z <- (dfm$mpg - mean(dfm$mpg)) / sd(dfm$mpg)
    +dfm$mpg_grp <- factor(ifelse(dfm$mpg_z < 0, "low", "high"),
    +  levels = c("low", "high")
    +)
    +
    +ggbarplot(dfm,
    +  x = "name", y = "mpg_z",
    +  fill = "mpg_grp", # change fill color by mpg_level
    +  color = "white", # Set bar border colors to white
    +  palette = "jco", # jco journal color palett. see ?ggpar
    +  sort.val = "asc", # Sort the value in ascending order
    +  sort.by.groups = FALSE, # Don't sort inside each group
    +  x.text.angle = 90, # Rotate vertically x axis texts
    +  ylab = "MPG z-score",
    +  xlab = FALSE,
    +  legend.title = "MPG Group"
    +)
    +

    +
    ggdotchart(dfm,
    +  x = "name", y = "mpg_z",
    +  color = "cyl", # Color by groups
    +  palette = c("#00AFBB", "#E7B800", "#FC4E07"), # Custom color palette
    +  sorting = "descending", # Sort value in descending order
    +  add = "segments", # Add segments from y = 0 to dots
    +  add.params = list(color = "lightgray", size = 2), # Change segment color and size
    +  group = "cyl", # Order by groups
    +  dot.size = 6, # Large dot size
    +  label = round(dfm$mpg_z, 1), # Add mpg values as dot labels
    +  font.label = list(
    +    color = "white", size = 9,
    +    vjust = 0.5
    +  ), # Adjust label parameters
    +  ggtheme = theme_pubr() # ggplot2 theme
    +) +
    +  geom_hline(yintercept = 0, linetype = 2, color = "lightgray")
    +

    +

    Plus d’informations : https://rpkgs.datanovia.com/ggpubr/

    +
    +
    +

    ggdendro

    +

    L’extension ggendro avec sa fonction ggdendrogram permet de représenter facilement des dendrogrammes avec ggplot2.

    +
    library(ggplot2)
    +library(ggdendro)
    +hc <- hclust(dist(USArrests), "ave")
    +hcdata <- dendro_data(hc, type = "rectangle")
    +ggplot() +
    +  geom_segment(data = segment(hcdata), aes(x = x, y = y, xend = xend, yend = yend)) +
    +  geom_text(data = label(hcdata), aes(x = x, y = y, label = label, hjust = 0), size = 3) +
    +  coord_flip() +
    +  scale_y_reverse(expand = c(0.2, 0))
    +

    +
    ### demonstrate plotting directly from object class hclust
    +ggdendrogram(hc)
    +

    +
    ggdendrogram(hc, rotate = TRUE)
    +

    +

    Plus d’informations : https://cran.r-project.org/web/packages/ggdendro/vignettes/ggdendro.html

    +
    +
    +

    circlize

    +

    L’extension circlize est l’extension de référence quand il s’agit de représentations circulaires. Un ouvrage entier lui est dédié : https://jokergoo.github.io/circlize_book/book/.

    +

    Voici un exemple issu de https://www.data-to-viz.com/story/AdjacencyMatrix.html.

    +
    library(tidyverse)
    +
    +# Load data
    +data <- read.table("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/13_AdjacencyDirectedWeighted.csv", header = TRUE)
    +# short names
    +colnames(data) <- c("Africa", "East Asia", "Europe", "Latin Ame.", "North Ame.", "Oceania", "South Asia", "South East Asia", "Soviet Union", "West.Asia")
    +rownames(data) <- colnames(data)
    +
    +# I need a long format
    +data_long <- data %>%
    +  rownames_to_column() %>%
    +  gather(key = "key", value = "value", -rowname)
    +
    +
    +library(circlize)
    +# parameters
    +circos.clear()
    +circos.par(start.degree = 90, gap.degree = 4, track.margin = c(-0.1, 0.1), points.overflow.warning = FALSE)
    +par(mar = rep(0, 4))
    +
    +# color palette
    +library(viridis)
    +mycolor <- viridis(10, alpha = 1, begin = 0, end = 1, option = "D")
    +mycolor <- mycolor[sample(1:10)]
    +
    +# Base plot
    +chordDiagram(
    +  x = data_long,
    +  grid.col = mycolor,
    +  transparency = 0.25,
    +  directional = 1,
    +  direction.type = c("arrows", "diffHeight"),
    +  diffHeight = -0.04,
    +  annotationTrack = "grid",
    +  annotationTrackHeight = c(0.05, 0.1),
    +  link.arr.type = "big.arrow",
    +  link.sort = TRUE,
    +  link.largest.ontop = TRUE
    +)
    +
    +# Add text and axis
    +circos.trackPlotRegion(
    +  track.index = 1,
    +  bg.border = NA,
    +  panel.fun = function(x, y) {
    +    xlim <- get.cell.meta.data("xlim")
    +    sector.index <- get.cell.meta.data("sector.index")
    +
    +    # Add names to the sector.
    +    circos.text(
    +      x = mean(xlim),
    +      y = 3.2,
    +      labels = sector.index,
    +      facing = "bending",
    +      cex = 0.8
    +    )
    +
    +    # Add graduation on axis
    +    circos.axis(
    +      h = "top",
    +      major.at = seq(from = 0, to = xlim[2], by = ifelse(test = xlim[2] > 10, yes = 2, no = 1)),
    +      minor.ticks = 1,
    +      major.tick.percentage = 0.5,
    +      labels.niceFacing = FALSE
    +    )
    +  }
    +)
    +
    `major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    +`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    +`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    +`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    +`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    +`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    +`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    +`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    +`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    +`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    +

    +
    +
    +

    Diagrammes de Sankey

    +

    Les diagrammes de Sankey sont un type alternatif de représentation de flux. Voici un premier exemple, qui reprend les données utilisées pour le diagramme circulaire précédent, avec la fonction sankeyNetwork de l’extension sankeyNetwork.

    +
    # Package
    +library(networkD3)
    +
    +# I need a long format
    +data_long <- data %>%
    +  rownames_to_column() %>%
    +  gather(key = "key", value = "value", -rowname) %>%
    +  filter(value > 0)
    +colnames(data_long) <- c("source", "target", "value")
    +data_long$target <- paste(data_long$target, " ", sep = "")
    +
    +# From these flows we need to create a node data frame: it lists every entities involved in the flow
    +nodes <- data.frame(name = c(as.character(data_long$source), as.character(data_long$target)) %>% unique())
    +
    +# With networkD3, connection must be provided using id, not using real name like in the links dataframe.. So we need to reformat it.
    +data_long$IDsource <- match(data_long$source, nodes$name) - 1
    +data_long$IDtarget <- match(data_long$target, nodes$name) - 1
    +
    +# prepare colour scale
    +ColourScal <- 'd3.scaleOrdinal() .range(["#FDE725FF","#B4DE2CFF","#6DCD59FF","#35B779FF","#1F9E89FF","#26828EFF","#31688EFF","#3E4A89FF","#482878FF","#440154FF"])'
    +
    +# Make the Network
    +sankeyNetwork(
    +  Links = data_long, Nodes = nodes,
    +  Source = "IDsource", Target = "IDtarget",
    +  Value = "value", NodeID = "name",
    +  sinksRight = FALSE, colourScale = ColourScal, nodeWidth = 40, fontSize = 13, nodePadding = 20
    +)
    +
    +

    Une alternative possible est fournie par l’extension ggalluvial et ses géométries geom_alluvium et geom_stratum.

    +
    library(ggalluvial)
    +ggplot(data = as.data.frame(Titanic)) +
    +  aes(axis1 = Class, axis2 = Sex, axis3 = Age, y = Freq) +
    +  scale_x_discrete(limits = c("Class", "Sex", "Age"), expand = c(.1, .05)) +
    +  xlab("Demographic") +
    +  geom_alluvium(aes(fill = Survived)) +
    +  geom_stratum() +
    +  geom_text(stat = "stratum", infer.label = TRUE) +
    +  theme_minimal()
    +
    Warning: The parameter `infer.label` is deprecated.
    +Use `aes(label = after_stat(stratum))`.
    +

    +

    Mentionnons également l’extension riverplot pour la création de diagrammes de Sankey.

    +
    +
    +

    DiagrammeR

    +

    DiagrammeR est dédiée à la réalisation de diagrammes en ayant recours à la syntaxe Graphviz (via la fonction grViz) ou encore à la syntaxe Mermaid (via la fonction mermaid).

    +
    library(DiagrammeR)
    +grViz("
    +digraph boxes_and_circles {
    +
    +  # a 'graph' statement
    +  graph [overlap = true, fontsize = 10]
    +
    +  # several 'node' statements
    +  node [shape = box,
    +        fontname = Helvetica]
    +  A; B; C; D; E; F
    +
    +  node [shape = circle,
    +        fixedsize = true,
    +        width = 0.9] // sets as circles
    +  1; 2; 3; 4; 5; 6; 7; 8
    +
    +  # several 'edge' statements
    +  A->1 B->2 B->3 B->4 C->A
    +  1->D E->A 2->4 1->5 1->F
    +  E->6 4->6 5->7 6->7 3->8
    +}
    +")
    +
    +
    mermaid("
    +graph LR
    +A(Rounded)-->B[Rectangular]
    +B-->C{A Rhombus}
    +C-->D[Rectangle One]
    +C-->E[Rectangle Two]
    +")
    +
    +
    mermaid("
    +sequenceDiagram
    +  customer->>ticket seller: ask ticket
    +  ticket seller->>database: seats
    +  alt tickets available
    +    database->>ticket seller: ok
    +    ticket seller->>customer: confirm
    +    customer->>ticket seller: ok
    +    ticket seller->>database: book a seat
    +    ticket seller->>printer: print ticket
    +  else sold out
    +    database->>ticket seller: none left
    +    ticket seller->>customer: sorry
    +  end
    +")
    +
    +
    mermaid("
    +gantt
    +       dateFormat  YYYY-MM-DD
    +       title Adding GANTT diagram functionality to mermaid
    +
    +       section A section
    +       Completed task            :done,    des1, 2014-01-06,2014-01-08
    +       Active task               :active,  des2, 2014-01-09, 3d
    +       Future task               :         des3, after des2, 5d
    +       Future task2              :         des4, after des3, 5d
    +
    +       section Critical tasks
    +       Completed task in the critical line :crit, done, 2014-01-06,24h
    +       Implement parser and jison          :crit, done, after des1, 2d
    +       Create tests for parser             :crit, active, 3d
    +       Future task in critical line        :crit, 5d
    +       Create tests for renderer           :2d
    +       Add to mermaid                      :1d
    +
    +       section Documentation
    +       Describe gantt syntax               :active, a1, after des1, 3d
    +       Add gantt diagram to demo page      :after a1  , 20h
    +       Add another diagram to demo page    :doc1, after a1  , 48h
    +
    +       section Last section
    +       Describe gantt syntax               :after doc1, 3d
    +       Add gantt diagram to demo page      :20h
    +       Add another diagram to demo page    :48h
    +")
    +
    +

    Plus d’informations : https://rich-iannone.github.io/DiagrammeR/

    +
    +
    +

    epicontacts

    +

    L’extension epicontacts permets de représenter des chaînes de transmission épidémiques.

    +

    +

    Pour aller plus loin, on pourra se référer (en anglais), au chapitre dédié du Epidemiologist R Handbook :

    +
    +
    +

    highcharter

    +

    L’extension highcharter permet de réaliser des graphiques HTML utilisant la librairie Javascript Highcharts.js.

    +
    library("highcharter")
    +data(diamonds, mpg, package = "ggplot2")
    +
    +hchart(mpg, "scatter", hcaes(x = displ, y = hwy, group = class))
    +
    +
    library(tidyverse)
    +library(highcharter)
    +mpgman3 <- mpg %>%
    +  group_by(manufacturer) %>%
    +  summarise(n = n(), unique = length(unique(model))) %>%
    +  arrange(-n, -unique)
    +
    +hchart(mpgman3, "treemap", hcaes(x = manufacturer, value = n, color = unique))
    +
    +
    data(unemployment)
    +
    +hcmap("countries/us/us-all-all",
    +  data = unemployment,
    +  name = "Unemployment", value = "value", joinBy = c("hc-key", "code"),
    +  borderColor = "transparent"
    +) %>%
    +  hc_colorAxis(dataClasses = color_classes(c(seq(0, 10, by = 2), 50))) %>%
    +  hc_legend(
    +    layout = "vertical", align = "right",
    +    floating = TRUE, valueDecimals = 0, valueSuffix = "%"
    +  )
    +
    +

    Plus d’informations : http://jkunst.com/highcharter/.

    +
    + +
    +
    + + + +

    Conditions et comparaisons

    + +
    + + +

    Une condition est une expression logique dont le résultat est soit TRUE (vrai) soit FALSE (faux).

    +

    Une condition comprend la plupart du temps un opérateur de comparaison. Les plus courants sont les suivants :

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Opérateur de comparaisonSignification
    ==égal à
    !=différent de
    >strictement supérieur à
    <strictement inférieur à
    >=supérieur ou égal à
    <=inférieur ou égal à
    +

    Voyons tout de suite un exemple :

    +
    library(questionr)
    +data(hdv2003)
    +d <- hdv2003
    +str(d$sexe == "Homme")
    +
     logi [1:2000] FALSE FALSE TRUE TRUE FALSE FALSE ...
    +

    Que s’est-il passé ? Nous avons fourni à R une condition qui signifie « la valeur de la variable sexe vaut “Homme” ». Et il nous a renvoyé un vecteur avec autant d’éléments qu’il y’a d’observations dans d, et dont la valeur est TRUE si l’observation correspond à un homme et FALSE dans les autres cas.

    +

    Prenons un autre exemple. On n’affichera cette fois que les premiers éléments de notre variable d’intérêt à l’aide de la fonction head :

    +
    head(d$age)
    +
    [1] 28 23 59 34 71 35
    +
    head(d$age > 40)
    +
    [1] FALSE FALSE  TRUE FALSE  TRUE FALSE
    +

    On voit bien ici qu’à chaque élément du vecteur d$age dont la valeur est supérieure à 40 correspond un élément TRUE dans le résultat de la condition.

    +

    On peut combiner ou modifier des conditions à l’aide des opérateurs logiques habituels :

    + + + + + + + + + + + + + + + + + + + +
    Opérateur logiqueSignification
    &et logique
    |ou logique
    !négation logique
    +

    Comment les utilise-t-on ? Voyons tout de suite des exemples. Supposons que je veuille déterminer quels sont dans mon échantillon les hommes ouvriers spécialisés :

    +
    d$sexe == "Homme" & d$qualif == "Ouvrier specialise"
    +

    Si je souhaite identifier les personnes qui bricolent ou qui font la cuisine :

    +
    d$bricol == "Oui" | d$cuisine == "Oui"
    +

    Si je souhaite isoler les femmes qui ont entre 20 et 34 ans :

    +
    d$sexe == "Femme" & d$age >= 20 & d$age <= 34
    +

    Si je souhaite récupérer les enquêtés qui ne sont pas cadres, on peut utiliser l’une des deux formes suivantes :

    +
    d$qualif != "Cadre"
    +!(d$qualif == "Cadre")
    +

    Lorsqu’on mélange « et » et « ou » il est nécessaire d’utiliser des parenthèses pour différencier les blocs. La condition suivante identifie les femmes qui sont soit cadre, soit employée :

    +
    d$sexe == "Femme" & (d$qualif == "Employe" | d$qualif == "Cadre")
    +

    L’opérateur %in% peut être très utile : il teste si une valeur fait partie des éléments d’un vecteur. Ainsi on pourrait remplacer la condition précédente par :

    +
    d$sexe == "Femme" & d$qualif %in% c("Employe", "Cadre")
    +

    Enfin, signalons qu’on peut utiliser les fonctions table ou summary pour avoir une idée du résultat de notre condition :

    +
    table(d$sexe)
    +
    
    +Homme Femme 
    +  899  1101 
    +
    table(d$sexe == "Homme")
    +
    
    +FALSE  TRUE 
    + 1101   899 
    +
    summary(d$sexe == "Homme")
    +
       Mode   FALSE    TRUE 
    +logical    1101     899 
    + +
    +
    + + + +

    Formules

    +
    -
    -

    Évolution du nombre moyen de bandes dessinées remises par élève

    -

    Nous disposons dans les rapports d’activités du nombre de bandes dessinées distribuées et du nombre d’élèves rencontrés. Nous pouvons donc regarder comment à évaluer au cours du temps le nombre moyen de bandes dessinées distribuées par élève, à savoir l’évolution du ratio bd_distribuees / eleves_rencontres.

    -
    ggplot(bd) +
    -  aes(x = date, y = bd_distribuees / eleves_rencontres, colour = type_etablissement) +
    -  geom_point()
    -

    -

    Comme on le voit sur la figure précédente, si l’on représente directement cet indicateur, nous obtenons plusieurs points pour chaque date. En effet, nous avons plusieurs observations par date et nous obtenons donc un point pour chacune de ces observations. Nous souhaiterions avoir la moyenne pour chaque date. Cela est possible en ayant recours à stat_weighted_mean de l’extension GGally. Pour que le calcul soit correct, il faut réaliser une moyenne pondérée par le dénominateur, à savoir ici le nombre d’élèves.

    -
    library(GGally)
    -ggplot(bd) +
    -  aes(
    -    x = date, y = bd_distribuees / eleves_rencontres,
    -    weight = eleves_rencontres, colour = type_etablissement
    -  ) +
    -  geom_point(stat = "weighted_mean")
    -

    -

    Le graphique sera plus lisible si nous représentons des lignes.

    -
    library(GGally)
    -ggplot(bd) +
    -  aes(
    -    x = date, y = bd_distribuees / eleves_rencontres,
    -    weight = eleves_rencontres, colour = type_etablissement
    -  ) +
    -  geom_line(stat = "weighted_mean")
    -

    -

    Pour améliorer la comparaison, il serait pertinent d’ajouter les intervalles de confiance sur le graphique. Cela peut être réalisé par exemple avec des rubans de couleur. Mais pour cela, il va falloir avoir recours à quelques astuces pour permettre le calcul de ces intervalles de confiance à la volée.

    -

    Tout d’abord, pour calculer l’intervalle de confiance d’un ratio de nombres entiers, nous pouvons utiliser un test de Poisson avec poisson.test auquel nous devons transmettre le numérateur et le dénominateur. Par exemple, pour 48 bandes dessinées distribuées à 27 élèves :

    -
    test <- poisson.test(48, 27)
    -test
    + + + +

    Ce chapitre vise à illustrer l’utilisation de la notation formule de R, qui désigne l’emploi de cette notation par l’expression formula. Cette notation est utilisée par de très nombreuses fonctions de R : on en a notamment vu plusieurs exemples dans le chapitre sur les graphiques bivariés, car l’extension ggplot2 se sert de cette notation dans ses paramètres facet_wrap et facet_grid.

    +

    Dans ce chapitre, on verra comment se servir de la notation formule dans deux contextes différents. D’une part, on verra que deux fonctions basiques de R se servent de cette notation pour produire des tableaux croisés et des statistiques bivariées. D’autre part, on verra que l’extension lattice se sert de cette notation pour créer des graphiques panelisés, dits graphiques à petits multiples.

    +

    Dans plusieurs autres chapitres, les opérations décrites ci-dessus sont effectuées avec les extensions dplyr d’une part, et ggplot2 d’autre part. On se servira également de ces extensions dans ce chapitre, de manière à mener une comparaison des différentes manières d’effectuer certaines opérations dans R, avec ou sans la notation formule :

    +
    library(dplyr)
    +library(ggplot2)
    +
    +

    Statistiques descriptives

    +

    Les premiers exemples de ce chapitre montrent l’utilisation de cette notation pour produire des tableaux croisés et des statistiques descriptives. Le jeu de données utilisé, hdv2003, a déjà été utilisé dans plusieurs chapitres, et font partie de l’extension questionr. Chargeons cette extension et le jeu de données hdv2003 :

    +
    library(questionr)
    +data(hdv2003)
    +

    Pour rappel, ce jeu de données contient des individus, leur âge, leur statut professionnel, et le nombre d’heures quotidiennes passées à regarder la télévision.

    +
    glimpse(hdv2003, 75)
    +
    Rows: 2,000
    +Columns: 20
    +$ id            <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ~
    +$ age           <int> 28, 23, 59, 34, 71, 35, 60, 47, 20, 28, 65, 47, 63,~
    +$ sexe          <fct> Femme, Femme, Homme, Homme, Femme, Femme, Femme, Ho~
    +$ nivetud       <fct> "Enseignement superieur y compris technique superie~
    +$ poids         <dbl> 2634.3982, 9738.3958, 3994.1025, 5731.6615, 4329.09~
    +$ occup         <fct> "Exerce une profession", "Etudiant, eleve", "Exerce~
    +$ qualif        <fct> Employe, NA, Technicien, Technicien, Employe, Emplo~
    +$ freres.soeurs <int> 8, 2, 2, 1, 0, 5, 1, 5, 4, 2, 3, 4, 1, 5, 2, 3, 4, ~
    +$ clso          <fct> Oui, Oui, Non, Non, Oui, Non, Oui, Non, Oui, Non, O~
    +$ relig         <fct> Ni croyance ni appartenance, Ni croyance ni apparte~
    +$ trav.imp      <fct> Peu important, NA, Aussi important que le reste, Mo~
    +$ trav.satisf   <fct> Insatisfaction, NA, Equilibre, Satisfaction, NA, Eq~
    +$ hard.rock     <fct> Non, Non, Non, Non, Non, Non, Non, Non, Non, Non, N~
    +$ lecture.bd    <fct> Non, Non, Non, Non, Non, Non, Non, Non, Non, Non, N~
    +$ peche.chasse  <fct> Non, Non, Non, Non, Non, Non, Oui, Oui, Non, Non, N~
    +$ cuisine       <fct> Oui, Non, Non, Oui, Non, Non, Oui, Oui, Non, Non, O~
    +$ bricol        <fct> Non, Non, Non, Oui, Non, Non, Non, Oui, Non, Non, O~
    +$ cinema        <fct> Non, Oui, Non, Oui, Non, Oui, Non, Non, Oui, Oui, O~
    +$ sport         <fct> Non, Oui, Oui, Oui, Non, Oui, Non, Non, Non, Oui, N~
    +$ heures.tv     <dbl> 0.0, 1.0, 0.0, 2.0, 3.0, 2.0, 2.9, 1.0, 2.0, 2.0, 1~
    +
    +

    Tableaux croisés avec xtabs

    +

    Utilisons, pour ce premier exemple, la variable occup du jeu de données hdv2003, qui correspond au statut professionnel des individus inclus dans l’échantillon. La fonction de base pour compter les individus par statut est la fonction table :

    +
    table(hdv2003$occup)
    
    -    Exact Poisson test
    +Exerce une profession               Chomeur 
    +                 1049                   134 
    +      Etudiant, eleve              Retraite 
    +                   94                   392 
    +  Retire des affaires              Au foyer 
    +                   77                   171 
    +        Autre inactif 
    +                   83 
    +

    Avec la fonction xtabs, le même résultat est produit à partir de la notation suivante :

    +
    xtabs(~occup, data = hdv2003)
    +
    occup
    +Exerce une profession               Chomeur 
    +                 1049                   134 
    +      Etudiant, eleve              Retraite 
    +                   94                   392 
    +  Retire des affaires              Au foyer 
    +                   77                   171 
    +        Autre inactif 
    +                   83 
    +

    Le premier argument est une formule, au sens où R entend cette expression. Le second argument, data, correspond au jeu de données auquel la formule doit être appliquée. On pourra se passer d’écrire explicitement cet argument dans les exemples suivants.

    +

    L’avantage de la fonction xtabs n’est pas évident dans ce premier exemple. En réalité, cette fonction devient utile lorsque l’on souhaite construire un ou plusieurs tableau(x) croisé(s). Par exemple, pour croiser la variable occup avec la variable sexe, une solution constiste à écrire :

    +
    with(hdv2003, table(occup, sexe))
    +
                           sexe
    +occup                   Homme Femme
    +  Exerce une profession   520   529
    +  Chomeur                  54    80
    +  Etudiant, eleve          48    46
    +  Retraite                208   184
    +  Retire des affaires      39    38
    +  Au foyer                  0   171
    +  Autre inactif            30    53
    +

    Ou alors, ce qui revient au même :

    +
    table(hdv2003$occup, hdv2003$sexe)
    +

    Avec xtabs, la même opération s’écrit de la manière suivante :

    +
    xtabs(~ occup + sexe, hdv2003)
    +
                           sexe
    +occup                   Homme Femme
    +  Exerce une profession   520   529
    +  Chomeur                  54    80
    +  Etudiant, eleve          48    46
    +  Retraite                208   184
    +  Retire des affaires      39    38
    +  Au foyer                  0   171
    +  Autre inactif            30    53
    +

    Cette écriture est plus courte que le code équivalent dans dplyr :

    +
    hdv2003 %>%
    +  group_by(occup) %>%
    +  summarise(
    +    Homme = sum(sexe == "Homme"),
    +    Femme = sum(sexe == "Femme")
    +  )
    +
    + +
    +

    Par contre, on pourra éventuellement utiliser count de dplyr. ATTENTION : le format du résultat ne sera pas le même.

    +
    hdv2003 %>%
    +  group_by(occup) %>%
    +  count(sexe)
    +
    + +
    +

    Pour un tableau croisé joliment mis en forme, on pourra avoir recours à tbl_cross de gtsummary.

    +
    library(gtsummary)
    +hdv2003 %>%
    +  tbl_cross(row = "occup", col = "sexe", percent = "row")
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique + sexe + Total
    HommeFemme
    occup
    Exerce une profession520 (50%)529 (50%)1 049 (100%)
    Chomeur54 (40%)80 (60%)134 (100%)
    Etudiant, eleve48 (51%)46 (49%)94 (100%)
    Retraite208 (53%)184 (47%)392 (100%)
    Retire des affaires39 (51%)38 (49%)77 (100%)
    Au foyer0 (0%)171 (100%)171 (100%)
    Autre inactif30 (36%)53 (64%)83 (100%)
    Total899 (45%)1 101 (55%)2 000 (100%)
    +

    De plus, xtabs permet de créer plusieurs tableaux croisés en une seule formule :

    +
    xtabs(~ occup + sexe + trav.imp, hdv2003)
    +
    , , trav.imp = Le plus important
     
    -
    -

    ggplotly (plotly)

    -

    Vous savez réaliser des graphiques avec ggplot2 ? Vous savez faire un graphique interactif. Rien de plus facile avec la fonction ggplotly de l’extension plotly.

    -

    Créons un graphique.

    -
    library(ggplot2)
    -p <- ggplot(iris) +
    -  aes(x = Petal.Width, y = Sepal.Length, color = Species) +
    -  geom_point()
    -

    Voici son rendu classique avec ggplot2.

    -
    p
    -

    -

    Et si on passe notre graphique à ggplotly. (N’hésitez pas à faire passer le curseur de votre souris sur le graphique.)

    -
    library(plotly)
    -ggplotly(p)
    -
    -

    Une documentation complète sur ggplotly est disponible sur https://plot.ly/ggplot2/.

    -
    -
    -

    ggvis

    -

    Il existe également une extension ggvis dédiée aux graphiques interactifs. Sa documentation complète est disponible sur https://ggvis.rstudio.com/.

    -
    + sexe +occup Homme Femme + Exerce une profession 13 16 + Chomeur 0 0 + Etudiant, eleve 0 0 + Retraite 0 0 + Retire des affaires 0 0 + Au foyer 0 0 + Autre inactif 0 0 -
    -
    +, , trav.imp = Aussi important que le reste + + sexe +occup Homme Femme + Exerce une profession 159 100 + Chomeur 0 0 + Etudiant, eleve 0 0 + Retraite 0 0 + Retire des affaires 0 0 + Au foyer 0 0 + Autre inactif 0 0 +, , trav.imp = Moins important que le reste + sexe +occup Homme Femme + Exerce une profession 328 380 + Chomeur 0 0 + Etudiant, eleve 0 0 + Retraite 0 0 + Retire des affaires 0 0 + Au foyer 0 0 + Autre inactif 0 0 -

    lattice : graphiques et formules

    +, , trav.imp = Peu important + sexe +occup Homme Femme + Exerce une profession 20 32 + Chomeur 0 0 + Etudiant, eleve 0 0 + Retraite 0 0 + Retire des affaires 0 0 + Au foyer 0 0 + Autre inactif 0 0
    +

    Cet exemple permet simplement de réaliser que la variable trav.imp, qui contient les réponses à une question portant sur l’importance du travail, n’a été mesurée (c’est-à-dire que la question n’a été posée) qu’aux seuls individus actifs de l’échantillon.

    - -
    - +
    +

    Statistiques bivariées avec aggregate

    +
    aggregate(heures.tv ~ sexe, mean, data = hdv2003)
    +
    + +
    +

    Ici, le premier argument est à nouveau une formule. Le second argument correspond à la statistique descriptive que l’on souhaite obtenir, et le dernier argument indique le jeu de données auquel appliquer les deux autres arguments. On peut d’ailleurs obtenir le même résultat en respectant de manière plus stricte l’ordre des arguments dans la syntaxe de la fonction aggregate :

    +
    aggregate(heures.tv ~ sexe, hdv2003, mean)
    +
    + +
    +

    Cette écriture est, à nouveau, plus compacte que le code équivalent dans dplyr, qui demande de spécifier le retrait des valeurs manquantes :

    +
    hdv2003 %>
    +  group_by(sexe) %>%
    +  summarise(heures.tv = mean(heures.tv, na.rm = TRUE))
    +

    À nouveau, on va pouvoir combiner plusieurs variables dans la formule que l’on passe à aggregate, ce qui va permettre d’obtenir la moyenne des heures de télévision quotidiennes par sexe et par statut professionnel :

    +
    aggregate(heures.tv ~ sexe + occup, hdv2003, mean)
    +
    + +
    +

    La même opération dplyr :

    +
    hdv2003 %>%
    +  group_by(occup, sexe) %>%
    +  summarise(heures.tv = mean(heures.tv, na.rm = TRUE))
    +

    La fonction aggregate permet bien sûr d’utiliser une autre fonction que la moyenne, comme dans cet exemple, suivi de son équivalent avec dplyr :

    +
    # âge médian par sexe et statut professionnel
    +aggregate(age ~ sexe + occup, hdv2003, median)
    +
    + +
    +
    # code équivalent avec l'extension 'dplyr'
    +hdv2003 %>%
    +  group_by(occup, sexe) %>%
    +  summarise(age = median(age, na.rm = TRUE))
    +

    Si, comme dans le cas de summarise, on souhaite passer des arguments supplémentaires à la fonction median, il suffit de les lister à la suite du nom de la fonction. Par exemple, on écrirait : aggregate(age ~ sexe + occup, hdv2003, median, na.rm = TRUE). Ceci étant, aggregate utilise par défaut l’option na.action = na.omit, donc il est bon de se rappeler que l’on peut désactiver cette option en utilisant l’option na.action = na.pass, ce qui permet éventuellement de conserver des lignes vides dans le tableau de résultat.

    +

    La fonction aggregate permet, par ailleurs, d’obtenir des résultats à plusieurs colonnes. Dans l’exemple ci-dessus, on illustre ce principe avec la fonction range, qui renvoie deux résultats (la valeur minimale et la valeur maximale de la variable, qui est toujours la variable age), chacun présentés dans une colonne :

    +
    aggregate(age ~ sexe + occup, hdv2003, range)
    +
        sexe                 occup age.1 age.2
    +1  Homme Exerce une profession    18    63
    +2  Femme Exerce une profession    18    67
    +3  Homme               Chomeur    18    63
    +4  Femme               Chomeur    18    63
    +5  Homme       Etudiant, eleve    18    34
    +6  Femme       Etudiant, eleve    18    35
    +7  Homme              Retraite    48    92
    +8  Femme              Retraite    41    96
    +9  Homme   Retire des affaires    57    91
    +10 Femme   Retire des affaires    57    93
    +11 Femme              Au foyer    22    90
    +12 Homme         Autre inactif    39    71
    +13 Femme         Autre inactif    19    97
    +

    Cette fonction ne peut pas être facilement écrite dans dplyr sans réécrire chacune des colonnes, ce que le bloc de code suivant illustre. On y gagne en lisibilité dans les intitulés de colonnes :

    +
    hdv2003 %>%
    +  group_by(occup, sexe) %>%
    +  summarise(
    +    min = min(age, na.rm = TRUE),
    +    max = max(age, na.rm = TRUE)
    +  )
    +

    Depuis la version 1.0.0 de dplyr, summarise accepte maintenant des fonctions pouvant renvoyer plusieurs valeurs, créant ainsi autant de lignes (voir https://www.tidyverse.org/blog/2020/03/dplyr-1-0-0-summarise/).

    +
    hdv2003 %>%
    +  group_by(occup, sexe) %>%
    +  summarise(age = range(age, na.rm = TRUE), type = c("min", "max"))
    +
    `summarise()` has grouped output by 'occup', 'sexe'. You can override using the `.groups` argument.
    +
    + +
    +

    On pourrait de même définir sa propre fonction et la passer à aggregate :

    +
    f <- function(x) c(mean = mean(x, na.rm = TRUE), sd = sd(x, na.rm = TRUE))
    +aggregate(age ~ sexe + occup, hdv2003, f)
    +
        sexe                 occup  age.mean    age.sd
    +1  Homme Exerce une profession 41.461538 10.438113
    +2  Femme Exerce une profession 40.710775 10.203864
    +3  Homme               Chomeur 38.925926 13.256329
    +4  Femme               Chomeur 38.012500 11.648321
    +5  Homme       Etudiant, eleve 20.895833  2.926326
    +6  Femme       Etudiant, eleve 21.586957  3.249452
    +7  Homme              Retraite 68.418269  8.018882
    +8  Femme              Retraite 69.510870  8.228957
    +9  Homme   Retire des affaires 71.179487  7.687556
    +10 Femme   Retire des affaires 73.789474  7.651737
    +11 Femme              Au foyer 50.730994 15.458412
    +12 Homme         Autre inactif 54.166667  6.597196
    +13 Femme         Autre inactif 59.962264 14.660206
    +

    Mais on réalisera vite une des limitations de aggregate dans ce cas-là : le tableau retourné ne contient pas 4 colonnes, mais 3 uniquement, ce que l’on peut vérifier à l’aide de dim ou str.

    +
    str(aggregate(age ~ sexe + occup, hdv2003, f))
    +
    'data.frame':   13 obs. of  3 variables:
    + $ sexe : Factor w/ 2 levels "Homme","Femme": 1 2 1 2 1 2 1 2 1 2 ...
    + $ occup: Factor w/ 7 levels "Exerce une profession",..: 1 1 2 2 3 3 4 4 5 5 ...
    + $ age  : num [1:13, 1:2] 41.5 40.7 38.9 38 20.9 ...
    +  ..- attr(*, "dimnames")=List of 2
    +  .. ..$ : NULL
    +  .. ..$ : chr [1:2] "mean" "sd"
    +

    Pour ce type d’opération, dans lequel on souhaite récupérer plusieurs variables calculées afin de travailler sur ces données agrégées soit dans le cadre d’opérations numériques soit de constructions graphiques, dplyr ou Hmisc s’avèrent plus commodes. Voici un exemple avec summarize de l’extension Hmisc :

    +
    library(Hmisc, quietly = TRUE)
    +with(hdv2003, summarize(age, llist(sexe, occup), f))
    +
    + +
    +

    Notons que Hmisc offre déjà une telle fonction (smean.sd), ce qui nous aurait épargné d’écrire notre propre fonction, f, et il en existe bien d’autres. Voici un exemple avec des intervalles de confiance estimés par bootstrap :

    +
    with(hdv2003, summarize(age, llist(sexe, occup), smean.cl.boot))
    +
    + +
    +

    Et un exemple avec dplyr.

    +
    hdv2003 %>%
    +  group_by(sexe, occup) %>%
    +  summarise(
    +    tibble(
    +      age_mean = mean(age, na.rm = TRUE),
    +      age_sd = sd(age, na.rm = TRUE)
    +    )
    +  )
    +
    `summarise()` has grouped output by 'sexe'. You can override using the `.groups` argument.
    +
    + +
    +

    Enfin, il est également possible d’utiliser plusieurs variables numériques à gauche de l’opérateur ~. En voici une illustration :

    +
    aggregate(cbind(age, poids) ~ sexe + occup, hdv2003, mean)
    +
    + +
    +
    +
    +
    +

    Panels graphiques avec lattice

    +

    Les exemples suivants montreront ensuite comment la notation formule peut servir à produire des graphiques par panel avec l’extension lattice.

    +
    library(lattice)
    +
    +

    L’extension lattice présente l’avantage d’être installée par défaut avec R. Il n’est donc pas nécessaire de l’installer préalablement.

    +
    +

    Chargeons les mêmes données que le chapitre sur les graphiques bivariés.

    +
    # charger l'extension lisant le format CSV
    +library(readr)
    +
    +# emplacement souhaité pour le jeu de données
    +file <- "data/debt.csv"
    +
    +# télécharger le jeu de données s'il n'existe pas
    +if (!file.exists(file)) {
    +  download.file("http://www.stat.cmu.edu/~cshalizi/uADA/13/hw/11/debt.csv",
    +    file,
    +    mode = "wb"
    +  )
    +}
    +
    +# charger les données dans l'objet 'debt'
    +debt <- read_csv(file)
    +
    New names:
    +* `` -> ...1
    +
    Rows: 1171 Columns: 5
    +
    -- Column specification ------------------------------------
    +Delimiter: ","
    +chr (1): Country
    +dbl (4): ...1, Year, growth, ratio
    +
    
    +i Use `spec()` to retrieve the full column specification for this data.
    +i Specify the column types or set `show_col_types = FALSE` to quiet this message.
    +

    Rejetons rapidement un coup d’oeil à ces données, qui sont structurées par pays (variable Country) et par année (variable Year). On y trouve deux variables, growth (le taux de croissance du produit intérieur brut réel), et ratio (le ratio entre la dette publique et le produit intérieur brut), ainsi qu’une première colonne vide, ne contenant que des numéros lignes, dont on va se débarrasser :

    +
    # inspection des données
    +glimpse(debt, 75)
    +
    Rows: 1,171
    +Columns: 5
    +$ ...1    <dbl> 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 15~
    +$ Country <chr> "Australia", "Australia", "Australia", "Australia", "Aust~
    +$ Year    <dbl> 1946, 1947, 1948, 1949, 1950, 1951, 1952, 1953, 1954, 195~
    +$ growth  <dbl> -3.5579515, 2.4594746, 6.4375341, 6.6119938, 6.9202012, 4~
    +$ ratio   <dbl> 190.41908, 177.32137, 148.92981, 125.82870, 109.80940, 87~
    +
    # suppression de la première colonne
    +debt <- debt[, -1]
    +
    +

    Visualisation bivariée

    +

    Le même graphique s’écrit de la manière suivante avec l’extension lattice :

    +
    xyplot(growth ~ Year, data = debt)
    +

    +
    +
    +

    Visualisation par petits multiples +

    +

    Appliquons désormais la même visualisation par petits multiples que vue dans le chapitre :

    +
    xyplot(growth ~ Year | Country, data = debt)
    +

    +

    Enfin, rajoutons quelques options au graphique, afin de montrer comment l’extension lattice fonctionne :

    +
    xyplot(growth ~ Year | Country,
    +  type = c("o", "l"),
    +  main = "Données Reinhart et Rogoff corrigées, 1946-2009",
    +  ylab = "Taux de croissance du PIB",
    +  xlab = NULL,
    +  data = debt
    +)
    +

    - -

    Bien que l’on ait fait le choix de présenter principalement l’extension ggplot2 plutôt que l’extension lattice, celle-ci reste un excellent choix pour la visualisation, notamment, de panels et de séries temporelles. On trouve de très beaux exemples d’utilisation de lattice en ligne, mais un peu moins de documentation, et beaucoup moins d’extensions, que pour ggplot2.

    -

    On peut trouver en ligne un support de cours détaillé (en anglais) de Deepayan Sarkar (https://www.isid.ac.in/~deepayan/R-tutorials/labs/04_lattice_lab.pdf), également l’auteur de l’ouvrage Lattice: Multivariate Data Visualization with R (http://lmdvr.r-forge.r-project.org/).

    -
    -

    Les bases des graphiques lattice -

    -

    R dispose de deux principaux systèmes graphiques : un système de base contrôlé par le package graphics et le système grid, sur lequel se basent à la fois les packages lattice et ggplot2. Ce système fournit les mêmes fonctionnalités de base que graphics mais offre une gestion de l’arrangement des objets graphiques plus développée, et surtout la possibilité d’utiliser ce que l’on appelle des graphiques en treillis. De plus, les graphiques peuvent être mis à jour très simplement, disposent de thèmes de couleur pré-définis, et offrent un certain degré d’interactivité, avec ou sans le package plotly. Enfin, la syntaxe est plus homogène et grandement simplifié, grâce à l’usage de formules.

    -
    -

    De l’intérêt des formules R

    -

    Voici par exemple comment afficher la courbe de densité (i.e., la version continue et “lissée” d’un histogramme) de deux séries d’observations définies par les niveaux du facteur supp dans le data frame ToothGrowth, disponible dans les exemples de base de R. Notons que l’on souhaite également faire apparaître les distributions univariées, un peu à l’image de ce que fournit rug. Or cette fonction ne permet pas d’exploiter une variable de groupement, donc il sera nécessaire de gérer tout cela manuellement. Voici les instructions permettant de générer le graphique désiré :

    -
    plot(density(ToothGrowth$len[ToothGrowth$supp == "OJ"]), main = "", xlab = "len", las = 1, lwd = 2, col = "coral")
    -lines(density(ToothGrowth$len[ToothGrowth$supp == "VC"]), lwd = 2, col = "cornflowerblue")
    -points(
    -  x = ToothGrowth$len[ToothGrowth$supp == "OJ"],
    -  y = runif(length(ToothGrowth$len[ToothGrowth$supp == "OJ"]),
    -    min = -0.001, max = 0.001
    -  ), col = "coral"
    -)
    -points(
    -  x = ToothGrowth$len[ToothGrowth$supp == "VC"],
    -  y = runif(length(ToothGrowth$len[ToothGrowth$supp == "VC"]),
    -    min = -0.001, max = 0.001
    -  ), col = "cornflowerblue"
    -)
    -legend("top", levels(ToothGrowth$supp), col = c("coral", "cornflowerblue"), lty = 1, bty = "n")
    -
    -Courbes de densité avec les graphiques de base -

    Il y a plusieurs points à retenir dans les instructions ci-dessus : (1) il est nécessaire de définir les deux courbes de densité et les deux distributions univariées, en prenant garde à bien indiquer comment sélectionner les observations (OJ ou VC) en préfixant systématiquement le nom des variables par le nom du data frame ; (2) la définition des couleurs se fait manuellement et si l’on souhaite changer de thème de couleur, il faudra mettre à jour l’ensemble des instructions, en prenant garde à ce que la lgende reste synchronisée avec les courbes de densité et les nuages de points ; et, bien entendu, (3) il est nécessaire de gérer soi-même la légende, ce qui signifie se rappeler les couleurs et l’ordre des niveaux du facteur considéré, ainsi que les axes graphiques dans le cas où l’on souhaite les maintenir coordonnés sur plusieurs panneaux graphiques.

    -

    Voici le même graphique avec lattice :

    -
    library(lattice)
    -densityplot(~len, data = ToothGrowth, group = supp, auto.key = TRUE)
    -
    -Courbes de densité avec le package lattice -

    Avec ggplot2, cela donnerait :

    -
    library(ggplot2)
    -ggplot(data = ToothGrowth, aes(x = len, color = supp)) +
    -  geom_line(stat = "density") +
    -  geom_rug()
    -
    -Courbes de densité avec le package ggplot2 -

    Clairement, on gagne en nombre d’instructions à taper dans la console et en clarté d’expression également, grâce notamment à l’usage de formules permettant de décrire la relation entre chacune des variables utilisées pour construire la représentation graphique.

    -
    +
    +

    Spécifier des modèles

    +

    Les formules R

    -

    Les formules utilisées dans le système lattice sont presque identiques à celles retouvées dans les modèles d’analyse de variance (aov) ou de régression (lm). En réalité, la notation par formule qu’utilise R est celle proposée par Wilkinson et coll. dans les années 70 pour schématiser la relation entre plusieurs variables dans un plan d’expérience. Plus spécifiquement, l’idée revient à exprimer une relation fonctionnelle, symbolise´e par l’opérateur ~, entre une variable re´ponse y et une ou plusieurs variables explicatives. Disons, pour simplifier, que y est une variable numérique, de même que x, et que a et b sont des variables catégorielles (des facteurs dans le langage R). Voici les principales relations auxquelles on peut s’intéresser dans un modèle statistique linéaire :

    +

    En réalité, la notation par formule qu’utilise R est celle proposée par Wilkinson et al. dans les années 70 pour schématiser la relation entre plusieurs variables dans un plan d’expérience. Plus spécifiquement, l’idée revient à exprimer une relation fonctionnelle, symbolise´e par l’opérateur ~, entre une variable re´ponse y et une ou plusieurs variables explicatives. Disons, pour simplifier, que y est une variable d’intérêt (numérique ou facteur selon le type de modèle), x une variable numérique et que a et b sont des variables catégorielles (des facteurs dans le langage R). Voici les principales relations auxquelles on peut s’intéresser dans un modèle statistique :

    • -y ~ x : régression linéaire simple,
    • +y ~ x : régression simple,
    • y ~ x + 0 : idem avec suppression du terme d’ordonnée à l’origine,
    • -y ~ a + b : ANOVA avec deux effets principaux,
    • +y ~ a + b : régresse avec deux effets principaux indépendants,
    • y ~ a * b : idem avec interaction (équivalent à 1 + a + b + a:b),
    • y ~ a / b : idem en considérant une relation d’emboîtement (équivalent à 1 + a + b + a %in% b).
    -

    Un exemple typique d’utilisation pour un modèle d’ANOVA à trois facteurs est donné ci-dessous :

    -
    fm <- y ~ a * b * c # modèle de base (A, B, C, AB, AC, BC, ABC)
    -mod1 <- aov(fm, data = dfrm) # estimation des parame`tres du mode`le
    -update(mod1, . ~ . - a:b:c) # suppression de l'interaction ABC
    -

    Quant on y réfléchit un peu, les relations ci-dessus peuvent très bien s’appliquer au cas de la composition graphique : y ~ x signifie dans ce cas que l’on souhaite représenter l’évolution de y en fonction de x. En d’autres termes, on s’intéresse à un nuage de dispersion. Le package lattice ajoute les notations suivantes :

    -
      -
    • -~ x : dans le cas où l’on ne décrit qu’une seule variable (i.e., sa distribution),
    • -
    • -a | b : dans le cas où l’on considère la variable a, conditionnellement à la variable b, c’est-à-dire les niveaux de a pour chacun des niveaux de b (ce qui revient à l’interaction a:b citée ci-dessus).
    • -
    -

    Cette dernière notation se révèlera être très utile dans le cas des représentations graphiques conditionnelles, par exemple lorsque l’on souhaite afficher la distribution d’une variable numérique dans différents groupes d’individus définis par les niveaux d’une variable catégorielle, ou lorsque l’on souhaite surligner d’une couleur différentes les points d’un diagramme de dispersion selon la valeur prise par une troisième variable.

    -

    Les formules R sont omniprésentes dans les modèles statistiques, dans les graphiques, mais également dans certaines commandes d’agrégation. Au bout du compte, avec une même formule il est possible de calculer des moyennes de groupes, réaliser une ANOVA et construire la représentation graphique associée. En voici un exemple :

    -
    fm <- len ~ supp
    -m <- aggregate(fm, data = ToothGrowth, mean)
    -summary(aov(fm, data = ToothGrowth))
    -bwplot(fm, data = ToothGrowth)
    -
    -
    -
    -

    Principaux types de graphiques avec lattice (et ggplot2)

    -

    Même si le package lattice fournit moins de commandes que ggplot2, il n’en demeure pas moins qu’il est tout aussi facile de réaliser des représentations graphiques simples en un tour de main. Voici quelques exemples de représentations graphiques uni- et bivariées. Les données d’illustration sont les mêmes que celles utilisées plus haut (ToothGrowth): il s’agit d’une expérience de biologie dans laquelle on s’intéresse à la croissance des odontoblastes de cochons d’inde quantifiée par leur longueur (variable len) lorsqu’on administre à ces derniers de la vitamine C soit sous forme d’acide ascorbique soit sous forme de jus d’orange (supp, OJ = jus d’orange), à différentes doses (dose, en mg).

    -
    -

    Histogramme

    -

    Un histogramme d’effectifs se construit avec histogram. Puisqu’il s’agit de décrire une seule variable, ou sa distribution plus précisément, la formule à employer ne contient pas de variable à gauche du symbole ~ et l’on se contente d’écrire la variable à résumer à droite dans la formule :

    -
    histogram(~len, data = ToothGrowth, type = "count")
    -
    -Histogramme d’effectifs -

    L’option type = "count" permet de forcer la représentation sous forme d’effectifs puisque, par défaut, c’est la densité qui est représentée. La formulation équivalente sous ggplot2 serait :

    -
    ggplot(data = ToothGrowth, aes(x = len)) +
    -  geom_histogram(binwidth = 5)
    -

    (Ou alors qplot(x = len, data = ToothGrowth, geom = "histogram", binwidth = 5).)

    -

    En ajoutant une facette pour tenir compte de la variable supp, cela donne :

    -
    histogram(~ len | supp, data = ToothGrowth, breaks = seq(0, 40, by = 5))
    -
    -Histogramme d’effectifs conditionné sur une variable catégorielle -

    Avec ggplot2, les facettes sont gérées grâce aux commandes facet_grid et facet_wrap.

    -
    -
    -

    Courbe de densité

    -

    Une courbe de densité se construit à l’aide de densityplot et la syntaxe est strictement identique à celle de histogram, à l’option type= près.

    -
    densityplot(~len, data = ToothGrowth, plot.points = FALSE, from = 0, to = 40)
    -
    -Courbe de densité -

    Il est possible de régler le paramètre de lissage à l’aide de l’option bw= : des valeurs plus élevées résultent en une courbe beaucoup plus lissée (essayez avec bw = 10 !) et donc beaucoup moins sensible aux variations locales de la densité.

    -

    À ce stade, on peut en profiter pour discuter les options de conditionnement sur une variable catégorielle et la manière de gérer la présentation graphique : dans le cas d’un histogramme, il est délicat de superposer deux distributions ou plus sur le même graphique, même en ajoutant de la transparence, d’où l’idée de représenter les distributions dans des panneaux graphiques séparés. C’est ce qu’on a réalisé en indiquant que l’on souhaitait décrire la variable len conditionnellement aux valeurs prises par supp (~ len | supp). Dans ce cas, l’opérateur | invoque une facette et un decoupage en autant de panneaux graphiques qu’il y a de valeurs uniques dans la variable supp. Une autre approche consiste à utiliser l’option groups=, et dans ce cas les différentes distributions seront affichées dans le même panneau graphique. Dans le cas d’une courbe de densité, cela revient à les superposer sur la même fenêtre graphique, avec un système de coordonnées unique. Les deux options de conditionnement peuvent être combinées naturellement.

    -

    Voici un exemple de graphique conditionnel un peu plus élaboré :

    -
    densityplot(~len,
    -  data = ToothGrowth, groups = supp, auto.key = TRUE, xlab = "len",
    -  par.settings = list(superpose.line = list(col = c("coral", "cornflowerblue")))
    -)
    -
    -Courbe de densité conditionnelle -

    Au passage, on en a profité pour modifier le thème de couleur. Notez qu’en utilisant par.settings=, lattice se charge de coordonner les couleurs de la légende (auto.key = TRUE) avec celle des éléments graphiques correspondants.

    -

    L’équivalent sous ggplot2 revient à peu près à l’instruction suivante :

    -
    ggplot(data = ToothGrowth) +
    -  aes(x = len, colour = supp) +
    -  geom_line(stat = "density") +
    -  expand_limits(x = c(0, 40)) +
    -  scale_colour_manual("", values = c("coral", "cornflowerblue")) +
    -  theme_bw()
    -

    -
    -
    -

    Diagramme en barres

    -

    Les diagrammes en barres peuvent avantageusement être remplacés par des diagrammes en points, tels que les diagrammes de Cleveland (cf. plus loin), mais en attendant voici comment en réaliser un à l’aide de barchart à partir de données agrégées :

    -
    library(latticeExtra, quietly = TRUE)
    -m <- aggregate(len ~ supp + dose, data = ToothGrowth, mean)
    -barchart(len ~ dose, data = m, groups = supp, horizontal = FALSE, auto.key = TRUE, par.settings = ggplot2like())
    -
    -Diagramme en barres -

    Notons que par.settings= permet non seulement de fournir des options additionnelles pour contrôler le rendu des éléments graphiques (couleur, type de ligne ou de symboles, etc.) mais également d’utiliser des thèmes graphiques disponibles dans le package latticeExtra.

    -
    -
    -

    Diagramme de type boîtes à moustaches

    -

    Les diagrammes en forme de boîtes à moustaches sont obtenus à l’aide de la commande bwplot. Voici un exemple d’utilisation :

    -
    bwplot(len ~ supp, data = ToothGrowth, pch = "|")
    -
    -Diagramme en forme de boîtes à moustaches -

    L’option pch= permet de contrôler la manière dont la médiane est figurée dans la boîte. Par défaut il s’agit d’un simple point, mais si l’on souhaite utiliser les représentations plus classiques, telles que celles trouvées dans boxplot ou geom_boxplot, il suffit de suivre l’exemple ci-dessus. Notons que dans le cas de cette représentation graphique, le conditionnement sur la variable supp est d’emblée réalisé par l’utilisation d’une formule invoquant la variable de conditionnement à droite de l’opérateur ~.

    -
    -
    -

    Diagramme en points

    -

    Le même type de représentation graphique peut être obtenu en utilisant directement les données individuelles, et non leur résumé en cinq points (tel que fournit par summary et exploité par bwplot). Dans ce cas, il s’agit de la commande dotplot, qui permet de construire des diagrammes de Cleveland (moyenne ou effectif total calculé pour une variable en fonction des niveaux d’une autre variable) ou, dans le cas où la variable à résumer consiste en une série de mesures individuelles numériques, des diagrammes de dispersion. Voici une illustration pour ce dernier cas de figure :

    -
    dotplot(len ~ supp, ToothGrowth, jitter.x = TRUE)
    -
    -Diagramme en points -
    -
    -
    -

    Diagramme de dispersion

    -

    Enfin, un diagramme de dispersion est construit à l’aide de la commande xyplot.

    -
    xyplot(len ~ dose, ToothGrowth, type = c("p", "smooth"))
    -
    -Diagramme de dispersion -

    Même si l’exemple ne s’y prête guère, on en a profité pour ajouter une courbe lowess de régression afin d’indiquer la tendance de covariation entre les deux variables numériques. L’aide en ligne pour xyplot n’est pas très utile dans ce cas, et il faut en fait aller regarder les options de personnalisation disponibles dans la sous-fonction correspondante : panel.xyplot.

    -
    +

    L’opérateur | est quant à lui utilisé par l’extension lme4 dans le cadre de modèles mixtes avec effets aléatoires.

    +

    Voir le chapitre dédié à la régression logistique pour des exemples de modèles multivariés et le chapitre dédié aux effets d’interaction pour plus de détails sur cette notion.

    - -
    -
    - - - -

    Cartes

    -
    - - -
    -

    Pour une présentation de l’analyse spatiale sous R, se référer au chapitre dédié.

    +
    +

    Pour aller plus loin

    +

    Comme vient de le voir dans ce chapitre, la notation formule apparaît çà et là dans les différentes fonctions de R est de ses extensions. Il est par conséquent utile d’en connaître les rudiments, et en particulier les opérateurs ~ (tilde) et +, ne serait-ce que pour pouvoir se servir des différentes fonctions présentées sur cette page. Le chapitre lattice et les formules fournit plus de détails sur ces aspects.

    +

    La notation formule devient cruciale dès que l’on souhaite rédiger des modèles : la formule y ~ x, par exemple, qui est équivalente à la formule y ~ 1 + x, correspond à l’équation mathématique Y = a + bX. On trouvera de nombreux exemples d’usage de cette notation dans les chapitres consacrés, notamment, à la régression linéaire ou à la régression logistique.

    +

    De la même manière, l’opérateur | (pipe) utilisé par l’extension lattice joue aussi un rôle très important dans la rédaction de modèles multi-niveaux, où il sert à indiquer les variables à pentes ou à coefficients aléatoires. Ces modèles sont présentés dans un chapitre dédié.

    -

    Il existe de multiple approches pour réaliser des cartes sous R, y compris avec ggplot2, mais également de manière native avec les extensions sp et sf. Il existe également des extensions apportant des fonctionalités additionnelles comme ggmap, mapview ou encore tmap.

    -

    Pour une introduction succincte en français, on pourra se référer à la section 5.4 du spport de cours Logiciel R et programmation d’Ewan Gallic.

    -

    Voir également l’excellente présentation Données géospatiales et cartographie avec R de Nicolas Roelandt : https://roelandtn.frama.io/slides/2090628_meetup_Raddict_datageo.html.

    -

    En complément (en anglais), la vignette Plotting Simple Features de l’extension sf ou encore le chapitre Making maps with R de l’ouvrage Geocomputation with R de Robin Lovelace, Jakub Nowosad et Jannes Muenchow.

    -

    On pourra également se référer à l’excellent package cartography et à son site dédié : https://github.com/riatelab/cartography/ (pour une présentation en vidéo et en français : https://youtu.be/OI3_AOg6pfc).

    -
    +
    -

    Autres extensions graphiques

    +

    Structures conditionnelles

    - -

    Pour trouver l’inspiration et des exemples de code, rien ne vaut l’excellent site https://www.r-graph-gallery.com/.

    -
    -

    GGally

    -

    L’extension GGally, déjà abordée dans d’autres chapitres, fournit plusieurs fonctions graphiques d’exploration des résultats d’un modèle ou des relations entre variables.

    -
    reg <- lm(Sepal.Length ~ Sepal.Width + Petal.Length + Petal.Width, data = iris)
    -library(GGally)
    -ggcoef(reg)
    -

    -
    data(tips, package = "reshape")
    -ggpairs(tips)
    -

    -

    Plus d’information : https://ggobi.github.io/ggally/

    +
    +

    La version originale de ce chapitre a été écrite par Ewen Gallic dans le cadre de son support de cours d’Ewen Gallic intitulé Logiciel R et programmation, chapitre 4 Boucles et calculs vectoriels.

    -
    -

    ggpubr

    -

    L’extension ggpubr fournit plusieurs fonctions pour produire clés en main différents graphiques bivariés avec une mise en forme allégée.

    -
    library(ggpubr)
    -data("ToothGrowth")
    -df <- ToothGrowth
    -ggboxplot(df,
    -  x = "dose", y = "len",
    -  color = "dose", palette = c("#00AFBB", "#E7B800", "#FC4E07"),
    -  add = "jitter", shape = "dose"
    -)
    -

    -
    data("mtcars")
    -dfm <- mtcars
    -# Convert the cyl variable to a factor
    -dfm$cyl <- as.factor(dfm$cyl)
    -# Add the name colums
    -dfm$name <- rownames(dfm)
    -# Calculate the z-score of the mpg data
    -dfm$mpg_z <- (dfm$mpg - mean(dfm$mpg)) / sd(dfm$mpg)
    -dfm$mpg_grp <- factor(ifelse(dfm$mpg_z < 0, "low", "high"),
    -  levels = c("low", "high")
    -)
    -
    -ggbarplot(dfm,
    -  x = "name", y = "mpg_z",
    -  fill = "mpg_grp", # change fill color by mpg_level
    -  color = "white", # Set bar border colors to white
    -  palette = "jco", # jco journal color palett. see ?ggpar
    -  sort.val = "asc", # Sort the value in ascending order
    -  sort.by.groups = FALSE, # Don't sort inside each group
    -  x.text.angle = 90, # Rotate vertically x axis texts
    -  ylab = "MPG z-score",
    -  xlab = FALSE,
    -  legend.title = "MPG Group"
    -)
    -

    -
    ggdotchart(dfm,
    -  x = "name", y = "mpg_z",
    -  color = "cyl", # Color by groups
    -  palette = c("#00AFBB", "#E7B800", "#FC4E07"), # Custom color palette
    -  sorting = "descending", # Sort value in descending order
    -  add = "segments", # Add segments from y = 0 to dots
    -  add.params = list(color = "lightgray", size = 2), # Change segment color and size
    -  group = "cyl", # Order by groups
    -  dot.size = 6, # Large dot size
    -  label = round(dfm$mpg_z, 1), # Add mpg values as dot labels
    -  font.label = list(
    -    color = "white", size = 9,
    -    vjust = 0.5
    -  ), # Adjust label parameters
    -  ggtheme = theme_pubr() # ggplot2 theme
    -) +
    -  geom_hline(yintercept = 0, linetype = 2, color = "lightgray")
    -

    -

    Plus d’informations : https://rpkgs.datanovia.com/ggpubr/

    +

    Il existe deux sortes de boucles dans R. Celles pour lesquelles les itérations continuent tant qu’une condition n’est pas invalidée (while), et celles pour lesquelles le nombre d’itérations est défini au moment de lancer la boucle (for).

    +

    Avant de présenter chacune de ces fonctions, il est nécessaire de préciser que les boucles ne sont pas le point fort de R. Dès que l’on souhaite appliquer une fonction à chaque élément d’un vecteur, et/ou que le résultat de chaque itération ne dépend pas de l’itération précédente, il est préférable de vectoriser les calculs (voir le chapitre sur la vectorisation).

    +
    +

    Les boucles avec while

    +

    Quand on souhaite répéter un calcul tant qu’une condition est satisfaite, on utilise la fonction while, avec la syntaxte suivante :

    +
    while (condition) {
    +  instruction
    +}
    +

    avec condition une valeur logique (TRUE ou FALSE), et instruction du code, qui peut être entouré d’accolades si on souhaite évaluer plusieurs instructions.

    +

    Le code instruction sera répété tant que condition est vrai. Prenons l’exemple d’une plante qui mesure 10 centimètres et qui va grandir de 10 % tous les ans jusqu’à atteindre 2 mètres.

    +
    taille <- 0.10
    +duree <- 0
    +while (taille < 2) {
    +  taille <- taille * 1.1
    +  duree <- duree + 1
    +}
    +message(glue::glue("La plante a atteint {round(taille, 1)} mètres en {duree} années."))
    +
    La plante a atteint 2.1 mètres en 32 années.
    -
    -

    ggdendro

    -

    L’extension ggendro avec sa fonction ggdendrogram permet de représenter facilement des dendrogrammes avec ggplot2.

    -
    library(ggplot2)
    -library(ggdendro)
    -hc <- hclust(dist(USArrests), "ave")
    -hcdata <- dendro_data(hc, type = "rectangle")
    -ggplot() +
    -  geom_segment(data = segment(hcdata), aes(x = x, y = y, xend = xend, yend = yend)) +
    -  geom_text(data = label(hcdata), aes(x = x, y = y, label = label, hjust = 0), size = 3) +
    -  coord_flip() +
    -  scale_y_reverse(expand = c(0.2, 0))
    -

    -
    ### demonstrate plotting directly from object class hclust
    -ggdendrogram(hc)
    -

    -
    ggdendrogram(hc, rotate = TRUE)
    -

    -

    Plus d’informations : https://cran.r-project.org/web/packages/ggdendro/vignettes/ggdendro.html

    +
    +

    Les boucles avec for

    +

    Quand on connaît le nombre d’itérations à l’avance, on peut utiliser la boucle for. La syntaxe est la suivante :

    +
    for (variable in vector) {
    +  instruction
    +}
    +

    avec variable le nom d’une variable locale à la boucle for, vector un vecteur à n éléments définissant les valeurs que prendra variable pour chacun des n tours, et instruction le code à exécuter à chaque itération.

    +

    On peut utiliser for pour remplir les éléments d’une liste, ou d’un vecteur.

    +
    # Mauvaise manière
    +resultat <- NULL
    +for (i in 1:3) {
    +  resultat[i] <- i
    +}
    +resultat
    +
    [1] 1 2 3
    +

    À chaque itération, R doit trouver le vecteur de destination en mémoire, créer un nouveau vecteur qui permettra de contenir plus de données, copier données depuis l’ancien vecteur pour les insérer dans le nouveau, et enfin supprimer l’ancien vecteur (Ross, 2014). C’est une opération coûteuse en temps. Un moyen de rendre cette allocation plus efficace est de créer a priori le vecteur ou la liste en le remplissant avec des données manquantes. Ainsi, R n’aura pas besoin de ré-allouer la mémoire à chaque itération.

    +
    # Manière plus économique
    +resultat <- rep(NA, 3)
    +for (i in 1:3) {
    +  resultat[i] <- i
    +}
    +resultat
    +
    [1] 1 2 3
    -
    -

    circlize

    -

    L’extension circlize est l’extension de référence quand il s’agit de représentations circulaires. Un ouvrage entier lui est dédié : https://jokergoo.github.io/circlize_book/book/.

    -

    Voici un exemple issu de https://www.data-to-viz.com/story/AdjacencyMatrix.html.

    -
    library(tidyverse)
    -
    -# Load data
    -data <- read.table("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/13_AdjacencyDirectedWeighted.csv", header = TRUE)
    -# short names
    -colnames(data) <- c("Africa", "East Asia", "Europe", "Latin Ame.", "North Ame.", "Oceania", "South Asia", "South East Asia", "Soviet Union", "West.Asia")
    -rownames(data) <- colnames(data)
    -
    -# I need a long format
    -data_long <- data %>%
    -  rownames_to_column() %>%
    -  gather(key = "key", value = "value", -rowname)
    -
    -
    -library(circlize)
    -# parameters
    -circos.clear()
    -circos.par(start.degree = 90, gap.degree = 4, track.margin = c(-0.1, 0.1), points.overflow.warning = FALSE)
    -par(mar = rep(0, 4))
    -
    -# color palette
    -library(viridis)
    -mycolor <- viridis(10, alpha = 1, begin = 0, end = 1, option = "D")
    -mycolor <- mycolor[sample(1:10)]
    -
    -# Base plot
    -chordDiagram(
    -  x = data_long,
    -  grid.col = mycolor,
    -  transparency = 0.25,
    -  directional = 1,
    -  direction.type = c("arrows", "diffHeight"),
    -  diffHeight = -0.04,
    -  annotationTrack = "grid",
    -  annotationTrackHeight = c(0.05, 0.1),
    -  link.arr.type = "big.arrow",
    -  link.sort = TRUE,
    -  link.largest.ontop = TRUE
    -)
    -
    -# Add text and axis
    -circos.trackPlotRegion(
    -  track.index = 1,
    -  bg.border = NA,
    -  panel.fun = function(x, y) {
    -    xlim <- get.cell.meta.data("xlim")
    -    sector.index <- get.cell.meta.data("sector.index")
    -
    -    # Add names to the sector.
    -    circos.text(
    -      x = mean(xlim),
    -      y = 3.2,
    -      labels = sector.index,
    -      facing = "bending",
    -      cex = 0.8
    -    )
    -
    -    # Add graduation on axis
    -    circos.axis(
    -      h = "top",
    -      major.at = seq(from = 0, to = xlim[2], by = ifelse(test = xlim[2] > 10, yes = 2, no = 1)),
    -      minor.ticks = 1,
    -      major.tick.percentage = 0.5,
    -      labels.niceFacing = FALSE
    -    )
    -  }
    -)
    -
    `major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    -`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    -`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    -`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    -`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    -`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    -`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    -`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    -`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    -`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.
    -

    +
    +

    Les conditions

    +

    On peut soumettre l’exécution de codes en R à conditions que certaines conditions soient honorées.

    +
    +

    Les instructions if … else

    +

    Les instructions if et else fournissent un moyen d’exécuter du code si une condition est respectée ou non. La syntaxe prend deux formes :

    +
    # Première forme (pas de code si condition == FALSE)
    +if (condition) {instruction si vrai}
    +
    +# Seconde forme
    +if (condition) {instruction si vrai} else {instruction si faux}
    +

    avec condition un logique, instruction si vrai le code à exécuter si la condition est vérifiée et instruction si faux le code à exécuter si la condition n’est pas remplie. À nouveau, on peut avoir recours aux accolades pour créer des regroupements.

    +
    # Simple condition
    +x <- 2
    +if (x == 2) print("Hello")
    +
    [1] "Hello"
    +
    x <- 3
    +if (x == 2) print("Hello")
    +
    +# Avec des instructions dans le cas contraire
    +if (x == 2) print("Hello") else print("x est différent de 2")
    +
    [1] "x est différent de 2"
    +
    if (x == 2) {
    +  print("Hello")
    +} else {
    +  x <- x - 1
    +  print(paste0("La nouvelle valeur de x : ", x))
    +}
    +
    [1] "La nouvelle valeur de x : 2"
    +
    +

    Attention, lorsque l’on fait des regroupements et qu’on utilise la structure if et else, il est nécessaire d’écrire le mot else sur la même ligne que la parenthèse fermante du groupe d’instructions à réaliser dans le cas où la condition du if est vérifiée.

    -
    -

    Diagrammes de Sankey

    -

    Les diagrammes de Sankey sont un type alternatif de représentation de flux. Voici un premier exemple, qui reprend les données utilisées pour le diagramme circulaire précédent, avec la fonction sankeyNetwork de l’extension sankeyNetwork.

    -
    # Package
    -library(networkD3)
    -
    -# I need a long format
    -data_long <- data %>%
    -  rownames_to_column() %>%
    -  gather(key = "key", value = "value", -rowname) %>%
    -  filter(value > 0)
    -colnames(data_long) <- c("source", "target", "value")
    -data_long$target <- paste(data_long$target, " ", sep = "")
    -
    -# From these flows we need to create a node data frame: it lists every entities involved in the flow
    -nodes <- data.frame(name = c(as.character(data_long$source), as.character(data_long$target)) %>% unique())
    -
    -# With networkD3, connection must be provided using id, not using real name like in the links dataframe.. So we need to reformat it.
    -data_long$IDsource <- match(data_long$source, nodes$name) - 1
    -data_long$IDtarget <- match(data_long$target, nodes$name) - 1
    -
    -# prepare colour scale
    -ColourScal <- 'd3.scaleOrdinal() .range(["#FDE725FF","#B4DE2CFF","#6DCD59FF","#35B779FF","#1F9E89FF","#26828EFF","#31688EFF","#3E4A89FF","#482878FF","#440154FF"])'
    -
    -# Make the Network
    -sankeyNetwork(
    -  Links = data_long, Nodes = nodes,
    -  Source = "IDsource", Target = "IDtarget",
    -  Value = "value", NodeID = "name",
    -  sinksRight = FALSE, colourScale = ColourScal, nodeWidth = 40, fontSize = 13, nodePadding = 20
    -)
    -
    -

    Une alternative possible est fournie par l’extension ggalluvial et ses géométries geom_alluvium et geom_stratum.

    -
    library(ggalluvial)
    -ggplot(data = as.data.frame(Titanic)) +
    -  aes(axis1 = Class, axis2 = Sex, axis3 = Age, y = Freq) +
    -  scale_x_discrete(limits = c("Class", "Sex", "Age"), expand = c(.1, .05)) +
    -  xlab("Demographic") +
    -  geom_alluvium(aes(fill = Survived)) +
    -  geom_stratum() +
    -  geom_text(stat = "stratum", infer.label = TRUE) +
    -  theme_minimal()
    -
    Warning: The parameter `infer.label` is deprecated.
    -Use `aes(label = after_stat(stratum))`.
    -

    -

    Mentionnons également l’extension riverplot pour la création de diagrammes de Sankey.

    -
    -

    DiagrammeR

    -

    DiagrammeR est dédiée à la réalisation de diagrammes en ayant recours à la syntaxe Graphviz (via la fonction grViz) ou encore à la syntaxe Mermaid (via la fonction mermaid).

    -
    library(DiagrammeR)
    -grViz("
    -digraph boxes_and_circles {
    -
    -  # a 'graph' statement
    -  graph [overlap = true, fontsize = 10]
    -
    -  # several 'node' statements
    -  node [shape = box,
    -        fontname = Helvetica]
    -  A; B; C; D; E; F
    -
    -  node [shape = circle,
    -        fixedsize = true,
    -        width = 0.9] // sets as circles
    -  1; 2; 3; 4; 5; 6; 7; 8
    -
    -  # several 'edge' statements
    -  A->1 B->2 B->3 B->4 C->A
    -  1->D E->A 2->4 1->5 1->F
    -  E->6 4->6 5->7 6->7 3->8
    -}
    -")
    -
    -
    mermaid("
    -graph LR
    -A(Rounded)-->B[Rectangular]
    -B-->C{A Rhombus}
    -C-->D[Rectangle One]
    -C-->E[Rectangle Two]
    -")
    -
    -
    mermaid("
    -sequenceDiagram
    -  customer->>ticket seller: ask ticket
    -  ticket seller->>database: seats
    -  alt tickets available
    -    database->>ticket seller: ok
    -    ticket seller->>customer: confirm
    -    customer->>ticket seller: ok
    -    ticket seller->>database: book a seat
    -    ticket seller->>printer: print ticket
    -  else sold out
    -    database->>ticket seller: none left
    -    ticket seller->>customer: sorry
    -  end
    -")
    -
    -
    mermaid("
    -gantt
    -       dateFormat  YYYY-MM-DD
    -       title Adding GANTT diagram functionality to mermaid
    -
    -       section A section
    -       Completed task            :done,    des1, 2014-01-06,2014-01-08
    -       Active task               :active,  des2, 2014-01-09, 3d
    -       Future task               :         des3, after des2, 5d
    -       Future task2              :         des4, after des3, 5d
    -
    -       section Critical tasks
    -       Completed task in the critical line :crit, done, 2014-01-06,24h
    -       Implement parser and jison          :crit, done, after des1, 2d
    -       Create tests for parser             :crit, active, 3d
    -       Future task in critical line        :crit, 5d
    -       Create tests for renderer           :2d
    -       Add to mermaid                      :1d
    -
    -       section Documentation
    -       Describe gantt syntax               :active, a1, after des1, 3d
    -       Add gantt diagram to demo page      :after a1  , 20h
    -       Add another diagram to demo page    :doc1, after a1  , 48h
    -
    -       section Last section
    -       Describe gantt syntax               :after doc1, 3d
    -       Add gantt diagram to demo page      :20h
    -       Add another diagram to demo page    :48h
    -")
    -
    -

    Plus d’informations : https://rich-iannone.github.io/DiagrammeR/

    +
    +

    La fonction switch

    +

    Avec la fonction switch, on peut indiquer à R d’exécuter un code en fonction du résultat obtenu lors d’un test. La syntaxe est la suivante :

    +
    switch(valeur_test,
    +  cas_1 = {
    +    instruction_cas_1
    +  },
    +  cas_2 = {
    +    instruction_cas_2
    +  },
    +  ...
    +)
    +

    avec valeur_test un nombre ou une chaîne de caractères. Si valeur_test vaut cas_1, alors uniquement instruction_cas_1 sera évaluée, si valeur_test vaut cas_2, alors ce sera instruction_cas_2 qui le sera, et ainsi de suite. On peut rajouter une valeur par défaut en utilisant la syntaxte suivante :

    +
    switch(valeur_test,
    +  cas_1 = {
    +    instruction_cas_1
    +  },
    +  cas_2 = {
    +    instruction_cas_2
    +  },
    +  ...,
    +  {
    +    instruction_defaut
    +  }
    +)
    +

    Voici un exemple d’utilisation, issu de la page d’aide de la fonction.

    +
    centre <- function(x, type) {
    +  switch(type,
    +    mean = mean(x),
    +    median = median(x),
    +    trimmed = mean(x, trim = .1)
    +  )
    +}
    +x <- rcauchy(10)
    +centre(x, "mean")
    +
    [1] 2.178666
    +
    centre(x, "median")
    +
    [1] 0.8209592
    +
    centre(x, "trimmed")
    +
    [1] 1.122283
    +
    -
    -

    epicontacts

    -

    L’extension epicontacts permets de représenter des chaînes de transmission épidémiques.

    -

    -

    Pour aller plus loin, on pourra se référer (en anglais), au chapitre dédié du Epidemiologist R Handbook :

    +
    +

    L’instruction repeat … break

    +

    L’instruction repeat permet de répéter une expression. Il est nécessaire d’ajouter un test d’arrêt, à l’aide de l’instruction break.

    +
    i <- 1
    +repeat {
    +  i <- i + 1
    +  if (i == 3) break
    +}
    +i
    +
    [1] 3
    -
    -

    highcharter

    -

    L’extension highcharter permet de réaliser des graphiques HTML utilisant la librairie Javascript Highcharts.js.

    -
    library("highcharter")
    -data(diamonds, mpg, package = "ggplot2")
    -
    -hchart(mpg, "scatter", hcaes(x = displ, y = hwy, group = class))
    -
    -
    library(tidyverse)
    -library(highcharter)
    -mpgman3 <- mpg %>%
    -  group_by(manufacturer) %>%
    -  summarise(n = n(), unique = length(unique(model))) %>%
    -  arrange(-n, -unique)
    -
    -hchart(mpgman3, "treemap", hcaes(x = manufacturer, value = n, color = unique))
    -
    -
    data(unemployment)
    -
    -hcmap("countries/us/us-all-all",
    -  data = unemployment,
    -  name = "Unemployment", value = "value", joinBy = c("hc-key", "code"),
    -  borderColor = "transparent"
    -) %>%
    -  hc_colorAxis(dataClasses = color_classes(c(seq(0, 10, by = 2), 50))) %>%
    -  hc_legend(
    -    layout = "vertical", align = "right",
    -    floating = TRUE, valueDecimals = 0, valueSuffix = "%"
    -  )
    -
    -

    Plus d’informations : http://jkunst.com/highcharter/.

    +
    +

    L’instruction next

    +

    L’instruction next autorise de passer immédiatement à l’itération suivante d’une boucle for, while ou repeat.

    +
    result <- rep(NA, 10)
    +for (i in 1:10) {
    +  if (i == 5) next
    +  result[i] <- i
    +}
    +# Le 5e élément de result n'a pas été traité
    +result
    +
     [1]  1  2  3  4 NA  6  7  8  9 10
    +
    +
    +

    Barre de progression

    +

    Lorsque l’exécution d’une boucle prend du temps, il peut être intéressant d’avoir une idée de l’état d’avancement des itérations. Pour cela, il est bien sûr possible d’afficher une valeur dans la console à chaque tour, chaque 10 tours, etc.

    +

    La fonction txtProgressBar de l’extension utils permet un affichage d’une barre de progression dans la console. Il suffit de lui fournir une valeur minimale et maximale, et de la mettre à jour à chaque itération. Le paramètre style autorise de surcroit à choisir un style pour la barre. Le style numéro 3 affiche un pourcentage de progression, et est utile lorsque d’autres résultats sont affichés dans la console lors de l’exécution de la boucle, dans la mesure où la barre est de nouveau affichée au complet dans la console si nécessaire.

    +

    Dans l’exemple qui suit, à chacun des dix tours, une pause de 0.1 seconde est effectuée, puis la barre de progression est mise à jour.

    +
    nb_tours <- 10
    +pb <- txtProgressBar(min = 1, max = nb_tours, style = 3)
    +for (i in 1:nb_tours) {
    +  Sys.sleep(0.1)
    +  setTxtProgressBar(pb, i)
    +}
    +
    
    +  |                                                        
    +  |                                                  |   0%
    +  |                                                        
    +  |======                                            |  11%
    +  |                                                        
    +  |===========                                       |  22%
    +  |                                                        
    +  |=================                                 |  33%
    +  |                                                        
    +  |======================                            |  44%
    +  |                                                        
    +  |============================                      |  56%
    +  |                                                        
    +  |=================================                 |  67%
    +  |                                                        
    +  |=======================================           |  78%
    +  |                                                        
    +  |============================================      |  89%
    +  |                                                        
    +  |==================================================| 100%
    +

    Pour plus d’options, on pourra se référer à la fonction progress_bar de l’extension progress, présentée en détail sur https://r-pkg.org/pkg/progress.

    +

    Si l’exécution est vraiment longue, et qu’on est impatient de connaître les résultats, il existe de plus une fonction amusante dans l’extension beepr, qui porte le nom de beep. Plusieurs sons peuvent être utilisés (voir la page d’aide de la fonction).

    +
    library(beepr)
    +beep("mario")
    +
    +
    +

    Pour approfondir

    +

    On pourra également consulter le chapitre Boucles et exécution conditionnelle de l’excellente Introduction à R et au tidyverse de Julien Barnier (https://juba.github.io/tidyverse).

    -
    +
    -

    Conditions et comparaisons

    +

    Vectorisation (dont purrr)

    + -

    Une condition est une expression logique dont le résultat est soit TRUE (vrai) soit FALSE (faux).

    -

    Une condition comprend la plupart du temps un opérateur de comparaison. Les plus courants sont les suivants :

    +
    +

    La version originale de ce chapitre a été écrite par Ewen Gallic dans le cadre de son support de cours d’Ewen Gallic intitulé Logiciel R et programmation, chapitre 4 Boucles et calculs vectoriels.

    +
    +

    Les boucles sont des opérations lentes en R. Il est cependant possible, dans de nombreux cas, d’éviter de les employer, en ayant recours à la vectorisation : au lieu d’appliquer une fonction à un scalaire, on l’applique à un vecteur. En fait, nous avons déjà eu recours à maintes reprises aux calculs vectoriels. En effet, lorsque nous avons procédé à des additions, des multiplications, etc. sur des vecteurs, nous avons effectué des calculs vectoriels.

    +

    Empruntons un exemple à Burns (2011) : dans des langages comme le C, pour effectuer la somme des logarithmes naturels des n premiers entiers, voici une manière de faire :

    +
    # Somme des logarithmes des 10 premiers entiers
    +somme_log <- 0
    +for (i in seq_len(10)) {
    +  somme_log <- somme_log + log(i)
    +}
    +somme_log
    +
    [1] 15.10441
    +

    Il est possible d’obtenir le même résultat, à la fois d’une manière plus élégante, mais surtout plus efficace en vectorisant le calcul :

    +
    sum(log(seq_len(10)))
    +
    [1] 15.10441
    +

    Derrière ce code, la fonction log applique la fonction logarithme sur toutes les valeurs du vecteur donné en paramètre. La fonction sum, quant à elle, se charge d’additionner tous les éléments du vecteur qui lui est donné en paramètre. Ces deux fonctions utilisent la vectorisation, mais d’une manière différente : la fonction log applique une opération à chaque élément d’un vecteur, tandis que la fonction sum produit un résultat basé sur l’ensemble du vecteur. L’avantage d’utiliser des fonctions vectorielles plutôt que d’écrire une boucle pour effectuer le calcul, est que ces premières font appel à des fonctions rédigées en C ou FORTRAN, qui utilisent aussi des boucles, mais comme ce sont des langages compilés et non pas interprétés, les itérations sont réalisées dans un temps réduit.

    +

    Il existe des fonctions, rédigées en C qui effectuent des boucles for. On leur donne souvent le nom de “fonctions de la famille apply”. Il ne s’agit pas de la vectorisation, mais ces fonctions sont souvent mentionnées dès que l’on parle de ce sujet. Ce sont des fonctionnelles qui prennent une fonction en input et retournent un vecteur en output (Wickham, 2014). Ces fonctions sont très utilisées, mais elles souffrent d’un manque d’uniformité. En effet, elles ont été rédigées par des personnes différentes, ayant chacune leur convention. L’extension plyr remédie à ce problème, et ajoute par la même occasion des fonctions supplémentaires, pour couvrir plus de cas que les “fonctions de la famille apply”.

    +

    Nous allons donc présenter dans un premier temps les fonctions du package plyr. Les fonctions du même type du package base seront tout de même présentées par la suite.

    +
    +

    Les fonctions de l’extension purrr

    +

    L’extension purrr du tidyverse a beaucoup évolué ces dernières années et permet d’itérer facilement des opérations sur une liste ou un tableau. Cette extension s’articule également avec les tableaux imbriqués rendus possibles par tibble et tidyr (voir la fonction nest et sa vignette dédiée : https://tidyr.tidyverse.org/articles/nest.html).

    +

    Concernant purrr, on pourra consulter le chapitre Itérer avec purrr de l’excellente Introduction à R et au tidyverse de Julien Barnier (https://juba.github.io/tidyverse).

    +

    Julien Barnier y aborde notamment la fonction map et ses variantes comme map_dbl, map_chr, map_int, map_dfr, map_dfc, modify, imap, walk

    +
    +
    +

    Les fonctions de l’extension plyr

    +

    Les fonctions que nous allons aborder dans cette section possèdent des noms faciles à se remémorer : la première lettre correspond au format d’entrée des données, la seconde au format de sortie souhaité, et la fin du nom se termine par le suffixe ply. Ainsi, la fonction llpply prend en entrée une liste, effectue une opération sur les éléments, et retourne une liste (Anderson, 2012).

    +

    Les différentes fonctions que nous allons passer en revue sont consignées dans le tableau ci-après, où les lignes correspondent aux formats d’entrée, et les lignes aux formats de sortie. Pour y avoir accès, il faut charger le package :

    +
    library(plyr)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +Format de sortie +
    +
    + + +array + +data.frame + +list +
    +Format d’entée + +array + +aaply + +adply + +alply +
    +Format d’entée + +data.frame + +daply + +ddply + +dlply +
    +Format d’entée + +list + +laply + +ldply + +llply +
    +

    Il est possible d’avoir plusieurs paramètres en input au lieu d’un seul objet. Les fonctions mlply, mdply et maply. Si à la place du m, la première lettre est un r, il s’agit alors de fonction de réplications. Enfin, si la seconde lettre est un trait de soulignement (_), alors le résultat retourné n’est pas affiché (le code utilise la fonction invisible.

    +

    Tous les paramètres de ces fonctions commencent par un point (.), afin d’éviter des incompatibilités avec la fonction à appliquer.

    +
    +

    Array en input : a*ply +

    +

    Les fonctions aaply, adply et alply appliquent une fonction à chaque portion d’un array et ensuitent joignent le résultat sous forme d’un array, d’un data.frame ou d’une list respectivement.

    +
    +

    Un array peut être vu comme un vecteur à plusieurs dimensions. Comme pour un vecteur, toutes les valeurs doivent être du même type. Un vecteur n’est finalement qu’un array à une seule dimension. De même, un array à deux dimensions correspond à ce qu’on appelle usuelement une matrice.

    +
    +

    Le paramètre .margins détermine la manière de découper le tableau. Il y en a quatre pour un tableau en deux dimensions :

    +
      +
    1. +.margins = 1 : par lignes ;
    2. +
    3. +.margins = 2 : par colonnes ;
    4. +
    5. +.margins = c(1,2) : par cellule ;
    6. +
    7. +.margins = c() : ne pas faire de découpement.
    8. +
    +

    Pour un tableau en trois dimensions, il y a trois découpages possibles en deux dimensions, trois en une dimension et une en zéro dimension (voir (Wickham, 2011)) au besoin.

    +
    tableau <- array(
    +  1:24,
    +  dim = c(3, 4, 2),
    +  dimnames = list(
    +    ligne = letters[1:3],
    +    colonne = LETTERS[1:4],
    +    annee = 2001:2002
    +  )
    +)
    +tableau
    +
    , , annee = 2001
    +
    +     colonne
    +ligne A B C  D
    +    a 1 4 7 10
    +    b 2 5 8 11
    +    c 3 6 9 12
    +
    +, , annee = 2002
    +
    +     colonne
    +ligne  A  B  C  D
    +    a 13 16 19 22
    +    b 14 17 20 23
    +    c 15 18 21 24
    +
    # La moyenne des valeurs pour chaque ligne
    +aaply(tableau, 1, mean) # résultat sous forme de tableau
    +
       a    b    c 
    +11.5 12.5 13.5 
    +
    adply(tableau, 1, mean) # résultat sous forme de data.frame
    - - + + + + + + + + + + + + + + + + + +
    Opérateur de comparaisonSignificationligneV1
    a11.5
    b12.5
    c13.5
    +
    alply(tableau, 1, mean) # résultat sous forme de liste
    +
    $`1`
    +[1] 11.5
    +
    +$`2`
    +[1] 12.5
    +
    +$`3`
    +[1] 13.5
    +
    +attr(,"split_type")
    +[1] "array"
    +attr(,"split_labels")
    +  ligne
    +1     a
    +2     b
    +3     c
    +
    # La moyenne des valeurs pour chaque colonne
    +# en ne simplifiant pas le résultat
    +aaply(tableau, 2, mean, .drop = FALSE)
    + + + + + + + + + + + + + + + + + + + + + + + +
    1
    A8
    B11
    C14
    D17
    +
    # Par lignes et colonnes
    +aaply(tableau, c(1, 2), mean)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ABCD
    a7101316
    b8111417
    c9121518
    +
    adply(tableau, c(1, 2), mean)
    + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + - - + + + - - + + + - - + + + - - + + + - - + + + + + + + + + + + + +
    lignecolonneV1
    ==égal àaA7
    bA8
    cA9
    aB10
    bB11
    !=différent decB12
    >strictement supérieur àaC13
    <strictement inférieur àbC14
    >=supérieur ou égal àcC15
    <=inférieur ou égal àaD16
    bD17
    cD18
    -

    Voyons tout de suite un exemple :

    -
    library(questionr)
    -data(hdv2003)
    -d <- hdv2003
    -str(d$sexe == "Homme")
    -
     logi [1:2000] FALSE FALSE TRUE TRUE FALSE FALSE ...
    -

    Que s’est-il passé ? Nous avons fourni à R une condition qui signifie « la valeur de la variable sexe vaut “Homme” ». Et il nous a renvoyé un vecteur avec autant d’éléments qu’il y’a d’observations dans d, et dont la valeur est TRUE si l’observation correspond à un homme et FALSE dans les autres cas.

    -

    Prenons un autre exemple. On n’affichera cette fois que les premiers éléments de notre variable d’intérêt à l’aide de la fonction head :

    -
    head(d$age)
    -
    [1] 28 23 59 34 71 35
    -
    head(d$age > 40)
    -
    [1] FALSE FALSE  TRUE FALSE  TRUE FALSE
    -

    On voit bien ici qu’à chaque élément du vecteur d$age dont la valeur est supérieure à 40 correspond un élément TRUE dans le résultat de la condition.

    -

    On peut combiner ou modifier des conditions à l’aide des opérateurs logiques habituels :

    +
    # Avec une fonction définie par l'utilisateur
    +standardise <- function(x) (x - min(x)) / (max(x) - min(x))
    +# Standardiser les valeurs par colonne
    +aaply(tableau, 2, standardise)
    +
    , , annee = 2001
    +
    +       ligne
    +colonne a          b         c
    +      A 0 0.07142857 0.1428571
    +      B 0 0.07142857 0.1428571
    +      C 0 0.07142857 0.1428571
    +      D 0 0.07142857 0.1428571
    +
    +, , annee = 2002
    +
    +       ligne
    +colonne         a         b c
    +      A 0.8571429 0.9285714 1
    +      B 0.8571429 0.9285714 1
    +      C 0.8571429 0.9285714 1
    +      D 0.8571429 0.9285714 1
    +
    +
    +

    Data.frame en input : d*ply +

    +

    Dans le cas de l’analyse d’enquêtes, on utilise principalement des tableaux de données ou data.frame. Aussi, la connaissance des fonction daply, ddply et dlply peut être utile. En effet, elles sont très utiles pour appliquer des fonctions à des groupes basés sur des combinaisons de variables, même si dans la majorité des cas il est maintenant plus facile de passer par les extensions dplyr ou data.table avec les opérations groupées (voir la section sur groub_by de dplyr ou encore celle sur le paramètre by de data.table.

    +

    Avec les fonctions d*ply, il est nécessaire d’indiquer quelles variables, ou fonctions de variables on souhaite utiliser, en l’indiquant au paramètre .variables. Elles peuvent être contenue dans le data frame fourni au paramètre .data, ou bien provenir de l’environnement global. R cherchera dans un premier temps si la variable est contenue dans le data.frame et, s’il ne trouve pas, ira chercher dans l’environnement global.

    +

    Pour indiquer que l’on désire faire le regroupement selon une variable – mettons variable_1 – il faudra fournir l’expression .(variable_1) au paramètre .variables. Si on souhaite effectuer les regroupement selon les interactions de plusieurs variables – variable_1, variable_2 et variable_3, il faut alors utiliser l’expression suivante : .(variable_1, variable_2, variable_3).

    +
    chomage <- data.frame(
    +  region = rep(c(rep("Bretagne", 4), rep("Corse", 2)), 2),
    +  departement = rep(c(
    +    "Cotes-d'Armor", "Finistere",
    +    "Ille-et-Vilaine", "Morbihan",
    +    "Corse-du-Sud", "Haute-Corse"
    +  ), 2),
    +  annee = rep(c(2011, 2010), each = 6),
    +  ouvriers = c(
    +    8738, 12701, 11390, 10228, 975, 1297,
    +    8113, 12258, 10897, 9617, 936, 1220
    +  ),
    +  ingenieurs = c(
    +    1420, 2530, 3986, 2025, 259, 254,
    +    1334, 2401, 3776, 1979, 253, 241
    +  )
    +)
    +chomage
    - - + + + + + - - + + + + + - - + + + + + - - + + + + + - -
    Opérateur logiqueSignificationregiondepartementanneeouvriersingenieurs
    &et logiqueBretagneCotes-d’Armor201187381420
    |ou logiqueBretagneFinistere2011127012530
    !négation logiqueBretagneIlle-et-Vilaine2011113903986
    -

    Comment les utilise-t-on ? Voyons tout de suite des exemples. Supposons que je veuille déterminer quels sont dans mon échantillon les hommes ouvriers spécialisés :

    -
    d$sexe == "Homme" & d$qualif == "Ouvrier specialise"
    -

    Si je souhaite identifier les personnes qui bricolent ou qui font la cuisine :

    -
    d$bricol == "Oui" | d$cuisine == "Oui"
    -

    Si je souhaite isoler les femmes qui ont entre 20 et 34 ans :

    -
    d$sexe == "Femme" & d$age >= 20 & d$age <= 34
    -

    Si je souhaite récupérer les enquêtés qui ne sont pas cadres, on peut utiliser l’une des deux formes suivantes :

    -
    d$qualif != "Cadre"
    -!(d$qualif == "Cadre")
    -

    Lorsqu’on mélange « et » et « ou » il est nécessaire d’utiliser des parenthèses pour différencier les blocs. La condition suivante identifie les femmes qui sont soit cadre, soit employée :

    -
    d$sexe == "Femme" & (d$qualif == "Employe" | d$qualif == "Cadre")
    -

    L’opérateur %in% peut être très utile : il teste si une valeur fait partie des éléments d’un vecteur. Ainsi on pourrait remplacer la condition précédente par :

    -
    d$sexe == "Femme" & d$qualif %in% c("Employe", "Cadre")
    -

    Enfin, signalons qu’on peut utiliser les fonctions table ou summary pour avoir une idée du résultat de notre condition :

    -
    table(d$sexe)
    -
    
    -Homme Femme 
    -  899  1101 
    -
    table(d$sexe == "Homme")
    -
    
    -FALSE  TRUE 
    - 1101   899 
    -
    summary(d$sexe == "Homme")
    -
       Mode   FALSE    TRUE 
    -logical    1101     899 
    - -
    -
    - - - -

    Formules

    - -
    - - - -

    Ce chapitre vise à illustrer l’utilisation de la notation formule de R, qui désigne l’emploi de cette notation par l’expression formula. Cette notation est utilisée par de très nombreuses fonctions de R : on en a notamment vu plusieurs exemples dans le chapitre sur les graphiques bivariés, car l’extension ggplot2 se sert de cette notation dans ses paramètres facet_wrap et facet_grid.

    -

    Dans ce chapitre, on verra comment se servir de la notation formule dans deux contextes différents. D’une part, on verra que deux fonctions basiques de R se servent de cette notation pour produire des tableaux croisés et des statistiques bivariées. D’autre part, on verra que l’extension lattice se sert de cette notation pour créer des graphiques panelisés, dits graphiques à petits multiples.

    -

    Dans plusieurs autres chapitres, les opérations décrites ci-dessus sont effectuées avec les extensions dplyr d’une part, et ggplot2 d’autre part. On se servira également de ces extensions dans ce chapitre, de manière à mener une comparaison des différentes manières d’effectuer certaines opérations dans R, avec ou sans la notation formule :

    -
    library(dplyr)
    -library(ggplot2)
    -
    -

    Statistiques descriptives

    -

    Les premiers exemples de ce chapitre montrent l’utilisation de cette notation pour produire des tableaux croisés et des statistiques descriptives. Le jeu de données utilisé, hdv2003, a déjà été utilisé dans plusieurs chapitres, et font partie de l’extension questionr. Chargeons cette extension et le jeu de données hdv2003 :

    -
    library(questionr)
    -data(hdv2003)
    -

    Pour rappel, ce jeu de données contient des individus, leur âge, leur statut professionnel, et le nombre d’heures quotidiennes passées à regarder la télévision.

    -
    glimpse(hdv2003, 75)
    -
    Rows: 2,000
    -Columns: 20
    -$ id            <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ~
    -$ age           <int> 28, 23, 59, 34, 71, 35, 60, 47, 20, 28, 65, 47, 63,~
    -$ sexe          <fct> Femme, Femme, Homme, Homme, Femme, Femme, Femme, Ho~
    -$ nivetud       <fct> "Enseignement superieur y compris technique superie~
    -$ poids         <dbl> 2634.3982, 9738.3958, 3994.1025, 5731.6615, 4329.09~
    -$ occup         <fct> "Exerce une profession", "Etudiant, eleve", "Exerce~
    -$ qualif        <fct> Employe, NA, Technicien, Technicien, Employe, Emplo~
    -$ freres.soeurs <int> 8, 2, 2, 1, 0, 5, 1, 5, 4, 2, 3, 4, 1, 5, 2, 3, 4, ~
    -$ clso          <fct> Oui, Oui, Non, Non, Oui, Non, Oui, Non, Oui, Non, O~
    -$ relig         <fct> Ni croyance ni appartenance, Ni croyance ni apparte~
    -$ trav.imp      <fct> Peu important, NA, Aussi important que le reste, Mo~
    -$ trav.satisf   <fct> Insatisfaction, NA, Equilibre, Satisfaction, NA, Eq~
    -$ hard.rock     <fct> Non, Non, Non, Non, Non, Non, Non, Non, Non, Non, N~
    -$ lecture.bd    <fct> Non, Non, Non, Non, Non, Non, Non, Non, Non, Non, N~
    -$ peche.chasse  <fct> Non, Non, Non, Non, Non, Non, Oui, Oui, Non, Non, N~
    -$ cuisine       <fct> Oui, Non, Non, Oui, Non, Non, Oui, Oui, Non, Non, O~
    -$ bricol        <fct> Non, Non, Non, Oui, Non, Non, Non, Oui, Non, Non, O~
    -$ cinema        <fct> Non, Oui, Non, Oui, Non, Oui, Non, Non, Oui, Oui, O~
    -$ sport         <fct> Non, Oui, Oui, Oui, Non, Oui, Non, Non, Non, Oui, N~
    -$ heures.tv     <dbl> 0.0, 1.0, 0.0, 2.0, 3.0, 2.0, 2.9, 1.0, 2.0, 2.0, 1~
    -
    -

    Tableaux croisés avec xtabs

    -

    Utilisons, pour ce premier exemple, la variable occup du jeu de données hdv2003, qui correspond au statut professionnel des individus inclus dans l’échantillon. La fonction de base pour compter les individus par statut est la fonction table :

    -
    table(hdv2003$occup)
    -
    
    -Exerce une profession               Chomeur 
    -                 1049                   134 
    -      Etudiant, eleve              Retraite 
    -                   94                   392 
    -  Retire des affaires              Au foyer 
    -                   77                   171 
    -        Autre inactif 
    -                   83 
    -

    Avec la fonction xtabs, le même résultat est produit à partir de la notation suivante :

    -
    xtabs(~occup, data = hdv2003)
    -
    occup
    -Exerce une profession               Chomeur 
    -                 1049                   134 
    -      Etudiant, eleve              Retraite 
    -                   94                   392 
    -  Retire des affaires              Au foyer 
    -                   77                   171 
    -        Autre inactif 
    -                   83 
    -

    Le premier argument est une formule, au sens où R entend cette expression. Le second argument, data, correspond au jeu de données auquel la formule doit être appliquée. On pourra se passer d’écrire explicitement cet argument dans les exemples suivants.

    -

    L’avantage de la fonction xtabs n’est pas évident dans ce premier exemple. En réalité, cette fonction devient utile lorsque l’on souhaite construire un ou plusieurs tableau(x) croisé(s). Par exemple, pour croiser la variable occup avec la variable sexe, une solution constiste à écrire :

    -
    with(hdv2003, table(occup, sexe))
    -
                           sexe
    -occup                   Homme Femme
    -  Exerce une profession   520   529
    -  Chomeur                  54    80
    -  Etudiant, eleve          48    46
    -  Retraite                208   184
    -  Retire des affaires      39    38
    -  Au foyer                  0   171
    -  Autre inactif            30    53
    -

    Ou alors, ce qui revient au même :

    -
    table(hdv2003$occup, hdv2003$sexe)
    -

    Avec xtabs, la même opération s’écrit de la manière suivante :

    -
    xtabs(~ occup + sexe, hdv2003)
    -
                           sexe
    -occup                   Homme Femme
    -  Exerce une profession   520   529
    -  Chomeur                  54    80
    -  Etudiant, eleve          48    46
    -  Retraite                208   184
    -  Retire des affaires      39    38
    -  Au foyer                  0   171
    -  Autre inactif            30    53
    -

    Cette écriture est plus courte que le code équivalent dans dplyr :

    -
    hdv2003 %>%
    -  group_by(occup) %>%
    -  summarise(
    -    Homme = sum(sexe == "Homme"),
    -    Femme = sum(sexe == "Femme")
    -  )
    -
    - -
    -

    Par contre, on pourra éventuellement utiliser count de dplyr. ATTENTION : le format du résultat ne sera pas le même.

    -
    hdv2003 %>%
    -  group_by(occup) %>%
    -  count(sexe)
    -
    - -
    -

    Pour un tableau croisé joliment mis en forme, on pourra avoir recours à tbl_cross de gtsummary.

    -
    library(gtsummary)
    -hdv2003 %>%
    -  tbl_cross(row = "occup", col = "sexe", percent = "row")
    -
    - - - - - - - - - - - - - - - - - - - +$logic + 0% 25% 50% 75% 100% + 0.0 0.0 0.5 1.0 1.0 +
    # Avec sapply
    +sapply(x, quantile)
    +
    Caractéristique - sexe - Total
    HommeFemme
    occup
    + + + + + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + +
    abetalogic
    0%1.000.04978710.0
    Exerce une profession520 (50%)529 (50%)1 049 (100%)
    25%3.250.25160740.0
    Chomeur54 (40%)80 (60%)134 (100%)
    50%5.501.00000000.5
    Etudiant, eleve48 (51%)46 (49%)94 (100%)
    75%7.755.05366901.0
    Retraite208 (53%)184 (47%)392 (100%)
    100%10.0020.08553691.0
    Retire des affaires39 (51%)38 (49%)77 (100%)
    +
    # Exemple avec USE.NAMES
    +sapply(LETTERS[1:3], nchar)
    +
    A B C 
    +1 1 1 
    +
    sapply(LETTERS[1:3], nchar, USE.NAMES = FALSE)
    +
    [1] 1 1 1
    +
    +
    +

    La fonction vapply

    +

    La fonction vapply est similaire à sapply, mais elle possède un type de valeurs spécifié, ce qui peut rendre l’utilisation plus sûre (et parfois plus rapide). Lorsqu’on lui fournit un data.frame, vapply retourne le même résultat que sapply. Cependant, quand on lui fournit une liste vide, vapply retourne un vecteur logique de longueur nulle (ce qui est plus sensé que la liste vide que returne sapply).

    +
    vapply(X, FUN, FUN.VALUE, ..., USE.NAMES)
    +

    avec X, FUN, ... et USE.NAMES les mêmes paramètres que pour sapply. Le paramètre FUN.VALUE doit être un vecteur, un masque pour la valeur retournée par la fonction de FUN.

    +
    # Retourner le vecteur
    +sapply(cars, is.numeric)
    +
    speed  dist 
    + TRUE  TRUE 
    +
    vapply(cars, is.numeric, FUN.VALUE = logical(1))
    +
    speed  dist 
    + TRUE  TRUE 
    +
    # Avec la liste vide
    +sapply(list(), is.numeric)
    +
    list()
    +
    vapply(list(), is.numeric, FUN.VALUE = logical(1))
    +
    logical(0)
    +
    +
    +

    La fonction apply

    +

    La fonction apply possède la syntaxe suivante :

    +
    apply(X, MARGIN, FUN, ...)
    +

    avec X une matrice ou un tableau, MARGIN indiquant si on souhaite appliquer la fonction FUN aux lignes (MARGIN = 1) ou aux colonnes (MARGIN = 2), et ... des paramètres supplémentaires éventuels à passer à la fonction FUN.

    +
    (X <- matrix(1:9, ncol = 3))
    + + + + + - - - - - + + + + - - - - - + + + + - - - - - +
    147
    Au foyer0 (0%)171 (100%)171 (100%)
    258
    Autre inactif30 (36%)53 (64%)83 (100%)
    369
    Total899 (45%)1 101 (55%)2 000 (100%)
    +
    # Somme par ligne
    +apply(X, MARGIN = 1, sum)
    +
    [1] 12 15 18
    +
    # Somme par colonne
    +apply(X, MARGIN = 2, sum)
    +
    [1]  6 15 24
    +
    # Fonction définie par l'utilisateur
    +apply(X, MARGIN = 1, function(x) sum(x) / sum(X))
    +
    [1] 0.2666667 0.3333333 0.4000000
    +
    +
    +

    La fonction tapply

    +

    La fonction tapply s’applique à chaque cellule d’un tableau, sur des regroupements définis par les variables catégorielles fournies. La syntaxe est la suivante :

    +
    tapply(X, INDEX, FUN, ..., simplify)
    +

    avec X le tableau de données, INDEX une liste d’un ou plusieurs facteurs, chacun de même taille que X. Le paramètre FUN renseigne la fonction que l’on souhaite appliquer. Si simplify vaut FALSE, le résultat est un tableau de mode list. Sinon (par défaut), le résultat est un tableau de scalaires.

    +
    data(iris)
    +head(iris)
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
    5.13.51.40.2setosa
    4.93.01.40.2setosa
    4.73.21.30.2setosa
    4.63.11.50.2setosa
    5.03.61.40.2setosa
    5.43.91.70.4setosa
    -
    -

    De plus, xtabs permet de créer plusieurs tableaux croisés en une seule formule :

    -
    xtabs(~ occup + sexe + trav.imp, hdv2003)
    -
    , , trav.imp = Le plus important
    -
    -                       sexe
    -occup                   Homme Femme
    -  Exerce une profession    13    16
    -  Chomeur                   0     0
    -  Etudiant, eleve           0     0
    -  Retraite                  0     0
    -  Retire des affaires       0     0
    -  Au foyer                  0     0
    -  Autre inactif             0     0
    -
    -, , trav.imp = Aussi important que le reste
    -
    -                       sexe
    -occup                   Homme Femme
    -  Exerce une profession   159   100
    -  Chomeur                   0     0
    -  Etudiant, eleve           0     0
    -  Retraite                  0     0
    -  Retire des affaires       0     0
    -  Au foyer                  0     0
    -  Autre inactif             0     0
    -
    -, , trav.imp = Moins important que le reste
    -
    -                       sexe
    -occup                   Homme Femme
    -  Exerce une profession   328   380
    -  Chomeur                   0     0
    -  Etudiant, eleve           0     0
    -  Retraite                  0     0
    -  Retire des affaires       0     0
    -  Au foyer                  0     0
    -  Autre inactif             0     0
    +
    # Moyenne de la longueur des sépales par espèce
    +tapply(iris$Sepal.Length, iris$Species, mean)
    +
        setosa versicolor  virginica 
    +     5.006      5.936      6.588 
    +
    # Pour retourner le résultat sous forme de liste
    +tapply(iris$Sepal.Length, iris$Species, mean, simplify = FALSE)
    +
    $setosa
    +[1] 5.006
     
    -, , trav.imp = Peu important
    +$versicolor
    +[1] 5.936
     
    -                       sexe
    -occup                   Homme Femme
    -  Exerce une profession    20    32
    -  Chomeur                   0     0
    -  Etudiant, eleve           0     0
    -  Retraite                  0     0
    -  Retire des affaires       0     0
    -  Au foyer                  0     0
    -  Autre inactif             0     0
    -

    Cet exemple permet simplement de réaliser que la variable trav.imp, qui contient les réponses à une question portant sur l’importance du travail, n’a été mesurée (c’est-à-dire que la question n’a été posée) qu’aux seuls individus actifs de l’échantillon.

    -
    -
    -

    Statistiques bivariées avec aggregate

    -
    aggregate(heures.tv ~ sexe, mean, data = hdv2003)
    -
    - -
    -

    Ici, le premier argument est à nouveau une formule. Le second argument correspond à la statistique descriptive que l’on souhaite obtenir, et le dernier argument indique le jeu de données auquel appliquer les deux autres arguments. On peut d’ailleurs obtenir le même résultat en respectant de manière plus stricte l’ordre des arguments dans la syntaxe de la fonction aggregate :

    -
    aggregate(heures.tv ~ sexe, hdv2003, mean)
    -
    - -
    -

    Cette écriture est, à nouveau, plus compacte que le code équivalent dans dplyr, qui demande de spécifier le retrait des valeurs manquantes :

    -
    hdv2003 %>
    -  group_by(sexe) %>%
    -  summarise(heures.tv = mean(heures.tv, na.rm = TRUE))
    -

    À nouveau, on va pouvoir combiner plusieurs variables dans la formule que l’on passe à aggregate, ce qui va permettre d’obtenir la moyenne des heures de télévision quotidiennes par sexe et par statut professionnel :

    -
    aggregate(heures.tv ~ sexe + occup, hdv2003, mean)
    -
    - -
    -

    La même opération dplyr :

    -
    hdv2003 %>%
    -  group_by(occup, sexe) %>%
    -  summarise(heures.tv = mean(heures.tv, na.rm = TRUE))
    -

    La fonction aggregate permet bien sûr d’utiliser une autre fonction que la moyenne, comme dans cet exemple, suivi de son équivalent avec dplyr :

    -
    # âge médian par sexe et statut professionnel
    -aggregate(age ~ sexe + occup, hdv2003, median)
    -
    - -
    -
    # code équivalent avec l'extension 'dplyr'
    -hdv2003 %>%
    -  group_by(occup, sexe) %>%
    -  summarise(age = median(age, na.rm = TRUE))
    -

    Si, comme dans le cas de summarise, on souhaite passer des arguments supplémentaires à la fonction median, il suffit de les lister à la suite du nom de la fonction. Par exemple, on écrirait : aggregate(age ~ sexe + occup, hdv2003, median, na.rm = TRUE). Ceci étant, aggregate utilise par défaut l’option na.action = na.omit, donc il est bon de se rappeler que l’on peut désactiver cette option en utilisant l’option na.action = na.pass, ce qui permet éventuellement de conserver des lignes vides dans le tableau de résultat.

    -

    La fonction aggregate permet, par ailleurs, d’obtenir des résultats à plusieurs colonnes. Dans l’exemple ci-dessus, on illustre ce principe avec la fonction range, qui renvoie deux résultats (la valeur minimale et la valeur maximale de la variable, qui est toujours la variable age), chacun présentés dans une colonne :

    -
    aggregate(age ~ sexe + occup, hdv2003, range)
    -
        sexe                 occup age.1 age.2
    -1  Homme Exerce une profession    18    63
    -2  Femme Exerce une profession    18    67
    -3  Homme               Chomeur    18    63
    -4  Femme               Chomeur    18    63
    -5  Homme       Etudiant, eleve    18    34
    -6  Femme       Etudiant, eleve    18    35
    -7  Homme              Retraite    48    92
    -8  Femme              Retraite    41    96
    -9  Homme   Retire des affaires    57    91
    -10 Femme   Retire des affaires    57    93
    -11 Femme              Au foyer    22    90
    -12 Homme         Autre inactif    39    71
    -13 Femme         Autre inactif    19    97
    -

    Cette fonction ne peut pas être facilement écrite dans dplyr sans réécrire chacune des colonnes, ce que le bloc de code suivant illustre. On y gagne en lisibilité dans les intitulés de colonnes :

    -
    hdv2003 %>%
    -  group_by(occup, sexe) %>%
    -  summarise(
    -    min = min(age, na.rm = TRUE),
    -    max = max(age, na.rm = TRUE)
    -  )
    -

    Depuis la version 1.0.0 de dplyr, summarise accepte maintenant des fonctions pouvant renvoyer plusieurs valeurs, créant ainsi autant de lignes (voir https://www.tidyverse.org/blog/2020/03/dplyr-1-0-0-summarise/).

    -
    hdv2003 %>%
    -  group_by(occup, sexe) %>%
    -  summarise(age = range(age, na.rm = TRUE), type = c("min", "max"))
    -
    `summarise()` has grouped output by 'occup', 'sexe'. You can override using the `.groups` argument.
    -
    - -
    -

    On pourrait de même définir sa propre fonction et la passer à aggregate :

    -
    f <- function(x) c(mean = mean(x, na.rm = TRUE), sd = sd(x, na.rm = TRUE))
    -aggregate(age ~ sexe + occup, hdv2003, f)
    -
        sexe                 occup  age.mean    age.sd
    -1  Homme Exerce une profession 41.461538 10.438113
    -2  Femme Exerce une profession 40.710775 10.203864
    -3  Homme               Chomeur 38.925926 13.256329
    -4  Femme               Chomeur 38.012500 11.648321
    -5  Homme       Etudiant, eleve 20.895833  2.926326
    -6  Femme       Etudiant, eleve 21.586957  3.249452
    -7  Homme              Retraite 68.418269  8.018882
    -8  Femme              Retraite 69.510870  8.228957
    -9  Homme   Retire des affaires 71.179487  7.687556
    -10 Femme   Retire des affaires 73.789474  7.651737
    -11 Femme              Au foyer 50.730994 15.458412
    -12 Homme         Autre inactif 54.166667  6.597196
    -13 Femme         Autre inactif 59.962264 14.660206
    -

    Mais on réalisera vite une des limitations de aggregate dans ce cas-là : le tableau retourné ne contient pas 4 colonnes, mais 3 uniquement, ce que l’on peut vérifier à l’aide de dim ou str.

    -
    str(aggregate(age ~ sexe + occup, hdv2003, f))
    -
    'data.frame':   13 obs. of  3 variables:
    - $ sexe : Factor w/ 2 levels "Homme","Femme": 1 2 1 2 1 2 1 2 1 2 ...
    - $ occup: Factor w/ 7 levels "Exerce une profession",..: 1 1 2 2 3 3 4 4 5 5 ...
    - $ age  : num [1:13, 1:2] 41.5 40.7 38.9 38 20.9 ...
    -  ..- attr(*, "dimnames")=List of 2
    -  .. ..$ : NULL
    -  .. ..$ : chr [1:2] "mean" "sd"
    -

    Pour ce type d’opération, dans lequel on souhaite récupérer plusieurs variables calculées afin de travailler sur ces données agrégées soit dans le cadre d’opérations numériques soit de constructions graphiques, dplyr ou Hmisc s’avèrent plus commodes. Voici un exemple avec summarize de l’extension Hmisc :

    -
    library(Hmisc, quietly = TRUE)
    -with(hdv2003, summarize(age, llist(sexe, occup), f))
    -
    - -
    -

    Notons que Hmisc offre déjà une telle fonction (smean.sd), ce qui nous aurait épargné d’écrire notre propre fonction, f, et il en existe bien d’autres. Voici un exemple avec des intervalles de confiance estimés par bootstrap :

    -
    with(hdv2003, summarize(age, llist(sexe, occup), smean.cl.boot))
    -
    - -
    -

    Et un exemple avec dplyr.

    -
    hdv2003 %>%
    -  group_by(sexe, occup) %>%
    -  summarise(
    -    tibble(
    -      age_mean = mean(age, na.rm = TRUE),
    -      age_sd = sd(age, na.rm = TRUE)
    -    )
    -  )
    -
    `summarise()` has grouped output by 'sexe'. You can override using the `.groups` argument.
    -
    - -
    -

    Enfin, il est également possible d’utiliser plusieurs variables numériques à gauche de l’opérateur ~. En voici une illustration :

    -
    aggregate(cbind(age, poids) ~ sexe + occup, hdv2003, mean)
    -
    - -
    -
    -
    -
    -

    Panels graphiques avec lattice

    -

    Les exemples suivants montreront ensuite comment la notation formule peut servir à produire des graphiques par panel avec l’extension lattice.

    -
    library(lattice)
    -
    -

    L’extension lattice présente l’avantage d’être installée par défaut avec R. Il n’est donc pas nécessaire de l’installer préalablement.

    -
    -

    Chargeons les mêmes données que le chapitre sur les graphiques bivariés.

    -
    # charger l'extension lisant le format CSV
    -library(readr)
    -
    -# emplacement souhaité pour le jeu de données
    -file <- "data/debt.csv"
    -
    -# télécharger le jeu de données s'il n'existe pas
    -if (!file.exists(file)) {
    -  download.file("http://www.stat.cmu.edu/~cshalizi/uADA/13/hw/11/debt.csv",
    -    file,
    -    mode = "wb"
    -  )
    -}
    -
    -# charger les données dans l'objet 'debt'
    -debt <- read_csv(file)
    -
    New names:
    -* `` -> ...1
    -
    Rows: 1171 Columns: 5
    -
    -- Column specification ------------------------------------
    -Delimiter: ","
    -chr (1): Country
    -dbl (4): ...1, Year, growth, ratio
    -
    
    -i Use `spec()` to retrieve the full column specification for this data.
    -i Specify the column types or set `show_col_types = FALSE` to quiet this message.
    -

    Rejetons rapidement un coup d’oeil à ces données, qui sont structurées par pays (variable Country) et par année (variable Year). On y trouve deux variables, growth (le taux de croissance du produit intérieur brut réel), et ratio (le ratio entre la dette publique et le produit intérieur brut), ainsi qu’une première colonne vide, ne contenant que des numéros lignes, dont on va se débarrasser :

    -
    # inspection des données
    -glimpse(debt, 75)
    -
    Rows: 1,171
    -Columns: 5
    -$ ...1    <dbl> 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 15~
    -$ Country <chr> "Australia", "Australia", "Australia", "Australia", "Aust~
    -$ Year    <dbl> 1946, 1947, 1948, 1949, 1950, 1951, 1952, 1953, 1954, 195~
    -$ growth  <dbl> -3.5579515, 2.4594746, 6.4375341, 6.6119938, 6.9202012, 4~
    -$ ratio   <dbl> 190.41908, 177.32137, 148.92981, 125.82870, 109.80940, 87~
    -
    # suppression de la première colonne
    -debt <- debt[, -1]
    -
    -

    Visualisation bivariée

    -

    Le même graphique s’écrit de la manière suivante avec l’extension lattice :

    -
    xyplot(growth ~ Year, data = debt)
    -

    +$virginica +[1] 6.588
    -
    -

    Visualisation par petits multiples -

    -

    Appliquons désormais la même visualisation par petits multiples que vue dans le chapitre :

    -
    xyplot(growth ~ Year | Country, data = debt)
    -

    -

    Enfin, rajoutons quelques options au graphique, afin de montrer comment l’extension lattice fonctionne :

    -
    xyplot(growth ~ Year | Country,
    -  type = c("o", "l"),
    -  main = "Données Reinhart et Rogoff corrigées, 1946-2009",
    -  ylab = "Taux de croissance du PIB",
    -  xlab = NULL,
    -  data = debt
    -)
    -

    +
    +

    La fonction mapply

    +

    La fonction mapply applique une fonction à plusieurs listes ou vecteurs. La syntaxe est la suivante :

    +
    mapply(FUN, ..., MoreArgs, SIMPLIFY, USE.NAMES)
    +

    avec FUN la fonction à appliquer aux vecteurs ou listes fournies (grâce à ...), MoreArgs une liste de paramètres supplémentaires à fournir à la fonction à appliquer. Les paramètres SIMPLIFY et USE.NAMES ont le même usage que pour la fonction sapply.

    +
    (l1 <- list(a = c(1:5), b = c(6:10)))
    +
    $a
    +[1] 1 2 3 4 5
    +
    +$b
    +[1]  6  7  8  9 10
    +
    (l2 <- list(c = c(11:15), d = c(16:20)))
    +
    $c
    +[1] 11 12 13 14 15
    +
    +$d
    +[1] 16 17 18 19 20
    +
    # La somme des éléments correspondants de l1 et l2
    +mapply(sum, l1$a, l1$b, l2$c, l2$d)
    +
    [1] 34 38 42 46 50
    +
    # Attention au recyclage silencieux !
    +(l1 <- list(a = c(1:5), b = c(6:20)))
    +
    $a
    +[1] 1 2 3 4 5
    +
    +$b
    + [1]  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
    +
    mapply(sum, l1$a, l1$b, l2$c, l2$d)
    +
     [1] 34 38 42 46 50 39 43 47 51 55 44 48 52 56 60
    -
    -

    Spécifier des modèles

    -
    -

    Les formules R

    -

    En réalité, la notation par formule qu’utilise R est celle proposée par Wilkinson et al. dans les années 70 pour schématiser la relation entre plusieurs variables dans un plan d’expérience. Plus spécifiquement, l’idée revient à exprimer une relation fonctionnelle, symbolise´e par l’opérateur ~, entre une variable re´ponse y et une ou plusieurs variables explicatives. Disons, pour simplifier, que y est une variable d’intérêt (numérique ou facteur selon le type de modèle), x une variable numérique et que a et b sont des variables catégorielles (des facteurs dans le langage R). Voici les principales relations auxquelles on peut s’intéresser dans un modèle statistique :

    -
      -
    • -y ~ x : régression simple,
    • -
    • -y ~ x + 0 : idem avec suppression du terme d’ordonnée à l’origine,
    • -
    • -y ~ a + b : régresse avec deux effets principaux indépendants,
    • -
    • -y ~ a * b : idem avec interaction (équivalent à 1 + a + b + a:b),
    • -
    • -y ~ a / b : idem en considérant une relation d’emboîtement (équivalent à 1 + a + b + a %in% b).
    • -
    -

    L’opérateur | est quant à lui utilisé par l’extension lme4 dans le cadre de modèles mixtes avec effets aléatoires.

    -

    Voir le chapitre dédié à la régression logistique pour des exemples de modèles multivariés et le chapitre dédié aux effets d’interaction pour plus de détails sur cette notion.

    +
    +

    La fonction Vectorize

    +

    La fonction Vectorize permet de convertir une fonction scalaire en une fonction vectorielle. Attention, cela ne permet pas d’améliorer la rapidité d’exécution du code. Par contre, son utilisation assez intuitive permet de gagner du temps. Il s’agit donc de faire l’arbitrage entre le temps passé à trouver un moyen élégant et efficace pour effectuer une opération en passant par de réels calculs vectoriels et le gain d’exécution que ce calcul vectoriel apporte vis-à-vis d’une boucle. La syntaxe de la Vectorize est la suivante :

    +
    Vectorize(FUN, vectorize.args, SIMPLIFY, USE.NAMES)
    +

    avec FUN une fonction à appliquer, vectorize.args un vecteur de paramètres (de type caractère) qui devraient être vectorisés (par défaut, tous les paramètre de FUN). Les paramètres SIMPLIFY et USE.NAMES on le même emploi que dans la fonction sapply.

    +
    f <- function(x = 1:3, y) c(x, y)
    +# On "vectorise" la fonction f
    +vf <- Vectorize(f, SIMPLIFY = FALSE)
    +f(1:3, 1:3)
    +
    [1] 1 2 3 1 2 3
    +
    vf(1:3, 1:3)
    +
    [[1]]
    +[1] 1 1
    +
    +[[2]]
    +[1] 2 2
    +
    +[[3]]
    +[1] 3 3
    +
    # Vectorise seulement y, pas x
    +vf(y = 1:3)
    +
    [[1]]
    +[1] 1 2 3 1
    +
    +[[2]]
    +[1] 1 2 3 2
    +
    +[[3]]
    +[1] 1 2 3 3
    + +
    -
    -

    Pour aller plus loin

    -

    Comme vient de le voir dans ce chapitre, la notation formule apparaît çà et là dans les différentes fonctions de R est de ses extensions. Il est par conséquent utile d’en connaître les rudiments, et en particulier les opérateurs ~ (tilde) et +, ne serait-ce que pour pouvoir se servir des différentes fonctions présentées sur cette page. Le chapitre lattice et les formules fournit plus de détails sur ces aspects.

    -

    La notation formule devient cruciale dès que l’on souhaite rédiger des modèles : la formule y ~ x, par exemple, qui est équivalente à la formule y ~ 1 + x, correspond à l’équation mathématique Y = a + bX. On trouvera de nombreux exemples d’usage de cette notation dans les chapitres consacrés, notamment, à la régression linéaire ou à la régression logistique.

    -

    De la même manière, l’opérateur | (pipe) utilisé par l’extension lattice joue aussi un rôle très important dans la rédaction de modèles multi-niveaux, où il sert à indiquer les variables à pentes ou à coefficients aléatoires. Ces modèles sont présentés dans un chapitre dédié.

    + +
    -
    +
    -

    Structures conditionnelles

    +

    Expressions régulières

    - -
    -

    La version originale de ce chapitre a été écrite par Ewen Gallic dans le cadre de son support de cours d’Ewen Gallic intitulé Logiciel R et programmation, chapitre 4 Boucles et calculs vectoriels.

    -
    -

    Il existe deux sortes de boucles dans R. Celles pour lesquelles les itérations continuent tant qu’une condition n’est pas invalidée (while), et celles pour lesquelles le nombre d’itérations est défini au moment de lancer la boucle (for).

    -

    Avant de présenter chacune de ces fonctions, il est nécessaire de préciser que les boucles ne sont pas le point fort de R. Dès que l’on souhaite appliquer une fonction à chaque élément d’un vecteur, et/ou que le résultat de chaque itération ne dépend pas de l’itération précédente, il est préférable de vectoriser les calculs (voir le chapitre sur la vectorisation).

    -
    -

    Les boucles avec while

    -

    Quand on souhaite répéter un calcul tant qu’une condition est satisfaite, on utilise la fonction while, avec la syntaxte suivante :

    -
    while (condition) {
    -  instruction
    -}
    -

    avec condition une valeur logique (TRUE ou FALSE), et instruction du code, qui peut être entouré d’accolades si on souhaite évaluer plusieurs instructions.

    -

    Le code instruction sera répété tant que condition est vrai. Prenons l’exemple d’une plante qui mesure 10 centimètres et qui va grandir de 10 % tous les ans jusqu’à atteindre 2 mètres.

    -
    taille <- 0.10
    -duree <- 0
    -while (taille < 2) {
    -  taille <- taille * 1.1
    -  duree <- duree + 1
    -}
    -message(glue::glue("La plante a atteint {round(taille, 1)} mètres en {duree} années."))
    -
    La plante a atteint 2.1 mètres en 32 années.
    -
    -
    -

    Les boucles avec for

    -

    Quand on connaît le nombre d’itérations à l’avance, on peut utiliser la boucle for. La syntaxe est la suivante :

    -
    for (variable in vector) {
    -  instruction
    -}
    -

    avec variable le nom d’une variable locale à la boucle for, vector un vecteur à n éléments définissant les valeurs que prendra variable pour chacun des n tours, et instruction le code à exécuter à chaque itération.

    -

    On peut utiliser for pour remplir les éléments d’une liste, ou d’un vecteur.

    -
    # Mauvaise manière
    -resultat <- NULL
    -for (i in 1:3) {
    -  resultat[i] <- i
    -}
    -resultat
    -
    [1] 1 2 3
    -

    À chaque itération, R doit trouver le vecteur de destination en mémoire, créer un nouveau vecteur qui permettra de contenir plus de données, copier données depuis l’ancien vecteur pour les insérer dans le nouveau, et enfin supprimer l’ancien vecteur (Ross, 2014). C’est une opération coûteuse en temps. Un moyen de rendre cette allocation plus efficace est de créer a priori le vecteur ou la liste en le remplissant avec des données manquantes. Ainsi, R n’aura pas besoin de ré-allouer la mémoire à chaque itération.

    -
    # Manière plus économique
    -resultat <- rep(NA, 3)
    -for (i in 1:3) {
    -  resultat[i] <- i
    -}
    -resultat
    -
    [1] 1 2 3
    -
    -
    -

    Les conditions

    -

    On peut soumettre l’exécution de codes en R à conditions que certaines conditions soient honorées.

    -
    -

    Les instructions if … else

    -

    Les instructions if et else fournissent un moyen d’exécuter du code si une condition est respectée ou non. La syntaxe prend deux formes :

    -
    # Première forme (pas de code si condition == FALSE)
    -if (condition) {instruction si vrai}
    -
    -# Seconde forme
    -if (condition) {instruction si vrai} else {instruction si faux}
    -

    avec condition un logique, instruction si vrai le code à exécuter si la condition est vérifiée et instruction si faux le code à exécuter si la condition n’est pas remplie. À nouveau, on peut avoir recours aux accolades pour créer des regroupements.

    -
    # Simple condition
    -x <- 2
    -if (x == 2) print("Hello")
    -
    [1] "Hello"
    -
    x <- 3
    -if (x == 2) print("Hello")
    -
    -# Avec des instructions dans le cas contraire
    -if (x == 2) print("Hello") else print("x est différent de 2")
    -
    [1] "x est différent de 2"
    -
    if (x == 2) {
    -  print("Hello")
    -} else {
    -  x <- x - 1
    -  print(paste0("La nouvelle valeur de x : ", x))
    -}
    -
    [1] "La nouvelle valeur de x : 2"
    -
    -

    Attention, lorsque l’on fait des regroupements et qu’on utilise la structure if et else, il est nécessaire d’écrire le mot else sur la même ligne que la parenthèse fermante du groupe d’instructions à réaliser dans le cas où la condition du if est vérifiée.

    -
    -
    -
    -

    La fonction switch

    -

    Avec la fonction switch, on peut indiquer à R d’exécuter un code en fonction du résultat obtenu lors d’un test. La syntaxe est la suivante :

    -
    switch(valeur_test,
    -  cas_1 = {
    -    instruction_cas_1
    -  },
    -  cas_2 = {
    -    instruction_cas_2
    -  },
    -  ...
    -)
    -

    avec valeur_test un nombre ou une chaîne de caractères. Si valeur_test vaut cas_1, alors uniquement instruction_cas_1 sera évaluée, si valeur_test vaut cas_2, alors ce sera instruction_cas_2 qui le sera, et ainsi de suite. On peut rajouter une valeur par défaut en utilisant la syntaxte suivante :

    -
    switch(valeur_test,
    -  cas_1 = {
    -    instruction_cas_1
    -  },
    -  cas_2 = {
    -    instruction_cas_2
    -  },
    -  ...,
    -  {
    -    instruction_defaut
    -  }
    -)
    -

    Voici un exemple d’utilisation, issu de la page d’aide de la fonction.

    -
    centre <- function(x, type) {
    -  switch(type,
    -    mean = mean(x),
    -    median = median(x),
    -    trimmed = mean(x, trim = .1)
    -  )
    -}
    -x <- rcauchy(10)
    -centre(x, "mean")
    -
    [1] 2.178666
    -
    centre(x, "median")
    -
    [1] 0.8209592
    -
    centre(x, "trimmed")
    -
    [1] 1.122283
    -
    -
    -
    -

    L’instruction repeat … break

    -

    L’instruction repeat permet de répéter une expression. Il est nécessaire d’ajouter un test d’arrêt, à l’aide de l’instruction break.

    -
    i <- 1
    -repeat {
    -  i <- i + 1
    -  if (i == 3) break
    -}
    -i
    -
    [1] 3
    -
    -
    -

    L’instruction next

    -

    L’instruction next autorise de passer immédiatement à l’itération suivante d’une boucle for, while ou repeat.

    -
    result <- rep(NA, 10)
    -for (i in 1:10) {
    -  if (i == 5) next
    -  result[i] <- i
    -}
    -# Le 5e élément de result n'a pas été traité
    -result
    -
     [1]  1  2  3  4 NA  6  7  8  9 10
    -
    -
    -

    Barre de progression

    -

    Lorsque l’exécution d’une boucle prend du temps, il peut être intéressant d’avoir une idée de l’état d’avancement des itérations. Pour cela, il est bien sûr possible d’afficher une valeur dans la console à chaque tour, chaque 10 tours, etc.

    -

    La fonction txtProgressBar de l’extension utils permet un affichage d’une barre de progression dans la console. Il suffit de lui fournir une valeur minimale et maximale, et de la mettre à jour à chaque itération. Le paramètre style autorise de surcroit à choisir un style pour la barre. Le style numéro 3 affiche un pourcentage de progression, et est utile lorsque d’autres résultats sont affichés dans la console lors de l’exécution de la boucle, dans la mesure où la barre est de nouveau affichée au complet dans la console si nécessaire.

    -

    Dans l’exemple qui suit, à chacun des dix tours, une pause de 0.1 seconde est effectuée, puis la barre de progression est mise à jour.

    -
    nb_tours <- 10
    -pb <- txtProgressBar(min = 1, max = nb_tours, style = 3)
    -for (i in 1:nb_tours) {
    -  Sys.sleep(0.1)
    -  setTxtProgressBar(pb, i)
    -}
    -
    
    -  |                                                        
    -  |                                                  |   0%
    -  |                                                        
    -  |======                                            |  11%
    -  |                                                        
    -  |===========                                       |  22%
    -  |                                                        
    -  |=================                                 |  33%
    -  |                                                        
    -  |======================                            |  44%
    -  |                                                        
    -  |============================                      |  56%
    -  |                                                        
    -  |=================================                 |  67%
    -  |                                                        
    -  |=======================================           |  78%
    -  |                                                        
    -  |============================================      |  89%
    -  |                                                        
    -  |==================================================| 100%
    -

    Pour plus d’options, on pourra se référer à la fonction progress_bar de l’extension progress, présentée en détail sur https://r-pkg.org/pkg/progress.

    -

    Si l’exécution est vraiment longue, et qu’on est impatient de connaître les résultats, il existe de plus une fonction amusante dans l’extension beepr, qui porte le nom de beep. Plusieurs sons peuvent être utilisés (voir la page d’aide de la fonction).

    -
    library(beepr)
    -beep("mario")
    -
    -
    -

    Pour approfondir

    -

    On pourra également consulter le chapitre Boucles et exécution conditionnelle de l’excellente Introduction à R et au tidyverse de Julien Barnier (https://juba.github.io/tidyverse).

    -
    +

    Les expressions régulières sont un outils pour rechercher / remplacer dans des chaînes de texte. Il est préférable d’avoir lu au préalable le chapitre dédié à la manipulation de texte.

    +

    Pour une introduction (en anglais) aux expressions régulières, on pourra se référer au chapitre Strings de l’ouvrage R for Data Science de Garrett Grolemund et Hadley Wickham (http://r4ds.had.co.nz/strings.html).

    +

    Pour aller plus loin, le site de l’extension stringr propose une présentation détaillée (en anglais) de la syntaxe des expressions régulières (http://stringr.tidyverse.org/articles/regular-expressions.html).

    +

    Pour des besoins plus pointus, on pourra aussi utiliser l’extension stringi sur laquelle est elle-même basée stringr.

    -
    +
    -

    Vectorisation (dont purrr)

    +

    R Markdown : les rapports automatisés

    +
    +

    Ce chapitre est évoqué dans le webin-R #17 (trajectoires de soins : un exemple de données longitudinales) sur YouTube.

    +
    -

    La version originale de ce chapitre a été écrite par Ewen Gallic dans le cadre de son support de cours d’Ewen Gallic intitulé Logiciel R et programmation, chapitre 4 Boucles et calculs vectoriels.

    +

    La version originale de ce chapitre a été écrite par Julien Barnier dans le cadre de son Introduction à R et au tidyverse.

    -

    Les boucles sont des opérations lentes en R. Il est cependant possible, dans de nombreux cas, d’éviter de les employer, en ayant recours à la vectorisation : au lieu d’appliquer une fonction à un scalaire, on l’applique à un vecteur. En fait, nous avons déjà eu recours à maintes reprises aux calculs vectoriels. En effet, lorsque nous avons procédé à des additions, des multiplications, etc. sur des vecteurs, nous avons effectué des calculs vectoriels.

    -

    Empruntons un exemple à Burns (2011) : dans des langages comme le C, pour effectuer la somme des logarithmes naturels des n premiers entiers, voici une manière de faire :

    -
    # Somme des logarithmes des 10 premiers entiers
    -somme_log <- 0
    -for (i in seq_len(10)) {
    -  somme_log <- somme_log + log(i)
    -}
    -somme_log
    -
    [1] 15.10441
    -

    Il est possible d’obtenir le même résultat, à la fois d’une manière plus élégante, mais surtout plus efficace en vectorisant le calcul :

    -
    sum(log(seq_len(10)))
    -
    [1] 15.10441
    -

    Derrière ce code, la fonction log applique la fonction logarithme sur toutes les valeurs du vecteur donné en paramètre. La fonction sum, quant à elle, se charge d’additionner tous les éléments du vecteur qui lui est donné en paramètre. Ces deux fonctions utilisent la vectorisation, mais d’une manière différente : la fonction log applique une opération à chaque élément d’un vecteur, tandis que la fonction sum produit un résultat basé sur l’ensemble du vecteur. L’avantage d’utiliser des fonctions vectorielles plutôt que d’écrire une boucle pour effectuer le calcul, est que ces premières font appel à des fonctions rédigées en C ou FORTRAN, qui utilisent aussi des boucles, mais comme ce sont des langages compilés et non pas interprétés, les itérations sont réalisées dans un temps réduit.

    -

    Il existe des fonctions, rédigées en C qui effectuent des boucles for. On leur donne souvent le nom de “fonctions de la famille apply”. Il ne s’agit pas de la vectorisation, mais ces fonctions sont souvent mentionnées dès que l’on parle de ce sujet. Ce sont des fonctionnelles qui prennent une fonction en input et retournent un vecteur en output (Wickham, 2014). Ces fonctions sont très utilisées, mais elles souffrent d’un manque d’uniformité. En effet, elles ont été rédigées par des personnes différentes, ayant chacune leur convention. L’extension plyr remédie à ce problème, et ajoute par la même occasion des fonctions supplémentaires, pour couvrir plus de cas que les “fonctions de la famille apply”.

    -

    Nous allons donc présenter dans un premier temps les fonctions du package plyr. Les fonctions du même type du package base seront tout de même présentées par la suite.

    -
    -

    Les fonctions de l’extension purrr

    -

    L’extension purrr du tidyverse a beaucoup évolué ces dernières années et permet d’itérer facilement des opérations sur une liste ou un tableau. Cette extension s’articule également avec les tableaux imbriqués rendus possibles par tibble et tidyr (voir la fonction nest et sa vignette dédiée : https://tidyr.tidyverse.org/articles/nest.html).

    -

    Concernant purrr, on pourra consulter le chapitre Itérer avec purrr de l’excellente Introduction à R et au tidyverse de Julien Barnier (https://juba.github.io/tidyverse).

    -

    Julien Barnier y aborde notamment la fonction map et ses variantes comme map_dbl, map_chr, map_int, map_dfr, map_dfc, modify, imap, walk

    +

    +

    L’extension rmarkdown permet de générer des documents de manière dynamique en mélangeant texte mis en forme et résultats produits par du code R. Les documents générés peuvent être au format HTML, PDF, Word, et bien d’autres1. C’est donc un outil très pratique pour l’exportation, la communication et la diffusion de résultats d’analyse.

    +

    Le présent document a lui-même été généré à partir de fichiers R Markdown.

    +

    rmarkdown ne fait pas partie du tidyverse, mais elle est installée et chargée par défaut par RStudio2.

    +
    +

    Rapport rapide à partir d’un script R

    +

    Si vos analyses sont présentes dans un script R et que ce script contient tout le nécessaire pour la réalisation de votre analyse (i.e. chargement des données et des packages requis), vous pouvez très facilement réaliser un rapport rapide au format HTML, Word ou PDF, contenant à la fois votre code et les sorties associées.

    +

    Il suffit de cliquer sur l’icône compile report dans le quadrant supérieur gauche.

    +
    +

    position de l’icône compile report

    -
    -

    Les fonctions de l’extension plyr

    -

    Les fonctions que nous allons aborder dans cette section possèdent des noms faciles à se remémorer : la première lettre correspond au format d’entrée des données, la seconde au format de sortie souhaité, et la fin du nom se termine par le suffixe ply. Ainsi, la fonction llpply prend en entrée une liste, effectue une opération sur les éléments, et retourne une liste (Anderson, 2012).

    -

    Les différentes fonctions que nous allons passer en revue sont consignées dans le tableau ci-après, où les lignes correspondent aux formats d’entrée, et les lignes aux formats de sortie. Pour y avoir accès, il faut charger le package :

    -
    library(plyr)
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    -Format de sortie +

    Position de l’icône compile report

    +

    De manière alternative, si votre script s’intitule analyses.R, vous pouvez exécuter la commande suivante :

    +
    rmarkdown::render("analyses.R")
    +

    Pour plus d’informations sur les options disponibles, vous pouvez également consulter https://rmarkdown.rstudio.com/articles_report_from_r_script.html.

    -
    - - -array - -data.frame - -list -
    -Format d’entée - -array - -aaply - -adply - -alply -
    -Format d’entée - -data.frame - -daply - -ddply - -dlply -
    -Format d’entée - -list - -laply - -ldply - -llply -
    -

    Il est possible d’avoir plusieurs paramètres en input au lieu d’un seul objet. Les fonctions mlply, mdply et maply. Si à la place du m, la première lettre est un r, il s’agit alors de fonction de réplications. Enfin, si la seconde lettre est un trait de soulignement (_), alors le résultat retourné n’est pas affiché (le code utilise la fonction invisible.

    -

    Tous les paramètres de ces fonctions commencent par un point (.), afin d’éviter des incompatibilités avec la fonction à appliquer.

    -
    -

    Array en input : a*ply -

    -

    Les fonctions aaply, adply et alply appliquent une fonction à chaque portion d’un array et ensuitent joignent le résultat sous forme d’un array, d’un data.frame ou d’une list respectivement.

    -
    -

    Un array peut être vu comme un vecteur à plusieurs dimensions. Comme pour un vecteur, toutes les valeurs doivent être du même type. Un vecteur n’est finalement qu’un array à une seule dimension. De même, un array à deux dimensions correspond à ce qu’on appelle usuelement une matrice.

    +
    +

    Un premier exemple

    +

    Voici un exemple de document R Markdown minimal :

    +
    ---
    +title: "Test R Markdown"
    +---
    +
    +
    +*R Markdown* permet de mélanger :
    +
    +- du texte libre mis en forme
    +- des blocs de code R
    +
    +Les blocs de code sont exécutés et leur résultat affiché, par exemple :
    +
    +```{r}
    +mean(mtcars$mpg)
    +```
    +
    +## Graphiques
    +
    +On peut également inclure des graphiques :
    +
    +```{r}
    +plot(mtcars$hp, mtcars$mpg)
    +```
    +

    Ce document peut être compilé sous différents formats. Lors de cette étape, le texte est mis en forme, les blocs de code sont exécutés, leur résultat ajouté au document, et le tout est transformé dans un des différents formats possibles.

    +

    Voici le rendu du document précédent au format HTML :

    +
    +

    Rendu HTML

    +
    +

    Rendu HTML

    +

    Le rendu du même document au format PDF :

    +
    +

    Rendu PDF

    +
    +

    Rendu PDF

    +

    Et le rendu au format Word :

    +
    +

    Rendu docx

    +
    +

    Rendu docx

    +

    Les avantages de ce système sont nombreux :

    +
      +
    • le code et ses résultats ne sont pas séparés des commentaires qui leur sont associés
    • +
    • le document final est reproductible
    • +
    • le document peut être très facilement régénéré et mis à jour, par exemple si les données source ont été modifiées.
    • +
    -

    Le paramètre .margins détermine la manière de découper le tableau. Il y en a quatre pour un tableau en deux dimensions :

    -
      -
    1. -.margins = 1 : par lignes ;
    2. -
    3. -.margins = 2 : par colonnes ;
    4. -
    5. -.margins = c(1,2) : par cellule ;
    6. -
    7. -.margins = c() : ne pas faire de découpement.
    8. -
    -

    Pour un tableau en trois dimensions, il y a trois découpages possibles en deux dimensions, trois en une dimension et une en zéro dimension (voir (Wickham, 2011)) au besoin.

    -
    tableau <- array(
    -  1:24,
    -  dim = c(3, 4, 2),
    -  dimnames = list(
    -    ligne = letters[1:3],
    -    colonne = LETTERS[1:4],
    -    annee = 2001:2002
    -  )
    -)
    -tableau
    -
    , , annee = 2001
    +
    +

    Créer un nouveau document

    +

    Un document R Markdown est un simple fichier texte enregistré avec l’extension .Rmd.

    +

    Sous RStudio, on peut créer un nouveau document en allant dans le menu File puis en choisissant New file puis R Markdown…. La boîte de dialogue suivante s’affiche :

    +
    +

    Création d’un document R Markdown

    +
    +

    Création d’un document R Markdown

    +

    On peut indiquer le titre, l’auteur du document ainsi que le format de sortie par défaut (il est possible de modifier facilement ses éléments par la suite). Plutôt qu’un document classique, on verra plus loin qu’on peut aussi choisir de créer une présentation sous forme de slides (entrée Presentation) ou de créer un document à partir d’un modèle (Entrée From Template).

    +

    Un fichier comportant un contenu d’exemple s’affiche alors. Vous pouvez l’enregistrer où vous le souhaitez avec une extension .Rmd.

    +
    +
    +

    Élements d’un document R Markdown

    +

    Un document R Markdown est donc un fichier texte qui ressemble à quelque chose comme ça :

    +
    ---
    +title: "Titre"
    +author: "Prénom Nom"
    +date: "10 avril 2017"
    +output: html_document
    +---
     
    -     colonne
    -ligne A B C  D
    -    a 1 4 7 10
    -    b 2 5 8 11
    -    c 3 6 9 12
    +```{r setup, include=FALSE}
    +knitr::opts_chunk$set(echo = TRUE)
    +```
     
    -, , annee = 2002
    +## Introduction
     
    -     colonne
    -ligne  A  B  C  D
    -    a 13 16 19 22
    -    b 14 17 20 23
    -    c 15 18 21 24
    -
    # La moyenne des valeurs pour chaque ligne
    -aaply(tableau, 1, mean) # résultat sous forme de tableau
    -
       a    b    c 
    -11.5 12.5 13.5 
    -
    adply(tableau, 1, mean) # résultat sous forme de data.frame
    - - - - - - - - - - - - - - - - - - - -
    ligneV1
    a11.5
    b12.5
    c13.5
    -
    alply(tableau, 1, mean) # résultat sous forme de liste
    -
    $`1`
    -[1] 11.5
    +Ceci est un document RMarkdown, qui mélange :
     
    -$`2`
    -[1] 12.5
    +- du texte balisé selon la syntaxe Markdown
    +- des bouts de code R qui seront exécutés
     
    -$`3`
    -[1] 13.5
    +Le code R se présente de la manière suivante :
     
    -attr(,"split_type")
    -[1] "array"
    -attr(,"split_labels")
    -  ligne
    -1     a
    -2     b
    -3     c
    -
    # La moyenne des valeurs pour chaque colonne
    -# en ne simplifiant pas le résultat
    -aaply(tableau, 2, mean, .drop = FALSE)
    - - - - - - - - - - - - - - - - - - - - - - - -
    1
    A8
    B11
    C14
    D17
    -
    # Par lignes et colonnes
    -aaply(tableau, c(1, 2), mean)
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    ABCD
    a7101316
    b8111417
    c9121518
    -
    adply(tableau, c(1, 2), mean)
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    lignecolonneV1
    aA7
    bA8
    cA9
    aB10
    bB11
    cB12
    aC13
    bC14
    cC15
    aD16
    bD17
    cD18
    -
    # Avec une fonction définie par l'utilisateur
    -standardise <- function(x) (x - min(x)) / (max(x) - min(x))
    -# Standardiser les valeurs par colonne
    -aaply(tableau, 2, standardise)
    -
    , , annee = 2001
    +```{r}
    +summary(cars)
    +```
     
    -       ligne
    -colonne a          b         c
    -      A 0 0.07142857 0.1428571
    -      B 0 0.07142857 0.1428571
    -      C 0 0.07142857 0.1428571
    -      D 0 0.07142857 0.1428571
    +## Graphiques
     
    -, , annee = 2002
    +On peut aussi inclure des graphiques, par exemple :
     
    -       ligne
    -colonne         a         b c
    -      A 0.8571429 0.9285714 1
    -      B 0.8571429 0.9285714 1
    -      C 0.8571429 0.9285714 1
    -      D 0.8571429 0.9285714 1
    +```{r} +plot(pressure) +```
    +
    +

    En-tête (préambule)

    +

    La première partie du document est son en-tête. Il se situe en tout début de document, et est délimité par trois tirets (---) avant et après :

    +
    ---
    +title: "Titre"
    +author: "Prénom Nom"
    +date: "10 avril 2017"
    +output: html_document
    +---
    +

    Cet en-tête contient les métadonnées du document, comme son titre, son auteur, sa date, plus tout un tas d’options possibles qui vont permettre de configurer ou personnaliser l’ensemble du document et son rendu. Ici, par exemple, la ligne output: html_document indique que le document généré doit être au format HTML.

    -
    -

    Data.frame en input : d*ply -

    -

    Dans le cas de l’analyse d’enquêtes, on utilise principalement des tableaux de données ou data.frame. Aussi, la connaissance des fonction daply, ddply et dlply peut être utile. En effet, elles sont très utiles pour appliquer des fonctions à des groupes basés sur des combinaisons de variables, même si dans la majorité des cas il est maintenant plus facile de passer par les extensions dplyr ou data.table avec les opérations groupées (voir la section sur groub_by de dplyr ou encore celle sur le paramètre by de data.table.

    -

    Avec les fonctions d*ply, il est nécessaire d’indiquer quelles variables, ou fonctions de variables on souhaite utiliser, en l’indiquant au paramètre .variables. Elles peuvent être contenue dans le data frame fourni au paramètre .data, ou bien provenir de l’environnement global. R cherchera dans un premier temps si la variable est contenue dans le data.frame et, s’il ne trouve pas, ira chercher dans l’environnement global.

    -

    Pour indiquer que l’on désire faire le regroupement selon une variable – mettons variable_1 – il faudra fournir l’expression .(variable_1) au paramètre .variables. Si on souhaite effectuer les regroupement selon les interactions de plusieurs variables – variable_1, variable_2 et variable_3, il faut alors utiliser l’expression suivante : .(variable_1, variable_2, variable_3).

    -
    chomage <- data.frame(
    -  region = rep(c(rep("Bretagne", 4), rep("Corse", 2)), 2),
    -  departement = rep(c(
    -    "Cotes-d'Armor", "Finistere",
    -    "Ille-et-Vilaine", "Morbihan",
    -    "Corse-du-Sud", "Haute-Corse"
    -  ), 2),
    -  annee = rep(c(2011, 2010), each = 6),
    -  ouvriers = c(
    -    8738, 12701, 11390, 10228, 975, 1297,
    -    8113, 12258, 10897, 9617, 936, 1220
    -  ),
    -  ingenieurs = c(
    -    1420, 2530, 3986, 2025, 259, 254,
    -    1334, 2401, 3776, 1979, 253, 241
    -  )
    -)
    -chomage
    - +
    +

    Texte du document

    +

    Le corps du document est constitué de texte qui suit la syntaxe Markdown. Un fichier Markdown est un fichier texte contenant un balisage léger qui permet de définir des niveaux de titres ou de mettre en forme le texte. Par exemple, le texte suivant :

    +
    Ceci est du texte avec *de l'italique* et **du gras**.
    +
    +On peut définir des listes à puces :
    +
    +- premier élément
    +- deuxième élément
    +

    Génèrera le texte mis en forme suivant :

    +
    +

    Ceci est du texte avec de l’italique et du gras.

    +

    On peut définir des listes à puces :

    +
      +
    • premier élément
    • +
    • deuxième élément
    • +
    +
    +

    On voit que des mots placés entre des astérisques sont mis en italique, des lignes qui commencent par un tiret sont transformés en liste à puce, etc.

    +

    On peut définir des titres de différents niveaux en faisant débuter une ligne par un ou plusieurs caractères # :

    +
    # Titre de niveau 1
    +
    +## Titre de niveau 2
    +
    +### Titre de niveau 3
    +

    Quand des titres ont été définis, si vous cliquez sur l’icône Show document outline totalement à droite de la barre d’outils associée au fichier R Markdown, une table des matières dynamique générée automatiquement à partir des titres présents dans le document s’affiche et vous permet de naviguer facilement dans celui-ci :

    +
    +

    Table des matières dynamique

    +
    +

    Table des matières dynamique

    +

    La syntaxe Markdown permet d’autres mises en forme, comme la possibilité d’insérer des liens ou des images. Par exemple, le code suivant :

    +
    [Exemple de lien](https://example.com)
    +

    Donnera le lien suivant :

    +
    +

    Exemple de lien

    +
    +

    Dans RStudio, le menu Help puis Markdown quick reference donne un aperçu plus complet de la syntaxe.

    +
    +
    +

    Blocs de code R

    +

    En plus du texte libre au format Markdown, un document R Markdown contient, comme son nom l’indique, du code R. Celui-ci est inclus dans des blocs (chunks) délimités par la syntaxe suivante :

    +
    ```{r}
    +x <- 1:5
    +```
    +

    Comme cette suite de caractères n’est pas très simple à saisir, vous pouvez utiliser le menu Insert de RStudio et choisir R3, ou utiliser le raccourci clavier Ctrl+Alt+i.

    +
    +

    Menu d’insertion d’un bloc de code

    +
    +

    Menu d’insertion d’un bloc de code

    +

    Dans RStudio les blocs de code R sont en général affichés avec une couleur de fond légèrement différente pour les distinguer du reste du document.

    +

    Quand votre curseur se trouve dans un bloc, vous pouvez saisir le code R que vous souhaitez, l’exécuter, utiliser l’autocomplétion, exactement comme si vous vous trouviez dans un script R. Vous pouvez également exécuter l’ensemble du code contenu dans un bloc à l’aide du raccourci clavier Ctrl+Shift+Entrée.

    +

    Dans RStudio, par défaut, les résultats d’un bloc de code (texte, tableau ou graphique) s’affichent directement dans la fenêtre d’édition du document, permettant de les visualiser facilement et de les conserver le temps de la session 4.

    +

    Lorsque le document est compilé au format HTML, PDF ou docx, chaque bloc est exécuté tour à tour, et le résultat inclus dans le document final, qu’il s’agisse de texte, d’un tableau ou d’un graphique. Les blocs sont liés entre eux, dans le sens où les données importées ou calculées dans un bloc sont accessibles aux blocs suivants. On peut donc aussi voir un document R Markdown comme un script R dans lequel on aurait intercalé du texte libre au format Markdown.

    +
    +

    À noter qu’avant chaque compilation, une nouvelle session R est lancée, ne contenant aucun objet. Les premiers blocs de code d’un document sont donc souvent utilisés pour importer des données, exécuter des recodages, etc.

    +
    +
    +
    +

    Compiler un document (Knit)

    +

    On peut à tout moment compiler, ou plutôt tricoter (Knit), un document R Markdown pour obtenir et visualiser le document généré. Pour cela, il suffit de cliquer sur le bouton Knit et de choisir le format de sortie voulu :

    +
    +

    Menu Knit

    +
    +

    Menu Knit

    +

    Vous pouvez aussi utiliser le raccourci Ctrl+Shift+K pour compiler le document dans le dernier format utilisé.

    +
    +

    Pour la génération du format PDF, vous devez avoir une installation fonctionnelle de LaTeX sur votre système. C’est en général le cas pour des ordinateurs Mac ou Linux, mais pas sous Windows : dans ce cas vous devrez installer une distribution comme MiKTeX.

    +
    +

    Un onglet R Markdown s’ouvre dans la même zone que l’onglet Console et indique la progression de la compilation, ainsi que les messages d’erreur éventuels. Si tout se passe bien, Le document devrait s’afficher soit dans une fenêtre Viewer de RStudio (pour la sortie HTML), soit dans le logiciel par défaut de votre ordinateur.

    +
    + +
    +

    Personnaliser le document généré

    +

    La personnalisation du document généré se fait en modifiant des options dans le préambule du document. RStudio propose néanmoins une petite interface graphique permettant de changer ces options plus facilement. Pour cela, cliquez sur l’icône en forme d’engrenage à droite du bouton Knit et choisissez Output Options…

    +
    +

    Options de sortie R Markdown

    +
    +

    Options de sortie R Markdown

    +

    Une boîte de dialogue s’affiche vous permettant de sélectionner le format de sortie souhaité et, selon le format, différentes options :

    +
    +

    Dialogue d’options de sortie R Markdown

    +
    +

    Dialogue d’options de sortie R Markdown

    +

    Pour le format HTML par exemple, l’onglet General vous permet de spécifier si vous voulez une table des matières, sa profondeur, les thèmes à appliquer pour le document et la coloration syntaxique des blocs R, etc. L’onglet Figures vous permet de changer les dimensions par défaut des graphiques générés.

    +
    +

    Une option très intéressante pour les fichiers HTML, accessible via l’onglet Advanced, est l’entrée Create standalone HTML document. Si elle est cochée (ce qui est le cas par défaut), le document HTML généré contiendra en un seul fichier le code HTML mais aussi les images et toutes les autres ressources nécessaires à son affichage. Ceci permet de générer des fichiers (parfois assez volumineux) que vous pouvez transférer très facilement à quelqu’un par mail ou en le mettant en ligne quelque part. Si la case n’est pas cochée, les images et autres ressources sont placées dans un dossier à part.

    +
    +

    Lorsque vous changez des options, RStudio va en fait modifier le préambule de votre document. Ainsi, si vous choisissez d’afficher une table des matières et de modifier le thème de coloration syntaxique, votre en-tête va devenir quelque chose comme :

    +
    ---
    +title: "Test R Markdown"
    +output:
    +   html_document: 
    +     highlight: kate
    +     toc: yes
    +---
    +

    Vous pouvez modifier les options directement en éditant le préambule.

    +

    À noter qu’il est possible de spécifier des options différentes selon les formats, par exemple :

    +
    ---
    +title: "Test R Markdown"
    +output:
    +  html_document: 
    +    highlight: kate
    +    toc: yes
    +  pdf_document: 
    +    fig_caption: yes
    +    highlight: kate
    +---
    +

    La liste complète des options possibles est présente sur le site de la documentation officielle (très complet et bien fait) et sur l’antisèche et le guide de référence, accessibles depuis RStudio via le menu Help puis Cheatsheets.

    +
    +
    +

    Options des blocs de code R

    +

    Il est également possible de passer des options à chaque bloc de code R pour modifier son comportement.

    +

    On rappelle qu’on bloc de code se présente de la manière suivante :

    +
    ```{r}
    +x <- 1:5
    +```
    +

    Les options d’un bloc de code sont à placer à l’intérieur des accolades {r}.

    +
    +

    Nom du bloc

    +

    La première possibilité est de donner un nom au bloc. Celui-ci est indiqué directement après le r :

    +

    {r nom_du_bloc}

    +

    Il n’est pas obligatoire de nommer un bloc, mais cela peut être utile en cas d’erreur à la compilation, pour identifier le bloc ayant causé le problème. Attention, on ne peut pas avoir deux blocs avec le même nom.

    +
    +
    +

    Options

    +

    En plus d’un nom, on peut passer à un bloc une série d’options sous la forme option = valeur. Voici un exemple de bloc avec un nom et des options :

    +
    ```{r mon_bloc, echo = FALSE, warning = TRUE}
    +x <- 1:5
    +```
    +

    Et un exemple de bloc non nommé avec des options :

    +
    ```{r echo = FALSE, warning = FALSE}
    +x <- 1:5
    +```
    +

    Une des options la plus utile est l’option echo. Par défaut echo vaut TRUE, et le bloc de code R est inséré dans le document généré, de cette manière :

    +
    x <- 1:5
    +print(x)
    +
    [1] 1 2 3 4 5
    +

    Mais si on positionne l’option echo=FALSE, alors le code R n’est plus inséré dans le document, et seul le résultat est visible :

    +
    [1] 1 2 3 4 5
    +

    Voici une liste de quelques unes des options disponibles :

    +
    +++++ - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - + + + - - - - - + + + - - - - - + + + - -
    regiondepartementanneeouvriersingenieursOptionValeursDescription
    BretagneCotes-d’Armor201187381420
    BretagneFinistere2011127012530
    BretagneIlle-et-Vilaine2011113903986
    BretagneMorbihan2011102282025
    CorseCorse-du-Sud2011975259
    CorseHaute-Corse20111297254
    BretagneCotes-d’Armor201081131334
    BretagneFinistere2010122582401
    BretagneIlle-et-Vilaine2010108973776
    echoTRUE/FALSEAfficher ou non le code R dans le document
    BretagneMorbihan201096171979evalTRUE/FALSEExécuter ou non le code R à la compilation
    CorseCorse-du-Sud2010936253includeTRUE/FALSEInclure ou non le code R et ses résultats dans le document
    CorseHaute-Corse20101220241results“hide”/“asis”/“markup”/“hold”Type de résultats renvoyés par le bloc de code
    -
    # Total chomeurs en Bretagne et en Corse pour les années 2010 et 2011
    -# sous forme de data.frame
    -ddply(chomage, .(annee), summarise, total_chomeurs = sum(ouvriers + ingenieurs))
    - - - - - - - - + + + - - + + +
    anneetotal_chomeurs
    201053025warningTRUE/FALSEAfficher ou non les avertissements générés par le bloc
    201155803messageTRUE/FALSEAfficher ou non les messages générés par le bloc
    -
    # sous forme de array
    -daply(chomage, .(annee), summarise, total_chomeurs = sum(ouvriers + ingenieurs))
    -
    $`2010`
    -[1] 53025
    -
    -$`2011`
    -[1] 55803
    -
    # sous forme de list
    -dlply(chomage, .(annee), summarise, total_chomeurs = sum(ouvriers + ingenieurs))
    -
    $`2010`
    -  total_chomeurs
    -1          53025
    -
    -$`2011`
    -  total_chomeurs
    -1          55803
    -
    -attr(,"split_type")
    -[1] "data.frame"
    -attr(,"split_labels")
    -  annee
    -1  2010
    -2  2011
    -
    # Total chomeurs pour les années 2010 et 2011, par région du data frame
    -ddply(chomage, .(annee, region), summarise,
    -  total_chomeurs = sum(ouvriers + ingenieurs)
    -)
    +

    Il existe de nombreuses autres options décrites notamment dans guide de référence R Markdown (PDF en anglais).

    +
    +
    +

    Modifier les options

    +

    Il est possible de modifier les options manuellement en éditant l’en-tête du bloc de code, mais on peut aussi utiliser une petite interface graphique proposée par RStudio. Pour cela, il suffit de cliquer sur l’icône d’engrenage située à droite sur la ligne de l’en-tête de chaque bloc :

    +
    +

    Menu d’options de bloc de code

    +
    +

    Menu d’options de bloc de code

    +

    Vous pouvez ensuite modifier les options les plus courantes, et cliquer sur Apply pour les appliquer.

    +
    +
    +

    Options globales

    +

    On peut vouloir appliquer une option à l’ensemble des blocs d’un document. Par exemple, on peut souhaiter par défaut ne pas afficher le code R de chaque bloc dans le document final.

    +

    On peut positionner une option globalement en utilisant la fonction knitr::opts_chunk$set(). Par exemple, insérer knitr::opts_chunk$set(echo = FALSE) dans un bloc de code positionnera l’option echo = FALSE par défaut pour tous les blocs suivants.

    +

    En général, on place toutes ces modifications globales dans un bloc spécial nommé setup et qui est le premier bloc du document :

    +
    ```{r setup, include=FALSE}
    +knitr::opts_chunk$set(echo = TRUE)
    +```
    +
    +

    Par défaut RStudio exécute systématiquement le contenu du bloc setup avant d’exécuter celui d’un autre bloc.

    +
    +
    +
    +

    Mise en cache des résultats

    +

    Compiler un document R Markdown peut être long, car il faut à chaque fois exécuter l’ensemble des blocs de code R qui le constituent.

    +

    Pour accélérer cette opération, R Markdown utilise un système de mise en cache : les résultats de chaque bloc sont enregistrés dans un fichier et à la prochaine compilation, si le code et les options du bloc n’ont pas été modifiés, c’est le contenu du fichier de cache qui est utilisé, ce qui évite d’exécuter le code R.

    +
    +

    On peut activer ou désactiver la mise en cache des résultats pour chaque bloc de code avec l’option cache = TRUE ou cache = FALSE, et on peut aussi désactiver totalement la mise en cache pour le document en ajoutant knitr::opts_chunk$set(echo = FALSE) dans le premier bloc setup.

    +
    +

    Ce système de cache peut poser problème par exemple si les données source changent : dans ce cas les résultats de certains blocs peuvent ne pas être mis à jour s’ils sont présents en cache. Dans ce cas, on peut vider le cache du document, ce qui forcera un recalcul de tous les blocs de code à la prochaine compilation. Pour cela, vous pouvez ouvrir le menu Knit et choisir Clear Knitr Cache… :

    +
    +

    Menu Knit

    +
    +

    Menu Knit

    +
    +
    +
    +
    +

    Rendu des tableaux

    +
    +

    Tableaux croisés

    +

    Par défaut, les tableaux issus de la fonction table sont affichés comme ils apparaissent dans la console de R, en texte brut :

    +
    library(questionr)
    +data(hdv2003)
    +tab <- lprop(table(hdv2003$qualif, hdv2003$sexe))
    +tab
    +
                              
    +                           Homme Femme Total
    +  Ouvrier specialise        47.3  52.7 100.0
    +  Ouvrier qualifie          78.4  21.6 100.0
    +  Technicien                76.7  23.3 100.0
    +  Profession intermediaire  55.0  45.0 100.0
    +  Cadre                     55.8  44.2 100.0
    +  Employe                   16.2  83.8 100.0
    +  Autre                     36.2  63.8 100.0
    +  Ensemble                  44.8  55.2 100.0
    +

    On peut améliorer leur présentation en utilisant la fonction kable de l’extension knitr. Celle-ci fournit un formatage adapté en fonction du format de sortie. On aura donc des tableaux propres que ce soit en HTML, PDF ou aux formats traitements de texte :

    +
    library(knitr)
    +kable(tab)
    - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + + - -
    anneeregiontotal_chomeursHommeFemmeTotal
    2010Bretagne50375Ouvrier specialise47.2906452.70936100
    2010Corse2650Ouvrier qualifie78.4246621.57534100
    2011Bretagne53018Technicien76.7441923.25581100
    2011Corse2785Profession intermediaire55.0000045.00000100
    -
    # Nombre d'observations pour chaque groupe
    -ddply(chomage, .(annee, region), nrow)
    - - - - - - - - - - + + + + - - - + + + + - - - + + + + - - - + + + +
    anneeregionV1
    2010Bretagne4Cadre55.7692344.23077100
    2010Corse2Employe16.1616283.83838100
    2011Bretagne4Autre36.2069063.79310100
    2011Corse2Ensemble44.8275955.17241100
    -
    # En utilisant une fonction définie par l'utilisateur
    -ddply(chomage, .(annee, region), function(x) {
    -  moy_ouvriers <- mean(x$ouvriers)
    -  moy_ingenieurs <- mean(x$ingenieurs)
    -  data.frame(moy_ouvriers = moy_ouvriers, moy_ingenieurs = moy_ingenieurs)
    -})
    +

    Différents arguments permettent de modifier la sortie de kable. digits, par exemple, permet de spécifier le nombre de chiffres significatifs à afficher dans les colonnes de nombres :

    +
    kable(tab, digits = 1)
    - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - -
    anneeregionmoy_ouvriersmoy_ingenieursHommeFemmeTotal
    2010Bretagne10221.252372.50Ouvrier specialise47.352.7100
    2010Corse1078.00247.00Ouvrier qualifie78.421.6100
    2011Bretagne10764.252490.25Technicien76.723.3100
    2011Corse1136.00256.50Profession intermediaire55.045.0100
    -
    -
    -

    List en input : l*ply -

    -

    Les fonctions du type l*ply prennent une liste en entrée. Il n’y a donc pas de paramétrage à effectuer pour choisir un découpage, il est déjà fait.

    -
    set.seed(1)
    -liste <- list(normale = rnorm(10), logiques = c(TRUE, TRUE, FALSE), x = c(0, NA, 3))
    -# Obtenir la longueur de chaque élément de la liste
    -laply(liste, length)
    -
    [1] 10  3  3
    -
    ldply(liste, length)
    - - - - - - - - + + + + - - + + + + - - + + + + + + + + + +
    .idV1
    normale10Cadre55.844.2100
    logiques3Employe16.283.8100
    x3Autre36.263.8100
    Ensemble44.855.2100
    -
    llply(liste, length)
    -
    $normale
    -[1] 10
    +

    Pour une présentation plus avancée, on pourra également avoir recours à l’extension gtsummary et aux fonctions tbl_summary, tbl_svysummary et tbl_regression.

    +
    library(gtsummary)
    +theme_gtsummary_language("fr", decimal.mark = ",", big.mark = " ")
    +
    Setting theme `language: fr`
    +
    hdv2003 %>%
    +  dplyr::select(sexe, qualif) %>%
    +  tbl_summary(by = "sexe")
    +
    + + + + + + + + + + + + - - - + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Caractéristique +Homme, N = 8991 + +Femme, N = 1 1011 +
    qualif
    2B
    Ouvrier specialise96 (13%)107 (12%)
    3C
    Ouvrier qualifie229 (31%)63 (6,9%)
    Technicien66 (8,9%)20 (2,2%)
    Profession intermediaire88 (12%)72 (7,9%)
    Cadre145 (20%)115 (13%)
    Employe96 (13%)498 (55%)
    Autre21 (2,8%)37 (4,1%)
    Manquant158189
    +

    + 1 + + + n (%) +

    +
    -

    L’appel de do.call("rbind", x) revient à faire rbind(x[[1]], x[[2]], ..., x[[n]]) avec x une liste de taille n.

    -
    -

    La fonction sapply

    -

    La fonction sapply applique une fonction aux éléments d’un vecteur ou d’une liste et peut retourner un vecteur, une liste ou une matrice. Elle possède la syntaxe suivante :

    -
    sapply(X, FUN, simplify, USE.NAMES)
    -

    X est le vecteur ou la liste auquel on souhaite appliquer la fonction FUN. Lorsque simplify vaut FALSE, le résultat est retourné sous forme de liste, exactement comme lapply (la fonction sapply s’appuie sur la fonction lapply). Lorsque simplify vaut TRUE (par défaut), le résultat est retourné dans une forme simplifiée, si cela est possible. Si tous les éléments retournés par la fonction FUN sont des scalaires, alors sapply retourne un vecteur ; sinon, si les éléments retournés ont la même taille, sapply retourne une matrice avec une colonne pour chaque élément de X auquel la fonction FUN est appliquée. Le paramètre USE.NAMES, quand il vaut TRUE (par défaut), et si X est de type character, utilise X comme nom pour le résultat, à moins que le résultat possède déjà des noms.

    -
    (x <- list(a = 1:10, beta = exp(-3:3), logic = c(TRUE, FALSE, FALSE, TRUE)))
    -
    $a
    - [1]  1  2  3  4  5  6  7  8  9 10
    -
    -$beta
    -[1]  0.04978707  0.13533528  0.36787944  1.00000000
    -[5]  2.71828183  7.38905610 20.08553692
    -
    -$logic
    -[1]  TRUE FALSE FALSE  TRUE
    -
    # Application de la fonction quantile() à chaque élément
    -# pour obtenir la médiane et les quartiles
    -# Avec lapply()
    -lapply(x, quantile)
    -
    $a
    -   0%   25%   50%   75%  100% 
    - 1.00  3.25  5.50  7.75 10.00 
    -
    -$beta
    -         0%         25%         50%         75%        100% 
    - 0.04978707  0.25160736  1.00000000  5.05366896 20.08553692 
    -
    -$logic
    -  0%  25%  50%  75% 100% 
    - 0.0  0.0  0.5  1.0  1.0 
    -
    # Avec sapply
    -sapply(x, quantile)
    - - - - - - - + +
    +

    Tableaux de données et tris à plat

    +

    En ce qui concerne les tableaux de données (tibble ou data frame), l’affichage HTML par défaut se contente d’un affichage texte comme dans la console, très peu lisible dès que le tableau dépasse une certaine dimension.

    +

    Une alternative est d’utiliser la fonction paged_table, qui affiche une représentation HTML paginée du tableau :

    +
    rmarkdown::paged_table(hdv2003)
    +

    Une autre alternative est d’utiliser kable, ou encore la fonction datatable de l’extension DT, qui propose encore davantage d’interactivité :

    +
    DT::datatable(hdv2003)
    +
    +

    Dans tous les cas il est déconseillé d’afficher de cette manière un tableau de données de très grandes dimensions, car le fichier HTML résultant contiendrait l’ensemble des données et serait donc très volumineux.

    +
    +

    On peut définir un mode d’affichage par défaut pour tous les tableaux de données en modifiant les Output options du format HTML (onglet General, Print dataframes as), ou en modifiant manuellement l’option df_print de l’entrée html_document dans le préambule.

    +

    À noter que les tableaux issus de la fonction freq de questionr s’affichent comme des tableaux de données (et non comme des tableaux croisés).

    +
    +
    +
    +

    Autres extensions pour présenter des tableaux

    +

    Il existe de nombreuses extensions offrant des fonctionnalités de présentation enrichie des tableaux et autres objets R.

    +
    +

    kableExtra

    +

    L’extension kableExtra a pour objectif d’étendre la fonction kable de knitr avec des options comme la possibilité de regrouper des colonnes, ajouter des notes de tableau, coloriser certaines cellules…

    +
    library(kableExtra)
    +dt <- mtcars[1:5, 1:4]
    +
    +kable(dt, format = "html", caption = "Demo Table") %>%
    +  kable_styling(
    +    bootstrap_options = "striped",
    +    full_width = F
    +  ) %>%
    +  add_header_above(c(" ", "Group 1" = 2, "Group 2[note]" = 2)) %>%
    +  add_footnote(c("table footnote"))
    +
    abetalogic
    + + + + + + + + + + + + + + + - - - - - + + + + + + - - - - - + + + + + + - - - - - + + + + + + - - - - - + + + + + + - - - - - + + + + + + + + +
    +Demo Table +
    + +
    +Group 1 +
    +
    +
    +Group 2a +
    +
    + +mpg + +cyl + +disp + +hp +
    0%1.000.04978710.0
    +Mazda RX4 + +21.0 + +6 + +160 + +110 +
    25%3.250.25160740.0
    +Mazda RX4 Wag + +21.0 + +6 + +160 + +110 +
    50%5.501.00000000.5
    +Datsun 710 + +22.8 + +4 + +108 + +93 +
    75%7.755.05366901.0
    +Hornet 4 Drive + +21.4 + +6 + +258 + +110 +
    100%10.0020.08553691.0
    +Hornet Sportabout + +18.7 + +8 + +360 + +175 +
    +a table footnote +
    -
    # Exemple avec USE.NAMES
    -sapply(LETTERS[1:3], nchar)
    -
    A B C 
    -1 1 1 
    -
    sapply(LETTERS[1:3], nchar, USE.NAMES = FALSE)
    -
    [1] 1 1 1
    +

    Pour une présentation détaillée, vous pouvez vous référer aux vignettes disponibles avec l’extension.

    +

    Notamment, il est possible d’utiliser kableExtra en conjonction avec l’extension formattable pour des rendus colorés et encore plus personnalisé de vos tableaux (voir la vignette dédiée).

    +
    library(knitr)
    +library(kableExtra)
    +library(formattable)
    +library(dplyr, quietly = TRUE)
    +
    
    +Attachement du package : 'dplyr'
    +
    L'objet suivant est masqué depuis 'package:kableExtra':
    +
    +    group_rows
    +
    Les objets suivants sont masqués depuis 'package:stats':
    +
    +    filter, lag
    +
    Les objets suivants sont masqués depuis 'package:base':
    +
    +    intersect, setdiff, setequal, union
    +
    mtcars[1:5, 1:4] %>%
    +  mutate(
    +    car = row.names(.),
    +    mpg = color_tile("white", "orange")(mpg),
    +    cyl = cell_spec(cyl, "html",
    +      angle = (1:5) * 60,
    +      background = "red", color = "white", align = "center"
    +    ),
    +    disp = ifelse(disp > 200,
    +      cell_spec(disp, "html", color = "red", bold = T),
    +      cell_spec(disp, "html", color = "green", italic = T)
    +    ),
    +    hp = color_bar("lightgreen")(hp)
    +  ) %>%
    +  relocate(car) %>%
    +  kable("html", escape = FALSE, row.names = FALSE) %>%
    +  kable_styling("hover", full_width = FALSE) %>%
    +  column_spec(5, width = "3cm") %>%
    +  add_header_above(c(" ", "Hello" = 2, "World" = 2))
    + + + + + +
    + +
    +Hello
    -
    -

    La fonction vapply

    -

    La fonction vapply est similaire à sapply, mais elle possède un type de valeurs spécifié, ce qui peut rendre l’utilisation plus sûre (et parfois plus rapide). Lorsqu’on lui fournit un data.frame, vapply retourne le même résultat que sapply. Cependant, quand on lui fournit une liste vide, vapply retourne un vecteur logique de longueur nulle (ce qui est plus sensé que la liste vide que returne sapply).

    -
    vapply(X, FUN, FUN.VALUE, ..., USE.NAMES)
    -

    avec X, FUN, ... et USE.NAMES les mêmes paramètres que pour sapply. Le paramètre FUN.VALUE doit être un vecteur, un masque pour la valeur retournée par la fonction de FUN.

    -
    # Retourner le vecteur
    -sapply(cars, is.numeric)
    -
    speed  dist 
    - TRUE  TRUE 
    -
    vapply(cars, is.numeric, FUN.VALUE = logical(1))
    -
    speed  dist 
    - TRUE  TRUE 
    -
    # Avec la liste vide
    -sapply(list(), is.numeric)
    -
    list()
    -
    vapply(list(), is.numeric, FUN.VALUE = logical(1))
    -
    logical(0)
    +
    +
    +World
    -
    -

    La fonction apply

    -

    La fonction apply possède la syntaxe suivante :

    -
    apply(X, MARGIN, FUN, ...)
    -

    avec X une matrice ou un tableau, MARGIN indiquant si on souhaite appliquer la fonction FUN aux lignes (MARGIN = 1) ou aux colonnes (MARGIN = 2), et ... des paramètres supplémentaires éventuels à passer à la fonction FUN.

    -
    (X <- matrix(1:9, ncol = 3))
    - - - - - - - - - - + - - - - + + + + + + -
    147
    258
    369
    +car + +mpg + +cyl + +disp + +hp +
    -
    # Somme par ligne
    -apply(X, MARGIN = 1, sum)
    -
    [1] 12 15 18
    -
    # Somme par colonne
    -apply(X, MARGIN = 2, sum)
    -
    [1]  6 15 24
    -
    # Fonction définie par l'utilisateur
    -apply(X, MARGIN = 1, function(x) sum(x) / sum(X))
    -
    [1] 0.2666667 0.3333333 0.4000000
    -
    -
    -

    La fonction tapply

    -

    La fonction tapply s’applique à chaque cellule d’un tableau, sur des regroupements définis par les variables catégorielles fournies. La syntaxe est la suivante :

    -
    tapply(X, INDEX, FUN, ..., simplify)
    -

    avec X le tableau de données, INDEX une liste d’un ou plusieurs facteurs, chacun de même taille que X. Le paramètre FUN renseigne la fonction que l’on souhaite appliquer. Si simplify vaut FALSE, le résultat est un tableau de mode list. Sinon (par défaut), le résultat est un tableau de scalaires.

    -
    data(iris)
    -head(iris)
    - - - - - - - - + - - - - - - - - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + +
    Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
    5.13.51.40.2setosa
    4.93.01.40.2setosa
    +Mazda RX4 + +21.0 + +6 + +160 + +110 +
    4.73.21.30.2setosa
    +Mazda RX4 Wag + +21.0 + +6 + +160 + +110 +
    4.63.11.50.2setosa
    +Datsun 710 + +22.8 + +4 + +108 + +93 +
    5.03.61.40.2setosa
    +Hornet 4 Drive + +21.4 + +6 + +258 + +110 +
    5.43.91.70.4setosa
    +Hornet Sportabout + +18.7 + +8 + +360 + +175 +
    -
    # Moyenne de la longueur des sépales par espèce
    -tapply(iris$Sepal.Length, iris$Species, mean)
    -
        setosa versicolor  virginica 
    -     5.006      5.936      6.588 
    -
    # Pour retourner le résultat sous forme de liste
    -tapply(iris$Sepal.Length, iris$Species, mean, simplify = FALSE)
    -
    $setosa
    -[1] 5.006
    +
    +
    +

    gt

    +

    L’extension gt en cours de développement par l’équipe de RStudio vise, un peu sur le modèle de ggplot2, à proposer une grammaire des tableaux. L’extension n’est pour le moment compatible qu’avec un rendu HTML mais la prise en compte du format PDF est prévue. C’est une extension encore assez nouvelle mais qui risque de se développer assez rapidement vu les équipes en charge de son développement.

    +

    Pour en savoir plus : https://gt.rstudio.com/

    +
    library(gt)
    +exibble %>%
    +  gt(
    +    rowname_col = "row",
    +    groupname_col = "group"
    +  ) %>%
    +  tab_spanner(
    +    label = "Date et/ou Heure",
    +    columns = c(date, time, datetime)
    +  ) %>%
    +  fmt_number(
    +    columns = num,
    +    decimals = 2,
    +    locale = "fr"
    +  ) %>%
    +  fmt_date(
    +    columns = date,
    +    date_style = 7
    +  ) %>%
    +  fmt_time(
    +    columns = time,
    +    time_style = 4
    +  ) %>%
    +  fmt_datetime(
    +    columns = datetime,
    +    date_style = 6,
    +    time_style = 4
    +  ) %>%
    +  fmt_currency(
    +    columns = currency,
    +    currency = "EUR",
    +    locale = "fr"
    +  ) %>%
    +  tab_options(
    +    column_labels.font.size = "small",
    +    table.font.size = "small",
    +    row_group.font.size = "small",
    +    data_row.padding = px(3)
    +  ) %>%
    +  summary_rows(
    +    groups = TRUE,
    +    columns = num,
    +    fns = list(
    +      moyenne = ~ mean(., na.rm = TRUE),
    +      total = ~ sum(., na.rm = TRUE)
    +    )
    +  ) %>%
    +  grand_summary_rows(
    +    columns = currency,
    +    fns = list("grand total" = ~ sum(., na.rm = TRUE)),
    +    formatter = fmt_currency,
    +    currency = "EUR"
    +  )
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    numcharfctr + Date et/ou Heure + currency
    datetimedatetime
    grp_a
    row_10,11apricotone15 janv. 20151:35 janv. 1, 2018 2:22 €49,95
    row_22,22bananatwo15 févr. 20152:40 févr. 2, 2018 2:33 €17,95
    row_333,33coconutthree15 mars 20153:45 mars 3, 2018 3:44 €1,39
    row_4444,40durianfour15 avr. 20154:50 avr. 4, 2018 3:55 €65 100,00
    moyenne120.02
    total480.06
    grp_b
    row_55 550,00NAfive15 mai 20155:55 mai 5, 2018 4:00 €1 325,81
    row_6NAfigsix15 juin 2015NAjuin 6, 2018 4:11 €13,26
    row_7777 000,00grapefruitsevenNA7:10 juil. 7, 2018 5:22 NA
    row_88 880 000,00honeydeweight15 août 20158:20 NA€0,44
    moyenne3,220,850.00
    total9,662,550.00
    grand total€66,508.79
    -

    Position de l’icône compile report

    -

    De manière alternative, si votre script s’intitule analyses.R, vous pouvez exécuter la commande suivante :

    -
    rmarkdown::render("analyses.R")
    -

    Pour plus d’informations sur les options disponibles, vous pouvez également consulter https://rmarkdown.rstudio.com/articles_report_from_r_script.html.

    -
    -

    Un premier exemple

    -

    Voici un exemple de document R Markdown minimal :

    -
    ---
    -title: "Test R Markdown"
    ----
    -
    +
    +

    gtExtras

    +

    L’extension gtExtras propose des fonctions additionnelles pour personnaliser encore plus les tableaux réalisés avec gt. Attention : il s’agit encore d’une extension expérimentale en développement.

    +

    Plus d’informations sur https://jthomasmock.github.io/gtExtras/

    +
    library(gtExtras)
    +
    +data.frame(x = c(1.2345, 12.345, 123.45, 1234.5, 12345)) %>%
    +  gt() %>%
    +  fmt(fns = function(x) {
    +    pad_fn(x, nsmall = 4)
    +  }) %>%
    +  tab_style(
    +    # MUST USE A MONO-SPACED FONT
    +    style = cell_text(font = google_font("Fira Mono")),
    +    locations = cells_body(columns = x)
    +  )
    +
    + + + + + + + + + + +
    x
    1.2345
    12.345&nbsp
    123.45&nbsp&nbsp
    1234.5&nbsp&nbsp&nbsp
    12345.&nbsp&nbsp&nbsp&nbsp
    -

    Pour une présentation plus avancée, on pourra également avoir recours à l’extension gtsummary et aux fonctions tbl_summary, tbl_svysummary et tbl_regression.

    -
    library(gtsummary)
    -theme_gtsummary_language("fr", decimal.mark = ",", big.mark = " ")
    -
    Setting theme `language: fr`
    -
    hdv2003 %>%
    -  dplyr::select(sexe, qualif) %>%
    -  tbl_summary(by = "sexe")
    -
    +
    +
    mtcars %>%
    +  head() %>%
    +  gt() %>%
    +  gt_color_rows(
    +    mpg:disp,
    +    palette = c("white", "green"),
    +    # could also use palette = c("#ffffff", "##00FF00")
    +    use_paletteer = FALSE
    +  )
    +
    Warning: Domain not specified, defaulting to observed range
    +within each specified column.
    +
    - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Caractéristique -Homme, N = 8991 - -Femme, N = 1 1011 -mpgcyldisphpdratwtqsecvsamgearcarb
    qualif
    Ouvrier specialise96 (13%)107 (12%)
    Ouvrier qualifie229 (31%)63 (6,9%)
    Technicien66 (8,9%)20 (2,2%)
    Profession intermediaire88 (12%)72 (7,9%)
    Cadre145 (20%)115 (13%)
    Employe96 (13%)498 (55%)
    Autre21 (2,8%)37 (4,1%)
    Manquant158189
    -

    - 1 - - - n (%) -

    -
    -
    -
    -
    -

    Tableaux de données et tris à plat

    -

    En ce qui concerne les tableaux de données (tibble ou data frame), l’affichage HTML par défaut se contente d’un affichage texte comme dans la console, très peu lisible dès que le tableau dépasse une certaine dimension.

    -

    Une alternative est d’utiliser la fonction paged_table, qui affiche une représentation HTML paginée du tableau :

    -
    rmarkdown::paged_table(hdv2003)
    -

    Une autre alternative est d’utiliser kable, ou encore la fonction datatable de l’extension DT, qui propose encore davantage d’interactivité :

    -
    DT::datatable(hdv2003)
    -
    -

    Dans tous les cas il est déconseillé d’afficher de cette manière un tableau de données de très grandes dimensions, car le fichier HTML résultant contiendrait l’ensemble des données et serait donc très volumineux.

    -
    -

    On peut définir un mode d’affichage par défaut pour tous les tableaux de données en modifiant les Output options du format HTML (onglet General, Print dataframes as), ou en modifiant manuellement l’option df_print de l’entrée html_document dans le préambule.

    -

    À noter que les tableaux issus de la fonction freq de questionr s’affichent comme des tableaux de données (et non comme des tableaux croisés).

    -
    -
    -
    -

    Autres extensions pour présenter des tableaux

    -

    Il existe de nombreuses extensions offrant des fonctionnalités de présentation enrichie des tableaux et autres objets R.

    -
    -

    kableExtra

    -

    L’extension kableExtra a pour objectif d’étendre la fonction kable de knitr avec des options comme la possibilité de regrouper des colonnes, ajouter des notes de tableau, coloriser certaines cellules…

    -
    library(kableExtra)
    -dt <- mtcars[1:5, 1:4]
    -
    -kable(dt, format = "html", caption = "Demo Table") %>%
    -  kable_styling(
    -    bootstrap_options = "striped",
    -    full_width = F
    -  ) %>%
    -  add_header_above(c(" ", "Group 1" = 2, "Group 2[note]" = 2)) %>%
    -  add_footnote(c("table footnote"))
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -Demo Table -
    - -
    -Group 1 -
    -
    -
    -Group 2a -
    -
    - -mpg - -cyl - -disp - -hp -
    -Mazda RX4 - -21.0 - -6 - -160 - -110 -
    -Mazda RX4 Wag - -21.0 - -6 - -160 - -110 -
    -Datsun 710 - -22.8 - -4 - -108 - -93 -
    -Hornet 4 Drive - -21.4 - -6 - -258 - -110 -
    -Hornet Sportabout - -18.7 - -8 - -360 - -175 -
    -a table footnote -
    -

    Pour une présentation détaillée, vous pouvez vous référer aux vignettes disponibles avec l’extension.

    -

    Notamment, il est possible d’utiliser kableExtra en conjonction avec l’extension formattable pour des rendus colorés et encore plus personnalisé de vos tableaux (voir la vignette dédiée).

    -
    library(knitr)
    -library(kableExtra)
    -library(formattable)
    -library(dplyr, quietly = TRUE)
    -
    
    -Attachement du package : 'dplyr'
    -
    L'objet suivant est masqué depuis 'package:kableExtra':
    -
    -    group_rows
    -
    Les objets suivants sont masqués depuis 'package:stats':
    -
    -    filter, lag
    -
    Les objets suivants sont masqués depuis 'package:base':
    -
    -    intersect, setdiff, setequal, union
    -
    mtcars[1:5, 1:4] %>%
    -  mutate(
    -    car = row.names(.),
    -    mpg = color_tile("white", "orange")(mpg),
    -    cyl = cell_spec(cyl, "html",
    -      angle = (1:5) * 60,
    -      background = "red", color = "white", align = "center"
    -    ),
    -    disp = ifelse(disp > 200,
    -      cell_spec(disp, "html", color = "red", bold = T),
    -      cell_spec(disp, "html", color = "green", italic = T)
    -    ),
    -    hp = color_bar("lightgreen")(hp)
    -  ) %>%
    -  relocate(car) %>%
    -  kable("html", escape = FALSE, row.names = FALSE) %>%
    -  kable_styling("hover", full_width = FALSE) %>%
    -  column_spec(5, width = "3cm") %>%
    -  add_header_above(c(" ", "Hello" = 2, "World" = 2))
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    - -
    -Hello -
    -
    -
    -World -
    -
    -car - -mpg - -cyl - -disp - -hp -
    -Mazda RX4 - -21.0 - -6 - -160 - -110 -
    -Mazda RX4 Wag - -21.0 - -6 - -160 - -110 -
    -Datsun 710 - -22.8 - -4 - -108 - -93 -
    -Hornet 4 Drive - -21.4 - -6 - -258 - -110 -
    -Hornet Sportabout - -18.7 - -8 - -360 - -175 -21.061601103.902.62016.460144
    21.061601103.902.87517.020144
    22.84108933.852.32018.611141
    21.462581103.083.21519.441031
    18.783601753.153.44017.020032
    18.162251052.763.46020.221031
    -
    -

    gt

    -

    L’extension gt en cours de développement par l’équipe de RStudio vise, un peu sur le modèle de ggplot2, à proposer une grammaire des tableaux. L’extension n’est pour le moment compatible qu’avec un rendu HTML mais la prise en compte du format PDF est prévue. C’est une extension encore assez nouvelle mais qui risque de se développer assez rapidement vu les équipes en charge de son développement.

    -
    library(gt)
    -
    
    -Attachement du package : 'gt'
    -
    L'objet suivant est masqué depuis 'package:formattable':
    -
    -    currency
    -
    exibble %>%
    -  gt(
    -    rowname_col = "row",
    -    groupname_col = "group"
    -  ) %>%
    -  tab_spanner(
    -    label = "Date et/ou Heure",
    -    columns = vars(date, time, datetime)
    -  ) %>%
    -  fmt_number(
    -    columns = vars(num),
    -    decimals = 2,
    -    locale = "fr"
    -  ) %>%
    -  fmt_date(
    -    columns = vars(date),
    -    date_style = 7
    -  ) %>%
    -  fmt_time(
    -    columns = vars(time),
    -    time_style = 4
    -  ) %>%
    -  fmt_datetime(
    -    columns = vars(datetime),
    -    date_style = 6,
    -    time_style = 4
    -  ) %>%
    -  fmt_currency(
    -    columns = vars(currency),
    -    currency = "EUR",
    -    locale = "fr"
    -  ) %>%
    -  tab_options(
    -    column_labels.font.size = "small",
    -    table.font.size = "small",
    -    row_group.font.size = "small",
    -    data_row.padding = px(3)
    -  ) %>%
    -  summary_rows(
    -    groups = TRUE,
    -    columns = vars(num),
    -    fns = list(
    -      moyenne = ~ mean(., na.rm = TRUE),
    -      total = ~ sum(., na.rm = TRUE)
    -    )
    -  ) %>%
    -  grand_summary_rows(
    -    columns = vars(currency),
    -    fns = list("grand total" = ~ sum(., na.rm = TRUE)),
    -    formatter = fmt_currency,
    -    currency = "EUR"
    -  )
    -
    Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    -Warning: `columns = vars(...)` has been deprecated in gt 0.3.0:
    -* please use `columns = c(...)` instead
    -
    +
    mtcars %>%
    +  head() %>%
    +  dplyr::select(cyl, mpg) %>%
    +  dplyr::mutate(
    +    mpg_pct_max = round(mpg / max(mpg) * 100, digits = 2),
    +    mpg_scaled = mpg / max(mpg) * 100
    +  ) %>%
    +  dplyr::mutate(mpg_unscaled = mpg) %>%
    +  gt() %>%
    +  gt_plt_bar_pct(column = mpg_scaled, scaled = TRUE) %>%
    +  gt_plt_bar_pct(column = mpg_unscaled, scaled = FALSE, fill = "blue", background = "lightblue") %>%
    +  cols_align("center", contains("scale")) %>%
    +  cols_width(
    +    4 ~ px(125),
    +    5 ~ px(125)
    +  )
    +
    - - - - - - - - - - - - - - - - +
    numcharfctr - Date et/ou Heure - currency
    datetimedatetime
    +++++++ + + + + + + + - - - - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    cylmpgmpg_pct_maxmpg_scaledmpg_unscaled
    grp_a
    row_10,11apricotone15 janv. 20151:35 janv. 1, 2018 2:22 €49,95621.092.11
    row_22,22bananatwo15 févr. 20152:40 févr. 2, 2018 2:33 €17,95621.092.11
    row_333,33coconutthree15 mars 20153:45 mars 3, 2018 3:44 €1,39
    row_4444,40durianfour15 avr. 20154:50 avr. 4, 2018 3:55 €65 100,00
    moyenne120.02
    total480.06
    grp_b
    row_55 550,00NAfive15 mai 20155:55 mai 5, 2018 4:00 €1 325,81422.8100.00
    row_6NAfigsix15 juin 2015NAjuin 6, 2018 4:11 €13,26621.493.86
    row_7777 000,00grapefruitsevenNA7:10 juil. 7, 2018 5:22 NA818.782.02
    row_88 880 000,00honeydeweight15 août 20158:20 NA€0,44618.179.39
    moyenne3,220,850.00
    total9,662,550.00
    grand total€66,508.79
    -

    Pour en savoir plus : https://gt.rstudio.com/

    printr

    L’extension printr développée par le même auteur que knitr étent le fonctionnement par défaut de knitr. Une fois chargée, le rendu automatique de certains objets (tel qu’un tableau croisé à trois variables) sera amélioré.

    library(printr)
    -
    Registered S3 method overwritten by 'printr':
    -  method                from     
    -  knit_print.data.frame rmarkdown
    -
    x1 <- sample(letters[1:2], 1000, TRUE)
    -x2 <- sample(letters[1:2], 1000, TRUE)
    -x3 <- sample(letters[1:2], 1000, TRUE)
    -table(x1, x2, x3)
    +
    x1 <- sample(letters[1:2], 1000, TRUE)
    +x2 <- sample(letters[1:2], 1000, TRUE)
    +x3 <- sample(letters[1:2], 1000, TRUE)
    +table(x1, x2, x3)
    @@ -42275,7 +76331,7 @@

    printr

    b @@ -42288,7 +76344,7 @@

    printr

    a @@ -42300,7 +76356,7 @@

    printr

    b @@ -42314,7 +76370,7 @@

    printr

    a @@ -42326,7 +76382,7 @@

    printr

    b @@ -42339,7 +76395,7 @@

    printr

    a @@ -42351,7 +76407,7 @@

    printr

    b @@ -42470,18 +76526,192 @@

    Ressources

    -

    Mettre en forme des nombres

    +

    Mettre en forme des nombres avec scales

    - -
    -

    Ce chapitre est en cours d’écriture.

    + +

    Ce chapitre est évoqué dans le webin-R #09 (Graphiques uni- et bivariés avec ggplot2) sur YouTube.

    -

    Le plus simple est d’avoir recours à l’extension scales et aux fonctions number, comma, percent, unit, ordinal et pvalue.

    +

    Il existe de nombreuses fonctions pour mettre en forme des nombres sous R. La fonction de base est format. Plusieurs packages proposent des variations pour rendre cette mise en forme plus facile. Cependant, s’il y a une extension à retenir, c’est l’extension scales.

    +
    +

    number() ou label_number() ?

    +

    Les deux fonctions de base sont number et label_number. Elles ont l’air très similaires et partagent un grand nombre de paramètres en commun. La différence est que number a besoin d’un vecteur numérique en entrée qu’elle va mettre en forme, tandis que que label_number renvoie une fonction que l’on pourra ensuite appliquer à un vecteur numérique.

    +
    library(scales)
    +x <- c(0.0023, .123, 4.567, 874.44, 8957845)
    +number(x)
    +
    [1] "0.00"         "0.12"         "4.57"        
    +[4] "874.44"       "8 957 845.00"
    +
    f <- label_number()
    +f(x)
    +
    [1] "0.00"         "0.12"         "4.57"        
    +[4] "874.44"       "8 957 845.00"
    +
    label_number()(x)
    +
    [1] "0.00"         "0.12"         "4.57"        
    +[4] "874.44"       "8 957 845.00"
    +

    Dans de nombreux cas de figure (par exemple pour un graphique ggplot2 ou un tableau gtsummary), il sera demandé de fournir une fonction, auquel cas on aura recours aux fonctions de scales préfixées par label_*().

    +
    +
    +

    label_number()

    +

    label_number est la fonction de base de mise en forme de nombres dans scales, une majorité des autres fonctions faisant appel à label_number et partageant les mêmes arguments.

    +

    Le paramètre accurary permets de définir le niveau d’arrondi à utiliser. Par exemple, .1 pour afficher une seule décimale. Il est aussi possible d’indiquer un nombre qui n’est pas une puissance de 10 (par exemple .25). Si on n’indique rien (NULL), alors label_number essaiera de deviner un nombre de décimales pertinent en fonction des valeurs du vecteur de nombres à mettre en forme.

    +
    label_number(accuracy = NULL)(x)
    +
    [1] "0.00"         "0.12"         "4.57"        
    +[4] "874.44"       "8 957 845.00"
    +
    label_number(accuracy = .1)(x)
    +
    [1] "0.0"         "0.1"         "4.6"         "874.4"      
    +[5] "8 957 845.0"
    +
    label_number(accuracy = .25)(x)
    +
    [1] "0.0"         "0.0"         "4.5"         "874.5"      
    +[5] "8 957 845.0"
    +
    label_number(accuracy = 10)(x)
    +
    [1] "0"         "0"         "0"         "870"      
    +[5] "8 957 840"
    +

    L’option scale permets d’indiquer un facteur multiplicatif à appliquer avant de mettre en forme. On utilisera le plus souvent les options prefix et suffix en même temps pour indiquer les unités.

    +
    label_number(scale = 100, suffix = "%")(x) # pour cent
    +
    [1] "0%"           "12%"          "457%"        
    +[4] "87 444%"      "895 784 500%"
    +
    label_number(scale = 1000, suffix = "\u2030")(x) # pour mille
    +
    [1] "2‰"             "123‰"           "4 567‰"        
    +[4] "874 440‰"       "8 957 845 000‰"
    +
    label_number(scale = .001, suffix = " milliers", accuracy = .1)(x)
    +
    [1] "0.0 milliers"     "0.0 milliers"     "0.0 milliers"    
    +[4] "0.9 milliers"     "8 957.8 milliers"
    +

    Les arguments decimal.mark et big.mark permettent de définir, respectivement, le séparateur de décimale et le séparateur de milliers. Ainsi, pour afficher des nombres à la française (virgule pour les décimales, espace pour les milliers) :

    +
    label_number(decimal.mark = ",", big.mark = " ")(x)
    +
    [1] "0,00"         "0,12"         "4,57"        
    +[4] "874,44"       "8 957 845,00"
    +

    Note : il est possible d’utiliser small.interval et small.mark pour ajouter des séparateurs parmi les décimales.

    +
    label_number(accuracy = 10^-9, small.mark = "|", small.interval = 3)(x)
    +
    [1] "0.002|300|000"         "0.123|000|000"        
    +[3] "4.567|000|000"         "874.440|000|000"      
    +[5] "8 957 845.000|000|000"
    +
    +
    +

    label_comma()

    +

    label_comma (et comma) est une variante de label_number qui, par défaut, affiche les nombres à l’américaine, avec une virgule comme séparateur de milliers.

    +
    label_comma()(x)
    +
    [1] "0.00"         "0.12"         "4.57"        
    +[4] "874.44"       "8,957,845.00"
    +
    +
    +

    label_percent()

    +

    label_percent (et percent) est une variante de label_number qui affiche les nombres sous formes de pourcentages (scale = 100, suffix = "%").

    +
    label_percent()(x)
    +
    [1] "0%"           "12%"          "457%"        
    +[4] "87 444%"      "895 784 500%"
    +
    +
    +

    label_dollar()

    +

    label_dollar est adapté à l’affichage des valeurs monétaires.

    +
    label_dollar()(x)
    +
    [1] "$0"         "$0"         "$5"         "$874"      
    +[5] "$8,957,845"
    +
    label_dollar(prefix = "", suffix = " €", accuracy = .01, big.mark = " ")(x)
    +
    [1] "0.00 \200"         "0.12 \200"         "4.57 \200"        
    +[4] "874.44 \200"       "8 957 845.00 \200"
    +

    L’option negative_parens permet d’afficher les valeurs négatives avec des parenthèses, convention utilisée dans certaines disciplines.

    +
    label_dollar()(c(12.5, -4, 21, -56.36))
    +
    [1] "$12.50"  "-$4.00"  "$21.00"  "-$56.36"
    +
    label_dollar(negative_parens = TRUE)(c(12.5, -4, 21, -56.36))
    +
    [1] "$12.50"   "($4.00)"  "$21.00"   "($56.36)"
    +
    +
    +

    label_pvalue(), style_pvalue() & signif_stars()

    +

    label_pvalue est adapté pour la mise en forme de p-valeurs.

    +
    label_pvalue()(c(0.000001, 0.023, 0.098, 0.60, 0.9998))
    +
    [1] "<0.001" "0.023"  "0.098"  "0.600"  ">0.999"
    +
    label_pvalue(accuracy = .01, add_p = TRUE)(c(0.000001, 0.023, 0.098, 0.60))
    +
    [1] "p<0.01" "p=0.02" "p=0.10" "p=0.60"
    +

    À noter, la fonction style_pvalue de l’extension gtsummary ayant à peu près le même objectif mais adaptant le nombre de décimales en fonction de la p-valeur.

    +
    gtsummary::style_pvalue(c(0.000001, 0.023, 0.098, 0.60, 0.9998))
    +
    [1] "<0.001" "0.023"  "0.10"   "0.6"    ">0.9"  
    +

    La fonction signif_stars de GGally permet quant à elle d’afficher les p-valeurs sous forme d’étoiles de significativité, Par défaut, trois astérisques si p < 0.001, deux si p < 0.01, une si p < 0.05 et un point si p < 0.10. Les valeurs sont bien sur paramétrables.

    +
    p <- c(0.5, 0.1, 0.05, 0.01, 0.001)
    +GGally::signif_stars(p)
    +
    Registered S3 method overwritten by 'GGally':
    +  method from   
    +  +.gg   ggplot2
    +
    [1] ""    "."   "*"   "**"  "***"
    +
    GGally::signif_stars(p, one = .15, point = NULL)
    +
    [1] ""    "*"   "*"   "**"  "***"
    +
    +
    +

    label_number_si()

    +

    label_number_si cherche le préfixe du Système international d’unités le plus proche et arrondi chaque valeur en fonction, en ajoutant la précision correspondante.

    +
    label_number_si(unit = "g")(c(.00000145, .0034, 5, 12478, 14569787))
    +
    [1] "1.4 µg"  "3.4 mg"  "5.0 g"   "12.5 kg" "14.6 Mg"
    +
    +
    +

    label_scientific()

    +

    label_scientific affiche les nombres dans un format scientifique (avec des puissances de 10).

    +
    label_scientific(unit = "g")(c(.00000145, .0034, 5, 12478, 14569787))
    +
    [1] "1.45e-06" "3.40e-03" "5.00e+00" "1.25e+04" "1.46e+07"
    +
    +
    +

    label_bytes()

    +

    label_bytes mets en forme des tailles exprimées en octets, utilisant au besoin des multiples de 1024.

    +
    b <- c(478, 1235468, 546578944897)
    +label_bytes()(b)
    +
    [1] "478 B"  "1 MB"   "547 GB"
    +
    label_bytes(units = "auto_binary")(b)
    +
    [1] "478 iB"  "1 MiB"   "509 GiB"
    +
    +
    +

    label_ordinal()

    +

    label_bytes permets d’afficher des rangs ou nombres ordinaux. Plusieurs langues sont disponibles.

    +
    label_ordinal()(1:5)
    +
    [1] "1st" "2nd" "3rd" "4th" "5th"
    +
    label_ordinal(rules = ordinal_french())(1:5)
    +
    [1] "1er" "2e"  "3e"  "4e"  "5e" 
    +
    label_ordinal(rules = ordinal_french(gender = "f", plural = TRUE))(1:5)
    +
    [1] "1res" "2es"  "3es"  "4es"  "5es" 
    +
    +
    +

    label_date(), label_date_short() & label_time()

    +

    label_date, label_date_short et label_time peuvent être utilisées pour la mise en forme de dates.

    +
    label_date()(as.Date("2020-02-14"))
    +
    [1] "2020-02-14"
    +
    label_date(format = "%d/%m/%Y")(as.Date("2020-02-14"))
    +
    [1] "14/02/2020"
    +
    label_date_short()(as.Date("2020-02-14"))
    +
    [1] "14\nfévr.\n2020"
    +

    La mise en forme des dates est un peu complexe. Ne pas hésiter à consulter le fichier d’aide de la fonction strptime pour plus d’informations.

    +
    +
    +

    label_wrap()

    +

    La fonction label_wrap est un peu différente. Elle permets d’insérer des retours à la ligne (\n) dans des chaines de caractères. Elle tient compte des espaces pour identifier les mots et éviter ainsi des coupures au milieu d’un mot.

    +
    x <- "Ceci est un texte assez long et que l'on souhaiterait afficher sur plusieurs lignes. Cependant, on souhaite éviter que des coupures apparaissent au milieu d'un mot."
    +label_wrap(80)(x)
    +
    [1] "Ceci est un texte assez long et que l'on souhaiterait afficher sur plusieurs\nlignes. Cependant, on souhaite éviter que des coupures apparaissent au milieu\nd'un mot."
    +
    label_wrap(80)(x) |> message()
    +
    Ceci est un texte assez long et que l'on souhaiterait afficher sur plusieurs
    +lignes. Cependant, on souhaite éviter que des coupures apparaissent au milieu
    +d'un mot.
    +
    label_wrap(40)(x) |> message()
    +
    Ceci est un texte assez long et que
    +l'on souhaiterait afficher sur
    +plusieurs lignes. Cependant, on
    +souhaite éviter que des coupures
    +apparaissent au milieu d'un mot.
    +
    @@ -47338,102 +81568,97 @@

    Rappel sur les âges

    Le package lubridate

    -

    Le package lubridate est spécialement développé pour faciliter la manipulation et le calcul autour des dates. La version de développement intègre depuis peu une fonction time_length adaptée au calcul des âges exacts.

    -
    -

    La fonction time_length étant récente, elle n’est pas encore disponible dans la version stable du package. Pour installer la version de développement de lubridate, on aura recours au package devtools :

    -
    library(devtools)
    -install_github("tidyverse/lubridate")
    -
    +

    L’extension lubridate est spécialement développée pour faciliter la manipulation et le calcul autour des dates. Elle intègre une fonction time_length adaptée au calcul des âges exacts.

    Nous noterons naiss la date de naissance et evt la date à laquelle nous calculerons l’âge.

    Calcul d’un âge exact

    On appelle âge exact l’expression d’un âge avec sa partie décimale.

    Une approche simple consiste à calculer une différence en jours puis à diviser par 365. Or, le souci c’est que toutes les années n’ont pas le même nombre de jours. Regardons par exemple ce qui se passe si l’on calcule l’âge au 31 décembre 1999 d’une personne née le 1er janvier 1900.

    -
    library(lubridate)
    -naiss <- ymd("1900-01-01")
    -evt <- ymd("1999-12-31")
    -time_length(interval(naiss, evt), "days")
    +
    library(lubridate)
    +naiss <- ymd("1900-01-01")
    +evt <- ymd("1999-12-31")
    +time_length(interval(naiss, evt), "days")
    [1] 36523
    -
    time_length(interval(naiss, evt), "days") / 365
    +
    time_length(interval(naiss, evt), "days") / 365
    [1] 100.063

    Or, au 31 décembre 1999, cette personne n’a pas encore fêté son centième anniversaire. Le calcul précédent ne prend pas en compte les années bissextiles. Une approche plus correcte serait de considérer que les années durent en moyenne 365,25 jours.

    -
    time_length(interval(naiss, evt), "days") / 365.25
    +
    time_length(interval(naiss, evt), "days") / 365.25
    [1] 99.99452

    Si cette approche semble fonctionner avec cet exemple, ce n’est plus le cas dans d’autres situations.

    -
    evt <- ymd("1903-01-01")
    -time_length(interval(naiss, evt), "days") / 365.25
    +
    evt <- ymd("1903-01-01")
    +time_length(interval(naiss, evt), "days") / 365.25
    [1] 2.997947

    Or, à la date du premier janvier 1903, cette personne a bien fêté son troisième anniversaire.

    Pour calculer proprement un âge en années (ou en mois), il est dès lors nécessaire de prendre en compte la date anniversaire et le fait que la durée de chaque année (ou mois) est variable. C’est justement ce que fait la fonction time_length appliquée à un objet de type Interval. On détermine le dernier et le prochain anniversaire et l’on rajoute, à l’âge atteint au dernier anniversaire, le ratio entre le nombre de jours entre l’événement et le dernier anniversaire par le nombre de jours entre le prochain et le dernier anniversaire.

    -
    naiss <- ymd("1900-01-01")
    -evt <- ymd("1999-12-31")
    -time_length(interval(naiss, evt), "years")
    +
    naiss <- ymd("1900-01-01")
    +evt <- ymd("1999-12-31")
    +time_length(interval(naiss, evt), "years")
    [1] 99.99726
    -
    evt <- ymd("1903-01-01")
    -time_length(interval(naiss, evt), "years")
    +
    evt <- ymd("1903-01-01")
    +time_length(interval(naiss, evt), "years")
    [1] 3
    -
    evt <- ymd("1918-11-11")
    -time_length(interval(naiss, evt), "years")
    +
    evt <- ymd("1918-11-11")
    +time_length(interval(naiss, evt), "years")
    [1] 18.86027

    Attention, cela n’est valable que si l’on présente à la fonction time_length un objet de type Interval (pour lequel on connait dès lors la date de début et la date de fin). Si l’on passe une durée (objet de type Duration) à la fonction time_length, le calcul s’effectuera alors en prenant en compte la durée moyenne d’une année (plus précisément 365 jours).

    -
    naiss <- ymd("1900-01-01")
    -evt <- ymd("1999-12-31")
    -time_length(interval(naiss, evt), "years")
    +
    naiss <- ymd("1900-01-01")
    +evt <- ymd("1999-12-31")
    +time_length(interval(naiss, evt), "years")
    [1] 99.99726
    -
    time_length(evt - naiss, "years")
    +
    time_length(evt - naiss, "years")
    [1] 99.99452
    -
    time_length(as.duration(interval(naiss, evt)), "years")
    +
    time_length(as.duration(interval(naiss, evt)), "years")
    [1] 99.99452

    Cas particulier des personnes nées un 29 février

    Pour les personnes nées un 29 février, il existe un certain flou concernant leur date d’anniversaire pour les années non bissextiles. Doit-on considérer qu’il s’agit du 28 février ou du 1er mars ?

    Au sens strict, on peut considérer que leur anniversaire a lieu entre le 28 février soir à minuit et le 1er mars à 0 heure du matin, autrement dit que le 28 février ils n’ont pas encore fêté leur anniversaire. C’est la position adoptée par la fonction time_length.

    -
    naiss <- ymd("1992-02-29")
    -evt <- ymd("2014-02-28")
    -time_length(interval(naiss, evt), "years")
    +
    naiss <- ymd("1992-02-29")
    +evt <- ymd("2014-02-28")
    +time_length(interval(naiss, evt), "years")
    [1] 21.99726
    -
    evt <- ymd("2014-03-01")
    -time_length(interval(naiss, evt), "years")
    +
    evt <- ymd("2014-03-01")
    +time_length(interval(naiss, evt), "years")
    [1] 22

    Cette approche permets également d’être cohérent avec la manière dont les dates sont prises en compte informatiquement. On considère en effet que lorsque seule la date est précisée (sans mention de l’heure), l’heure correspondante est 0:00. Autrement dit, "2014-03-01" est équivalent à "2014-03-01 00:00:00". L’approche adoptée permet donc d’être cohérent lorsque l’anniversaire est calculé en tenant compte des heures.

    -
    naiss <- ymd("1992-02-29")
    -evt <- ymd_hms("2014-02-28 23:00:00")
    -time_length(interval(naiss, evt), "years")
    +
    naiss <- ymd("1992-02-29")
    +evt <- ymd_hms("2014-02-28 23:00:00")
    +time_length(interval(naiss, evt), "years")
    [1] 21.99989
    -
    evt <- ymd_hms("2014-03-01 00:00:00")
    -time_length(interval(naiss, evt), "years")
    +
    evt <- ymd_hms("2014-03-01 00:00:00")
    +time_length(interval(naiss, evt), "years")
    [1] 22
    -
    evt <- ymd_hms("2014-03-01 01:00:00")
    -time_length(interval(naiss, evt), "years")
    +
    evt <- ymd_hms("2014-03-01 01:00:00")
    +time_length(interval(naiss, evt), "years")
    [1] 22.00011
    -
    naiss <- ymd_hms("1992-02-29 12:00:00")
    -evt <- ymd_hms("2014-03-01 01:00:00")
    -time_length(interval(naiss, evt), "years")
    +
    naiss <- ymd_hms("1992-02-29 12:00:00")
    +evt <- ymd_hms("2014-03-01 01:00:00")
    +time_length(interval(naiss, evt), "years")
    [1] 22.00011

    Âge révolu ou âge au dernier anniversaire

    Une fois que l’on sait calculer un âge exact, le calcul d’un âge révolu ou âge au dernier anniversaire est assez simple. Il suffit de ne garder que la partie entière de l’âge exact (approche conseillée).

    -
    naiss <- ymd("1980-01-09")
    -evt <- ymd("2015-01-01")
    -time_length(interval(naiss, evt), "years")
    +
    naiss <- ymd("1980-01-09")
    +evt <- ymd("2015-01-01")
    +time_length(interval(naiss, evt), "years")
    [1] 34.97808
    -
    trunc(time_length(interval(naiss, evt), "years"))
    +
    trunc(time_length(interval(naiss, evt), "years"))
    [1] 34

    Une autre approche consiste à convertir l’intervalle en objet de type Period et à ne prendre en compte que les années.

    -
    as.period(interval(naiss, evt))
    +
    as.period(interval(naiss, evt))
    [1] "34y 11m 23d 0H 0M 0S"
    -
    as.period(interval(naiss, evt))@year
    +
    as.period(interval(naiss, evt))@year
    [1] 34

    Âge par différence de millésimes

    L’âge par différence de millésimes, encore appelé âge atteint dans l’année, s’obtient tout simplement en soustrayant l’année de naissance à l’année de l’événement.

    -
    naiss <- ymd("1980-01-09")
    -evt <- ymd("2015-01-01")
    -year(evt) - year(naiss)
    +
    naiss <- ymd("1980-01-09")
    +evt <- ymd("2015-01-01")
    +year(evt) - year(naiss)
    [1] 35
    @@ -47444,11 +81669,11 @@

    Calcul d’un âge moyen

    Notes

    L’ensemble des fonctions présentées peuvent être appliquées à des vecteurs et, par conséquent, aux colonnes d’un tableau de données (data.frame).

    En l’absence du package lubridate, il reste facile de calculer une durée en jours avec les fonctions de base de R :

    -
    naiss <- as.Date("1900-01-01")
    -evt <- as.Date("1999-12-31")
    -evt - naiss
    +
    naiss <- as.Date("1900-01-01")
    +evt <- as.Date("1999-12-31")
    +evt - naiss
    Time difference of 36523 days
    -
    as.integer(evt - naiss)
    +
    as.integer(evt - naiss)
    [1] 36523
    @@ -48125,6 +82350,10 @@

    +

    échelle de Likert

    +

    éditeur de script

    • Présentation et Philosophie
    • @@ -48241,6 +82470,7 @@

      facteurs d’inflation de la variance

      factor

        @@ -48278,6 +82508,7 @@

        FIV

        fonction

          @@ -48511,6 +82742,7 @@

          Kaplan-Meier

          Kruskal-Wallis, test

          @@ -48562,6 +82794,10 @@

          +

          Likert, échelle

          +

          linéaire, régression

          • Statistique bivariée
          • @@ -49371,9 +83607,14 @@

            +

            test de Fosher

            +

            test de Kruskal-Wallis

            test de Mood

              @@ -49383,6 +83624,10 @@

              +

              test de Wilcoxon-Mann-Whitney

              +

              test de Wilcoxon/Mann-Whitney

              • Comparaisons (moyennes et proportions)
              • @@ -49391,6 +83636,7 @@

                test du Chi², résidus

                @@ -49408,6 +83654,7 @@

                texte

                  @@ -49552,6 +83799,7 @@

                  variance inflation factor

                  variance, analyse de

                    @@ -49586,6 +83834,7 @@

                    VIF

                    violin plot

                      @@ -49745,13 +83994,43 @@

                      +

                      add_ci (gtsummary)

                      + +

                      add_difference (gtsummary)

                      + +

                      add_glance_source_note (gtsummary)

                      + +

                      add_glance_table (gtsummary)

                      +

                      add_global_p (gtsummary)

                      +

                      add_inline_forest_plot (bstfun)

                      + +

                      add_n (gtsummary)

                      + +

                      add_n.tbl_summary (gtsummary)

                      +

                      add_overall (gtsummary)

                      @@ -49759,6 +84038,39 @@

                      +

                      add_p.tbl_cross (gtsummary)

                      + +

                      add_p.tbl_summary (gtsummary)

                      + +

                      add_p.tbl_svysummary (gtsummary)

                      + +

                      add_significance_stars (gtsummary)

                      + +

                      add_sparkline (bstfun)

                      + +

                      add_stat (gtsummary)

                      + +

                      add_stat_label (gtsummary)

                      + +

                      add_vif (gtsummary)

                      +

                      addNA (base)

                        @@ -49815,6 +84127,50 @@

                        +

                        all_categorical (gtsummary)

                        + +

                        all_continous (gtsummary)

                        + +

                        all_continuous (gtsummary)

                        + +

                        all_continuous2 (gtsummary)

                        + +

                        all_contrasts (gtsummary)

                        + +

                        all_dichotolous (gtsummary)

                        + +

                        all_dichotomous (gtsummary)

                        + +

                        all_interaction (gtsummary)

                        + +

                        all_intercepts (gtsummary)

                        + +

                        all_of (tidyselect)

                        + +

                        all_stat_cols (gtsummary)

                        +

                        allEffects (effects)

                        • Effets d’interaction dans un modèle
                        • @@ -49910,6 +84266,30 @@

                          +

                          as_flex_table (gtsummary)

                          + +

                          as_forest_plot (bstfun)

                          + +

                          as_hux_table (gtsummary)

                          + +

                          as_kable (gtsummary)

                          + +

                          as_kable_extra (gtsummary)

                          + +

                          as_tibble (gtsummary)

                          +

                          as_tibble (tibble)

                          • Introduction au tidyverse
                          • @@ -49969,6 +84349,14 @@

                            +

                            bold_labels (gtsummary)

                            + +

                            bold_levels (gtsummary)

                            +

                            boxplot (ade4)

                            • Analyse des correspondances multiples (ACM)
                            • @@ -50029,6 +84417,7 @@

                              class (base)

                                @@ -50085,9 +84474,9 @@

                                -

                                comma (scales)

                                +

                                comma (scales)

                                complete (tidyr)

                                +

                                contains (tidyselect)

                                + +

                                continuous_summary (gtsummary)

                                +

                                contour (graphics)

                                • Statistique bivariée
                                • @@ -50385,6 +84782,10 @@

                                  +

                                  everything (tidyselect)

                                  +

                                  example (utils)

                                  fct_inorder (forcats)

                                    @@ -50518,6 +84920,7 @@

                                    fisher.test (stats)

                                    fixed (stringr)

                                      @@ -50537,8 +84940,13 @@

                                      +

                                      forestplot (forestplot)

                                      +

                                      format (base)

                                      formula (stats)

                                      @@ -50549,6 +84957,10 @@

                                      +

                                      freq (cleaner)

                                      +

                                      freq (questionr)

                                      +

                                      ggcoef_model (gtsummary)

                                      +

                                      ggcoef_multinom (GGally)

                                      +

                                      glance (broom)

                                      +

                                      glimpse (dplyr)

                                      glm.nb (MASS)

                                        @@ -50939,6 +85360,10 @@

                                        +

                                        gtsave (gt)

                                        +

                                        guide_axis_minor (ggh4x)

                                        • Étendre ggplot2
                                        • @@ -51101,6 +85526,14 @@

                                          +

                                          italicize_labels (gtsummary)

                                          + +

                                          italicize_levels (gtsummary)

                                          +

                                          J

                                          @@ -51125,12 +85558,61 @@

                                          +

                                          kruskal.test (stats)

                                          +

                                          L

                                          +

                                          label_bytes (scales)

                                          + +

                                          label_comma (scales)

                                          + +

                                          label_date (scales)

                                          + +

                                          label_date_short (scales)

                                          + +

                                          label_dollar (scales)

                                          + +

                                          label_number (scales)

                                          + +

                                          label_number_si (scales)

                                          + +

                                          label_percent (scales)

                                          + +

                                          label_pvalue (scales)

                                          + +

                                          label_scientific (scales)

                                          + +

                                          label_time (scales)

                                          +

                                          label_wrap (scales)

                                          labelled (labelled)

                                            @@ -51371,6 +85853,18 @@

                                            +

                                            modify_footnote (gtsummary)

                                            + +

                                            modify_header (gtsummary)

                                            + +

                                            modify_spanning_header (gtsummary)

                                            +

                                            month.abb (base)

                                            -

                                            ordinal (scales)

                                            -

                                            ordLORgee (multgee)

                                            • Quel type de modèles choisir ?
                                            • @@ -51573,7 +86063,7 @@

                                              Period (lubridate)

                                                @@ -51659,6 +86149,7 @@

                                                poisson.test (stats)

                                                polr (MASS)

                                                +

                                                proportion_summary (gtsummary)

                                                +

                                                ptol_pal (ggthemes)

                                                pvalue (scales)

                                                @@ -51799,6 +86293,10 @@

                                                +

                                                ratio_summary (gtsummary)

                                                +

                                                rbind (base)

                                                • Fusion de tables
                                                • @@ -51969,6 +86467,7 @@

                                                  rename.variable (questionr)

                                                    @@ -52088,6 +86587,10 @@

                                                    +

                                                    save_as_docx (flextable)

                                                    +

                                                    scale_color_paletteer_c (paletteer)

                                                    +

                                                    separate_p_footnotes (gtsummary)

                                                    +

                                                    seq (base)

                                                    +

                                                    set_variable_labels (labelled)

                                                    +

                                                    setDF (data.table)

                                                    • Manipulations avancées avec data.table
                                                    • @@ -52287,6 +86798,14 @@

                                                      +

                                                      show_header_names (gtsummary)

                                                      + +

                                                      signif_stars (GGally)

                                                      +

                                                      skim (skimr)

                                                      • Visualiser ses données
                                                      • @@ -52328,6 +86847,10 @@

                                                        +

                                                        starts_with (tidyselect)

                                                        +

                                                        stat_count (ggplot2)

                                                        • Exemples de graphiques avancés
                                                        • @@ -52439,6 +86962,11 @@

                                                          strptime (base)

                                                          +

                                                          style_pvalue (gtsummary)

                                                          +

                                                          subset (base)

                                                            @@ -52506,6 +87034,7 @@

                                                            survfit (survival)

                                                            survreg (survival)

                                                              @@ -52641,6 +87170,7 @@

                                                              table (base)

                                                                @@ -52681,24 +87211,51 @@

                                                                +

                                                                tbl_continuous (gtsummary)

                                                                +

                                                                tbl_cross (gtsummary)

                                                                +

                                                                tbl_custom_summary (gtsummary)

                                                                +

                                                                tbl_df (dplyr)

                                                                +

                                                                tbl_likert (bstfun)

                                                                +

                                                                tbl_merge (gtsummary)

                                                                tbl_regression (gtsummary)

                                                                +

                                                                tbl_spit (gtsummary)

                                                                + +

                                                                tbl_stack (gtsummary)

                                                                + +

                                                                tbl_strata (gtsummary)

                                                                +

                                                                tbl_summary (gtsummary)

                                                                +

                                                                tbl_survfit (gtsummary)

                                                                +

                                                                tbl_svysummary (gtsummary)

                                                                tbl_uvregression (gtsummary)

                                                                terrain.colors (grDevices)

                                                                  @@ -52735,6 +87299,18 @@

                                                                  +

                                                                  theme_gtsummary_journal (gtsummary)

                                                                  + +

                                                                  theme_gtsummary_language (gtsummary)

                                                                  + +

                                                                  theme_gtsummary_mean_sd (gtsummary)

                                                                  +

                                                                  theme_minimal (ggplot2)

                                                                  • Exemples de graphiques avancés
                                                                  • @@ -52747,6 +87323,7 @@

                                                                    tidi_plus_plus (broom.helpers)

                                                                    @@ -52827,10 +87405,6 @@

                                                                    -

                                                                    unit (scales)

                                                                    -

                                                                    unite (tidyr)

                                                                    • Réorganiser ses données avec tidyr
                                                                    • @@ -52838,6 +87412,7 @@

                                                                      unlabelled (labelled)

                                                                      @@ -52895,6 +87470,7 @@

                                                                      Vectorize (base)

                                                                      stringi

                                                                      survival

                                                                      @@ -54290,6 +88894,7 @@

                                                                    • Listes et Tableaux de données
                                                                    • Manipulations avancées avec data.table
                                                                    • Manipuler les données avec dplyr
                                                                    • +
                                                                    • Tableaux statistiques avancés avec gtsummary
                                                                    • Trajectoires de soins : un exemple de données longitudinales
                                                                    • Vectorisation (dont purrr)
                                                                    • Visualiser ses données
                                                                    • @@ -54309,6 +88914,7 @@

                                                                      tidyselect

                                                                      tidyverse

                                                                        diff --git a/analyse-R.pdf b/analyse-R.pdf index 2b635482..deb2386c 100644 Binary files a/analyse-R.pdf and b/analyse-R.pdf differ diff --git a/analyse-de-reseaux.html b/analyse-de-reseaux.html index 76f11e1b..ad01dd1b 100644 --- a/analyse-de-reseaux.html +++ b/analyse-de-reseaux.html @@ -277,6 +277,7 @@
                                                                          +
                                                                        • Tableaux statistiques avancés avec gtsummary
                                                                        • Effets d'interaction dans un modèle
                                                                        • Multicolinéarité dans la régression
                                                                        • Quel type de modèles choisir ?
                                                                        • @@ -323,7 +324,7 @@
                                                                            -
                                                                          • Mettre en forme des nombres
                                                                          • +
                                                                          • Mettre en forme des nombres avec scales
                                                                          • Couleurs et Palettes
                                                                          • Annotations mathématiques
                                                                          • Calculer un âge
                                                                          • diff --git a/analyse-de-sequences.html b/analyse-de-sequences.html index 253dc8af..645292b7 100644 --- a/analyse-de-sequences.html +++ b/analyse-de-sequences.html @@ -372,6 +372,7 @@
                                                                              +
                                                                            • Tableaux statistiques avancés avec gtsummary
                                                                            • Effets d'interaction dans un modèle
                                                                            • Multicolinéarité dans la régression
                                                                            • Quel type de modèles choisir ?
                                                                            • @@ -418,7 +419,7 @@
                                                                                -
                                                                              • Mettre en forme des nombres
                                                                              • +
                                                                              • Mettre en forme des nombres avec scales
                                                                              • Couleurs et Palettes
                                                                              • Annotations mathématiques
                                                                              • Calculer un âge
                                                                              • diff --git a/analyse-de-survie.html b/analyse-de-survie.html index d55b7d74..e29d6a43 100644 --- a/analyse-de-survie.html +++ b/analyse-de-survie.html @@ -372,6 +372,7 @@
                                                                                  +
                                                                                • Tableaux statistiques avancés avec gtsummary
                                                                                • Effets d'interaction dans un modèle
                                                                                • Multicolinéarité dans la régression
                                                                                • Quel type de modèles choisir ?
                                                                                • @@ -418,7 +419,7 @@
                                                                                    -
                                                                                  • Mettre en forme des nombres
                                                                                  • +
                                                                                  • Mettre en forme des nombres avec scales
                                                                                  • Couleurs et Palettes
                                                                                  • Annotations mathématiques
                                                                                  • Calculer un âge
                                                                                  • @@ -572,27 +573,29 @@

                                                                                    Préparation des données avec data.table

                                                                                    5 value labels: [1] très pauvre [2] pauvre [3] moyen [4] riche [5] très riche

                                                                                    Les tableaux de données sont au format tibble (c’est-à-dire sont de la classe tbl_df) et les variables catégorielles sont du type haven_labelled (voir le chapitre sur les vecteurs labellisés). Ce format correspond au format de données si on les avait importées depuis SPSS avec l’extension haven (voir le chapitre sur l’import de données).

                                                                                    En premier lieu, il nous faut convertir les tableaux de données au format data.table, ce qui peut se faire avec la fonction setDT. Par ailleurs, nous allons également charger en mémoire l’extension labelled pour la gestion des vecteurs labellisés.

                                                                                    -
                                                                                    library(labelled)
                                                                                    -library(data.table)
                                                                                    -setDT(menages)
                                                                                    -setDT(femmes)
                                                                                    -setDT(enfants)
                                                                                    +
                                                                                    library(labelled)
                                                                                    +
                                                                                    Warning: le package 'labelled' a été compilé avec la version
                                                                                    +R 4.1.2
                                                                                    +
                                                                                    library(data.table)
                                                                                    +setDT(menages)
                                                                                    +setDT(femmes)
                                                                                    +setDT(enfants)

                                                                                    En premier lieu, il nous faut calculer la durée d’observation des enfants, à savoir le temps passé entre la date de naissance (variable du fichier enfants) et la date de passation de l’entretien (fournie par le tableau de données femmes). Pour récupérer des variables du fichier femmes dans le fichier enfants, nous allons procéder à une fusion de table (voir le chapitre dédié). Pour le calcul de la durée d’observation, nous allons utiliser le package lubridate (voir le chapitre calculer un âge et celui sur la gestion des dates). Nous effectuerons l’analyse en mois (puisque l’âge au décès est connu en mois). Dès lors, la durée d’observation sera calculée en mois.

                                                                                    -
                                                                                    enfants <- merge(
                                                                                    -  enfants,
                                                                                    -  femmes[, .(id_femme, date_entretien)],
                                                                                    -  by = "id_femme",
                                                                                    -  all.x = TRUE
                                                                                    -)
                                                                                    -
                                                                                    -# duree observation en mois
                                                                                    -library(lubridate, quietly = TRUE)
                                                                                    -enfants[, duree_observation := time_length(interval(date_naissance, date_entretien), unit = "months")]
                                                                                    +
                                                                                    enfants <- merge(
                                                                                    +  enfants,
                                                                                    +  femmes[, .(id_femme, date_entretien)],
                                                                                    +  by = "id_femme",
                                                                                    +  all.x = TRUE
                                                                                    +)
                                                                                    +
                                                                                    +# duree observation en mois
                                                                                    +library(lubridate, quietly = TRUE)
                                                                                    +enfants[, duree_observation := time_length(interval(date_naissance, date_entretien), unit = "months")]

                                                                                    ATTENTION : il y 11 enfants soi-disant nés après la date d’enquête ! Quelle que soit l’enquête, il est rare de ne pas observer d’incohérences. Dans le cas présent, il est fort possible que la date d’entretien puisse parfois être erronnée (par exemple si l’enquêteur a inscrit une date sur le questionnaire papier le jour du recensement du ménage mais n’ai pu effectué le questionnaire individuel que plus tard). Nous décidons ici de procéder à une correction en ajoutant un mois aux dates d’entretien problématiques. D’autres approches auraient pu être envisagées, comme par exemple exclure ces observations problématiques. Cependant, cela aurait impacté le calcul du range de naissance pour les autres enfants issus de la même mère. Quoiqu’il en soit, il n’y a pas de réponse unique. À vous de vous adapter au contexte particulier de votre analyse.

                                                                                    -
                                                                                    enfants[duree_observation < 0, date_entretien := date_entretien %m+% months(1)]
                                                                                    -enfants[, duree_observation := time_length(interval(date_naissance, date_entretien), unit = "months")]
                                                                                    +
                                                                                    enfants[duree_observation < 0, date_entretien := date_entretien %m+% months(1)]
                                                                                    +enfants[, duree_observation := time_length(interval(date_naissance, date_entretien), unit = "months")]

                                                                                    Regardons maintenant comment les âges au décès ont été collectés.

                                                                                    -
                                                                                    freq(enfants$age_deces)
                                                                                    +
                                                                                    freq(enfants$age_deces)

                                                                                    Tout d’abord, la modalité pas d’adulte n’est pas représentée dans l’échantillon. On aura donc recours à l’argument drop_unused_labels pour ne pas conserver cette modalité. Par ailleurs, nous considérons que la situation familiale à partir de laquelle nous voudrons comparer les autres dans notre modèle, donc celle qui doit être considérée comme la modalité de référence, est celle du ménage nucléaire. Cette modalité (deux adultes de sexe opposé) n’étant pas la première, nous aurons recours à la fonction relevel.

                                                                                    -
                                                                                    enfants[, structure := to_factor(structure, drop_unused_labels = TRUE)]
                                                                                    -enfants[, structure := relevel(structure, "deux adultes de sexe opposé")]
                                                                                    +
                                                                                    enfants[, structure := to_factor(structure, drop_unused_labels = TRUE)]
                                                                                    +enfants[, structure := relevel(structure, "deux adultes de sexe opposé")]

                                                                                    Regardons la variable educ.

                                                                                    -
                                                                                    freq(enfants$educ)
                                                                                    +
                                                                                    freq(enfants$educ)

                                                                                    La modalité supérieur est peu représentée dans notre échantillon. Nous allons la fusionner avec la modalité secondaire (voir la section Regrouper les modalités d’une variable du chapitre Recodage).

                                                                                    -
                                                                                    enfants[, educ2 := educ]
                                                                                    -enfants[educ == 3, educ2 := 2]
                                                                                    -val_label(enfants$educ2, 2) <- "secondaire ou plus"
                                                                                    -val_label(enfants$educ2, 3) <- NULL
                                                                                    -enfants[, educ2 := to_factor(educ2)]
                                                                                    -freq(enfants$educ2)
                                                                                    +
                                                                                    enfants[, educ2 := educ]
                                                                                    +enfants[educ == 3, educ2 := 2]
                                                                                    +val_label(enfants$educ2, 2) <- "secondaire ou plus"
                                                                                    +val_label(enfants$educ2, 3) <- NULL
                                                                                    +enfants[, educ2 := to_factor(educ2)]
                                                                                    +freq(enfants$educ2)

                                                                                    Calculons maintenant l’âge de la mère à la naissance de l’enfant (voir le chapitre Calculer un âge) et découpons le en groupes d’âges (voir la section Découper une variable numérique en classes du chapitre Recodage).

                                                                                    -
                                                                                    enfants[, age_mere_naissance := time_length(
                                                                                    -  interval(date_naissance_mere, date_naissance), 
                                                                                    -  unit = "years"
                                                                                    -  )]
                                                                                    -
                                                                                    -enfants$gpage_mere_naissance <- cut(
                                                                                    -  enfants$age_mere_naissance, 
                                                                                    -  include.lowest = TRUE, right = FALSE,
                                                                                    -  breaks=c(13, 20, 30, 50)
                                                                                    -)
                                                                                    -levels(enfants$gpage_mere_naissance) <- c(
                                                                                    -  "19 ou moins", "20-29", "30 et plus"
                                                                                    -)
                                                                                    -enfants$gpage_mere_naissance <- relevel(enfants$gpage_mere_naissance, "20-29")
                                                                                    -freq(enfants$gpage_mere_naissance)
                                                                                    +
                                                                                    enfants[, age_mere_naissance := time_length(
                                                                                    +  interval(date_naissance_mere, date_naissance), 
                                                                                    +  unit = "years"
                                                                                    +  )]
                                                                                    +
                                                                                    +enfants$gpage_mere_naissance <- cut(
                                                                                    +  enfants$age_mere_naissance, 
                                                                                    +  include.lowest = TRUE, right = FALSE,
                                                                                    +  breaks=c(13, 20, 30, 50)
                                                                                    +)
                                                                                    +levels(enfants$gpage_mere_naissance) <- c(
                                                                                    +  "19 ou moins", "20-29", "30 et plus"
                                                                                    +)
                                                                                    +enfants$gpage_mere_naissance <- relevel(enfants$gpage_mere_naissance, "20-29")
                                                                                    +freq(enfants$gpage_mere_naissance)

                                                                                    Reste à calculer si le rang de naissance de l’enfant est supérieur au nombre idéal d’enfants tel que défini par la mère. On aura recours à la fonction rank appliquée par groupe (ici calculé séparément pour chaque mère). L’argument ties.method permet d’indiquer comment gérer les égalités (ici les naissances multiples, e.g. les jumeaux). Comme nous voulons comparer le rang de l’enfant au nombre idéal d’enfants, nous allons retenir la méthode "max" pour obtenir, dans le cas présent, le nombre total d’enfants déjà nés1. Avant de calculer un rang, il est impératif de trier préalablement le tableau (voir le chapitre Tris).

                                                                                    -
                                                                                    setorder(enfants, id_femme, date_naissance)
                                                                                    -enfants[, rang := rank(date_naissance, ties.method = "max"), by = id_femme]
                                                                                    -enfants[, rang_apres_ideal := "non"]
                                                                                    -# note: unclass() requis en raison d'un bug non corrigé dans haven empéchant de comparer haven_labelled_spss et integer
                                                                                    -enfants[rang > unclass(nb_enf_ideal), rang_apres_ideal := "oui"]
                                                                                    -enfants[, rang_apres_ideal := factor(rang_apres_ideal)]
                                                                                    -enfants[, rang_apres_ideal := relevel(rang_apres_ideal, "non")]
                                                                                    +
                                                                                    setorder(enfants, id_femme, date_naissance)
                                                                                    +enfants[, rang := rank(date_naissance, ties.method = "max"), by = id_femme]
                                                                                    +enfants[, rang_apres_ideal := "non"]
                                                                                    +# note: unclass() requis en raison d'un bug non corrigé dans haven empéchant de comparer haven_labelled_spss et integer
                                                                                    +enfants[rang > unclass(nb_enf_ideal), rang_apres_ideal := "oui"]
                                                                                    +enfants[, rang_apres_ideal := factor(rang_apres_ideal)]
                                                                                    +enfants[, rang_apres_ideal := relevel(rang_apres_ideal, "non")]

                                                                                  Préparation des données avec dplyr

                                                                                  Tout d’abord, regardons sous quel format elles sont stockées.

                                                                                  -
                                                                                  data(fecondite)
                                                                                  -class(menages)
                                                                                  +
                                                                                  data(fecondite)
                                                                                  +class(menages)
                                                                                  [1] "tbl_df"     "tbl"        "data.frame"
                                                                                  -
                                                                                  describe(menages)
                                                                                  +
                                                                                  describe(menages)
                                                                                  [1814 obs. x 5 variables] tbl_df tbl data.frame
                                                                                   
                                                                                   $id_menage: Identifiant du ménage
                                                                                  @@ -725,33 +728,33 @@ 

                                                                                  Préparation des données avec dplyr

                                                                                  5 value labels: [1] très pauvre [2] pauvre [3] moyen [4] riche [5] très riche

                                                                                  Les tableaux de données sont déjà au format tibble (c’est-à-dire sont de la classe tbl_df)2 et les variables catégorielles sont du type labelled (voir le chapitre sur les vecteurs labellisés). Ce format correspond au format de données si on les avait importées depuis SPSS avec l’extension haven (voir le chapitre sur l’import de données).

                                                                                  Nous allons charger en mémoire l’extension labelled pour la gestion des vecteurs labellisés en plus de dplyr.

                                                                                  -
                                                                                  library(dplyr)
                                                                                  -library(labelled)
                                                                                  +
                                                                                  library(dplyr)
                                                                                  +library(labelled)

                                                                                  En premier lieu, il nous faut calculer la durée d’observation des enfants, à savoir le temps passé entre la date de naissance (variable du fichier enfants) et la date de passation de l’entretien (fournie par le tableau de données femmes). Pour récupérer des variables du fichier femmes dans le fichier enfants, nous allons procéder à une fusion de table (voir le chapitre dédié). Pour le calcul de la durée d’observation, nous allons utiliser le package lubridate (voir le chapitre calculer un âge et celui sur la gestion des dates). Nous effectuerons l’analyse en mois (puisque l’âge au décès est connu en mois). Dès lors, la durée d’observation sera calculée en mois.

                                                                                  ATTENTION : les étiquettes de valeurs sont le plus souvent perdus lors des fusions de tables avec dplyr. Pour les récupérer après fusion, nous allons conserver une version originale du tableau de données enfants et utiliser la fonction copy_labels_from de l’extension labelled.

                                                                                  -
                                                                                  enfants_original <- enfants
                                                                                  -
                                                                                  -library(lubridate)
                                                                                  -enfants <- enfants %>%
                                                                                  -  left_join(
                                                                                  -    femmes %>% select(id_femme, date_entretien),
                                                                                  -    by = "id_femme"
                                                                                  -  ) %>%
                                                                                  -  copy_labels_from(enfants_original) %>%
                                                                                  -  mutate(duree_observation = time_length(
                                                                                  -    interval(date_naissance, date_entretien), 
                                                                                  -    unit = "months"
                                                                                  -  ))
                                                                                  +
                                                                                  enfants_original <- enfants
                                                                                  +
                                                                                  +library(lubridate)
                                                                                  +enfants <- enfants %>%
                                                                                  +  left_join(
                                                                                  +    femmes %>% select(id_femme, date_entretien),
                                                                                  +    by = "id_femme"
                                                                                  +  ) %>%
                                                                                  +  copy_labels_from(enfants_original) %>%
                                                                                  +  mutate(duree_observation = time_length(
                                                                                  +    interval(date_naissance, date_entretien), 
                                                                                  +    unit = "months"
                                                                                  +  ))

                                                                                  ATTENTION : il y 11 enfants soi-disant nés après la date d’enquête ! Quelle que soit l’enquête, il est rare de ne pas observer d’incohérences. Dans le cas présent, il est fort possible que la date d’entretien puisse parfois être erronnée (par exemple si l’enquêteur a inscrit une date sur le questionnaire papier le jour du recensement du ménage mais n’ai pu effectué le questionnaire individuel que plus tard). Nous décidons ici de procéder à une correction en ajoutant un mois aux dates d’entretien problématiques. D’autres approches auraient pu être envisagées, comme par exemple exclure ces observations problématiques. Cependant, cela aurait impacté le calcul du range de naissance pour les autres enfants issus de la même mère. Quoiqu’il en soit, il n’y a pas de réponse unique. À vous de vous adapter au contexte particulier de votre analyse.

                                                                                  -
                                                                                  enfants$date_entretien[enfants$duree_observation < 0] <-
                                                                                  -  enfants$date_entretien[enfants$duree_observation < 0] %m+% months(1)
                                                                                  -enfants <- enfants %>%
                                                                                  -  mutate(duree_observation = time_length(
                                                                                  -    interval(date_naissance, date_entretien), 
                                                                                  -    unit = "months"
                                                                                  -  ))
                                                                                  +
                                                                                  enfants$date_entretien[enfants$duree_observation < 0] <-
                                                                                  +  enfants$date_entretien[enfants$duree_observation < 0] %m+% months(1)
                                                                                  +enfants <- enfants %>%
                                                                                  +  mutate(duree_observation = time_length(
                                                                                  +    interval(date_naissance, date_entretien), 
                                                                                  +    unit = "months"
                                                                                  +  ))

                                                                                  Regardons maintenant comment les âges au décès ont été collectés.

                                                                                  -
                                                                                  freq(enfants$age_deces)
                                                                                  +
                                                                                  freq(enfants$age_deces)

                                                                                  Tout d’abord, la modalité pas d’adulte n’est pas représentée dans l’échantillon. On aura donc recours à l’argument drop_unused_labels pour ne pas conserver cette modalité. Par ailleurs, nous considérons que la situation familiale à partir de laquelle nous voudrons comparer les autres dans notre modèle, donc celle qui doit être considérée comme la modalité de référence, est celle du ménage nucléaire. Cette modalité (deux adultes de sexe opposé) n’étant pas la première, nous aurons recours à la fonction relevel{data-pkg = “stats”}.

                                                                                  -
                                                                                  enfants <- enfants %>%
                                                                                  -  mutate(structure = relevel(
                                                                                  -    to_factor(structure, drop_unused_labels = TRUE),
                                                                                  -    "deux adultes de sexe opposé"
                                                                                  -  ))
                                                                                  +
                                                                                  enfants <- enfants %>%
                                                                                  +  mutate(structure = relevel(
                                                                                  +    to_factor(structure, drop_unused_labels = TRUE),
                                                                                  +    "deux adultes de sexe opposé"
                                                                                  +  ))

                                                                                  Regardons la variable educ.

                                                                                  -
                                                                                  freq(enfants$educ)
                                                                                  +
                                                                                  freq(enfants$educ)

                                                                                  La modalité supérieur est peu représentée dans notre échantillon. Nous allons la fusionner avec la modalité secondaire (voir la section Regrouper les modalités d’une variable du chapitre Recodage).

                                                                                  -
                                                                                  enfants <- enfants %>%
                                                                                  -  mutate(educ2 = ifelse(educ == 3, 2, educ)) %>%
                                                                                  -  set_value_labels(educ2 = c(
                                                                                  -    aucun = 0,
                                                                                  -    primaire = 1,
                                                                                  -    "secondaire ou plus" = 2
                                                                                  -  )) %>%
                                                                                  -  mutate(educ2 = to_factor(educ2))
                                                                                  -freq(enfants$educ2)
                                                                                  +
                                                                                  enfants <- enfants %>%
                                                                                  +  mutate(educ2 = ifelse(educ == 3, 2, educ)) %>%
                                                                                  +  set_value_labels(educ2 = c(
                                                                                  +    aucun = 0,
                                                                                  +    primaire = 1,
                                                                                  +    "secondaire ou plus" = 2
                                                                                  +  )) %>%
                                                                                  +  mutate(educ2 = to_factor(educ2))
                                                                                  +freq(enfants$educ2)

                                                                                  Calculons maintenant l’âge de la mère à la naissance de l’enfant (voir le chapitre Caluler un âge) et découpons le en groupes d’âges (voir la section Découper une variable numérique en classes du chapitre Recodage).

                                                                                  -
                                                                                  enfants <- enfants %>%
                                                                                  -  mutate(
                                                                                  -    age_mere_naissance = time_length(
                                                                                  -      interval(date_naissance_mere, date_naissance), 
                                                                                  -      unit = "years"
                                                                                  -    ),
                                                                                  -    gpage_mere_naissance = cut(
                                                                                  -      age_mere_naissance, 
                                                                                  -      include.lowest = TRUE, right = FALSE,
                                                                                  -      breaks=c(13, 20, 30, 50)
                                                                                  -    )
                                                                                  -  )
                                                                                  -  
                                                                                  -levels(enfants$gpage_mere_naissance) <- c(
                                                                                  -  "19 ou moins", "20-29", "30 et plus"
                                                                                  -)
                                                                                  -enfants$gpage_mere_naissance <- relevel(enfants$gpage_mere_naissance, "20-29")
                                                                                  -freq(enfants$gpage_mere_naissance)
                                                                                  +
                                                                                  enfants <- enfants %>%
                                                                                  +  mutate(
                                                                                  +    age_mere_naissance = time_length(
                                                                                  +      interval(date_naissance_mere, date_naissance), 
                                                                                  +      unit = "years"
                                                                                  +    ),
                                                                                  +    gpage_mere_naissance = cut(
                                                                                  +      age_mere_naissance, 
                                                                                  +      include.lowest = TRUE, right = FALSE,
                                                                                  +      breaks=c(13, 20, 30, 50)
                                                                                  +    )
                                                                                  +  )
                                                                                  +  
                                                                                  +levels(enfants$gpage_mere_naissance) <- c(
                                                                                  +  "19 ou moins", "20-29", "30 et plus"
                                                                                  +)
                                                                                  +enfants$gpage_mere_naissance <- relevel(enfants$gpage_mere_naissance, "20-29")
                                                                                  +freq(enfants$gpage_mere_naissance)

                                                                                  Reste à calculer si le rang de naissance de l’enfant est supérieur au nombre idéal d’enfants tel que défini par la mère. On aura recours à la fonction rank appliquée par groupe (ici calculé séparément pour chaque mère). L’argument ties.method permet d’indiquer comment gérer les égalités (ici les naissances multiples, e.g. les jumeaux). Comme nous voulons comparer le rang de l’enfant au nombre idéal d’enfants, nous allons retenir la méthode "max" pour obtenir, dans le cas présent, le nombre total d’enfants déjà nés3. Avant de calculer un rang, il est impératif de trier préalablement le tableau (voir le chapitre Tris).

                                                                                  -
                                                                                  enfants <- enfants %>%
                                                                                  -  arrange(id_femme, date_naissance) %>%
                                                                                  -  group_by(id_femme) %>%
                                                                                  -  mutate(
                                                                                  -    rang = rank(date_naissance, ties.method = "max"),
                                                                                  -    rang_apres_ideal = ifelse(rang > as.integer(nb_enf_ideal), "oui", "non"),
                                                                                  -    rang_apres_ideal = factor(rang_apres_ideal, levels = c("non", "oui"))
                                                                                  -  )
                                                                                  +
                                                                                  enfants <- enfants %>%
                                                                                  +  arrange(id_femme, date_naissance) %>%
                                                                                  +  group_by(id_femme) %>%
                                                                                  +  mutate(
                                                                                  +    rang = rank(date_naissance, ties.method = "max"),
                                                                                  +    rang_apres_ideal = ifelse(rang > as.integer(nb_enf_ideal), "oui", "non"),
                                                                                  +    rang_apres_ideal = factor(rang_apres_ideal, levels = c("non", "oui"))
                                                                                  +  )

                                                                                  Kaplan-Meier

                                                                                  La courbe de survie de Kaplan-Meier s’obtient avec la fonction survfit de l’extension survival.

                                                                                  -
                                                                                  library(survival)
                                                                                  -km_global <- survfit(Surv(time, deces) ~ 1, data = enfants)
                                                                                  -km_global
                                                                                  +
                                                                                  library(survival)
                                                                                  +km_global <- survfit(Surv(time, deces) ~ 1, data = enfants)
                                                                                  +km_global
                                                                                  Call: survfit(formula = Surv(time, deces) ~ 1, data = enfants)
                                                                                   
                                                                                           n events median 0.95LCL 0.95UCL
                                                                                   [1,] 1584    142     NA      NA      NA

                                                                                  Pour la représenter, on pourra avoir recours à la fonction ggsurvplot de l’extension survminer.

                                                                                  -
                                                                                  library(survminer, quietly = TRUE)
                                                                                  -
                                                                                  
                                                                                  -Attachement du package : 'ggpubr'
                                                                                  -
                                                                                  L'objet suivant est masqué depuis 'package:JLutils':
                                                                                  -
                                                                                  -    get_legend
                                                                                  -
                                                                                  L'objet suivant est masqué depuis 'package:plyr':
                                                                                  -
                                                                                  -    mutate
                                                                                  +
                                                                                  library(survminer, quietly = TRUE)
                                                                                  
                                                                                   Attachement du package : 'survminer'
                                                                                  L'objet suivant est masqué depuis 'package:survival':
                                                                                   
                                                                                       myeloma
                                                                                  -
                                                                                  ggsurvplot(km_global)
                                                                                  +
                                                                                  ggsurvplot(km_global)
                                                                                  Courbe de survie de Kaplan-Meier @@ -894,22 +889,22 @@

                                                                                  Kaplan-Meier

                                                                                  On peut facilement représenter à la place la courbe cumulée des évènements (l’inverse de la courbe de survie) et la table des effectifs en fonction du temps.

                                                                                  -
                                                                                  ggsurvplot(km_global, fun = "event", risk.table = TRUE, surv.scale = "percent")
                                                                                  +
                                                                                  ggsurvplot(km_global, fun = "event", risk.table = TRUE, surv.scale = "percent")
                                                                                  Courbe cumulée des évènements et table des effectifs

                                                                                  Pour comparer deux groupes (ici les filles et les garçons), il suffit d’indiquer la variable de comparaison à survfit.

                                                                                  -
                                                                                  km_sexe <- survfit(Surv(time, deces) ~ sexe, data = enfants)
                                                                                  -km_sexe
                                                                                  +
                                                                                  km_sexe <- survfit(Surv(time, deces) ~ sexe, data = enfants)
                                                                                  +km_sexe
                                                                                  Call: survfit(formula = Surv(time, deces) ~ sexe, data = enfants)
                                                                                   
                                                                                                   n events median 0.95LCL 0.95UCL
                                                                                   sexe=masculin 762     94     NA      NA      NA
                                                                                   sexe=féminin  822     48     NA      NA      NA

                                                                                  La fonction survdiff permets de calculer le test du logrank afin de comparer des courbes de survie. La mortalité infanto-juvénile diffère-t-elle significativement selon le sexe de l’enfant ?

                                                                                  -
                                                                                  survdiff(Surv(time, deces) ~ sexe, data = enfants)
                                                                                  +
                                                                                  survdiff(Surv(time, deces) ~ sexe, data = enfants)
                                                                                  Call:
                                                                                   survdiff(formula = Surv(time, deces) ~ sexe, data = enfants)
                                                                                   
                                                                                  @@ -920,7 +915,7 @@ 

                                                                                  Kaplan-Meier

                                                                                  Chisq= 21.8 on 1 degrees of freedom, p= 3e-06

                                                                                  Une fois encore, on aura recours à ggsurvplot pour représenter les courbes de survie.

                                                                                  -
                                                                                  ggsurvplot(km_sexe, conf.int = TRUE, risk.table = TRUE, pval = TRUE, data = enfants)
                                                                                  +
                                                                                  ggsurvplot(km_sexe, conf.int = TRUE, risk.table = TRUE, pval = TRUE, data = enfants)
                                                                                  Courbes de Kaplan-Meier selon le sexe @@ -930,12 +925,12 @@

                                                                                  Kaplan-Meier

                                                                                  Modèle de Cox

                                                                                  Un modèle de Cox se calcule aisément avec coxph{survival}.

                                                                                  -
                                                                                  mod1 <- coxph(
                                                                                  -  Surv(time, deces) ~ sexe + milieu + richesse + 
                                                                                  -  structure + educ2 + gpage_mere_naissance + rang_apres_ideal, 
                                                                                  -  data = enfants
                                                                                  -)
                                                                                  -mod1
                                                                                  +
                                                                                  mod1 <- coxph(
                                                                                  +  Surv(time, deces) ~ sexe + milieu + richesse + 
                                                                                  +  structure + educ2 + gpage_mere_naissance + rang_apres_ideal, 
                                                                                  +  data = enfants
                                                                                  +)
                                                                                  +mod1
                                                                                  Call:
                                                                                   coxph(formula = Surv(time, deces) ~ sexe + milieu + richesse + 
                                                                                       structure + educ2 + gpage_mere_naissance + rang_apres_ideal, 
                                                                                  @@ -1025,7 +1020,7 @@ 

                                                                                  Modèle de Cox

                                                                                  Likelihood ratio test=38.16 on 15 df, p=0.0008546 n= 1584, number of events= 142

                                                                                  De nombreuses variables ne sont pas significatives. Voyons si nous pouvons, avec la fonction step, améliorer notre modèle par minimisation de l’AIC ou Akaike Information Criterion (voir la section Sélection de modèles du chapitre sur la Régression logistique).

                                                                                  -
                                                                                  mod2 <- step(mod1)
                                                                                  +
                                                                                  mod2 <- step(mod1)
                                                                                  Start:  AIC=2027.07
                                                                                   Surv(time, deces) ~ sexe + milieu + richesse + structure + educ2 + 
                                                                                       gpage_mere_naissance + rang_apres_ideal
                                                                                  @@ -1084,8 +1079,10 @@ 

                                                                                  Modèle de Cox

                                                                                  - milieu 1 2013.9 - sexe 1 2031.6

                                                                                  On peut obtenir facilement les coefficients du modèle avec l’excellente fonction tidy de l’extension broom. Ne pas oublier de préciser exponentiate = TRUE. En effet, dans le cas d’un modèle de Cox, l’exponentiel des coefficients corresponds au ratio des risques instantannés ou hazard ratio (HR) en anglais.

                                                                                  -
                                                                                  library(broom, quietly = TRUE)
                                                                                  -tidy(mod2, exponentiate = TRUE)
                                                                                  +
                                                                                  library(broom, quietly = TRUE)
                                                                                  +
                                                                                  Warning: le package 'broom' a été compilé avec la version R
                                                                                  +4.1.2
                                                                                  +
                                                                                  tidy(mod2, exponentiate = TRUE)

    @@ -42263,7 +76319,7 @@

    printr

    a
    -127 +126
    -129 +132
    -136 +138
    -97 +100
    -134 +135
    -123 +118
    -116 +114
    -138 +137