L’IA comme double-programmeur QC en R : peut-elle être le contrôle indépendant ?

Laissez un copilote IA écrire la re-dérivation indépendante en QC par double programmation et diffdf attrape un vrai bug de production — puis voyez le faux rapport propre qui montre pourquoi une IA n’est pas un relecteur indépendant qualifié, et pourquoi un humain est maître de l’approbation

La QC par double programmation valide un jeu de données clinique dérivé en faisant re-dériver ce dernier par un programmeur indépendant à partir de la spécification, puis en comparant les deux avec diffdf. Un copilote IA peut-il être ce second programmeur ? Cette leçon exécute les deux versants de la réponse sur des données pharmaverse publiques. Dans le premier scénario, une re-dérivation rédigée par l’IA attrape un vrai bug de production — un BMI initial qui a oublié d’élever la taille au carré — et diffdf pointe les 254 sujets. Dans le second, une spécification laconique et ambiguë amène le code de production et la QC IA à commettre le même faux pas d’unité, diffdf rapporte « No issues were found! », et ce rapport propre est faux. La limite honnête : une IA peut rédiger du code de QC et être une seconde paire d’yeux utile, mais son indépendance n’est pas garantie et elle ne peut pas porter la responsabilité — un humain est maître de l’approbation de la QC.

Date de publication

2 juillet 2026

Modifié

7 juillet 2026

AstucePoints clés
  • Un copilote IA peut écrire la re-dérivation indépendante en QC par double programmation. Confiez-lui la même spécification, demandez-lui de re-dériver la variable d’une autre façon, et comparez avec diffdf() — l’IA joue le second programmeur, vite.
  • Quand les deux dérivations diffèrent réellement, ça marche. Un BMI de production (indice de masse corporelle, le poids en kg divisé par la taille en mètres au carré) qui a oublié d’élever la taille au carré s’exécute proprement et paraît plausible ; la re-dérivation rédigée par l’IA est en désaccord sur les 254 sujets, et diffdf() les nomme tous.
  • Le hic : une IA n’est pas un second programmeur indépendant. Face à la même spécification laconique et ambiguë, le code de production et la QC IA commettent le même faux pas d’unité — et diffdf() rapporte « No issues were found! ». Un rapport propre entre deux dérivations corrélées n’est pas une validation.
  • Un faux rapport propre ressemble exactement à un vrai. Seule une re-dérivation vraiment indépendante — celle qui vérifie les métadonnées d’unité que la spécification ambiguë a omises — attrape l’erreur partagée (254 écarts).
  • L’IA rédige le code de QC ; un humain est maître de la QC. Un copilote IA est une seconde paire d’yeux utile, mais sa sortie est elle-même non qualifiée, son indépendance n’est pas garantie, et elle ne peut pas signer un enregistrement de QC. Le programmeur qualifié est maître du contrôle indépendant et de la responsabilité.

Introduction

La double programmation — aussi appelée programmation indépendante — est la façon dont un jeu de données clinique dérivé gagne la confiance. Un programmeur de production construit le jeu de données qui sera soumis ; un second programmeur indépendant, travaillant à partir de la même spécification mais écrivant son propre code, le re-dérive ; les deux sont comparés, et s’ils concordent, la dérivation est validée. La QC (contrôle qualité) signifie ici exactement cela : une re-dérivation indépendante, pas une revue de code. Les mécanismes de la comparaison sont exposés dans La double programmation en R avec diffdf — clés, tolérance, typage strict, lecture du rapport. Cette leçon ne les réenseigne pas. Elle pose une question différente, plus tranchante : un copilote IA peut-il être ce second programmeur indépendant ?

La réponse est un authentique « oui, et » — oui, elle peut écrire une re-dérivation rapide qui attrape de vrais bugs ; et la propriété même qui fait fonctionner la double programmation — l’indépendance — est la seule chose qu’une IA ne peut pas garantir. Nous exécutons les deux moitiés sur des données pharmaverse publiques, de sorte que chaque ligne diffdf() s’exécute. D’abord une QC IA qui attrape un vrai bug de production. Puis le retournement honnête : un faux rapport propre, où l’IA est d’accord avec une mauvaise réponse aussi sûrement qu’elle le serait avec une bonne.

La tâche de QC : le BMI initial sur ADVS

