Chapter 7 Tableaux

Dans ce chapitre, nous présenterons quelques packages permettant d’obtenir des tables descriptives complètes, faciles à mettre en forme pour les intégrer dans un rapport d’analyse.

7.1 Base d’exemple pour une analyse uni- et bivariée

Nous allons utiliser la base df_1 déjà utilisées au sein de laquelle nous allons créer quelques données manquantes pour corser les choses !

rm(list=ls())
## Import des données
df_1 <- read.csv2("data/df_1.csv")
meta_df_1 <- read.csv2("data/meta_df_1.csv")

# pour chaque variable, 10% des valeurs sont remplacées au hasard par des manquants
set.seed(6543)
df_1miss <- df_1
for (i in 2:ncol(df_1miss)) {
  df_1miss[[i]] <- ifelse(rbinom(n = nrow(df_1miss), size = 1, prob = 0.10) == 1, 
                          NA, df_1miss[[i]])
}
summary(df_1miss)
##      subjid            sex              imc            trait      
##  Min.   :  1.00   Min.   :0.0000   Min.   :15.40   Min.   :1.000  
##  1st Qu.: 75.75   1st Qu.:0.0000   1st Qu.:22.20   1st Qu.:1.000  
##  Median :150.50   Median :1.0000   Median :24.50   Median :2.000  
##  Mean   :150.50   Mean   :0.5054   Mean   :24.36   Mean   :1.906  
##  3rd Qu.:225.25   3rd Qu.:1.0000   3rd Qu.:26.30   3rd Qu.:3.000  
##  Max.   :300.00   Max.   :1.0000   Max.   :32.20   Max.   :3.000  
##                   NAs    :23       NAs    :37      NAs    :33     
##       pas       
##  Min.   : 92.0  
##  1st Qu.:125.0  
##  Median :138.0  
##  Mean   :137.2  
##  3rd Qu.:149.0  
##  Max.   :177.0  
##  NAs    :33
# on va créer des variables qualitatives au format factor pour le sexe et traitement
df_1miss$sexL <- factor(df_1miss$sex,
                        labels = meta_df_1$labs[meta_df_1$var == "sex"])
df_1miss$traitL <- factor(df_1miss$trait,
                          labels = meta_df_1$labs[meta_df_1$var == "trait"])

## pensez à vérifier que le recodage est correct
# table(df_1miss$sexL, df_1miss$sex)
# table(df_1miss$traitL, df_1miss$trait)

# mise à jour de la base de méta données
meta_df_1 <- rbind(meta_df_1, 
                   data.frame(var = "sexL", 
                              label = meta_df_1$label[meta_df_1$var == "sex"],
                              id_labs = names(table(as.numeric(df_1miss$sexL))),
                              code_labs = as.numeric(names(table(as.numeric(df_1miss$sexL)))),
                              labs = levels(df_1miss$sexL)))
meta_df_1 <- rbind(meta_df_1, 
                   data.frame(var = "traitL", 
                              label = meta_df_1$label[meta_df_1$var == "trait"],
                              id_labs = names(table(as.numeric(df_1miss$traitL))),
                              code_labs = as.numeric(names(table(as.numeric(df_1miss$traitL)))),
                              labs = levels(df_1miss$traitL)))

L’objectif sera de faire une table descriptive univariée de l’IMC, de la PAS, du sexe et du traitement. Puis, on fera une table bivariée de l’IMC, de la PAS et du sexe en fonction des groupes de traitement.

7.2 Package table1

Le package table1 permet de mettre facilement en forme des tableaux de statistiques descriptives uni- ou bivariées que l’on peut ensuite copier-coller dans un rapport (ou directement l’intégrer dans un rapport écrit avec Rmarkdown ou Quarto).

7.2.1 Tables univariées

Une table univariée peut être obtenue avec une écriture au format formula, où les variables à décrire sont à gauche d’un signe ~ espacés de +. Voici un exmple ci-dessous

library(table1)
## Pour une analyse descriptive univariée
table1(~ imc + pas + traitL + sexL, data = df_1miss)
Overall
(N=300)
imc
Mean (SD) 24.4 (3.04)
Median [Min, Max] 24.5 [15.4, 32.2]
Missing 37 (12.3%)
pas
Mean (SD) 137 (16.9)
Median [Min, Max] 138 [92.0, 177]
Missing 33 (11.0%)
traitL
Placebo 104 (34.7%)
Traitement A 84 (28.0%)
Traitement B 79 (26.3%)
Missing 33 (11.0%)
sexL
Féminin 137 (45.7%)
Masculin 140 (46.7%)
Missing 23 (7.7%)

A noter que par défaut, les données manquantes des variables catégorielles sont considérées comme une catégorie à part entière, et les pourcentages sont calculés en prenant en compte les manquants. Par exemple pour le sexe, 45.7% + 46.7% + 7.7% = 100% en prenant en compte les 7.7% de données manquantes.

Généralement pour calculer les pourcentages, on préfèrera exclure les données manquantes.

Le package table1 permet d’ajouter un nom de variable plus explicite aux variables :

## la fonction label() permet d'ajouter un nom de variable sous forme d'attribut
label(df_1miss$sexL) <- meta_df_1$label[meta_df_1$var == "sexL"][1]
label(df_1miss$traitL) <- meta_df_1$label[meta_df_1$var == "traitL"][1]
label(df_1miss$imc) <- meta_df_1$label[meta_df_1$var == "imc"]
label(df_1miss$pas) <- meta_df_1$label[meta_df_1$var == "pas"]

attributes(df_1miss$sexL)
## $levels
## [1] "Féminin"  "Masculin"
## 
## $class
## [1] "factor"
## 
## $label
## [1] "Sexe"
attributes(df_1miss$imc)
## $label
## [1] "IMC (kg/m²)"
## si on relance la fonction, les noms de variables explicites sont visibles
table1(~ imc + pas + traitL + sexL, data = df_1miss)
Overall
(N=300)
IMC (kg/m²)
Mean (SD) 24.4 (3.04)
Median [Min, Max] 24.5 [15.4, 32.2]
Missing 37 (12.3%)
PAS (mmHg)
Mean (SD) 137 (16.9)
Median [Min, Max] 138 [92.0, 177]
Missing 33 (11.0%)
Traitement
Placebo 104 (34.7%)
Traitement A 84 (28.0%)
Traitement B 79 (26.3%)
Missing 33 (11.0%)
Sexe
Féminin 137 (45.7%)
Masculin 140 (46.7%)
Missing 23 (7.7%)

Il est possible de choisir plus spécifiquement quels paramètres afficher. Pour plus d’information sur les paramètres de distributions disponibles avec table1, vous pouvez chercher dans l’aide ?stats.default.

# par défaut, les variables continues sont présentées avec cet argument
render.continuous.default
## function (x, ...) 
## {
##     with(stats.apply.rounding(stats.default(x, ...), ...), c("", 
##         `Mean (SD)` = sprintf("%s (%s)", MEAN, SD), `Median [Min, Max]` = sprintf("%s [%s, %s]", 
##             MEDIAN, MIN, MAX)))
## }
## <bytecode: 0x000001e0014ac2a8>
## <environment: namespace:table1>
# les variables catégorielles sont présentése avec cet argument
render.categorical.default
## function (x, ..., na.is.category = TRUE) 
## {
##     c("", sapply(stats.apply.rounding(stats.default(x, ...), 
##         ...), function(y) with(y, sprintf("%s (%s%%)", FREQ, 
##         if (na.is.category) PCT else PCTnoNA))))
## }
## <bytecode: 0x000001e0014a0e78>
## <environment: namespace:table1>

On peut créer de nouvelles fonctions de rendu en prenant ces deux fonctions pour modèle. Ci-dessous, on va créer une fonction de rendu pour les variables quantitatives qui ne donne que la moyenne et la déviation standard entre parenthèses.

