ADLB en R avec admiral : analyse de laboratoire et BDS

Dérivez un jeu de données d’analyse de laboratoire (ADLB) conforme au CDISC avec admiral — PARAMCD/AVAL, valeur initiale (ABLFL), variation par rapport à la valeur initiale (CHG/PCHG) et l’indicateur de plage de référence (ANRIND) — et apprenez la structure de données de base que partage chaque ADaM Findings

Un tutoriel complet et exécutable pour l’ADLB, le jeu de données d’analyse de laboratoire, et à travers lui la BDS (structure de données de base) — la forme une-ligne-par-sujet-par-paramètre-par-point-temporel qu’utilise chaque ADaM Findings (ADLB, ADVS, ADEG). Découvrez ce qu’est la BDS (la colonne vertébrale PARAMCD/PARAM/AVAL, opposée à l’unique ligne par sujet d’ADSL), puis construisez un ADLB conforme en R à partir du domaine SDTM LB avec admiral : derive_vars_dt() pour la date d’analyse, derive_var_anrind() pour l’indicateur de plage de référence, derive_var_extreme_flag() pour le marqueur de la valeur initiale (ABLFL), et derive_var_base()/derive_var_chg()/derive_var_pchg() pour la variation par rapport à la valeur initiale — sur des données pharmaverse publiques, pour que chaque ligne s’exécute.

Date de publication

30 juin 2026

Modifié

7 juillet 2026

AstucePoints clés
  • ADLB (le jeu de données d’analyse de laboratoire) est le jeu de données Findings le plus volumineux et le plus analysé, et il est votre porte d’entrée vers la BDS (structure de données de base) — la forme verticale, une-ligne-par-sujet-par-paramètre-par-point-temporel que partagent ADLB, ADVS (signes vitaux) et ADEG (ECG).
  • La colonne vertébrale de la BDS est PARAMCD / AVAL / AVISIT. Chaque test de laboratoire à chaque visite est sa propre ligne : PARAMCD nomme l’analyte, AVAL contient sa valeur numérique et AVISIT la visite d’analyse. C’est l’inverse de l’unique ligne large par sujet d’ADSL.
  • admiral dérive les variables Findings de façon déclarative. derive_vars_dt() construit la date d’analyse, derive_var_anrind() classe chaque valeur LOW / NORMAL / HIGH par rapport à sa plage de référence, et derive_var_extreme_flag() marque l’enregistrement de la valeur initiale (ABLFL).
  • La variation par rapport à la valeur initiale est tout l’enjeu. derive_var_base() copie la valeur initiale de chaque sujet sur chaque ligne post-initiale, puis derive_var_chg() et derive_var_pchg() calculent la variation absolue et en pourcentage — les chiffres sur lesquels repose chaque tableau et figure de laboratoire de sécurité.
  • Définissez la valeur initiale une seule fois, correctement. La valeur initiale est la dernière valeur le jour de la première dose ou avant, marquée par ABLFL == "Y". Réussissez ce seul marqueur et chaque CHG, PCHG et tableau de transition en aval seront corrects.

Introduction

La revue de sécurité repose sur les analyses de laboratoire. Enzymes hépatiques, marqueurs rénaux, hématologie — un essai de phase 3 collecte des dizaines de tests de laboratoire à chaque visite, et presque chaque question de sécurité (« ce médicament a-t-il fait bouger l’ALT ? la créatinine de quelqu’un a-t-elle grimpé ? ») trouve sa réponse dans le jeu de données ADLB. C’est régulièrement le jeu de données ADaM (Analysis Data Model, le modèle de données d’analyse) le plus volumineux d’une soumission, et c’est l’exemple canonique de la structure que partage chaque jeu de données Findings : la BDS (structure de données de base).

Cette leçon construit un ADLB conforme en R avec admiral, le package pharmaverse pour la dérivation ADaM, en travaillant sur les données d’exemple publiques de pharmaverse afin que chaque ligne s’exécute telle quelle. Au passage, vous apprendrez la BDS elle-même — car une fois que vous savez construire ADLB, ADVS et ADEG sont les mêmes gestes avec un domaine source différent. La construction suit la vignette « Creating a BDS Finding ADaM » d’admiral, ré-écrite en une seule démonstration guidée pour les analyses de laboratoire.

Voici où nous allons — la variation moyenne par rapport à la valeur initiale de l’ALT (une enzyme hépatique) au cours de l’étude, par bras de traitement, calculée directement à partir de l’ADLB que nous dérivons :

Line chart of mean change from baseline in alanine aminotransferase (ALT, a liver enzyme) over scheduled visits from week 2 to week 26, for three treatment arms built from the derived ADLB dataset. Placebo (azure) stays close to zero throughout. Both Xanomeline active arms (orange high dose, green low dose) rise above placebo in the early weeks — a mild early increase in the liver enzyme — before drifting back. The y-axis is mean change from baseline in units per litre; a dashed line marks zero change.

