Contrôles de conformité SDTM en R : Controlled Terminology et sdtmchecks à grande échelle

Prouvez qu’une étude SDTM mappée est prête pour la soumission en R — vérifiez les valeurs contre la Controlled Terminology CDISC, exécutez la batterie de conformité sdtmchecks sur chaque domaine et lisez les constats, et voyez comment les métadonnées Define-XML (via metacore et metatools) décrivent les jeux de données qu’un évaluateur ouvre en premier

Une fois les domaines SDTM mappés, la tâche suivante est de prouver qu’ils se conforment au standard CDISC avant la soumission. Cette leçon le fait en R : ce qu’est la Controlled Terminology CDISC (les codelists NCI EVS, extensible vs non extensible, valeur de soumission vs décode) et un contrôle de valeur exécutable ; la conformité automatisée à grande échelle avec le package pharmaverse sdtmchecks — un contrôle unique et son contrat de retour PASS / FAIL, puis run_all_checks() sur toute une étude et son résumé des constats ; et le lien Define-XML, contenir et vérifier ces métadonnées lisibles par machine avec metacore et metatools. Elle trace aussi la répartition précise des tâches entre les contrôles de règles sdtmchecks, la conformité complète à la Controlled Terminology (Pinnacle 21), les contrôles spec-versus-données (metatools), et les métadonnées Define-XML elles-mêmes.

Date de publication

1 juillet 2026

Modifié

7 juillet 2026

AstucePoints clés
  • La conformité est une étape distincte du mapping — et elle n’est pas optionnelle. Un domaine mappé peut être structurellement correct et échouer quand même à la soumission : une valeur hors codelist, un enregistrement en double, une date impossible. Vous validez avant de livrer.
  • La Controlled Terminology (CT) est un vocabulaire fixe, pas du texte libre. CDISC (Clinical Data Interchange Standards Consortium) publie un codelist pour chaque variable régie (SEX, RACE, AESEV…) ; chaque terme a une valeur de soumission et un décode, et un codelist est soit extensible (vous pouvez ajouter des termes), soit non extensible (figé). Les valeurs sont maintenues par NCI EVS.
  • sdtmchecks exécute une batterie de règles d’intégrité des données, en un seul appel sur toute l’étude. Un contrôle unique renvoie TRUE (réussi) ou FALSE porteur d’un message et d’un data frame des lignes fautives ; run_all_checks() exécute toute la batterie et vous renvoie un résumé des constats que vous triez.
  • sdtmchecks n’est pas un validateur CT complet — connaissez la répartition des tâches. La conformité complète Controlled Terminology + Define-XML relève de Pinnacle 21 ; les contrôles CT spec-versus-données en R relèvent de metatools face à un objet metacore ; les métadonnées elles-mêmes, c’est Define-XML. Chaque outil a un rôle ; se tromper d’outil laisse des trous.
  • Define-XML est la table des matières de l’évaluateur, et metacore la lit. metacore parse un fichier Define-XML en un objet R que vous pouvez interroger pour ses codelists et ses specs — il n’écrit pas le fichier Define-XML. Le sens compte.

Introduction

Vous avez mappé l’étude. Le domaine démographie DM est à une ligne par sujet, les signes vitaux, les événements indésirables et l’exposition au médicament portent chacun leurs observations, chaque variable est dans la bonne colonne. Cela a l’air terminé. Ça ne l’est pas — car paraître conforme et être conforme sont deux affirmations différentes, et un évaluateur réglementaire vérifie la seconde avec un logiciel. Un sexe codé "Male" au lieu de "M", un événement indésirable saisi deux fois, un résultat de laboratoire sans unités, un jour d’étude compté depuis une date de référence manquante : rien de tout cela ne casse la forme de la table, et tout cela échoue à la conformité. Cette leçon est l’étape entre « mappé » et « prêt pour la soumission » — vous validez les domaines SDTM (Study Data Tabulation Model) que vous avez construits.

Trois choses font ce travail, et cette leçon les prend dans l’ordre. D’abord, la Controlled Terminology (CT) — les vocabulaires publiés par CDISC qui fixent les valeurs autorisées — et un contrôle exécutable vérifiant qu’une colonne ne contient que des termes permis. Ensuite, la conformité automatisée à grande échelle avec sdtmchecks, le package pharmaverse qui exécute une batterie de règles d’intégrité des données sur chaque domaine d’un coup et vous renvoie ses constats. Enfin, les métadonnées Define-XML qui décrivent les jeux de données à un évaluateur, et comment metacore et metatools contiennent et vérifient cette spec en R. Tout au long, une chose à bien saisir dès le départ : aucun outil ne fait tout, alors nous traçons la répartition des tâches exacte — quel outil attrape quelle classe de problème.

