Domaine SDTM AE en R avec sdtm.oak : événements indésirables

Mappez un extrait EDC brut d’événements indésirables vers un domaine SDTM AE conforme à CDISC en R avec sdtm.oak — la classe d’observation Events, les qualificateurs de Controlled Terminology, les dates ISO 8601, et la variable de séquence

Un tutoriel complet et exécutable pour le domaine SDTM AE (événements indésirables) en R. Découvrez ce qu’est la classe d’observation Events de SDTM CDISC (un enregistrement par événement, indexé par la variable topic AETERM), en quoi elle diffère de la classe Findings, puis mappez un extrait brut d’événements indésirables vers un domaine AE conforme avec sdtm.oak : generate_oak_id_vars() pour la traçabilité des enregistrements, assign_no_ct() pour le terme verbatim et les AEDECOD/AEBODSYS codés selon MedDRA, assign_ct() pour les qualificateurs de Controlled Terminology (AESEV/AESER/AEREL/AEOUT), assign_datetime() pour les AESTDTC/AEENDTC ISO 8601, et derive_seq() pour AESEQ — chaque ligne exécutable, alimentant le jeu de données ADAE en aval.

Date de publication

1 juillet 2026

Modifié

7 juillet 2026

AstucePoints clés
  • AE est un domaine Events, pas un domaine Findings. La classe Findings (signes vitaux, analyses biologiques) enregistre des mesures indexées par un code de test ; la classe Events enregistre des choses qui sont arrivées — une ligne par événement indésirable, indexée par le terme rapporté AETERM.
  • La variable topic est AETERM (texte libre), et elle ne relève d’aucune Controlled Terminology. assign_no_ct() copie le terme rapporté verbatim tel qu’il a été collecté ; les AEDECOD et AEBODSYS codés proviennent du codage MedDRA réalisé en amont, ils sont donc eux aussi copiés, pas recodés.
  • Les qualificateurs, eux, sont contrôlés. assign_ct() recode la sévérité, la gravité, le lien de causalité et l’évolution collectés vers leurs termes standard CDISC (AESEV, AESER, AEREL, AEOUT).
  • La temporalité tient en deux dates ISO 8601. assign_datetime() construit le début (AESTDTC) et la fin (AEENDTC) à partir des champs date/heure bruts — et renvoie sans risque une date partielle lorsque le jour brut est inconnu, plutôt que d’en inventer un.
  • derive_seq() numérote les événements. AESEQ est unique par sujet, et l’ensemble du domaine devient l’entrée standard que lit le jeu de données d’analyse ADAE en aval.

Introduction

Les événements indésirables sont l’épine dorsale de la sécurité de tout essai clinique. Chaque étude les collecte, chaque régulateur les examine de près, et l’évaluateur qui décide si un médicament est sûr les lit à travers un seul jeu de données standardisé : le domaine SDTM (Study Data Tabulation Model) AE (adverse events, événements indésirables). Ce que le site enregistre réellement est plus désordonné — une page de CRF (case report form, cahier d’observation) avec un terme rapporté en texte libre, une sévérité cochée « Mild », une date de début saisie comme le coordinateur l’a saisie. Le travail ici consiste à transformer cet extrait brut en un domaine AE conforme qu’un régulateur peut ouvrir et lire de la même façon pour n’importe quelle étude.

Cette leçon réalise ce mapping de bout en bout en R avec sdtm.oak, le package pharmaverse conçu pour la transformation des données brutes en SDTM. C’est la contrepartie Events du mapping d’un domaine Findings (signes vitaux) avec sdtm.oak : les mêmes algorithmes oak, une classe d’observation différente. Le SDTM est l’amont du flux de données clinique — brut → SDTM → ADaM → tables, listings et figures — de sorte qu’un domaine AE propre ici est ce qui rend l’étape suivante, la construction du jeu de données d’analyse ADAE des événements indésirables avec admiral, simple.

Voici où nous allons — le domaine AE conforme que nous allons construire, résumé par système d’organes et par sévérité directement à partir du résultat mappé :

Stacked horizontal bar chart of the mapped SDTM AE domain. Each bar is one system organ class (Nervous system disorders, Gastrointestinal disorders, General disorders, Skin and subcutaneous tissue disorders, Psychiatric disorders) on the y axis, its length the number of adverse events on the x axis, segmented by severity (mild in light blue, moderate in mid blue, severe in dark blue). Nervous system and gastrointestinal disorders each carry four events spanning mild to severe; the smaller body systems carry one or two. The point is that a raw adverse-event extract has become a tidy, standardized set of events that is immediately queryable by body system and severity.

Chaque segment ici est un enregistrement SDTM — un événement rapporté, codé vers un système d’organes, gradé pour sa sévérité. À la fin de cette leçon, vous les aurez tous produits à partir d’un extrait brut. Nouveau dans le pipeline plus large ? Commencez par le flux de données des essais cliniques, qui situe où se place le SDTM, et les standards CDISC, qui placent le SDTM aux côtés de CDASH et ADaM.

Ce qu’est la classe Events du SDTM

Le SDTM — le Study Data Tabulation Model — est le standard CDISC (Clinical Data Interchange Standards Consortium) pour organiser les données collectées d’un essai en domaines fixes. Il est publié sous la forme du standard fondamental SDTM et du SDTM Implementation Guide (SDTMIG). Le SDTM range chaque observation dans l’une des trois classes générales d’observation, et la classe détermine la forme d’un enregistrement :

Classe d’observation Ce qu’elle enregistre Exemples de domaines Variable topic
Interventions Ce qui est administré ou fait au sujet CM (médicaments concomitants), EX (exposition) --TRT
Events Ce qui est arrivé au sujet AE (événements indésirables), MH (antécédents médicaux), DS (disposition) --TERM
Findings Mesures et évaluations VS (signes vitaux), LB (analyses biologiques), EG (ECG) --TESTCD

La leçon compagnon sur les signes vitaux mappait un domaine Findings, où la variable topic est un code de test (VSTESTCD = « ce qui a été mesuré ») et où chaque enregistrement répond à « qu’a-t-on mesuré, et quel était le résultat ». Les Events sont différents. Un événement indésirable n’est pas une mesure — c’est une occurrence, donc la variable topic est le terme rapporté AETERM (« ce qui est arrivé »), et il y a un enregistrement par événement, non par évaluation. Cette seule différence — un terme au lieu d’un code de test, un événement au lieu d’une mesure — constitue toute la distinction entre les deux classes.

Un enregistrement AE, c’est ce terme topic accompagné de ses qualificateurs, identifiants et de sa temporalité :

Rôle de la variable Exemple AE Ce qu’elle porte
Identifiants STUDYID, USUBJID, DOMAIN, AESEQ L’étude, le sujet, le code de domaine (AE), et un numéro de séquence unique par enregistrement.
Topic AETERM Le terme rapporté verbatim, exactement tel que collecté sur le CRF.
Synonyme / dictionnaire AEDECOD, AEBODSYS Le terme préféré (PT) MedDRA et la classe de système d’organes (SOC) — l’événement codé.
Qualificateurs de résultat AESEV, AESER, AEREL, AEOUT Sévérité, gravité, lien de causalité avec le médicament à l’étude, et évolution.
Temporalité AESTDTC, AEENDTC Date/heure de début et de fin au format ISO 8601 (variables --DTC).

Deux conventions font l’essentiel du mapping. D’abord, la variable topic (AETERM) nomme l’observation et tout le reste la qualifie. Ensuite, la plupart des colonnes de qualificateurs sont restreintes par la Controlled Terminology (CT) — des codelists publiés par CDISC qui fixent les valeurs autorisées (AESEV doit valoir MILD, pas « mild » ni « 1 »). CDISC les maintient sous le nom de SDTM Controlled Terminology. Mapper le brut vers le SDTM consiste, en substance, à remplir les bonnes colonnes de qualificateurs avec les bonnes valeurs valides au regard de la CT — le même travail que pour les Findings, réalisé pour une forme d’enregistrement différente.

L’extrait brut d’événements indésirables et sa Controlled Terminology

Deux entrées pilotent un mapping : l’extrait brut et la Controlled Terminology de l’étude. sdtm.oak est livré avec des exemples bruts pour les médicaments concomitants et les signes vitaux, mais pas pour les événements indésirables ; nous construisons donc un petit extrait AE brut dans la forme sous laquelle un système EDC (electronic data capture, saisie électronique des données) l’exporte — une ligne large par événement, des noms de colonnes choisis par le promoteur, des dates dans le format qu’utilisait le formulaire.

library(sdtm.oak)
library(dplyr, warn.conflicts = FALSE)