La variable sous QC est un BMI initial dérivé. Nous le construisons à partir d’ADVS (le jeu de données d’analyse des signes vitaux), un jeu de données BDS (Basic Data Structure — structure de données de base : une ligne par sujet, par paramètre et par visite) qui porte WEIGHT en kilogrammes et HEIGHT en centimètres. Nous prenons l’enregistrement initial de chaque sujet — la ligne marquée ABLFL == "Y" (l’indicateur d’enregistrement initial) — et remettons le poids et la taille côte à côte. La taille initiale est mesurée une seule fois à la sélection, donc elle existe pour 254 des sujets inclus ; c’est là l’ensemble d’analyse.

library(pharmaverseadam)
library(dplyr)
library(tidyr)
data("advs", package = "pharmaverseadam")

# Baseline weight + height, one row per subject (ABLFL == "Y")
bl <- advs %>%
  filter(PARAMCD %in% c("WEIGHT", "HEIGHT"), ABLFL == "Y") %>%
  select(USUBJID, PARAMCD, AVAL) %>%
  distinct() %>%
  pivot_wider(names_from = PARAMCD, values_from = AVAL) %>%
  as.data.frame()

nrow(bl)                       # 254 subjects with a baseline weight and height
[1] 254
head(bl, 3)
      USUBJID HEIGHT WEIGHT
1 01-701-1015 147.32  54.43
2 01-701-1023 162.56  80.29
3 01-701-1028 177.80  99.34

La spécification de la dérivation est celle des manuels : BMI = poids (kg) / taille (m)². La taille est enregistrée en centimètres, elle doit donc être divisée par 100 avant d’être élevée au carré. Calculées correctement, les valeurs tombent dans une plage physiologiquement sensée — un fait qu’il vaut la peine de fixer maintenant, car c’est l’ancre de plausibilité sur laquelle les deux scénarios s’appuient :

library(pharmaverseadam)
library(dplyr)
library(tidyr)
data("advs", package = "pharmaverseadam")
bl <- advs %>%
  filter(PARAMCD %in% c("WEIGHT", "HEIGHT"), ABLFL == "Y") %>%
  select(USUBJID, PARAMCD, AVAL) %>%
  distinct() %>%
  pivot_wider(names_from = PARAMCD, values_from = AVAL) %>%
  as.data.frame()

# The correct derivation: kg / m^2, height converted cm -> m
correct_bmi <- bl$WEIGHT / (bl$HEIGHT / 100)^2
range(correct_bmi)             # roughly 13.7 to 40.2 -- real human BMIs
[1] 13.66745 40.16536

Environ 13.7 à 40.2 — de vrais BMI adultes. Gardez cette plage à l’esprit : une dérivation qui produit des nombres bien en dehors est manifestement fausse, et une qui produit des nombres à l’intérieur peut malgré tout être fausse d’une manière qu’aucun contrôle de plage ne détectera. Les deux pièges arrivent.

Scénario A : la QC IA attrape un vrai bug de production

Ici la spécification est sans ambiguïté : le BMI est le poids en kilogrammes divisé par la taille en mètres au carré ; la taille est en centimètres, donc divisez d’abord par 100. Nous semons un bug de production réaliste — le genre qui survit à un coup d’œil sur la sortie. Le programmeur de production a oublié d’élever la taille au carré :

library(pharmaverseadam)
library(dplyr)
library(tidyr)
data("advs", package = "pharmaverseadam")
bl <- advs %>%
  filter(PARAMCD %in% c("WEIGHT", "HEIGHT"), ABLFL == "Y") %>%
  select(USUBJID, PARAMCD, AVAL) %>%
  distinct() %>%
  pivot_wider(names_from = PARAMCD, values_from = AVAL) %>%
  as.data.frame()

# PRODUCTION (buggy): forgot the exponent -> weight / (height in metres), not squared
prod_bmi <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT / 100))
range(prod_bmi$BMI)            # 23 to 65 -- LOOKS like a plausible BMI table
[1] 23.09259 65.29281

Voilà pourquoi le bug est dangereux : la sortie erronée va d’environ 23 à 65. Un relecteur qui survole le tableau voit des nombres qui se lisent comme des BMI, et passe à autre chose. L’erreur ne s’annonce pas.

Jouons maintenant l’étape de QC avec un copilote IA. Munie de la même spécification (pas du code de production) et invitée à re-dériver la variable indépendamment, une IA produit la formule directe issue de la spécification : kilogrammes sur mètres au carré. Voici la re-dérivation qu’un copilote IA renvoie à partir de ce prompt :

