library(rtables)
library(tern)
library(dplyr)
library(pharmaverseadam)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
# Safety population: subjects who took >= 1 dose (SAFFL == "Y"); ADSL is subject-level
adsl <- pharmaverseadam::adsl |>
filter(SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
# Treatment-emergent AEs only (TRTEMFL == "Y"); ADAE is one row per AE record
adae <- pharmaverseadam::adae |>
filter(TRTEMFL == "Y", SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
# The AI-drafted layout: arms as columns, "subjects with >=1 AE", then per-PT counts
lyt <- basic_table(show_colcounts = TRUE) |>
split_cols_by("TRT01A") |>
summarize_num_patients(
var = "USUBJID",
.stats = "unique",
.labels = c(unique = "Subjects with >=1 AE")
) |>
count_occurrences(vars = "AEDECOD")Rédiger du code TLF clinique avec l’IA en R : puis valider les chiffres
Laissez un copilote IA rédiger un récapitulatif des événements indésirables rtables/tern en quelques secondes — puis exécutez le contrôle du dénominateur d’une ligne qui attrape un pourcentage faux d’apparence plausible avant qu’il n’atteigne un régulateur
Un copilote IA peut rédiger un TLF de soumission — un récapitulatif des événements indésirables rtables/tern par bras de traitement — en quelques secondes, et le brouillon est structurellement correct et obtient même les bons décomptes de sujets. Cette leçon fait exactement cela, puis valide le brouillon face à la population de sécurité. L’IA omet alt_counts_df, de sorte que chaque pourcentage est calculé sur les enregistrements d’événements indésirables au lieu des sujets : le taux d’événements indésirables du placebo affiche 23.1 % alors que le vrai taux est 75.6 % — une sous-déclaration catastrophique que l’en-tête trahit par (N=281). Un contrôle du N de la population de sécurité d’une ligne l’attrape, le correctif à un argument restaure les bons 75.6 % / 87.5 % / 94.4 %, et la responsabilité reste humaine : l’IA rédige vite la structure du tableau, vous êtes maître de la population et du dénominateur. Sur des données pharmaverse publiques, de sorte que chaque ligne s’exécute.
- Un copilote IA rédige un TLF rapidement. Un TLF (une table, une liste ou une figure — les livrables de soumission à partir desquels un rapport d’étude clinique est construit) comme un récapitulatif des événements indésirables par bras de traitement est exactement le code de disposition rtables/tern bien balisé qu’un assistant produit bien.
- Le brouillon est structurellement correct — et obtient même les bons décomptes. Le tableau de l’IA a les bras en colonnes et le nombre correct de sujets (n, petit n) dans chaque cellule. Ce qu’il se trompe silencieusement, c’est le dénominateur — le N (grand N, le décompte de population de la colonne) par lequel chaque pourcentage est divisé.
- La prise signature : l’IA omet un argument (
alt_counts_df), de sorte que chaque pourcentage est divisé par les enregistrements d’événements indésirables au lieu des sujets. L’en-tête placebo affiche(N=281)et le taux d’événements indésirables affiche 23.1 % — alors que la population de sécurité compte 86 sujets et que le vrai taux est 75.6 %. Un contrôletable(adsl$TRT01A)d’une ligne l’attrape. - Le sens compte : ce bug SOUS-DÉCLARE. Comme il y a plus d’enregistrements d’événements indésirables que de sujets, le mauvais dénominateur réduit chaque taux — un tableau qui annonce à un régulateur que 23.1 % des sujets placebo ont eu un événement indésirable alors que les trois quarts en ont eu. C’est un échec plus dangereux qu’un surdécompte.
- Rédigé par l’IA n’est pas validé par l’IA. L’IA rédige la structure ; le programmeur est maître de la population d’analyse et du dénominateur, et le QC humain attrape le pourcentage faux d’apparence plausible. La responsabilité ne se transfère jamais à l’outil.
Introduction
Un statisticien demande le tableau de sécurité : les événements indésirables par bras de traitement, prêts pour le rapport d’étude clinique. Un copilote IA — un assistant de codage IA tel que Claude, ChatGPT ou GitHub Copilot — rédigera la disposition rtables / tern plus vite que vous n’ouvrez la documentation du package. C’est vraiment utile : un récapitulatif des événements indésirables est du code répétitif et bien balisé, et l’assistant en a vu des milliers d’exemples.
Mais ceci est un TLF — une table, une liste ou une figure, les livrables à partir desquels un CSR (rapport d’étude clinique, dont la structure est définie par l’ICH E3) est assemblé et que lit un régulateur — donc « le code s’est exécuté » n’a jamais été le critère pour présenter un chiffre à un relecteur. Tout l’enjeu de cette leçon est la frontière : un copilote IA rédige bien la structure du tableau, mais il n’est pas maître des chiffres. Il obtient les bons décomptes de sujets et se trompe silencieusement de dénominateur, et les pourcentages qui en résultent semblent tout à fait plausibles. Rédigez, puis validez.
Si vous n’avez jamais construit ce tableau à la main, construisez-le d’abord à partir de zéro dans la leçon sur le récapitulatif des événements indésirables — cette leçon enseigne en détail la mécanique rtables/tern (summarize_num_patients(), count_occurrences(), le dénominateur alt_counts_df). Cette leçon est le complément agentique : non pas comment construire le tableau, mais comment travailler avec une IA qui le rédige — et le contrôle dont l’humain est maître qui maintient les chiffres au niveau soumission. Elle s’appuie sur la carte des capacités qui ouvre cette série et sa démo de dérivation ADTTE. Nous travaillons sur des données pharmaverse d’exemple publiques, de sorte que chaque ligne de validation s’exécute telle quelle.
Le tableau, en une maquette
Voici le tableau que veut le statisticien, décrit à la façon dont une maquette (le modèle vierge qu’un programmeur remplit) le décrit :
Pour chaque bras de traitement (une colonne, coiffée de son décompte de sujets
(N=xx)), le nombre et le pourcentage de sujets ayant au moins un événement indésirable émergent sous traitement — un TEAE, un événement indésirable apparu ou aggravé après la première dose — puis, en dessous et en retrait, le décompte et le pourcentage pour chaque événement indésirable individuel selon son PT (Preferred Term, le nom du dictionnaire de l’événement). Les pourcentages sont ceux de la population de sécurité du bras : chaque sujet ayant pris au moins une dose du médicament à l’étude.
Lisez-le comme un relecteur QC, car deux propositions constituent toute la leçon :
- Le numérateur (n, petit n) — le nombre de sujets ayant l’événement. Un sujet qui a le même événement indésirable trois fois est compté une seule fois.
- Le dénominateur (N, grand N) — la population de sécurité du bras, tirée des données au niveau sujet, et non des enregistrements d’événements indésirables. C’est le
(N=xx)dans l’en-tête de colonne, et c’est le nombre sur lequel l’IA est sur le point de se tromper.
Un copilote IA fait correspondre proprement cette maquette à l’API de disposition de rtables. Savoir s’il fait correspondre correctement le dénominateur est la question à laquelle répond le reste de cette leçon.
La disposition rédigée par l’IA
Sollicité avec la maquette ci-dessus et invité à « rédiger le récapitulatif des événements indésirables rtables/tern par bras », un assistant produit le bloc ci-dessous. C’est du rtables idiomatique : une maquette basic_table(), des colonnes scindées par bras de traitement, une ligne summarize_num_patients() pour « les sujets avec ≥1 événement indésirable », et count_occurrences() pour les fréquences par événement. Trois éléments font le travail, et il vaut la peine de nommer chacun avant que le code ne s’exécute :
basic_table(show_colcounts = TRUE)démarre la disposition et demande à rtables d’afficher le décompte de population(N=xx)dans chaque en-tête de colonne — l’affichage du dénominateur.split_cols_by("TRT01A")crée une colonne par bras de traitement.summarize_num_patients()compte les sujets distincts ayant un événement ;count_occurrences()compte les sujets par événement indésirable individuel (AEDECOD, le Preferred Term).
Le brouillon filtre les deux jeux de données sur la population de sécurité (SAFFL == "Y") et les événements indésirables sur ceux émergents sous traitement (TRTEMFL == "Y"), et il transforme la variable de bras en facteur pour que les colonnes sortent dans l’ordre du protocole — Placebo, puis les deux doses actives. Voici ce qu’il a produit :
La disposition est correcte. La question est de savoir ce que vous passez à build_table() — et c’est là que le brouillon dérape.
Construire le brouillon : les chiffres semblent plausibles
L’IA termine avec build_table(lyt, df = adae) — elle passe les données d’événements indésirables et s’arrête là. Cela s’exécute sans la moindre erreur ni le moindre avertissement, et le tableau qu’elle imprime a tout à fait la forme d’une soumission :
library(rtables)
library(tern)
library(dplyr)
library(pharmaverseadam)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
adsl <- pharmaverseadam::adsl |>
filter(SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
adae <- pharmaverseadam::adae |>
filter(TRTEMFL == "Y", SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
lyt <- basic_table(show_colcounts = TRUE) |>
split_cols_by("TRT01A") |>
summarize_num_patients(var = "USUBJID", .stats = "unique",
.labels = c(unique = "Subjects with >=1 AE")) |>
count_occurrences(vars = "AEDECOD")
# The AI-drafted build call: adverse-event data only, no denominator source
tbl_wrong <- build_table(lyt, df = adae)
head(tbl_wrong, 5) Placebo Xanomeline Low Dose Xanomeline High Dose
(N=281) (N=427) (N=414)
————————————————————————————————————————————————————————————————————————————————
Subjects with >=1 AE 65 (23.1%) 84 (19.7%) 68 (16.4%)
ABDOMINAL DISCOMFORT 0 0 1 (0.2%)
ABDOMINAL PAIN 1 (0.4%) 3 (0.7%) 1 (0.2%)
ACROCHORDON EXCISION 0 0 1 (0.2%)
ACTINIC KERATOSIS 0 0 1 (0.2%)
Lisez la première ligne. 65 sujets placebo ont eu au moins un événement indésirable, à 23.1 %. Le décompte de 65 est correct. Le pourcentage, non — et rien dans le tableau ne le signale. Un relecteur QC qui parcourt du regard les décomptes laisse passer ce tableau ; il faut regarder le dénominateur.
Valider : le contrôle du N de la population de sécurité
La validation la moins coûteuse est celle qui attrape ce bug en une seule ligne. Chaque pourcentage du tableau est n / N, et vous savez ce que N doit être : le nombre de sujets dans la population de sécurité de chaque bras. Comptez-le directement à partir des données au niveau sujet :
library(dplyr)
library(pharmaverseadam)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
adsl <- pharmaverseadam::adsl |>
filter(SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
# The known safety-population N per arm -- the ONLY correct denominator
table(adsl$TRT01A)
Placebo Xanomeline Low Dose Xanomeline High Dose
86 96 72
La population de sécurité compte 86 · 96 · 72 sujets. Mais l’en-tête du tableau indiquait (N=281) pour le placebo. Ces deux nombres ne peuvent pas être corrects tous les deux, et le contrôle QC vient de vous dire lequel le tableau utilise. Confirmez-le en lisant les décomptes de colonne directement sur le tableau construit :
library(rtables)
library(tern)
library(dplyr)
library(pharmaverseadam)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
adae <- pharmaverseadam::adae |>
filter(TRTEMFL == "Y", SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
lyt <- basic_table(show_colcounts = TRUE) |>
split_cols_by("TRT01A") |>
summarize_num_patients(var = "USUBJID", .stats = "unique",
.labels = c(unique = "Subjects with >=1 AE")) |>
count_occurrences(vars = "AEDECOD")
tbl_wrong <- build_table(lyt, df = adae)
# What denominator did the table actually use?
col_counts(tbl_wrong) # 281 427 414 -- wrong[1] 281 427 414
c(records_placebo = nrow(adae[adae$TRT01A == "Placebo", ]))records_placebo
281
Le tableau a divisé par 281, et 281 est exactement le nombre d’enregistrements d’événements indésirables du placebo. Quand vous ne dites pas à rtables d’où vient le décompte de population, il se rabat sur le comptage des lignes des données à partir desquelles vous avez construit le tableau — et ADAE (le jeu de données d’analyse des événements indésirables) a une ligne par événement, non par sujet. Le dénominateur est devenu les événements, non les sujets.
La lecture honnête : cela SOUS-DÉCLARE le taux d’événements indésirables
Le sens de l’erreur compte. Il y a toujours au moins autant d’enregistrements d’événements indésirables que de sujets — les sujets ont plusieurs événements — donc le mauvais dénominateur (281) est plus grand que le bon (86). Un dénominateur plus grand rend chaque pourcentage plus petit :
n_placebo <- 65 # subjects with >=1 AE (correct numerator)
N_records <- 281 # AE records (WRONG denominator the AI used)
N_subjects <- 86 # safety population (CORRECT denominator)
c(shown_rate = round(100 * n_placebo / N_records, 1), # 23.1% -- what the AI table prints
true_rate = round(100 * n_placebo / N_subjects, 1)) # 75.6% -- the realityshown_rate true_rate
23.1 75.6
Le tableau de l’IA annonce à un régulateur que 23.1 % des sujets placebo ont eu un événement indésirable. La vérité est 75.6 % — plus de trois sur quatre. Ce n’est pas une chicane d’arrondi ; c’est un tableau qui sous-déclare la charge en événements indésirables d’un facteur d’environ trois, et il la sous-déclare exactement sous la forme qu’un relecteur s’attend à voir. Un surdécompte attirerait au moins l’attention. Un sous-décompte assuré, net et plausible est l’échec le plus dangereux, et le fait que les numérateurs soient corrects est ce qui le fait passer au travers d’une revue rapide.
Le correctif tient en un argument
La correction tient en un seul argument. build_table() accepte un alt_counts_df — le jeu de données alternatif que rtables utilise uniquement pour calculer les décomptes de colonne. Pointez-le vers l’ADSL au niveau sujet (le jeu de données d’analyse au niveau sujet), et chaque dénominateur devient la population de sécurité :
library(rtables)
library(tern)
library(dplyr)
library(pharmaverseadam)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
adsl <- pharmaverseadam::adsl |>
filter(SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
adae <- pharmaverseadam::adae |>
filter(TRTEMFL == "Y", SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
lyt <- basic_table(show_colcounts = TRUE) |>
split_cols_by("TRT01A") |>
summarize_num_patients(var = "USUBJID", .stats = "unique",
.labels = c(unique = "Subjects with >=1 AE")) |>
count_occurrences(vars = "AEDECOD")
# The fix: alt_counts_df = adsl -> denominators come from the SUBJECT-level data
tbl_right <- build_table(lyt, df = adae, alt_counts_df = adsl)
head(tbl_right, 5) Placebo Xanomeline Low Dose Xanomeline High Dose
(N=86) (N=96) (N=72)
————————————————————————————————————————————————————————————————————————————————
Subjects with >=1 AE 65 (75.6%) 84 (87.5%) 68 (94.4%)
ABDOMINAL DISCOMFORT 0 0 1 (1.4%)
ABDOMINAL PAIN 1 (1.2%) 3 (3.1%) 1 (1.4%)
ACROCHORDON EXCISION 0 0 1 (1.4%)
ACTINIC KERATOSIS 0 0 1 (1.4%)
Désormais les en-têtes affichent (N=86), (N=96), (N=72) — la population de sécurité — et les taux affichent 75.6 % · 87.5 % · 94.4 %, la vraie charge en événements indésirables. Confirmez que le dénominateur est corrigé :
library(rtables)
library(tern)
library(dplyr)
library(pharmaverseadam)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
adsl <- pharmaverseadam::adsl |>
filter(SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
adae <- pharmaverseadam::adae |>
filter(TRTEMFL == "Y", SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
lyt <- basic_table(show_colcounts = TRUE) |>
split_cols_by("TRT01A") |>
summarize_num_patients(var = "USUBJID", .stats = "unique",
.labels = c(unique = "Subjects with >=1 AE")) |>
count_occurrences(vars = "AEDECOD")
tbl_right <- build_table(lyt, df = adae, alt_counts_df = adsl)
col_counts(tbl_right) # 86 96 72 -- the safety population[1] 86 96 72
all(col_counts(tbl_right) == c(86, 96, 72))[1] TRUE
86 · 96 · 72, correspondant exactement au contrôle table(adsl$TRT01A). Un argument a fait passer le dénominateur des enregistrements d’événements aux sujets et a fait passer le taux du placebo de 23.1 % à 75.6 %.
Pourquoi les décomptes étaient corrects mais les pourcentages faux
La partie instructive est de savoir pourquoi le bug est si discret : l’IA a obtenu les bons numérateurs. Regardez ce que chaque fonction compte réellement. summarize_num_patients(var = "USUBJID", .stats = "unique") compte les sujets distincts qui apparaissent dans les données d’événements indésirables — et c’est 65 sujets placebo, que vous passiez ou non alt_counts_df. Le numérateur lit les données à partir desquelles vous avez construit le tableau ; le dénominateur lit alt_counts_df (ou, à défaut, se rabat sur le nombre de lignes de ces mêmes données). Les deux sont calculés à partir de sources différentes, ce qui explique pourquoi omettre l’un n’a cassé que la moitié de la fraction :
library(dplyr)
library(pharmaverseadam)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
adae <- pharmaverseadam::adae |>
filter(TRTEMFL == "Y", SAFFL == "Y") |>
mutate(TRT01A = factor(TRT01A, levels = arm_levels))
# The numerator is a DISTINCT-subject count -- unaffected by the denominator bug
adae |>
group_by(TRT01A) |>
summarize(subjects_with_AE = n_distinct(USUBJID), ae_records = n(), .groups = "drop")# A tibble: 3 × 3
TRT01A subjects_with_AE ae_records
<fct> <int> <int>
1 Placebo 65 281
2 Xanomeline Low Dose 84 427
3 Xanomeline High Dose 68 414
65 · 84 · 68 sujets distincts — les bons numérateurs, et exactement ce que les deux tableaux ont imprimé. Les décomptes d’enregistrements d’événements (281 · 427 · 414) sont ce par quoi le tableau défectueux a divisé. Un QC qui ne vérifie que les décomptes voit deux nombres corrects et laisse passer le tableau ; c’est le ratio qui est faux, et seul un contrôle du dénominateur le trouve.
Qui est maître de quoi
Alignez ce que l’IA a fait et ce que vous avez fait, et la division est exacte. L’assistant a rédigé la structure vite — l’échafaudage basic_table() / split_cols_by() / summarize_num_patients() / count_occurrences(), les bras en colonnes, la disposition rtables idiomatique — et il a même produit les bons décomptes de sujets. C’est du temps réellement gagné. Mais il a pris une décision silencieuse qu’un humain plus un contrôle QC ont dû attraper : il a laissé le dénominateur se rabattre sur les enregistrements d’événements indésirables au lieu de la population de sécurité, et a produit un tableau qui sous-déclarait le taux d’événements indésirables d’un facteur trois tout en paraissant parfaitement prêt pour la soumission.
La responsabilité n’a donc jamais bougé. Vous êtes maître de la population d’analyse — les sujets dont les pourcentages rendent compte — et du dénominateur qui l’encode, et vous êtes maître du QC qui prouve que les chiffres sont justes, non pas seulement plausibles. L’IA rédige ; le programmeur qualifié et le promoteur restent redevables de chaque valeur que le tableau montre à un régulateur. Rédigé par l’IA n’est pas validé par l’IA : une exécution propre et un tableau net sont le début de la validation, jamais sa fin.
Demandez à Prova « Un copilote IA a rédigé pour moi un récapitulatif des événements indésirables rtables/tern — comment le mettre en QC pour qu’un mauvais dénominateur ou une mauvaise population d’analyse ne puisse pas livrer un pourcentage faux d’apparence plausible ? » — elle répond en s’appuyant sur les leçons de validation de cette série, avec les contrôles exécutables de population de sécurité et col_counts() que vous pouvez essayer sur votre propre brouillon. The runtime is the judge. Demandez à Prova →
Problèmes fréquents
L’IA omet alt_counts_df, donc chaque pourcentage est faux. C’est l’erreur spécifique à tern la plus courante, et celle autour de laquelle cette leçon est construite. Sans alt_counts_df, rtables prend le décompte de colonne dans le nombre de lignes des données à partir desquelles vous avez construit le tableau — pour un jeu de données au niveau événement comme ADAE, ce sont des enregistrements, non des sujets. Passez toujours alt_counts_df = adsl (filtré sur la même population d’analyse) pour que le dénominateur soit la population de sécurité, et confirmez-le avec col_counts(tbl).
Les décomptes semblent corrects, donc le tableau semble correct. Le signe le plus dangereux de ce bug est qu’il n’y a aucune erreur et que les numérateurs sont corrects — seuls les pourcentages sont faussés. Ne mettez jamais un tableau d’événements indésirables en QC en parcourant du regard les seuls décomptes. Vérifiez explicitement le dénominateur : table(adsl$TRT01A) donne la population de sécurité, col_counts(tbl) donne ce que le tableau a réellement utilisé, et les deux doivent correspondre.
add_colcounts() versus basic_table(show_colcounts = TRUE). Les deux affichent l’en-tête (N=xx) — ils sont interchangeables pour montrer le décompte. Aucun ne fixe le dénominateur : le décompte provient toujours d’alt_counts_df dans build_table(). Afficher le décompte de colonne et fournir le bon décompte de colonne sont deux étapes distinctes ; faites les deux.
Les échecs de sélection faussent votre N si vous omettez SAFFL. L’ADSL au niveau sujet inclut des échecs de sélection qui n’ont jamais été dosés. La population de sécurité est uniquement SAFFL == "Y", et vous devez filtrer là-dessus à la fois l’ADSL (la source du dénominateur) et l’ADAE (les événements). Omettez le filtre sur l’ADSL et votre (N=xx) est le décompte des inclus, non la population de sécurité — un dénominateur différent, et faux.
Questions fréquentes
Comme outil de rédaction, oui — comme source de chiffres validés, non. Un copilote IA rédige la disposition rtables/tern d’un tableau d’événements indésirables ou de démographie (l’échafaudage basic_table() / split_cols_by() / summarize_num_patients() / count_occurrences()) bien plus vite que vous ne le tapez, et il obtient généralement la bonne structure et même les bons décomptes de sujets. Mais le brouillon est non qualifié : un programmeur qualifié doit le relire et il doit franchir les mêmes barrières de QC que tout tableau franchit — ici, un contrôle du dénominateur face à la population de sécurité connue. Utilisé ainsi, il accélère la rédaction sans affaiblir la validation.
Parce que le numérateur et le dénominateur proviennent de sources différentes. summarize_num_patients(.stats = "unique") et count_occurrences() comptent les sujets distincts dans les données d’événements indésirables — donc les décomptes sont corrects. Le pourcentage divise cela par le décompte de colonne, qui provient d’alt_counts_df ; omettez-le et rtables se rabat sur le nombre de lignes de l’ADAE au niveau événement (enregistrements, non sujets). Le résultat est un numérateur correct sur un mauvais dénominateur — un pourcentage plausible, net et faux.
alt_counts_df est l’argument de build_table() qui indique à rtables dans quel jeu de données prendre les décomptes de population de colonne (N) — le jeu de données alternatif utilisé uniquement lors du calcul des décomptes de colonne. Pour un tableau d’événements indésirables, vous construisez à partir de l’ADAE au niveau événement, mais le dénominateur doit être la population de sécurité au niveau sujet, donc vous passez alt_counts_df = adsl. Sans lui, le dénominateur revient par défaut au nombre de lignes de l’ADAE (enregistrements d’événements indésirables), ce qui est faux pour toute statistique en « pourcentage de sujets ».
Calculez la population que vous attendez directement à partir des données au niveau sujet — table(adsl$TRT01A) pour la population de sécurité par bras — puis lisez ce que le tableau construit a réellement utilisé avec col_counts(tbl), et confirmez qu’ils correspondent. Si l’en-tête montre des décomptes d’enregistrements d’événements indésirables (plus grands que les décomptes de sujets), alt_counts_df a été omis. Cette seule comparaison attrape le bug de dénominateur tern le plus courant en une seule ligne.
Pas tel quel. Une sortie d’IA est un brouillon non qualifié, quel que soit le modèle qui l’a produit. Elle devient acceptable seulement après qu’un programmeur qualifié a relu chaque ligne au regard de la maquette du tableau et qu’elle a franchi la même validation que tout TLF franchit — les chiffres réconciliés face à la population d’analyse, un contrôle indépendant des décomptes et des dénominateurs, et une revue documentée. La source du brouillon est sans importance pour la barrière ; la barrière est la même qu’un tableau écrit à la main doit franchir.
Testez vos connaissances
Un copilote IA rédige un récapitulatif des événements indésirables et termine avec build_table(lyt, df = adae) — sans alt_counts_df. Le code s’exécute sans erreur et imprime un tableau net dont les décomptes de sujets sont tous corrects, mais dont l’en-tête placebo affiche (N=281).
- En prose : nommez le contrôle d’une ligne qui attrape cela avant que le tableau ne soit livré, expliquez quel nombre est faux (le décompte ou le pourcentage) et pourquoi, et indiquez dans quel sens court l’erreur (surdéclaration ou sous-déclaration du taux d’événements indésirables).
- En R : étant donné un numérateur correct de 65 sujets placebo, un décompte d’enregistrements d’événements indésirables de 281 et une population de sécurité de 86, calculez à la fois le taux affiché et le vrai taux, et écrivez une assertion d’une ligne qui signale le dénominateur du tableau comme faux.
Les numérateurs sont bons ; c’est le dénominateur qui est faux. La population de sécurité par bras provient de l’ADSL au niveau sujet (table(adsl$TRT01A)), et le dénominateur réel du tableau provient de col_counts(tbl) — comparez-les. Comme il y a plus d’enregistrements d’événements indésirables (281) que de sujets (86), diviser par les enregistrements rend le pourcentage trop petit, donc le tableau sous-déclare le taux.
Étape 1. Le contrôle est table(adsl$TRT01A) (le N connu de la population de sécurité) comparé à col_counts(tbl) (ce que le tableau a utilisé) — ou simplement lire l’en-tête (N=281) en sachant que la population de sécurité du placebo est 86. Les décomptes sont corrects ; les pourcentages sont faux, car le pourcentage divise le bon décompte de sujets par le mauvais dénominateur (enregistrements d’événements indésirables, 281, au lieu des sujets, 86). Comme 281 > 86, chaque taux ressort plus petit que la vérité, donc le tableau sous-déclare la charge en événements indésirables — 23.1 % affiché contre un vrai 75.6 %.
n_placebo <- 65 # subjects with >=1 AE (correct numerator)
N_records <- 281 # AE records (WRONG denominator, no alt_counts_df)
N_subjects <- 86 # safety population (CORRECT denominator)
shown_rate <- 100 * n_placebo / N_records # 23.1%
true_rate <- 100 * n_placebo / N_subjects # 75.6%
c(shown = round(shown_rate, 1), true = round(true_rate, 1))
# The gate: the table's denominator must equal the safety population
stopifnot(N_records == N_subjects) # ERROR -> the denominator is wrongLe stopifnot() échoue bruyamment sur le mauvais dénominateur, transformant un sous-décompte silencieux et plausible en une erreur immédiate. Le correctif tient en un argument : build_table(lyt, df = adae, alt_counts_df = adsl).
A. Rien — les décomptes sont corrects, donc le tableau est correct. B. Le dénominateur : alt_counts_df a été omis, donc les pourcentages divisent par les enregistrements d’événements indésirables (281) au lieu des sujets (86), sous-déclarant chaque taux. C. Le numérateur : summarize_num_patients() a compté des événements au lieu de sujets.
B. Les décomptes (numérateurs) sont des décomptes de sujets distincts et sont corrects, ce qui écarte C et est précisément ce qui rend le bug dangereux — rien dans les décomptes n’a l’air faux. L’en-tête (N=281) montre que le dénominateur s’est rabattu sur les enregistrements d’événements indésirables parce qu’alt_counts_df n’a pas été fourni ; passer alt_counts_df = adsl restaure (N=86) et le vrai taux de 75.6 %, ce qui écarte A.
Conclusion
Un copilote IA est un véritable accélérateur pour le travail sur les TLF : il transforme une maquette de tableau en un récapitulatif des événements indésirables rtables/tern idiomatique en quelques secondes, et l’échafaudage qu’il produit — les bras en colonnes, la ligne « sujets avec ≥1 événement indésirable », les décomptes par événement — est la partie répétitive que vous alliez taper de toute façon. Il obtient même les bons décomptes de sujets. Mais le brouillon est arrivé avec une erreur de dénominateur silencieuse, et cette leçon a montré pourquoi ce mot compte : un tableau qui sous-déclarait le taux d’événements indésirables du placebo à 23.1 % alors que les trois quarts des sujets avaient eu un événement, attrapé non par la lecture des décomptes mais par un contrôle table(adsl$TRT01A) d’une ligne face à la population de sécurité, et corrigé par un seul alt_counts_df = adsl. L’IA rédige la structure vite ; vous êtes maître de la population d’analyse, du dénominateur et du QC qui prouve que les chiffres sont justes. L’IA change la vitesse du premier brouillon. Elle ne change rien à qui répond du tableau. The runtime is the judge.
Cette leçon est reproductible : la disposition rédigée par l’IA, le tableau au mauvais dénominateur, le contrôle de population de sécurité qui l’attrape et le correctif à un argument se reproduisent tous ici sur des données pharmaverse publiques. Copiez n’importe quel bloc et exécutez-le pour les reproduire. L’étape de rédaction par l’IA se produit dans votre propre outillage ; le R qui valide le brouillon est réel et s’exécute. The runtime is the judge.
Leçons connexes
- Le tableau des événements indésirables en R : récapituler les TEAE par SOC et Preferred Term — la construction rtables/tern à partir de zéro qu’un assistant est, de fait, en train de rédiger pour vous ici ; commencez là pour la mécanique. · L’IA pour la programmation clinique : ce qui marche, ce qui échoue — la carte des capacités qui ouvre cette série et le principe « valider avant d’utiliser » que ce tableau met en œuvre. · L’IA rédige une dérivation ADTTE admiral à partir d’un critère — la même boucle rédiger-puis-valider appliquée à une dérivation ADaM de délai jusqu’à l’événement. · Générer des métadonnées Define-XML avec l’IA — la boucle l’IA-rédige-puis-l’outillage-valide appliquée aux métadonnées de soumission.
- Où cela s’inscrit : ceci est l’instance TLF de la série de programmation clinique agentique — un vrai tableau de soumission, une vraie disposition rédigée par l’IA, et le contrôle du dénominateur d’une ligne qui maintient ses chiffres au niveau soumission. Elle applique la discipline rédiger-puis-valider de la série à la sortie qu’un régulateur lit le plus attentivement.
Réutilisation
Citation
@online{2026,
author = {},
title = {Rédiger du code TLF clinique avec l’IA en R : puis valider
les chiffres},
date = {2026-07-02},
url = {https://www.datanovia.com/learn/pharma-clinical/08-agentic-clinical-programming/ai-drafts-tlf-code},
langid = {fr}
}