Exporter un fichier XPT en R avec xportr : le workflow conforme CDISC

Amenez un jeu de données ADaM validé jusqu’à un fichier SAS Transport (XPT v5) prêt pour la soumission — le pipeline xportr complet (metadata, type, length, label, format, order, write), le contrôle de conformité strict_checks, et comment une violation de nom de variable ou de libellé est détectée et corrigée

Un tutoriel pratique sur l’export d’un jeu de données clinique vers un fichier SAS Transport (XPT) version 5 conforme à la soumission avec le package pharmaverse xportr. Découvrez le pipeline d’export complet — attachez une spécification de métadonnées une fois, puis fixez le type, la longueur, le libellé, le format d’affichage et l’ordre de chaque variable et écrivez le fichier de transport ; relisez-le pour prouver que les libellés, l’ordre et le libellé du jeu de données ont survécu. Puis la partie qui compte pour une soumission : le contrôle de conformité strict_checks (pourquoi la valeur par défaut livre silencieusement un fichier non conforme, et comment strict_checks = TRUE transforme une violation en erreur bloquante), une violation-et-correction résolue (un nom de variable de plus de 8 caractères et un libellé de plus de 40 caractères signalés, puis corrigés en un nom CDISC et réexportés proprement), et le pilotage du tout depuis une spécification metacore. Sur des données pharmaverse publiques, de sorte que chaque ligne s’exécute.

Date de publication

1 juillet 2026

Modifié

7 juillet 2026

AstucePoints clés
  • xportr écrit un fichier de transport conforme à la soumission, haven en écrit un brut. Tous deux produisent du XPT (SAS Transport, XPORT) version 5, mais seul xportr applique une spécification de métadonnées et vérifie les contraintes CDISC (Clinical Data Interchange Standards Consortium) au fil de l’eau.
  • Le pipeline consiste à attacher une spécification une fois, puis à l’appliquer étape par étape. xportr_metadata() attache la spécification ; xportr_type(), xportr_length(), xportr_label(), xportr_format(), xportr_order() et xportr_df_label() fixent le type de stockage, la longueur, le libellé, le format d’affichage et l’ordre des colonnes de chaque variable, plus le libellé du jeu de données ; xportr_write() émet le .xpt.
  • strict_checks = FALSE est la valeur par défaut dangereuse — il livre un fichier non conforme avec un simple avertissement. Utilisez strict_checks = TRUE pour une soumission : il promeut chaque violation en erreur bloquante et refuse d’ écrire.
  • Les limites v5 sont strictes : noms ≤ 8 caractères, libellés ≤ 40, valeurs caractères ≤ 200. Un nom trop long (TREATMENTARM) ou un libellé est signalé ; la correction est un nom CDISC en règle (TRT01P) et un libellé plus court.
  • Pour une date ADaM, le type de la spécification est numeric avec un format DATE9. — jamais date. type = "date" corrompt une vraie colonne Date à l’écriture ; type = "numeric" + format = "DATE9." fait un aller-retour propre.

Introduction

Vous disposez d’un jeu de données ADaM (Analysis Data Model) validé — dérivé, double-programmé et approuvé. Il ne peut pas être livré en .rds ni en .sas7bdat. Une soumission réglementaire fait circuler les jeux de données dans un seul et unique format : XPT, le format SAS Transport (XPORT) version 5 — le format d’échange nommé dans les Study Data Standards Resources de la FDA et imposé dans l’ensemble des livrables CDISC (Clinical Data Interchange Standards Consortium). Votre tâche dans cette leçon est le dernier kilomètre de l’assemblage : transformer ce data frame prêt pour l’analyse en un .xpt qu’un évaluateur peut ouvrir, avec les bons types, longueurs, libellés et ordre des variables — et prouver qu’il est conforme avant qu’il ne quitte votre machine.

Vous ne fabriquez pas un XPT à la main. En R, le package xportr (qui fait partie du pharmaverse) écrit un fichier de transport v5 conforme à partir d’un data frame et d’une spécification de métadonnées — la même information au niveau variable que porte un fichier de métadonnées Define-XML — et vérifie les contraintes v5 au fil de l’écriture. La leçon compagnon a cartographié où se situe le .xpt dans la soumission ; celle-ci en est la mécanique : le pipeline complet, le contrôle de conformité et ce qui se passe lorsqu’une variable enfreint les règles.

Voici la forme de l’ensemble du travail — le pipeline que vous vous apprêtez à exécuter, de bout en bout :

A left-to-right flow schematic of the xportr export pipeline in five boxes joined by arrows. Box 1 (light blue): an ADaM data.frame, labelled ADSL (validated). Box 2 (mid blue): xportr_metadata(), attach the spec plus domain. Box 3 (mid blue): apply the spec, listing the six step functions type, length, label, format, order, df_label. Box 4 (dark navy): xportr_write() with strict_checks = TRUE, marked as the gate. Box 5 (brand azure): adsl.xpt, a SAS Transport version-5 file that is compliant. The visual point is that one metadata spec is attached once, applied variable by variable, then written and gate-checked into a compliant transport file.

Nous travaillons sur un vrai jeu de données au niveau sujet — ADSL (le jeu de données d’analyse au niveau sujet, une ligne par sujet) — issu de pharmaverseadam, de sorte que chaque bloc s’exécute tel quel.

Le pipeline d’export complet

Le schéma est toujours le même : attachez la spécification une fois, puis appliquez-la une propriété à la fois, puis écrivez. La spécification est un simple data frame doté d’un ensemble fixe de colonnes — dataset, variable, type, label, length, order et format — une ligne par variable. Dans une vraie soumission, cette information provient de votre source Define-XML ; ici nous l’écrivons en ligne pour que vous voyiez exactement ce que lit chaque étape.

xportr_metadata() attache la spécification et le nom de domaine pour que les étapes suivantes n’en aient pas chacune besoin. Puis, dans l’ordre : xportr_type() convertit chaque variable en son type de stockage SAS, xportr_length() fixe la longueur v5, xportr_label() applique les libellés de variables ≤ 40 caractères, xportr_format() attache les formats d’affichage SAS, xportr_order() réordonne les colonnes pour correspondre à la spécification, et xportr_df_label() fixe le libellé au niveau du jeu de données. Enfin xportr_write() exécute le contrôle de conformité et émet le .xpt.

library(xportr)
library(haven)
library(pharmaverseadam)

data("adsl", package = "pharmaverseadam")

keep  <- c("STUDYID", "USUBJID", "SUBJID", "SITEID", "AGE",
           "AGEU", "SEX", "RACE", "ARM", "SAFFL")
adsl2 <- adsl[, keep]

# The variable-level metadata spec — the information a Define-XML carries.
var_spec <- data.frame(
  dataset  = "ADSL",
  variable = keep,
  type     = c("character", "character", "character", "character", "numeric",
               "character", "character", "character", "character", "character"),
  length   = c(20, 30, 10, 10, 8, 10, 2, 40, 40, 2),
  label    = c("Study Identifier", "Unique Subject Identifier", "Subject Identifier",
               "Study Site Identifier", "Age", "Age Units", "Sex", "Race",
               "Description of Planned Arm", "Safety Population Flag"),
  format   = "",
  order    = 1:10,
  stringsAsFactors = FALSE
)

# The dataset-level label — note the 2nd argument is a metadata frame, not a string.
df_spec <- data.frame(dataset = "ADSL", label = "Subject-Level Analysis Dataset",
                      stringsAsFactors = FALSE)

out <- file.path(tempdir(), "adsl.xpt")   # the member name ("adsl") must be <= 8 characters
adsl2 |>
  xportr_metadata(var_spec, "ADSL")       |>  # attach the spec + domain once
  xportr_type()                           |>  # coerce each variable to its SAS type
  xportr_length(length_source = "metadata") |>  # set the v5 lengths from the spec
  xportr_label()                          |>  # apply the <= 40-char labels
  xportr_order()                          |>  # reorder columns per the spec
  xportr_format()                         |>  # attach SAS display formats
  xportr_df_label(df_spec)                |>  # set the dataset label
  xportr_write(out, strict_checks = TRUE)     # conformance-check, then write

