Working With Cypher Data

pDestiny·2022년 9월 10일
0

Network Science

목록 보기
7/10
post-thumbnail

Aggregating Data

Using count() to aggregate data

neo4j는 RDMS와 다르다는 것을 count 함수로 알수가 있다. 예를 들면 아래와 같은 쿼리가 있을 때,

MATCH (a:Person)-[:ACTED_IN]->(m:Movie)
WHERE a.name = 'Tom Hanks'
RETURN a.name AS actorName,
count(*) AS numMovies

RDMS와 같은 경우 a와 movie를 조인하고, groupby a.name을 하여야 하지만, 웬걸, graph는 관계형이기 때문에 이미 groupby한 것과 마찬가지로 연결이 되어 있어 groupby 없이 연산해도 자동으로 groupby 한 것 처럼 연산되어, a.name별로 출연한 영화의 숫자가 연산된다. 위의 쿼리는 a.name 이 Tom Hanks 만 골랐지만 아래와 같은 query도 가능하다는 것이다.

MATCH (a:Person)-[:ACTED_IN]->(m:Movie)<-[:DIRECTED]-(d:Person)
RETURN a.name AS actorName,
d.name AS directorName,
count(*) AS numMovies
ORDER BY numMovies DESC

Lists in the Graph

데이터 타입으로 list가 존재한다. 이때, 여러개의 properties를 묶어 list로 반환하는 것이 가능하다.

MATCH (p:Person)
RETURN p.name, [p.born, p.died] AS lifeTime
LIMIT 10

Using collect() to create a list
collect를 쓰면 여러개의 instance를 리스트로 묶어 하나의 feature로 쓸 수 있다. 아래와 같이 movie node가 여러개 있을때, 이는 list가 아니다. 하지만 movie node의 collect(m.title)과 같이 여러 노드의 property들을 리스트로 collect 를 이용해 리스트로 묶어 버릴 수 있다.

MATCH (a:Person)-[:ACTED_IN]->(m:Movie)
RETURN a.name AS actor,
count(*) AS total,
collect(m.title) AS movies # collect(m.title)은 actor가 출연한 무비의 title 리스트를 출력한다.
ORDER BY total DESC LIMIT 10

property 뿐만 아니라 RETURN clause에서 노드를 하나의 리스트로 출력하는 것도 가능하다.

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name ='Tom Cruise'
RETURN collect(m) AS tomCruiseMovies

기존에는 테이블 형태로 표시되던 노드 set이 리스트의 형태로 하나의 데이터 형태로 반환되는 것을 볼 수 있다.

Eliminating duplication in lists
collect에서 중복을 제거하는 방법도 있다. collect 안에 DISTINCT clause를 집어 넣는 것이다.

MATCH (a:Person)-[:ACTED_IN]->(m:Movie)
WHERE m.year = 1920
RETURN  collect( DISTINCT m.title) AS movies,
collect( a.name) AS actors

Accessing elements of a list
리스트는 index로 접근가능한데, 기본적으로 neo4j는 0 base index를 채택하고 있음으로 첫번째 리스트의 요소는 list[0] 로 접근할 수 있다. 그리고 .. 을 이용해 range를 indexing 하는 것이 가능하다.

0번째 인덱스 접근

MATCH (a:Person)-[:ACTED_IN]->(m:Movie)
RETURN m.title AS movie,
collect(a.name)[0] AS castMember,
size(collect(a.name)) as castSize

2번째부터 마지막 인덱스까지의 리스트를 반환한다.

MATCH (a:Person)-[:ACTED_IN]->(m:Movie)
RETURN m.title AS movie,
collect(a.name)[2..] AS castMember,
size(collect(a.name)) as castSize