Puis, on va créer une fonction de rendu pour les variables catégorielles qui calcule les pourcentages en excluant les données manquantes.

# fonction pour afficher uniquement la moyenne +/- écart type pour les variables
# quantitatives
render_var_quanti <- function(x) {   
  with(stats.default(x), 
       c("", 
         `Mean (SD)` = sprintf("%0.1f (%0.1f)", MEAN, SD)))
}

# fonction pour exclure les données manquantes pour le calcul des pourcentages
render_var_quali <- function (x, ..., na.is.category = FALSE) { # FALSE remplace TRUE
    c("",
      sapply(stats.apply.rounding(stats.default(x, ...), ...),
             function(y) with(y, sprintf("%s (%s%%)", FREQ,
                                         if (na.is.category) PCT else PCTnoNA)))) 
}

## on applique ensuite ces deux fonctions de rendus dans la fonction table1
table1(~ imc + pas + traitL + sexL, 
       data = df_1miss, 
       render.continuous = render_var_quanti, 
       render.categorical = render_var_quali)
Overall
(N=300)
IMC (kg/m²)
Mean (SD) 24.4 (3.0)
Missing 37 (12.3%)
PAS (mmHg)
Mean (SD) 137.2 (16.9)
Missing 33 (11.0%)
Traitement
Placebo 104 (39.0%)
Traitement A 84 (31.5%)
Traitement B 79 (29.6%)
Missing 33 (11.0%)
Sexe
Féminin 137 (49.5%)
Masculin 140 (50.5%)
Missing 23 (7.7%)

A présent, on voit que pour le sexe, la somme 49.5% + 50.5% = 100%, la catégorie des manquants n’est pas prise en compte pour le calcul des pourcentages.

7.2.2 Tables bivariées

Pour obtenir une table bivariée, on ajoute une barre verticale suivie d’une variable de format factor.

## Exemple de formule pour une table bivariée en fonction de traitL
table1(~ imc + pas + sexL | traitL, data = df_1miss)
# mais cet exemple ne fonctionne pas car la variable de stratification 
# ne doit pas contenir de données manquante +++

## Il faut donc exclure les lignes avec un traitement manquant dans la base
table1(~ imc + pas + sexL | traitL, 
       data = subset(df_1miss, subset = !is.na(traitL))) 
# la fonction subset a effacé les attributs "level" des noms de variables
## Pour ne pas supprimer les attibuts de variables, il vaut mieux utiliser
## la fonction filter du tidyverse (qui conserve les attributs)
table1(~ imc + pas + sexL | traitL, 
       data = dplyr::filter(df_1miss, !is.na(traitL))) 
Placebo
(N=104)
Traitement A
(N=84)
Traitement B
(N=79)
Overall
(N=267)
IMC (kg/m²)
Mean (SD) 24.4 (2.84) 24.7 (3.03) 24.2 (3.53) 24.4 (3.12)
Median [Min, Max] 24.5 [17.0, 31.2] 24.8 [15.4, 30.3] 24.1 [16.1, 32.2] 24.6 [15.4, 32.2]
Missing 11 (10.6%) 11 (13.1%) 9 (11.4%) 31 (11.6%)
PAS (mmHg)
Mean (SD) 143 (15.8) 130 (16.2) 137 (17.0) 137 (17.1)
Median [Min, Max] 142 [105, 177] 130 [92.0, 161] 138 [103, 177] 138 [92.0, 177]
Missing 17 (16.3%) 9 (10.7%) 7 (8.9%) 33 (12.4%)
Sexe
Féminin 40 (38.5%) 40 (47.6%) 37 (46.8%) 117 (43.8%)
Masculin 58 (55.8%) 40 (47.6%) 30 (38.0%) 128 (47.9%)
Missing 6 (5.8%) 4 (4.8%) 12 (15.2%) 22 (8.2%)

Comme pour l’analyse univariée, on peut choisir de n’afficher que la moyenne et l’écart type pour les variables quantitatives et calculer les pourcentages en excluant les manquants pour les variables qualitatives.

table1(~ imc + pas + sexL | traitL, 
       data = dplyr::filter(df_1miss, !is.na(traitL)),
       render.continuous = render_var_quanti, 
       render.categorical = render_var_quali) 
Placebo
(N=104)
Traitement A
(N=84)
Traitement B
(N=79)
Overall
(N=267)
IMC (kg/m²)
Mean (SD) 24.4 (2.8) 24.7 (3.0) 24.2 (3.5) 24.4 (3.1)
Missing 11 (10.6%) 11 (13.1%) 9 (11.4%) 31 (11.6%)
PAS (mmHg)
Mean (SD) 143.1 (15.8) 130.5 (16.2) 137.2 (17.0) 137.2 (17.1)
Missing 17 (16.3%) 9 (10.7%) 7 (8.9%) 33 (12.4%)
Sexe
Féminin 40 (40.8%) 40 (50.0%) 37 (55.2%) 117 (47.8%)
Masculin 58 (59.2%) 40 (50.0%) 30 (44.8%) 128 (52.2%)
Missing 6 (5.8%) 4 (4.8%) 12 (15.2%) 22 (8.2%)

Une des limites du package table1 est qu’il ne permet pas d’afficher une colonne de p-values pour donner les résultats de tests de comparaison bivariés.

7.3 Package gtsummary

Le package gtsummary permet de calculer des statistiques descriptives univariées ou bivariées et de les présenter facilement sous forme de tableau. Il s’agit d’une alternative au package table1.

Ce package permet également de présenter facilement les sorties de régression sous forme tabulée que l’on peut intégrer directement dans un rapport d’analyse.

La fonction principale est la fonction gtsummary()

library(gtsummary)
?gtsummary::tbl_summary
## Par défaut : 
## - les variables quantitatives sont décrites par médiane (Q25, Q75)
## - les données manquantes sont indiqués par "unknown"
## - les pourcentages sont calculés par colonne après exclusion des manquants 
##   et sont arrondis sans chiffre après la virgule
df_1miss |> 
  tbl_summary(include = c(imc, pas, traitL, sexL))
Characteristic N = 3001
IMC (kg/m²) 24.50 (22.20, 26.30)
    Unknown 37
PAS (mmHg) 138 (125, 149)
    Unknown 33
Traitement
    Placebo 104 (39%)
    Traitement A 84 (31%)
    Traitement B 79 (30%)
    Unknown 33
Sexe
    Féminin 137 (49%)
    Masculin 140 (51%)
    Unknown 23
1 Median (Q1, Q3); n (%)

On peut modifier les paramètres à présenter :

## Dans l'exemple ci-dessous :
## - argument "statistic" : 
##   les variables continues sont présentées par la moyenne ± SD
##   les variables qualitatives sont présentés par l'effectif (%)
## - argument "digits" :
##   on arrondi la moyenne et l'écart type à 1 chiffre après la virgule
##   et l'effectif et le pourcentage à 0 et 1 chiffre après la virgule
## - argument "missing" : quel attitude vis à vis des manquants
## - argument "missing_text" : texte pour la ligne des effectifs manquants
## - argument "percent" : calcul des % en colonnes, en lignes, ou par case
df_1miss |> 
  tbl_summary(include = c(imc, pas, traitL, sexL), 
              statistic = list(all_continuous() ~ "{mean} ± {sd}", 
                                all_categorical() ~ "{n} ({p}%)"),
              digits = list(all_continuous() ~ c(1, 1), 
                            all_categorical() ~ c(0, 1)),
              missing = c("ifany"), # exclude if any missing
              missing_text = "N manquant", 
              percent = c("column")
              )
Characteristic N = 3001
IMC (kg/m²) 24.4 ± 3.0
    N manquant 37
PAS (mmHg) 137.2 ± 16.9
    N manquant 33