library(pharmaverseadam)
library(dplyr)
library(tidyr)
data("advs", package = "pharmaverseadam")
bl <- advs %>%
  filter(PARAMCD %in% c("WEIGHT", "HEIGHT"), ABLFL == "Y") %>%
  select(USUBJID, PARAMCD, AVAL) %>%
  distinct() %>%
  pivot_wider(names_from = PARAMCD, values_from = AVAL) %>%
  as.data.frame()

# AI QC re-derivation FROM THE SPEC: kg / m^2, height squared
ai_qc <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT / 100)^2)
range(ai_qc$BMI)              # 13.7 to 40.2 -- the correct range
[1] 13.66745 40.16536

Deux dérivations de la même variable, à partir de la même spécification, écrites par deux « programmeurs » différents — l’un d’eux étant l’IA. La double programmation pose maintenant la seule question qui compte : concordent-elles ? Comparez-les avec diffdf(), en prenant l’identifiant de sujet comme clé pour qu’il apparie par sujet, pas par ordre de ligne.

library(pharmaverseadam)
library(dplyr)
library(tidyr)
library(diffdf)
data("advs", package = "pharmaverseadam")
bl <- advs %>%
  filter(PARAMCD %in% c("WEIGHT", "HEIGHT"), ABLFL == "Y") %>%
  select(USUBJID, PARAMCD, AVAL) %>%
  distinct() %>%
  pivot_wider(names_from = PARAMCD, values_from = AVAL) %>%
  as.data.frame()

prod_bmi <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT / 100))
ai_qc    <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT / 100)^2)

# keys = "USUBJID": match subjects by identity, not by row position
diffdf(prod_bmi, ai_qc, keys = "USUBJID")
Differences found between the objects!

Summary of BASE and COMPARE
  ====================================
    PROPERTY      BASE        COMP    
  ------------------------------------
      Name      prod_bmi     ai_qc    
     Class     data.frame  data.frame 
    Rows(#)       254         254     
   Columns(#)      2           2      
  ------------------------------------


Not all Values Compared Equal
  =============================
   Variable  No of Differences 
  -----------------------------
     BMI            254        
  -----------------------------


First 10 of 254 rows are shown in table below
  ===========================================
   VARIABLE    USUBJID      BASE    COMPARE  
  -------------------------------------------
     BMI     01-701-1015  36.94678  25.07927 
     BMI     01-701-1023  49.39099  30.38324 
     BMI     01-701-1028  55.87177  31.42394 
     BMI     01-701-1033  50.46788  28.79600 
     BMI     01-701-1034  40.40274  26.07638 
     BMI     01-701-1047  45.17801  30.40447 
     BMI     01-701-1097  46.19028  27.34609 
     BMI     01-701-1111  37.84125  23.91384 
     BMI     01-701-1115  43.46126  23.93110 
     BMI     01-701-1118  39.49207  21.89868 
  -------------------------------------------

diffdf() rapporte Differences found between the objects! — chacun des 254 sujets (BMI 254 écarts), les dix premiers étant détaillés : BASE (production) contre COMPARE (la QC IA), valeur par valeur. L’écart — le désaccord entre les deux dérivations — est total, et il pointe droit sur le bug. Le sujet 01-701-1015 affiche 36.9 en production contre 25.1 dans la QC IA ; le nombre de production est celui qui n’est pas au carré. La re-dérivation indépendante rédigée par l’IA a fait exactement ce qu’aurait fait celle d’un programmeur QC : elle a fait remonter une véritable erreur de production qui avait échappé à l’œil.

Corrigez la production — élevez la taille au carré — et relancez la comparaison :

library(pharmaverseadam)
library(dplyr)
library(tidyr)
library(diffdf)
data("advs", package = "pharmaverseadam")
bl <- advs %>%
  filter(PARAMCD %in% c("WEIGHT", "HEIGHT"), ABLFL == "Y") %>%
  select(USUBJID, PARAMCD, AVAL) %>%
  distinct() %>%
  pivot_wider(names_from = PARAMCD, values_from = AVAL) %>%
  as.data.frame()

# Production, corrected: height squared
prod_fixed <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT / 100)^2)
ai_qc      <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT / 100)^2)

diffdf(prod_fixed, ai_qc, keys = "USUBJID")
No issues were found!

