neo4j - data science - part 2

3 분 소요

Exploratory Data Analysis(EDA)

  • 이전에는 neo4j sandbox를 설정하였고, 여기서는 데이터를 탐색합니다. 흔히 말하는 Exploratory Data Analysis를 사용하며, 특히 python의 이미 잘 구축된 라이브러리들을 사용하여 진행합니다.
  • EDA는 그냥 데이터들의 분포가 어떻고, 어떻게 밀집되어 있고, 구간이 어떻게 나와 있는지 등 다양한 관점에 대해서 차트를 만들어보는 수준이다, 즉, 본격 분석에 들어가기 앞서서, ‘몇 가지 가정을 세우는 단계’라고 생각하면 됩니다.

Tools

  • 다음의 3가지 툴을 사용합니다. 각각에 대해서 간략하게 설명을 정리하였습니다.
  • py2neo: py2neo는 neo4j와 파이썬 데이터 분석 라이브러리들을 연결합니다.
  • pandas: 유명한 라이브러리이므로 설명은 생략하고, neo4j로부터 가져온 데이터를 후처리하기 위해서 사용한다 흠…
  • Matplotlib: 파이썬 2D 시각화 라이브러리입니다. 특히, 이 아이는 jupyter notebook 위에서 돌아갈 때, 아주 강력하죠.

Citation Dataset

  • 본 프로젝트에서 제시한 Citation Dataset의 데이터 구조는 다음으로 구성되어 있습니다.
    • (author)<-[author]-(article)
    • (article)<-[cited]-(article)
    • (article)<-[venue]-(venue)

exploratory data analysis on jupyter

  • 이제 jupyter notebook에서 탐색적 데이터 분석을 해봅시다.
  • 우선, 환경 설정을 해줍니다.
!pip install py2neo==4.1.3 pandas matplotlib sklearn

from py2neo import Graph
import pandas as pd

import matplotlib 
import matplotlib.pyplot as plt

plt.style.use('fivethirtyeight')
pd.set_option('display.float_format', lambda x: '%.3f' % x)

# Change the line of code below to use the IP Address, Bolt Port,  and Password of your Sandbox.
# graph = Graph("<Bolt URL>", auth=("neo4j", "<Password>")) 
 
graph = Graph("bolt://18.207.236.58:35753", auth=("neo4j", "<password>"))
  • 환경 설정이 완료되었습니다. 이제 graph에는 우리가 접속한 DB의 Graph가 연결되어 있는 것이죠.
  • 우선, 이 Graph의 schema부터 파악해봅시다. query를 graph에 넘긴 다음, .data()로 값을 가져옵니다.
    • 반환되는 값은 list이며, 크기는 1인데, 이는 아마도, 반환되는 그래프가 1개이기 때문이 아닐까 싶습니다.
db_schema = graph.run("CALL db.schema()").data()
print(type(db_schema))
print(len(db_schema))
print('=='*20)
for key, value in db_schema[0].items():
    print(key)
    print('--'*20)
    for node_or_rel in value:
        print(node_or_rel)
    print('--'*20)
print('=='*20)
  • 위 코드를 수행한 결과를 보면, 아래와 같이 구성되어 있음을 알 수 있습니다.
<class 'list'>
1
========================================
nodes
----------------------------------------
(_-39:Venue {constraints: ['CONSTRAINT ON ( venue:Venue ) ASSERT venue.name IS UNIQUE'], indexes: [], name: 'Venue'})
(_-37:Article {constraints: ['CONSTRAINT ON ( article:Article ) ASSERT article.index IS UNIQUE'], indexes: [], name: 'Article'})
(_-38:Author {constraints: ['CONSTRAINT ON ( author:Author ) ASSERT author.name IS UNIQUE'], indexes: [], name: 'Author'})
----------------------------------------
relationships
----------------------------------------
(Article)-[:VENUE {}]->(Venue)
(Article)-[:AUTHOR {}]->(Author)
(Article)-[:CITED {}]->(Article)
----------------------------------------
========================================
  • 이제, schema가 아니라, 데이터를 가져와 봅시다. 각 label별로 노드가 몇 개나 있는지를 한번 확인해볼게요.
import functools
"""
- query의 결과를 가져올 때, 반드시 .data()로 가져와야 하는 것은 아니다. 
- .to_series, to_dataframes등의 방법도 있으나, 
- 나는 python native 구조인 dictionary, list로 가져오는 것을 선호하여 다음과 같이 가져온 후에 후처리하는 식으로 처리하였다.
"""
labels = graph.run("CALL db.labels()").data() 
print(labels)
print("--"*20)
labels = functools.reduce(lambda x, y: x+y, [list(k_v.values()) for k_v in labels])
print(labels)

result_dict = {}
for label in labels: 
    query = f"""
    MATCH (:{label})
    RETURN count(*) as count
    """
    query_data = graph.run(query).data()
    result_dict[label] = query_data[0]['count']
print("--"*20)
for label, count in result_dict.items():
    print(f"{label:10s} : {count:7d}")
print("--"*20)
  • 실행 결과는 다음과 같다.
[{'label': 'Article'}, {'label': 'Author'}, {'label': 'Venue'}]
----------------------------------------
['Article', 'Author', 'Venue']
----------------------------------------
Article    :   51956
Author     :   80299
Venue      :       4
  • 이제는 어떤 relationship들이 있는지를 한번 알아볼게요.
rel_labels = graph.run("CALL db.relationshipTypes()").data()
rel_labels = [k_v['relationshipType'] for k_v in rel_labels]
rel_labels

rel_result_dict = {}
for rel_label in rel_labels:
    query = f"""
    MATCH ()-[rel:{rel_label}]->() 
    RETURN count(*) as count
    """
    count = graph.run(query).data()
    rel_result_dict[rel_label] = count[0]['count']
print(rel_result_dict)
  • 결과는 다음과 같습니다.
{'VENUE': 51956, 'AUTHOR': 140575, 'CITED': 28706}

wrap-up

raw-code


!pip install py2neo==4.1.3 pandas matplotlib sklearn

from py2neo import Graph
import pandas as pd

import matplotlib 
import matplotlib.pyplot as plt

plt.style.use('fivethirtyeight')
pd.set_option('display.float_format', lambda x: '%.3f' % x)

# Change the line of code below to use the IP Address, Bolt Port,  and Password of your Sandbox.
# graph = Graph("<Bolt URL>", auth=("neo4j", "<Password>")) 
 
graph = Graph("bolt://18.207.236.58:35753", auth=("neo4j", "<password>"))

## set environment done. 

## db schema check 

db_schema = graph.run("CALL db.schema()").data()
print(type(db_schema))
print(len(db_schema))
print('=='*20)
for key, value in db_schema[0].items():
    print(key)
    print('--'*20)
    for node_or_rel in value:
        print(node_or_rel)
    print('--'*20)
print('=='*20)

## nodes
import functools
"""
- query의 결과를 가져올 때, 반드시 .data()로 가져와야 하는 것은 아니다. 
- .to_series, to_dataframes등의 방법도 있으나, 
- 나는 python native 구조인 dictionary, list로 가져오는 것을 선호하여 다음과 같이 가져온 후에 후처리하는 식으로 처리하였다.
"""
labels = graph.run("CALL db.labels()").data() 
print(labels)
print("--"*20)
labels = functools.reduce(lambda x, y: x+y, [list(k_v.values()) for k_v in labels])
print(labels)

result_dict = {}
for label in labels: 
    query = f"""
    MATCH (:{label})
    RETURN count(*) as count
    """
    query_data = graph.run(query).data()
    result_dict[label] = query_data[0]['count']
print("--"*20)
for label, count in result_dict.items():
    print(f"{label:10s} : {count:7d}")
print("--"*20)


## relationship 
rel_labels = graph.run("CALL db.relationshipTypes()").data()
rel_labels = [k_v['relationshipType'] for k_v in rel_labels]
rel_labels

rel_result_dict = {}
for rel_label in rel_labels:
    query = f"""
    MATCH ()-[rel:{rel_label}]->() 
    RETURN count(*) as count
    """
    count = graph.run(query).data()
    rel_result_dict[rel_label] = count[0]['count']
print(rel_result_dict)

댓글남기기