
Tableau des événements indésirables en R (rtables/tern)
Construisez le tableau de sécurité que porte chaque rapport d’étude clinique — les événements indésirables apparus sous traitement par classe de système d’organes et terme préféré, répartis par bras de traitement — avec le moteur de mise en page rtables et tern sur des données ADAE pharmaverse publiques
Un tutoriel complet et exécutable pour le tableau de synthèse des événements indésirables — le tableau de sécurité TEAE par classe de système d’organes (SOC) et terme préféré (PT). Apprenez ce qu’est le tableau (une synthèse globale des « sujets avec au moins un événement indésirable apparu sous traitement », puis des comptes SOC/PT imbriqués en n (%) de sujets, où le dénominateur est la population de sécurité), puis construisez-le avec rtables et tern : summarize_num_patients() pour les comptes de patients globaux et par SOC, count_occurrences() pour les fréquences par terme préféré, et alt_counts_df pour le dénominateur au niveau sujet — sur des données ADAE pharmaverseadam publiques, de sorte que chaque ligne s’exécute.
- Le tableau de synthèse des événements indésirables (AE) est le tableau de sécurité que porte chaque rapport d’étude clinique — les événements indésirables apparus sous traitement (TEAE) résumés par classe de système d’organes (SOC) et terme préféré (PT) imbriqué, répartis par bras de traitement.
- Comptez les sujets, pas les événements. Les nombres sont des n (%) de sujets ayant présenté au moins un tel événement — un sujet qui rapporte un prurit à trois reprises est compté une seule fois. Compter les lignes brutes revient à compter les événements, l’erreur classique du tableau des AE.
- Le dénominateur est la population de sécurité. Chaque pourcentage est rapporté au N du bras de la colonne (l’en-tête
(N=xx)), tiré de l’ADSL au niveau sujet — fourni àrtablesviaalt_counts_df, jamais à partir des enregistrements d’AE eux-mêmes. - Deux fonctions
ternle construisent.summarize_num_patients()donne les lignes « sujets avec ≥ 1 AE » globales et par SOC ;count_occurrences()donne les fréquences par terme préféré. - Le tableau publié est trié par fréquence et soumis à un seuil. Le tableau complet atteint des centaines de termes préférés ; un tableau de soumission trie les SOC et les PT par fréquence décroissante et conserve les AE survenant chez ≥ un pourcentage choisi de n’importe quel bras.
Introduction
Après le tableau démographique, la sortie suivante que consulte un évaluateur est le tableau de synthèse des événements indésirables — le cœur de la section sécurité d’un rapport d’étude clinique (CSR). Il répond à la question à laquelle existe toute lecture de sécurité : quels événements indésirables sont survenus, à quelle fréquence, et y en a-t-il eu davantage sur les bras actifs que sous placebo ? Les événements sont regroupés par classe de système d’organes (SOC), listés par terme préféré (PT) au sein de chaque classe, et comptés comme le nombre et le pourcentage de sujets atteints dans chaque bras de traitement.
C’est un tableau dans le flux de travail TLF (tableaux, listings et figures) pharma, et il est construit à partir d’un seul jeu de données d’analyse : ADAE, le jeu de données d’analyse des événements indésirables (une ligne par enregistrement d’événement indésirable). Cette leçon construit le tableau à la manière de l’industrie — avec le moteur de mise en page rtables et son compagnon clinique tern — sur des données pharmaverse publiques, de sorte que chaque ligne s’exécute telle qu’elle est écrite. Elle suit la vignette canonique du tableau des événements indésirables de tern, ré-écrite en une construction guidée unique.
Voici où nous allons — les classes de système d’organes les plus fréquentes par bras, calculées directement à partir de l’ADAE que nous résumons ci-dessous :
Les deux classes liées à la peau — general disorders and administration-site conditions et skin and subcutaneous tissue disorders — dominent, et les deux bras actifs se situent bien au-dessus du placebo sur ce point : la réaction attendue à un patch transdermique. À la fin de cette leçon, vous aurez produit ce signal sous forme de tableau SOC/PT formel. Si « ADAE », « apparu sous traitement » ou « SOC/PT » vous sont nouveaux, construisez d’abord le jeu de données dans Créer l’ADAE en R avec admiral, qui dérive l’ADAE que ce tableau lit.
Ce qu’est un tableau de synthèse des événements indésirables
Un tableau de synthèse des AE est un tableau de comptage doté d’une anatomie fixe à deux niveaux : un bloc supérieur résumant les AE globalement, puis un bloc par classe de système d’organes (SOC) — un regroupement MedDRA (Medical Dictionary for Regulatory Activities) de systèmes corporels apparentés — avec les termes préférés (PT) à l’intérieur. La forme est conventionnelle, de sorte que chacun de ceux que vous construisez se lit de la même façon :
| Partie | Ce que c’est | Dans cette leçon |
|---|---|---|
| Synthèse globale | Sujets avec ≥ 1 TEAE, et le nombre total d’événements, par bras | Les deux premières lignes |
| Groupes de lignes | Un bloc par classe de système d’organes | Skin disorders, GI disorders, … |
| Lignes au sein d’un groupe | Une ligne par terme préféré | Application-site pruritus, diarrhoea, … |
| Colonnes | Les bras de traitement, plus un Total | Placebo · Xanomeline Low · Xanomeline High · All Patients |
| Statistique de cellule | n (%) de sujets atteints dans ce bras | 21 (29.2%) |
| En-tête de colonne (N=) | La taille de la population de sécurité du bras | Le dénominateur de chaque pourcentage |
Deux conventions décident si le tableau est correct :
- Vous comptez les sujets, pas les événements. Un « événement indésirable apparu sous traitement (TEAE) » est un événement survenu à la première dose ou après — les événements que comptent les tableaux de sécurité. Mais le tableau rapporte combien de sujets ont présenté au moins un tel événement, non combien d’événements sont survenus. Un sujet qui rapporte un prurit au site d’application sur trois jours distincts est un seul sujet dans le compte de ce terme préféré. Se tromper ici — compter les enregistrements d’AE — gonfle chaque nombre et constitue l’erreur la plus courante du tableau des AE.
- Le dénominateur du pourcentage est la population de sécurité, pas le nombre d’enregistrements d’AE. Un 29.2 % pour « application-site pruritus » sur le bras à forte dose signifie 29.2 % de la population de sécurité à forte dose, tiré du jeu de données au niveau sujet — jamais un pourcentage des lignes d’AE. C’est pourquoi le dénominateur doit être fourni séparément, comme vous le verrez.
Les données
Nous utilisons l’ADAE (jeu de données d’analyse des événements indésirables) et l’ADSL (jeu de données d’analyse au niveau sujet) fournis dans pharmaverseadam — l’étude pilote CDISC (Clinical Data Interchange Standards Consortium) synthétique et libre de droits comparant un placebo à deux doses de Xanomeline, administré sous forme de patch transdermique. L’ADAE porte déjà le AEBODSYS codé (classe de système d’organes) et le AEDECOD (terme préféré), le bras de traitement TRT01A, et l’indicateur d’apparition sous traitement TRTEMFL, de sorte qu’aucune dérivation n’est nécessaire ici — ce travail est réalisé dans la leçon ADAE.
Deux filtres mettent en place l’analyse. Nous conservons les événements apparus sous traitement (TRTEMFL == "Y") pour les enregistrements d’AE, et nous résumons la population de sécurité (SAFFL == "Y", l’indicateur de population de sécurité) — la population standard pour un tableau de sécurité, qui écarte les sujets en échec de sélection n’ayant jamais reçu de dose.
library(dplyr, warn.conflicts = FALSE)
library(pharmaverseadam)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
# ADSL — one row per subject; the safety population is the denominator source
adsl <- pharmaverseadam::adsl %>%
filter(SAFFL == "Y") %>%
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
# ADAE — one row per AE record; keep the treatment-emergent events for the safety population
adae <- pharmaverseadam::adae %>%
filter(TRTEMFL == "Y", SAFFL == "Y") %>%
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
adae %>%
select(USUBJID, TRT01A, AEBODSYS, AEDECOD, TRTEMFL) %>%
head(5)# A tibble: 5 × 5
USUBJID TRT01A AEBODSYS AEDECOD TRTEMFL
<chr> <fct> <chr> <chr> <chr>
1 01-701-1015 Placebo GENERAL DISORDERS AND ADMINISTRATION SITE… APPLIC… Y
2 01-701-1015 Placebo GENERAL DISORDERS AND ADMINISTRATION SITE… APPLIC… Y
3 01-701-1015 Placebo GASTROINTESTINAL DISORDERS DIARRH… Y
4 01-701-1023 Placebo SKIN AND SUBCUTANEOUS TISSUE DISORDERS ERYTHE… Y
5 01-701-1023 Placebo SKIN AND SUBCUTANEOUS TISSUE DISORDERS ERYTHE… Y
Faire de TRT01A un facteur avec le placebo en premier fixe l’ordre des colonnes du tableau (placebo, puis dose croissante) au lieu de le laisser tomber dans l’ordre alphabétique. Après les filtres, adsl contient les 254 sujets dosés (86 sous placebo, 96 à faible dose, 72 à forte dose) et adae contient leurs 1 122 enregistrements d’AE apparus sous traitement. Ces deux objets — les événements et le dénominateur au niveau sujet — sont tout ce dont le tableau a besoin.
Construire la synthèse globale des AE
Commencez par le haut du tableau : combien de sujets de chaque bras ont présenté au moins un AE apparu sous traitement, et combien d’événements y a-t-il eu au total ? La fonction tern qui crée la mise en page pour cela est summarize_num_patients(), qui compte les sujets uniques (chaque sujet une fois) et les enregistrements non uniques (chaque événement). Vous lisez la mise en page de haut en bas comme une recette ; rien n’est calculé tant que build_table() ne l’applique pas aux données.
library(rtables)
library(tern)
basic_table() %>%
split_cols_by("TRT01A") %>% # one column per arm
add_overall_col("All Patients") %>% # the Total column
add_colcounts() %>% # the (N=xx) header row
summarize_num_patients(
var = "USUBJID",
.stats = c("unique", "nonunique"),
.labels = c(
unique = "Patients with at least one AE",
nonunique = "Total number of events"
)
) %>%
build_table(df = adae, alt_counts_df = adsl) Placebo Xanomeline Low Dose Xanomeline High Dose All Patients
(N=86) (N=96) (N=72) (N=254)
——————————————————————————————————————————————————————————————————————————————————————————————————————
Patients with at least one AE 65 (75.6%) 84 (87.5%) 68 (94.4%) 217 (85.4%)
Total number of events 281 427 414 1122
L’argument clé est alt_counts_df = adsl. Le corps du tableau est construit à partir d’adae (les enregistrements d’AE), mais le N de colonne dans l’en-tête — le dénominateur du pourcentage — doit provenir des sujets, c’est pourquoi nous fournissons à build_table() l’ADSL au niveau sujet comme jeu de données de comptage alternatif. Lisez le résultat : 65 des 86 sujets sous placebo (75.6 %) ont présenté un AE apparu sous traitement, contre 87.5 % à faible dose et 94.4 % à forte dose — davantage de sujets atteints sur les bras actifs, le schéma attendu. La deuxième ligne compte les événements (1 122 au total), ce qui explique qu’elle soit plus grande que les comptes de sujets au-dessus. Sans alt_counts_df, les pourcentages seraient calculés à partir du nombre d’enregistrements d’AE et n’auraient aucun sens.
Construire le tableau par classe de système d’organes et terme préféré
Le tableau complet répète cette synthèse à l’intérieur de chaque classe de système d’organes, puis liste les fréquences par terme préféré en dessous. Deux étapes supplémentaires le construisent : split_rows_by("AEBODSYS") ouvre un groupe de lignes par SOC, et count_occurrences() compte les sujets par terme préféré (une fois par sujet, même en cas d’événements répétés).
lyt <- basic_table() %>%
split_cols_by("TRT01A") %>%
add_overall_col("All Patients") %>%
add_colcounts() %>%
# top block: overall subjects-with-an-AE summary
summarize_num_patients(
var = "USUBJID", .stats = c("unique", "nonunique"),
.labels = c(unique = "Patients with at least one AE",
nonunique = "Total number of events")
) %>%
# one row group per system organ class
split_rows_by(
"AEBODSYS", child_labels = "visible", nested = FALSE,
indent_mod = -1L, split_fun = drop_split_levels
) %>%
# per-SOC subjects-with-an-AE-in-this-class summary
summarize_num_patients(
var = "USUBJID", .stats = "unique",
.labels = c(unique = "Patients with at least one AE in this class")
) %>%
# preferred-term frequencies within each class
count_occurrences(vars = "AEDECOD")
tbl <- build_table(lyt, df = adae, alt_counts_df = adsl)
nrow(tbl)[1] 278
C’est un tableau des AE complet et correct — mais il compte 278 lignes, car l’étude pilote a enregistré un terme préféré pour presque chaque type d’événement. Une soumission n’en imprime jamais la totalité : elle trie par fréquence et applique un seuil de reporting. Nous traitons cela ensuite. Le point important ici est que lyt est une mise en page réutilisable — une description du tableau, pas un résultat — de sorte que le même objet peut être ré-appliqué à n’importe quel jeu de données (un sous-groupe, une autre étude) sans rien re-spécifier.
En faire le tableau publié : trier par fréquence et appliquer un seuil
Un vrai tableau de sécurité est trié par fréquence décroissante (les SOC et PT les plus fréquents en premier, pas dans l’ordre alphabétique) et limité aux AE survenant assez souvent pour compter — par convention ceux présents chez au moins un pourcentage choisi de sujets dans n’importe quel bras (5 % est un seuil courant). Sélectionnez d’abord les termes préférés qui franchissent le seuil :
# subjects per arm (the denominators)
arm_n <- table(adsl$TRT01A)
# keep preferred terms reported by >=5% of subjects in ANY arm
pt_keep <- adae %>%
distinct(USUBJID, TRT01A, AEDECOD) %>% # subject-level: one row per subject x PT
count(TRT01A, AEDECOD, name = "n_subj") %>%
mutate(pct = 100 * n_subj / as.numeric(arm_n[as.character(TRT01A)])) %>%
group_by(AEDECOD) %>%
summarise(max_pct = max(pct), .groups = "drop") %>%
filter(max_pct >= 5) %>%
pull(AEDECOD)
length(pt_keep)[1] 24
Reconstruisez ensuite le tableau sur ces termes préférés et triez-le. prune_table() supprime les lignes vides, sort_at_path() ordonne les classes de système d’organes par nombre total de sujets (cont_n_allcols) et les termes préférés au sein de chaque classe par fréquence (score_occurrences) :
adae_common <- adae %>%
filter(AEDECOD %in% pt_keep) %>%
mutate(AEBODSYS = as.factor(AEBODSYS), AEDECOD = as.factor(AEDECOD))
basic_table(
title = "Table 14-3.01",
subtitles = "Treatment-Emergent Adverse Events in >=5% of Any Arm (Safety Population)"
) %>%
split_cols_by("TRT01A") %>%
add_overall_col("All Patients") %>%
add_colcounts() %>%
split_rows_by(
"AEBODSYS", child_labels = "visible", nested = FALSE,
indent_mod = -1L, split_fun = drop_split_levels
) %>%
summarize_num_patients(
var = "USUBJID", .stats = "unique",
.labels = c(unique = "Patients with at least one AE in this class")
) %>%
count_occurrences(vars = "AEDECOD") %>%
build_table(df = adae_common, alt_counts_df = adsl) %>%
prune_table() %>%
sort_at_path(path = "AEBODSYS", scorefun = cont_n_allcols) %>%
sort_at_path(path = c("AEBODSYS", "*", "AEDECOD"), scorefun = score_occurrences)Table 14-3.01
Treatment-Emergent Adverse Events in >=5% of Any Arm (Safety Population)
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
Placebo Xanomeline Low Dose Xanomeline High Dose All Patients
(N=86) (N=96) (N=72) (N=254)
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
SKIN AND SUBCUTANEOUS TISSUE DISORDERS
Patients with at least one AE in this class 19 (22.1%) 36 (37.5%) 35 (48.6%) 90 (35.4%)
PRURITUS 8 (9.3%) 21 (21.9%) 25 (34.7%) 54 (21.3%)
ERYTHEMA 8 (9.3%) 14 (14.6%) 14 (19.4%) 36 (14.2%)
RASH 5 (5.8%) 13 (13.5%) 8 (11.1%) 26 (10.2%)
HYPERHIDROSIS 2 (2.3%) 4 (4.2%) 8 (11.1%) 14 (5.5%)
SKIN IRRITATION 3 (3.5%) 6 (6.2%) 5 (6.9%) 14 (5.5%)
BLISTER 0 5 (5.2%) 1 (1.4%) 6 (2.4%)
GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS
Patients with at least one AE in this class 16 (18.6%) 39 (40.6%) 31 (43.1%) 86 (33.9%)
APPLICATION SITE PRURITUS 6 (7.0%) 23 (24.0%) 21 (29.2%) 50 (19.7%)
APPLICATION SITE ERYTHEMA 3 (3.5%) 13 (13.5%) 14 (19.4%) 30 (11.8%)
APPLICATION SITE DERMATITIS 5 (5.8%) 9 (9.4%) 7 (9.7%) 21 (8.3%)
APPLICATION SITE IRRITATION 3 (3.5%) 9 (9.4%) 9 (12.5%) 21 (8.3%)
APPLICATION SITE VESICLES 1 (1.2%) 5 (5.2%) 5 (6.9%) 11 (4.3%)
FATIGUE 1 (1.2%) 5 (5.2%) 5 (6.9%) 11 (4.3%)
GASTROINTESTINAL DISORDERS
Patients with at least one AE in this class 12 (14.0%) 10 (10.4%) 16 (22.2%) 38 (15.0%)
DIARRHOEA 9 (10.5%) 5 (5.2%) 3 (4.2%) 17 (6.7%)
VOMITING 3 (3.5%) 4 (4.2%) 6 (8.3%) 13 (5.1%)
NAUSEA 3 (3.5%) 3 (3.1%) 6 (8.3%) 12 (4.7%)
SALIVARY HYPERSECRETION 0 0 4 (5.6%) 4 (1.6%)
NERVOUS SYSTEM DISORDERS
Patients with at least one AE in this class 4 (4.7%) 14 (14.6%) 15 (20.8%) 33 (13.0%)
DIZZINESS 2 (2.3%) 9 (9.4%) 10 (13.9%) 21 (8.3%)
HEADACHE 3 (3.5%) 3 (3.1%) 5 (6.9%) 11 (4.3%)
SYNCOPE 0 5 (5.2%) 2 (2.8%) 7 (2.8%)
CARDIAC DISORDERS
Patients with at least one AE in this class 6 (7.0%) 8 (8.3%) 10 (13.9%) 24 (9.4%)
SINUS BRADYCARDIA 2 (2.3%) 7 (7.3%) 8 (11.1%) 17 (6.7%)
MYOCARDIAL INFARCTION 4 (4.7%) 2 (2.1%) 4 (5.6%) 10 (3.9%)
INFECTIONS AND INFESTATIONS
Patients with at least one AE in this class 8 (9.3%) 5 (5.2%) 9 (12.5%) 22 (8.7%)
NASOPHARYNGITIS 2 (2.3%) 4 (4.2%) 6 (8.3%) 12 (4.7%)
UPPER RESPIRATORY TRACT INFECTION 6 (7.0%) 1 (1.0%) 3 (4.2%) 10 (3.9%)
RESPIRATORY, THORACIC AND MEDIASTINAL DISORDERS
Patients with at least one AE in this class 1 (1.2%) 5 (5.2%) 5 (6.9%) 11 (4.3%)
COUGH 1 (1.2%) 5 (5.2%) 5 (6.9%) 11 (4.3%)
Le tableau se lit désormais comme une sortie de sécurité publiée. Les deux classes liées à la peau arrivent en tête, exactement comme la figure d’introduction le promettait. Application-site pruritus est le terme préféré le plus marquant — 7.0 % sous placebo contre 24.0 % et 29.2 % sur les deux bras Xanomeline — suivi de application-site erythema et dermatitis. Le schéma est cohérent et cliniquement évident : Xanomeline est ici un patch transdermique, donc les réactions cutanées et au site d’application sont le signal dominant, bien au-dessus du placebo, tandis que les classes systémiques (gastro-intestinales, du système nerveux) sont plus uniformément réparties. Ce contraste — les bras actifs bien au-dessus du placebo sur les classes cutanées — est précisément ce qu’un évaluateur de sécurité cherche en lisant ce tableau.
Une alternative gtsummary rapide — et quand utiliser chaque outil
Pour une exploration rapide ou un manuscrit, gtsummary produit une version HTML soignée de la synthèse globale en quelques appels. Elle est excellente pour la vue de haut niveau « sujets avec un AE par bras » :
library(gtsummary)
adsl %>%
mutate(any_teae = USUBJID %in% adae$USUBJID) %>% # did the subject have a TEAE?
select(TRT01A, any_teae) %>%
tbl_summary(
by = TRT01A,
label = any_teae ~ "Patients with at least one TEAE",
statistic = any_teae ~ "{n} ({p}%)"
) %>%
add_overall(last = TRUE) %>%
modify_header(label ~ "**Adverse events**") %>%
bold_labels()| Adverse events | Placebo N = 861 |
Xanomeline Low Dose N = 961 |
Xanomeline High Dose N = 721 |
Overall N = 2541 |
|---|---|---|---|---|
| Patients with at least one TEAE | 65 (76%) | 84 (88%) | 68 (94%) | 217 (85%) |
| 1 n (%) | ||||
Les nombres correspondent exactement à la synthèse tern — 65 (76 %) sous placebo, et ainsi de suite — car les deux lisent les mêmes sujets. Utilisez ceci comme repère de décision entre les deux moteurs :
| gtsummary | rtables + tern | |
|---|---|---|
| Idéal pour | Exploration rapide, manuscrits, la synthèse AE de haut niveau | Le tableau de sécurité SOC/PT complet pour une soumission |
| Tableau SOC → PT imbriqué | Possible, mais tern est conçu pour ça |
La norme, avec tri/élagage/seuils |
| Contrôle du dénominateur | tbl_summary sur les données sujet |
alt_counts_df (sujets) vs df (événements) |
| Sortie | HTML soigné / gt | Monospace, paginée, prête pour RTF |
| Rôle dans l’industrie | Universitaire + analyse précoce | La norme pharma pour les TLF validés |
En pratique, les équipes utilisent gtsummary pour explorer et rédiger, puis rtables/tern pour le tableau SOC/PT validé qui entre dans la soumission. Ils sont complémentaires, pas concurrents.
Demandez à Prova « comment ajouter à mon tableau des événements indésirables tern une colonne montrant la différence de risque par rapport au placebo ? » — elle répond en s’appuyant sur les leçons de ce pilier, avec du code exécutable que vous pouvez essayer sur les données pharmaverse d’exemple. The runtime is the judge. Demander à Prova →
Problèmes fréquents
Vos comptes sont bien trop élevés — vous avez compté les événements, pas les sujets. Un tableau des AE rapporte les sujets ayant présenté au moins un événement, mais adae compte de nombreuses lignes par sujet. summarize_num_patients() et count_occurrences() comptent tous deux les sujets uniques pour vous, ce qui est précisément la raison pour laquelle vous les utilisez au lieu de count() sur les lignes brutes. Si un nombre de « sujets avec un AE » dépasse le N du bras, c’est que vous comptez des enregistrements d’AE.
Chaque pourcentage est rapporté au mauvais total. Si vos pourcentages semblent trop petits, c’est que vous avez oublié alt_counts_df = adsl. Le corps du tableau provient des enregistrements d’AE, mais le dénominateur doit être la population de sécurité au niveau sujet — alt_counts_df est ce qui la fournit. Sans lui, tern divise par le nombre de lignes d’AE de chaque colonne et l’en-tête (N=xx) est erroné.
Le tableau est alphabétique au lieu d’être ordonné par fréquence. Par défaut, rtables liste les classes de système d’organes et les termes préférés dans l’ordre des facteurs/alphabétique. Un tableau de sécurité publié est trié par fréquence décroissante. Utilisez sort_at_path() avec cont_n_allcols (pour ordonner les SOC) et score_occurrences (pour ordonner les PT au sein de chaque SOC), comme montré ci-dessus.
Les événements pré-traitement gonflent le tableau. Si vous résumez l’ADAE brut sans filter(TRTEMFL == "Y"), les événements survenus avant la première dose (souvent des antécédents médicaux saisis comme AE) s’infiltrent dans les comptes. Un tableau TEAE compte uniquement les événements apparus sous traitement — filtrez sur TRTEMFL == "Y" d’abord, à chaque fois.
Questions fréquentes
C’est le tableau de sécurité qui résume les événements indésirables apparus sous traitement — ceux survenus à la première dose ou après — par classe de système d’organes (SOC) et terme préféré (PT), répartis par bras de traitement. Chaque cellule est le nombre et le pourcentage de sujets atteints dans ce bras (pas le nombre d’événements), le pourcentage étant rapporté à la taille de la population de sécurité du bras. C’est le tableau central de la section sécurité d’un rapport d’étude clinique, défini dans la directive ICH E3 sur la structure du CSR.
Utilisez rtables + tern : répartissez les colonnes par bras de traitement, ajoutez les effectifs de colonne, puis combinez summarize_num_patients() pour les lignes « sujets avec ≥ 1 AE » avec count_occurrences(vars = "AEDECOD") pour les fréquences par terme préféré, à l’intérieur d’un split_rows_by("AEBODSYS") pour les classes de système d’organes. Appliquez la mise en page avec build_table(df = adae, alt_counts_df = adsl), où alt_counts_df fournit le dénominateur au niveau sujet. La construction complète est détaillée ci-dessus sur des données pharmaverse publiques.
Aux sujets. La cellule 21 (29.2%) signifie que 21 sujets — 29.2 % de la population de sécurité de ce bras — ont présenté au moins un tel événement, quel que soit le nombre de fois où chacun l’a rapporté. Le dénominateur est le N de colonne dans l’en-tête, tiré de l’ADSL au niveau sujet via alt_counts_df. Compter les enregistrements d’AE au lieu des sujets (et diviser par le nombre d’enregistrements) est l’erreur la plus courante du tableau des AE.
Calculez, par terme préféré, le pourcentage de sujets atteints dans chaque bras, conservez les termes dont le maximum entre les bras est ≥ 5 %, filtrez l’ADAE sur ces termes, et reconstruisez le tableau. Le seuil est un choix d’étude (5 % dans n’importe quel bras est courant) ; l’exemple détaillé ci-dessus dérive les termes préférés éligibles avec un distinct() + count() sur les sujets, puis reconstruit et trie le tableau.
Triez le tableau construit avec rtables::sort_at_path(). Ordonnez les classes de système d’organes par nombre total de sujets avec scorefun = cont_n_allcols à path = "AEBODSYS", et ordonnez les termes préférés au sein de chaque classe avec scorefun = score_occurrences à path = c("AEBODSYS", "*", "AEDECOD"). Exécutez prune_table() d’abord pour supprimer les lignes vides. Sans cela, le tableau reste dans l’ordre des facteurs/alphabétique, ce qui n’est pas la façon dont un tableau de sécurité est présenté.
Utilisez gtsummary pour l’exploration rapide, les manuscrits et la synthèse de haut niveau « sujets avec un AE par bras » — il produit du HTML soigné en quelques appels. Utilisez rtables/tern pour le tableau de sécurité SOC/PT imbriqué complet qui entre dans une soumission réglementaire : il est conçu pour la mise en page, les dénominateurs sujets-vs-événements (alt_counts_df), le tri par fréquence et la sortie monospace, exportable en RTF, dont un CSR a besoin. De nombreuses équipes utilisent les deux.
Testez vos connaissances
En utilisant les objets adsl et adae de cette leçon, construisez une synthèse globale des événements indésirables graves apparus sous traitement par bras de traitement : le nombre et le pourcentage de sujets ayant présenté au moins un TEAE grave, plus le nombre total d’événements graves. Les AE graves sont signalés par AESER == "Y" dans les données. Conservez le dénominateur de la population de sécurité.
Les AE graves sont un sous-ensemble des enregistrements d’AE, alors filtrez adae sur AESER == "Y" et passez-le comme df à build_table() — mais conservez alt_counts_df = adsl pour que le dénominateur reste la population de sécurité complète (le pourcentage est « de tous les sujets dosés », pas « de ceux ayant un AE grave »). La mise en page est la même synthèse globale summarize_num_patients() que vous avez construite en premier.
library(dplyr)
library(rtables)
library(tern)
adae_serious <- adae %>% filter(AESER == "Y")
basic_table() %>%
split_cols_by("TRT01A") %>%
add_overall_col("All Patients") %>%
add_colcounts() %>%
summarize_num_patients(
var = "USUBJID", .stats = c("unique", "nonunique"),
.labels = c(unique = "Patients with at least one serious AE",
nonunique = "Total number of serious events")
) %>%
build_table(df = adae_serious, alt_counts_df = adsl)Les AE graves sont bien plus rares que les AE en général, donc les comptes sont petits dans chaque bras. Le détail crucial est alt_counts_df = adsl : il maintient le dénominateur à la population de sécurité complète, de sorte qu’une cellule se lit « de tous les sujets dosés sur ce bras, voici combien ont eu un événement grave » — le sens dont un tableau de sécurité a besoin. Pour l’étendre à une ventilation SOC/PT, ajoutez les mêmes étapes split_rows_by("AEBODSYS") + count_occurrences("AEDECOD") sur adae_serious.
A. Le nombre d’enregistrements d’événements indésirables (chaque occurrence) dans ce bras B. Le nombre de sujets ayant présenté au moins un tel événement dans ce bras, rapporté au N de la population de sécurité du bras C. Le nombre de sujets ayant cet événement, rapporté à l’ensemble des participants de l’étude
B. Chaque cellule compte les sujets (un sujet ayant trois épisodes du même terme préféré est compté une fois), et le dénominateur du pourcentage est le N du bras de cette colonne — la population de sécurité, fournie via alt_counts_df. A est le compte d’événements (la ligne « Total number of events », et l’erreur classique si vous comptez les lignes brutes) ; C utiliserait le total général comme dénominateur au lieu du N du bras, ce que la répartition par TRT01A évite.
Conclusion
Le tableau de synthèse des événements indésirables est la lecture de sécurité d’un rapport d’étude clinique, et il se résume à deux règles : comptez les sujets, pas les événements, et divisez par la population de sécurité. Avec rtables et tern, ces règles sont intégrées à la mise en page — summarize_num_patients() pour les comptes de sujets globaux et par classe, count_occurrences() pour les fréquences par terme préféré, et alt_counts_df pour le dénominateur au niveau sujet. Ajoutez un tri par fréquence et un seuil de reporting et vous obtenez le tableau publié : des classes de système d’organes et des termes préférés ordonnés selon leur fréquence, avec le signal actif-versus-placebo — ici, les réactions cutanées du patch transdermique — se lisant directement sur la page. La mise en page est réutilisable, de sorte que le même objet produit un tableau de sous-groupe, un tableau des AE graves, ou le tableau de la prochaine étude sans re-spécification.
Cette leçon est reproductible : chaque résultat de cette page a été produit par le code montré — copiez n’importe quel bloc et exécutez-le pour les reproduire. The runtime is the judge.
Leçons connexes
- Créer l’ADAE en R avec admiral — dérive le jeu de données des événements indésirables (la structure de données d’occurrence, OCCDS —
TRTEMFL, indicateurs d’occurrence) que ce tableau résume. · Le tableau démographique rtables — la Table 1 qui ouvre le CSR, construite avec le même moteur de mise en pagertables/ternetgtsummary. · Construire le jeu de données ADaM temps-jusqu’à-événement (ADTTE) — le jeu de données d’efficacité derrière la figure de Kaplan-Meier, la prochaine sortie de la série TLF. - Où cela s’inscrit : construire les ADaM avec admiral (ADSL, ADAE, ADTTE) → générer les tableaux, listings et figures (le tableau démographique → les tableaux de synthèse des AE (vous êtes ici) → la figure de Kaplan-Meier) → assembler la soumission. Le tableau des AE est la pièce maîtresse de sécurité de cette séquence.
Réutilisation
Citation
@online{2026,
author = {},
title = {Tableau des événements indésirables en R (rtables/tern)},
date = {2026-06-30},
url = {https://www.datanovia.com/learn/pharma-clinical/04-tlf-generation/adverse-event-table-rtables},
langid = {fr}
}