# A raw adverse-event EDC extract: one row per event, as collected
ae_raw <- data.frame(
  PATNUM   = c("375","375","375","376","376","377","377","377","378","378","379","379"),
  AESPID   = c("AE01","AE02","AE03","AE01","AE02","AE01","AE02","AE03","AE01","AE02","AE01","AE02"),
  AETERM_R = c("HEADACHE","Nausea","Insomnia","Fatigue","MILD RASH","Dizziness",
               "Headache","Vomiting","Diarrhoea","Headache","Nausea","Pyrexia"),
  AEPT_R   = c("Headache","Nausea","Insomnia","Fatigue","Rash","Dizziness",
               "Headache","Vomiting","Diarrhoea","Headache","Nausea","Pyrexia"),
  AESOC_R  = c("Nervous system disorders","Gastrointestinal disorders","Psychiatric disorders",
               "General disorders","Skin and subcutaneous tissue disorders","Nervous system disorders",
               "Nervous system disorders","Gastrointestinal disorders","Gastrointestinal disorders",
               "Nervous system disorders","Gastrointestinal disorders","General disorders"),
  AESTDAT  = c("05 Jan 2023","07 Jan 2023","10 Jan 2023","12 Feb 2023","13 Feb 2023","02 Mar 2023",
               "04 Mar 2023","06 Mar 2023","09 Jan 2023","15 Jan 2023","21 Feb 2023","25 Feb 2023"),
  AESTTIM  = c("08:30","14:10","22:00","09:00","20:45","07:15","11:00","16:20","13:30","08:00","19:45","06:30"),
  AEENDAT  = c("06 Jan 2023","07 Jan 2023","12 Jan 2023","UN UNK 2023","20 Feb 2023","02 Mar 2023",
               "05 Mar 2023","06 Mar 2023","11 Jan 2023","16 Jan 2023","22 Feb 2023","27 Feb 2023"),
  AEENTIM  = c("10:00","18:30","07:00","","12:00","23:00","09:00","20:00","10:00","12:00","08:00","14:00"),
  SEV      = c("Mild","Moderate","Mild","Moderate","Mild","Severe","Moderate","Severe","Moderate","Mild","Moderate","Mild"),
  SER      = c("No","No","No","No","No","Yes","No","Yes","No","No","No","No"),
  REL      = c("Not related","Related","Not related","Not related","Related","Related",
               "Not related","Related","Related","Not related","Related","Not related"),
  OUT      = c("Recovered","Recovered","Recovered","Not recovered","Recovered","Recovering",
               "Recovered","Recovered","Recovered","Recovered","Recovered","Recovered"),
  stringsAsFactors = FALSE
)

ae_raw |>
  select(PATNUM, AETERM_R, AESOC_R, AESTDAT, SEV, SER, OUT) |>
  head()
  PATNUM  AETERM_R                                AESOC_R     AESTDAT      SEV
1    375  HEADACHE               Nervous system disorders 05 Jan 2023     Mild
2    375    Nausea             Gastrointestinal disorders 07 Jan 2023 Moderate
3    375  Insomnia                  Psychiatric disorders 10 Jan 2023     Mild
4    376   Fatigue                      General disorders 12 Feb 2023 Moderate
5    376 MILD RASH Skin and subcutaneous tissue disorders 13 Feb 2023     Mild
6    377 Dizziness               Nervous system disorders 02 Mar 2023   Severe
  SER           OUT
1  No     Recovered
2  No     Recovered
3  No     Recovered
4  No Not recovered
5  No     Recovered
6 Yes    Recovering

Chaque ligne brute est un événement rapporté : un terme verbatim (AETERM_R), un terme préféré et un système d’organes pré-codés (AEPT_R, AESOC_R), une date/heure de début et de fin, et la sévérité, la gravité et l’évolution collectées. La Controlled Terminology fait correspondre chaque valeur collectée à son terme CDISC standard au sein d’un codelist. Une CT d’étude réelle est livrée dans le cadre de la spécification SDTM ; ici, nous définissons le petit ensemble de codelists AE dont nous avons besoin :

# Study controlled terminology: collected value -> CDISC standard term, by codelist
ae_ct <- data.frame(
  codelist_code = c("C66769","C66769","C66769", "C66742","C66742",
                    "C66768","C66768","C66768", "STUDYREL","STUDYREL"),
  term_code     = c("C41338","C41339","C41340", "C49488","C49487",
                    "C49498","C49496","C49494", "R01","R02"),
  term_value    = c("MILD","MODERATE","SEVERE",           # C66769  AESEV severity
                    "Y","N",                              # C66742  no/yes
                    "RECOVERED/RESOLVED","RECOVERING/RESOLVING","NOT RECOVERED/NOT RESOLVED",  # C66768 AEOUT
                    "NOT RELATED","RELATED"),             # STUDYREL  study relationship
  collected_value = c("Mild","Moderate","Severe", "Yes","No",
                      "Recovered","Recovering","Not recovered", "Not related","Related"),
  term_preferred_term = NA_character_, term_synonyms = NA_character_,
  stringsAsFactors = FALSE
)

ae_ct |> select(codelist_code, term_value, collected_value)
   codelist_code                 term_value collected_value
1         C66769                       MILD            Mild
2         C66769                   MODERATE        Moderate
3         C66769                     SEVERE          Severe
4         C66742                          Y             Yes
5         C66742                          N              No
6         C66768         RECOVERED/RESOLVED       Recovered
7         C66768       RECOVERING/RESOLVING      Recovering
8         C66768 NOT RECOVERED/NOT RESOLVED   Not recovered
9       STUDYREL                NOT RELATED     Not related
10      STUDYREL                    RELATED         Related

collected_value est ce que le CRF a enregistré ; term_value est le terme standard CDISC que sdtm.oak substituera. Les codelists de sévérité, de gravité et d’évolution (C66769, C66742, C66768) sont des standards CDISC ; le lien de causalité avec le médicament à l’étude n’a pas de codelist CDISC unique, les études définissent donc le leur — ici STUDYREL. L’argument ct_clst de chaque fonction sensible à la CT pointe vers l’une de ces valeurs codelist_code.

Estampiller la traçabilité des enregistrements avec oak_id_vars

Parce que nous mappons un champ brut à la fois puis recousons les morceaux ensemble, sdtm.oak a besoin d’une clé stable sur chaque ligne brute. generate_oak_id_vars() en ajoute trois : oak_id (le numéro de ligne), raw_source (de quel extrait elle provient) et patient_number.

ae_raw <- ae_raw |>
  generate_oak_id_vars(pat_var = "PATNUM", raw_src = "ae_raw")

ae_raw |>
  select(oak_id, raw_source, patient_number, AETERM_R) |>
  head()
  oak_id raw_source patient_number  AETERM_R
1      1     ae_raw            375  HEADACHE
2      2     ae_raw            375    Nausea
3      3     ae_raw            375  Insomnia
4      4     ae_raw            376   Fatigue
5      5     ae_raw            376 MILD RASH
6      6     ae_raw            377 Dizziness

Ces trois colonnes constituent les oak_id_vars. Chaque mapping de qualificateur ci-dessous passe id_vars = oak_id_vars(), ce qui indique à sdtm.oak de joindre la nouvelle colonne sur le bon enregistrement par cette clé — le mécanisme qui permet aux mappings partiels de se combiner sans dupliquer de lignes.

Mapper la variable topic

Un enregistrement Events commence par son topic — le terme rapporté. Contrairement à un code de test Findings, AETERM est du texte libre ne relevant d’aucune Controlled Terminology, nous copions donc la valeur collectée verbatim avec assign_no_ct() (« assign », parce qu’elle copie une valeur collectée ; « no_ct », parce qu’il n’y a aucun codelist par lequel recoder).

ae <- assign_no_ct(
  raw_dat = ae_raw,
  raw_var = "AETERM_R",
  tgt_var = "AETERM"
)

ae |>
  select(oak_id, patient_number, AETERM) |>
  head()
# A tibble: 6 × 3
  oak_id patient_number AETERM   
   <int> <chr>          <chr>    
1      1 375            HEADACHE 
2      2 375            Nausea   
3      3 375            Insomnia 
4      4 376            Fatigue  
5      5 376            MILD RASH
6      6 377            Dizziness

Une seule colonne SDTM existe pour l’instant, un enregistrement par événement brut. Remarquez que AETERM conserve la casse verbatim (HEADACHE, MILD RASH) exactement telle que le site l’a saisie — le SDTM préserve le terme rapporté inchangé ; la standardisation intervient dans les colonnes codées, juste après.

Mapper les termes codés selon MedDRA

AEDECOD (le terme préféré) et AEBODSYS (la classe de système d’organes) sont la vue codée de l’événement. Ils proviennent de MedDRA (le Medical Dictionary for Regulatory Activities, le standard mondial de codage des événements indésirables — meddra.org) : un codeur médical fait correspondre chaque AETERM verbatim à un PT (preferred term, terme préféré) MedDRA et à sa SOC (system organ class, classe de système d’organes). Ce codage a lieu en amont de ce mapping, de sorte que, dans l’extrait brut, les valeurs codées se trouvent déjà dans AEPT_R et AESOC_R — nous les copions telles quelles avec assign_no_ct() ; sdtm.oak mappe la structure, il ne réalise pas le codage du dictionnaire.

ae <- ae |>
  assign_no_ct(raw_dat = ae_raw, raw_var = "AEPT_R",
               tgt_var = "AEDECOD", id_vars = oak_id_vars()) |>
  assign_no_ct(raw_dat = ae_raw, raw_var = "AESOC_R",
               tgt_var = "AEBODSYS", id_vars = oak_id_vars())

ae |>
  select(patient_number, AETERM, AEDECOD, AEBODSYS) |>
  head()
# A tibble: 6 × 4
  patient_number AETERM    AEDECOD   AEBODSYS                              
  <chr>          <chr>     <chr>     <chr>                                 
1 375            HEADACHE  Headache  Nervous system disorders              
2 375            Nausea    Nausea    Gastrointestinal disorders            
3 375            Insomnia  Insomnia  Psychiatric disorders                 
4 376            Fatigue   Fatigue   General disorders                     
5 376            MILD RASH Rash      Skin and subcutaneous tissue disorders
6 377            Dizziness Dizziness Nervous system disorders              
Note

Le codage MedDRA est une étape distincte, qui ne fait pas partie du mapping. Dériver AEDECOD/AEBODSYS requiert un dictionnaire MedDRA sous licence et un jugement médical, et se fait dans un logiciel de codage dédié avant la programmation SDTM. Cette leçon suppose que le codage est terminé (l’extrait brut porte les colonnes codées) et les mappe dans la structure SDTM — ce qui est exactement la façon dont un vrai domaine AE est construit.

Mapper les qualificateurs de Controlled Terminology

Maintenant, les colonnes qui qualifient chaque événement : à quel point il était sévère, s’il était grave, s’il était lié au médicament à l’étude, et comment il a évolué. Chacune est une valeur collectée recodée via la Controlled Terminology, chacune utilise donc assign_ct() avec le codelist qui la régit :

  • AESEV (sévérité) — codelist C66769 : Mild/Moderate/SevereMILD/MODERATE/SEVERE.
  • AESER (grave) — le codelist no/yes C66742 : No/YesN/Y.
  • AEREL (lien de causalité avec le médicament à l’étude) — le codelist d’étude STUDYREL.
  • AEOUT (évolution) — codelist C66768 : RecoveredRECOVERED/RESOLVED, et ainsi de suite.
ae <- ae |>
  assign_ct(raw_dat = ae_raw, raw_var = "SEV", tgt_var = "AESEV",
            ct_spec = ae_ct, ct_clst = "C66769", id_vars = oak_id_vars()) |>
  assign_ct(raw_dat = ae_raw, raw_var = "SER", tgt_var = "AESER",
            ct_spec = ae_ct, ct_clst = "C66742", id_vars = oak_id_vars()) |>
  assign_ct(raw_dat = ae_raw, raw_var = "REL", tgt_var = "AEREL",
            ct_spec = ae_ct, ct_clst = "STUDYREL", id_vars = oak_id_vars()) |>
  assign_ct(raw_dat = ae_raw, raw_var = "OUT", tgt_var = "AEOUT",
            ct_spec = ae_ct, ct_clst = "C66768", id_vars = oak_id_vars())

ae |>
  select(patient_number, AETERM, AESEV, AESER, AEREL, AEOUT) |>
  head()
# A tibble: 6 × 6
  patient_number AETERM    AESEV    AESER AEREL       AEOUT                     
  <chr>          <chr>     <chr>    <chr> <chr>       <chr>                     
1 375            HEADACHE  MILD     N     NOT RELATED RECOVERED/RESOLVED        
2 375            Nausea    MODERATE N     RELATED     RECOVERED/RESOLVED        
3 375            Insomnia  MILD     N     NOT RELATED RECOVERED/RESOLVED        
4 376            Fatigue   MODERATE N     NOT RELATED NOT RECOVERED/NOT RESOLVED
5 376            MILD RASH MILD     N     RELATED     RECOVERED/RESOLVED        
6 377            Dizziness SEVERE   Y     RELATED     RECOVERING/RESOLVING      

Chaque valeur collectée est désormais un terme standard CDISC : AESEV vaut MILD/MODERATE/SEVERE, AESER vaut N/Y. C’est tout l’intérêt de la Controlled Terminology — un évaluateur, ou un outil de validation de la FDA, lit les mêmes valeurs codées d’une étude à l’autre, quelle que soit la façon dont chaque site les a formulées sur le CRF.

Mapper les variables de temporalité

Un événement a un début et une fin, chacun étant une date/heure au format ISO 8601 (variables --DTC). L’ISO 8601 est le standard de date international (2023-01-05T08:30) qui rend les dates triables et comparables d’une étude à l’autre. La date et l’heure brutes se trouvent dans deux champs, dans un format non standard ; assign_datetime() les analyse et écrit la colonne conforme.

ae <- ae |>
  assign_datetime(
    raw_dat = ae_raw,
    raw_var = c("AESTDAT", "AESTTIM"),
    tgt_var = "AESTDTC",
    raw_fmt = c(list(c("d-m-y", "dd mmm yyyy")), "H:M")
  ) |>
  assign_datetime(
    raw_dat = ae_raw,
    raw_var = c("AEENDAT", "AEENTIM"),
    tgt_var = "AEENDTC",
    raw_fmt = c(list(c("d-m-y", "dd mmm yyyy")), "H:M")
  )

ae |>
  select(patient_number, AETERM, AESTDTC, AEENDTC) |>
  head()
# A tibble: 6 × 4
  patient_number AETERM    AESTDTC          AEENDTC         
  <chr>          <chr>     <iso8601>        <iso8601>       
1 375            HEADACHE  2023-01-05T08:30 2023-01-06T10:00
2 375            Nausea    2023-01-07T14:10 2023-01-07T18:30
3 375            Insomnia  2023-01-10T22:00 2023-01-12T07:00
4 376            Fatigue   2023-02-12T09:00 2023            
5 376            MILD RASH 2023-02-13T20:45 2023-02-20T12:00
6 377            Dizziness 2023-03-02T07:15 2023-03-02T23:00

raw_fmt indique à sdtm.oak comment la date et l’heure brutes ont été écrites (05 Jan 2023, 08:30) ; il renvoie la forme standard 2023-01-05T08:30. Regardez la quatrième ligne : sa date de fin brute était UN UNK 2023 (jour et mois inconnus), et AEENDTC devient simplement 2023 — une date ISO 8601 partielle valide. C’est le comportement correct : le SDTM enregistre la précision qui a réellement été collectée et n’invente jamais de jour. La vignette sur la gestion des dates documente l’ensemble complet des formats pris en charge et des règles relatives aux dates partielles.

Ajouter les identifiants et le numéro de séquence

Un enregistrement conforme a besoin de ses identifiants et d’un numéro de séquence. STUDYID, DOMAIN et USUBJID sont de simples affectations ; derive_seq() affecte AESEQ — un numéro de séquence unique par enregistrement au sein d’un sujet, une exigence SDTM.

ae <- ae |>
  mutate(
    STUDYID = "CDISC01",
    DOMAIN = "AE",
    USUBJID = paste0(STUDYID, "-", patient_number)
  ) |>
  derive_seq(
    tgt_var = "AESEQ",
    rec_vars = c("USUBJID", "AETERM", "AESTDTC")
  ) |>
  select(STUDYID, DOMAIN, USUBJID, AESEQ, AETERM, AEDECOD, AEBODSYS,
         AESEV, AESER, AEREL, AEOUT, AESTDTC, AEENDTC)

head(ae)
# A tibble: 6 × 13
  STUDYID DOMAIN USUBJID   AESEQ AETERM AEDECOD AEBODSYS AESEV AESER AEREL AEOUT
  <chr>   <chr>  <chr>     <int> <chr>  <chr>   <chr>    <chr> <chr> <chr> <chr>
1 CDISC01 AE     CDISC01-…     1 HEADA… Headac… Nervous… MILD  N     NOT … RECO…
2 CDISC01 AE     CDISC01-…     2 Insom… Insomn… Psychia… MILD  N     NOT … RECO…
3 CDISC01 AE     CDISC01-…     3 Nausea Nausea  Gastroi… MODE… N     RELA… RECO…
4 CDISC01 AE     CDISC01-…     1 Fatig… Fatigue General… MODE… N     NOT … NOT …
5 CDISC01 AE     CDISC01-…     2 MILD … Rash    Skin an… MILD  N     RELA… RECO…
6 CDISC01 AE     CDISC01-…     1 Dizzi… Dizzin… Nervous… SEVE… Y     RELA… RECO…
# ℹ 2 more variables: AESTDTC <iso8601>, AEENDTC <iso8601>

Voilà un domaine AE conforme : d’abord les colonnes d’identifiants SDTM, puis le topic (AETERM), les termes codés, les qualificateurs et la temporalité. derive_seq() ordonne les enregistrements par rec_vars et les numérote 1, 2, 3… au sein de chaque sujet, de sorte que AESEQ soit unique par USUBJID — la clé sur laquelle repose chaque jointure en aval. Un rapide contrôle structurel confirme le contrat SDTM :

required <- c("STUDYID", "DOMAIN", "USUBJID", "AESEQ", "AETERM", "AEDECOD")
all(required %in% names(ae))                  # required identifiers + topic present?
[1] TRUE
length(unique(ae$DOMAIN)) == 1                # one domain code?
[1] TRUE
!any(duplicated(ae[c("USUBJID", "AESEQ")]))   # AESEQ unique within subject?
[1] TRUE

Trois TRUE : le domaine est structurellement sain. En production, le contrôle complet est un outil de conformité (Pinnacle 21, ou le package pharmaverse sdtmchecks, présenté dans la leçon sur les signes vitaux) qui exécute les mêmes règles d’intégrité qu’un régulateur. Avec un domaine AE conforme en main, l’étape suivante le lit directement : la construction d’ADAE, le jeu de données d’analyse des événements indésirables, avec admiral ajoute l’indicateur « treatment-emergent » (événement survenu sous traitement) et les indicateurs d’occurrence par-dessus exactement ces colonnes.

🟢 Avec un agent IA

Demandez à Prova « comment ajouter les qualificateurs AEACN (action taken) et AEACNOTH à ce domaine SDTM AE, et quel codelist de Controlled Terminology régit AEACN ? » — elle répond en s’appuyant sur les leçons de ce pilier, avec du code sdtm.oak exécutable que vous pouvez essayer sur les données d’exemple. The runtime is the judge. Demandez à Prova →

Problèmes fréquents

Vous cherchez un code de test et il n’y en a aucun — l’AE n’a pas de --TESTCD. C’est le piège de la classe Events : vous raisonnez en termes de Findings. Un événement indésirable est une occurrence, pas une mesure, donc sa variable topic est le terme rapporté AETERM, mappé avec assign_no_ct() (texte libre, pas de CT) — il n’y a pas de AETESTCD. Si vous vous surprenez à chercher un codelist de codes de test pour l’AE, vous avez la mauvaise classe d’observation en tête ; mappez le terme, pas un test.

Une date partielle ou manquante fait renvoyer à assign_datetime() une chaîne courte comme 2023 — et vous supposez qu’elle a échoué. Ce n’est pas le cas. Lorsque le jour ou le mois brut est inconnu (UN UNK 2023), le résultat ISO 8601 correct est le 2023 tronqué (ou 2023-01) — le SDTM n’enregistre que la précision collectée et ne fabrique jamais de jour. Ne le « corrigez » pas en complétant avec 01 ; une --DTC partielle est conforme, et le jeu de données d’analyse en aval en impute délibérément les dates d’analyse.

assign_ct() avertit qu’une valeur « could not be mapped », et la colonne SDTM conserve le texte brut. La valeur collectée n’est pas dans le codelist que vous avez indiqué — c’est un vrai constat, pas un bug. sdtm.oak laisse passer la valeur brute inchangée et vous le signale, pour que vous corrigiez la terminologie, pas les données. Vérifiez le codelist avec subset(ae_ct, codelist_code == "<code>") et confirmez que sa colonne collected_value contient bien la valeur brute que vous mappez (une cause fréquente est une différence de casse ou d’orthographe, p. ex. "YES" contre "Yes").

Questions fréquentes

Le domaine SDTM AE est le jeu de données CDISC SDTM qui contient les événements indésirables d’une étude — un enregistrement standardisé par événement, dans la classe d’observation Events. Chaque enregistrement porte le terme rapporté AETERM, les AEDECOD et AEBODSYS codés selon MedDRA, des qualificateurs de Controlled Terminology (AESEV, AESER, AEREL, AEOUT), des dates de début/fin ISO 8601 (AESTDTC, AEENDTC), et des identifiants (USUBJID, AESEQ). C’est un domaine obligatoire dans une soumission réglementaire et la source du jeu de données d’analyse ADAE.

Findings enregistre une mesure (une valeur d’analyse biologique, une pression artérielle) — sa variable topic est un code de test (--TESTCD), et chaque enregistrement répond à « qu’a-t-on mesuré, et quel était le résultat ». Events enregistre quelque chose qui est arrivé (un événement indésirable, un antécédent médical) — sa variable topic est un terme (--TERM, p. ex. AETERM), avec un enregistrement par événement. Les mêmes algorithmes sdtm.oak, une forme d’enregistrement différente : pour l’AE, vous mappez un terme rapporté, pas un code de test accompagné d’un résultat.

AETERM est le terme rapporté verbatim — l’événement indésirable exactement tel que l’investigateur l’a écrit sur le CRF (« MILD RASH »). AEDECOD est le terme préféré (PT) MedDRA vers lequel il a été codé (« Rash ») — le terme de dictionnaire standardisé qui permet de regrouper et de compter les événements de façon cohérente entre sites et études. AETERM est copié tel quel avec assign_no_ct() ; AEDECOD (et la classe de système d’organes AEBODSYS) proviennent du codage MedDRA réalisé avant la programmation SDTM.

Non. sdtm.oak mappe la structure — il déplace les AEDECOD/AEBODSYS déjà codés de l’extrait brut dans les colonnes SDTM. Le codage MedDRA lui-même (attribuer un terme préféré et une classe de système d’organes à chaque terme verbatim) nécessite un dictionnaire MedDRA sous licence et un jugement médical, et se fait dans un logiciel de codage dédié en amont. Au moment où vous mappez vers le SDTM, les colonnes codées existent déjà dans les données brutes.

Utilisez assign_no_ct() pour les valeurs copiées telles quelles (le AETERM en texte libre, et les AEDECOD/ AEBODSYS pré-codés) ; assign_ct() pour les valeurs collectées recodées via un codelist (AESEV, AESER, AEREL, AEOUT) ; assign_datetime() pour les dates ISO 8601 (AESTDTC, AEENDTC) ; et derive_seq() pour AESEQ. generate_oak_id_vars() estampille d’abord la clé de traçabilité sur laquelle chaque mapping se rejoint. La vignette sur les algorithmes en dresse la liste complète.

Testez vos connaissances

L’extrait brut ne porte pas encore l’action entreprise vis-à-vis du médicament à l’étude, mais supposons qu’il le fasse, dans un champ ACN collecté comme Dose Not Changed / Drug Withdrawn / Dose Reduced. Esquissez le mapping du qualificateur SDTM AEACN : quelle fonction sdtm.oak utiliseriez-vous, et à quoi ressemblerait l’étape de Controlled Terminology ?

AEACN est une valeur collectée qui doit être recodée vers des termes standard CDISC — c’est donc le même schéma que AESEV/AEOUT. Il vous faut la fonction qui recode via un codelist, plus une entrée de codelist faisant correspondre chaque valeur collectée à son term_value.

Utilisez assign_ct(), avec une entrée dans la CT de l’étude (le codelist CDISC C66767 régit AEACN) qui fait correspondre chaque valeur collectée à son terme standard :

# add C66767 rows to ae_ct: collected -> standard
#   "Dose Not Changed" -> "DOSE NOT CHANGED"
#   "Drug Withdrawn"   -> "DRUG WITHDRAWN"
#   "Dose Reduced"     -> "DOSE REDUCED"
ae <- ae |>
  assign_ct(raw_dat = ae_raw, raw_var = "ACN", tgt_var = "AEACN",
            ct_spec = ae_ct, ct_clst = "C66767", id_vars = oak_id_vars())

C’est la chaîne d’algorithmes identique à celle des autres qualificateurs, avec le champ brut, la variable cible et le codelist échangés — ce qui explique précisément pourquoi les algorithmes de sdtm.oak sont réutilisables : chaque qualificateur CT se mappe de la même façon.

A. Parce que l’AE est un domaine Findings et que AETERM est son code de test B. Parce que l’AE est un domaine Events — il enregistre des occurrences, indexées par le terme rapporté C. Parce que sdtm.oak exige que chaque domaine utilise une variable topic --TERM

B. L’AE appartient à la classe d’observation Events, qui enregistre des choses qui sont arrivées. Sa variable topic est donc le terme rapporté AETERM (« ce qui est arrivé »), avec un enregistrement par événement — contrairement à la classe Findings (signes vitaux, analyses biologiques), dont le topic est un code de test --TESTCD (« ce qui a été mesuré »). A se trompe de classe (l’AE est Events, pas Findings), et C est faux — la variable topic dépend de la classe d’observation, pas d’une règle du package.

Conclusion

Mapper le domaine AE est la même boucle champ par champ que pour n’importe quel domaine SDTM, appliquée à la classe Events
remplir la bonne colonne SDTM à partir du bon champ brut avec le bon algorithme. La variable topic AETERM nomme chaque événement (copiée verbatim avec assign_no_ct()) ; les AEDECOD/AEBODSYS codés selon MedDRA sont copiés depuis le codage en amont ; assign_ct() recode les qualificateurs de sévérité, de gravité, de lien de causalité et d’évolution via la Controlled Terminology ; assign_datetime() construit le début et la fin ISO 8601 — en respectant les dates partielles ; et derive_seq() numérote les événements. Maîtrisez la boucle Events ici et tout domaine de type événement — antécédents médicaux, disposition — est la même boucle avec des champs différents. Avec un SDTM AE conforme en main, l’étape suivante est la couche d’analyse
la construction du jeu de données ADAE des événements indésirables avec admiral.
Note

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

Cette page vous a-t-elle été utile ?

Recevez les nouvelles leçons R & Python par e-mail

Pratique, reproductible, sans spam. Désinscription à tout moment.

Double opt-in. Nous ne partageons jamais votre e-mail.

Partager cette pageXLinkedInRedditHN

Réutilisation

Citation

BibTeX
@online{2026,
  author = {},
  title = {Domaine SDTM AE en R avec sdtm.oak : événements indésirables},
  date = {2026-07-01},
  url = {https://www.datanovia.com/learn/pharma-clinical/02-sdtm-programming/sdtm-ae-adverse-events-domain},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“Domaine SDTM AE en R avec sdtm.oak : événements indésirables.” 2026. July 1. https://www.datanovia.com/learn/pharma-clinical/02-sdtm-programming/sdtm-ae-adverse-events-domain.