Traitement
    Placebo 104 (39.0%)
    Traitement A 84 (31.5%)
    Traitement B 79 (29.6%)
    N manquant 33
Sexe
    Féminin 137 (49.5%)
    Masculin 140 (50.5%)
    N manquant 23
1 Mean ± SD; n (%)

L’argument by permet de présenter une table bivariée :

## Table bivariée selon le traitment
df_1miss |> 
  tbl_summary(by = traitL, # présentation bivariée selon la variable traitL
              include = c(imc, pas, sexL), 
              statistic = list(all_continuous() ~ "{mean} ± {sd}", 
                               all_categorical() ~ "{n} ({p}%)"),
              digits = list(all_continuous() ~ c(1, 1), 
                            all_categorical() ~ c(0, 1)),
              missing = c("ifany"), # exclude if any missing
              missing_text = "N manquant", 
              percent = c("column")
  )
## 33 missing rows in the "traitL" column have been removed.
Characteristic Placebo
N = 104
1
Traitement A
N = 84
1
Traitement B
N = 79
1
IMC (kg/m²) 24.4 ± 2.8 24.7 ± 3.0 24.2 ± 3.5
    N manquant 11 11 9
PAS (mmHg) 143.1 ± 15.8 130.5 ± 16.2 137.2 ± 17.0
    N manquant 17 9 7
Sexe


    Féminin 40 (40.8%) 40 (50.0%) 37 (55.2%)
    Masculin 58 (59.2%) 40 (50.0%) 30 (44.8%)
    N manquant 6 4 12
1 Mean ± SD; n (%)
## On peut ajouter une colonne total avec la fonction "add_overall()"
df_1miss |> 
  tbl_summary(by = traitL, # présentation bivariée selon la variable traitL
              include = c(imc, pas, sexL), 
              statistic = list(all_continuous() ~ "{mean} ± {sd}", 
                               all_categorical() ~ "{n} ({p}%)"),
              digits = list(all_continuous() ~ c(1, 1), 
                            all_categorical() ~ c(0, 1)),
              missing = c("ifany"), # exclude if any missing
              missing_text = "N manquant", 
              percent = c("column")
  ) |>
  add_overall(last = TRUE, # TRUE = en fin du tableau, FALSE = au début du tableau
              col_label = "**Total**  \nN = {style_number(N)}") # Titre de colonne
## 33 missing rows in the "traitL" column have been removed.
Characteristic Placebo
N = 104
1
Traitement A
N = 84
1
Traitement B
N = 79
1
Total
N = 267
1
IMC (kg/m²) 24.4 ± 2.8 24.7 ± 3.0 24.2 ± 3.5 24.4 ± 3.1
    N manquant 11 11 9 31
PAS (mmHg) 143.1 ± 15.8 130.5 ± 16.2 137.2 ± 17.0 137.2 ± 17.1
    N manquant 17 9 7 33
Sexe



    Féminin 40 (40.8%) 40 (50.0%) 37 (55.2%) 117 (47.8%)
    Masculin 58 (59.2%) 40 (50.0%) 30 (44.8%) 128 (52.2%)
    N manquant 6 4 12 22
1 Mean ± SD; n (%)

7.4 Méthodes plus flexibles

Les packages table1 et gtsummary permettent d’obtenir des tables uni- et bivariées très facilement, mais ils restent en partie limités pour adapter la table à sa guise.

Pour obtenir des tables avec une plus grande liberté de présentation, on peut créer un tableau (data.frame) contenant exactement les résultats que l’on veut présenter.

Par exemple, on va chercher à obtenir une mise en forme comme celles de la figure ci-dessous :
Formats souhaités

Figure 7.1: Formats souhaités

7.4.1 Création d’une base de données pour une table univariée

7.4.1.1 Variables quantitatives

On va créer un tableau à 2 colonnes :

  • la première colonne contiendra les noms de variables et les paramètres statistiques
  • la deuxième colonne contiendra au format texte : les effectifs observés et manquants, ou la moyenne et l’écart type entre parenthèses.
# il faut calculer les effectifs observés (non manquants), les effectifs manquants,
# la moyenne et l'écart type :
N <- length(which(!is.na(df_1miss$imc)))
N_miss <- length(which(is.na(df_1miss$imc)))
moy <- mean(df_1miss$imc, na.rm = TRUE)
std <- sd(df_1miss$imc, na.rm = TRUE)

## puis on créé un data.frame à deux colonne contenant les résultats
## mis en forme selon la présentation souhaitée
tab_imc <- data.frame(var = c(meta_df_1$label[meta_df_1$var == "imc"],
                              "n / n missing", 
                              "moyenne (DS)"),
                      stat= c("", # la première ligne de la colonne stat est vide
                              paste0(N, " / ", N_miss),
                              paste0(round(moy, digits = 1), " (",
                                     round(std, digits = 1), ")")))
tab_imc
##             var     stat
## 1   IMC (kg/m²)         
## 2 n / n missing 263 / 37
## 3  moyenne (DS) 24.4 (3)

Pour automatiser la création de cette table, on peut écrire une nouvelle fonction, comme dans l’exemple ci-dessous

# la fonction prend 3 arguments
tab_univ_quanti <- function(data, # le data frame
                            metadata, # la base de méta-données
                            variables) { # vecteur de noms de variables quanti
  # la table associée à chaque variable sera stockée dans une liste
  tab_list <- list() # créée une liste vide
  
  for (i in variables) { # boucle pour chaque variable du vecteur variables
    N <- length(which(!is.na(data[[i]])))
    N_miss <- length(which(is.na(data[[i]])))
    moyenne <- mean(data[[i]], na.rm = TRUE)
    std <- sd(data[[i]], na.rm = TRUE)
    
    tab_list[[i]] <- data.frame(var = c(metadata$label[metadata$var == i],
                                  "n / n missing", 
                                  "moyenne (DS)"),
                          stat= c("", # la première ligne de la colonne stat est vide
                                  paste0(N, " / ", N_miss),
                                  paste0(round(moyenne, digits = 1), " (",
                                         round(std, digits = 1), ")")))
  }
  
  # on commence par créer une table commune vide
  tab_pooled <- data.frame(matrix("", ncol = 2, nrow = 0))
  # on empile chaque table stockée dans la liste les unes après les autres
  for(j in 1:length(tab_list)) {
    tab_pooled <- rbind(tab_pooled, tab_list[[j]])
  }
  
  return(tab_pooled)
}

tab_quanti <- tab_univ_quanti(data = df_1miss, # le data frame
                              metadata = meta_df_1, # la base de méta-données
                              variables = c("imc", "pas")) # variables quantitatives
tab_quanti
##             var         stat
## 1   IMC (kg/m²)             
## 2 n / n missing     263 / 37
## 3  moyenne (DS)     24.4 (3)
## 4    PAS (mmHg)             
## 5 n / n missing     267 / 33
## 6  moyenne (DS) 137.2 (16.9)

7.4.1.2 Variables qualitatives

On va faire une table indiquant sur chaque ligne l’effectif et le pourcentage après exclusion des manquants. La dernière ligne contient les effectifs observés et manquants. Comme précédemment, la première colonne contiendra le nom de la variable et le nom des labels.

# On commence avec une table à 2 colonnes indiquant les effectifs et les pourcentages.
# Les pourcentages seront calculés en excluant les données manquantes 
# (comme sous une hypothèse "missing completely at random" MCAR)
tab_sex_temp <- cbind(table(df_1miss$sexL, useNA = "no"), 
                      prop.table(table(df_1miss$sexL, useNA = "no"))) 
