1 - Général

Une série d’articles expliquant tout l’environnement de la recherche en santé numérique

1.1 - Qu'est-ce qu'un entrepôt de données de santé ?

Par Boris Delange | 14.10.2024

Dans cet article, nous allons :

  • Expliquer ce que sont le système d'information hospitalier (SIH) et un entrepôt de données de santé (EDS)
  • Expliquer les concepts d'ETL et d'interopérabilité
  • Détailler les flux de données intégrés dans les EDS
  • Vous donner les clefs pour pouvoir accéder à ces données en pratique

Informatisation des hôpitaux

Depuis quelques années déjà, la majorité des services hospitaliers est passée des pancartes et des prescriptions papiers à des logiciels de soins.

L’ensemble de ces logiciels s’appelle le système d’information hospitalier, ou SIH.

Il existe de nombreux logiciels pour chaque hôpital : un logiciel pour l’imagerie, un pour la prescription de chimiothérapie, un autre pour la réanimation etc.

Toutes les données utilisées et produites par les logiciels sont stockées dans des bases de données.

Donc en soi, toute donnée que vous voyez apparaître à l’écran est techniquement récupérable, il “suffirait” de copier ces données pour les utiliser dans le cadre de la recherche.


Cours "Données de santé", faculté de médecine de Rennes, 2024


Entrepôts de données de santé

Un entrepôt de données de santé (EDS) est un espace de stockage où sont copiées les données issues du soin, pour être utilisées en recherche.

La grande majorité des CHU sont pourvus d’EDS, avec des niveaux de maturité plus ou moins avancés.

En effet, il ne suffit pas de copier ces données, elles doivent être transformées et calquées sur un modèle de données pour être utilisées en recherche.

Tout ce processus est appelé l’ETL, pour Extract, Transform and Load.


Cours "Données de santé", faculté de médecine de Rennes, 2024


ETL et interopérabilité

Comme on l’a vu, les logiciels médicaux sont nombreux et créés par différents éditeurs. Chaque logiciel stocke ses données sur une base de données avec un schéma différent, défini par l’éditeur.

Il faut imaginer ça comme un tableur Excel avec des noms de colonnes différents pour chaque logiciel. Vous imaginez bien qu’il est ainsi difficile de fusionner ces données (on ne peut pas fusionner deux fichiers Excel contenant des noms de colonnes différents).

L’interopérabilité est définie par la capacité de matériels, de logiciels ou de protocoles différents à fonctionner ensemble et à partager des informations.

Il s’agit de la capacité des logiciels médicaux à pouvoir échanger des données.

Les logiciels médicaux pour la grande majorité ne sont pas interopérables, ce qui empêche de fusionner les données pour les utiliser en recherche.

C’est là qu’intervient le travail d’ETL.


Cours "Données de santé", faculté de médecine de Rennes, 2024


L’ETL se compose de trois étapes :

  • Extract : les données des logiciels médicaux sont copiées sur des serveurs de données servant au traitement de ces données brutes. A ce stade, les données sont au format de l’éditeur de chaque logiciel, donc non interopérables entre-elles.
  • Transform : c’est l’étape cruciale et la plus complexe, qui permet de transformer toutes ces données en un format unique, qui permettra de “fusionner” ces données. Il faut imaginer cela comme des tableurs qui auront au terme de cette transformation les mêmes colonnes pour chaque logiciel, permettant d’obtenir un seul tableur.
  • Load : une fois ces données transformées, elles alimentent les serveurs de données servant à la recherche.

Ce processus d’ETL est réalisé à intervalles réguliers, par exemple une fois par semaine, lorsque les serveurs alloués au soin sont moins sollicités (la nuit ou le week-end).

Flux de données

On appelle flux de données l’intégration d’une source de données à l’EDS.

Par exemple, les données de biologie constituent un flux, puisque l’on réalise l’ETL depuis la base de données du logiciel qui gère ces données de biologie.

On peut citer les flux suivants :

  • Données démographiques : les patients avec leur âge, leur sexe et leurs séjours hospitaliers
  • Données biologiques
  • Données de pancarte : les paramètres vitaux relevés chez les patients, par exemple la pression artérielle, la fréquence cardiaque etc
  • Données de prescription : les médicaments reçus par les patients
  • etc

Comme on l’a vu, la majorité des CHU en France sont équipés d’EDS, avec un degré de maturité différent.

Ce degré de maturité dépend des flux de données disponibles. En effet, le travail d’ETL permettant d’obtenir ces flux de données intégrés à l’EDS de façon régulière est un travail colossal.

Une fois un flux de données créé, les données de ce flux sont intégrées au fur et à mesure à l’EDS, par exemple une fois par semaine.

Le travail ne s’arrête pas là : il faut assurer la qualité continue de ce flux.

Il est fréquent que tout le processus d’ETL d’un flux soit mis à défaut par une mise à jour d’un logiciel. Les équipes de l’ETL ont des outils qui permettent de s’assurer que les données sont correctement intégrées à l’EDS. Si ce n’est pas le cas, ils reçoivent une alerte, leur permettant de corriger le problème.

Schémas de BDD

Si vous n’avez pas les idées claires sur qu’est une base de données (BDD) et un schéma de BDD, vous pouvez lire cet article.

Nous avons vu que l’EDS est une grande base de données qui permet d’avoir au même endroit et au même format les données de santé issues des différents logiciels.

Il existe plusieurs schémas de BDD concernant les données de santé.

Parmi ceux-ci, nous pouvons citer le modèle OMOP (pour Observational Medical Outcomes Partnership), qui est un schéma de BDD (ou modèle de données) commun, dans le sens où il s’adapte à plusieurs sources de données de santé (différents hôpitaux de différents pays, les données de la médecine libérale etc).

Il est largement utilisé dans de nombreux pays.

C’est ce modèle que nous utilisons dans LinkR.

Et en pratique ?

En pratique, ces EDS sont déployés dans la majorité des CHU et quelques centres hospitaliers périphériques.

Vous pouvez vous rapprocher des équipes en charge de l’EDS de votre hôpital pour demander un accès aux données. Vous serez accompagnés dans la rédaction d’un protocole de recherche et les différentes démarches juridiques vous seront expliquées.

Un obstacle fréquemment évoqué est la nécessité de maîtriser des compétences en programmation pour exploiter ces données.

En effet, des langages comme R, Python et SQL sont souvent requis pour analyser ces données.

C’est pourquoi nous avons créé LinkR, qui permet de manipuler ces données sans connaissance en programmation, et qui permet de travailler plus facilement de façon collaborative sur des projets entre cliniciens et data scientists.

Dans le prochain article, nous verrons les avantages des EDS par rapport au recueil de données manuel.

Conclusion

Les points à retenir :

  • Le système d'information hospitalier (SIH) est l'ensemble des logiciels servant pour le soin
  • Les entrepôts de données de santé (EDS) concentrent les données des patients pour un usage secondaire, pour la recherche
  • Le processus d'ETL permet de transformer les données du SIH, non interopérables, en données interopérables et prêtes à être utilisées pour la recherche au sein des EDS
  • Le modèle de données OMOP est un schéma de base de données utilisement à l'échelle mondiale, c'est celui qui a été choisi pour fonctionner avec notre logiciel LinkR

LinkR est une plateforme de data science qui a été créée pour faciliter l'accessibilité des données d'EDS.

Cette application permet notamment aux professionnels de santé, sans connaissance en programmation, de manipuler ces données.

>>> Testez LinkR ici

1.2 - Comparaison entre recueil de données et EDS

Par Boris Delange | 16.10.2024

Dans cet article, nous allons :

  • Décrire les limites du recueil de données : beaucoup de temps humain et des données de mauvaise qualité
  • Décrire les avantages de travailler avec les entrepôts de données de santé (EDS) : un gain de temps, des études avec de plus grands effectifs et des données de meilleure qualité
  • Décrire les freins à l'utilisation des données des EDS
  • Donner des pistes pour surmonter ces freins

Limites du recueil de données

On entend par recueil de données le fait d’extraire des données pour les utiliser dans le cadre d’une étude.

Généralement, on utilise le système d’information hospitalier (les logiciels servant pour le soin) pour afficher les données, que l’on recopie sur un tableur Excel.

Les variables nécessaires à l’étude sont définies en amont permettant de créer le tableur Excel avec une colonne correspondant à chaque variable.

Si l’on prend l’exemple d’une étude visant à prédire la mortalité d’un patient à partir de paramètres à son admission en réanimation, nous aurions un tableur Excel ressemblant à ceci :

patient_id age sexe date_admission créatininémie pression art systolique bilirubinémie diurèse
1 45 M 2024-10-01 1.2 120 0.8 1500
2 60 F 2024-09-25 1.0 135 1.1 1800
3 38 F 2024-10-05 0.9 110 0.7 1600
4 52 M 2024-09-20 1.4 140 1.3 1400
5 29 F 2024-10-08 1.1 125 0.9 1700

La créatininémie, la pression artérielle systolique, la bilirubinémie et la diurèse seront les valeurs à l’admission.


Première problématique

Le temps passé pour recueillir les données des patients est directement proportionnel au nombre de patients et au nombre de paramètres.


Si on estime par exemple à 15 minutes le temps pour recueillir 10 paramètres chez un patient, et 45 minutes pour 50 paramètres (on imagine que ce n’est pas totalement linéaire, le temps d’accéder au dossier, de se l’approprier, avant de recueillir les données), on peut estimer le temps passé comme ceci :



Si on a une étude avec 200 patients et 50 paramètres (soit 10000 cases à remplir à la main), on voit que l’on est déjà à 150 heures.

De fait, étant donné les moyens humains nécessaires pour obtenir de telles données, ces études dépassent rarement 200 patients.

Deuxième problématique

Ces données recueillies peuvent être de mauvaise qualité.


Certaines données sont simples à recueillir, l’âge du patient, son sexe, sa date d’admission… Il y a peu de risques de se tromper lors du recueil de ces données (quoiqu’au bout du 150ème patient, cela peut arriver).

Le problème réside surtout dans les données où un choix doit être fait au moment du recueil.

Nous avons volontairement été flous sur la définition des variables à l’admission. Nous avons indiqué “la créatininémie, la pression artérielle systolique, la bilirubinémie et la diurèse seront les valeurs à l’admission”.

Les questions que l'on se pose durant le recueil :

  • Si je n'ai qu'une valeur de créatininémie à l'admission, c'est assez simple, je prends la valeur disponible.
  • Si je n'ai pas de valeur, jusqu'à combien de temps je peux remonter pour avoir une valeur ?
  • Si j'ai plusieurs valeurs, je prends le maximum ? La dernière valeur ? La moyenne ?


Si nous n’avons pas clairement défini les variables, ces choix seront laissés à la discrétion de la personne faisant le recueil, et donc pourront varier d’une personne à l’autre.

Pour modifier une valeur entrée dans le tableur, il faudra retourner la chercher dans le dossier du patient, donc ce temps est à multiplier par le nombre de patients…

Et si par malheur j’ai oublié de recueillir un paramètre, je vais de la même façon devoir retourner dans chaque dossier, un par un…

Donc du fait de ces choix faits au moment du recueil et du risque d’erreur lors du recueil, ces données pourront être de mauvaise qualité.

D'où l'importance de bien définir les variables à recueillir.

Nous publierons prochainement un article là-dessus.

Avantages des EDS

Premier avantage

Le temps passé à extraire les données depuis l'EDS n'est plus proportionnel au nombre de patients mais uniquement au nombre de paramètres.


Nous reprenons le graphique précédent et comparons le temps passé entre le recueil de données manuel et l’extraction des données avec l’EDS.

Sur cette figure, la ligne rouge en pointillés est le temps passé à extraire les données avec l’EDS.

On voit donc que très rapidement, à partir de 50 patients pour 10 paramètres et 75 patients pour 50 paramètres, il devient plus rentable de travailler avec l’EDS.

On peut ainsi imaginer travailler avec des bases de plusieurs centaires de milliers de patients, ce qui est évidemment impossible avec le recueil manuel.

Deuxième avantage

L'extraction des données fonctionnant avec des scripts à partir des données brutes, une simple modification des scripts permet de générer de nouveau les données finales.

Ce qui est génial !


Les EDS sont des grandes bases de données qui contiennent les données brutes des patients (leurs données biologiques, les prescriptions etc).

Les scripts sont des suites d’instructions permettant d’obtenir les données finales (le tableau du premier paragraphe) à partir des données brutes (les données de l’EDS, avec une ligne par donnée).

Pour obtenir le tableau présenté au début de cet article, le script ressemblerait à ceci :

-- 1) Sélection des patients

-- On met ici le code pour sélectionner les patients selon nos critères d'inclusion et pour récupérer leur âge et leur sexe.

SELECT patient_id, age, sexe    -- Sélection des colonnes patient_id (numéro du patient), age et sexe
FROM patients                   -- Depuis la table patients
WHERE age >= 18                 -- Avec âge supérieur ou égal à 18 ans

-- 2) Récupération des séjours

-- On récupère les séjours et on les ajoute la date d'admission à nos données

SELECT stay_id, unite         -- Sélection des colonnes stay_id (numéro du séjour), unite (le nom de l'unité, par exemple cardiologie)
FROM stays                    -- Depuis la table des séjours (stays)
WHERE los >= 1                -- Où la durée de séjour est supérieur ou égale à 1 jour
AND unite = 'Cardiologie'     -- Et où l'unité est la cardiologie

-- 3) Récupération de la biologie

-- On récupère la moyenne des données de biologie dans les 24 heures suivant l'admission dans le service

SELECT AVG(valeur_numerique)                                           -- Sélection de la moyenne (AVG comme average) de la valeur de la biologie
FROM labo                                                              -- Depuis la table labo
WHERE lab_datetime >= adm_datetime                                     -- Où le prélèvement est réalisé après l'admission dans le service...
AND lab_datetime <= adm_datetime + INTERVAL 24 HOUR                    -- ... et avant 24 heures après l'arrivée
AND concept IN ('creatininemie', 'pression_art_sys', 'bilirubinemie')  -- Et où le nom de la biologie est dans cette liste

Euh attends... C'est du code ça ? Je n'y comprends rien !


Le SQL est un langage qui sert à requêter les bases de données.

C’est un langage assez pragmatique, si on comprend les quelques mots clefs, il suffit de lire le code pour comprendre ce que l’on obtient.

Les mots-clefs sont les suivants :

  • SELECT : vous choisissez les colonnes que vous voulez garder
  • FROM : de quelle table seront extraites les données ?
  • WHERE : quels filtres appliquer sur les données ?

Si l’on reprend le code pour les patients :

SELECT patient_id, age, sexe
FROM patients
WHERE age >= 18

On sélectionne les colonnes patient_id, age et sexe de la table des patients qui sont âgés de plus de 18 ans.

On obtiendra donc :

patient_id age sexe
1 45 M
2 60 F
3 38 F
4 52 M
5 29 F

Et ainsi de suite pour le reste du code, pour obtenir le tableau final.

patient_id age sexe date_admission créatininémie pression art systolique bilirubinémie
1 45 M 2024-10-01 1.2 120 0.8
2 60 F 2024-09-25 1.0 135 1.1
3 38 F 2024-10-05 0.9 110 0.7
4 52 M 2024-09-20 1.4 140 1.3
5 29 F 2024-10-08 1.1 125 0.9

Cool... Mais concrètement, en quoi c'est "génial" ?


Ce qui est génial est que ce code peut être appliqué pour obtenir une infinité de patients.

Souvenez-vous des pointillés rouges, le temps est proportionnel au nombre de paramètres à extraire et non au nombre de patients.

Plus on voudra extraire de paramètres, plus ce script sera complexe et plus on passera de temps à l’écrire.

Mais par contre, que l’on ait 100 patients ou 100 000 patients à extraire, ce script sera le même !

J'en profite pour combattre une idée reçue.

On pense souvent que ces bases de données prennent un espace monstrueux.

En fait non, les méthodes actuelles de stockage de données sont optimisées, et vous pouvez stocker facilement une base de 50 000 patients sur votre ordinateur.

En fait, c'est le cas de la MIMIC-III, cette base contient les données de 50 000 patients, avec toutes les notes quotidiennes du personnel médical, avec un paramètre vital par minute, avec toutes les prescriptions etc.

Cette base de données occupe 3 Go seulement au format Parquet (plus optimisé que le CSV).

Et pas besoin de serveurs de calculs monstrueux pour travailler dessus, n'importe quel ordinateur portable est suffisant (hors cas particuliers tels que les algorithmes de machine learning qui peuvent être gourmands).


Deuxième aspect génial, c’est que si vous voulez ajouter une variable à votre tableau, il suffira de modifier le script et de le relancer, encore une fois quelque soit le nombre de patients.

Et troisième avantage, vous pourrez relancer vos scripts de façon itérative : si vous le relancez deux ans plus tard, vous aurez les données de patients de deux années supplémentaires.

Freins à l’utilisation des EDS

Vous devez à ce stade être convaincus de l’intérêt d’utiliser les EDS plutôt que le recueil manuel.

Mais alors, pourquoi ne fait-on pas toute la recherche médicale avec ces EDS ?

Il existe plusieurs freins qui limitent l'utilisation de ces EDS :

  • Le travail d'ETL pour intégrer les différents flux de données
  • La nécessité de connaissances en programmation


  1. ETL et flux de données

Comme on l’a vu dans un précédent article, le travail d’ETL (le fait de transformer les données issues du soins pour être utilisées dans le cadre de la recherche, ou utilisation secondaire des données de santé) est un travail laborieux.

Ainsi, les EDS se construisent petit à petit en intégrant les différents flux de données progressivement.

Il est donc parfois nécessaire de recourir à une partie de recueil manuel si certaines données manquent dans l’EDS.


  1. Connaissances en programmation

Nous l’avons vu avec les requêtes SQL ci-dessus, il est nécessaire d’avoir des connaissances en programmation pour tirer profit de ces EDS.

Cette suite d’article vous donne les clefs pour vous initier à la programmation.

Cela dit, et c’est là la raison de la naissance du projet LinkR, notre plateforme vous permet d’accéder à ces données sans connaissance en programmation.

Après une demande à l’équipe de votre EDS local, les données peuvent être mises à disposition sur LinkR, et vous pourrez y travailler de façon collaborative.

Une interface graphique vous permettra de créer des onglets pour afficher et analyser les données.

Une interface de programmation intégrée permettra au data scientist de vous aider dans la réalisation de certains scripts.

On insiste, le fait de ne RIEN connaître en programmation n'est pas un frein !

Conclusion

Les points à retenir :

  • Le recueil de données manuel est chronophage et aboutit à des données qui peuvent être de mauvaise qualité
  • Les EDS permettent d'obtenir des données de meilleures qualité, en plus grande quantité en moins de temps
  • Il existe quelques freins à l'utilisation des EDS, que LinkR permet en partie de surmonter

2 - Bases de données

Des ressources sur les principaux modèles de données et sur des bases de données accessibles en ligne

2.1 - Qu'est-ce qu'une base de données ?

Par Boris Delange | 15.10.2024

Dans cet article, nous allons :

  • Définir ce qu'est une base de données en partant d'une analogie avec Excel
  • Evoquer les principes de conception qui permettent de créer une base de données
  • Aborder les notions de tables, de jointures et de schémas
  • Aborder le SQL, ce langage de programmation qui permet de requêter les bases de données

Principes de conception

Une base de données (BDD) est comme un grand tableur Excel, où chaque feuille représente une table.

Tout l’enjeu d’une base de données est :

  • d’éviter les redondances des données, pour prendre moins d’espace
  • de contenir le moins de cases vides possibles, toujours pour occuper moins d’espace
  • d’être le plus flexible possible, pour pouvoir ajouter de nouvelles données que l’on n’avait pas prévues initialement

Structurer les données en tables

Comment feriez-vous pour stocker dans un tableur Excel les données de biologie et les données démographiques de 5 patients ?


Mettons que l’on ait besoin de stocker le taux d’hémoglobine, les plaquettes et les leucocytes.

Le premier réflexe qui vient à l’esprit est de créer une colonne par paramètre de biologie.

Nous ajoutons une colonne date_biologie pour connaître la date de réalisation du prélèvement biologique.


patient_id age sexe date_admission date_sortie date_biologie hémoglobine plaquettes leucocytes
1 45 M 2024-10-01 2024-10-10 2024-10-03 13.5 / /
1 45 M 2024-10-01 2024-10-10 2024-10-04 / 150,000 7,200
2 60 F 2024-09-25 2024-10-05 2024-09-26 12.8 180,000 8,000
3 38 F 2024-10-05 2024-10-12 2024-10-07 14.0 220,000 /
3 38 F 2024-10-05 2024-10-12 2024-10-08 / / 6,500
4 52 M 2024-09-20 2024-09-30 2024-09-21 11.5 140,000 9,500
5 29 F 2024-10-08 2024-10-15 2024-10-09 13.2 170,000 7,800
5 29 F 2024-10-16 2024-10-20 2024-10-16 14.2 / /

Comment lire ce tableau ?


Le patient 1 a un seul séjour (une seule date_admission) et deux dosages biologiques à deux dates différentes durant ce même séjour (deux valeurs pour date_biologie).

Le patient 5 a deux séjours (deux valeurs pour date_admission), avec une biologie prélevée par séjour (deux valeurs différentes de date_biologie).

