티스토리 뷰

이번 글에서는 그래프 경로 탐색을 통해 데이터간의 복잡한 관계를 찾아보는 것에 대해 다루려고 한다.

데이터 간의 연결 관계를 찾아나가는 것은 데이터를 통해 정보를 파악하는 것에 도움이 되는 측면이 있는데 이와 함께 그 연결관계에 대한 명확한 설명력이 있어야 한다.

따라서 그래프 경로 탐색 알고리즘을 통해 데이터 간의 연결을 탐색하는 것에 활용할 수 있다.

그래프 경로 탐색의 보통 최단 경로 또는 모든 연결 경로를 탐색하는데 대다수의 그래프DB에서 플러그인 형태를 통해 지원을 하고 있다. 지원하지 않더라도 SPARQL을 직접 작성하여  구현이 가능하다.

여기에서는 RDF-Star를 지원하는 RDF 계열의 그래프 DB 중 하나인  GraphDB (OntoText)를 사용하여 특정 두 데이터 간의 경로를 찾아보고, Networkx를 통해 그래프로 표현하는 것을 다룬다.

 

위에서 언급한 OntoText는 아래의 홈페이지를 참고하면 좋다.

https://graphdb.ontotext.com/

 

GraphDB Downloads and Resources

 

graphdb.ontotext.com

개인 사용자는 무료로 사용할 수 있는 라이선스를 제공하기 때문에 로컬 환경에 설치하여 활용이 가능하다.

 

이전 글에서는 그래프 탐색 GUI를 통해 정보를 확장하여 탐색을 하였다면,

이번에는 두 기업간의 연결 관계를 찾기 위해서 SPARQL 질의를 통해 경로탐색을 해보려고 한다.

OntoText에서 제공하는 경로탐색 플러그인 중 모든 경로 를 찾는 쿼리를 응용하여 두 데이터를 입력하여 경로를 찾아볼 수 있다.

PREFIX path: <http://www.ontotext.com/path#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX joy: <http://joyhong.tistory.com/resource/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX afn: <http://jena.apache.org/ARQ/function#>

SELECT ?pathIndex ?edgeIndex ?start ?slabel (afn:localname(?property) as ?prop) ?end ?elabel
WHERE {
    VALUES (?src ?dst) {
        (joy:Company_1 joy:Company_2)
    }
    SERVICE path:search {
        <urn:path> path:findPath path:allPaths ;
                   path:sourceNode ?src ;
                   path:destinationNode ?dst ;
                   path:pathIndex ?pathIndex ;
                   path:resultBindingIndex ?edgeIndex ;
                   path:startNode ?start;
                   path:endNode ?end;
                   path:maxPathLength 4 ;
                   path:bidirectional true ;
                   path:exportBinding ?property ;
                   SERVICE <urn:path> {
            ?start ?property ?end.
            ?start !a ?end .
            FILTER(isIRI(?end))
        }
    }
    OPTIONAL{?start rdfs:label ?slabel.}
    OPTIONAL{?end rdfs:label ?elabel.}
#    FILTER(?pathIndex <3)
}

찾고자하는 경로의 길이는 최대 4로 설정하였고, 방향은 상관없이 양방향으로 찾도록 하였다.

그리고 rdf:type을 통해 연결되는 관계는 찾지 않도록 하였고, 데이터타입 관계인 값을 통해 연결되는 관계 또한 찾지 않도록 설정하였다.

실행 결과를 보면 pathIndex가 하나의 경로에 해당되는 식별자이고 edgeIndex가 그 경로상에서의 순서가 된다.

위 그림에서는 0번 경로에 총 4개의 노드가 있다는 것을 알 수 있다.

 

SPARQL을 통해 쿼리 레벨에서만 확인을 하면 뭔가 심심하니

쥬피터 노트북을 통해 경로를 찾고 Networkx로 그림을 그려보면 좋을 것 같다.

OntoText에서 제공하는 SPARQL Endpoint를 통해 SPARQL을 질의하여 결과를 받아오면 되는. 

이 때 SPARQLWrapper를 사용하면 된다.

from SPARQLWrapper import SPARQLWrapper, CSV
import pandas as pd
import io
import networkx as nx
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