Cette figure se lit sur quatre colonnes d’ADLB — PARAMCD, AVISIT, CHG et TRT01A — une ligne par sujet par test par visite. À la fin de cette leçon, vous les aurez toutes dérivées. Si « ADaM », « BDS » ou « SDTM » (le Study Data Tabulation Model — les données collectées standardisées à partir desquelles ADaM est construit) vous sont nouveaux, commencez par les normes CDISC (Clinical Data Interchange Standards Consortium), qui situent la place d’ADLB ; ADLB relit aussi ses variables au niveau du sujet depuis l’ADSL que vous construisez en premier.

Ce qu’est la structure BDS

ADSL, que vous avez rencontré dans la leçon ADSL, a une ligne par sujet — large, avec une colonne pour chaque attribut. Un jeu de données Findings ne peut pas fonctionner ainsi : un sujet a une valeur pour chaque test de laboratoire à chaque visite, bien trop nombreuses pour être des colonnes. Les jeux de données ADaM Findings sont donc verticaux au lieu d’être larges. Cette forme verticale est la BDS (structure de données de base), définie par la norme CDISC ADaM et partagée par ADLB, ADVS (signes vitaux) et ADEG (ECG).

La règle de la BDS est simple : une ligne par sujet, par paramètre, par point temporel. L’ALT d’un sujet à la semaine 2 est une ligne ; son ALT à la semaine 4 est une autre ligne ; sa créatinine à la semaine 2 est encore une autre ligne. Une seule colonne, AVAL, contient quelle que soit la valeur que cette ligne mesure — l’analyte est nommé par PARAMCD, non par la colonne. C’est ce qui permet à un seul jeu de données de contenir 40 tests de laboratoire différents sans 40 colonnes de valeurs différentes.

Une poignée de variables portent la structure. Voici les colonnes BDS porteuses que cette leçon dérive :

Variable Étiquette Ce qu’elle contient
PARAMCD Code du paramètre L’identifiant de l’analyte — ALT, CREAT, WBC. Une valeur nomme le test de chaque ligne.
PARAM Paramètre Le paramètre lisible par l’humain, p. ex. Alanine aminotransférase (U/L).
AVAL Valeur d’analyse Le résultat numérique pour ce paramètre à ce point temporel.
AVALC Valeur d’analyse (C) Le résultat en caractères, pour les résultats non numériques.
AVISIT / AVISITN Visite d’analyse L’étiquette de la visite d’analyse et son ordre de tri.
ADT / ADY Date / jour d’analyse La date de la mesure, et le jour d’étude relatif à la première dose.
ABLFL Marqueur d’enregistrement de la valeur initiale "Y" sur l’unique ligne qui est la valeur initiale de ce couple sujet-paramètre.
BASE Valeur initiale La AVAL initiale, copiée sur chaque ligne du couple sujet-paramètre.
CHG / PCHG Variation / % de variation par rapport à la valeur initiale AVAL − BASE, et cette variation en pourcentage de BASE.
ANRIND Indicateur de plage de référence d’analyse LOW / NORMAL / HIGH — où AVAL se situe par rapport à sa plage de référence.
ANL01FL Marqueur d’analyse 01 "Y" sur les enregistrements qui alimentent une analyse donnée (p. ex. une ligne par visite).

Deux conventions méritent d’être fixées d’emblée :

  • AVAL est indépendant du paramètre. La même colonne contient l’ALT en U/L sur une ligne et la créatinine en µmol/L sur la suivante. Lisez toujours AVAL en même temps que PARAMCD — ne présumez jamais d’une unité. C’est tout le compromis de la BDS : une flexibilité maximale, au prix d’une colonne de valeurs qui ne signifie rien à elle seule.
  • La valeur initiale est un marqueur dérivé, pas une visite. ABLFL == "Y" marque l’enregistrement qu’admiral choisit comme valeur initiale (ici, la dernière valeur le jour de la première dose ou avant). CHG et PCHG sont calculés par rapport à cette valeur marquée, de sorte que le marqueur est la dérivation la plus importante du jeu de données.

L’essai que nous utilisons est l’étude pilote CDISC livrée dans pharmaversesdtm — une étude synthétique, libre de licence, à bras parallèles comparant un placebo à deux doses de Xanomeline.

Les données source

ADLB est construit à partir du domaine SDTM LB (Laboratory Test Results) plus l’ADSL que vous avez dérivé en premier. LB est déjà dans une forme longue, une-ligne-par-résultat — proche de la BDS — donc l’essentiel du travail consiste à dériver les variables d’analyse (dates, valeur initiale, variation) par-dessus. Nous tirons les variables de traitement au niveau du sujet depuis ADSL.

library(admiral)
library(dplyr, warn.conflicts = FALSE)
library(pharmaversesdtm)
library(pharmaverseadam)
library(lubridate)
library(stringr)

# LB — the raw lab domain, one row per result. convert_blanks_to_na() turns
# SDTM's empty strings "" into proper NA so admiral's date/merge functions work.
lb <- pharmaversesdtm::lb %>% convert_blanks_to_na()

# ADSL — one row per subject, the treatment + demographics backbone we merge from.
adsl <- pharmaverseadam::adsl

lb %>%
  select(USUBJID, LBTESTCD, LBTEST, VISIT, LBDTC, LBSTRESN, LBSTNRLO, LBSTNRHI) %>%
  filter(LBTESTCD == "ALT", USUBJID == "01-701-1015") %>%
  head(5)
# A tibble: 5 × 8
  USUBJID     LBTESTCD LBTEST             VISIT LBDTC LBSTRESN LBSTNRLO LBSTNRHI
  <chr>       <chr>    <chr>              <chr> <chr>    <dbl>    <dbl>    <dbl>
1 01-701-1015 ALT      Alanine Aminotran… SCRE… 2013…       27        6       34
2 01-701-1015 ALT      Alanine Aminotran… WEEK… 2014…       41        6       34
3 01-701-1015 ALT      Alanine Aminotran… WEEK… 2014…       18        6       34
4 01-701-1015 ALT      Alanine Aminotran… WEEK… 2014…       26        6       34
5 01-701-1015 ALT      Alanine Aminotran… WEEK… 2014…       22        6       34

Chaque ligne LB porte déjà le résultat (LBSTRESN, le résultat numérique standardisé), le test (LBTESTCD/LBTEST), la visite, la date (LBDTC) et la plage de référence (LBSTNRLO/LBSTNRHI). Notre travail consiste à transformer ces colonnes SDTM en colonnes BDS aux noms d’analyse ci-dessus.

Fusionner ADSL et dériver les dates d’analyse

Commencez par amener les variables de traitement d’ADSL sur chaque enregistrement de laboratoire — chaque ADaM Findings commence ici, car la logique de variation par rapport à la valeur initiale et la logique d’apparition sous traitement reposent toutes deux sur TRTSDT (première dose). derive_vars_dt() convertit ensuite le LBDTC en caractères en une vraie date d’analyse ADT, et derive_vars_dy() calcule le jour d’étude ADY relatif à la première dose.

adsl_vars <- exprs(TRTSDT, TRTEDT, TRT01A, TRT01P)

adlb <- lb %>%
  # bring treatment dates + arms from ADSL onto each lab record
  derive_vars_merged(
    dataset_add = adsl,
    new_vars = adsl_vars,
    by_vars = exprs(STUDYID, USUBJID)
  ) %>%
  # ADT: the analysis date, parsed from the ISO character LBDTC
  derive_vars_dt(new_vars_prefix = "A", dtc = LBDTC) %>%
  # ADY: study day of the measurement, relative to first dose (TRTSDT)
  derive_vars_dy(reference_date = TRTSDT, source_vars = exprs(ADT))

adlb %>%
  filter(LBTESTCD == "ALT", USUBJID == "01-701-1015") %>%
  select(USUBJID, VISIT, ADT, ADY, TRTSDT) %>%
  head(5)
# A tibble: 5 × 5
  USUBJID     VISIT       ADT          ADY TRTSDT    
  <chr>       <chr>       <date>     <dbl> <date>    
1 01-701-1015 SCREENING 1 2013-12-26    -7 2014-01-02
2 01-701-1015 WEEK 2      2014-01-16    15 2014-01-02
3 01-701-1015 WEEK 4      2014-01-30    29 2014-01-02
4 01-701-1015 WEEK 6      2014-02-12    42 2014-01-02
5 01-701-1015 WEEK 8      2014-03-05    63 2014-01-02

derive_vars_dt() fait l’analyse de date peu glamour mais cruciale ; derive_vars_dy() donne le décompte familier du jour d’étude (le jour 1 est la première dose, les jours négatifs sont la sélection pré-dose), qui est la façon dont les évaluateurs lisent la chronologie.

Poser la colonne vertébrale de la BDS : PARAMCD, PARAM et AVAL

Nommez maintenant le paramètre de chaque ligne et hissez sa valeur dans AVAL — les trois colonnes qui en font un jeu de données BDS. Dans cette leçon, les codes des tests de laboratoire se mappent directement : LBTESTCD devient PARAMCD, LBTEST devient PARAM, et le résultat numérique standardisé LBSTRESN devient AVAL. (Dans un ADLB de production complet, vous mapperiez ceux-ci via une table de correspondance de paramètres contrôlée pour que PARAMCD, PARAM et PARAMN soient régis par vos métadonnées ; l’affectation directe garde l’idée de la BDS au premier plan.)

