neo4j 実例で学ぶCypher -2-


はじめに前提的なこと

  • 楽天のような店子をたくさん持つプラットフォーマーの立場で考えます
  • 前回前々回から引き続きです

サンプルデータの用意

データ構造

CSVにすると

category.csv
category_id:ID,name,:LABEL
cate1,カテゴリ1,category
shops.csv
shop_id:ID,name,category_id,:LABEL
shop1,ショップA,cate1,shop
shop2,ショップB,cate1,shop
items.csv
tem_id:ID,name,shop_id,:LABEL
item1,商品A,shop1,item
item2,商品B,shop1,item
item3,商品C,shop2,item
item4,商品D,shop2,item
item5,商品E,shop2,item
orders.csv
order_id:ID,date,user_id,shop_id,category_id,price,item_id,:LABEL
order1,2016-04-02,user1,shop1,cate1,9500,item1,order
order2,2016-04-03,user2,shop1,cate1,1000,item1,order
order3,2016-04-04,user3,shop1,cate1,1200,item2,order
order4,2016-04-05,user4,shop2,cate1,15000,item3,order
users.csv
user_id:ID,name,email,age:int,industry,gender,:LABEL
user1,user_name1,[email protected],37,サービス業,male,user
user2,user_name2,[email protected],17,ネットサービス,male,user
user3,user_name3,[email protected],27,公務員,female,user
user4,user_name4,[email protected],44,公務員,male,user
user5,user_name5,[email protected],55,公務員,male,user
owner.csv
owner_id:ID,name,:LABEL
owner1,owner1,owner
owner2,owner2,owner

サンプルデータの投入

➜  neo4j neo4j-import --into /usr/local/Cellar/neo4j/2.3.2/libexec/data/graph9.db --nodes category.csv --nodes items.csv --nodes orders.csv --nodes shops.csv --nodes users.csv --nodes owner.csv
Unable to find any JVMs matching version "1.7".
Importing the contents of these files into /usr/local/Cellar/neo4j/2.3.2/libexec/data/graph9.db:
Nodes:
  /Users/a12091/neo4j/category.csv

  /Users/a12091/neo4j/items.csv

  /Users/a12091/neo4j/orders.csv

  /Users/a12091/neo4j/shops.csv

  /Users/a12091/neo4j/users.csv

  /Users/a12091/neo4j/owner.csv

Available memory:
  Free machine memory: 18.05 MB
  Max heap memory : 1.78 GB

Nodes
[*>:??-------------------------------------------------------------------------------------|P||] 10k
Done in 594ms
Prepare node index
[*DETECT:7.63 MB-------------------------------------------------------------------------------]   0
Done in 24ms
Calculate dense nodes
[*>:??-----------------------------------------------------------------------------------------]   0
Done in 12ms
Relationships
[*>:??-----------------------------------------------------------------------------------------]   0
Done in 10ms
Node --> Relationship
[*>:??------------------------------------------|LINK-------------------|v:??------------------] 10k
Done in 11ms
Relationship --> Relationship
[*>:??-----------------------------------------------------------------------------------------]   0
Done in 12ms
Node counts
[*COUNT:76.29 MB-------------------------------------------------------------------------------] 10k
Done in 133ms
Relationship counts
[*>:??-----------------------------------------------------------------------------------------]   0
Done in 1ms

IMPORT DONE in 2s 345ms. Imported:
  19 nodes
  0 relationships
  85 properties

リレーションをはる

buy

  • 注文とuserを紐付ける
MATCH (o:order),(u:user) 
WHERE o.user_id = u.user_id 
CREATE (u)-[:BUY]->(o);

order

  • 注文データと商品を紐付ける
MATCH (o:order),(i:item) 
WHERE o.item_id = i.item_id 
CREATE (o)-[:ORDER]->(i);

part of

  • 商品とショップを紐付ける
MATCH (s:shop),(i:item)
WHERE s.shop_id = i.shop_id
CREATE (i)-[:PART]->(s);

categorize

  • ショップとカテゴリーを紐付ける
MATCH (c:category),(s:shop)
WHERE c.category_id = s.category_id
CREATE (s)-[:CATE]->(c);

frend

  • ショップオーナーと友人ユーザを紐付ける
MATCH (ow:owner),(u:user)
WHERE ow.owner_id = "owner1" AND u.user_id = "user1"
CREATE (ow)-[:FREND]->(u);

MATCH (ow:owner),(u:user)
WHERE ow.owner_id = "owner1" AND u.user_id = "user2"
CREATE (ow)-[:FREND]->(u);

recommend

  • ショップオーナーのおすすめ商品を紐付け
MATCH (ow:owner),(i:item)
WHERE ow.owner_id = "owner1" AND i.item_id = "item1"
CREATE (ow)-[:RECCOMEND]->(i);

favorite

  • ユーザとお気に入り商品を紐付ける
MATCH (u:user),(i:item)
WHERE u.user_id = "user3" AND i.item_id = "item2"
CREATE (u)-[:FAVORITE]->(i);

MATCH (u:user),(i:item)
WHERE u.user_id = "user3" AND i.item_id = "item3"
CREATE (u)-[:FAVORITE]->(i);

MATCH (u:user),(i:item)
WHERE u.user_id = "user4" AND i.item_id = "item2"
CREATE (u)-[:FAVORITE]->(i);

MATCH (u:user),(i:item)
WHERE u.user_id = "user4" AND i.item_id = "item4"
CREATE (u)-[:FAVORITE]->(i);

MATCH (u:user),(i:item)
WHERE u.user_id = "user5" AND i.item_id = "item5"
CREATE (u)-[:FAVORITE]->(i);

owner

  • ショップオーナーとショップを紐付ける
MATCH (ow:owner),(s:shop)
WHERE ow.owner_id = "owner1" AND s.shop_id = "shop1"
CREATE (ow)-[:OWMER]->(s);


MATCH (ow:owner),(s:shop)
WHERE ow.owner_id = "owner2" AND s.shop_id = "shop2"
CREATE (ow)-[:OWMER]->(s);

抽出したいデータ(ここからがメイン)

1:内訳としてショップAの購入者でオーナーの知人とそうでない人はどのくらいのか

サンプルデータから自分でデータを追うと

  • user1とuser2の2名が知人でuser3は知人ではない人からの購入
  • :FRENDというリレーションが貼られてるのはuser1とuser3のみ
  • なので知人が2でその他は1となる

Cypherで書いてみる

neo4j-sh (?)$ MATCH (u:user)-->(o:order)-->(i:item)-->(shop {name:"ショップA"})<--(ow:owner)-[:FREND]->(user)
> RETURN count(DISTINCT user) as frend_of_owner , count(DISTINCT u) - count(DISTINCT user) as other
> ;
+------------------------+
| frend_of_owner | other |
+------------------------+
| 2              | 1     |
+------------------------+
  • 知人が2でその他は1となり一致

2:favorite(お気に入り)が多い商品は

サンプルデータから表にまとめてみると

user favorite item
user3 商品B
user3 商品C
user4 商品B
user4 商品D
user5 商品E
  • 商品Bがuser3とuser4からお気に入り登録されているので2ユーザがfavorite
  • 商品Cがuser3からお気に入り登録されているので1ユーザがfavorite
  • 商品Dがuser4からお気に入り登録されているので1ユーザがfavorite
  • 商品Eがuser5からお気に入り登録されているので1ユーザがfavorite

が答え

Cypherで書いてみる

neo4j-sh (?)$ MATCH (u:user)-[:FAVORITE]->(i:item)
> RETURN i.name,count(*)
> ;
+-------------------+
| i.name | count(*) |
+-------------------+
| "商品E"  | 1        |
| "商品B"  | 2        |
| "商品D"  | 1        |
| "商品C"  | 1        |
+-------------------+
4 rows
37 ms
  • count(*)はユーザ数を表す
  • それぞれ一致

3:商品をfavorite(お気に入り)をして該当の商品を購入したユーザ数は?

サンプルデータから表にまとめてみると

商品 favorite 購入したか 購入user
商品A x user1
商品A x user2
商品B user3
商品B x user4
商品C x user3
商品C x user4
商品D x user4
商品E x user5
  • favorite=○で且つ購入したか=○を探す
  • user3だけが商品Bをfavoriteして購入しているので1ユーザが答え

ビジュアライズしてみる

Cypherで書いてみる パターン1

neo4j-sh (?)$ MATCH (u:user)-[:FAVORITE]->(i:item)<--(o:order)<--(u:user)
> RETURN count(*);
+----------+
| count(*) |
+----------+
| 1        |
+----------+

Cypherで書いてみる パターン2

neo4j-sh (?)$ MATCH (u:user)-[:FAVORITE]->(i:item),(u:user)-->(o:order)-->(i:item)
> RETURN COUNT(*);
+----------+
| COUNT(*) |
+----------+
| 1        |
+----------+
1 row
82 ms

Cypherで書いてみる パターン3

neo4j-sh (?)$ MATCH (u:user)-->(o:order)-->(i:item)
> WHERE u-[:FAVORITE]->i
> RETURN count(*);
+----------+
| count(*) |
+----------+
| 1        |
+----------+
1 row
109 ms
  • count(*)はユーザ数を表す
  • 1ユーザとなり一致。色々と書き方がある

4:商品をfavoite(お気に入り)をしてないけど購入したユーザ数は?

サンプルデータから表にまとめてみると

商品 favorite 購入したか 購入user
商品A x user1
商品A x user2
商品B user3
商品B x user4
商品C x user3
商品C x user4
商品D x user4
商品E x user5
  • 購入したか=○で且つfavorite=xを探す
  • ユーザはuser1,user2,user4はfavoriteしていなが購入しているので3ユーザが答え

ビジュアライズしてみる

Cypherで書いてみる

neo4j-sh (?)$ MATCH (u:user)-->(o:order)-->(i:item)
> WHERE NOT u-[:FAVORITE]->i
> RETURN count(*)
> ;
+----------+
| count(*) |
+----------+
| 3        |
+----------+
1 row
81 ms
  • count(*)はユーザ数を表す
  • 3ユーザとなり一致

5:商品をfavorite(お気に入り)をしたが該当の商品を買わなかったユーザ数は?

サンプルデータから表にまとめてみると

商品 favorite 購入したか 購入user
商品A x user1
商品A x user2
商品B user3
商品B x user4
商品C x user3
商品C x user4
商品D x user4
商品E x user5
  • favorite=○で且つ購入したか=xを探す
  • favorite(お気に入り)をしたが、購入していないユニークユーザはuser3,user4,user5で3ユーザが答え

ビジュアライズしてみる

  • 注意)商品Bはuser3からfavoriteされ、購入しているが、user4からfavoriteされて購入されていない

Cypherで書いてみる

neo4j-sh (?)$ MATCH (u:user)-[:FAVORITE]->(i:item),(o:order)
> WHERE NOT o<--u
> RETURN count(DISTINCT u);
+-------------------+
| count(DISTINCT u) |
+-------------------+
| 3                 |
+-------------------+
1 row
89 ms
  • count(*)はユーザ数を表す
  • 3ユーザとなり一致

まとめ、雑感的なもの

  • ちょっと複雑めにリレーションを張って色々な条件で抽出してみたが、まぁなんとなくできた
  • Webインターフェースがあったおかげで理解がスムーズだった
    • チュートリアルもわかりやすかった
  • グラフDBを使うことで割と欲しい集計データが簡単に抽出できるとわかった
  • プロダクジョンではmysqlを使ってて、どういう方法、タイミングでneo4jへ連携する部分でまだ考えなきゃいけないなぁと感じた
    • 夜間バッチかなぁ。。。毎晩夜間バッチでslaveのmysqlのデータをneo4jへ突っ込む的な
  • Cypherでサブクエリ的なものが使えないっぽいのでリレーションが重要だと感じた
  • CypherにはEXPLAIN,PROFILEがあるので自分のQLの計測が出来る。プロダクジョンとかの大量のデータを扱う際に必要になってくるだろうな
  • スケールに関するものも一通りあるっぽいなぁ http://neo4j.com/docs/stable/ha.html
  • 可能性はいろいろあるね
    • ただ負荷に対するノウハウ等が少ないのでいきなりユーザが使うサービスには使うのは怖いかな
    • 社内的な分析にまずは使ってみるが良いんじゃないかなと