Partager via


Tutoriel : Découvrir les relations dans le jeu de données Synthea, à l’aide du lien sémantique

Ce tutoriel montre comment détecter les relations dans le jeu de données public Synthea en utilisant le lien sémantique.

Lorsque vous travaillez avec de nouvelles données ou que vous travaillez sans modèle de données existant, il peut être utile de découvrir automatiquement les relations. Cette détection de relation peut vous aider à :

  • comprendre le modèle à un niveau élevé,
  • obtenir plus d’insights lors de l’analyse exploratoire des données,
  • valider les nouvelles données ou celles mises à jour, les données entrantes, et
  • Données propres.

Même si les relations sont connues à l’avance, une recherche de relations peut vous aider à mieux comprendre le modèle de données ou l’identification des problèmes de qualité des données.

Dans ce tutoriel, vous commencez par un exemple de base simple où vous expérimentez seulement trois tables afin que les connexions entre elles soient faciles à suivre. Ensuite, un exemple plus complexe est affiché avec un ensemble de tables plus volumineux.

Dans ce tutoriel, vous allez apprendre à :

  • Utiliser des composants de la bibliothèque Python (SemPy) du lien sémantique qui prennent en charge l’intégration à Power BI et vous aident à automatiser l’analyse des données. Ces composants sont les suivants :
    • FabricDataFrame : structure de type pandas améliorée avec des informations sémantiques supplémentaires.
    • Des fonctions permettant de tirer des modèles sémantiques d’un espace de travail Fabric dans votre notebook.
    • Des fonctions qui automatisent la découverte et la visualisation des relations dans vos modèles sémantiques.
  • Résoudre les problèmes de processus de découverte de relation pour les modèles sémantiques avec plusieurs tables et interdépendances.

Prérequis

  • Sélectionnez Espaces de travail dans le volet de navigation gauche pour rechercher et sélectionner votre espace de travail. Cet espace de travail devient votre espace de travail actuel.

Suivre le notebook

Le notebook relationships_detection_tutorial.ipynb accompagne ce tutoriel.

Pour ouvrir le notebook accompagnant ce tutoriel, suivez les instructions fournies dans Préparer votre système pour le tutoriel sur la science des données afin d’importer les notebooks dans votre espace de travail.

Si vous préférez copier et coller le code de cette page, vous pouvez créer un nouveau notebook.

Assurez-vous d’attacher un lakehouse au notebook avant de commencer à exécuter du code.

Configurer le notebook

Dans cette section, vous configurez un environnement de notebook avec les modules et données nécessaires.

  1. Installez SemPy à partir de PyPI en utilisant la fonctionnalité d’installation inline %pip dans le notebook :

    %pip install semantic-link
    
  2. Effectuez les importations nécessaires des modules SemPy que vous devez utiliser par la suite :

    import pandas as pd
    
    from sempy.samples import download_synthea
    from sempy.relationships import (
        find_relationships,
        list_relationship_violations,
        plot_relationship_metadata
    )
    
  3. Importez pandas pour appliquer une option de configuration qui permet de mettre en forme la sortie :

    import pandas as pd
    pd.set_option('display.max_colwidth', None)
    
  4. Extrayez les exemples de données. Pour ce tutoriel, vous allez utiliser le jeu de données Synthea de dossiers médicaux synthétiques (version réduite pour plus de simplicité) :

    download_synthea(which='small')
    

Détecter les relations sur un petit sous-ensemble de tables Synthea

  1. Sélectionnez trois tables dans un ensemble plus grand :

    • patients spécifie les informations des patients
    • encounters spécifie les patients qui ont eu des rencontres médicales (par exemple, un rendez-vous médical, une procédure)
    • providers spécifie quels prestataires médicaux ont pris en charge les patients

    La table encounters résout une relation plusieurs-à-plusieurs entre patients et providers et peut être décrite comme une entité associative :

    patients = pd.read_csv('synthea/csv/patients.csv')
    providers = pd.read_csv('synthea/csv/providers.csv')
    encounters = pd.read_csv('synthea/csv/encounters.csv')
    
  2. Recherchez des relations entre les tables à l’aide de la fonction find_relationships de SemPy :

    suggested_relationships = find_relationships([patients, providers, encounters])
    suggested_relationships
    
  3. Visualisez les relations DataFrame dans un graphique en utilisant la fonction plot_relationship_metadata de SemPy.

    plot_relationship_metadata(suggested_relationships)
    

    Capture d’écran montrant les relations entre les tableaux du jeu de données.

    La fonction définit la hiérarchie de relation du côté gauche vers le côté droit, qui correspond aux tables « from » et « to » dans la sortie. En d’autres termes, les tables indépendantes « from » situées à gauche utilisent leurs clés étrangères pour pointer vers leurs tables de dépendance « to » sur le côté droit. Chaque zone d’entité affiche les colonnes qui participent du côté « from » ou « to » d’une relation.

    Par défaut, les relations sont générées sous la forme « m:1 » (pas comme « 1:m ») ou « 1:1 ». Les relations « 1:1 » peuvent être générées d’une ou des deux façons, selon que le ratio des valeurs mappées à toutes les valeurs dépasse coverage_threshold dans une ou les deux directions. Plus loin dans ce tutoriel, vous découvrirez le cas moins fréquent des relations « m:m ».

Résoudre les problèmes de détection des relations

L’exemple de base montre une détection de relation réussie sur des données Synthea propres. En pratique, les données sont rarement nettoyées, ce qui empêche une détection réussie. Il existe plusieurs techniques qui peuvent être utiles lorsque les données ne sont pas nettoyées.

Cette section de ce tutoriel traite de la détection des relations lorsque le modèle sémantique contient des données non nettoyées.

  1. Commencez par manipuler les DataFrames d’origine pour obtenir des données « sales » et imprimer la taille des données non nettoyées.

    # create a dirty 'patients' dataframe by dropping some rows using head() and duplicating some rows using concat()
    patients_dirty = pd.concat([patients.head(1000), patients.head(50)], axis=0)
    
    # create a dirty 'providers' dataframe by dropping some rows using head()
    providers_dirty = providers.head(5000)
    
    # the dirty dataframes have fewer records than the clean ones
    print(len(patients_dirty))
    print(len(providers_dirty))
    
    
  2. Pour la comparaison, imprimez les tailles des tables d’origine :

    print(len(patients))
    print(len(providers))
    
  3. Recherchez des relations entre les tables à l’aide de la fonction find_relationships de SemPy :

    find_relationships([patients_dirty, providers_dirty, encounters])
    

    La sortie du code indique qu’aucune relation n’est détectée en raison des erreurs que vous avez introduites précédemment pour créer le modèle sémantique « non nettoyé ».

Utiliser la validation

La validation est le meilleur outil pour résoudre les échecs de détection des relations, car :

  • Elle signale clairement pourquoi une relation particulière ne suit pas les règles de Clé étrangère et ne peut donc pas être détectée.
  • Elle s’exécute rapidement avec des modèles sémantiques volumineux, car elle se concentre uniquement sur les relations déclarées et n’effectue pas de recherche.

La validation peut utiliser n’importe quel DataFrame avec des colonnes similaires à celles générées par find_relationships. Dans le code suivant, le DataFrame suggested_relationships fait référence à patients plutôt qu’à patients_dirty, mais vous pouvez créer un alias pour les DataFrames avec un dictionnaire :

dirty_tables = {
    "patients": patients_dirty,
    "providers" : providers_dirty,
    "encounters": encounters
}

errors = list_relationship_violations(dirty_tables, suggested_relationships)
errors

Assouplir les critères de recherche

Dans des scénarios plus troubles, vous pouvez essayer d’assouplir vos critères de recherche. Cette méthode augmente la possibilité de faux positifs.

  1. Définissez include_many_to_many=True et évaluez si c’est utile :

    find_relationships(dirty_tables, include_many_to_many=True, coverage_threshold=1)
    

    Les résultats montrent que la relation entre encounters et patients a été détectée, mais il existe deux problèmes :

    • La relation indique une direction de patients à encounters, ce qui est l’inverse de la relation attendue. Cela est dû au fait que tous les patients ont été couverts par les encounters (Coverage From est 1,0) alors que les encounters ne sont couverts que partiellement par les patients (Coverage To = 0,85), car les lignes des patients sont manquantes.
    • Il existe une correspondance accidentelle sur une colonne GENDER de faible cardinalité, qui correspond par nom et valeur dans les deux tables, mais il ne s’agit pas d’une relation « m:1 » d’intérêt. La cardinalité faible est indiquée par les colonnes Unique Count From et Unique Count To.
  2. Réexécutez find_relationships pour rechercher uniquement les relations « m:1 », mais avec une valeur coverage_threshold=0.5 inférieure :

    find_relationships(dirty_tables, include_many_to_many=False, coverage_threshold=0.5)
    

    Le résultat affiche la direction correcte des relations de encounters à providers. Toutefois, la relation de encounters à patients n’est pas détectée, car patients n’est pas unique, elle ne peut donc pas être du côté « Un » de la relation « m:1 ».

  3. Assouplissez include_many_to_many=True et coverage_threshold=0.5 :

    find_relationships(dirty_tables, include_many_to_many=True, coverage_threshold=0.5)
    

    Maintenant, les deux relations d’intérêt sont visibles, mais il y a beaucoup plus de d’informations :

    • La correspondance de cardinalité faible sur GENDER est présente.
    • Une correspondance « m:m » de cardinalité plus élevée sur ORGANIZATION est apparue, ce qui rend évident que ORGANIZATION est probablement une colonne dé-normalisée dans les deux tables.

Mettre en correspondance les noms de colonnes

Par défaut, SemPy considère comme correspondance uniquement les attributs qui affichent une similarité du nom, en tirant parti du fait que les concepteurs de base de données nomment généralement les colonnes associées de la même façon. Ce comportement permet d’éviter des relations erronées, qui se produisent plus fréquemment avec des clés entières de cardinalité faible. Par exemple, s’il existe des catégories de produits 1,2,3,...,10 et un code d’état de commande 1,2,3,...,10, elles sont confondus les unes avec les autres lorsque vous examinez uniquement les mappages de valeurs sans prendre en compte les noms de colonnes. Les relations erronées ne devraient pas être un problème avec les clés de type GUID.

SemPy étudie une similarité entre les noms de colonnes et les noms de table. La correspondance est approximative et ne respecte pas la casse. Elle ignore les substrings « decorator » les plus fréquemment rencontrées, telles que « id », « code », « name », « key », « pk », « fk ». Par conséquent, les cas de correspondance les plus classiques sont les suivants :

  • un attribut appelé « column » dans l’entité « foo » correspond à un attribut appelé « column » (également « COLUMN » ou « Column ») dans l’entité « bar ».
  • un attribut appelé « column » dans l’entité « foo » correspond à un attribut appelé « column_id » dans « bar ».
  • un attribut appelé « bar » dans l’entité « foo » correspond à un attribut appelé « code » dans « bar ».

En mettant en correspondance les noms de colonnes en premier, la détection s’exécute plus rapidement.

  1. Mettez en correspondance les noms de colonnes :

    • Pour comprendre quelles colonnes sont sélectionnées pour une évaluation ultérieure, utilisez l’option verbose=2 (verbose=1 répertorie uniquement les entités en cours de traitement).
    • Le paramètre name_similarity_threshold détermine la façon dont les colonnes sont comparées. Le seuil de 1 indique que vous êtes intéressé uniquement par les correspondances à 100 %.
    find_relationships(dirty_tables, verbose=2, name_similarity_threshold=1.0);
    

    L’exécution avec une similarité à 100 % ne tient pas compte des petites différences entre les noms. Dans votre exemple, les tables ont une forme au pluriel avec le suffixe « s », ce qui n’entraîne aucune correspondance exacte. Cela est géré correctement avec la valeur name_similarity_threshold=0.8 par défaut.

  2. Réexécutez avec la valeur name_similarity_threshold=0.8 par défaut :

    find_relationships(dirty_tables, verbose=2, name_similarity_threshold=0.8);
    

    Notez que l’ID de patients à la forme plurielle est maintenant comparé à patient au singulier sans ajouter trop d’autres comparaisons erronées au temps d’exécution.

  3. Réexécutez avec la valeur name_similarity_threshold=0 par défaut :

    find_relationships(dirty_tables, verbose=2, name_similarity_threshold=0);
    

    La modification de name_similarity_threshold à 0 est l’autre extrême et indique que vous souhaitez comparer toutes les colonnes. Cela est rarement nécessaire et entraîne une augmentation du temps d’exécution et des correspondances erronées qui doivent être examinées. Observez le nombre de comparaisons dans la sortie détaillée.

Résumé des astuces de dépannage

  1. Commencez à partir d’une correspondance exacte pour les relations « m:1 » (autrement dit, include_many_to_many=False et coverage_threshold=1.0 par défaut). C’est généralement l’option souhaitée.
  2. Utilisez un focus étroit sur des sous-ensembles de tables plus petits.
  3. Utilisez la validation pour détecter les problèmes de qualité des données.
  4. Utilisez verbose=2 si vous souhaitez comprendre les colonnes prises en compte pour la relation. Cela peut entraîner une grande quantité de sortie.
  5. Tenez compte des compromis des arguments de recherche. include_many_to_many=True et coverage_threshold<1.0 peuvent produire des relations erronées qui peuvent être plus difficiles à analyser et qui devront être filtrées.

Détecter des relations sur le jeu de données complet Synthea

L’exemple de base simple était un outil pratique d’apprentissage et de résolution des problèmes. Dans la pratique, vous pouvez commencer à partir d’un modèle sémantique tel que le jeu de données complet Synthea, qui a beaucoup plus de tables. Explorez le jeu de données complet Synthea comme suit.

  1. Lisez tous les fichiers du répertoire synthea/csv :

    all_tables = {
        "allergies": pd.read_csv('synthea/csv/allergies.csv'),
        "careplans": pd.read_csv('synthea/csv/careplans.csv'),
        "conditions": pd.read_csv('synthea/csv/conditions.csv'),
        "devices": pd.read_csv('synthea/csv/devices.csv'),
        "encounters": pd.read_csv('synthea/csv/encounters.csv'),
        "imaging_studies": pd.read_csv('synthea/csv/imaging_studies.csv'),
        "immunizations": pd.read_csv('synthea/csv/immunizations.csv'),
        "medications": pd.read_csv('synthea/csv/medications.csv'),
        "observations": pd.read_csv('synthea/csv/observations.csv'),
        "organizations": pd.read_csv('synthea/csv/organizations.csv'),
        "patients": pd.read_csv('synthea/csv/patients.csv'),
        "payer_transitions": pd.read_csv('synthea/csv/payer_transitions.csv'),
        "payers": pd.read_csv('synthea/csv/payers.csv'),
        "procedures": pd.read_csv('synthea/csv/procedures.csv'),
        "providers": pd.read_csv('synthea/csv/providers.csv'),
        "supplies": pd.read_csv('synthea/csv/supplies.csv'),
    }
    
  2. Recherchez des relations entre les tables à l’aide de la fonction find_relationships de SemPy :

    suggested_relationships = find_relationships(all_tables)
    suggested_relationships
    
  3. Visualisez les relations :

    plot_relationship_metadata(suggested_relationships)
    

    Capture d’écran des relations entre les tableaux.

  4. Comptez le nombre de nouvelles relations « m:m » découvertes avec include_many_to_many=True. Ces relations sont en plus des relations « m:1 » précédemment affichées, par conséquent, vous devez filtrer sur multiplicity :

    suggested_relationships = find_relationships(all_tables, coverage_threshold=1.0, include_many_to_many=True) 
    suggested_relationships[suggested_relationships['Multiplicity']=='m:m']
    
  5. Vous pouvez trier les données de relation par différentes colonnes pour mieux comprendre leur nature. Par exemple, vous pouvez choisir de classer la sortie par Row Count From et Row Count To, ce qui permet d’identifier les tables les plus volumineuses.

    suggested_relationships.sort_values(['Row Count From', 'Row Count To'], ascending=False)
    

    Dans un autre modèle sémantique, il serait peut-être important de se concentrer sur le nombre de valeurs null Null Count From ou Coverage To.

    Cette analyse peut vous aider à comprendre si l’une des relations peut être non valide et si vous devez les supprimer de la liste des candidats.

Découvrez d’autres tutoriels pour le lien sémantique / SemPy :