리스트는 생각보다 많이 쓰임으로 neo4j Cypher Manual의 List 파트의 함수들을 언급하고자 한다.

  1. keys(expression)::List
    추가적으로 배울 key-value pair의 map에서 더 유용한 함수이긴 하지만 List를 반환함으로 여기 적는다. keys는 파이썬의 dict.keys()와 동일하게 map의 key값만 가져와서 리스트로 반환한다. 예를 들어 Alice 노드의 properties가 name, age, eyes 였다면 아래와 같이 키값들의 리스트를 반환한다.

    MATCH (a) WHERE a.name = 'Alice'
    RETURN keys(a) # ["name","age","eyes"]
  2. labels(node)::List
    labels는 node 셋이 가지고 있는 label set을 반환한다.

    MATCH (a) WHERE a.name = 'Alice'
    RETURN labels(a) # ["Person","Developer"]
  3. nodes(path)::List
    nodes는 어떤 path가 주어졌을 때, 그 path에 존재하는 node들만 반환한다. 예를 들어 아래와 같은 Query가 가능하다. 노드의 리스트를 반환한다.

    MATCH p = (a)-->(b)-->(c)
    WHERE a.name = 'Alice' AND c.name = 'Eskil'
    RETURN nodes(p) # [Node[0]{name:"Alice",age:38,eyes:"brown"},Node[1]{name:"Bob",age:25,eyes:"blue"},Node[4]{eyes:"blue",array:["one","two","three"],name:"Eskil",age:41}]
  4. range(start, end [, step])::List
    python의 range와 동일하지만, generator가 아니라 list를 출력한다. 반대로 출력하려면, step을 음수로 지정하면 된다.(start와 end를 바꿔줘야 하는것도 잊지 말자)

    RETURN range(0, 10), # [0,1,2,3,4,5,6,7,8,9,10]
    range(2, 18, 3),  # [2,5,8,11,14,17]
    range(0, 5, -1) # [] 
  5. reduce(accumulator=initial, variable IN list | expression)
    reduce()는 expression에 따라 list를 initial 값에 aggregating 하는 기능을 가진 함수이다.

    MATCH p = (a)-->(b)-->(c)
    WHERE a.name = 'Alice' AND b.name = 'Bob' AND c.name = 'Daniel'
    RETURN reduce(totalAge = 0, n IN nodes(p) | totalAge + n.age) AS reduction

    위의 reduce로 설명하자명, path에서 node를 리스트로 뽑아내고, 그 리스트의 나이의 합을 계산하는 연산이다. 맨 처음 accumulator에서 totalAge = 0으로 세팅 되어 있고, expression에서 totalAge에서 n.age값이 계속 더해져 reduction으로 반환된다. reduce 함수의 return type은 initial value를 어떻게 설정하느냐에 따라 다르다.

  6. relationships(path)::List
    nodes 함수와 다르게 path에서 relationship만 뽑아서 반환하는 함수이다.

    MATCH p = (a)-->(b)-->(c)
    WHERE a.name = 'Alice' AND c.name = 'Eskil'
    RETURN relationships(p) # [:KNOWS[0]{},:MARRIED[4]{}]

    Alice ->()-> Eskil로 이어지는 path가 있을 때, 이 path에서 어떤 관계인지를 알아내기 위해 path의 relationship을 얻어 낼 수 있다. 그러면 relationships는 relationship들을 리스트로 반환한다.

  7. reverse(List)::List

    오리지널 리스트를 거꾸로 반환하는 함수 자세한 설명은 생략한다.

  8. tail(List)::List, head(List)::List
    tail은 첫번째 데이터를 제외하고 반환하며, head는 맨 마지막 데이터를 제외하고 반환한다. 자세한 설명은 생략한다.

  9. toBooleanList(), toFloatList(), toIntegerList(), toStringList()
    List의 요소들을 Bool, Float, Integer, String로 바꿔준다. null값은 null을 돌려주고, nested list 같은 변경이 곤란한 요소역시 null로 치환된다.

List Comprehension
python과 비슷하게 리스트를 한줄로 풀어 쓸 수 있다. 예를 들면 아래의 Query와 같이 풀어 낼 수 있다.

MATCH (m:Movie)
RETURN m.title as movie,
[x IN m.countries WHERE x = 'USA' OR x = 'Germany']
AS country LIMIT 500

m.countries 중에 'USA'나 'Genmany' 인 값만 추려서 리스트로 만드는 list comprehension 이다.

Pattern comprehension

Pattern comprehension은 query의 caldinality(instance 수)를 변경하지 않고 리스를 만들어 낼 수 있는 굉장히 강력한 방법이다. 마치 OPTIONAL MATCHcollect 함수를 엮어 사용한 듯한 효과를 낸다.

MATCH (m:Movie)
WHERE m.year = 2015
RETURN m.title,
[(dir:Person)-[:DIRECTED]->(m) | dir.name] AS directors,
[(actor:Person)-[:ACTED_IN]->(m) | actor.name] AS actors

여기서 주목해야할 점은 pattern을 설정하고, 그 패턴으로 부터 얻은 변수들을 pipe를 통해 구체적으로 어떤 값이 list에 포함될 것인가를 리스트로 묶었다는 점이다.

[<pattern> | <expression>]

이 쿼리는 2015년에 개봉된 영화의 director 이름 리스트와, 배우의 이름 리스트를 반환한다.

여기서 pattern comprehension을 포함한 다른 예제를 보인다.

MATCH (a:Person {name: 'Tom Hanks'})
RETURN [(a)-->(b:Movie)
WHERE b.title CONTAINS "Toy" | b.title + ": " + b.year]
AS movies

Tom Hanks오 관련있는 movie를 가져오되, title에 Toy가 포함된 pattern을 가져오되, 반환값을 title과 year를 ':'로 붙인 값을 리스트로 반환한다. 즉, 반드시 어떤 한 값일 필요는 없고 값을 반환하는 expression이기만 하면 된다는 의미이며, pattern에 where 구문으로 일반 match다음에 들어가는 방식으로 pattern을 제한 할 수 있다는 점을 시사하는 예제라고 볼 수 있다.

Working with maps

파이썬과 같이 dictionary를 사용할 수 있으며, key 값으로 indexing도 할 수있다.

RETURN {Jan: 31, Feb: 28, Mar: 31, Apr: 30 ,
May: 31, Jun: 30 , Jul: 31, Aug: 31, Sep: 30,
Oct: 31, Nov: 30, Dec: 31}['Feb'] AS daysInFeb
RETURN {Jan: 31, Feb: 28, Mar: 31, Apr: 30 ,
May: 31, Jun: 30 , Jul: 31, Aug: 31, Sep: 30,
Oct: 31, Nov: 30, Dec: 31}.Feb AS daysInFeb
RETURN keys({Jan: 31, Feb: 28, Mar: 31, Apr: 30 ,
May: 31, Jun: 30 ,Jul: 31, Aug: 31, Sep: 30,
Oct: 31, Nov: 30, Dec: 31}) AS months

Counting Results

Using count() write a query to return the number of movies a person directed. What is the highest number of movies a director directed in our graph?

어떤 director가 가장 많은 영화를 감독했는지를 묻는 질문이다. 자동으로 grouping 되어 있음으로 groupby가 neo4j에 필요 없다는 것을 알려주는 문제이기도 하다.

match (p:Person)-[:DIRECTED]->(m:Movie)
return p.name, count(m)
order by count(m) desc

Creating Lists

Write and execute a query to return the list of actors for each movie. Order and limit the results so that the movie with the largest cast is returned. What movie had the largest list of actors? (Enter a case-sensitive string for the movie title.)

각 movie의 출연하는 actor들의 수를 반환하라는 문제이다.

MATCH (a:Actor)-[:ACTED_IN]->(m:Movie)
WITH m.title AS title, size(collect(a)) AS numActors
RETURN title, numActors
ORDER BY numActors DESC
LIMIT 1 # Hamlet, 24

size(collect(a))로 instance를 리스트로 반환하고, size로 list의 길이를 재는 것을 할 수 있느냐는 문제이다. WITH으로 살짝 변환을 줘봤다.

Working with Dates and Times

Working with durations

duration은 두 시간 간격에 얼마나 떨어져 있는지를 알려준다. 아래의 코드는 2012-03-27 과 2012-04-22일 사이의 간격에서 얼마나 떨어져 있는지를 나타내는 코드이다.

MERGE (x:Test {id: 1})
SET x.date1 = date('2012-03-27'), x.date2 = date('2012-04-22')
RETURN duration.between(x.date1, x.date2) 

P26D로 나오는데, 이를 days로 표현할 수 있다.

MATCH (x:Test {id: 1})
RETURN duration.inDays(x.date1, x.date2).days # 26

Using APOC to format dates and times

APOC 라이브러리는 모든 타입의 데이터를 다루기에 좋은 함수들을 가지고 있다. APOC 함수를 이용해 원하는 format의 datetime을 사용할 수 있다.

MATCH (x:Test {id: 1})
RETURN x.date1 as d, apoc.temporal.format(x.date1, 'yyyy_MM_dd') # 2012.03_27

Working with dates and times:dates

먼저 데이터를 세팅한다.

MERGE (x:Test {id: 1})
SET
x.date = date(),
x.datetime = datetime(),
x.timestamp = timestamp(),
x.date1 = date('2022-04-08'),
x.date2 = date('2022-09-20'),
x.datetime1 = datetime('2022-02-02T15:25:33'),
x.datetime2 = datetime('2022-02-02T22:06:12')
RETURN x

Write a query to retrieve this Test node and calculate the number of days between date1 and date2. Enter the number of days.

위의 내용을 고스란히 가져오면 된다.

match (x:Test{id: 1})
return duration.inDays(x.date1, x.date2).days # 165

Working with dates and times:times

Write a query to retrieve this Test node and calculate the number of minutes between datetime1 and datetime2. Enter the number of minutes.

duration에는 inMinutes는 없다. 그러므로 inSeconds를 이용하고, 60으로 나눈뒤에 toInteger로 바꾸면 된다.

match (x:Test{id: 1})
return toInteger(
	duration.inSeconds(x.datetime1, x.datetime2).seconds / 60) 
    as integer # 400
profile
Bioinformatician

0개의 댓글