tab_sex_temp
##          [,1]      [,2]
## Féminin   137 0.4945848
## Masculin  140 0.5054152
# La fonction paste0 peut être utilisée pour concaténer les 2 colonnes en un seul 
# vecteur, avec une mise en forme des pourcentages entre parenthèses et 1 chiffre
# après la virgule
paste0(tab_sex_temp[,1], " (", round(tab_sex_temp[,2] * 100, digits = 1), "%)")
## [1] "137 (49.5%)" "140 (50.5%)"
tab_sex <- data.frame(var = c(meta_df_1$label[meta_df_1$var == "sexL"][1],
                              levels(df_1miss$sexL)),
                      stat= c("", # la première ligne de la colonne stat est vide
                              paste0(tab_sex_temp[,1], 
                                     " (", 
                                     round(tab_sex_temp[,2] * 100, digits = 1), 
                                     "%)")))
tab_sex <- rbind(tab_sex, 
                 c("n / n missing", 
                   paste0(length(which(!is.na(df_1miss$sexL))), 
                          " / ", 
                          length(which(is.na(df_1miss$sexL))))))
tab_sex
##             var        stat
## 1          Sexe            
## 2       Féminin 137 (49.5%)
## 3      Masculin 140 (50.5%)
## 4 n / n missing    277 / 23

Comme pour les variables quantitatives, on peut créer une fonction pour automatiser la création de tables pour les variables qualitatives.

## Fonction pour automatiser la création de table pour les variables quali
tab_univ_quali <- function(data, # le data frame
                           metadata, # la base de méta-données
                           variables) { # vecteur de noms de variables quanti
  # la table associée à chaque variable sera stockée dans une liste
  tab_list <- list() # créée une liste vide
  
  for (i in variables) { # boucle pour chaque variable du vecteur variables
    N <- length(which(!is.na(data[[i]])))
    N_miss <- length(which(is.na(data[[i]])))
    
    tab_temp <- cbind(table(data[[i]], useNA = "no"), 
                          prop.table(table(data[[i]], useNA = "no"))) 
    
    
    tab_list[[i]] <- data.frame(var = c(paste0(metadata$label[metadata$var == i][1],
                                               ", n (%)"),
                                        levels(data[[i]])),
                                stat= c("", # la première ligne de la colonne stat est vide
                                        paste0(tab_temp[,1], 
                                               " (", 
                                               round(tab_temp[,2] * 100, digits = 1), 
                                               "%)")))
    if (N_miss >= 1) { # s'il y a des  manquants, on ajoute les effectifs observés 
      tab_list[[i]] <- rbind(tab_list[[i]], 
                             c("n / n missing", 
                               paste0(length(which(!is.na(data[[i]]))), 
                                      " / ", 
                                      length(which(is.na(data[[i]]))))))
    }
  }
  
  # on commence par créer une table commune vide
  tab_pooled <- data.frame(matrix("", ncol = 2, nrow = 0))
  # on empile chaque table stockée dans la liste les unes après les autres
  for(j in 1:length(tab_list)) {
    tab_pooled <- rbind(tab_pooled, tab_list[[j]])
  }
  
  return(tab_pooled)
}

tab_quali <- tab_univ_quali(data = df_1miss, # le data frame
                            metadata = meta_df_1, # la base de méta-données
                            variables = c("sexL", "traitL"))
tab_quali
##                 var        stat
## 1       Sexe, n (%)            
## 2           Féminin 137 (49.5%)
## 3          Masculin 140 (50.5%)
## 4     n / n missing    277 / 23
## 5 Traitement, n (%)            
## 6           Placebo   104 (39%)
## 7      Traitement A  84 (31.5%)
## 8      Traitement B  79 (29.6%)
## 9     n / n missing    267 / 33

7.5 Package tinytable

On va combiner la table univariée des variables quantitatives et qualitatives.

tab_desc <- rbind(tab_quanti, tab_quali)

# on va changer les noms de colonnes
# en indiquant les effectifs totaux dans la colonne des résultats statistiques
names(tab_desc) <- c("Variables", 
                     paste0("N = ", nrow(df_1miss))) 

tab_desc
##            Variables      N = 300
## 1        IMC (kg/m²)             
## 2      n / n missing     263 / 37
## 3       moyenne (DS)     24.4 (3)
## 4         PAS (mmHg)             
## 5      n / n missing     267 / 33
## 6       moyenne (DS) 137.2 (16.9)
## 7        Sexe, n (%)             
## 8            Féminin  137 (49.5%)
## 9           Masculin  140 (50.5%)
## 10     n / n missing     277 / 23
## 11 Traitement, n (%)             
## 12           Placebo    104 (39%)
## 13      Traitement A   84 (31.5%)
## 14      Traitement B   79 (29.6%)
## 15     n / n missing     267 / 33

Le package tinytable permet d’obtenir rapidement et simplement des tables mises en forme pour être copiées-collées dans un rapport d’analyse (ou une sortie Rmarkdown ou Quarto).

Le package est simple, avec peu de fonctions à connaître pour être utilisé et ne dépend pas d’autres packages R.

library(tinytable)
tt(tab_desc)
Variables N = 300
IMC (kg/m²)
n / n missing 263 / 37
moyenne (DS) 24.4 (3)
PAS (mmHg)
n / n missing 267 / 33
moyenne (DS) 137.2 (16.9)
Sexe, n (%)
Féminin 137 (49.5%)
Masculin 140 (50.5%)
n / n missing 277 / 23
Traitement, n (%)
Placebo 104 (39%)
Traitement A 84 (31.5%)
Traitement B 79 (29.6%)
n / n missing 267 / 33

On peut facilement modifier le style de certaines cellules (cf ?style_tt pour plus d’informations).

# Ci-dessous, les celulles avec les noms de variables s'affichent en gras. (bold)
# et une indentation vers la droite est ajoutée pour les noms des paramètres
tt(tab_desc) |>
  style_tt(
    i = which(tab_desc[,2] == ""), # sélectionner les lignes sans statistiques
    j = 1, # sélectionne la 1ère colonne
    bold = TRUE) |>
  style_tt(
    i = which(tab_desc[,2] != ""), # sélectionner les lignes avec statistiques
    j = 1,
    indent = 2 # ajoute une indentation vers la droite de 2 unités
  )
Variables N = 300
IMC (kg/m²)
n / n missing 263 / 37
moyenne (DS) 24.4 (3)
PAS (mmHg)
n / n missing 267 / 33
moyenne (DS) 137.2 (16.9)
Sexe, n (%)
Féminin 137 (49.5%)
Masculin 140 (50.5%)
n / n missing 277 / 23
Traitement, n (%)
Placebo 104 (39%)
Traitement A 84 (31.5%)
Traitement B 79 (29.6%)
n / n missing 267 / 33

7.6 Préparation d’un tableau bivarié avec p-values

7.6.1 Préparation d’un data.frame

Pour cette analyse, on va se restreindre à la base de 267 patients sans données manquantes sur le traitement.

table(df_1miss$traitL, useNA = "ifany")
## 
##      Placebo Traitement A Traitement B         <NA> 
##          104           84           79           33
# on voit que la variable traitement est manquante chez 33 participants :
# on exclut ces 33 participants de l'analyse bivariée

Pour croiser l’IMC et la PAS en fonction du traitement, on va créer une fonction qui récupère sous forme de vecteur :

  • les effectifs observés et manquants (n / n missing)
  • la moyenne et l’écart type (moyenne (DS)).
mean_sd_fct <- function(x, dig = 1, remove_miss = TRUE) {  
  n_n_miss <- paste0(length(which(!is.na(x))), 
                     " / ",
                     length(which(is.na(x))))
  mean_sd <- paste0(round(mean(x, na.rm = remove_miss), digits = dig), 
                    " (",
                    round(sd(x, na.rm = remove_miss), digits = dig),
                    ")")
  return(c(n_n_miss, mean_sd))
}

## exemples de résultat obtenu avec la fonction mean_sd_fct()
mean_sd_fct(x = df_1miss[!is.na(df_1miss$traitL), "imc"],  
            dig = 1, 
            remove_miss = TRUE)