Le pipe affiche quelques règles informatives (variables trouvées dans la spécification, colonnes ordonnées) — ce sont des messages de progression, non des avertissements. Comme nous avons passé strict_checks = TRUE, l’écriture n’a réussi que parce que le jeu de données est déjà conforme. Prouvez à présent que le .xpt est un vrai fichier v5 lisible : relisez-le et vérifiez que les libellés, l’ordre des colonnes et le libellé du jeu de données ont tous survécu au transport.

library(haven)

back <- read_xpt(out)

names(back)                         # column order preserved from the spec
 [1] "STUDYID" "USUBJID" "SUBJID"  "SITEID"  "AGE"     "AGEU"    "SEX"    
 [8] "RACE"    "ARM"     "SAFFL"  
attr(back$AGE, "label")             # the variable label survived
[1] "Age"
attr(back, "label")                 # the dataset-level label survived
[1] "Subject-Level Analysis Dataset"
file.info(out)$size                 # the file, in bytes
[1] 54800

Les colonnes reviennent dans l’ordre de la spécification, AGE porte toujours son libellé "Age", et le libellé du jeu de données "Subject-Level Analysis Dataset" est attaché au data frame — les métadonnées ont fait l’aller-retour. Ce .xpt est le livrable, l’objet exact qui va dans analysis/adam/datasets/.

Les dates ont besoin de type = "numeric", pas de type = "date"

Les dates ADaM (TRTSDT, ADT, numériques dérivées de RFSTDTC) sont le seul endroit où la spécification piège les gens. Une Date R est stockée sous forme de nombre, et SAS représente les dates de la même façon avec un format d’affichage. Ainsi le type de la spécification pour une date est numeric, et vous fixez format = "DATE9." pour contrôler son affichage. Écrire une vraie colonne Date avec type = "date" la corrompt à l’écriture — type = "date" est traité comme un type caractère et casse la valeur de date sous-jacente. Fixez le type à numeric et laissez le format faire le travail d’affichage :

library(xportr)
library(haven)

# A subject-level slice with a real ADaM date variable (class "Date").
dates <- data.frame(
  USUBJID = c("01-701-1015", "01-701-1023"),
  TRTSDT  = as.Date(c("2013-08-05", "2012-07-21")),
  stringsAsFactors = FALSE
)

# For an ADaM date: type = "numeric" with a SAS display format, NOT type = "date".
date_spec <- data.frame(
  dataset  = "ADSL",
  variable = c("USUBJID", "TRTSDT"),
  type     = c("character", "numeric"),
  length   = c(20, 8),
  label    = c("Unique Subject Identifier", "Date of First Exposure to Treatment"),
  format   = c("", "DATE9."),
  order    = 1:2,
  stringsAsFactors = FALSE
)

out_dt <- file.path(tempdir(), "adsldt.xpt")
dates |>
  xportr_metadata(date_spec, "ADSL") |>
  xportr_type()                      |>
  xportr_length(length_source = "metadata") |>
  xportr_label()                     |>
  xportr_format()                    |>  # applies the DATE9. display format
  xportr_order()                     |>
  xportr_write(out_dt, strict_checks = TRUE)

back_dt <- read_xpt(out_dt)
class(back_dt$TRTSDT)                 # "Date" — round-tripped as a real date
[1] "Date"
back_dt$TRTSDT                        # the dates, intact
[1] "2013-08-05" "2012-07-21"
attr(back_dt$TRTSDT, "format.sas")    # "DATE9" — the SAS display format is carried
[1] "DATE9"

Les dates reviennent sous forme de vraie colonne Date avec le format d’affichage DATE9 attaché. C’est le schéma pour chaque variable de date et de datetime ADaM — type numeric, un format DATEw./DATETIMEw. — et c’est le piège qu’il vaut la peine de mémoriser avant d’exporter un jeu de données comportant des dates.

Le contrôle de conformité : strict_checks

