Compartilhar via


Tutorial: Descobrir relações no conjunto de dados do Synthea usando o link semântico

Este tutorial ilustra como detectar relações no conjunto de dados do Synthea público usando o link semântico.

Quando você está trabalhando com novos dados ou trabalhando sem um modelo de dados existente, pode ser útil descobrir relações automaticamente. Essa detecção de relação pode ajudá-lo a:

  • entender o modelo em um alto nível
  • obter mais insights durante a análise de dados exploratória,
  • validar dados atualizados ou novos dados de entrada e
  • e limpar dados.

Mesmo que as relações sejam conhecidas com antecedência, uma pesquisa por relações pode ajudar a entender melhor o modelo de dados ou a identificação de problemas de qualidade de dados.

Neste tutorial, você começa com um exemplo de linha de base simples em que você experimenta apenas três tabelas para que as conexões entre elas sejam fáceis de seguir. Em seguida, você mostra um exemplo mais complexo com um conjunto de tabelas maior.

Neste tutorial, você aprenderá a:

  • Use componentes da biblioteca Python do link semântico (SemPy) que dão suporte à integração com o Power BI e ajudam a automatizar a análise de dados. Esses componentes incluem:
    • FabricDataFrame - uma estrutura semelhante a pandas aprimorada com informações semânticas adicionais.
    • Funções para extrair modelos semânticos de um workspace do Fabric para seu notebook.
    • Funções que automatizam a descoberta e visualização de relações em seus modelos semânticos.
  • Solucionar problemas do processo de descoberta de relação para modelos semânticos com várias tabelas e interdependências.

Pré-requisitos

  • Selecione Workspaces no painel de navegação esquerdo para localizar e selecionar seu workspace. Essa área de trabalho se torna sua área de trabalho atual.

Acompanhar no notebook

O notebook relationships_detection_tutorial.ipynb acompanha este tutorial.

Configurar o notebook

Nesta seção, você configurará um ambiente de notebook com os módulos e dados necessários.

  1. Instale SemPy do PyPI usando o recurso de instalação %pip em linha dentro do notebook:

    %pip install semantic-link
    
  2. Execute as importações necessárias de módulos SemPy que você precisará mais tarde:

    import pandas as pd
    
    from sempy.samples import download_synthea
    from sempy.relationships import (
        find_relationships,
        list_relationship_violations,
        plot_relationship_metadata
    )
    
  3. Importe pandas para impor uma opção de configuração que ajuda na formatação de saída:

    import pandas as pd
    pd.set_option('display.max_colwidth', None)
    
  4. Extraia os dados de exemplo. Para este tutorial, você usará o conjunto de dados do Synthea de registros médicos sintéticos (versão pequena para simplificar):

    download_synthea(which='small')
    

Detectar relações em um pequeno subconjunto de tabelas Synthea

  1. Selecione três tabelas de um conjunto maior:

    • patients especifica informações do paciente
    • encounters especifica os pacientes que tiveram encontros médicos (por exemplo, uma consulta médica, procedimento)
    • providers especifica quais provedores médicos atenderam aos pacientes

    A tabela encounters resolve uma relação muitos para muitos entre patients e providers e pode ser descrita como uma entidade associativa:

    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. Encontre relações entre as tabelas usando a função find_relationships do SemPy:

    suggested_relationships = find_relationships([patients, providers, encounters])
    suggested_relationships
    
  3. Visualize as relações dataframe como um grafo, usando a função plot_relationship_metadata do SemPy.

    plot_relationship_metadata(suggested_relationships)
    

    Captura de tela mostrando relações entre tabelas no conjunto de dados.

    A função estabelece a hierarquia de relação do lado esquerdo para o direito, o que corresponde a tabelas "de" e "para" na saída. Em outras palavras, as tabelas independentes "de" no lado esquerdo usam suas chaves de referência para apontar para suas tabelas de dependência "para" no lado direito. Cada caixa de entidade mostra colunas que participam do lado "de" ou "para" de uma relação.

    Por padrão, as relações são geradas como "m:1" (não como "1:m") ou "1:1". As relações "1:1" podem ser geradas de uma ou ambas as maneiras, dependendo de a razão entre valores mapeados e todos os valores exceder coverage_threshold em apenas uma ou ambas as direções. Posteriormente neste tutorial, você abordará o caso menos frequente de relações "m:m".