Voici où atterrit une vraie exécution d’étude — les constats issus du passage de la batterie de conformité automatisée sur six domaines mappés, une barre par contrôle ayant signalé des enregistrements :

Horizontal lollipop chart titled 'One conformance run over six SDTM domains', showing the eight sdtmchecks checks that flagged records, ordered by count. From largest to smallest: check_lb_lbstresu with 2915 flagged records (lab results with missing units, by far the largest), check_ae_dup with 460 (duplicate adverse-event rows), check_ae_aeout_aeendtc_nonfatal with 250, check_dm_usubjid_ae_usubjid with 81, check_lb_lbstnrlo_lbstnrhi with 72, check_vs_height with 52, check_dm_actarm_arm with 12, and check_lb_lbstresc_char with 6. Each check is a labelled azure dot at the end of a stem from zero. The visual point is that most of the run passes silently and a small set of checks flags records that need a programmer's eye — one issue (missing lab units) dominating the triage.

Chaque barre est un vrai constat issu des données d’exemple du pharmaverse — un lot d’enregistrements qu’un programmeur doit examiner avant que cette étude ne soit livrée. À la fin de la leçon, vous aurez produit ce résumé vous-même, compris ce que fait chaque couche de vérification, et su vers quel outil vous tourner. Vous découvrez la place du SDTM ? Commencez par le flux de données d’un essai clinique et le modèle de tabulation SDTM.

Ce qu’est la Controlled Terminology CDISC

La Controlled Terminology (CT) est l’ensemble des vocabulaires fixes et publiés dans lesquels les variables SDTM doivent puiser leurs valeurs. Là où une variable est régie par la CT, sa valeur n’est pas du texte libre — elle doit appartenir à une liste définie. CDISC définit la CT comme « l’ensemble des codelists et des valeurs valides utilisés avec les items de données au sein des jeux de données définis par CDISC » (CDISC Controlled Terminology). Un codelist est une telle liste — pour une variable ou une classe de réponses. Chaque entrée d’un codelist a deux parties : la valeur de soumission (le code qui va dans le jeu de données, p. ex. F) et le décode (sa signification lisible, p. ex. Female). Ainsi la variable SEX est régie par le codelist Sex, dont les valeurs de soumission sont F, M, U, UNDIFFERENTIATED — et "Female" n’en fait pas partie.

Deux propriétés d’un codelist comptent en pratique :

  • Extensible vs non extensible. Un codelist non extensible est figé — vous ne pouvez utiliser que ses termes publiés (Sex est non extensible ; vous ne pouvez pas inventer un nouveau code de sexe). Un codelist extensible permet à un promoteur d’ajouter des termes propres à l’étude via une New Term Request formelle lorsque l’ensemble publié ne couvre pas une valeur réelle.
  • Qui le maintient. La CT CDISC est développée et distribuée par NCI EVS — les Enterprise Vocabulary Services du National Cancer Institute, qui font partie du NCI Thesaurus. Elle est gratuite, ne nécessite aucune licence, et est mise à jour trimestriellement (terminologie CDISC de NCI EVS). Chaque codelist a un identifiant stable (le codelist Sex est C66731), et chaque version est datée — de sorte qu’une soumission indique toujours quelle version de CT elle respecte (téléchargements de terminologie SDTM de NCI EVS).

La question de conformité la plus simple que vous puissiez poser est : cette colonne ne contient-elle que des termes permis ? Vous pouvez y répondre en base R en comparant les valeurs d’une variable au codelist. Ici, nous vérifions SEX sur le domaine DM mappé. Le vecteur de codelist ci-dessous est illustratif — un petit substitut du véritable codelist NCI versionné C66731, que vous récupéreriez depuis le téléchargement NCI EVS plutôt que de le coder en dur :

library(pharmaversesdtm)
data(dm)

# ILLUSTRATIVE stand-in for the NCI Sex codelist C66731 — pull the authoritative,
# versioned list from NCI EVS rather than hardcoding it in production
sex_codelist <- c("F", "M", "U", "UNDIFFERENTIATED")