Tout ce qui précède s’est écrit proprement parce que les données étaient déjà conformes. La question intéressante est ce qui se passe quand elles ne le sont pas — et là, xportr a une valeur par défaut qui va vous piéger. xportr_write() exécute d’abord un contrôle de validation, mais son argument strict_checks vaut par défaut FALSE, ce qui signifie qu’un contrôle en échec n’est qu’un avertissement et que le fichier est écrit quand même. Pour une exploration interactive, c’est pratique ; pour une soumission, c’est une mine — vous pouvez livrer un .xpt non conforme sans jamais le remarquer.

Prenez un jeu de données qui enfreint deux règles v5 : une variable nommée TREATMENTARM (12 caractères, au-delà de la limite de 8 caractères) avec un libellé bien au-delà de 40 caractères. D’abord, exécutez le contrôle directement avec xpt_validate() — le moteur derrière strict_checks — comme un contrôle préalable programmatique. Il renvoie un vecteur caractère de constats (vide signifie propre) :

library(xportr)

bad <- data.frame(
  USUBJID      = c("01-701-1015", "01-701-1023"),
  TREATMENTARM = c("Placebo", "Xanomeline High Dose"),
  AGE          = c(63, 64),
  stringsAsFactors = FALSE
)
spec_bad <- data.frame(
  dataset  = "ADSL",
  variable = c("USUBJID", "TREATMENTARM", "AGE"),
  type     = c("character", "character", "numeric"),
  length   = c(20, 40, 8),
  label    = c("Unique Subject Identifier",
               "Planned Treatment Arm for the Analysis Period Number One in the Study",
               "Age"),
  format   = "",
  order    = 1:3,
  stringsAsFactors = FALSE
)

badm <- bad |>
  xportr_metadata(spec_bad, "ADSL") |>
  xportr_type()   |>
  xportr_length(length_source = "metadata") |>
  xportr_label()

xpt_validate(badm)   # a character vector of findings — empty means conformant
[1] "Variable `TREATMENTARM` must be 8 characters or less."                                                                    
[2] "Label 'TREATMENTARM=Planned Treatment Arm for the Analysis Period Number One in the Study' must be 40 characters or less."

Deux constats : le nom est trop long, et le libellé est trop long. Observez maintenant la valeur par défaut vous trahir. Avec strict_checks = FALSE, xportr_write() affiche ces mêmes constats comme un avertissement et écrit tout de même le fichier :

out_bad <- file.path(tempdir(), "adslbad.xpt")
badm |> xportr_write(out_bad, strict_checks = FALSE)
Warning: The following validation checks failed:
• Variable `TREATMENTARM` must be 8 characters or less.
• Label 'TREATMENTARM=Planned Treatment Arm for the Analysis Period Number One in the Study' must be 40 characters or less.
file.exists(out_bad)   # TRUE — the non-conformant file was written anyway
[1] TRUE

Le fichier existe. Un jeu de données non conforme vient de passer entre les mailles du filet avec pour seule alerte un avertissement que vous auriez pu faire défiler sans le voir. Fixez strict_checks = TRUE et le même appel devient une erreur bloquante qui avorte avant d’ écrire quoi que ce soit :

out_bad2 <- file.path(tempdir(), "adslbad2.xpt")
badm |> xportr_write(out_bad2, strict_checks = TRUE)
Error in `xportr_write()`:
! The following validation checks failed:
• Variable `TREATMENTARM` must be 8 characters or less.
• Label 'TREATMENTARM=Planned Treatment Arm for the Analysis Period Number One in the Study' must be 40 characters or less.
file.exists(out_bad2)   # FALSE — strict_checks aborted before any file was written
[1] FALSE

L’erreur liste exactement ce qui a échoué et aucun fichier n’est créé. Voici la règle à intérioriser : écrivez toujours les jeux de données de soumission avec strict_checks = TRUE. Un échec de conformité doit arrêter votre pipeline, non le décorer d’un avertissement.

Corriger une violation à la manière CDISC

Le contrôle vous a dit ce qui ne va pas ; le corriger est un changement de métadonnées, non un bricolage. Le nom TREATMENTARM devrait être la variable CDISC standard TRT01P (traitement planifié pour la période 01) — ce qui explique pourquoi les noms CDISC sont courts au départ — et le libellé doit passer sous les 40 caractères. Corrigez les deux dans les données et la spécification, et la même écriture stricte réussit :