No issues were found! — les deux dérivations indépendantes concordent désormais sur chaque sujet. Dans ce scénario, la QC IA a gagné sa place : une re-dérivation indépendante a attrapé un vrai bug et confirmé le correctif. C’est l’argument pour utiliser une IA comme une seconde paire d’yeux rapide. Voici maintenant l’argument contre le fait de s’y fier.

Scénario B : le faux rapport propre

Les vraies spécifications sont rarement aussi nettes que celle du Scénario A. Supposons que la spécification que le programmeur de production et le programmeur QC reçoivent tous deux soit laconique : « BMI = WEIGHT / HEIGHT². » Aucune clause d’unité. La colonne s’appelle littéralement HEIGHT, et elle contient des centimètres — mais la spécification ne le dit pas, et un programmeur pressé lit la colonne au premier degré comme des mètres. Le code de production commet ce faux pas :

library(pharmaverseadam)
library(dplyr)
library(tidyr)
data("advs", package = "pharmaverseadam")
bl <- advs %>%
  filter(PARAMCD %in% c("WEIGHT", "HEIGHT"), ABLFL == "Y") %>%
  select(USUBJID, PARAMCD, AVAL) %>%
  distinct() %>%
  pivot_wider(names_from = PARAMCD, values_from = AVAL) %>%
  as.data.frame()

# PRODUCTION: reads HEIGHT (cm) literally as metres -> forgot the /100 conversion
prod_unit <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT)^2)
range(prod_unit$BMI)          # ~0.0025 -- absurd, but nobody has looked yet
[1] 0.001366745 0.004016536

Maintenant la QC IA. Munie de la même spécification laconique et de la même colonne nommée HEIGHT, un copilote IA fait correspondre le motif à la lecture la plus littérale — WEIGHT / HEIGHT² — et commet le faux pas identique. Elle n’a pas plus de raison que l’humain pressé de soupçonner les unités, et elle a été entraînée sur les mêmes conventions que celles qui ont produit la spécification ambiguë au départ :

library(pharmaverseadam)
library(dplyr)
library(tidyr)
data("advs", package = "pharmaverseadam")
bl <- advs %>%
  filter(PARAMCD %in% c("WEIGHT", "HEIGHT"), ABLFL == "Y") %>%
  select(USUBJID, PARAMCD, AVAL) %>%
  distinct() %>%
  pivot_wider(names_from = PARAMCD, values_from = AVAL) %>%
  as.data.frame()

# AI QC: same terse spec, same literal reading of HEIGHT -> the SAME unit slip
ai_unit <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT)^2)

Comparez les deux avec diffdf(), exactement comme avant :

library(pharmaverseadam)
library(dplyr)
library(tidyr)
library(diffdf)
data("advs", package = "pharmaverseadam")
bl <- advs %>%
  filter(PARAMCD %in% c("WEIGHT", "HEIGHT"), ABLFL == "Y") %>%
  select(USUBJID, PARAMCD, AVAL) %>%
  distinct() %>%
  pivot_wider(names_from = PARAMCD, values_from = AVAL) %>%
  as.data.frame()

prod_unit <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT)^2)
ai_unit   <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT)^2)

diffdf(prod_unit, ai_unit, keys = "USUBJID")
No issues were found!

No issues were found! — et ce rapport propre est un mensonge. Les deux dérivations sont fausses (chaque BMI vaut environ 0.0025, faux d’un facteur 10 000), mais elles sont fausses de la même manière, si bien que diffdf() ne trouve rien à contredire. C’est le mode de défaillance que la double programmation est censée empêcher : deux implémentations qui partagent une erreur concorderont toujours, et leur concordance ressemble exactement à une vraie validation. Le rapport est, octet pour octet, le même « No issues were found! » que celui que vous avez approuvé dans le Scénario A.

Ce qui l’attrape, c’est une véritable indépendance. Un programmeur QC vraiment indépendant ne se contente pas de retaper la formule laconique — il vérifie la source : HEIGHT dans ADVS est enregistrée en centimètres, elle doit donc être convertie avant d’être élevée au carré. Cette re-dérivation est en désaccord avec l’erreur partagée sur chaque sujet :

library(pharmaverseadam)
library(dplyr)
library(tidyr)
library(diffdf)
data("advs", package = "pharmaverseadam")
bl <- advs %>%
  filter(PARAMCD %in% c("WEIGHT", "HEIGHT"), ABLFL == "Y") %>%
  select(USUBJID, PARAMCD, AVAL) %>%
  distinct() %>%
  pivot_wider(names_from = PARAMCD, values_from = AVAL) %>%
  as.data.frame()

prod_unit  <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT)^2)
# TRULY independent: checks the units metadata (HEIGHT is cm) and converts
qc_correct <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT / 100)^2)

diff <- diffdf(prod_unit, qc_correct, keys = "USUBJID", suppress_warnings = TRUE)
diffdf_has_issues(diff)       # TRUE -> 254 differences, the shared bug exposed
[1] TRUE
diff
Differences found between the objects!

Summary of BASE and COMPARE
  ====================================
    PROPERTY      BASE        COMP    
  ------------------------------------
      Name     prod_unit   qc_correct 
     Class     data.frame  data.frame 
    Rows(#)       254         254     
   Columns(#)      2           2      
  ------------------------------------


Not all Values Compared Equal
  =============================
   Variable  No of Differences 
  -----------------------------
     BMI            254        
  -----------------------------


First 10 of 254 rows are shown in table below
  ==============================================
   VARIABLE    USUBJID       BASE      COMPARE  
  ----------------------------------------------
     BMI     01-701-1015  0.002507927  25.07927 
     BMI     01-701-1023  0.003038324  30.38324 
     BMI     01-701-1028  0.003142394  31.42394 
     BMI     01-701-1033  0.002879600  28.79600 
     BMI     01-701-1034  0.002607638  26.07638 
     BMI     01-701-1047  0.003040447  30.40447 
     BMI     01-701-1097  0.002734609  27.34609 
     BMI     01-701-1111  0.002391384  23.91384 
     BMI     01-701-1115  0.002393110  23.93110 
     BMI     01-701-1118  0.002189868  21.89868 
  ----------------------------------------------

diffdf_has_issues() renvoie TRUE, et le rapport nomme les 254 sujets. La différence entre le faux rapport propre du Scénario B et cette vraie prise n’est pas l’outil — diffdf() a fait exactement ce qu’on lui a demandé les deux fois. La différence, c’est l’indépendance : une QC qui partage l’hypothèse du code de production produit un « No issues found » qui ressemble à une validation sans en être une. (suppress_warnings = TRUE empêche seulement l’écart de remonter aussi sous forme d’avertissement R ; le rapport est le même.)

Pourquoi une IA n’est pas un second programmeur indépendant

Le Scénario B n’est pas une bizarrerie liée à un prompt. Il est structurel, et c’est exactement ce contre quoi la discipline met elle-même en garde. Le guide PHUSE sur la QC indépendante est explicite : la prémisse de la double programmation est que deux programmeurs qui créent de façon autonome une sortie identique peuvent être considérés comme corrects — et qu’« il est vital que les programmeurs ne regardent pas le programme de l’autre ». Il avertit aussi que « les spécifications d’un jeu de données peuvent avoir plus d’une interprétation possible » et que, même avec deux programmeurs, « il reste de la place pour l’erreur humaine ». Un copilote IA brise cette prémisse de trois manières concrètes :

  • Erreurs corrélées. L’hypothèse d’indépendance exige que les erreurs des deux programmeurs soient non corrélées — qu’une mauvaise hypothèse de l’un ait peu de chances d’être partagée par l’autre. Une IA entraînée sur les mêmes conventions et les mêmes motifs ambigus que tout le monde reproduit la lecture la plus courante, qui est précisément celle qu’un programmeur de production pressé a le plus de chances d’avoir utilisée. Les erreurs sont corrélées par construction, et des erreurs corrélées mettent la double programmation en échec (Scénario B).
  • Fausse concordance silencieuse. L’IA renvoie « la dérivation concorde » avec la même assurance, que la réponse partagée soit juste ou fausse. Elle n’a aucun signal qui distingue une vraie absence d’écart d’une fausse — le rapport propre du Scénario B était aussi calme et complet que celui du Scénario A.
  • Aucune responsabilité. La double programmation se termine par une approbation : un programmeur nommé et qualifié atteste que le contrôle indépendant a été effectué et réussi, et en répond lors d’un audit. Une IA ne peut pas signer un enregistrement de QC, ne peut pas être le second programmeur responsable, et ne peut pas être tenue responsable d’un chiffre faux qui atteint une soumission.

Le R Validation Hub formule le principe général pour les outils qui sous-tendent ce travail : l’assurance vient d’une validation documentée et portée par un humain, pas de la parole de l’outil lui-même. Un copilote IA se situe à l’intérieur de ce processus comme un rédacteur rapide de code de QC — jamais comme le relecteur indépendant qui le certifie.

Qui est maître de quoi

Alignez les deux scénarios et la frontière est exacte. Dans le Scénario A, le copilote IA a écrit une re-dérivation qui a attrapé un vrai bug de production — un travail authentique et utile, une seconde paire d’yeux rapide. Dans le Scénario B, le même copilote a produit un faux rapport propre, en étant d’accord avec une mauvaise réponse parce qu’il partageait la mauvaise hypothèse. Rien à propos de l’IA n’a changé entre les deux ; ce qui a changé, c’est de savoir si sa dérivation était réellement indépendante de l’erreur de production, et c’est la seule propriété dont dépend la double programmation et que l’IA ne peut pas garantir.

La responsabilité ne bouge donc jamais. Un humain — le programmeur QC qualifié — est maître de la re-dérivation indépendante (y compris du jugement de questionner la spécification et de vérifier les métadonnées d’unité autour desquelles le Scénario B a basculé), est maître de la lecture du rapport diffdf() comme d’une preuve plutôt que d’un verdict, et est maître de l’approbation sur laquelle un régulateur s’appuie. C’est un livrable GxP (les réglementations qualité de « bonnes pratiques » régissant les données cliniques), et valider un jeu de données dérivé signifie une vérification documentée, indépendante et portée par un humainla norme que le CDISC ADaM (Analysis Data Model, le modèle de données d’analyse) fixe pour les données d’analyse. Une IA peut rédiger le code de QC et accélérer la rédaction ; elle n’est jamais la validation. Dit simplement : utilisez une IA comme une seconde paire d’yeux, jamais comme le relecteur indépendant attitré — et ne lisez jamais un « No issues were found! » de diffdf() comme « validé » quand la même IA a écrit les deux côtés.

🟢 Avec un agent IA

Demandez à Prova « Une IA a écrit ma re-dérivation QC indépendante et diffdf dit “No issues were found!” — comment savoir si ce rapport propre est réel et non une erreur partagée ? » — elle répond en s’appuyant sur les leçons de validation de cette série, avec les contrôles exécutables de double programmation diffdf() et d’indépendance que vous pouvez essayer sur votre propre dérivation. The runtime is the judge. Demandez à Prova →

Problèmes fréquents

Lire « No issues were found! » comme « validé » quand une IA a écrit les deux côtés. Un rapport diffdf() propre signifie seulement que deux dérivations concordent ; il signifie validé uniquement si les deux sont réellement indépendantes. Le Scénario B a produit exactement le même rapport propre à partir de deux dérivations qui partageaient un bug d’unité. Si la même IA (ou la même personne, ou un programme copié) a produit à la fois le code de production et la re-dérivation QC, leur concordance ne porte aucun poids de validation — l’indépendance qui fait fonctionner la double programmation manque. Établissez que le chemin de QC est indépendant avant de vous fier à son rapport propre.

Passer un résultat de pivot_wider() directement à diffdf(). pivot_wider() renvoie un tibble, et mêler des entrées tibble et data-frame peut enregistrer de fausses différences de classe. Enveloppez les données remises en forme dans as.data.frame() (comme le fait chaque bloc ici) pour que diffdf() compare des valeurs, pas des types de conteneur. Exécuter distinct() avant le pivot évite aussi les colonnes-listes quand un sujet a plus d’un enregistrement initial.

Oublier keys, de sorte que diffdf() apparie par ordre de ligne. Sans keys = "USUBJID", diffdf() aligne les deux data frames par position ; si la production et la QC ont trié leur sortie différemment, il signale chaque ligne. Prenez toujours pour clé la ou les colonnes qui identifient un sujet de façon unique afin que la comparaison apparie par identité. (Les mécanismes complets — clés non uniques, tolérance, typage strict — sont traités dans La double programmation en R avec diffdf.)

Questions fréquentes

Comme outil de rédaction, oui — comme relecteur indépendant attitré, non. Un copilote IA peut écrire la re-dérivation QC à partir de la spécification plus vite que vous ne la tapez, et quand sa dérivation diffère réellement de la production, il attrape de vrais bugs (Scénario A ci-dessus). Mais le code de QC de l’IA est lui-même non qualifié, et un humain qualifié doit être maître de l’approbation. Utilisez-le pour rédiger et accélérer le contrôle, jamais pour le certifier.

Non. La double programmation fonctionne parce que les deux programmeurs sont indépendants, si bien que leurs erreurs ont peu de chances d’être les mêmes. Une IA entraînée sur les mêmes conventions et les mêmes motifs ambigus que tout le monde tend à reproduire la lecture la plus courante — la même qu’a utilisée un programmeur de production pressé — de sorte que ses erreurs sont corrélées avec celles de la production, ce qui est exactement ce qui casse la méthode (Scénario B). Le guide PHUSE sur la QC indépendante fait de l’indépendance toute la prémisse.

Parce que diffdf() compare deux jeux de données pour vérifier leur concordance ; il ne peut pas savoir si la valeur sur laquelle ils s’accordent est correcte. Quand la production et la QC IA ont commis la même erreur d’unité, leurs sorties étaient identiques, donc diffdf() a correctement rapporté aucune différence — un faux rapport propre. Un rapport propre valide une dérivation seulement quand les deux chemins qui l’ont produite sont réellement indépendants ; une re-dérivation vraiment indépendante qui a vérifié les métadonnées d’unité a été en désaccord sur les 254 sujets.

Un programmeur humain qualifié, toujours. La re-dérivation de l’IA est un brouillon non qualifié ; un programmeur QC nommé la relit, confirme qu’elle est réellement indépendante de la logique de production, lit le rapport diffdf() comme une preuve, et atteste du résultat dans la piste d’audit. La responsabilité d’une valeur dérivée qui atteint une soumission ne se transfère jamais à l’outil — c’est la frontière que la SOP d’IA auditable pour la programmation clinique formalise.

Les mécanismes sont le même appel diffdf(base, compare, keys = ...) enseigné dans La double programmation en R avec diffdf. Ce qui change ici, c’est qui écrit la seconde dérivation et ce que vaut sa concordance. Quand une IA écrit le côté QC, un rapport diffdf() propre peut être un faux rapport propre, parce que l’IA peut partager l’hypothèse de la production. La leçon porte sur l’indépendance que le rapport suppose silencieusement, pas sur la comparaison elle-même.

Testez vos connaissances

Un collègue fait la QC d’une variable BMI dérivée en demandant à un copilote IA de la re-dériver à partir de la même spécification d’une ligne qu’a utilisée le programmeur de production, puis exécute diffdf(prod, ai_qc, keys = "USUBJID") et obtient No issues were found!. Il marque la variable comme validée.

  1. En prose : expliquez l’unique condition sous laquelle ce rapport propre valide bien la dérivation, et l’unique condition sous laquelle il ne la valide pas — et pourquoi le rapport paraît identique dans les deux cas.
  2. En R : construisez un petit exemple à deux sujets où HEIGHT est en centimètres, écrivez un prod et un ai_qc de BMI qui oublient tous deux la conversion /100, confirmez que diffdf() ne rapporte aucun écart, puis ajoutez une re-dérivation vraiment indépendante qui convertit les unités et montrez qu’elle est en désaccord.

diffdf() teste si deux dérivations concordent, pas si elles sont correctes. Le rapport propre valide uniquement si prod et ai_qc sont réellement indépendants ; s’ils partagent la même mauvaise hypothèse (la conversion d’unité manquante), ils concordent et le rapport est un faux rapport propre. Une troisième dérivation qui divise HEIGHT par 100 avant de l’élever au carré sera en désaccord avec les deux.

Étape 1. Le rapport propre valide la dérivation uniquement si les dérivations de production et de QC sont indépendantes — écrites à partir de la spécification sans partage de code ni d’hypothèses — de sorte qu’une erreur partagée soit peu probable. Il ne valide pas quand les deux chemins sont corrélés (ici, la même IA, ou la même mauvaise lecture littérale d’une spécification ambiguë, a produit les deux) : ils commettent l’erreur identique, concordent exactement, et diffdf() rapporte « No issues were found! » — un faux rapport propre qui est, octet pour octet, identique à un vrai. Le rapport ne peut pas vous dire lequel c’est ; seul le fait de savoir que le chemin de QC est réellement indépendant le peut.

library(diffdf)

bl <- data.frame(
  USUBJID = c("01-001", "01-002"),
  WEIGHT  = c(70, 90),            # kg
  HEIGHT  = c(175, 180)           # cm -- must be /100 before squaring
)

# prod and ai_qc share the same unit bug (no /100 conversion)
prod  <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT)^2)
ai_qc <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT)^2)
diffdf(prod, ai_qc, keys = "USUBJID")          # "No issues were found!" -- FALSE clean

# a truly independent re-derivation that checks the units (HEIGHT is cm)
qc_correct <- data.frame(USUBJID = bl$USUBJID, BMI = bl$WEIGHT / (bl$HEIGHT / 100)^2)
diffdf_has_issues(diffdf(prod, qc_correct, keys = "USUBJID", suppress_warnings = TRUE))  # TRUE

Le premier diffdf() est propre parce que les deux dérivations partagent le bug ; l’indépendant signale les deux sujets. Même outil, verdicts opposés — la différence, c’est l’indépendance, pas diffdf().

A. Oui — deux dérivations ont concordé, ce qu’exige la double programmation. B. Pas nécessairement — si l’IA partage l’hypothèse de la production, les deux peuvent être fausses de la même manière et concorder, de sorte que le rapport propre peut être un faux rapport propre ; un humain qualifié doit confirmer que la QC était réellement indépendante et être maître de l’approbation. C. Oui, du moment qu’un modèle actuel et capable a écrit le code de QC.

B. La double programmation ne valide que lorsque les deux dérivations sont indépendantes. Une IA peut reproduire la même mauvaise hypothèse que la production (erreurs corrélées), auquel cas les deux concordent et diffdf() rapporte « No issues were found! » pour un bug partagé — un faux rapport propre identique à un vrai, ce qui explique pourquoi A est faux. La capacité du modèle ne crée pas d’indépendance (ce qui écarte C) ; seul un humain qualifié qui confirme que le chemin de QC est réellement indépendant, et qui est maître de l’approbation, fait du rapport propre une validation.