dm$SEX_CT_FLAG <- ifelse(dm$SEX %in% sex_codelist, "OK", "NOT IN CODELIST")
table(dm$SEX, dm$SEX_CT_FLAG)
   
     OK
  F 179
  M 127

Chaque valeur est OK — le SEX mappé ne contient que F et M, tous deux dans le codelist. C’est le contrôle qui réussit. Regardez-le échouer : supposons qu’un enregistrement soit passé sans re-codage, portant encore la valeur collectée "Male" au lieu de M.

library(pharmaversesdtm)
data(dm)
sex_codelist <- c("F", "M", "U", "UNDIFFERENTIATED")

dm$SEX[5] <- "Male"                                        # an un-recoded value sneaks in
offending <- unique(dm$SEX[!(dm$SEX %in% sex_codelist)])
offending
[1] "Male"

Le contrôle renvoie désormais "Male" — une valeur absente du codelist, signalée en une ligne. C’est là l’essence de la conformité CT : pour chaque variable régie, les valeurs des données doivent être un sous-ensemble des valeurs de soumission du codelist. Le faire correctement — pour chaque colonne régie par la CT, contre le bon codelist versionné — est plus lourd qu’un seul %in%, ce qui est précisément pourquoi le reste de cette leçon porte sur les outils qui l’automatisent.

Conformité automatisée à grande échelle avec sdtmchecks

Vérifier une colonne à la main ne passe pas à l’échelle de cinquante domaines et de centaines de règles. sdtmchecks empaquette une batterie de règles de conformité sélectionnées et actionnables — enregistrements en double, violations de logique de dates, incohérences de clés inter-domaines, valeurs requises manquantes — sous forme d’un ensemble de fonctions check_* que vous exécutez sur vos données mappées (documentation sdtmchecks). Chaque contrôle encode une vraie question d’intégrité des données qu’un évaluateur poserait.

Un contrôle unique et son contrat de retour

Commencez par un seul contrôle pour que le contrat de retour soit sans équivoque. Chaque fonction check_* prend le ou les domaines dont elle a besoin et renvoie l’une de deux choses. Un résultat propre est un simple TRUE :

library(sdtmchecks)
library(pharmaversesdtm)
data(dm)

check_dm_usubjid_dup(DM = dm)      # any duplicated USUBJID in DM?
[1] TRUE
check_dm_age_missing(DM = dm)      # any missing AGE in DM?
[1] TRUE

Deux TRUE : DM n’a pas d’identifiants de sujet en double ni d’âges manquants. Quand un contrôle échoue, il renvoie FALSE — mais un FALSE nu serait inutile, alors la fonction lui attache deux choses : un message lisible décrivant le problème, et un data frame des lignes exactes qui posent problème. Voici un contrôle qui trouve quelque chose, sur le domaine des événements indésirables :

library(sdtmchecks)
library(pharmaversesdtm)
data(ae)

result <- check_ae_dup(AE = ae)    # duplicated adverse-event records?
result[1]                          # the verdict: FALSE = a problem was found
[1] FALSE
attr(result, "msg")                # the message: what went wrong
[1] "AE has duplicated entries. "
head(attr(result, "data"), 3)      # the offending rows, ready to hand back to data management
# A tibble: 3 × 6
# Groups:   USUBJID, AETERM, AEDECOD, AESTDTC, AEENDTC, AESEV [2]
  USUBJID     AETERM        AEDECOD       AESTDTC    AEENDTC    AESEV   
  <chr>       <chr>         <chr>         <chr>      <chr>      <chr>   
1 01-701-1023 ERYTHEMA      ERYTHEMA      2012-08-07 2012-08-30 MILD    
2 01-701-1023 ERYTHEMA      ERYTHEMA      2012-08-07 2012-08-30 MILD    
3 01-701-1047 HIATUS HERNIA HIATUS HERNIA 2013-02-12 2013-02-12 MODERATE

C’est tout le contrat : isTRUE(result) vous dit réussi ou échoué ; attr(result, "msg") vous dit quoi ; attr(result, "data") vous donne les lignes, prêtes à exporter vers une requête de data management. Vous ne devinez jamais — le contrôle vous remet les preuves.

Une nuance décide si vous lisez le message ou non : un FALSE ne signifie pas toujours une violation. Il peut aussi signifier que le contrôle n’a pas pu s’exécuter — une variable requise manquait dans le domaine — et le message est la façon de distinguer les deux :

library(sdtmchecks)
library(pharmaversesdtm)
data(vs)

result <- check_vs_sbp_lt_dbp(VS = vs)   # systolic < diastolic blood pressure?
result[1]
[1] FALSE
attr(result, "msg")
[1] "VS is missing the variable: VSSPID"

Le verdict est FALSE, mais le message indique « VS is missing the variable: VSSPID » — ce n’est pas un constat de mauvaises pressions artérielles, c’est le contrôle qui signale que l’exemple VS du pharmaverse manque d’une variable dont il a besoin, donc il n’a pas pu évaluer la règle. Lisez toujours le message avant de traiter un FALSE comme une violation. Un FALSE que vous ne pouvez pas expliquer est souvent un « impossible à exécuter », pas un problème de données.

Toute l’étude en un seul appel

Exécuter toute la batterie à la main irait à l’encontre du but. run_all_checks() exécute toute la batterie en un seul appel. Il est piloté par les métadonnées : il lit sdtmchecksmeta (la table de chaque contrôle, sa priorité et les domaines dont il a besoin) et, surtout, découvre vos jeux de données dans l’environnement global par leur nom. Vous chargez donc d’abord les domaines — comme de simples objets nommés ae, dm, ex, et ainsi de suite — et le runner les trouve ; tout domaine que vous n’avez pas chargé est simplement rapporté comme introuvable et ignoré.

library(sdtmchecks)
library(pharmaversesdtm)

# load the domains into the global environment; run_all_checks finds them by name
data(ae); data(dm); data(ex); data(vs); data(ds); data(lb)
res <- run_all_checks(priority = c("High", "Medium"), verbose = FALSE)
# each element is one check's result; nrec = flagged records (NA or 0 = clean)
get1 <- function(x, f) { v <- x[[f]]; if (is.null(v) || length(v) == 0) NA else v[1] }
summ <- do.call(rbind, lapply(names(res), function(nm) {
  x <- res[[nm]]
  nr <- get1(x, "nrec")
  status <- if (is.na(nr)) "NOT RUN" else if (nr == 0) "PASS" else "FLAG"
  data.frame(check = nm, nrec = nr, status = status,
             msg = ifelse(is.na(get1(x, "msg")), "", as.character(get1(x, "msg"))),
             stringsAsFactors = FALSE)
}))

# the triage: how did the battery come out, and what flagged?
table(summ$status)

FLAG PASS 
   8  101 

La plupart des contrôles réussissent silencieusement ; l’intérêt de l’exécution est la courte liste de ceux qui échouent. Sortez les contrôles signalés, du plus grand au plus petit, avec le message qui explique pourquoi :

library(sdtmchecks)
library(pharmaversesdtm)
data(ae); data(dm); data(ex); data(vs); data(ds); data(lb)
res <- run_all_checks(priority = c("High", "Medium"), verbose = FALSE)
get1 <- function(x, f) { v <- x[[f]]; if (is.null(v) || length(v) == 0) NA else v[1] }

flagged <- do.call(rbind, lapply(names(res), function(nm) {
  n <- get1(res[[nm]], "nrec")
  if (is.na(n) || n == 0) return(NULL)
  data.frame(check = nm, flagged = n,
             message = as.character(get1(res[[nm]], "msg")), stringsAsFactors = FALSE)
}))
flagged <- flagged[order(-flagged$flagged), ]
print(flagged[, c("check", "flagged")], row.names = FALSE)
                           check flagged
               check_lb_lbstresu    2915
                    check_ae_dup     460
 check_ae_aeout_aeendtc_nonfatal     250
     check_dm_usubjid_ae_usubjid      81
      check_lb_lbstnrlo_lbstnrhi      72
                 check_vs_height      52
             check_dm_actarm_arm      12
          check_lb_lbstresc_char       6

Huit contrôles signalent des enregistrements — les mêmes huit que le graphique héros a dessinés. Lisez le haut de la liste comme un tri : check_lb_lbstresu signale 2915 résultats de laboratoire aux unités manquantes (de loin le plus gros problème), check_ae_dup trouve 460 lignes d’événements indésirables en double, check_dm_usubjid_ae_usubjid rapporte 81 sujets dans DM sans événement indésirable, et check_dm_actarm_arm signale les 12 sujets dont le bras réel diffère de leur bras prévu. Chacun est une vraie question pour l’équipe d’étude — certains sont des erreurs de données à requêter, d’autres (comme bras réel ≠ bras prévu) sont attendus et n’ont besoin que d’être confirmés. Ce tri — pas un tampon réussi/échoué — est la sortie d’une exécution de conformité. Pour remettre l’ensemble des constats à un évaluateur, report_to_xlsx(res, outfile = "conformance.xlsx") écrit l’ensemble complet des résultats, une feuille de calcul par contrôle, sous forme d’un rapport Excel mis en forme.

Ce que sdtmchecks ne fait pas. Il exécute des règles d’intégrité des données sélectionnées — ce n’est pas un validateur de Controlled Terminology complet, et il ne valide pas Define-XML. Pour la conformité complète des codelists CT et le jeu de règles SDTMIG (SDTM Implementation Guide) qu’exécute un régulateur, l’outil standard de l’industrie est Pinnacle 21 (pinnacle21.com), que la FDA et la PMDA (l’agence japonaise des produits pharmaceutiques et dispositifs médicaux) citent dans leurs guides de conformité technique. Voyez sdtmchecks comme la première ligne rapide, scriptable et en R qui attrape tôt les problèmes courants et à forte valeur — pas le contrôle de soumission final.

Define-XML, metacore et metatools

Les contrôles automatisés attrapent les mauvaises données. Une soumission expédie aussi ses métadonnées — la description lisible par machine de chaque jeu de données, variable, codelist et dérivation — sous forme d’un fichier Define-XML. Define-XML est la table des matières de l’évaluateur : le premier fichier qu’un évaluateur de la FDA ou de la PMDA ouvre, parce qu’il lui dit ce qu’est chaque jeu de données et variable avant de regarder le moindre enregistrement. La version actuelle est v2.1 (CDISC Define-XML), et elle relie les standards à vos jeux de données et la Controlled Terminology à vos codelists — exactement les relations qu’un outil de conformité complet valide.

En R, la façon pharmaverse de contenir ces métadonnées est un objet metacore. define_to_metacore() parse un fichier Define-XML en un objet R immuable avec des tables tidy pour les specs de jeux de données, les specs de variables et les codelists — que vous pouvez ensuite interroger. Ici nous lisons le Define-XML pilote fourni avec le package et en tirons directement le codelist SEX :

library(metacore)

define_path <- metacore_example("SDTM_define.xml")     # a real pilot Define-XML ships with metacore
mc <- define_to_metacore(define_path, verbose = "silent")

get_control_term(mc, SEX)      # the SEX codelist, read from the Define-XML
# A tibble: 3 × 2
  code  decode 
  <chr> <chr>  
1 F     Female 
2 M     Male   
3 U     Unknown

Le codelist revient sous forme d’une table tidy de code et decodeF/Female, M/Male, U/Unknown — lue directement depuis les métadonnées propres à l’étude. Notez le sens : define_to_metacore() va Define-XML → metacore. metacore lit et décrit les métadonnées ; il n’écrit pas le fichier Define-XML (ce fichier est généré par d’autres outils comme Pinnacle 21 ou des générateurs Define-XML dédiés). Prendre cela à l’envers est une idée fausse courante — metacore est le conteneur que vous interrogez, pas un auteur de Define-XML.

La spec en main, metatools vérifie vos données face à elle. check_ct_col() vérifie que les valeurs d’une colonne se conforment au codelist contenu dans la spec metacore, et get_bad_ct() renvoie les valeurs fautives. C’est le contrôle CT piloté par la spec que le %in% en base R plus haut ne faisait qu’esquisser — désormais le codelist provient des vraies métadonnées de l’étude, pas d’un vecteur codé en dur :

library(metacore)
library(metatools)
library(haven)

mc <- define_to_metacore(metacore_example("SDTM_define.xml"), verbose = "silent")
dm_spec <- select_dataset(mc, "DM")                    # narrow the spec to DM
dm_data <- read_xpt(metatools_example("dm.xpt"))       # a pilot DM dataset ships with metatools

# a conformant column passes through unchanged (no error, no warning)
ok <- check_ct_col(dm_data, dm_spec, SEX)
identical(ok$SEX, dm_data$SEX)                          # TRUE — passthrough on a clean column
[1] TRUE
# now inject an out-of-codelist value and find it
dm_bad <- dm_data
dm_bad$SEX[1:3] <- "X"
get_bad_ct(dm_bad, dm_spec, SEX)                        # the offending value(s), read against the spec
[1] "X"

check_ct_col() est un pass-through : sur une colonne propre elle renvoie les données inchangées (donc elle s’insère dans un pipeline de mapping), et sur une violation elle émet un avertissement plutôt que de s’arrêter — elle signale, elle n’interrompt pas. get_bad_ct() renvoie les valeurs fautives ("X"), lues face au codelist que le Define-XML déclare réellement. C’est là la différence avec le contrôle codé en dur plus haut : la source de vérité est les métadonnées propres à l’étude.

Quel outil fait quoi — la répartition des tâches

Quatre outils, quatre rôles. Se tromper d’outil laisse un trou, alors gardez la séparation claire :

Couche Ce qu’elle vérifie Outil
Conformité complète codelist CT + Define-XML Chaque valeur de Controlled Terminology contre les codelists NCI/CDISC officiels versionnés, plus le jeu de règles SDTMIG — le contrôle de niveau soumission que la FDA/PMDA citent Pinnacle 21
Règles d’intégrité des données sélectionnées (en R) ~109 contrôles actionnables : doublons, logique de dates, clés inter-domaines, valeurs requises manquantes — pas un validateur CT complet sdtmchecks
Contrôle CT spec-versus-données (en R) Les valeurs d’une colonne contre le codelist contenu dans la spec de l’étude (check_ct_col / get_bad_ct) metatools + metacore
Les métadonnées elles-mêmes La description lisible par machine des jeux de données, variables, codelists, dérivations — la table des matières de l’évaluateur Define-XML (lu dans metacore)

Le workflow pratique les utilise ensemble : sdtmchecks pour un premier passage rapide et scriptable sur vos données mappées ; metatools face à une spec metacore pour confirmer que les données correspondent aux métadonnées déclarées de l’étude ; et Pinnacle 21 comme rapport de conformité complet — codelists CT et règles SDTMIG — qui accompagne la soumission. sdtmchecks et metatools rendent les problèmes visibles tôt, en R, là où vous les corrigez ; Pinnacle 21 est le contrôle à la fin. La même spec metacore, d’ailleurs, pilote la construction ADaM pilotée par les métadonnées en aval — un seul objet de métadonnées, réutilisé de la conformité SDTM à la construction des jeux de données d’analyse.

🟢 Avec un agent IA

Demandez à Prova « J’ai lancé run_all_checks() et check_ae_dup a signalé 460 lignes en double — comment sortir les USUBJID fautifs dans un listing de requêtes de data management, et lesquels des huit contrôles signalés sont de vraies erreurs plutôt que des constats attendus comme bras réel ≠ bras prévu ? » — 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 d’exemple du pharmaverse. The runtime is the judge. Demandez à Prova →

Problèmes fréquents

Un FALSE qui est en réalité un « impossible à exécuter », pas une violation. Un contrôle sdtmchecks renvoie FALSE à la fois quand il trouve un problème et quand une variable requise manque, de sorte qu’il n’a pas pu évaluer — comme check_vs_sbp_lt_dbp() l’a fait plus haut avec « VS is missing the variable: VSSPID ». Si vous traitez chaque FALSE comme une erreur de données, vous poursuivrez des constats qui sont en réalité « fournissez la variable manquante ». Lisez toujours attr(result, "msg") : un message nommant une variable manquante est un « impossible à exécuter » ; un message décrivant de mauvaises données est un vrai constat.

Un contrôle inter-domaines revient vide parce que vous ne lui avez donné qu’un domaine. Certains contrôles comparent entre domaines — check_dm_usubjid_ae_usubjid() réconcilie les sujets entre DM et AE, et a besoin des deux. Appelé avec un seul, il ne peut pas faire son travail. Quand vous exécutez un seul contrôle inter-domaines à la main, passez tous les domaines de sa signature ; run_all_checks() s’en charge pour vous en découvrant chaque domaine nécessaire depuis l’environnement global par leur nom.

run_all_checks() rapporte « domain not found » et ignore silencieusement des contrôles. Le runner trouve les jeux de données par leur nom d’objet dans l’environnement global — il ne prend pas une liste de data frames. Si vous chargez vos domaines dans une liste nommée, ou oubliez d’en charger un, ses contrôles sont ignorés plutôt qu’exécutés, et un résumé d’apparence propre cache des contrôles non exécutés. Chargez chaque domaine comme un simple objet de premier niveau (data(ae), ae <- read_xpt(...)) nommé exactement comme les métadonnées l’attendent, et confirmez que le nombre d’exécutions correspond aux contrôles que vous vouliez.

Questions fréquentes

La conformité SDTM est le degré auquel un jeu de données SDTM mappé suit le standard CDISC : structure et variables de domaine correctes, valeurs tirées des codelists de Controlled Terminology publiés, logique de dates et clés valides, et métadonnées qui correspondent aux données (le Define-XML). Elle se vérifie après le mapping, avec un logiciel — un domaine structurellement correct peut quand même échouer à la conformité sur une valeur hors codelist, un enregistrement en double, ou une variable requise manquante. Une soumission expédie un rapport de conformité (typiquement issu de Pinnacle 21) aux côtés des jeux de données.

sdtmchecks est un package R open-source qui exécute une batterie de règles d’intégrité des données sélectionnées (doublons, logique de dates, clés inter-domaines, valeurs manquantes) — un premier passage rapide et scriptable que vous exécutez en R pendant que vous corrigez encore les données. Pinnacle 21 est l’outil de conformité standard de l’industrie qui valide le jeu complet de codelists de Controlled Terminology et les règles SDTMIG, et c’est le rapport que la FDA et la PMDA citent ; c’est le contrôle de soumission. Ils sont complémentaires : sdtmchecks attrape les problèmes courants tôt en R, Pinnacle 21 est le contrôle de conformité complet à la fin. sdtmchecks n’est pas un validateur CT complet et ne remplace pas Pinnacle 21.

La Controlled Terminology (CT) CDISC est l’ensemble des codelists publiés — des listes fixes de valeurs valides — dans lesquelles les variables SDTM (et CDASH, ADaM) régies doivent puiser. Chaque entrée de codelist a une valeur de soumission (le code stocké dans les données, p. ex. M) et un décode (sa signification, p. ex. Male) ; un codelist est non extensible (figé, comme Sex) ou extensible (un promoteur peut ajouter des termes via une New Term Request). La CT CDISC est maintenue et distribuée par NCI EVS (National Cancer Institute Enterprise Vocabulary Services), gratuite et mise à jour trimestriellement. Voir la page CDISC Controlled Terminology.

Define-XML est un fichier standard CDISC, lisible par machine, qui décrit les métadonnées d’une soumission — chaque jeu de données, variable, codelist de Controlled Terminology, et dérivation. C’est le premier fichier qu’un évaluateur réglementaire ouvre (la « table des matières » des données), et il est exigé par la FDA et la PMDA pour chaque étude. La version actuelle est v2.1. En R, vous pouvez lire un fichier Define-XML dans un objet metacore avec define_to_metacore() et interroger ses specs et codelists ; voir CDISC Define-XML.

Non. metacore lit un fichier Define-XML vers un objet R — define_to_metacore() parse le XML en tables tidy de specs de jeux de données, specs de variables et codelists que vous pouvez interroger. Le sens est Define-XML → metacore, pas l’inverse. Le fichier Define-XML lui-même est généré par d’autres outils (par exemple Pinnacle 21 Enterprise ou des générateurs Define-XML dédiés). Traitez un objet metacore comme le conteneur de métadonnées que vous interrogez — pour des contrôles CT avec metatools, ou pour piloter une construction ADaM pilotée par les métadonnées — pas comme un auteur de Define-XML.

Testez vos connaissances

Vous exécutez run_all_checks() sur votre étude mappée et trois contrôles reviennent FALSE :

  1. check_ae_dup — message « AE has duplicated entries. », avec un data frame de 12 lignes attaché.
  2. check_vs_sbp_lt_dbp — message « VS is missing the variable: VSSPID », sans ligne de données.
  3. check_dm_actarm_arm — message « DM has entries where ARM != ACTARM. », avec un data frame de 4 lignes.

Pour chacun, décidez : est-ce une erreur de données à requêter, un « impossible à exécuter » (corrigez l’entrée, pas les données), ou un constat attendu à confirmer ? Que faites-vous ensuite pour chacun ?

Lisez chaque message, pas seulement le FALSE. L’un nomme une variable manquante (donc le contrôle n’a jamais évalué la règle). L’un est un vrai doublon que le data management doit résoudre. L’un signale une différence réelle mais souvent attendue — rappelez-vous, d’après la leçon sur le domaine DM, pourquoi ARM et ACTARM diffèrent légitimement.

  1. check_ae_dup — une erreur de données à requêter. Le message décrit de mauvaises données et un data frame des lignes fautives est attaché. Exportez attr(result, "data"), remettez les lignes AE en double au data management comme requête, et résolvez-les (généralement le site ou l’EDC a porté un enregistrement deux fois).
  2. check_vs_sbp_lt_dbp — un « impossible à exécuter », pas un constat. Le message nomme une variable manquante (VSSPID), donc le contrôle n’a pas pu évaluer du tout la règle systolique-vs-diastolique. Corrigez l’entrée : confirmez si cette variable appartient à votre mapping VS, ajoutez-la si oui, et relancez. Il n’y a aucune erreur de pression artérielle ici — rien à requêter dans les données.
  3. check_dm_actarm_arm — un constat attendu à confirmer. ARM (prévu) et ACTARM (réel) diffèrent par conception quand un sujet est dosé différemment de sa randomisation. Ce n’est pas nécessairement une erreur : confirmez que les 4 sujets sont de véritables cas de mauvais dosage que l’étude connaît déjà (l’efficacité utilise le bras prévu, la sécurité le bras réel), documentez-les, et passez à la suite — vous ne corrigez pas la différence.

La leçon : un FALSE est le début du tri, pas un verdict. Le message et les données attachées vous disent laquelle des trois sortes de constats vous avez.

A. Les données violent forcément la règle et doivent être requêtées B. Soit la règle a été violée, soit le contrôle n’a pas pu s’exécuter (une variable manquante) — lisez le message pour savoir lequel C. Le jeu de données n’a pas pu se charger

B. Un contrôle sdtmchecks renvoie FALSE à la fois quand il trouve une vraie violation et quand une variable requise manque de sorte qu’il n’a pas pu évaluer la règle. Le message attaché (attr(result, "msg")) lève l’ambiguïté : un message décrivant de mauvaises données est un vrai constat (avec les lignes fautives dans attr(result, "data")) ; un message nommant une variable manquante est un « impossible à exécuter » — corrigez l’entrée, pas les données. A est faux parce qu’il ignore le cas « impossible à exécuter », et C est faux parce qu’un échec de chargement serait une erreur, pas un résultat FALSE.

Conclusion

Le mapping produit les jeux de données SDTM ; la conformité prouve qu’ils sont prêts pour la soumission, et c’est une étape distincte avec ses propres outils. La Controlled Terminology fixe les valeurs — chaque variable régie puise dans un codelist NCI EVS publié de valeurs de soumission et de décodes, extensible ou non — et le contrôle le plus simple est de savoir si les valeurs d’une colonne sont un sous-ensemble de son codelist. sdtmchecks étend cette idée en une batterie de règles d’intégrité des données : un contrôle unique renvoie TRUE, ou FALSE porteur d’un message et des lignes fautives, et run_all_checks() exécute toute la batterie sur chaque domaine et vous renvoie un résumé des constats que vous triez — en lisant chaque message pour séparer vraies erreurs, différences attendues et « impossibles à exécuter ». Et Define-XML, contenu en R comme un objet metacore et vérifié face aux données avec metatools, est la couche de métadonnées qu’un évaluateur lit en premier. La seule chose à retenir est la répartition des tâches : sdtmchecks pour des contrôles de règles rapides en R, metatools/metacore pour spec-versus-données, et Pinnacle 21 pour le contrôle de conformité complet CT-et-SDTMIG. Utilisez chacun pour son rôle et l’étude qui atteint l’évaluateur est une étude qui a déjà répondu à ses questions.

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

Références

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 = {Contrôles de conformité SDTM en R : Controlled Terminology et
    sdtmchecks à grande échelle},
  date = {2026-07-01},
  url = {https://www.datanovia.com/learn/pharma-clinical/02-sdtm-programming/sdtm-conformance-checks},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“Contrôles de conformité SDTM en R : Controlled Terminology et sdtmchecks à grande échelle.” 2026. July 1. https://www.datanovia.com/learn/pharma-clinical/02-sdtm-programming/sdtm-conformance-checks.