library(xportr)
library(haven)

good <- data.frame(
  USUBJID = c("01-701-1015", "01-701-1023"),
  TRT01P  = c("Placebo", "Xanomeline High Dose"),   # renamed to the CDISC variable
  AGE     = c(63, 64),
  stringsAsFactors = FALSE
)
spec_good <- data.frame(
  dataset  = "ADSL",
  variable = c("USUBJID", "TRT01P", "AGE"),
  type     = c("character", "character", "numeric"),
  length   = c(20, 40, 8),
  label    = c("Unique Subject Identifier",
               "Planned Treatment for Period 01",   # now <= 40 characters
               "Age"),
  format   = "",
  order    = 1:3,
  stringsAsFactors = FALSE
)

out_fix <- file.path(tempdir(), "adslfix.xpt")
good |>
  xportr_metadata(spec_good, "ADSL") |>
  xportr_type()   |>
  xportr_length(length_source = "metadata") |>
  xportr_label()  |>
  xportr_order()  |>
  xportr_write(out_fix, strict_checks = TRUE)   # passes the gate

back_fix <- read_xpt(out_fix)
names(back_fix)                         # TRT01P in place of TREATMENTARM
[1] "USUBJID" "TRT01P"  "AGE"    
attr(back_fix$TRT01P, "label")          # the shortened label
[1] "Planned Treatment for Period 01"

Écriture propre, et le fichier exporté porte TRT01P avec son libellé conforme. Voilà toute la boucle : contrôle préalable avec xpt_validate() → écriture avec strict_checks = TRUE → correction du nom ou du libellé signalé dans la spécification → réexport. Vous itérez jusqu’à ce que le contrôle reste silencieux.

Piloter le tout depuis une spécification metacore

Écrire la spécification en ligne convient pour l’enseignement, mais sur une vraie étude la spécification n’est pas quelque chose que vous ressaisissez — elle est les métadonnées de l’étude, la source de vérité unique qui produit aussi le Define-XML. Le package metacore détient cette spécification sous forme d’objet structuré, et xportr accepte un objet metacore partout où il accepte un data frame de métadonnées. Ainsi le même pipeline s’exécute directement à partir de la spécification, sans aucun data frame écrit à la main :

library(metacore)
library(xportr)
library(pharmaverseadam)

# Load the pilot ADaM metacore object (a machine-readable spec).
e  <- new.env()
load(metacore_example("pilot_ADaM.rda"), envir = e)
mc <- get("metacore", envir = e)
adsl_mc <- select_dataset(mc, "ADSL")   # the ADSL slice of the spec

data("adsl", package = "pharmaverseadam")
common  <- intersect(adsl_mc$var_spec$variable, names(adsl))
adsl_in <- adsl[, common]

out_mc <- file.path(tempdir(), "adslmc.xpt")
adsl_in |>
  xportr_metadata(adsl_mc, "ADSL") |>
  xportr_type()   |>
  xportr_length() |>
  xportr_label()  |>
  xportr_order()  |>
  xportr_format() |>
  xportr_df_label(adsl_mc) |>
  xportr_write(out_mc, strict_checks = FALSE)

file.exists(out_mc)
[1] TRUE

Les métadonnées circulent désormais d’un seul objet, à la fois vers le fichier de transport et — dans la leçon sur les métadonnées Define-XML qui suit dans cette série — vers le define.xml. C’est le bénéfice d’un workflow piloté par la spécification : définissez chaque variable une fois, et chaque livrable lit à partir de la même définition. (Une remarque sur le chargement : les fichiers d’exemple metacore existent en deux variantes — load() un .rda, mais load_metacore() un .rds ; ne les mélangez pas.)

xportr face à haven::write_xpt()

Sous le capot, xportr_write() appelle haven::write_xpt(version = 5) — donc les deux produisent un vrai fichier de transport v5. La différence réside dans tout ce qui se passe avant l’écriture :

haven::write_xpt(x, path, version = 5) xportr_write(x, path, strict_checks = TRUE)
Écrit un XPT v5 Oui Oui (il appelle haven en interne)
Applique une spécification de métadonnées Non — écrit le data frame tel quel Oui — types, longueurs, libellés, formats, ordre
Vérifie les limites v5 Non Oui — noms ≤ 8, libellés ≤ 40, valeurs ≤ 200
Refuse un fichier non conforme Non Oui, avec strict_checks = TRUE

Utilisez haven::write_xpt() lorsque vous avez seulement besoin d’un fichier v5 à partir d’un data frame déjà correct. Utilisez xportr lorsque le fichier est un livrable de soumission et doit être prouvé conforme. La couche de conformité est la raison d’être de xportr.

Ce que xportr ne fait pas

Soyez précis sur le périmètre de xportr afin de saisir le bon outil à la bonne étape :

  • Il écrit et vérifie le XPT — types de variables, longueurs, libellés, formats, ordre et les contraintes v5.
  • Il ne génère pas de Define-XML. Il consomme seulement une spécification metacore ; produire le fichier de métadonnées Define-XML est le travail de metacore/metatools, traité dans la leçon Define-XML qui suit.
  • Il n’effectue pas une conformité CDISC complète. Il vérifie les limites du format de transport, non les centaines de règles des guides d’implémentation pour SDTM (Study Data Tabulation Model) et ADaM (SDTMIG / ADaMIG), la terminologie contrôlée ou les contrôles inter-jeux de données — cela relève de Pinnacle 21 / CDISC CORE.
  • Il ne dérive ni ne transforme les données. Cela, c’est admiral/sdtm.oak. xportr est la dernière étape — l’assemblage de données prêtes pour l’analyse en vue du transport.

Franchir le contrôle xportr signifie que votre fichier est un fichier de transport v5 bien formé et correctement libellé. Cela ne signifie pas que la soumission est conforme au CDISC de bout en bout — c’est un contrôle distinct, ultérieur.

🟢 Avec un agent IA

Demandez à Prova « j’ai un ADSL validé — écris-moi le pipeline xportr pour l’exporter vers un XPT v5 conforme, avec strict_checks activé et les dates gérées correctement. » — elle répond en s’appuyant sur les leçons de ce pilier, avec du code pharmaverse exécutable que vous pouvez essayer sur des données d’exemple. The runtime is the judge. Demandez à Prova →

Problèmes fréquents

Une variable de date ressort corrompue ou provoque une erreur à l’écriture. Vous avez fixé type = "date" dans la spécification pour une vraie colonne Date. xportr traite type = "date" comme un type caractère et casse la valeur sous-jacente. Pour toute date ou datetime ADaM, utilisez type = "numeric" avec un format d’affichage SAS (format = "DATE9.", "DATETIME20.", etc.) ; la valeur reste numérique et se relit comme une Date.

xportr_write() produit l’erreur « file name must be 8 characters or less ». Le nom du membre .xpt — le nom de base du fichier sans l’extension — est lui-même un identifiant v5 et est plafonné à 8 caractères. xportr_write("adsl_nodate.xpt") échoue parce que adsl_nodate fait 11 caractères. Nommez le fichier d’après son jeu de données : adsl.xpt, adae.xpt, adtte.xpt.

Un fichier non conforme a été livré sans que vous vous en aperceviez. Vous avez écrit avec la valeur par défaut strict_checks = FALSE, si bien qu’une violation de longueur ou de libellé n’était qu’un avertissement et que le fichier a été écrit quand même. Écrivez toujours les jeux de données de soumission avec strict_checks = TRUE pour qu’une violation soit une erreur bloquante qui arrête le pipeline, et exécutez xpt_validate() en contrôle préalable si vous voulez les constats sans tenter d’écriture.

Questions fréquentes

Utilisez le package xportr pour un jeu de données de soumission : df |> xportr_metadata(spec, "ADSL") |> xportr_type() |> xportr_length() |> xportr_label() |> xportr_order() |> xportr_write("adsl.xpt", strict_checks = TRUE). Cela attache une spécification de métadonnées, applique le type, la longueur, le libellé et l’ordre de chaque variable, vérifie les contraintes CDISC v5 et écrit un fichier de transport conforme. Si vous avez seulement besoin d’un fichier v5 brut à partir d’un data frame déjà correct, haven::write_xpt(df, "adsl.xpt", version = 5) le fait sans aucun contrôle de conformité.

xportr_write() appelle haven::write_xpt(version = 5) en interne, donc les deux produisent un vrai XPT v5. La différence est la couche de conformité : xportr applique d’abord une spécification de métadonnées (types, longueurs, libellés, formats, ordre des colonnes) et valide les limites CDISC v5 (noms de variables ≤ 8 caractères, libellés ≤ 40, valeurs caractères ≤ 200), et avec strict_checks = TRUE il refuse d’écrire un fichier non conforme. haven::write_xpt() écrit le data frame exactement tel quel, sans application de métadonnées ni contrôle de conformité. Utilisez haven pour un fichier v5 rapide, xportr pour un livrable de soumission.

SAS Transport version 5 plafonne les noms de variables à 8 caractères, les libellés de variables à 40 caractères et les valeurs caractères à 200 caractères, sans compression. C’est pourquoi les noms de variables SDTM et ADaM sont concis (USUBJID, AVAL, TRT01P). xportr signale toute violation ; la structure d’enregistrement est spécifiée dans le document technique de SAS sur le format de transport v5.

strict_checks est l’argument de xportr_write() qui décide de la façon dont un contrôle de conformité en échec est géré. Il vaut par défaut FALSE, ce qui signifie qu’une violation (un nom, un libellé ou une valeur qui dépasse la limite) n’est qu’un avertissement et le fichier est écrit quand même — dangereux pour une soumission. Fixez strict_checks = TRUE et une violation devient une erreur bloquante qui avorte avant l’écriture. Utilisez toujours TRUE pour les jeux de données de soumission. Vous pouvez aussi exécuter xpt_validate(df) seul pour obtenir les constats sous forme de vecteur caractère sans tenter d’écriture.

Ne fixez pas type = "date" dans la spécification — cela corrompt une vraie colonne Date à l’écriture. Une Date R est un nombre, et SAS stocke les dates de la même façon avec un format d’affichage, donc fixez type = "numeric" et format = "DATE9." (ou "DATETIME20." pour un datetime). Exécutez xportr_format() dans le pipeline pour appliquer le format. La valeur reste numérique sur le disque et se relit comme une vraie Date avec son format d’affichage attaché.

Testez vos connaissances

On vous confie un petit jeu de données à exporter. Il a une variable nommée SUBJECTAGEGROUP (15 caractères) libellée "Subject Age Group Category for the Pooled Analysis Population" (bien au-delà de 40 caractères). Écrivez le code qui (1) exécute xpt_validate() pour afficher les constats, (2) tente une écriture stricte et observe son avortement, puis (3) corrige le nom en la variable CDISC AGEGR1 et raccourcit le libellé en "Pooled Age Group" pour qu’une écriture stricte réussisse. Quels deux constats le contrôle préalable rapporte-t-il, et pourquoi l’écriture stricte refuse-t-elle l’original ?

Les deux limites v5 en jeu sont noms ≤ 8 caractères et libellés ≤ 40 caractèresSUBJECTAGEGROUP et le long libellé enfreignent les deux. Construisez la spécification de métadonnées avec les colonnes dataset, variable, type, length, label, format et order, faites passer les données par xportr_metadata() |> xportr_type() |> xportr_length() |> xportr_label(), puis appelez xpt_validate() sur le résultat. Pour la correction, changez à la fois le nom de colonne dans le data frame et les entrées variable/label de la spécification.

library(xportr)

df <- data.frame(
  USUBJID         = c("01-701-1015", "01-701-1023"),
  SUBJECTAGEGROUP = c(">64", "18-64"),
  stringsAsFactors = FALSE
)
spec <- data.frame(
  dataset  = "ADSL",
  variable = c("USUBJID", "SUBJECTAGEGROUP"),
  type     = c("character", "character"),
  length   = c(20, 20),
  label    = c("Unique Subject Identifier",
               "Subject Age Group Category for the Pooled Analysis Population"),
  format   = "",
  order    = 1:2,
  stringsAsFactors = FALSE
)

# (1) Pre-flight: two findings — name > 8 chars, label > 40 chars
prepped <- df |>
  xportr_metadata(spec, "ADSL") |> xportr_type() |>
  xportr_length(length_source = "metadata") |> xportr_label()
xpt_validate(prepped)

# (2) Strict write aborts (no file written)
prepped |> xportr_write(file.path(tempdir(), "bad.xpt"), strict_checks = TRUE)

# (3) Fix the name and label, then the strict write passes
good <- data.frame(
  USUBJID = c("01-701-1015", "01-701-1023"),
  AGEGR1  = c(">64", "18-64"),
  stringsAsFactors = FALSE
)
spec_good <- data.frame(
  dataset  = "ADSL", variable = c("USUBJID", "AGEGR1"),
  type     = c("character", "character"), length = c(20, 10),
  label    = c("Unique Subject Identifier", "Pooled Age Group"),
  format   = "", order = 1:2, stringsAsFactors = FALSE
)
good |>
  xportr_metadata(spec_good, "ADSL") |> xportr_type() |>
  xportr_length(length_source = "metadata") |> xportr_label() |> xportr_order() |>
  xportr_write(file.path(tempdir(), "adsl.xpt"), strict_checks = TRUE)

Le contrôle préalable rapporte deux constats : Variable SUBJECTAGEGROUP must be 8 characters or less et un libellé qui must be 40 characters or less. Avec strict_checks = TRUE, l’écriture refuse parce que l’une ou l’autre violation est une erreur bloquante. Renommer en le nom CDISC de 6 caractères AGEGR1 et raccourcir le libellé en "Pooled Age Group" (16 caractères) règle les deux, et l’écriture stricte réussit.

A. haven::write_xpt(df, "adsl.xpt", version = 5) B. xportr_write(df, "adsl.xpt", strict_checks = FALSE) C. xportr_write(df, "adsl.xpt", strict_checks = TRUE) après le pipeline xportr_metadata()typelengthlabel

C. Seul le pipeline xportr complet avec strict_checks = TRUE à la fois applique la spécification de métadonnées et transforme une violation v5 en erreur bloquante, de sorte qu’un jeu de données non conforme ne peut pas être écrit. A écrit un vrai fichier v5 mais n’effectue aucun contrôle de conformité — il écrira volontiers un nom trop long. B exécute le contrôle mais, avec strict_checks = FALSE, se contente d’avertir et écrit le fichier quand même. Pour un livrable de soumission, utilisez toujours C.

Conclusion

Exporter un jeu de données de soumission est un pipeline discipliné, non un simple appel à write. Vous attachez la spécification de métadonnées une fois, l’appliquez variable par variable — type, longueur, libellé, format, ordre et le libellé du jeu de données — et écrivez avec strict_checks = TRUE pour que toute entorse aux limites v5 (noms ≤ 8, libellés ≤ 40, valeurs ≤ 200) vous arrête net au lieu de livrer un avertissement. Quand le contrôle signale une variable, la correction est un changement de métadonnées — le nom CDISC et un libellé conforme — non un contournement. Traitez les dates en numeric avec un format DATE9., pilotez la spécification depuis un objet metacore pour qu’elle alimente aussi le Define-XML, et rappelez-vous la limite de xportr : il prouve que le fichier de transport est bien formé, non que toute la soumission est conforme au CDISC. Réussissez cette étape et le .xpt qui atterrit dans analysis/adam/datasets/ est exactement ce qu’un évaluateur s’attend à ouvrir.

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.

Références

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 = {Exporter un fichier XPT en R avec xportr : le workflow
    conforme CDISC},
  date = {2026-07-01},
  url = {https://www.datanovia.com/learn/pharma-clinical/07-submission-packaging/xpt-export-xportr},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“Exporter un fichier XPT en R avec xportr : le workflow conforme CDISC.” 2026. July 1. https://www.datanovia.com/learn/pharma-clinical/07-submission-packaging/xpt-export-xportr.