adlb <- adlb %>%
  mutate(
    PARAMCD = LBTESTCD,    # the analyte code names the row
    PARAM   = LBTEST,      # human-readable parameter
    AVAL    = LBSTRESN,    # the numeric value for THIS parameter
    AVALC   = LBSTRESC     # character value, for non-numeric findings
  )

adlb %>%
  filter(USUBJID == "01-701-1015", VISIT == "WEEK 2") %>%
  select(USUBJID, PARAMCD, PARAM, AVAL, AVALC) %>%
  head(6)
# A tibble: 6 × 5
  USUBJID     PARAMCD PARAM                       AVAL AVALC
  <chr>       <chr>   <chr>                      <dbl> <chr>
1 01-701-1015 ALB     Albumin                    39    39   
2 01-701-1015 ALP     Alkaline Phosphatase       50    50   
3 01-701-1015 ALT     Alanine Aminotransferase   41    41   
4 01-701-1015 AST     Aspartate Aminotransferase 33    33   
5 01-701-1015 BILI    Bilirubin                   8.55 8.55 
6 01-701-1015 BUN     Blood Urea Nitrogen         3.57 3.57 

Ce seul sujet, à une seule visite, a maintenant plusieurs lignes — une par test de laboratoire — chacune avec la même colonne AVAL signifiant un analyte différent. C’est la forme BDS rendue concrète : lisez AVAL uniquement aux côtés de PARAMCD.

Dériver la visite d’analyse

Les tableaux et les figures regroupent par visite d’analyse, et non par la chaîne SDTM brute VISIT. Nous dérivons une étiquette AVISIT propre et un AVISITN numérique pour le tri, en ramenant les visites non programmées et de sélection à NA afin qu’elles n’encombrent pas les résumés par visite.

adlb <- adlb %>%
  mutate(
    AVISIT = case_when(
      str_detect(VISIT, "SCREEN|UNSCHED|RETRIEVAL|AMBUL") ~ NA_character_,
      !is.na(VISIT) ~ str_to_title(VISIT)
    ),
    AVISITN = as.numeric(case_when(
      VISIT == "BASELINE" ~ "0",
      str_detect(VISIT, "WEEK") ~ str_trim(str_replace(VISIT, "WEEK", ""))
    ))
  )

adlb %>% count(VISIT, AVISIT, AVISITN)
# A tibble: 27 × 4
   VISIT               AVISIT   AVISITN     n
   <chr>               <chr>      <dbl> <int>
 1 AMBUL ECG PLACEMENT <NA>          NA     5
 2 AMBUL ECG REMOVAL   <NA>          NA    52
 3 BASELINE            Baseline       0    12
 4 RETRIEVAL           <NA>          NA    35
 5 SCREENING 1         <NA>          NA  9233
 6 UNSCHEDULED 1.1     <NA>          NA   781
 7 UNSCHEDULED 1.2     <NA>          NA   157
 8 UNSCHEDULED 1.3     <NA>          NA    73
 9 UNSCHEDULED 12.1    <NA>          NA    71
10 UNSCHEDULED 13.1    <NA>          NA    15
# ℹ 17 more rows

Les visites programmées WEEK n se mappent vers Week n avec AVISITN = n ; tout ce qui n’est pas programmé devient NA et est exclu des visites d’analyse plus loin. Dériver la visite une seule fois ici signifie que la lecture démographique, le tableau de sécurité et la figure se découpent tous sur la même AVISIT.

Classer chaque valeur par rapport à sa plage de référence (ANRIND)

Une valeur de laboratoire n’est interprétable que par rapport à sa plage de référence. ADaM stocke ce jugement dans ANRIND, l’indicateur de plage de référence d’analyseLOW, NORMAL ou HIGH. La fonction derive_var_anrind() d’admiral le calcule, mais elle attend la plage dans les colonnes d’analyse ANRLO/ANRHI, donc nous les copions d’abord depuis la plage SDTM (LBSTNRLO/LBSTNRHI).

adlb <- adlb %>%
  mutate(
    ANRLO = LBSTNRLO,   # analysis range low  (from the SDTM normal range)
    ANRHI = LBSTNRHI    # analysis range high
  ) %>%
  derive_var_anrind()

adlb %>% count(ANRIND)
# A tibble: 4 × 2
  ANRIND     n
  <chr>  <int>