## [1] "236 / 31"   "24.4 (3.1)"
tapply(X = df_1miss$imc, 
       INDEX = df_1miss$traitL,
       FUN = mean_sd_fct, # fonction à utiliser sur X
       dig = 1, remove_miss = TRUE) # arguments de la fonction     
## $Placebo
## [1] "93 / 11"    "24.4 (2.8)"
## 
## $`Traitement A`
## [1] "73 / 11"  "24.7 (3)"
## 
## $`Traitement B`
## [1] "70 / 9"     "24.2 (3.5)"

Préparation d’une table croisant le traitement avec :

  • l’IMC
  • la PAS
  • le sexe
# Les effectifs totaux seront affichés sur la première ligne
df_results <- data.frame(var = c("N total", 
                                 meta_df_1$label[meta_df_1$var == "imc"],
                                 "n / n missing", 
                                 "moyenne (DS)",
                                 meta_df_1$label[meta_df_1$var == "pas"],
                                 "n / n missing", 
                                 "moyenne (DS)",
                                 paste0(meta_df_1$label[meta_df_1$var == "sexL"][1], ", n (%)"),
                                 levels(df_1miss$sexL),
                                 "n / n missing"))
df_results <- data.frame(df_results, 
                         matrix("", ncol = length(levels(df_1miss$traitL)) + 2,
                                nrow = nrow(df_results)))
names(df_results) <- c("Variable", levels(df_1miss$traitL), "p-value", "Total")
df_results
##         Variable Placebo Traitement A Traitement B p-value Total
## 1        N total                                                
## 2    IMC (kg/m²)                                                
## 3  n / n missing                                                
## 4   moyenne (DS)                                                
## 5     PAS (mmHg)                                                
## 6  n / n missing                                                
## 7   moyenne (DS)                                                
## 8    Sexe, n (%)                                                
## 9        Féminin                                                
## 10      Masculin                                                
## 11 n / n missing

Ensuite on remplit le contenu du tableau (à noter qu’on va exclure des analyses les données manquantes concernant le traitement).

## Ligne N total 
n_obs_tot <- table(df_1miss$traitL, useNA = "no")
df_results[1,c("Placebo", 
               "Traitement A", 
               "Traitement B")] <- paste0("N = ", n_obs_tot)
df_results[1, "Total"] <- paste0("N = ", sum(n_obs_tot))
df_results
##         Variable Placebo Traitement A Traitement B p-value   Total
## 1        N total N = 104       N = 84       N = 79         N = 267
## 2    IMC (kg/m²)                                                  
## 3  n / n missing                                                  
## 4   moyenne (DS)                                                  
## 5     PAS (mmHg)                                                  
## 6  n / n missing                                                  
## 7   moyenne (DS)                                                  
## 8    Sexe, n (%)                                                  
## 9        Féminin                                                  
## 10      Masculin                                                  
## 11 n / n missing
## Lignes de l'IMC en fonction du traitement
imc_by_trait <- tapply(X = df_1miss$imc, 
                       INDEX = df_1miss$traitL,
                       FUN = mean_sd_fct, # fonction à utiliser sur X
                       dig = 1, remove_miss = TRUE)
      
df_results[3:4, c("Placebo")] <- imc_by_trait$Placebo
df_results[3:4, c("Traitement A")] <- imc_by_trait$`Traitement A`
df_results[3:4, c("Traitement B")] <- imc_by_trait$`Traitement B`
# note : pour avoir des effectifs cohérents dans la colonne total, 
# les analyses doivent être réalisées dans le sous-ensemble de données 
# sans manquants concernant le traitement +++
df_results[3:4, c("Total")] <- mean_sd_fct(x = subset(df_1miss,
                                                      subset = !is.na(traitL))$imc,  
                                           dig = 1, 
                                           remove_miss = TRUE)
anova_imc_by_traitL <- anova(aov(imc ~ traitL, data = df_1miss))
anova_imc_by_traitL$`Pr(>F)` 
## [1] 0.5259026        NA
df_results[3,"p-value"] <- paste0(round(anova_imc_by_traitL$`Pr(>F)`[1], digits = 2),
                                  "<sup>a") # code html pour afficher "a" en exposant
df_results
##         Variable    Placebo Traitement A Traitement B    p-value      Total
## 1        N total    N = 104       N = 84       N = 79               N = 267
## 2    IMC (kg/m²)                                                           
## 3  n / n missing    93 / 11      73 / 11       70 / 9 0.53<sup>a   236 / 31
## 4   moyenne (DS) 24.4 (2.8)     24.7 (3)   24.2 (3.5)            24.4 (3.1)
## 5     PAS (mmHg)                                                           
## 6  n / n missing                                                           
## 7   moyenne (DS)                                                           
## 8    Sexe, n (%)                                                           
## 9        Féminin                                                           
## 10      Masculin                                                           
## 11 n / n missing
# Lignes de la PAS en fonction du traitement
pas_by_trait <- tapply(X = df_1miss$pas, 
                       INDEX = df_1miss$traitL,
                       FUN = mean_sd_fct, # fonction à utiliser sur X
                       dig = 1, remove_miss = TRUE)
df_results[6:7, c("Placebo")] <- pas_by_trait$Placebo
df_results[6:7, c("Traitement A")] <- pas_by_trait$`Traitement A`
df_results[6:7, c("Traitement B")] <- pas_by_trait$`Traitement B`
df_results[6:7, c("Total")] <- mean_sd_fct(x = subset(df_1miss, 
                                                      subset = !is.na(traitL))$pas,  
                                           dig = 1, 
                                           remove_miss = TRUE)
anova_pas_by_traitL <- anova(aov(pas ~ traitL, data = df_1miss))
anova_pas_by_traitL$`Pr(>F)` 
## [1] 1.079829e-05           NA
df_results[6,"p-value"] <- "<0.0001<sup>a"

## On complète la table pour le sexe
# on commence par décrire les effectifs avec les manquants 
# pour calculer les effectifs par groupe de traitement
table(df_1miss$sexL, df_1miss$traitL, useNA = "ifany")
##           
##            Placebo Traitement A Traitement B <NA>
##   Féminin       40           40           37   20
##   Masculin      58           40           30   12
##   <NA>           6            4           12    1
# total des 3 premières colonnes, en comptant les manquants
tot_trait <- colSums(table(df_1miss$sexL, df_1miss$traitL, useNA = "ifany")[,1:3])
# nombre de manquants
n_miss_sex <- table(df_1miss$sexL, df_1miss$traitL, useNA = "ifany")[3,1:3]
# total des effectifs non-manquants
n_obs_sex <- colSums(table(df_1miss$sexL, df_1miss$traitL, useNA = "no"))

# Effectifs et pourcentages calculés en excluant les manquants (hypothèse MCAR)
tab_sex_n <- table(df_1miss$sexL, df_1miss$traitL, useNA = "no")
tab_sex_pct <- prop.table(table(df_1miss$sexL, df_1miss$traitL, useNA = "no"),
                          margin = 2) # pour les pourcentages en colonnes
tab_sexL_by_traitL <- paste0(tab_sex_n, 
                             " (", 
                             round(tab_sex_pct * 100, digits = 1),
                             "%)")
dim(tab_sexL_by_traitL) <- dim(tab_sex_n)
tab_sexL_by_traitL
##      [,1]         [,2]       [,3]        
## [1,] "40 (40.8%)" "40 (50%)" "37 (55.2%)"
## [2,] "58 (59.2%)" "40 (50%)" "30 (44.8%)"
# On complète la table bivariée
df_results[9:10,2:4] <- tab_sexL_by_traitL
df_results[11, 2:4] <- paste0(n_obs_sex, " / ", n_miss_sex)
# colonne Total
df_results[9:10,"Total"] <- paste0(rowSums(tab_sex_n), 
                                  " (",
                                  round(prop.table(rowSums(tab_sex_n)) * 100, 
                                        digits = 1),
                                  "%)")
df_results[11,"Total"] <- paste0(sum(n_obs_sex), " / ", sum(n_miss_sex))
# colonne p-value
chi2_sex_trait <- chisq.test(table(df_1miss$sexL, df_1miss$traitL, useNA = "no"))
df_results[9, "p-value"] <- paste0(round(chi2_sex_trait$p.value, digits = 2), 
                                   "<sup>b")
df_results
##         Variable      Placebo Traitement A Traitement B       p-value
## 1        N total      N = 104       N = 84       N = 79              
## 2    IMC (kg/m²)                                                     
## 3  n / n missing      93 / 11      73 / 11       70 / 9    0.53<sup>a
## 4   moyenne (DS)   24.4 (2.8)     24.7 (3)   24.2 (3.5)              
## 5     PAS (mmHg)                                                     
## 6  n / n missing      87 / 17       75 / 9       72 / 7 <0.0001<sup>a
## 7   moyenne (DS) 143.1 (15.8) 130.5 (16.2)   137.2 (17)              
## 8    Sexe, n (%)                                                     
## 9        Féminin   40 (40.8%)     40 (50%)   37 (55.2%)    0.17<sup>b
## 10      Masculin   58 (59.2%)     40 (50%)   30 (44.8%)              
## 11 n / n missing       98 / 6       80 / 4      67 / 12              
##           Total
## 1       N = 267
## 2              
## 3      236 / 31
## 4    24.4 (3.1)
## 5              
## 6      234 / 33
## 7  137.2 (17.1)
## 8              
## 9   117 (47.8%)
## 10  128 (52.2%)
## 11     245 / 22

7.6.2 Présentation avec tinytable

Comme pour la table d’analyse univarié, on peut utiliser le package tinytable pour présenter cette table bivariée

tt(df_results, 
   cap = "Table : Analyses bivariées", 
   notes = list(a = "Anova", b = "Chi-2")) |>
  style_tt( # affiche les noms de variables en gras
    i = which(df_results[,2] == ""),
    j = 1,
    bold = TRUE) |> 
  style_tt( # ajoute une indentation vers la droite d'une unité
    i = which(df_results[,2] != ""), 
    j = 1,
    indent = 1) |>
  group_tt( # ajoute un nom de variable pour les 3 colonnes de traitements
    j = list("Traitements" = 2:4))
Table : Analyses bivariées
Traitements
Variable Placebo Traitement A Traitement B p-value Total
a Anova
b Chi-2
N total N = 104 N = 84 N = 79 N = 267
IMC (kg/m²)
n / n missing 93 / 11 73 / 11 70 / 9 0.53a 236 / 31
moyenne (DS) 24.4 (2.8) 24.7 (3) 24.2 (3.5) 24.4 (3.1)
PAS (mmHg)
n / n missing 87 / 17 75 / 9 72 / 7 <0.0001a 234 / 33
moyenne (DS) 143.1 (15.8) 130.5 (16.2) 137.2 (17) 137.2 (17.1)
Sexe, n (%)
Féminin 40 (40.8%) 40 (50%) 37 (55.2%) 0.17b 117 (47.8%)
Masculin 58 (59.2%) 40 (50%) 30 (44.8%) 128 (52.2%)
n / n missing 98 / 6 80 / 4 67 / 12 245 / 22

Dans la table finale, pensez à vérifier qu’il n’y a pas d’erreurs : les pourcentages doivent être corrects et la somme des effectifs doit toujours donner les mêmes totaux, en lignes et en colonnes.

7.7 Package gt

Le package gt est un autre package qui permet de présenter des tableaux et de les mettre en forme avec une grande flexibilité.

## On va récupérer les data.frame univariés et bivariés crées précédemment 
## et les mettre en forme avec le package gt
tab_desc
##            Variables      N = 300
## 1        IMC (kg/m²)             
## 2      n / n missing     263 / 37
## 3       moyenne (DS)     24.4 (3)
## 4         PAS (mmHg)             
## 5      n / n missing     267 / 33
## 6       moyenne (DS) 137.2 (16.9)
## 7        Sexe, n (%)             
## 8            Féminin  137 (49.5%)
## 9           Masculin  140 (50.5%)
## 10     n / n missing     277 / 23
## 11 Traitement, n (%)             
## 12           Placebo    104 (39%)
## 13      Traitement A   84 (31.5%)
## 14      Traitement B   79 (29.6%)
## 15     n / n missing     267 / 33
df_results
##         Variable      Placebo Traitement A Traitement B       p-value
## 1        N total      N = 104       N = 84       N = 79              
## 2    IMC (kg/m²)                                                     
## 3  n / n missing      93 / 11      73 / 11       70 / 9    0.53<sup>a
## 4   moyenne (DS)   24.4 (2.8)     24.7 (3)   24.2 (3.5)              
## 5     PAS (mmHg)                                                     
## 6  n / n missing      87 / 17       75 / 9       72 / 7 <0.0001<sup>a
## 7   moyenne (DS) 143.1 (15.8) 130.5 (16.2)   137.2 (17)              
## 8    Sexe, n (%)                                                     
## 9        Féminin   40 (40.8%)     40 (50%)   37 (55.2%)    0.17<sup>b
## 10      Masculin   58 (59.2%)     40 (50%)   30 (44.8%)              
## 11 n / n missing       98 / 6       80 / 4      67 / 12              
##           Total
## 1       N = 267
## 2              
## 3      236 / 31
## 4    24.4 (3.1)
## 5              
## 6      234 / 33
## 7  137.2 (17.1)
## 8              
## 9   117 (47.8%)
## 10  128 (52.2%)
## 11     245 / 22
## Installer la library gt, puis la charger
library(gt)

Le principe du package gt est d’afficher le corps de la table (body table), puis d’y ajouter les différents éléments (la colonne stub à gauche indiquant les noms des rangs, les titres de colonnes, les titres et sous-titres de la table, les notes de bas de tableau, etc). Vous trouverez plus d’information sur la vignette d’introduction du package et la vignette qui donne des exemples pour des tables d’analyses de données cliniques.

7.7.1 Table univariée

On va présenter les données du data frame tab_desc.

On commence par récupérer le “corps” du tableau (table body). Il faut que le “corps” soit au format data.frame.

## Le "corps" correspond aux données de la 2ème colonne de "tab_desc"
## (sans garder les lignes vides)
tab_desc[which(tab_desc[,2] != ""), 2] # c'est un vecteur caractère
##  [1] "263 / 37"     "24.4 (3)"     "267 / 33"     "137.2 (16.9)" "137 (49.5%)" 
##  [6] "140 (50.5%)"  "277 / 23"     "104 (39%)"    "84 (31.5%)"   "79 (29.6%)"  
## [11] "267 / 33"
# On stocke ce vecteur dans la variable "stat" au sein d'un data.frame
corps_df <- data.frame(stat = tab_desc[which(tab_desc[,2] != ""), 2])

## On peut alors appliquer la fonction gt() à ce data.frame
corps <- gt(corps_df)
corps
stat
263 / 37
24.4 (3)
267 / 33
137.2 (16.9)
137 (49.5%)
140 (50.5%)
277 / 23
104 (39%)
84 (31.5%)
79 (29.6%)
267 / 33

On peut ensuite ajouter différents éléments à ce corps de table :

  • titre et sous-titre avec tab_header,
  • notes de bas de tableaux avec tab_footnote,
  • sources avec tab_source_note
## ajouter un titre et un sous-titre avec la fonction tab_header()
corps |>
  tab_header(title = "Titre", 
             subtitle = "Sous-titre")
Titre
Sous-titre
stat
263 / 37
24.4 (3)
267 / 33
137.2 (16.9)
137 (49.5%)
140 (50.5%)
277 / 23
104 (39%)
84 (31.5%)
79 (29.6%)
267 / 33
## ajouter des sources avec la fonction tab_source_note()
corps |>
  tab_header(title = "Titre", 
             subtitle = "Sous-titre") |>
  tab_source_note(
    source_note = "sources que l'on souhaite citer"
  )
Titre
Sous-titre
stat
263 / 37
24.4 (3)
267 / 33
137.2 (16.9)
137 (49.5%)
140 (50.5%)
277 / 23
104 (39%)
84 (31.5%)
79 (29.6%)
267 / 33
sources que l'on souhaite citer
## ajouter une note de bas de tableau avec la fonction tab_footnote()
# à noter qu'elle se positionne automatiquement avant les sources
corps |>
  tab_header(title = "Titre", 
             subtitle = "Sous-titre") |>
  tab_source_note(
    source_note = "sources que l'on souhaite citer"
  ) |>
  tab_footnote(
    footnote = "Note de bas de tableau"
  )
Titre
Sous-titre
stat
263 / 37
24.4 (3)
267 / 33
137.2 (16.9)
137 (49.5%)
140 (50.5%)
277 / 23
104 (39%)
84 (31.5%)
79 (29.6%)
267 / 33
Note de bas de tableau
sources que l'on souhaite citer

On peut ensuite ajouter le stub : la partie à gauche du tableau qui indique les noms des rangs. A noter que l’argument rowname_col peut être utilisé pour définir le stub.

## On va se servir du contenu de la table tab_desc 
## (en supprimant les lignes indiquant les noms de variables)
corps_df <- tab_desc[which(tab_desc$`N = 300` != ""),]
corps_df
##        Variables      N = 300
## 2  n / n missing     263 / 37
## 3   moyenne (DS)     24.4 (3)
## 5  n / n missing     267 / 33
## 6   moyenne (DS) 137.2 (16.9)
## 8        Féminin  137 (49.5%)
## 9       Masculin  140 (50.5%)
## 10 n / n missing     277 / 23
## 12       Placebo    104 (39%)
## 13  Traitement A   84 (31.5%)
## 14  Traitement B   79 (29.6%)
## 15 n / n missing     267 / 33
gt(corps_df, 
   rowname_col = "Variables") # l'argument rowname_col définit le "stub"
N = 300
n / n missing 263 / 37
moyenne (DS) 24.4 (3)
n / n missing 267 / 33
moyenne (DS) 137.2 (16.9)
Féminin 137 (49.5%)
Masculin 140 (50.5%)
n / n missing 277 / 23
Placebo 104 (39%)
Traitement A 84 (31.5%)
Traitement B 79 (29.6%)
n / n missing 267 / 33
## On peut donner un nom de colonne au "stub" avec la fonction tab_stubhead()
gt(corps_df, 
   rowname_col = "Variables") |>
  tab_stubhead("Variables")
Variables N = 300
n / n missing 263 / 37
moyenne (DS) 24.4 (3)
n / n missing 267 / 33
moyenne (DS) 137.2 (16.9)
Féminin 137 (49.5%)
Masculin 140 (50.5%)
n / n missing 277 / 23
Placebo 104 (39%)
Traitement A 84 (31.5%)
Traitement B 79 (29.6%)
n / n missing 267 / 33
## On peut finalement regrouper les lignes par variable en ajoutant un nom
## de variable avec la fonction tab_row_group()
gt(corps_df, 
   rowname_col = "Variables") |>
  tab_stubhead("Variables") |>
  tab_row_group(
    label = meta_df_1$label[meta_df_1$var == "imc"], 
    rows = 1:2 # indiquer les rangs correspondant à cette variable
  ) |>
  tab_row_group(
    label = meta_df_1$label[meta_df_1$var == "pas"], 
    rows = 3:4 # indiquer les rangs correspondant à cette variable
  ) |>
  tab_row_group(
    label = meta_df_1$label[meta_df_1$var == "sexL"][1], 
    rows = 5:7 # indiquer les rangs correspondant à cette variable
  ) |>
  tab_row_group(
    label = meta_df_1$label[meta_df_1$var == "traitL"][1], 
    rows = 8:11 # indiquer les rangs correspondant à cette variable
  ) 
Variables N = 300
Traitement
Placebo 104 (39%)
Traitement A 84 (31.5%)
Traitement B 79 (29.6%)
n / n missing 267 / 33
Sexe
Féminin 137 (49.5%)
Masculin 140 (50.5%)
n / n missing 277 / 23
PAS (mmHg)
n / n missing 267 / 33
moyenne (DS) 137.2 (16.9)
IMC (kg/m²)
n / n missing 263 / 37
moyenne (DS) 24.4 (3)
## Une autre façon de faire est d'ajouter une colonne avec les labels de variables
## dans le data frame utilisé pour le corps de la table
corps_df <- tab_desc[which(tab_desc$`N = 300` != ""),]
corps_df$label <- c(rep(meta_df_1$label[meta_df_1$var == "imc"], 2), 
                    rep(meta_df_1$label[meta_df_1$var == "pas"], 2), 
                    rep(meta_df_1$label[meta_df_1$var == "sexL"][1], 3),
                    rep(meta_df_1$label[meta_df_1$var == "traitL"][1], 4))
corps_df
##        Variables      N = 300       label
## 2  n / n missing     263 / 37 IMC (kg/m²)
## 3   moyenne (DS)     24.4 (3) IMC (kg/m²)
## 5  n / n missing     267 / 33  PAS (mmHg)
## 6   moyenne (DS) 137.2 (16.9)  PAS (mmHg)
## 8        Féminin  137 (49.5%)        Sexe
## 9       Masculin  140 (50.5%)        Sexe
## 10 n / n missing     277 / 23        Sexe
## 12       Placebo    104 (39%)  Traitement
## 13  Traitement A   84 (31.5%)  Traitement
## 14  Traitement B   79 (29.6%)  Traitement
## 15 n / n missing     267 / 33  Traitement
## La colonne label peut alors être déclarée dans l'argument "groupname_col"
gt(corps_df, 
   rowname_col = "Variables", 
   groupname_col = "label") 
N = 300
IMC (kg/m²)
n / n missing 263 / 37
moyenne (DS) 24.4 (3)
PAS (mmHg)
n / n missing 267 / 33
moyenne (DS) 137.2 (16.9)
Sexe
Féminin 137 (49.5%)
Masculin 140 (50.5%)
n / n missing 277 / 23
Traitement
Placebo 104 (39%)
Traitement A 84 (31.5%)
Traitement B 79 (29.6%)
n / n missing 267 / 33
## Table univariée complète :
# la fonction tab_stub_indent() permet d'ajouter une indentation pour le stub 
# en gardant les noms de variable alignés à gauche
gt(corps_df, 
   rowname_col = "Variables", 
   groupname_col = "label") |>
  tab_stub_indent(rows = everything(), # indentation pour tous les rangs du stub 
                  indent = 3) |>       # de 3 vers la droite
  tab_stubhead("Variables") |> 
  tab_header(title = "Titre", 
             subtitle = "Sous-titre") |>
  tab_source_note(
    source_note = "sources que l'on souhaite citer"
  ) |>
  tab_footnote(
    footnote = "Note de bas de tableau"
  )
Titre
Sous-titre
Variables N = 300
IMC (kg/m²)
n / n missing 263 / 37
moyenne (DS) 24.4 (3)
PAS (mmHg)
n / n missing 267 / 33
moyenne (DS) 137.2 (16.9)
Sexe
Féminin 137 (49.5%)
Masculin 140 (50.5%)
n / n missing 277 / 23
Traitement
Placebo 104 (39%)
Traitement A 84 (31.5%)
Traitement B 79 (29.6%)
n / n missing 267 / 33
Note de bas de tableau
sources que l'on souhaite citer

7.7.2 Table bivariée

On va utiliser le data frame df_results.