Solucionar problemas de detecção de relação

O exemplo de linha de base mostra uma detecção de relação bem-sucedida em dados limpos Synthea. Na prática, os dados raramente são limpos, o que impede a detecção bem-sucedida. Há várias técnicas que podem ser úteis quando os dados não estão limpos.

Esta seção deste tutorial aborda a detecção de relação quando o modelo semântico contém dados sujos.

  1. Comece manipulando os DataFrames originais para obter dados "sujos" e imprima o tamanho dos dados sujos.

    # 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. Para comparação, imprima os tamanhos das tabelas originais:

    print(len(patients))
    print(len(providers))
    
  3. Encontre relações entre as tabelas usando a função find_relationships do SemPy:

    find_relationships([patients_dirty, providers_dirty, encounters])
    

    A saída do código mostra que não há relações detectadas devido aos erros que você introduziu anteriormente para criar o modelo semântico "sujo".

Usar a validação

A validação é a melhor ferramenta para solucionar falhas na detecção de relações porque:

  • Ele explica claramente por que uma relação específica não segue as regras de chave estrangeira e, portanto, não pode ser detectada.
  • Ele é executado rapidamente com modelos semânticos grandes porque se concentra apenas nas relações declaradas e não executa uma pesquisa.

A validação pode usar qualquer DataFrame com colunas semelhantes à gerada por find_relationships. No código a seguir, o DataFrame suggested_relationships refere-se a patients em vez de patients_dirty, mas você pode alias os DataFrames com um dicionário:

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

errors = list_relationship_violations(dirty_tables, suggested_relationships)
errors

Afrouxar critérios de pesquisa

Em cenários mais obscuros, você pode tentar afrouxar seus critérios de pesquisa. Esse método aumenta a possibilidade de falsos positivos.

  1. Defina include_many_to_many=True e avalie se ele ajuda:

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

    Os resultados mostram que a relação de encounters a patients foi detectada, mas há dois problemas:

    • A relação indica uma direção de patients para encounters, que é um inverso da relação esperada. Isso ocorre porque todos os patients passaram a ser cobertor por encounters (Coverage From é 1,0), enquanto os encounters são apenas parcialmente cobertos por patients (Coverage To = 0,85), uma vez que as linhas dos pacientes estão ausentes.
    • Há uma correspondência acidental em uma coluna GENDER de baixa cardinalidade, que acaba correspondendo por nome e valor em ambas as tabelas, mas não é uma relação de interesse "m:1". A baixa cardinalidade é indicada por colunas Unique Count From e Unique Count To.
  2. Execute novamente find_relationships para procurar apenas relações "m:1", mas com um coverage_threshold=0.5inferior:

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

    O resultado mostra a direção correta das relações de encounters para providers. No entanto, a relação de encounters a patients não é detectada, porque patients não é exclusivo, portanto, não pode estar no lado "Um" da relação "m:1".

  3. Amenizar include_many_to_many=True e coverage_threshold=0.5:

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

    Agora, ambas as relações de interesse são visíveis, mas há muito mais ruído:

    • A baixa correspondência de cardinalidade de GENDER está presente.
    • Apareceu uma maior correspondência de cardinalidade "m:m" de ORGANIZATION, tornando evidente que ORGANIZATION é provavelmente uma coluna desnormalizada para ambas as tabelas.

Corresponder nomes de coluna

Por padrão, o SemPy considera como correspondência apenas os atributos que mostram similaridade de nome, aproveitando o fato de que os designers de banco de dados geralmente nomeam colunas relacionadas da mesma maneira. Esse comportamento ajuda a evitar relações espúrias, que ocorrem com mais frequência com chaves inteiros de baixa cardinalidade. Por exemplo, se houver 1,2,3,...,10 categorias de produto e 1,2,3,...,10 códigos de status de pedido, eles serão confundidos entre si quando apenas mapeamentos de valores forem analisados, sem levar em conta os nomes de coluna. Relações espúrias não devem ser um problema com chaves do tipo GUID.

O SemPy analisa uma semelhança entre nomes de coluna e nomes de tabela. A correspondência é aproximada e não diferencia maiúsculas de minúsculas. Ela ignora as subcadeias de caracteres "decorador" encontradas com mais frequência, como "id", "code", "name", "key", "pk" e "fk". Como resultado, os casos de correspondência mais típicos são:

  • um atributo chamado “column” na entidade “foo” corresponde a um atributo chamado “column” (também “COLUMN” ou “Column”) na entidade 'bar'.
  • Um atributo chamado 'column' na entidade 'foo' corresponde a um atributo chamado 'column_id' na entidade 'bar'.
  • um atributo chamado “bar” na entidade “foo” corresponde a um atributo chamado “code” em “bar”.

Ao corresponder primeiro os nomes de coluna, a detecção é executada mais rapidamente.

  1. Combine os nomes das colunas.

    • Para entender quais colunas são selecionadas para avaliação adicional, use a opção verbose=2 (verbose=1 lista apenas as entidades que estão sendo processadas).
    • O parâmetro name_similarity_threshold determina como as colunas são comparadas. O limite de 1 indica que seu interesse é em apenas 100% de correspondência.
    find_relationships(dirty_tables, verbose=2, name_similarity_threshold=1.0);
    

    A execução com 100% de similaridade não contabiliza pequenas diferenças entre os nomes. Em seu exemplo, as tabelas têm um formato plural com sufixo "s", o que não resulta em correspondência exata. Isso é bem resolvido com o padrão name_similarity_threshold=0.8.

  2. Executar novamente com o name_similarity_threshold=0.8padrão:

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

    Observe que o ID para a forma plural patients agora é comparado ao patient singular, sem adicionar muitas outras comparações espúrias ao tempo de execução.

  3. Executar novamente com o name_similarity_threshold=0padrão:

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

    Alterar name_similarity_threshold para 0 é o outro extremo e indica que você deseja comparar todas as colunas. Isso raramente é necessário e resulta em maior tempo de execução e correspondências espúrias que precisam ser revisadas. Observe o número de comparações na saída detalhada.

Resumo das dicas de solução de problemas

  1. Comece a partir da correspondência exata para relações "m:1" (ou seja, o padrão include_many_to_many=False e coverage_threshold=1.0). Isso geralmente é o que você quer.
  2. Use um foco estreito em subconjuntos menores de tabelas.
  3. Use a validação para detectar problemas de qualidade de dados.
  4. Use verbose=2 se quiser entender quais colunas são consideradas para a relação. Isso pode resultar em uma grande quantidade de saída.
  5. Esteja ciente das desvantagens dos argumentos de pesquisa. include_many_to_many=True e coverage_threshold<1.0 podem produzir relações espúrias que podem ser mais difíceis de analisar e precisarão ser filtradas.

Detectar relações no conjunto de dados completo Synthea

O exemplo de linha de base simples foi uma ferramenta conveniente de aprendizado e solução de problemas. Na prática, você pode começar a partir de um modelo semântico, como o conjunto de dados de Synthea completo, que tem muito mais tabelas. Explore o conjunto de dados synthea completo da seguinte maneira.

  1. Leia todos os arquivos do diretório 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. Encontre relações entre as tabelas usando a função find_relationships do SemPy:

    suggested_relationships = find_relationships(all_tables)
    suggested_relationships
    
  3. Visualizar relações:

    plot_relationship_metadata(suggested_relationships)
    

    Captura de tela das relações entre tabelas.

  4. Conte quantas novas relações "m:m" serão descobertas com include_many_to_many=True. Essas relações são além das relações "m:1" mostradas anteriormente; portanto, você precisa filtrar em multiplicity:

    suggested_relationships = find_relationships(all_tables, coverage_threshold=1.0, include_many_to_many=True) 
    suggested_relationships[suggested_relationships['Multiplicity']=='m:m']
    
  5. Você pode classificar os dados de relação por várias colunas para obter uma compreensão mais profunda de sua natureza. Por exemplo, você pode optar por ordenar a saída por Row Count From e Row Count To, que ajudam a identificar as maiores tabelas.

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

    Em um modelo semântico diferente, talvez seja importante se concentrar no número de nulos Null Count From ou Coverage To.

    Essa análise pode ajudá-lo a entender se alguma das relações pode ser inválida e se você precisar removê-las da lista de candidatos.

Confira outros tutoriais para link semântico/SemPy: