Compartilhar via


Tutorial: Descobrir relacionamentos no conjunto de dados Synthea utilizando o link semântico

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

Ao trabalhar com novos dados ou sem um modelo de dados existente, descobrir relações automaticamente pode ser útil. Essa detecção de relação pode ajudar você 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
  • Limpar os 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 na identificação de problemas de qualidade dos dados.

Neste tutorial, você começa com um exemplo de linha de base simples no qual você testa 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 o seu notebook.
    • Funções que automatizam a descoberta e a visualização das 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. Esse workspace se torna seu workspace atual.

Acompanhar no notebook

O notebook relationships_detection_tutorial.ipynb acompanha este tutorial.

Para abrir o notebook que acompanha este tutorial, siga as instruções em Preparar seu sistema para tutoriais de ciência de dados para importar os notebooks para seu workspace.

Se preferir copiar e colar o código a partir dessa página, você poderá criar um novo notebook.

Certifique-se de anexar um lakehouse ao notebook antes de começar a executar o código.

Configurar o notebook

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

  1. Instale o SemPy por meio do PyPI usando a funcionalidade de instalação em linha %pip no notebook:

    %pip install semantic-link
    
  2. Faça as importações necessárias dos módulos da SemPy de 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 o 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. Efetue pull dos dados de exemplo. Para este tutorial, você usará o conjunto de dados 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 as informações do paciente
    • encounters especifica quais pacientes tiveram contato com médicos (por exemplo, uma consulta médica, um 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 da SemPy:

    suggested_relationships = find_relationships([patients, providers, encounters])
    suggested_relationships
    
  3. Visualize as relações de DataFrame como um gráfico usando a função plot_relationship_metadata da SemPy.

    plot_relationship_metadata(suggested_relationships)
    

    Captura de tela que mostra relacionamentos 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 "1:m") ou "1:1". As relações "1:1" podem ser geradas de uma ou ambas as formas, dependendo de se a taxa de valores mapeados para todos os valores excede o coverage_threshold em apenas uma ou em 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 uma detecção bem-sucedida. Há várias técnicas que podem ser úteis quando os dados não são limpos.

Esta seção deste tutorial aborda a detecção de relação quando o modelo semântico está sujo.

  1. Comece trabalhando com os DataFrames originais para obter dados "sujos" e imprima o tamanho desses dados.

    # 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 da 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 introduzidos anteriormente para criar o modelo semântico "sujo".

Usar a validação

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

  • Ela relata claramente por que uma relação específica não segue as regras da Chave de referência e, portanto, não pode ser detectada.
  • Ela é executada rapidamente com grandes modelos semânticos 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 a patients_dirty, mas você pode gerar alias dos DataFrames com um dicionário:

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

errors = list_relationship_violations(dirty_tables, suggested_relationships)
errors

Amenizar os critérios de pesquisa

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

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

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

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

    • A relação indica uma direção de patients para encounters, o 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 pelas colunas Unique Count From e Unique Count To.
  2. Execute find_relationships novamente para procurar apenas relações "m:1", mas com um coverage_threshold=0.5 mais baixo:

    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. Porém, a relação de encounters para patients não é detectada, porque patients não é exclusivo e, 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 estã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, a 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 nomeiam colunas relacionadas da mesma forma. Esse comportamento ajuda a evitar relações espúrias, que ocorrem com mais frequência com chaves inteiras 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.

A 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” em “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. Corresponda 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. No seu exemplo, as tabelas apresentam 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. Execute novamente com o padrão name_similarity_threshold=0.8:

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

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

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

    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). Geralmente, esse é o resultado desejado.
  2. Use um foco restrito 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 cuja análise pode ser mais difícil e precisarão ser filtrados.

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 do completo Synthea, que tem muito mais tabelas. Explore o conjunto de dados completo synthea 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 da SemPy:

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

    plot_relationship_metadata(suggested_relationships)
    

    Captura de tela dos relacionamentos entre tabelas.

  4. Conte quantas novas relações "m:m" serão descobertas com include_many_to_many=True. Tratam-se de relações além das "m:1" mostradas anteriormente; portanto, você precisa filtrar: 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 em várias colunas para obter uma compreensão mais profunda da natureza deles. Por exemplo, você pode optar por ordenar a saída por Row Count From e Row Count To, o que ajuda a identificar as tabelas maiores.

    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 ajudar você a entender se alguma das relações pode ser inválida e se você precisa removê-la da lista de candidatos.

Faça o check-out de outros tutoriais sobre link semântico / SemPy: