Créer une table 1 avec R

C'est quoi une table 1 ?

Lorsqu’on rédige un rapport d’analyse statistique, ou une publication, pour communiquer les résultats d’une étude réalisée dans le domaine de la santé, on commence généralement par décrire dans un tableau les groupes de patients étudiés, en termes de variables démographiques (age, sexe, etc), et liées à la pathologie (paramètres sanguins, etc ) à baseline (avant la prise du ou des traitements). Cette table contient aussi généralement (mais pas toujours) des informations sur le test statistique employé pour comparer les deux groupes, et la p-value obtenue.

C’est ce tableau descriptif que l’on appelle la table 1.

Par exemple, dans un essai randomisé contrôlé, qui vise à comparer un groupe de patients recevant le traitement évalué, et un groupe de patients recevant un placebo, la table 1 va permettre de vérifier que la randomisation a bien fonctionné, et que les groupes sont donc comparables.

Voici un exemple d’une table 1 d’un essai de phase III :

Table 1

Cette table 1 est issue de la publication :Fenaux, P., Platzbecker, U., Mufti, G. J., Garcia-Manero, G., Buckstein, R., Santini, V., … & Sekeres, M. A. (2020).. New England Journal of Medicine, 382(2), 140-151. Elle est consultable en cliquant ici.

On trouve également des tables 1 dans les publications relatives à des travaux de construction de scores prédictifs. Dans cette situation, la table 1 permet de décrire les liaisons uni-variées entre les variables d’intérêt et l’événement étudié. Les variables possédant une liaison significatives sont ensuite généralement incluses dans un modèle de régression multivariée.

En voici un exemple :

Cette table est issue de la publication Al-Hasan, M. N., Lahr, B. D., Eckel-Passow, J. E., & Baddour, L. M. (2013). Predictive scoring model of mortality in Gram-negative bloodstream infection. Clinical Microbiology and Infection, 19(10), 948-954. Elle est consultable ici.

Faire une table 1 avec R

Comment souvent avec le logiciel R, il existe plusieurs solutions pour réaliser une table 1. Ces diverses solutions passent notamment par les packages Table1, table1, ou encore tableone, et sans doute d’autres encore.

A ce jour, je me suis uniquement intéressée au package Table1, c’est donc celui que je vais utiliser ici.

Les data

Pour illustrer cette création de table 1, je vais employer le jeu de données heart_disease (que je vais renommer HD) ; il est inclus dans le package funModelling. Ce jeu de données concerne un essai clinique dans le domaine de la cardiologie.

library(funModeling)
HD <- heart_disease
str(HD)
## 'data.frame':    303 obs. of  16 variables:
##  $ age                   : int  63 67 67 37 41 56 62 57 63 53 ...
##  $ gender                : Factor w/ 2 levels "female","male": 2 2 2 2 1 2 1 1 2 2 ...
##  $ chest_pain            : Factor w/ 4 levels "1","2","3","4": 1 4 4 3 2 2 4 4 4 4 ...
##  $ resting_blood_pressure: int  145 160 120 130 130 120 140 120 130 140 ...
##  $ serum_cholestoral     : int  233 286 229 250 204 236 268 354 254 203 ...
##  $ fasting_blood_sugar   : Factor w/ 2 levels "0","1": 2 1 1 1 1 1 1 1 1 2 ...
##  $ resting_electro       : Factor w/ 3 levels "0","1","2": 3 3 3 1 3 1 3 1 3 3 ...
##  $ max_heart_rate        : int  150 108 129 187 172 178 160 163 147 155 ...
##  $ exer_angina           : int  0 1 1 0 0 0 0 1 0 1 ...
##  $ oldpeak               : num  2.3 1.5 2.6 3.5 1.4 0.8 3.6 0.6 1.4 3.1 ...
##  $ slope                 : int  3 2 2 3 1 1 3 1 2 3 ...
##  $ num_vessels_flour     : int  0 3 2 0 0 0 2 0 1 0 ...
##  $ thal                  : Factor w/ 3 levels "3","6","7": 2 1 3 1 1 1 1 1 3 3 ...
##  $ heart_disease_severity: int  0 2 1 0 0 0 3 0 2 1 ...
##  $ exter_angina          : Factor w/ 2 levels "0","1": 1 2 2 1 1 1 1 2 1 2 ...
##  $ has_heart_disease     : Factor w/ 2 levels "no","yes": 1 2 2 1 1 1 2 1 2 2 ... 

La variable spécifiant les deux groupes de patients pour lesquels je souhaite décrire les variables à baseline est la variable has_heart_disease qui a deux niveaux noet yes.

Les variables démographiques et liées à l apathologie, que nous allons décrire sont :

  • gender (male / female)
  • age
  • chest_pain (4 niveaux, 1 à 4)
  • serum_cholestoral
  • max_heart_rate

Le package Table 1

A ce jour, le package Table1 n’est pas disponible sur CRAN. Pour le télécharger, nous devons le récupérer à partir du GitHub d’Erica Wozniak, son développeur. Pour cela, il est nécessaire d’employer les commandes suivantes :

# installation de devtools depuis CRAN
install.packages("devtools"
library(devtools)

# installation de Table1 depuis github
install_github("emwozniak/Table1")
library(Table1) 

Remarque : sous Mac, il est, semble-t-il, nécessaire de charger le package remotes pour insatller un package depuis github.

La fonction make.table et ses principaux arguments

La principale fonction de ce package est la fonction make.table().

Cette fonction comporte de nombreux argumentsLes principaux sont :

  •  `strat` : qui permet d’indiquer la variable qui identifie les groupes de patients, ici `has_heart_disease`.
  • `cat.varlist` : qui permet de lister les variables démographiques catégorielles que l’on souhaite inclure dans la Table.
  • `cont.varlist` : qui permet de lister les variables numériques continues.

Par exemple :

make.table(dat=HD,
strat="has_heart_disease",
cat.varlist=c("gender","chest_pain"),
cont.varlist=c("age","serum_cholestoral", "max_heart_rate"))
##                 Variable                   no                   yes
##  age                                                               
##     Count                                 164                   139
##     Mean (SD)                    52.59 (9.51)          56.63 (7.94)
##     Median (IQR)                52.00 (14.25)         58.00 (10.00)
##     Q1, Q3                       44.75, 59.00          52.00, 62.00
##     Min, Max                     29.00, 76.00          35.00, 77.00
##     Missing                                 0                     0
##                                                                    
##  gender                                                            
##    Count (%)                     164 (54.13%)          139 (45.87%)
##    (Row %)(Col %)                                                  
##    female                72 (74.23%) (43.90%)  25 (25.77%) (17.99%)
##    male                  92 (44.66%) (56.10%) 114 (55.34%) (82.01%)
##    Missing                                  0                     0
##                                                                    
##  chest_pain                                                        
##    Count (%)                     164 (54.13%)          139 (45.87%)
##    (Row %)(Col %)                                                  
##    1                     16 (69.57%) ( 9.76%)   7 (30.43%) ( 5.04%)
##    2                     41 (82.00%) (25.00%)   9 (18.00%) ( 6.47%)
##    3                     68 (79.07%) (41.46%)  18 (20.93%) (12.95%)
##    4                     39 (27.08%) (23.78%) 105 (72.92%) (75.54%)
##    Missing                                  0                     0
##                                                                    
##  serum_cholestoral                                                 
##     Count                                 164                   139
##     Mean (SD)                  242.64 (53.46)        251.47 (49.49)
##     Median (IQR)               234.50 (58.50)        249.00 (66.00)
##     Q1, Q3                     208.75, 267.25        217.50, 283.50
##     Min, Max                   126.00, 564.00        131.00, 409.00
##     Missing                                 0                     0
##                                                                    
##  max_heart_rate                                                    
##     Count                                 164                   139
##     Mean (SD)                  158.38 (19.20)        139.26 (22.59)
##     Median (IQR)               161.00 (23.25)        142.00 (31.50)
##     Q1, Q3                     148.75, 172.00        125.00, 156.50
##     Min, Max                    96.00, 202.00         71.00, 195.00
##     Missing                                 0                     0
##                                                                    
##                 Overall
##                        
##                     303
##            54.44 (9.04)
##           56.00 (13.00)
##            48.00, 61.00
##            29.00, 77.00
##                       0
##                        
##                        
##                     303
##                        
##   97 (100.00%) (32.01%)
##  206 (100.00%) (67.99%)
##                       0
##                        
##                        
##                     303
##                        
##   23 (100.00%) ( 7.59%)
##   50 (100.00%) (16.50%)
##   86 (100.00%) (28.38%)
##  144 (100.00%) (47.52%)
##                       0
##                        
##                        
##                     303
##          246.69 (51.78)
##          241.00 (64.00)
##          211.00, 275.00
##          126.00, 564.00
##                       0
##                        
##                        
##                     303
##          149.61 (22.88)
##          153.00 (32.50)
##          133.50, 166.00
##           71.00, 202.00
##                       0
## 

Ajouter les tests statistiques

Des arguments supplémentaires permettent de réaliser les tests statistiques de comparaison et d’indiquer leur p-value dans la table 1. Il s’agit de :

  •  `cat.ptype` qui permet de préciser les tests qui doivent être employés pour les variables catégorielles.
  • `cont.ptype` qui permet de faire de même avec les variables numériques continues.

Une liste des tests utilisables est fournie dans l’aide de la fonction stat.col

?stat.col
ajouter un test statistique sur une table 1

Ainsi, par exemple, si je souhaite faire :

  • un test du Chi2 pour la variable chest_pain.
  • un test exact de Fisher pour la variable gender.
  • un test t de Student pour la variable age.
  • un test de Wilcoxon pour les variables serum_cholestoral, max_heart_rate.

je vais utiliser les commandes suivantes :

make.table(dat=HD,
           strat="has_heart_disease",
           colnames=c("", "Without heart disease","With heart disease", "Overall", "Test Statistic"),
           
           cat.varlist=c("gender","chest_pain"),
           cat.header=c("Gender", "Chest pain"),
           cat.rownames = list(c("Female", "Male"),c("I", "II", "III", "IV")) ,
           cat.ptype    = c("fisher", "chisq"),   

           cont.varlist=c("age","serum_cholestoral", "max_heart_rate"),
           cont.header=c("Age","Serum cholestoral (mg/dl)", "Max heart rate (bpm)"),
           cont.ptype   = c("ttest", "wilcox","wilcox")

##                                  Without heart disease    With heart disease
##  Age                                                                        
##     Count                                          164                   139
##     Mean (SD)                             52.59 (9.51)          56.63 (7.94)
##     Median (IQR)                         52.00 (14.25)         58.00 (10.00)
##     Q1, Q3                                44.75, 59.00          52.00, 62.00
##     Min, Max                              29.00, 76.00          35.00, 77.00
##     Missing                                          0                     0
##                                                                             
##  Gender                                                                     
##    Count (%)                              164 (54.13%)          139 (45.87%)
##    (Row %)(Col %)                                                           
##    Female                         72 (74.23%) (43.90%)  25 (25.77%) (17.99%)
##    Male                           92 (44.66%) (56.10%) 114 (55.34%) (82.01%)
##    Missing                                           0                     0
##                                                                             
##  Chest pain                                                                 
##    Count (%)                              164 (54.13%)          139 (45.87%)
##    (Row %)(Col %)                                                           
##    I                              16 (69.57%) ( 9.76%)   7 (30.43%) ( 5.04%)
##    II                             41 (82.00%) (25.00%)   9 (18.00%) ( 6.47%)
##    III                            68 (79.07%) (41.46%)  18 (20.93%) (12.95%)
##    IV                             39 (27.08%) (23.78%) 105 (72.92%) (75.54%)
##    Missing                                           0                     0
##                                                                             
##  Serum cholestoral (mg/dl)                                                  
##     Count                                          164                   139
##     Mean (SD)                           242.64 (53.46)        251.47 (49.49)
##     Median (IQR)                        234.50 (58.50)        249.00 (66.00)
##     Q1, Q3                              208.75, 267.25        217.50, 283.50
##     Min, Max                            126.00, 564.00        131.00, 409.00
##     Missing                                          0                     0
##                                                                             
##  Max heart rate (bpm)                                                       
##     Count                                          164                   139
##     Mean (SD)                           158.38 (19.20)        139.26 (22.59)
##     Median (IQR)                        161.00 (23.25)        142.00 (31.50)
##     Q1, Q3                              148.75, 172.00        125.00, 156.50
##     Min, Max                             96.00, 202.00         71.00, 195.00
##     Missing                                          0                     0
##                                                                             
##                 Overall    Test Statistic
##                                    <0.001
##                     303            t-test
##            54.44 (9.04)                  
##           56.00 (13.00)                  
##            48.00, 61.00                  
##            29.00, 77.00                  
##                       0                  
##                                          
##                                    <0.001
##                     303      Fisher exact
##                                          
##   97 (100.00%) (32.01%)                  
##  206 (100.00%) (67.99%)                  
##                       0                  
##                                          
##                                    <0.001
##                     303        Chi-square
##                                          
##   23 (100.00%) ( 7.59%)                  
##   50 (100.00%) (16.50%)                  
##   86 (100.00%) (28.38%)                  
##  144 (100.00%) (47.52%)                  
##                       0                  
##                                          
##                                     0.035
##                     303 Wilcoxon rank-sum
##          246.69 (51.78)                  
##          241.00 (64.00)                  
##          211.00, 275.00                  
##          126.00, 564.00                  
##                       0                  
##                                          
##                                    <0.001
##                     303 Wilcoxon rank-sum
##          149.61 (22.88)                  
##          153.00 (32.50)                  
##          133.50, 166.00                  
##           71.00, 202.00                  
##                       0                  
## 

Exporter la table

La table 1 créée peut facilement être exportée en format.csv.

Pour cela, il est nécessaire de :

  1. stocker la table dans un objet
  2. supprimer le nom des lignes de cet objet table, car sinon ils apparaîtront en trop
  3. exporter la table vers un fichier csv
# creation d'un dossier "resultats" à la racine du projet R
dir.create("resultats")

# stockage de la table dans l'objet mytable1
mytable1 <- make.table(dat=HD,
  strat="has_heart_disease",
  cat.varlist=c("gender","chest_pain"),
  cont.varlist=c("age","serum_cholestoral", "max_heart_rate"))

# supression du nom de lignes
rownames(mytable1 )<-NULL

# exportation
write.csv2(mytable1, here::here("resultats", "ma_table1.csv"), row.names=FALSE) 

Une autre solution consiste à :

  1. utiliser un script en R markdown,
  2. stocker la table dans un objet
  3. supprimer le nom des lignes de cet objet table, car sinon ils apparaîtront en trop
  4. générer avec la fonction kable()
  5. kniter le document en format word pour récupérer la table
# stockage de la table dans l'objet mytable1
mytable1 <- make.table(dat=HD,
           strat="has_heart_disease",
           colnames=c("", "Without heart disease","With heart disease", "Overall", "Test Statistic"),
           
           cat.varlist=c("gender","chest_pain"),
           cat.header=c("Gender", "Chest pain"),
           cat.rownames = list(c("Female", "Male"),c("I", "II", "III", "IV")) ,
           cat.ptype    = c("chisq", "chisq"),   
   
           cont.varlist=c("age","serum_cholestoral", "max_heart_rate"),
           cont.header=c("Age","Serum cholestoral (mg/dl)", "Max heart rate (bpm)"),
                   cont.ptype   = c("wilcox", "wilcox","wilcox"))

# supression du nom de lignes
row.names(mytable1) <- NULL 

# generation de la table
library(knitr)
kable(mytable1) 

Voici un aperçu du rendu word obtenu :

Pour aller plus loin

Pour aller plus loin, vous pouvez consulter un tutoriel de Table1 rédigé par Erica Wozniak, en cliquant ici.

Si jamais ce package Table1 ne vous donne pas entière satisfaction, vous pouvez également consulter la vignette du package table1 (sans la majuscule), en cliquant ici.

Je vous recommande aussi cette discussion sur l’intérêt (ou pas) d’afficher les p-values dans la table 1 relative à un essai randomisé.

Allez zou…maintenant que vous avez le mode d’emploi, vous n’avez plus d’excuses pour ne pas produire de belles Tables 1 !

Si cet article vous a plu, ou vous a été utile, et si vous le souhaitez, vous pouvez soutenir ce blog en faisant un don sur sa page Tipeee 🙏

Poursuivez votre lecture

25 réponses

  1. bonjour,
    merci pour ce sujet original mais très utilisé (dans les articles médicaux en particulier).
    Une petite remarque à propos des data utilisées: il y a une coquille dans serum_cholestoral au lieu de serum_cholesterol. Pour pouvoir réaliser l’exemple présenté, il faut faire une faute d’orthographe ! Heureusement il y a colnames. Je renvoie les personnes intéressées aux relations cholestérol – infarctus…
    Cela dit ce post est excellent (comme souvent)

    JcB

  2. Merci Claire, pour cet article très utile qui m’a permis de faire un beau tableau que je vais mettre dans l’article que je suis en train d’écrire!

  3. Je suis très ravi Claire Della Vedova de vous relire. Je me demandais à quoi a été du ce silence.

    Je vous félicite et vous encourage beaucoup pour cette dévotion de nous informer et nous former.

    Don Folly Fofolo

  4. Bonjour,
    votre artcile est excellent et vraiment très utile; j’aimerais tant pouvoir à utiliser ce package mais j’ai des difficultés à installer Table1 depuis git_hub:
    install_github(“emwozniak/Table1”)

    message affiché :
    Installing 1 packages: rlang
    trying URL ‘https://cran.rstudio.com/bin/windows/contrib/4.0/rlang_0.4.7.zip’
    Content type ‘application/zip’ length 1137690 bytes (1.1 MB)
    downloaded 1.1 MB

    Que faire après téléchargement du dossier zip.?

    merci par avance

        1. Bonjour Claire,
          J’ai des difficultés avec kable(myTable1)
          Le tableau d’affiche bien dans le fichier Rmd et dans la console mais impossible de l’afficher dans word.
          Message d’erreur indiquant que l’objet (myTable1) n’existe pas.
          J’ai essayé avec pander et j’ai le même problème.
          Peut-être avez vous une solution??
          merci par avance,
          et encore mille fois merci pour vos articles que j’apprécie énormément et qui m’aide beaucoup

    1. Bonjour Cécile,

      j’ai essayé avec l’argument dec=4, mais cela ne fonctionne pas sur la pvalue.

      make.table(dat=heart_disease,
      strat=”has_heart_disease”,
      cat.varlist=c(“gender”,”chest_pain”),
      cont.varlist=c(“age”,”serum_cholestoral”, “max_heart_rate”),
      cat.ptype = c(“chisq”, “chisq”),
      cont.ptype = c(“wilcox”, “ttest”,”ttest”),
      dec=4)
      Je vous suggère de demander au développeur du package, sur le repo GitHub : https://github.com/emwozniak/Table1/issues
      Bonne continuation

  5. bonjour,

    mes tables1 sont trop larges et dans word et Rstudio la dernière colonne se trouve en dessous du tableau.
    mon tableau n’est pas d’un seul bloc, ne tient pas sur la largeur de page.
    comment résourdre ce problème ?

    merci de votre aide

    1. Bonjour Bruno,

      Cela est sans doute possible de régler la largeur des colonnes avec du code css, mais personnellement je ne sais pas le faire.
      A part vous proposer de rectifier les largeurs sous Word, je n’ai pas d’autres solutions.
      Bonne continuation

  6. Bonjour Claire,
    merci pour vos articles cela m’aide énormément.
    J’aurais besoin de votre aide je n’arrive pas à insérer une table avec kable (myTable1) comme “Cazes” :
    ” le tableau d’affiche bien dans le fichier Rmd et dans la console mais impossible de l’afficher dans word.
    Message d’erreur indiquant que l’objet (myTable1) n’existe pas.”
    Comme je débute je ne sais vraiment pas quoi faire.
    Ma table est une data. frame et non un objet car elle vient de make.table.
    Merci pour votre aide
    Julie

    1. Bonjour Julie,

      je viens de rester le code, et de mon côté tout fonctionne.
      Je vais vous envoyer mon .Rmd et vous me direz si celui-ci fonctionne ou pas de votre côté.

      1. Bonjour Claire,
        Je n’avais pas téléchargé le package funmodeling
        Et je n’avais pas compris qu’il fallait charger le fichier de données sur le script rmd.
        Merci énormément.
        Bonne journée

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Bonjour !

vous venez souvent ?

Identifiez-vous pour avoir accès à toutes les fontionnalités !

Aide mémoire off'R ;)

Enregistrez vous pour recevoir gratuitement mes fiches “aide mémoire” (ou cheat sheets) qui vous permettront de réaliser facilement les principales analyses biostatistiques avec le logiciel R et pour être informés des mises à jour du site.