Conclusion

Un copilote IA peut jouer le second programmeur dans la QC par double programmation, et le Scénario A en montre le bon côté : munie de la spécification, elle a écrit une re-dérivation indépendante qui a attrapé un vrai bug de production — un BMI initial qui a oublié d’élever la taille au carré — et diffdf() a nommé les 254 sujets. C’est une seconde paire d’yeux rapide et utile. Mais le Scénario B montre la frontière qui ne bouge jamais : munie d’une spécification laconique et ambiguë, l’IA a commis le même faux pas d’unité que la production, diffdf() a rapporté un « No issues were found! » propre, et ce rapport propre était faux. La propriété dont dépend la double programmation — l’indépendance — est celle qu’une IA ne peut pas garantir, parce que ses erreurs sont corrélées avec les conventions que tout le monde partage. Une IA rédige donc le code de QC ; un humain qualifié est maître du contrôle indépendant, lit le rapport comme une preuve, et approuve. L’IA change la vitesse du brouillon de QC. Elle ne change rien à qui est responsable du chiffre. The runtime is the judge.

Note

Cette leçon est reproductible : la tâche de QC, le bug du Scénario A et sa prise, le faux rapport propre du Scénario B, et la re-dérivation vraiment indépendante qui l’expose 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 fait dans votre propre outillage ; le R qui réalise et teste la QC est réel et s’exécute. The runtime is the judge.

Leçons connexes

  • La double programmation en R avec diffdf — les mécanismes du moteur que cette leçon suppose : clés, tolérance, typage strict, et lecture intégrale du rapport de différences. · Une SOP d’IA auditable pour la programmation clinique — la piste d’audit prompt → brouillon → valider avant d’utiliser → approuvé qui garde le programmeur QC indépendant du brouillon de l’IA ; cette leçon est la démonstration concrète de pourquoi cette indépendance compte. · 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 » à l’œuvre ici. · 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, avec un diffdf() de confirmation indépendant.
  • Où cela s’inscrit : c’est l’instance QC de la série de programmation clinique agentique — un copilote IA placé dans le siège du validateur, où son utilité (Scénario A) et sa limite structurelle (Scénario B) sont toutes deux visibles. Elle retourne la discipline rédiger-puis-valider de la série sur l’étape de validation elle-même.
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 = {L’IA comme double-programmeur QC en R : peut-elle être le
    contrôle indépendant\,?},
  date = {2026-07-02},
  url = {https://www.datanovia.com/learn/pharma-clinical/08-agentic-clinical-programming/ai-as-qc-double-programmer},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“L’IA comme double-programmeur QC en R : peut-elle être le contrôle indépendant ?” 2026. July 2. https://www.datanovia.com/learn/pharma-clinical/08-agentic-clinical-programming/ai-as-qc-double-programmer.