1 HIGH    1636
2 LOW      915
3 NORMAL 54108
4 <NA>    2921
adlb %>%
  filter(PARAMCD == "ALT", USUBJID == "01-701-1015") %>%
  select(USUBJID, PARAMCD, AVISIT, AVAL, ANRLO, ANRHI, ANRIND) %>%
  head(5)
# A tibble: 5 × 7
  USUBJID     PARAMCD AVISIT  AVAL ANRLO ANRHI ANRIND
  <chr>       <chr>   <chr>  <dbl> <dbl> <dbl> <chr> 
1 01-701-1015 ALT     <NA>      27     6    34 NORMAL
2 01-701-1015 ALT     Week 2    41     6    34 HIGH  
3 01-701-1015 ALT     Week 4    18     6    34 NORMAL
4 01-701-1015 ALT     Week 6    26     6    34 NORMAL
5 01-701-1015 ALT     Week 8    22     6    34 NORMAL

derive_var_anrind() lit la règle sur les données : en dessous de ANRLO c’est LOW, au-dessus de ANRHI c’est HIGH, entre les deux c’est NORMAL, et une valeur ou une plage manquante donne NA. Pour l’ALT de ce sujet, la valeur de la semaine 2 de 41 se situe au-dessus de la limite supérieure de 34, donc admiral la marque HIGH — le genre de marqueur qu’une revue de sécurité filtre directement.

Marquer l’enregistrement de la valeur initiale (ABLFL)

C’est la dérivation la plus importante de tout ADaM Findings. La valeur initiale est la référence à laquelle chaque valeur post-dose est comparée, et la définition standard est la dernière valeur non manquante le jour de la première dose ou avant. Nous marquons cet enregistrement avec ABLFL == "Y", un par sujet par paramètre, en utilisant la fonction derive_var_extreme_flag() d’admiral enveloppée dans restrict_derivation() pour que seuls les enregistrements pré-dose soient éligibles.

adlb <- adlb %>%
  restrict_derivation(
    derivation = derive_var_extreme_flag,
    args = params(
      by_vars = exprs(STUDYID, USUBJID, PARAMCD),   # one baseline per subject per parameter
      order   = exprs(ADT, VISITNUM, LBSEQ),        # ... the LAST such record by date
      new_var = ABLFL,
      mode    = "last"
    ),
    filter = (!is.na(AVAL) & ADT <= TRTSDT)         # ... that is on/before first dose, non-missing
  )

adlb %>% count(ABLFL)
# A tibble: 2 × 2
  ABLFL     n
  <chr> <int>
1 Y      9159
2 <NA>  50421
adlb %>%
  filter(PARAMCD == "ALT", USUBJID == "01-701-1015", !is.na(AVAL)) %>%
  arrange(ADT) %>%
  select(USUBJID, PARAMCD, VISIT, ADT, TRTSDT, AVAL, ABLFL) %>%
  head(5)
# A tibble: 5 × 7
  USUBJID     PARAMCD VISIT       ADT        TRTSDT      AVAL ABLFL
  <chr>       <chr>   <chr>       <date>     <date>     <dbl> <chr>
1 01-701-1015 ALT     SCREENING 1 2013-12-26 2014-01-02    27 Y    
2 01-701-1015 ALT     WEEK 2      2014-01-16 2014-01-02    41 <NA> 
3 01-701-1015 ALT     WEEK 4      2014-01-30 2014-01-02    18 <NA> 
4 01-701-1015 ALT     WEEK 6      2014-02-12 2014-01-02    26 <NA> 
5 01-701-1015 ALT     WEEK 8      2014-03-05 2014-01-02    22 <NA> 

Lisez les trois arguments comme la définition de la valeur initiale elle-même : by_vars — une valeur initiale par sujet par paramètre ; filter — uniquement les enregistrements le jour de la première dose ou avant avec une valeur ; order + mode = "last" — parmi ceux-ci, prendre le plus récent. Pour l’ALT de ce sujet, l’enregistrement de sélection (avant la première dose) est marqué ABLFL == "Y" ; chaque semaine post-dose est NA. Changez le filter ou le mode et vous changez ce que « valeur initiale » signifie pour toute la soumission — ce qui est exactement pourquoi cela vit dans un seul endroit auditable.

Calculer la variation par rapport à la valeur initiale (BASE, CHG, PCHG)

Avec la valeur initiale marquée, la variation par rapport à la valeur initiale tient en trois étapes déclaratives. derive_var_base() copie la AVAL de la valeur initiale marquée sur chaque ligne de ce couple sujet-paramètre comme BASE ; puis derive_var_chg() calcule CHG = AVAL − BASE et derive_var_pchg() calcule la variation en pourcentage.

adlb <- adlb %>%
  # copy the ABLFL=="Y" value onto every row of the subject-parameter
  derive_var_base(
    by_vars = exprs(STUDYID, USUBJID, PARAMCD),
    source_var = AVAL,
    new_var = BASE
  ) %>%
  derive_var_chg() %>%    # CHG  = AVAL - BASE
  derive_var_pchg()       # PCHG = 100 * (AVAL - BASE) / BASE

adlb %>%
  filter(PARAMCD == "ALT", USUBJID == "01-701-1015", !is.na(AVISIT)) %>%
  arrange(AVISITN) %>%
  select(USUBJID, AVISIT, BASE, AVAL, CHG, PCHG, ANRIND) %>%
  head(8)
# A tibble: 8 × 7
  USUBJID     AVISIT   BASE  AVAL   CHG   PCHG ANRIND
  <chr>       <chr>   <dbl> <dbl> <dbl>  <dbl> <chr> 
1 01-701-1015 Week 2     27    41    14  51.9  HIGH  
2 01-701-1015 Week 4     27    18    -9 -33.3  NORMAL
3 01-701-1015 Week 6     27    26    -1  -3.70 NORMAL
4 01-701-1015 Week 8     27    22    -5 -18.5  NORMAL
5 01-701-1015 Week 12    27    27     0   0    NORMAL
6 01-701-1015 Week 16    27    17   -10 -37.0  NORMAL
7 01-701-1015 Week 20    27    21    -6 -22.2  NORMAL
8 01-701-1015 Week 24    27    23    -4 -14.8  NORMAL

Lisez maintenant l’histoire d’un sujet directement sur les lignes : l’ALT part d’une valeur initiale de 27, bondit à 41 à la semaine 2 (CHG = +14, PCHG = +51.9%, marqué HIGH), puis revient dans la plage normale pour le reste de l’étude. Ce récit pour un seul sujet — valeur, valeur initiale, variation, marqueur de plage, au fil des visites — est ce à quoi sert la BDS, et il se généralise à chaque analyte et chaque sujet dans la même forme.

Marquer les enregistrements d’analyse (ANL01FL)

Un tableau de sécurité veut généralement un enregistrement par sujet par paramètre par visite — mais un sujet peut avoir des répétitions (un nouveau prélèvement non programmé, un doublon). ANL01FL marque l’unique enregistrement qui alimente l’analyse. Nous marquons la dernière valeur par couple sujet-paramètre-visite, restreinte aux visites d’analyse programmées.

adlb <- adlb %>%
  restrict_derivation(
    derivation = derive_var_extreme_flag,
    args = params(
      by_vars = exprs(STUDYID, USUBJID, PARAMCD, AVISIT),  # one per subject/param/visit
      order   = exprs(ADT, AVAL),
      new_var = ANL01FL,
      mode    = "last"
    ),
    filter = !is.na(AVISITN) & !is.na(AVAL)                # scheduled visits, non-missing
  )

adlb %>% count(ANL01FL)
# A tibble: 2 × 2
  ANL01FL     n
  <chr>   <int>
1 Y       48107
2 <NA>    11473

En aval, un tableau de laboratoire par visite filtre sur ANL01FL == "Y" et est garanti d’avoir une ligne par couple sujet-paramètre-visite — aucun double comptage dû à un prélèvement répété. Comme ABLFL, la règle de sélection vit à un seul endroit, de sorte que chaque tableau qui utilise ANL01FL sélectionne les enregistrements de la même façon.

Lire le jeu de données BDS final

Le résultat est un ADLB conforme : vertical, une ligne par sujet par paramètre par visite, avec les colonnes d’analyse que définit la BDS. Un dernier coup d’œil confirme la forme.

# The BDS shape: many rows per subject, the value named by PARAMCD
adlb %>%
  filter(USUBJID == "01-701-1015", AVISIT == "Week 2") %>%
  select(USUBJID, PARAMCD, AVISIT, AVAL, BASE, CHG, ANRIND, ABLFL, ANL01FL) %>%
  head(8)
# A tibble: 8 × 9
  USUBJID     PARAMCD AVISIT  AVAL  BASE    CHG ANRIND ABLFL ANL01FL
  <chr>       <chr>   <chr>  <dbl> <dbl>  <dbl> <chr>  <chr> <chr>  
1 01-701-1015 ALB     Week 2 39    38     1     NORMAL <NA>  Y      
2 01-701-1015 ALP     Week 2 50    34    16     NORMAL <NA>  Y      
3 01-701-1015 ALT     Week 2 41    27    14     HIGH   <NA>  Y      
4 01-701-1015 AST     Week 2 33    40    -7     NORMAL <NA>  Y      
5 01-701-1015 BASOLE  Week 2  0.15  0.4  -0.25  HIGH   <NA>  Y      
6 01-701-1015 BILI    Week 2  8.55 10.3  -1.71  NORMAL <NA>  Y      
7 01-701-1015 BUN     Week 2  3.57  3.57  0     NORMAL <NA>  Y      
8 01-701-1015 CA      Week 2  2.35  2.20  0.150 NORMAL <NA>  Y      
# How many parameters and how many analysis rows did we build?
adlb %>%
  summarise(
    n_rows       = n(),
    n_subjects   = n_distinct(USUBJID),
    n_parameters = n_distinct(PARAMCD)
  )
# A tibble: 1 × 3
  n_rows n_subjects n_parameters
   <int>      <int>        <int>
1  59580        254           47

Un sujet à une visite s’étend sur de nombreuses lignes — une par test de laboratoire — partageant chacune la machinerie AVAL/BASE/CHG. Cette uniformité est le gain de la BDS : le même code de dérivation, les mêmes programmes de tableaux, et les mêmes modèles de figures fonctionnent pour chaque analyte, et — en remplaçant LB par le domaine VS ou EG — pour les signes vitaux et l’ECG aussi. Construisez ADLB une fois et vous avez construit le modèle de chaque jeu de données Findings de la soumission.

🟢 Avec un agent IA

Demandez à Prova « comment dériver un grade de toxicité (ATOXGR) pour mes valeurs de laboratoire ADLB avec le système de notation NCI-CTCAE d’admiral ? » — elle répond en s’appuyant sur les leçons de ce pilier, avec du code admiral exécutable que vous pouvez essayer sur les données d’exemple pharmaverse. The runtime is the judge. Demandez à Prova →

Problèmes fréquents

Chaque CHG est NA, ou la valeur initiale est la mauvaise valeur. Cela remonte toujours à ABLFL. Si aucun enregistrement n’est marqué ABLFL == "Y" pour un couple sujet-paramètre, BASE est NA et chaque CHG aussi. La cause habituelle est le filter de la valeur initiale : ADT <= TRTSDT nécessite un vrai ADT (donc derive_vars_dt() doit s’exécuter en premier) et un vrai TRTSDT fusionné depuis ADSL. Si la valeur initiale ressemble à une valeur post-dose, vérifiez le mode — la valeur initiale est le mode = "last" des enregistrements pré-dose, pas le premier enregistrement global.

Confondre la forme BDS longue avec une forme large. Les nouveaux venus d’un état d’esprit large, une-colonne-par-test, essaient de lire AVAL sans PARAMCD et obtiennent du non-sens (l’ALT et la créatinine moyennées ensemble). En BDS, regroupez ou filtrez toujours par PARAMCD d’abord — AVAL n’a aucun sens à elle seule. Si un résumé semble impossible, vous avez presque certainement mélangé des paramètres.

Les lignes en double gonflent vos décomptes. Un sujet avec un nouveau prélèvement non programmé a deux enregistrements à ce qui ressemble à la même visite. Si un tableau compte en double, vous avez filtré sur AVISIT mais oublié ANL01FL == "Y" — ce marqueur existe précisément pour choisir l’unique enregistrement d’analyse par couple sujet-paramètre-visite. Filtrez dessus pour tout résumé par visite.

Questions fréquentes

La BDS est la structure ADaM pour les données Findingsune ligne par sujet, par paramètre, par point temporel. Une seule colonne AVAL contient la valeur, et PARAMCD nomme quel paramètre cette ligne mesure, de sorte qu’un seul jeu de données peut contenir de nombreux tests différents sans une colonne par test. ADLB (laboratoire), ADVS (signes vitaux) et ADEG (ECG) l’utilisent tous. La norme CDISC ADaM la définit.

ADSL est une ligne par sujet — large, avec une colonne pour chaque attribut. La BDS est verticale — une ligne par sujet par paramètre par point temporel — car un sujet a une valeur pour chaque test de laboratoire à chaque visite, bien trop nombreuses pour être des colonnes. ADSL contient les faits au niveau du sujet une seule fois ; les jeux de données BDS contiennent des mesures répétées, et refusionnent les variables au niveau du sujet d’ADSL par USUBJID.

Partez du domaine SDTM LB, fusionnez les variables de traitement depuis ADSL, puis dérivez la date d’analyse avec derive_vars_dt(), posez la colonne vertébrale de la BDS (PARAMCD/PARAM/AVAL), classez les valeurs par rapport à leur plage de référence avec derive_var_anrind(), marquez la valeur initiale avec derive_var_extreme_flag() (ABLFL), et calculez la variation par rapport à la valeur initiale avec derive_var_base(), derive_var_chg() et derive_var_pchg(). La séquence complète est détaillée ci-dessus et suit la vignette BDS finding d’admiral.

La valeur initiale est la dernière valeur non manquante le jour de la première dose ou avant (TRTSDT), une par sujet par paramètre. admiral la dérive avec derive_var_extreme_flag() à l’intérieur de restrict_derivation() : by_vars la clé par couple sujet-paramètre, le filter ne garde que les enregistrements pré-dose non manquants, et mode = "last" choisit le plus récent de ceux-ci. L’enregistrement choisi obtient ABLFL == "Y", et CHG/PCHG sont calculés par rapport à lui.

CHG est la variation absolue par rapport à la valeur initiale, AVAL − BASE (dans les unités propres du paramètre). PCHG est la variation en pourcentage, 100 × (AVAL − BASE) / BASE. admiral les dérive avec derive_var_chg() et derive_var_pchg() une fois que BASE est en place. Utilisez CHG quand le montant absolu importe et PCHG quand le mouvement relatif importe ou que les unités varient largement d’un sujet à l’autre.

ANRIND est l’indicateur de plage de référence d’analyseLOW, NORMAL ou HIGH, selon où AVAL se situe par rapport à la plage de référence (ANRLO/ANRHI). admiral le calcule avec derive_var_anrind(). C’est ce sur quoi les revues de sécurité filtrent pour trouver les valeurs hors plage, et la base des tableaux de transition (plage initiale vs plage post-initiale).

Testez vos connaissances

En utilisant l’objet adlb construit dans cette leçon, comptez combien d’enregistrements d’analyse (ANL01FL == "Y") sont LOW, NORMAL ou HIGH pour la créatinine (PARAMCD == "CREAT"), par bras de traitement. Quel bras a le plus de valeurs de créatinine HIGH ?

Vous avez déjà ANRIND et ANL01FL sur chaque ligne. Filtrez vers PARAMCD == "CREAT", les enregistrements d’analyse (ANL01FL == "Y") et les vrais bras de traitement (retirez "Screen Failure"), puis count() par TRT01A et ANRIND. Aucune nouvelle dérivation n’est nécessaire — les marqueurs font le travail.

adlb %>%
  filter(PARAMCD == "CREAT", ANL01FL == "Y", TRT01A != "Screen Failure") %>%
  count(TRT01A, ANRIND)

Le point est conceptuel : parce que ANRIND a été dérivé une fois, les deux mêmes marqueurs (ANRIND pour la classification, ANL01FL pour l’enregistrement d’analyse) répondent à toute question hors plage pour tout paramètre — vous ne redérivez jamais la logique de plage. Remplacez "CREAT" par "ALT" ou "K" et la même ligne fonctionne.

A. Une ligne par sujet B. Une ligne par sujet par paramètre par point temporel C. Une ligne par sujet par visite, tous les paramètres en colonnes

B. La structure de données de base est une ligne par sujet, par paramètre, par point temporel, avec la valeur dans une seule colonne AVAL nommée par PARAMCD. A est la structure au niveau du sujet d’ADSL. C est la forme large que la BDS évite délibérément — mettre les paramètres en colonnes est exactement ce qui empêche un jeu de données Findings de se généraliser à des dizaines de tests.

Conclusion

ADLB ressemble à un mur de lignes, mais c’est en réalité une seule idée répétée : la structure de données de base — une ligne par sujet, par paramètre, par point temporel, avec la valeur dans AVAL et l’analyte dans PARAMCD. Tout le reste est de la dérivation sur cette colonne vertébrale : derive_vars_dt() pour la date, derive_var_anrind() pour le marqueur de plage de référence, derive_var_extreme_flag() pour la valeur initiale (ABLFL), et derive_var_base() / derive_var_chg() / derive_var_pchg() pour les chiffres de variation par rapport à la valeur initiale sur lesquels repose chaque tableau de laboratoire de sécurité. Réussissez le marqueur de la valeur initiale et le reste suit. Et parce que la BDS est partagée, dès que vous savez construire ADLB vous savez construire ADVS et ADEG — même structure, mêmes gestes admiral, un domaine source différent. Cette réutilisation est la raison d’être de la BDS.

Note

Cette leçon est reproductible : chaque résultat de cette page a été produit par le code montré — copiez n’importe quel bloc et exécutez-le pour les reproduire. The runtime is the judge.

Leçons connexes

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

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

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

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

Partager cette pageXLinkedInRedditHN

Réutilisation

Citation

BibTeX
@online{2026,
  author = {},
  title = {ADLB en R avec admiral : analyse de laboratoire et BDS},
  date = {2026-06-30},
  url = {https://www.datanovia.com/learn/pharma-clinical/03-adam-admiral/adlb-bds-admiral},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“ADLB en R avec admiral : analyse de laboratoire et BDS.” 2026. June 30. https://www.datanovia.com/learn/pharma-clinical/03-adam-admiral/adlb-bds-admiral.