fbpx

婚活支援プロジェクト(4/4): 改善したデータモデルによるデータ処理 #neo4j

この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。

グラフデータベースNeo4jの実践的な利用方法の1例を紹介するために始めたトライアルプロジェクトがいよいよ最終回を迎えました。今回は、トライアルプロジェクトを進めているなかで発生した問題点などの改善策を盛り込んだデータモデルを利用して理想の相手を探してみたいと思います。

第1回目:構想からデータモデル設計まで
第2回目:データ処理と問題点
第3回目:どのように改善したらいいのか
第4回目:改善したデータモデルによるデータ処理(今回)

追加のサブドメイン作成及びデータロード

データモデル

最終的に完成したデータモデルは以下のようなものです。波線のようにリクエスト用のサブドメイン(RE_CATEGORY)と必須要素のフィルター条件用のサブドメイン(FI_CATEGORY)を追加し、居住地域のフィルター条件は人(:Person)の属性(filter_location)として持たせています。

re-datamodel1
MY:Original RE:Request FI:Filter

これから、改善したデータモデルに基づいてノードを追加していFきます。この作業を行いたくない方は、完成したグラフデータベースをダウンロードして利用してください。

ダウンロード konkatu-4.gdb.zip

完成したグラフデータベースを利用する方は、『M04さんのデータ処理をやってみる』に進んでください。

リクエスト用サブドメインの追加

次のCypherクエリーをすべてWebインターフェースに入力して実行してください。

WITH ["野球","サッカー","バレーボール","バスケットボール","テーブルテニス","ヨガ","散歩","ダンス","その他"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (a1:Sport2 {title: list[i]}))

WITH ["土日祝日","平日","不定期"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (b1:Holiday2 {title: list[i]}))

WITH ["聞くタイプ","話すタイプ","とちらでも良い"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (c1:Talk2 {title: list[i]}))

WITH ["積極的","消極的","とちらでもない"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (d1:Meeting2 {title: list[i]}))

WITH [140,150,160,170,180,190,200] as list
FOREACH(i IN range(0, length(list)-1) |
CREATE (e1:Height2 {title: list[i]}))

WITH ["外向的","内向的"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (f1:Personality2 {title: list[i]}))

WITH ["読書","映画感想","音楽感想","TV感想","ゲーム","寝る","友たちとお喋り","料理を作る","その他"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (g1:Indoor2 {title: list[i]}))

WITH ["吸う","吸わない"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (h1:Smoking2 {title: list[i]}))

WITH ["飲む","飲まない"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (j1:Alcohol2 {title: list[i]}))

WITH ["好き","嫌い"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (k1:Karaoke2 {title: list[i]}))

WITH ["信じる","信じない"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (m1:Fortunetelling2 {title: list[i]}))

WITH ["意識して運動する","特に意識していない"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (n1:Healthcare2 {title: list[i]}))

WITH ["海","山","川","公園","遊園地","どちらでもいい"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (o1:Outdoor2 {title: list[i]}))

WITH [100,200,300,400,500,600,700,800,900] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (p1:Income2 {title: list[i]}))

WITH ["北海道","青森県","岩手県","宮城県","秋田県","山形県","福島県","茨城県","栃木県","群馬県","","埼玉県","千葉県","東京都","神奈川県","新潟県","富山県","石川県","福井県","山梨県","長野県","岐阜県","静岡県","愛知県","三重県","滋賀県","京都府","大阪府","兵庫県","奈良県","和歌山県","鳥取県","島根県","岡山県","広島県","山口県","徳島県","香川県","愛媛県","高知県","福岡県","佐賀県","長崎県","熊本県","大分県","宮崎県","鹿児島県","沖縄県"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (q1:Location2 {title: list[i]}))

WITH ["有り","無し"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (r1:Maritalhistory2 {title: list[i]}))

WITH ["犬派","猫派"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (s1:Pet2 {title: list[i]}))

WITH ["義務教育","高等学校","各種専門学校","短大・高専","大学","大学院"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (t1:Education2 {title: list[i]}))

WITH ["公務員","会社員","自営業","企業経営・役員","教師","医師","医療関係","保育士","農林・漁業","パート・アルバイト","学生","家事手伝","その他"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (u1:Job2 {title: list[i]}))

WITH [1995,1994,1993,1992,1991,1990,1989,1988,1987,1986,1985,1984,1983,1982,1981,1980,1979,1978,1977,1976,1975,1974,1973,1972,1971,1970] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (v1:Born2 {title: list[i]}))

結果を確認してみましょう。次のCypherクエリーをWebインターフェースに入力して実行し、ラベル、要素の集合、要素数を確認してください。

MATCH (a:Sport2) RETURN labels(a) AS ラベル,collect(a.title) AS 要素,length(collect(a.title)) AS 数 UNION ALL
MATCH (b:holiday2) RETURN labels(b) AS ラベル, collect(b.title) AS 要素,length(collect(b.title)) AS 数 UNION ALL
MATCH (c:Talk2) RETURN labels(c) AS ラベル, collect(c.title) AS 要素,length(collect(c.title)) AS 数 UNION ALL
MATCH (d:Height2) RETURN labels(d) AS ラベル, collect(d.title) AS 要素,length(collect(d.title)) AS 数 UNION ALL
MATCH (e:Personality2) RETURN labels(e) AS ラベル, collect(e.title) AS 要素,length(collect(e.title)) AS 数 UNION ALL
MATCH (f:Indoor2) RETURN labels(f) AS ラベル, collect(f.title) AS 要素,length(collect(f.title)) AS 数 UNION ALL
MATCH (g:Smoking2) RETURN labels(g) AS ラベル, collect(g.title) AS 要素,length(collect(g.title)) AS 数 UNION ALL
MATCH (h:Alcohol2) RETURN labels(h) AS ラベル, collect(h.title) AS 要素,length(collect(h.title)) AS 数 UNION ALL
MATCH (i:Karaoke2) RETURN labels(i) AS ラベル, collect(i.title) AS 要素,length(collect(i.title)) AS 数 UNION ALL
MATCH (j:Fortunetelling2) RETURN labels(j) AS ラベル, collect(j.title) AS 要素,length(collect(j.title)) AS 数 UNION ALL
MATCH (k:Outdoor2) RETURN labels(k) AS ラベル, collect(k.title) AS 要素,length(collect(k.title)) AS 数 UNION ALL
MATCH (l:Income2) RETURN labels(l) AS ラベル, collect(l.title) AS 要素,length(collect(l.title)) AS 数 UNION ALL
MATCH (m:Born2) RETURN labels(m) AS ラベル, collect(m.title) AS 要素,length(collect(m.title)) AS 数 UNION ALL
MATCH (n:Location2) RETURN labels(n) AS ラベル, collect(n.title) AS 要素,length(collect(n.title)) AS 数 UNION ALL
MATCH (o:Pet2) RETURN labels(o) AS ラベル, collect(o.title) AS 要素,length(collect(o.title)) AS 数 UNION ALL
MATCH (p:Education2) RETURN labels(p) AS ラベル, collect(p.title) AS 要素,length(collect(p.title)) AS 数 UNION ALL
MATCH (q:Job2) RETURN labels(q) AS ラベル, collect(q.title) AS 要素,length(collect(q.title)) AS 数 UNION ALL
MATCH (r:Meet2) RETURN labels(r) AS ラベル, collect(r.title) AS 要素,length(collect(r.title)) AS 数 UNION ALL
MATCH (s:Marriage2) RETURN labels(s) AS ラベル, collect(s.title) AS 要素,length(collect(s.title)) AS 数 UNION ALL
MATCH (t:Healthcare2) RETURN labels(t) AS ラベル, collect(t.title) AS 要素,length(collect(t.title)) AS 数
ラベル 要素 数
[Sport2] [野球, サッカー, バレーボール, バスケットボール, テーブルテニス, ヨガ, 散歩, ダンス, その他] 9
[Talk2] [聞くタイプ, 話すタイプ, とちらでも良い] 3
[Height2] [140, 150, 160, 170, 180, 190, 200] 7
[Personality2] [外向的, 内向的] 2
[Indoor2] [読書, 映画感想, 音楽感想, TV感想, ゲーム, 寝る, 友たちとお喋り, 料理を作る, その他] 9
[Smoking2] [吸う, 吸わない] 2
[Alcohol2] [飲む, 飲まない] 2
[Karaoke2] [好き, 嫌い] 2
[Fortunetelling2] [信じる, 信じない] 2
[Outdoor2] [海, 山, 川, 公園, 遊園地, どちらでもいい] 6
[Income2] [100, 200, 300, 400, 500, 600, 700, 800, 900] 9
[Born2] [1995, 1994, 1993, 1992, 1991, 1990, 1989, 1988, 1987, 1986, 1985, 1984, 1983, 1982, 1981, 1980, 1979, 1978, 1977, 1976, 1975, 1974, 1973, 1972, 1971, 1970] 26
[Location2] [北海道, 青森県, 岩手県, 宮城県, 秋田県, 山形県, 福島県, 茨城県, 栃木県, 群馬県, (empty), 埼玉県, 千葉県, 東京都, 神奈川県, 新潟県, 富山県, 石川県, 福井県, 山梨県, 長野県, 岐阜県, 静岡県, 愛知県, 三重県, 滋賀県, 京都府, 大阪府, 兵庫県, 奈良県, 和歌山県, 鳥取県, 島根県, 岡山県, 広島県, 山口県, 徳島県, 香川県, 愛媛県, 高知県, 福岡県, 佐賀県, 長崎県, 熊本県, 大分県, 宮崎県, 鹿児島県, 沖縄県] 48
[Pet2] [犬派, 猫派] 2
[Education2] [義務教育, 高等学校, 各種専門学校, 短大・高専, 大学, 大学院] 6
[Job2] [公務員, 会社員, 自営業, 企業経営・役員, 教師, 医師, 医療関係, 保育士, 農林・漁業, パート・アルバイト, 学生, 家事手伝, その他] 13
[Healthcare2] [意識して運動する, 特に意識していない] 2

必須要素フィルター条件用のサブドメインの追加

次のCypherクエリーをすべてWebインターフェースに入力して実行してください。

WITH ["野球","サッカー","バレーボール","バスケットボール","テーブルテニス","ヨガ","散歩","ダンス","その他"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (a1:Sport3 {title: list[i]}))

WITH ["土日祝日","平日","不定期"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (b1:Holiday3 {title: list[i]}))

WITH ["聞くタイプ","話すタイプ","とちらでも良い"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (c1:Talk3 {title: list[i]}))

WITH ["積極的","消極的","とちらでもない"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (d1:Meeting3 {title: list[i]}))

WITH [140,150,160,170,180,190,200] as list
FOREACH(i IN range(0, length(list)-1) |
CREATE (e1:Height3 {title: list[i]}))

WITH ["外向的","内向的"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (f1:Personality3 {title: list[i]}))

WITH ["読書","映画感想","音楽感想","TV感想","ゲーム","寝る","友たちとお喋り","料理を作る","その他"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (g1:Indoor3 {title: list[i]}))

WITH ["吸う","吸わない"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (h1:Smoking3 {title: list[i]}))

WITH ["飲む","飲まない"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (j1:Alcohol3 {title: list[i]}))

WITH ["好き","嫌い"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (k1:Karaoke3 {title: list[i]}))

WITH ["信じる","信じない"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (m1:Fortunetelling3 {title: list[i]}))

WITH ["意識して運動する","特に意識していない"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (n1:Healthcare3 {title: list[i]}))

WITH ["海","山","川","公園","遊園地","どちらでもいい"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (o1:Outdoor3 {title: list[i]}))

WITH [100,200,300,400,500,600,700,800,900] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (p1:Income3 {title: list[i]}))

WITH ["有り","無し"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (r1:Maritalhistory3 {title: list[i]}))

WITH ["犬派","猫派"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (s1:Pet3 {title: list[i]}))

WITH ["義務教育","高等学校","各種専門学校","短大・高専","大学","大学院"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (t1:Education3 {title: list[i]}))

WITH ["公務員","会社員","自営業","企業経営・役員","教師","医師","医療関係","保育士","農林・漁業","パート・アルバイト","学生","家事手伝","その他"] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (u1:Job3 {title: list[i]}))

WITH [1995,1994,1993,1992,1991,1990,1989,1988,1987,1986,1985,1984,1983,1982,1981,1980,1979,1978,1977,1976,1975,1974,1973,1972,1971,1970] AS list
FOREACH(i IN range(0, length(list)-1) |
CREATE (v1:Born3 {title: list[i]}))

結果を確認してみましょう。次のCypherクエリーをWebインターフェースに入力して実行し、ラベル、要素の集合、要素数を確認してください。

MATCH (a:Sport3) RETURN labels(a) AS ラベル,collect(a.title) AS 要素,length(collect(a.title)) AS 数 UNION ALL
MATCH (b:holiday3) RETURN labels(b) AS ラベル, collect(b.title) AS 要素,length(collect(b.title)) AS 数 UNION ALL
MATCH (c:Talk3) RETURN labels(c) AS ラベル, collect(c.title) AS 要素,length(collect(c.title)) AS 数 UNION ALL
MATCH (d:Height3) RETURN labels(d) AS ラベル, collect(d.title) AS 要素,length(collect(d.title)) AS 数 UNION ALL
MATCH (e:Personality3) RETURN labels(e) AS ラベル, collect(e.title) AS 要素,length(collect(e.title)) AS 数 UNION ALL
MATCH (f:Indoor3) RETURN labels(f) AS ラベル, collect(f.title) AS 要素,length(collect(f.title)) AS 数 UNION ALL
MATCH (g:Smoking3) RETURN labels(g) AS ラベル, collect(g.title) AS 要素,length(collect(g.title)) AS 数 UNION ALL
MATCH (h:Alcohol3) RETURN labels(h) AS ラベル, collect(h.title) AS 要素,length(collect(h.title)) AS 数 UNION ALL
MATCH (i:Karaoke3) RETURN labels(i) AS ラベル, collect(i.title) AS 要素,length(collect(i.title)) AS 数 UNION ALL
MATCH (j:Fortunetelling3) RETURN labels(j) AS ラベル, collect(j.title) AS 要素,length(collect(j.title)) AS 数 UNION ALL
MATCH (k:Outdoor3) RETURN labels(k) AS ラベル, collect(k.title) AS 要素,length(collect(k.title)) AS 数 UNION ALL
MATCH (l:Income3) RETURN labels(l) AS ラベル, collect(l.title) AS 要素,length(collect(l.title)) AS 数 UNION ALL
MATCH (m:Born3) RETURN labels(m) AS ラベル, collect(m.title) AS 要素,length(collect(m.title)) AS 数 UNION ALL
MATCH (o:Pet3) RETURN labels(o) AS ラベル, collect(o.title) AS 要素,length(collect(o.title)) AS 数 UNION ALL
MATCH (p:Education3) RETURN labels(p) AS ラベル, collect(p.title) AS 要素,length(collect(p.title)) AS 数 UNION ALL
MATCH (q:Job3) RETURN labels(q) AS ラベル, collect(q.title) AS 要素,length(collect(q.title)) AS 数 UNION ALL
MATCH (r:Meet3) RETURN labels(r) AS ラベル, collect(r.title) AS 要素,length(collect(r.title)) AS 数 UNION ALL
MATCH (s:Marriage3) RETURN labels(s) AS ラベル, collect(s.title) AS 要素,length(collect(s.title)) AS 数 UNION ALL
MATCH (t:Healthcare3) RETURN labels(t) AS ラベル, collect(t.title) AS 要素,length(collect(t.title)) AS 数
ラベル 要素 数
[Sport3] [野球, サッカー, バレーボール, バスケットボール, テーブルテニス, ヨガ, 散歩, ダンス, その他] 9
[Talk3] [聞くタイプ, 話すタイプ, とちらでも良い] 3
[Height3] [140, 150, 160, 170, 180, 190, 200] 7
[Personality3] [外向的, 内向的] 2
[Indoor3] [読書, 映画感想, 音楽感想, TV感想, ゲーム, 寝る, 友たちとお喋り, 料理を作る, その他] 9
[Smoking3] [吸う, 吸わない] 2
[Alcohol3] [飲む, 飲まない] 2
[Karaoke3] [好き, 嫌い] 2
[Fortunetelling3] [信じる, 信じない] 2
[Outdoor3] [海, 山, 川, 公園, 遊園地, どちらでもいい] 6
[Income3] [100, 200, 300, 400, 500, 600, 700, 800, 900] 9
[Born3] [1995, 1994, 1993, 1992, 1991, 1990, 1989, 1988, 1987, 1986, 1985, 1984, 1983, 1982, 1981, 1980, 1979, 1978, 1977, 1976, 1975, 1974, 1973, 1972, 1971, 1970] 26
[Pet3] [犬派, 猫派] 2
[Education3] [義務教育, 高等学校, 各種専門学校, 短大・高専, 大学, 大学院] 6
[Job3] [公務員, 会社員, 自営業, 企業経営・役員, 教師, 医師, 医療関係, 保育士, 農林・漁業, パート・アルバイト, 学生, 家事手伝, その他] 13
[Healthcare3] [意識して運動する, 特に意識していない] 2

リクエスト用の関係性の作成

次のCypherクエリーをすべてWebインターフェースに入力して実行してください。

MATCH
(man:Man { name: "M04"}),
(a1:Alcohol2 { title: "飲む" }),
(a2:Born2 { title: 1981 }),
(b2:Born2 { title: 1982 }),
(c2:Born2 { title: 1983 }),
(d2:Born2 { title: 1984 }),
(e2:Born2 { title: 1985 }),
(f2:Born2 { title: 1986 }),
(g2:Born2 { title: 1987 }),
(h2:Born2 { title: 1988 }),
(i2:Born2 { title: 1989 }),
(a3:Education2 { title: "短大・高専" }),
(b3:Education2 { title: "大学" }),
(c3:Education2 { title: "大学院" }),
(a4:Fortunetelling2 { title: "信じない" }),
(a5:Healthcare2 { title: "意識して運動する" }),
(a6:Height2 { title : 140 }),
(b6:Height2 { title : 150 }),
(c6:Height2 { title : 160 }),
(a7:Holiday2 { title : "土日祝日" }),
(a8:Income2 { title : 100 }),
(b8:Income2 { title : 200 }),
(c8:Income2 { title : 300 }),
(d8:Income2 { title : 400 }),
(e8:Income2 { title : 500 }),
(f8:Income2 { title : 600 }),
(g8:Income2 { title : 700 }),
(h8:Income2 { title : 800 }),
(i8:Income2 { title : 900 }),
(a9:Indoor2 { title : "映画感想" }),
(b9:Indoor2 { title : "ゲーム" }),
(a10:Job2 { title : "公務員" }),
(b10:Job2 { title : "会社員" }),
(c10:Job2 { title : "自営業" }),
(d10:Job2 { title : "企業経営・役員" }),
(e10:Job2 { title : "教師" }),
(f10:Job2 { title : "医師" }),
(g10:Job2 { title : "医療関係" }),
(h10:Job2 { title : "保育士" }),
(i10:Job2 { title : "農林・漁業" }),
(j10:Job2 { title : "パート・アルバイト" }),
(k10:Job2 { title : "その他" }),
(a11:Karaoke2 { title : "好き" }),
(a12:Location2 { title : "埼玉県" }),
(b12:Location2 { title : "千葉県" }),
(c12:Location2 { title : "東京都" }),
(d12:Location2 { title : "神奈川県" }),
(a13:Maritalhistory2 { title : "無し" }),
(a14:Meeting2 { title : "積極的" }),
(a15:Outdoor2 { title : "海" }),
(b15:Outdoor2 { title : "川" }),
(a16:Personality2 { title : "外向的" }),
(b16:Personality2 { title : "内向的" }),
(a17:Pet2 { title : "猫派" }),
(a18:Smoking2 { title : "吸わない" }),
(a19:Sport2 { title : "野球" }),
(b19:Sport2 { title : "サッカー" }),
(c19:Sport2 { title : "バレーボール" }),
(d19:Sport2 { title : "バスケットボール" }),
(e19:Sport2 { title : "テーブルテニス" }),
(f19:Sport2 { title : "ヨガ" }),
(g19:Sport2 { title : "散歩" }),
(h19:Sport2 { title : "ダンス" }),
(i19:Sport2 { title : "その他" }),
(a20:Talk2 { title : "話すタイプ" })
//RETURN *
CREATE
man-[:RE_ALCOHOL]->a1,
man-[:RE_BORN]->a2,
man-[:RE_BORN]->b2,
man-[:RE_BORN]->c2,
man-[:RE_BORN]->d2,
man-[:RE_BORN]->e2,
man-[:RE_BORN]->f2,
man-[:RE_BORN]->g2,
man-[:RE_BORN]->h2,
man-[:RE_BORN]->i2,
man-[:RE_EDUCATION]->a3,
man-[:RE_EDUCATION]->b3,
man-[:RE_EDUCATION]->c3,
man-[:RE_FORTUNETELLING]->a4,
man-[:RE_HEALTHCARE]->a5,
man-[:RE_HEIGHT]->a6,
man-[:RE_HEIGHT]->b6,
man-[:RE_HEIGHT]->c6,
man-[:RE_HOLIDAY]->a7,
man-[:RE_INCOME]->a8,
man-[:RE_INCOME]->b8,
man-[:RE_INCOME]->c8,
man-[:RE_INCOME]->d8,
man-[:RE_INCOME]->e8,
man-[:RE_INCOME]->f8,
man-[:RE_INCOME]->g8,
man-[:RE_INCOME]->h8,
man-[:RE_INCOME]->i8,
man-[:RE_INDOOR]->a9,
man-[:RE_INDOOR]->b9,
man-[:RE_JOB]->a10,
man-[:RE_JOB]->b10,
man-[:RE_JOB]->c10,
man-[:RE_JOB]->d10,
man-[:RE_JOB]->e10,
man-[:RE_JOB]->f10,
man-[:RE_JOB]->g10,
man-[:RE_JOB]->h10,
man-[:RE_JOB]->i10,
man-[:RE_JOB]->j10,
man-[:RE_JOB]->k10,
man-[:RE_KARAOKE]->a11,
man-[:RE_LOCATION]->a12,
man-[:RE_LOCATION]->b12,
man-[:RE_LOCATION]->c12,
man-[:RE_LOCATION]->d12,
man-[:RE_MARITALHISTORY]->a13,
man-[:RE_MEET]->a14,
man-[:RE_OUTDOOR]->a15,
man-[:RE_OUTDOOR]->b15,
man-[:RE_PERSONALITY]->a16,
man-[:RE_PERSONALITY]->b16,
man-[:RE_PET]->a17,
man-[:RE_SMOKING]->a18,
man-[:RE_SPORT]->a19,
man-[:RE_SPORT]->b19,
man-[:RE_SPORT]->c19,
man-[:RE_SPORT]->d19,
man-[:RE_SPORT]->e19,
man-[:RE_SPORT]->f19,
man-[:RE_SPORT]->g19,
man-[:RE_SPORT]->h19,
man-[:RE_SPORT]->i19,
man-[:RE_TALK]->a20

グラフをサンプリングして実行結果を確認してみましょう。以下は教育水準のグラフです。

MATCH ()-[r:RE_EDUCATION]->() RETURN r

re_education

しかし大量のグラフを作成した場合、都度目視で確認することは困難です。次のようにタイプや要素、数を確認するCypherクエリーを利用しましょう。

MATCH ()-[a:RE_SPORT]->(aa) RETURN type(a) AS タイプ,collect(aa.title) AS 要素,length(collect(aa.title)) AS 数 UNION ALL
MATCH ()-[b:RE_HOLIDAY]->(bb) RETURN type(b) AS タイプ,collect(bb.title) AS 要素,length(collect(bb.title)) AS 数 UNION ALL
MATCH ()-[c:RE_TALK]->(cc) RETURN type(c) AS タイプ,collect(cc.title) AS 要素,length(collect(cc.title)) AS 数 UNION ALL
MATCH ()-[d:RE_HEIGHT]->(dd) RETURN type(d) AS タイプ,collect(dd.title) AS 要素,length(collect(dd.title)) AS 数 UNION ALL
MATCH ()-[e:RE_PERSONALITY]->(ee) RETURN type(e) AS タイプ,collect(ee.title) AS 要素,length(collect(ee.title)) AS 数 UNION ALL
MATCH ()-[f:RE_INDOOR]->(ff) RETURN type(f) AS タイプ,collect(ff.title) AS 要素,length(collect(ff.title)) AS 数 UNION ALL
MATCH ()-[g:RE_SMOKING]->(gg) RETURN type(g) AS タイプ,collect(gg.title) AS 要素,length(collect(gg.title)) AS 数 UNION ALL
MATCH ()-[h:RE_ALCOHOL]->(hh) RETURN type(h) AS タイプ,collect(hh.title) AS 要素,length(collect(hh.title)) AS 数 UNION ALL
MATCH ()-[i:RE_KARAOKE]->(ii) RETURN type(i) AS タイプ,collect(ii.title) AS 要素,length(collect(ii.title)) AS 数 UNION ALL
MATCH ()-[j:RE_FORTUNETELLING]->(jj) RETURN type(j) AS タイプ,collect(jj.title) AS 要素,length(collect(jj.title)) AS 数 UNION ALL
MATCH ()-[k:RE_OUTDOOR]->(kk) RETURN type(k) AS タイプ,collect(kk.title) AS 要素,length(collect(kk.title)) AS 数 UNION ALL
MATCH ()-[l:RE_INCOME]->(ll) RETURN type(l) AS タイプ,collect(ll.title) AS 要素,length(collect(ll.title)) AS 数 UNION ALL
MATCH ()-[m:RE_BORN]->(mm) RETURN type(m) AS タイプ,collect(mm.title) AS 要素,length(collect(mm.title)) AS 数 UNION ALL
MATCH ()-[n:RE_LOCATION]->(nn) RETURN type(n) AS タイプ,collect(nn.title) AS 要素,length(collect(nn.title)) AS 数 UNION ALL
MATCH ()-[o:RE_PET]->(oo) RETURN type(o) AS タイプ,collect(oo.title) AS 要素,length(collect(oo.title)) AS 数 UNION ALL
MATCH ()-[p:RE_EDUCATION]->(pp) RETURN type(p) AS タイプ,collect(pp.title) AS 要素,length(collect(pp.title)) AS 数 UNION ALL
MATCH ()-[q:RE_JOB]->(qq) RETURN type(q) AS タイプ,collect(qq.title) AS 要素,length(collect(qq.title)) AS 数 UNION ALL
MATCH ()-[r:RE_MEET]->(rr) RETURN type(r) AS タイプ,collect(rr.title) AS 要素,length(collect(rr.title)) AS 数 UNION ALL
MATCH ()-[s:RE_MARRIAGE]->(ss) RETURN type(s) AS タイプ,collect(ss.title) AS 要素,length(collect(ss.title)) AS 数 UNION ALL
MATCH ()-[t:RE_HEALTHCARE]->(tt) RETURN type(t) AS タイプ,collect(tt.title) AS 要素,length(collect(tt.title)) AS 数
タイプ 要素 数
RE_SPORT [野球, サッカー, バレーボール, バスケットボール, テーブルテニス, ヨガ, 散歩, ダンス, その他] 9
RE_HOLIDAY [土日祝日] 1
RE_TALK [話すタイプ] 1
RE_HEIGHT [160, 150, 140] 3
RE_PERSONALITY [外向的, 内向的] 2
RE_INDOOR [映画感想, ゲーム] 2
RE_SMOKING [吸わない] 1
RE_ALCOHOL [飲む] 1
RE_KARAOKE [好き] 1
RE_FORTUNETELLING [信じない] 1
RE_OUTDOOR [海, 川] 2
RE_INCOME [100, 200, 300, 400, 500, 600, 700, 800, 900] 9
RE_BORN [1989, 1988, 1985, 1984, 1987, 1986, 1981, 1983, 1982] 9
RE_LOCATION [東京都, 神奈川県, 埼玉県, 千葉県] 4
RE_PET [猫派] 1
RE_EDUCATION [短大・高専, 大学, 大学院] 3
RE_JOB [公務員, 会社員, 自営業, 企業経営・役員, 教師, 医師, 医療関係, 保育士, 農林・漁業, パート・アルバイト, その他] 11
RE_HEALTHCARE [意識して運動する] 1

必須要素フィルター条件用の関係性の作成

次のCypherクエリーをすべてWebインターフェースに入力して実行してください。

MATCH
(man:Man { name:"M04"}),
(a1:Born3 { title: 1970 }),
(b1:Born3 { title: 1971 }),
(c1:Born3 { title: 1972 }),
(d1:Born3 { title: 1973 }),
(e1:Born3 { title: 1974 }),
(f1:Born3 { title: 1975 }),
(g1:Born3 { title: 1976 }),
(h1:Born3 { title: 1977 }),
(i1:Born3 { title: 1978 }),
(j1:Born3 { title: 1979 }),
(k1:Born3 { title: 1980 }),
(l1:Born3 { title: 1990 }),
(m1:Born3 { title: 1991}),
(n1:Born3 { title: 1992}),
(o1:Born3 { title: 1993}),
(p1:Born3 { title: 1994}),
(q1:Born3 { title: 1995}),
(a3:Education3 { title: "義務教育" }),
(b3:Education3 { title: "高等学校" }),
(c3:Education3 { title: "各種専門学校" }),
(a4:Height3 { title: 170 }),
(b4:Height3 { title: 180 }),
(c4:Height3 { title: 190 }),
(d4:Height3 { title: 200 }),
(a5:Job3 { title: "家事手伝" }),
(b5:Job3 { title: "学生" })
//RETURN *
CREATE
man-[:FI_BORN]->a1,
man-[:FI_BORN]->b1,
man-[:FI_BORN]->c1,
man-[:FI_BORN]->d1,
man-[:FI_BORN]->e1,
man-[:FI_BORN]->f1,
man-[:FI_BORN]->g1,
man-[:FI_BORN]->h1,
man-[:FI_BORN]->i1,
man-[:FI_BORN]->j1,
man-[:FI_BORN]->k1,
man-[:FI_BORN]->l1,
man-[:FI_BORN]->m1,
man-[:FI_BORN]->n1,
man-[:FI_BORN]->o1,
man-[:FI_BORN]->p1,
man-[:FI_BORN]->q1,
man-[:FI_EDUCATION]->a3,
man-[:FI_EDUCATION]->b3,
man-[:FI_EDUCATION]->c3,
man-[:FI_HEIGHT]->a4,
man-[:FI_HEIGHT]->b4,
man-[:FI_HEIGHT]->c4,
man-[:FI_HEIGHT]->d4,
man-[:FI_JOB]->a5,
man-[:FI_JOB]->b5

居住地域も忘れないでください。

MERGE (man:Person:Man { name : "M04"})
ON MATCH SET man.filter_location="北海道|青森県|岩手県|宮城県|秋田県|山形県|福島県|茨城県|栃木県|群馬県|新潟県|富山県|石川県|福井県|山梨県|長野県|岐阜県|静岡県|愛知県|三重県|滋賀県|京都府|大阪府|兵庫県|奈良県|和歌山県|鳥取県|島根県|岡山県|広島県|山口県|徳島県|香川県|愛媛県|高知県|福岡県|佐賀県|長崎県|熊本県|大分県|宮崎県|鹿児島県|沖縄県"

グラフをサンプリングして実行結果を確認してみましょう。以下は、教育水準のグラフです。

MATCH ()-[r:FI_EDUCATION]->() RETURN r

fi_education

次のCypherクエリーを利用してタイプや要素、数を確認しましょう。

MATCH ()-[a:FI_BORN]->(aa) RETURN type(a) AS タイプ,collect(aa.title) AS 要素,length(collect(aa.title)) AS 数 UNION ALL
MATCH ()-[b:FI_EDUCATION]->(bb) RETURN type(b) AS タイプ,collect(bb.title) AS 要素,length(collect(bb.title)) AS 数 UNION ALL
MATCH ()-[c:FI_HEIGHT]->(cc) RETURN type(c) AS タイプ,collect(cc.title) AS 要素,length(collect(cc.title)) AS 数 UNION ALL
MATCH ()-[d:FI_JOB]->(dd) RETURN type(d) AS タイプ,collect(dd.title) AS 要素,length(collect(dd.title)) AS 数
タイプ 要素 数
FI_BORN [1995, 1994, 1993, 1992, 1991, 1990, 1980, 1979, 1978, 1977, 1976, 1975, 1974, 1973, 1972, 1971, 1970] 17
FI_EDUCATION [義務教育, 高等学校, 各種専門学校] 3
FI_HEIGHT [170, 180, 190, 200] 4
FI_JOB [家事手伝, 学生] 2

最後に、居住地域のフィルター条件を確認してみましょう。

MATCH (man:Man { name:"M04" }) RETURN split(man.filter_location, "|") AS list
list
[北海道, 青森県, 岩手県, 宮城県, 秋田県, 山形県, 福島県, 茨城県, 栃木県, 群馬県, 新潟県, 富山県, 石川県, 福井県, 山梨県, 長野県, 岐阜県, 静岡県, 愛知県, 三重県, 滋賀県, 京都府, 大阪府, 兵庫県, 奈良県, 和歌山県, 鳥取県, 島根県, 岡山県, 広島県, 山口県, 徳島県, 香川県, 愛媛県, 高知県, 福岡県, 佐賀県, 長崎県, 熊本県, 大分県, 宮崎県, 鹿児島県, 沖縄県]

M04さんのデータ処理をやってみる

ここで、この連載を愛読している皆様にご了承を得たいことがあります。第2回目のデータ処理時のCypherクエリーのフィルター条件の設定に間違いがあることが分かりました。まず、第2回目のCypherクエリーの間違いを直して実行してみてから、今回のデータ処理をさせて頂きます。申し訳ありません。

第2回目のCypherクエリーの訂正

以下のCypherクエリーが第2回目の間違いを訂正したクエリー文です。今回の分析で候補者26人のなかでM04さんのリクエストに合っている人は5人しか存在しないことが分かりました。

WITH ["飲む",1981,1982,1983,1984,1985,1986,1987,1989,"大学","短大・高専","大学院","信じない","意識して運動する",140,150,160,"土日祝日",100,200,300,400,500,600,700,800,900,"映画感想","ゲーム","パート・アルバイト","農林・漁業","保育士","自営業","企業経営・役員","教師","医師","公務員","会社員","その他","医療関係","好き","東京都","神奈川県","埼玉県","千葉県","無し","積極的","海","川","外向的","内向的","猫派","吸わない","ダンス","散歩","野球","サッカー","バレーボール","バスケットボール","テーブルテニス","ヨガ","話すタイプ"] AS list
MATCH (woman:Person:Woman)-[*]->(things)
WHERE NOT woman-[:DISAGREE|SUCCESS]-()
WITH woman, COLLECT(things.title) AS wlist,list, [1980,1979,1978,1977,1976,1975,1974,1973, 1972,1971,1970,1995,1994,1993,1992,1991,1990, "義務教育","高等学校","各種専門学校","北海道","青森県","岩手県","宮城県","秋田県","山形県","福島県","茨城県","栃木県","群馬県","新潟県","富山県","石川県","福井県","山梨県","長野県","岐阜県","静岡県","愛知県","三重県","滋賀県","京都府","大阪府","兵庫県","奈良県","和歌山県","鳥取県","島根県","岡山県, 広島県","山口県","徳島県","香川県","愛媛県","高知県","福岡県","佐賀県","長崎県","熊本県","大分県"," 宮崎県","鹿児島県","沖縄県","家事手伝","学生",170,180,190,200] AS list2
WHERE NONE ( x IN wlist WHERE x IN list2)
WITH woman, filter( x IN list WHERE x IN wlist) AS shared
WITH woman.name AS wlist, shared AS slist, 1.0 * length(shared) / 20 AS rank
RETURN wlist AS 名前, slist AS 共通要素, rank AS 類似性
ORDER BY rank DESC
LIMIT 10

以下は出力される結果の例です。

名前 共通要素 類似性
W13 [飲む, 1984, 大学, 信じない, 意識して運動する, 150, 700, ゲーム, 自営業, 好き, 埼玉県, 無し, 海, 外向的, 吸わない, バスケットボール] 0.8
W24 [1982, 大学, 意識して運動する, 140, 600, ゲーム, 自営業, 好き, 埼玉県, 無し, 積極的, 海, 外向的, 猫派, 吸わない, バレーボール] 0.8
W14 [飲む, 1983, 大学, 信じない, 160, 土日祝日, 400, 企業経営・役員, 好き, 神奈川県, 海, 内向的, 猫派, 野球, 話すタイプ] 0.75
W22 [飲む, 1984, 大学, 信じない, 意識して運動する, 160, 土日祝日, 300, 公務員, 好き, 東京都, 外向的, 猫派, 野球] 0.7
W11 [1986, 短大・高専, 信じない, 150, 300, 公務員, 東京都, 無し, 内向的, 吸わない, ヨガ] 0.55

第2回目のデータ処理時のCypherクエリーの間違いは次の通りです。

・必須条件のフィルター要素を年齢のみにしていました。実際は、年齢、身長、学歴、職業、居住地域の5個でした。
・必須条件のフィルターを実行するクエリーブロックの箇所をリクエスト要素とのパターンマッチを実行してから行っていました。これは、データ処理量を減らすためにも、不正確なパターンを見逃さないためにも、その前のクエリーブロックで実行すべきことでした。次の構文のことです。

WHERE NONE ( x IN wlist WHERE x IN list2)

今回の処理

今回のCypherクエリーでは、すべてのパターンの取得をCypherクエリーのなかで自己完結しています。第2回目のデータ処理時のCypherクエリーと比較してみてください。各種リストの要素が剥き出しになっている箇所がすべてなくなっています。

MATCH (man:Man{ name:"M04"})-[:RE_SPORT|RE_HOLIDAY|RE_TALK|RE_HEIGHT|RE_PERSONALITY|RE_INDOOR|RE_SMOKING|RE_ALCOHOL|RE_KARAOKE|RE_FORTUNETELLING|RE_OUTDOOR|RE_INCOME|RE_BORN|RE_LOCATION|RE_PET|RE_EDUCATION|RE_JOB|RE_MEET|RE_MARITALHISTORY|RE_HEALTHCARE]->(request),
man-[:FI_BORN|FI_EDUCATION|FI_HEIGHT|FI_JOB]->(filterCondition)
WITH split(man.filter_location, "|") AS fitlerLocation, collect(distinct request.title) AS requestList, collect(distinct filterCondition.title) AS filterList
MATCH (woman:Person:Woman)-[*]->(things)
WHERE NOT woman-[:DISAGREE|SUCCESS]-()
WITH woman, COLLECT(things.title) AS womanCondition, fitlerLocation, filterList, requestList
WHERE NONE ( x IN womanCondition WHERE x IN filterList) AND NONE ( x IN womanCondition WHERE x IN fitlerLocation)
WITH woman, filter( x IN requestList WHERE x IN womanCondition) AS shared, fitlerLocation, filterList
WITH woman.name AS wlist, shared AS slist, 1.0 * length(shared) / 20 AS rank
RETURN wlist AS 名前, slist AS 共通要素, rank AS ランク
ORDER BY rank DESC
LIMIT 10

以下のような結果が出力されます。

名前 共通要素 ランク
W13 [飲む, 1984, 大学, 信じない, 意識して運動する, 150, 700, ゲーム, 自営業, 好き, 埼玉県, 無し, 海, 外向的, 吸わない, バスケットボール] 0.8
W24 [1982, 大学, 意識して運動する, 140, 600, ゲーム, 自営業, 好き, 埼玉県, 無し, 積極的, 海, 外向的, 猫派, 吸わない, バレーボール] 0.8
W14 [飲む, 1983, 大学, 信じない, 160, 土日祝日, 400, 企業経営・役員, 好き, 神奈川県, 海, 内向的, 猫派, 野球, 話すタイプ] 0.75
W22 [飲む, 1984, 大学, 信じない, 意識して運動する, 160, 土日祝日, 300, 公務員, 好き, 東京都, 外向的, 猫派, 野球] 0.7
W11 [1986, 短大・高専, 信じない, 150, 300, 公務員, 東京都, 無し, 内向的, 吸わない, ヨガ] 0.55

しかしながらM04さんのリクエストに合っている人は5人しか存在しないことは変わりません。もちろん、今回のトライアルプロジェクトで登録している女性の人数が少ないためですが、それでも26人のなかでの5人という結果は少し寂しいかもしれませんね。M04さん、頑張ってください!

M04さんの候補者でリクエストと異なる要素をみる

前項の5人の候補者の情報からM04さんのリクエストと一致しない要素を出してみます。必須条件としては出してないですが、内心、これだけはM04さんの気持ちでは勘弁してほしいと思うことがあるかもしれません。

MATCH (man:Man{ name:"M04"})-[:RE_SPORT|RE_HOLIDAY|RE_TALK|RE_HEIGHT|RE_PERSONALITY|RE_INDOOR|RE_SMOKING|RE_ALCOHOL|RE_KARAOKE|RE_FORTUNETELLING|RE_OUTDOOR|RE_INCOME|RE_BORN|RE_LOCATION|RE_PET|RE_EDUCATION|RE_JOB|RE_MEET|RE_MARITALHISTORY|RE_HEALTHCARE]->(request),
man-[:FI_BORN|FI_EDUCATION|FI_HEIGHT|FI_JOB]->(filterCondition)
WITH split(man.filter_location, "|") AS fitlerLocation, collect(distinct request.title) AS requestList, collect(distinct filterCondition.title) AS filterList
MATCH (woman:Person:Woman)-[*]->(things)
WHERE NOT woman-[:DISAGREE|SUCCESS]-()
WITH woman, COLLECT(things.title) AS womanCondition, fitlerLocation, filterList, requestList
WHERE NONE ( x IN womanCondition WHERE x IN filterList) AND NONE ( x IN womanCondition WHERE x IN fitlerLocation)
WITH woman, filter( x IN requestList WHERE x IN womanCondition) AS shared, fitlerLocation, filterList, womanCondition, requestList
WITH woman.name AS wlist, shared AS slist, 1.0 * length(shared) / 20 AS rank , womanCondition, requestList
WITH wlist,slist,rank, requestList, womanCondition
ORDER BY rank DESC
LIMIT 10
RETURN wlist AS 名前, slist AS 共通要素, rank AS 類似性, filter( x IN womanCondition WHERE NOT(x IN requestList)) AS 不一致

以下のような結果が出力されます。

名前 共通要素 類似性 不一致
W13 [飲む, 1984, 大学, 信じない, 意識して運動する, 150, 700, ゲーム, 自営業, 好き, 埼玉県, 無し, 海, 外向的, 吸わない, バスケットボール] 0.8 [不定期, とちらでも良い, 消極的, 犬派]
W24 [1982, 大学, 意識して運動する, 140, 600, ゲーム, 自営業, 好き, 埼玉県, 無し, 積極的, 海, 外向的, 猫派, 吸わない, バレーボール] 0.8 [平日, とちらでも良い, 飲まない, 信じる]
W14 [飲む, 1983, 大学, 信じない, 160, 土日祝日, 400, 企業経営・役員, 好き, 神奈川県, 海, 内向的, 猫派, 野球, 話すタイプ] 0.75 [とちらでもない, 寝る, 吸う, 特に意識していない, 有り]
W22 [飲む, 1984, 大学, 信じない, 意識して運動する, 160, 土日祝日, 300, 公務員, 好き, 東京都, 外向的, 猫派, 野球] 0.7 [聞くタイプ, 消極的, 音楽感想, 吸う, 山, 有り]
W11 [1986, 短大・高専, 信じない, 150, 300, 公務員, 東京都, 無し, 内向的, 吸わない, ヨガ] 0.55 [平日, 聞くタイプ, とちらでもない, 音楽感想, 飲まない, 嫌い, 特に意識していない, 公園, 犬派]

まとめ

これで「婚活プロジェクト」の連載は終了させて頂きます。今回のトライアルプロジェクトは、現実の世界に有り得ることを素材にしてはいますが、様々なことを単純化しています。今回のデータモデルや処理方法が、このようなケースにおけるベストプラクティスとは思っておりません。もしお気づきのところがありましたら是非コメントを頂けると幸いです。ここまで愛読して頂いた方達に何か役に立つことが1つでもあったとすれば幸いです。

[情報と情報の繋がりは価値である]
GoogleやFacebook, Twitterなど、世界をリードするIT企業では、既に10年以上前から「情報と情報の繋がりは価値である」いう思想を持っていたようです。これらの企業達は、繋がりを起業や新しいサービス展開のための中核に据えていたと言われています。そして、繋がりを利用するための手段(グラフデータベース)の開発や利用にも熱心に取り組んできました。汎用型グラフデータベースとも言えるNeo4jも、これらの企業の思想や技術に影響を受けているといわれています。
従来のリレーショナルデータベースの考え方から言うと、繋がりはER図やジョインなどのための紐付と表現し、情報を検索するための手段という位置づけでした。もちろん、情報を検索するための手段というのも重要なことですが、「情報と情報の繋がりは価値である」という思想とは、大きな隔たりがあるような気がします。
グラフデータベースを利用するに当たっては、この思想の違いを理解して頂きたいです。既に存在する繋がりから新しい価値を発掘する、繋がりが存在しなかった箇所に繋がりを作ることで、さらに新しい価値連鎖を引き起こすことが、これからグラフデータベースがもたらす未来なのかも知れません。

Author

モダンアーキテクチャー基盤のソリューションアーキテクトとして活動しています。

[著書]
・Amazon Cloudテクニカルガイド―EC2/S3からVPCまで徹底解析
・Amazon Elastic MapReduceテクニカルガイド ―クラウド型Hadoopで実現する大規模分散処理
・Cypherクエリー言語の事例で学ぶグラフデータベースNeo4j
・Neo4jを使うグラフ型データベース入門(共著)
・RDB技術者のためのNoSQLガイド(共著)

leeの記事一覧

新規CTA