corps_biv <- df_results
corps_biv
##         Variable      Placebo Traitement A Traitement B       p-value
## 1        N total      N = 104       N = 84       N = 79              
## 2    IMC (kg/m²)                                                     
## 3  n / n missing      93 / 11      73 / 11       70 / 9    0.53<sup>a
## 4   moyenne (DS)   24.4 (2.8)     24.7 (3)   24.2 (3.5)              
## 5     PAS (mmHg)                                                     
## 6  n / n missing      87 / 17       75 / 9       72 / 7 <0.0001<sup>a
## 7   moyenne (DS) 143.1 (15.8) 130.5 (16.2)   137.2 (17)              
## 8    Sexe, n (%)                                                     
## 9        Féminin   40 (40.8%)     40 (50%)   37 (55.2%)    0.17<sup>b
## 10      Masculin   58 (59.2%)     40 (50%)   30 (44.8%)              
## 11 n / n missing       98 / 6       80 / 4      67 / 12              
##           Total
## 1       N = 267
## 2              
## 3      236 / 31
## 4    24.4 (3.1)
## 5              
## 6      234 / 33
## 7  137.2 (17.1)
## 8              
## 9   117 (47.8%)
## 10  128 (52.2%)
## 11     245 / 22
# On commence par supprimer les indicateurs <sup>a et <sup>b des p-values
# c'est à dire qu'on supprime les 6 derniers caractères
corps_biv$`p-value` <- substr(corps_biv$`p-value`, 1, nchar(corps_biv$`p-value`) - 6)

# On supprime les lignes avec les noms de variables
corps_biv <- corps_biv[which(!corps_biv$Placebo == ""), ]

# On ajoute une colonne "label" au data frame pour indiquer les noms
# de variables
corps_biv$label <- c("", 
                     rep(meta_df_1$label[meta_df_1$var == "imc"], 2), 
                     rep(meta_df_1$label[meta_df_1$var == "pas"], 2), 
                     rep(meta_df_1$label[meta_df_1$var == "sexL"][1], 3))

corps_biv
##         Variable      Placebo Traitement A Traitement B p-value        Total
## 1        N total      N = 104       N = 84       N = 79              N = 267
## 3  n / n missing      93 / 11      73 / 11       70 / 9    0.53     236 / 31
## 4   moyenne (DS)   24.4 (2.8)     24.7 (3)   24.2 (3.5)           24.4 (3.1)
## 6  n / n missing      87 / 17       75 / 9       72 / 7 <0.0001     234 / 33
## 7   moyenne (DS) 143.1 (15.8) 130.5 (16.2)   137.2 (17)         137.2 (17.1)
## 9        Féminin   40 (40.8%)     40 (50%)   37 (55.2%)    0.17  117 (47.8%)
## 10      Masculin   58 (59.2%)     40 (50%)   30 (44.8%)          128 (52.2%)
## 11 n / n missing       98 / 6       80 / 4      67 / 12             245 / 22
##          label
## 1             
## 3  IMC (kg/m²)
## 4  IMC (kg/m²)
## 6   PAS (mmHg)
## 7   PAS (mmHg)
## 9         Sexe
## 10        Sexe
## 11        Sexe
gt(corps_biv, 
   rowname_col = "Variable", # colonne avec les noms de rangs
   groupname_col = "label")  # colnne avec les noms de variables
Placebo Traitement A Traitement B p-value Total
N total N = 104 N = 84 N = 79 N = 267
IMC (kg/m²)
n / n missing 93 / 11 73 / 11 70 / 9 0.53 236 / 31
moyenne (DS) 24.4 (2.8) 24.7 (3) 24.2 (3.5) 24.4 (3.1)
PAS (mmHg)
n / n missing 87 / 17 75 / 9 72 / 7 <0.0001 234 / 33
moyenne (DS) 143.1 (15.8) 130.5 (16.2) 137.2 (17) 137.2 (17.1)
Sexe
Féminin 40 (40.8%) 40 (50%) 37 (55.2%) 0.17 117 (47.8%)
Masculin 58 (59.2%) 40 (50%) 30 (44.8%) 128 (52.2%)
n / n missing 98 / 6 80 / 4 67 / 12 245 / 22

On peut regrouper plusieurs colonnes avec la fonction tab_spanner() (dans cet exemple, on va regrouper les groupes “Placebo”, “Traitement A” et “Traitement B” sous le nom “Traitement”).

## On peut ajouter un groupe de colonnes avec la fonction tab_spanner()
gt(corps_biv, 
   rowname_col = "Variable", 
   groupname_col = "label") |>
  tab_spanner(
    label = "Traitement", 
    columns = c("Placebo","Traitement A","Traitement B")
  )
Traitement
p-value Total
Placebo Traitement A Traitement B
N total N = 104 N = 84 N = 79 N = 267
IMC (kg/m²)
n / n missing 93 / 11 73 / 11 70 / 9 0.53 236 / 31
moyenne (DS) 24.4 (2.8) 24.7 (3) 24.2 (3.5) 24.4 (3.1)
PAS (mmHg)
n / n missing 87 / 17 75 / 9 72 / 7 <0.0001 234 / 33
moyenne (DS) 143.1 (15.8) 130.5 (16.2) 137.2 (17) 137.2 (17.1)
Sexe
Féminin 40 (40.8%) 40 (50%) 37 (55.2%) 0.17 117 (47.8%)
Masculin 58 (59.2%) 40 (50%) 30 (44.8%) 128 (52.2%)
n / n missing 98 / 6 80 / 4 67 / 12 245 / 22
## Les notes de bas de tableau peuvent être utilisées pour indiquer le type
## de test statistique utilisé associé aux différentes p-values
# Notes : on peut appliquer les formats markdown avec du texte dans la fonction md()
# (exemple dans le sous-titre)
# ainsi que des notations mathématiques (exemple dans la source)
gt(corps_biv, 
   rowname_col = "Variable", 
   groupname_col = "label") |>
  tab_spanner(                          # colonne de groupe de traitements
    label = "Traitement", 
    columns = c("Placebo","Traitement A","Traitement B")
  ) |>
  tab_stub_indent(rows = everything(), # indentation de 3 vers la droite
                  indent = 3) |>       
  tab_footnote(                         # Anova en note de bas de table
    footnote = "Anova",
    locations = cells_body(columns = "p-value", rows = c(2,4))
  ) |>
  tab_footnote(                         # chi2 en note de bas de table
    footnote = "Chi-2",
    locations = cells_body(columns = "p-value", rows = 6)
  ) |> 
  tab_header(title = "Titre",           # ajouter un titre et un sous-titre
             subtitle = md("Texte en **gras** ou en *italique*")) |>
  tab_source_note(
    source_note = md("$$\\mathbb{E}(Y) = \\beta_0 + \\theta_1 X$$")
  )
Titre
Texte en gras ou en italique
Traitement
p-value Total
Placebo Traitement A Traitement B
N total N = 104 N = 84 N = 79 N = 267
IMC (kg/m²)
n / n missing 93 / 11 73 / 11 70 / 9 0.531 236 / 31
moyenne (DS) 24.4 (2.8) 24.7 (3) 24.2 (3.5) 24.4 (3.1)
PAS (mmHg)
n / n missing 87 / 17 75 / 9 72 / 7 <0.00011 234 / 33
moyenne (DS) 143.1 (15.8) 130.5 (16.2) 137.2 (17) 137.2 (17.1)
Sexe
Féminin 40 (40.8%) 40 (50%) 37 (55.2%) 0.172 117 (47.8%)
Masculin 58 (59.2%) 40 (50%) 30 (44.8%) 128 (52.2%)
n / n missing 98 / 6 80 / 4 67 / 12 245 / 22
1 Anova
2 Chi-2
E(Y)=β0+θ1X\mathbb{E}(Y) = \beta_0 + \theta_1 X