Nous pouvons remarquer deux choses :

  • Il existe une redondance des données démographiques (âge, sexe, dates d'admission et de sortie)
  • Nous avons dû créer une ligne par date de prélèvement biologique, ce qui fait que nous avons des cases vides aux dates où certaines biologies n'ont pas été réalisées


Si l’on revient à nos trois principes de conception (éviter les redondances, moins de cases vides et flexibilité), il semblerait que l’on puisse faire mieux.

Pourquoi ne pas créer une table (ou une feuille Excel pour continuer avec l’analogie) pour les patients ?


patient_id age sexe
1 45 M
2 60 F
3 38 F
4 52 M
5 29 F

On gagne ainsi de la place avec 3 lignes en moins.

Pourquoi ne pas avoir intégré les séjours dans cette table ?


Si on avait intégré les séjours dans cette table (avec les colonnes date_admission et date_sortie), nous aurions certes eu une seule ligne pour les patients 1 à 4, mais deux lignes pour le patient 5, qui a deux séjours différents.


Toujours dans une logique de diminuer le nombre de lignes, on préférera créer une table pour les séjours.

patient_id admission_id date_admission date_sortie
1 1 2024-10-01 2024-10-10
2 2 2024-09-25 2024-10-05
3 3 2024-10-05 2024-10-12
4 4 2024-09-20 2024-09-30
5 5 2024-10-08 2024-10-15
5 6 2024-10-16 2024-10-20

Pour finir, nous allons créer une table pour stocker les données de biologie.

patient_id admission_id date_biologie hémoglobine plaquettes leucocytes
1 1 2024-10-03 13.5 / /
1 1 2024-10-04 / 150,000 7,200
2 2 2024-09-26 12.8 180,000 8,000
3 3 2024-10-07 14.0 220,000 /
3 3 2024-10-07 / / 6,500
4 4 2024-09-21 11.5 140,000 9,500
5 5 2024-10-09 13.2 170,000 7,800
5 6 2024-10-16 14.2 / /

OK, mais ici nous avons encore des cases vides, nous pourrions optimiser.


La solution est de créer une colonne pour le nom du paramètre biologique, et une colonne pour sa valeur. Ainsi, plus de case vide !

patient_id admission_id date_biologie paramètre valeur
1 1 2024-10-03 hémoglobine 13.5
1 1 2024-10-04 plaquettes 150,000
1 1 2024-10-04 leucocytes 7,200
2 2 2024-09-26 hémoglobine 12.8
2 2 2024-09-26 plaquettes 180,000
2 2 2024-09-26 leucocytes 8,000
3 3 2024-10-07 hémoglobine 14.0
3 3 2024-10-07 plaquettes 220,000
3 3 2024-10-07 leucocytes 6,500
4 4 2024-09-21 hémoglobine 11.5
4 4 2024-09-21 plaquettes 140,000
4 4 2024-09-21 leucocytes 9,500
5 5 2024-10-09 hémoglobine 13.2
5 5 2024-10-09 plaquettes 170,000
5 5 2024-10-09 leucocytes 7,800
5 6 2024-10-16 hémoglobine 14.2

Nous venons de créer une base de données !


Alors certes, cela peut paraître moins lisible au premier abord, mais quand on a des millions de données, il est nécessaire d’optimiser leur stockage.

Et vous le verrez si vous faites un peu de programmation, cette manière d’organiser les données est finalement bien plus lisible qu’un fichier Excel à 50, 100 colonnes…

Jointures

Les données sont maintenant éparpillées sur plusieurs tables.

Comment les fusionner de nouveau ?


Si l’on veut fusionner plusieurs tables, on fera ce que l’on appelle une jointure.

Par exemple, si on fait une jointure entre les tables patients et séjours, en faisant une correspondance sur la colonne patient_id, nous obtiendrons cette table :

patient_id age sexe admission_id date_admission date_sortie
1 45 M 1 2024-10-01 2024-10-10
2 60 F 2 2024-09-25 2024-10-05
3 38 F 3 2024-10-05 2024-10-12
4 52 M 4 2024-09-20 2024-09-30
5 29 F 5 2024-10-08 2024-10-15
5 29 F 6 2024-10-16 2024-10-20

On pourra joindre les données de la table biologie de la même façon, et obtenir le tableau du début de l’article.

Requêter une base de données

Il existe un langage de programmation qui permet spécifiquement de requêter les bases de données.

Ce langage se nomme SQL, pour Structured Query Language.

C’est un langage assez simple et facile d’accès.

Il se compose de quelques mots clefs qui permettent d’obtenir les données que l’on veut, dont (non exhaustif) :

  • SELECT : vous choisissez les colonnes que vous voulez garder
  • FROM : de quelle table seront extraites les données ?
  • WHERE : quels filtres appliquer sur les données ?

Par exemple :

SELECT patient_id, age, sexe
FROM patients
WHERE age > 45

On sélectionne les colonnes patient_id, age, sexe de la table patients où la valeur de la colonne age est supérieur à la valeur 45.

On a le résultat suivant :

patient_id age sexe
2 60 F
4 52 M

Schémas de BDD

Ce que l’on appelle un schéma de base de données est la structure des tables qui composent une base de données.

Il spécifie :

  • le nom des tables
  • le nom des colonnes de chaque table
  • le type de données de chaque colonne (si la colonne doit comprendre des données de type texte ou numérique par exemple)

Par exemple, OMOP est un schéma de BDD spécialisée dans les données de santé.

Le schéma de données du modèle OMOP

Cette base est assez complexe, ce qui est nécessaire pour englober toutes les données de santé.

De même que nous l’avons fait plus haut, vous pouvez retrouver la table person qui correspond aux patients et la table visit_detail qui correspond aux séjours.

Conclusion

Les points à retenir :

  • Une base de données est un ensemble de tables avec un schéma particulier (noms des colonnes et type des données)
  • Les schémas des bases sont contruits selon des principes : éviter les redondances, optimiser l'espace et permettre la flexibilité
  • Les tables peuvent être liées entre-elles à l'aide de jointures
  • Le SQL est un langage de programmation permettant de requêter les bases de données

Pour aller plus loin :

2.2 - MIMIC

Par Boris Delange | 29.07.2024

Description de la base

La base de données MIMIC, pour Medical Information Mart for Intensive Care, est une base de données nord-américaine contenant des données de plus de 50 000 patients admis en réanimation. Il s’agit de l’une des bases de données de réanimation les plus utilisées, du fait de son accès gratuit.

Malgré des données d’une qualité imparfaite, elle constitue un bon socle pour apprendre à manipuler les données issues d’entrepôts de données de santé (EDS).

Elle existe en plusieurs versions, dont la plus récente est la MIMIC-IV.

Données test (publiques)

La base de donneés MIMIC comporte pour les versions III et IV des bases tests, qui contiennent les données anonymisées de 100 patients et qui sont accessibles publiquement.

Vous pouvez télécharger les données ici :

Données complètes

Pour accéder aux bases de données complètes, il est nécessaire de valider quelques étapes.

Rendez-vous sur la page de la base MIMIC-III.

Vous verrez cet encadré tout en bas de la page :

Vous devez donc commencer par vous inscrire sur le site physionet.org.

Vous devrez faire une demande d’accès à Physionet, en renseignant quelques informations et en donnant les coordonnées d’un superviseur ou d’un collègue, à qui un mail sera envoyé.

Vous devrez ensuite compléter le CITI Course, il s’agit d’une formation nécessaire afin d’accéder aux données hébergées sur le site Physionet. Les différentes étapes sont détaillées ici.

Vous pourrez ensuite télécharger le certificat une fois le CITI Course terminé, vous pourrez le le déposer ici pour validation par l’équipe de Physionet.

Il ne vous restera plus qu’à signer le data use agreement.

2.3 - Requêtes usuelles v5.4

Par Boris Delange, Antoine Lamer | 02.09.2024 | MàJ 12.11.2024

Introduction

Nous présentons ici quelques requêtes que l’on utilise fréquemment pour requêter les tables de bases de données au format OMOP.

Ces requêtes se basent sur la version 5.4 du schéma OMOP.

Le schéma de données du modèle commun OMOP v5.4 de Martijn Schuemie et Renske Los

Pour chaque table, les requêtes sont disponibles :

  • en utilisant la librairie dplyr de R (directement depuis LinkR)
  • en utilisant du SQL (PostgreSQL)
  • en utilisant la librairie pandas de Python

Pour le code en dplyr, nous utiliserons la fonction join_concepts qui est disponible dans LinkR (pas besoin de les déclarer de nouveau) :

# Permet de faire la jointure des ID et des noms de concepts

join_concepts <- function(df, concept_df, cols) {
  
  for (col in cols) {
    key <- paste0(col, "_concept_id")
    name <- paste0(col, "_concept_name")
    
    df <- df %>%
      dplyr::left_join(
        concept_df %>%
          dplyr::select(!!key := concept_id, !!name := concept_name),
        by = key,
        copy = TRUE
      ) %>%
      dplyr::relocate(!!name, .after = !!key)
  }
  
  return(df)
}

Dans les exemples, les données des tables OMOP sont chargées dans une liste d$.

# Charger les tables dans une liste 'd'
d <- list()

for (table in c("person", "visit_detail")){
    sql <- glue::glue_sql("SELECT * FROM {`table`}, .con = con)
    d[[table]] <- DBI::dbGetQuery(con, sql)
}

CONDITION_OCCURRENCE

OMOP CDM v5.4 PERSON Table
  1. Jointure des noms de concepts
d$condition_occurrence %>%
    join_concepts(d$concept, c("condition", "condition_type"))
  1. Calcul du nombre d’occurrences par condition_concept_name
d$condition_occurrence %>%
    join_concepts(d$concept, "condition") %>%
    dplyr::collect() %>%
    dplyr::count(condition_concept_name, sort = TRUE) %>%
    print(n = 100)

DRUG_STRENGTH

OMOP CDM v5.4 DRUG_STRENGTH Table
  1. Jointure des noms de concepts
d$drug_exposure %>%
    join_concepts(d$concept, c("drug", "drug_type", "route")) %>%
    dplyr::left_join(
        d$drug_strength %>%
            join_concepts(d$concept, c("ingredient", "amount_unit", "numerator_unit", "denominator_unit")) %>%
            dplyr::select(
                drug_concept_id, ingredient_concept_id, ingredient_concept_name,
                amount_value, amount_unit_concept_id, amount_unit_concept_name,
                numerator_value, numerator_unit_concept_id, numerator_unit_concept_name,
                denominator_value, denominator_unit_concept_id, denominator_unit_concept_name
            ),
        by = "drug_concept_id",
        copy = TRUE
    )
  1. Calcul du nombre d’occurrences par drug_concept_name
d$drug_exposure %>%
    join_concepts(d$concept, "drug") %>%
    dplyr::count(drug_concept_name, sort = TRUE) %>%
    dplyr::collect() %>%
    print(n = 100)
  1. Affichage des administrations

Comme indiqué ici, il existe différents cas pour calculer les doses des administrations médicamenteuses.

Nous pouvons retenir deux cas :

  • le champ amount_value est rempli, dans ce cas c’est celui-ci qu’il faut utiliser (cas des médicaments per os)
  • le champ amount_value n’est pas rempli, dans ce cas il faut utiliser le le champ numerator_value (cas des administrations intra-veineuses, sous-cutanées, en aérosols et en patchs)

Nous allons créer un champ amount, qui sera la synthèse des champs amount_value et numerator_value, ce sera la quantité délivrée du médicament.

Ensuite, nous pourrons déterminer le débit, en rapportant le champ amount au temps entre le début et la fin de l’administration. Nous allons pour cela créer un champ duration_hours, qui sera le nombre d’heures pendant lequel est délivré le médicament.

Nous créons également un champ daily_dose pour calculer la dose quotidienne moyenne sur la période, exprimée en daily_dose_unit.

Le modèle OMOP a initialement été développé pour des analyses épidémiologiques, il n’était pas important de savoir précisément la date et l’heure de l’administration d’un médicament, mais uniquement la quantité reçue globalement sur une période temps.

Ceci pose problème dans le cas des entrepôts de données de santé, où l’heure précise d’administration d’un médicament nous importe.

Il est donc important lors du processus d’ETL de créer une nouvelle ligne à chaque changement de dose, et une ligne par administration intermittente.

Par exemple, si un patient reçoit 1 g de paracétamol toutes les 6 heures pendant 5 jours, plutôt que de créer une ligne avec quantity = 5*4 = 20 et la date de fin à + 5 jours de la date de début, on préférera créer une ligne par administration, soit 20 lignes avec les horaires précis d’administration, avec date de début = date de fin.

d$drug_exposure %>%
    join_concepts(d$concept, c("drug", "drug_type", "route")) %>%
    dplyr::left_join(
        d$drug_strength %>%
            join_concepts(d$concept, c("ingredient", "amount_unit", "numerator_unit", "denominator_unit")) %>%
            dplyr::select(
                drug_concept_id, ingredient_concept_id, ingredient_concept_name,
                amount_value, amount_unit_concept_id, amount_unit_concept_name,
                numerator_value, numerator_unit_concept_id, numerator_unit_concept_name,
                denominator_value, denominator_unit_concept_id, denominator_unit_concept_name
            ),
        by = "drug_concept_id",
        copy = TRUE
    ) %>%
    dplyr::collect() %>%
    dplyr::arrange(person_id, drug_exposure_start_datetime) %>%
    dplyr::mutate(
        amount = dplyr::case_when(
            !is.na(amount_value) ~ quantity * amount_value,
            !is.na(numerator_value) ~ quantity * numerator_value
        ),
        amount_unit = dplyr::case_when(
            !is.na(amount_value) ~ amount_unit_concept_name,
            !is.na(numerator_value) ~ numerator_unit_concept_name
        ),
        duration_hours = as.numeric(difftime(drug_exposure_end_datetime, drug_exposure_start_datetime, units = "hours")),
        rate = dplyr::case_when(
            !is.na(numerator_value) & !is.na(duration_hours) & duration_hours > 0 ~ amount / duration_hours
        ),
        rate_unit = dplyr::case_when(
            !is.na(rate) & !is.na(amount_unit) ~ paste0(amount_unit, " per hour")
        ),
        daily_dose = dplyr::case_when(
            is.na(rate) & !is.na(amount) ~ amount / duration_hours * 24
        ),
        daily_dose_unit = dplyr::case_when(
            is.na(rate) & !is.na(amount_unit) ~ paste0(amount_unit, " per day")
        )
    ) %>%
    dplyr::select(
        person_id, drug_concept_name,
        drug_exposure_start_datetime, drug_exposure_end_datetime, duration_hours,
        amount, amount_unit, rate, rate_unit, daily_dose, daily_dose_unit
    )

MEASUREMENT

OMOP CDM v5.4 MEASUREMENT Table
  1. Jointure des noms de concepts
d$measurement %>%
    join_concepts(d$concept, c("measurement", "measurement_type", "operator", "unit"))
  1. Calcul du nombre d’occurrences par measurement_concept_name et unit_concept_name
d$measurement %>%
    join_concepts(d$concept, c("measurement", "unit")) %>%
    dplyr::count(measurement_concept_name, unit_concept_name, sort = TRUE) %>%
    dplyr::collect() %>%
    print(n = 100)

OBSERVATION

OMOP CDM v5.4 OBSERVATION Table
  1. Jointure des noms de concepts
d$observation %>%
    join_concepts(d$concept, c("observation", "observation_type", "value_as", "qualifier", "unit"))
  1. Calcul du nombre d’occurrences par observation_concept_name
d$observation %>%
    join_concepts(d$concept, "observation") %>%
    dplyr::count(observation_concept_name, sort = TRUE) %>%
    dplyr::collect() %>%
    print(n = 100)

PERSON

OMOP CDM v5.4 PERSON Table
  1. Calcul de l’âge

L’âge est calculé depuis les variables d$visit_occurrence (hospitalisations) ou d$visit_detail (séjours dans les unités au cours d’une hospitalisation).

Il s’agit de l’âge du patient à l’admission pour chaque hospitalisation ou admission dans une unité.

# Le code de calcul des dates étant mal converti en SQL, nous collectons les données avec dplyr::collect(),
# ce qui signifie que les données sont copiées dans un dataframe localement.
# Le code dplyr n'est ainsi pas converti en SQL.

d$visit_occurrence %>%
    dplyr::left_join(
        d$person %>% dplyr::select(person_id, birth_datetime),
        by = "person_id"
    ) %>%
    dplyr::collect() %>%
    dplyr::mutate(
        age = round(as.numeric(difftime(visit_start_datetime, birth_datetime, units = "days")) / 365.25, 1)
    )
-- DuckDB / PostgreSQL

SELECT 
    v.*, 
    ROUND(
        EXTRACT(EPOCH FROM (v.visit_start_datetime - p.birth_datetime)) / (365.25 * 86400), 
        1
    ) AS age
FROM 
    visit_occurrence v
LEFT JOIN 
    (SELECT person_id, birth_datetime FROM person) p
ON 
    v.person_id = p.person_id;
import pandas as pd

merged_df = pd.merge(
    visit_occurrence,
    person[['person_id', 'birth_datetime']],
    on='person_id',
    how='left'
)

merged_df['age'] = round(
    (merged_df['visit_start_datetime'] - merged_df['birth_datetime']).dt.total_seconds() / (365.25 * 86400), 
    1
)
  1. Jointure des noms de concepts
d$person %>%
    join_concepts(d$concept, c("gender", "race", "ethnicity"))
-- DuckDB / PostgreSQL

SELECT 
    p.*, 
    c.concept_name AS gender_concept_name
FROM 
    person p
LEFT JOIN 
    (SELECT concept_id AS gender_concept_id, concept_name FROM concept) c
ON 
    p.gender_concept_id = c.gender_concept_id;
import pandas as pd

concept_df = concept[['concept_id', 'concept_name']].rename(
    columns={'concept_id': 'gender_concept_id', 'concept_name': 'gender_concept_name'}
)

merged_df = pd.merge(
    person, 
    concept_df, 
    how='left', 
    left_on='gender_concept_id', 
    right_on='gender_concept_id'
)
  1. Calcul du nombre d’occurrences par gender_concept_name
d$person %>%
    join_concepts(d$concept, "gender") %>%
    dplyr::count(gender_concept_name, sort = TRUE) %>%
    dplyr::collect() %>%
    print(n = 100)

VISIT_DETAIL

OMOP CDM v5.4 VISIT_DETAIL Table
  1. Jointure des noms de concepts
d$visit_detail %>%
    join_concepts(d$concept, c("visit_detail", "visit_detail_type", "admitted_from", "discharge_to"))
  1. Calcul du nombre d’occurrences par visit_detail_concept_name
d$visit_detail %>%
    join_concepts(d$concept, "visit_detail")) %>%
    dplyr::count(visit_detail_concept_name, sort = TRUE) %>%
    dplyr::collect() %>%
    print(n = 100)

VISIT_OCCURRENCE

OMOP CDM v5.4 VISIT_OCCURRENCE Table
  1. Jointure des noms de concepts
d$visit_occurrence %>%
    join_concepts(d$concept, c("visit", "visit_type", "admitted_from", "discharge_to"))
  1. Calcul du nombre d’occurrences par visit_concept_name
d$visit_occurrence %>%
    join_concepts(d$concept, "visit") %>%
    dplyr::count(visit_concept_name, sort = TRUE) %>%
    dplyr::collect() %>%
    print(n = 100)

3 - Cas d'usage

Des cas concrets d’utilisation des données à partir des entrepôts de données de santé

3.1 - Tableau de bord de réanimation

Par Boris Delange | 01.08.2024

Vue d’ensemble

Dashboard

Ce tableau de bord permet d’obtenir une visualistaion des indicateurs de qualité des services de réanimation.

Il a l’avantage de pouvoir être configué directement par les cliniciens, sans nécessité de connaissances préalables en programmation.

Il est organisé en plusieurs onglets :

  • Démographie : Cette section montre le nombre de patients admis sur la période choisie, ainsi que leurs informations démographiques (âge, sexe), le taux de mortalité, le nombre d’admissions, le taux de réadmission et les principaux diagnostics ICD-10 des séjours.

  • Ventilation : Cette section fournit des données sur la ventilation mécanique, y compris le nombre de patients sous ventilateurs, la durée de la ventilation, le taux d’échec d’extubation, le taux d’auto-extubation et les paramètres ventilatoires sélectionnés.

  • Sédation : Nous y retrouvons les médicaments utilisés pour la sédation, la durée totale de sédation, la consommation de neuroleptiques etc.

  • Dialyse : Cette section fournit des informations sur le nombre de patients dialysés, avec quel type de dialyse.


Installation

Pour installer ce projet, vous pouvez suivre le tutoriel “Mise en place” de la documentation.

Vous pouvez également suivre ces tutoriels, si vous souhaitez importer vos propres données :

Contributions

LinkR est un projet en cours de développement.

En cas de problème pour l’installation ou pour l’utilisation, ou si vous avez des suggestions pour améliorer l’application, merci de nous contacter à linkr-app@pm.me.

Prochaines étapes

Pour le moment, seule la partie “Démographie” est disponible, les autres sections en cours de développement.

Ce tableau de bord est configuré pour visualiser les données de réanimation. Une prochaine étape sera d’appliquer ce modèle de tableau de bords aux autres services hospitaliers et à la médecine libérale.