plt.rcParams['font.sans-serif']=['D2Coding'] 
plt.rcParams['axes.unicode_minus']=False 

# 경로를 찾기
conn_details = 'http://localhost:7200/repositories/Blog'
sparql = SPARQLWrapper(conn_details)
sparql.setReturnFormat(CSV)
sparql.addParameter('infer', 'false')  # 추론 결과 제외
query = """
PREFIX path: <http://www.ontotext.com/path#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX joy: <http://joyhong.tistory.com/resource/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX afn: <http://jena.apache.org/ARQ/function#>

SELECT ?pathIndex ?edgeIndex ?start ?slabel (afn:localname(?property) as ?prop) ?end ?elabel
WHERE {
    VALUES (?src ?dst) {
        (joy:Company_1 joy:Company_2 )
    }
    SERVICE path:search {
        <urn:path> path:findPath path:allPaths ;
                   path:sourceNode ?src ;
                   path:destinationNode ?dst ;
                   path:pathIndex ?pathIndex ;
                   path:resultBindingIndex ?edgeIndex ;
                   path:startNode ?start;
                   path:endNode ?end;
                   path:maxPathLength 4 ;
                   path:bidirectional true ;
                   path:exportBinding ?property ;
                   SERVICE <urn:path> {
            ?start ?property ?end.
            ?start !a ?end .
            FILTER(isIRI(?end))
        }
    }
    OPTIONAL{?start rdfs:label ?slabel.}
    OPTIONAL{?end rdfs:label ?elabel.}
#    FILTER(?pathIndex <3)
} 

"""

sparql.setQuery(query)
results = sparql.query().convert()

df = pd.read_csv(io.BytesIO(results))

# Networkx로 그래프 생성
nxg = nx.from_pandas_edgelist(df, 'start', 'end', 'prop')
nodes_label = dict()
links_label = dict()
for x in df.itertuples():
    nodes_label[x[3]] = x[4]
    nodes_label[x[6]] = x[7]
    links_label[(x[3],x[6])] = x[5]

for node in nxg:
    nxg.add_node(node, label=nodes_label[node])

nx.set_edge_attributes(nxg, links_label, 'label')
nx.draw(nxg, pos=nx.kamada_kawai_layout(nxg), labels=nodes_label, with_labels = True )

실행을 하면 아래와 같이 결과가 나온다. sparql 쿼리에서 양방향으로 찾도록 하였기 때문에 그래프는 무방향그래프로 사용하였다.

 

여기에서 링크에 레이블을 부착하여 보면 아래와 같다.

nx.draw(nxg, pos=nx.kamada_kawai_layout(nxg),
        labels=nodes_label,
       font_color='black')
nx.draw_networkx_edge_labels(
    nxg, pos=nx.kamada_kawai_layout(nxg),
    edge_labels=dict(((e[0], e[1]), f"{e[2]['prop']}") for e in nxg.edges(data=True)),
    font_color='blue'
)
plt.show()

 

하는 김에 한번 더 코드를 추가하여 pyvis로 그려고면 아래와 같이 할 수 있겠다.

from pyvis.network import Network

nw = Network(width='100%', notebook=True)
nw.from_nx(nxg)
nw.show("nx.html")

지금까지 경로 탐색을 통해 특정 데이터 간의 관계를 한번의 질의로 찾아 보았다. 이외에도 순환경로나 최단길이를 찾아볼 수도 있을 것이다. 

데이터를 지식그래프 형태로 구성하고 활용하는 것으로 인해 그래프 구조를 통한 분석이 가능하다. 어떠한 목적을 가지고 데이터를 분석함에 있어 효율적이고 빠르게 할 수 있는 방안이 있다면 한번쯤은 적용해볼만한 사항이지 않을까 싶다. 

지금도 여전히 테이블 형태의 데이터를 통한 분석이 큰 장점을 가져다 주고 있지만 위와 같은 경로의 문제에 대해서는 그래프가 분명 장점이 있을 것이다.

다음에는 그래프 패턴을 통해 무엇인가를 찾아보도록 하겠다.

끝.

 

최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함