
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.
- 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é ; lesAEDECODetAEBODSYScodé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.AESEQest 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é :
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
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é) — codelistC66769:Mild/Moderate/Severe→MILD/MODERATE/SEVERE.AESER(grave) — le codelist no/yesC66742:No/Yes→N/Y.AEREL(lien de causalité avec le médicament à l’étude) — le codelist d’étudeSTUDYREL.AEOUT(évolution) — codelistC66768:Recovered→RECOVERED/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.
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
AETERMnomme chaque événement (copiée verbatim avecassign_no_ct()) ; lesAEDECOD/AEBODSYScodé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 ; etderive_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.
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
- Mapper des données EDC brutes vers un domaine SDTM Findings avec sdtm.oak — la contrepartie « signes vitaux » de cette leçon ; les mêmes algorithmes oak sur la classe Findings, avec un contrôle de conformité
sdtmchecks. · Le flux de données des essais cliniques : du CRF au SDTM, ADaM, TLF — où se place le SDTM dans le pipeline que cette leçon alimente. · Les standards CDISC : CDASH, SDTM, ADaM, Define-XML — comment le SDTM se rapporte aux standards qui l’entourent. · Créer ADAE en R avec admiral — l’étape suivante, la dérivation du jeu de données d’analyse des événements indésirables à partir de ce domaine SDTM AE. - Où cela s’inscrit : les fondations réglementaires et CDISC → le mapping des événements indésirables bruts vers le domaine SDTM AE (vous êtes ici) → la construction d’ADAE avec admiral → les tables de sécurité que livre chaque soumission. Le SDTM AE est la source standardisée que lit chaque résumé d’événements indésirables en aval.
Réutilisation
Citation
@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}
}