
Pythonでグラフ・ネットワーク図を描く
こんにちは。コグラフ株式会社データアナリティクス事業部の塩見です。「このアイテムが好きな人は、こんなアイテムも好き」という関連性を可視化できるグラフ・ネットワーク図。かっこいいと感じる人は多いのではないでしょうか。しかし、実際に描こうとするとデータの準備でつまずくことがあります。アイテム間の関係が明示的に存在するデータは実務では少ないのです。SNSのようにユーザー同士が直接つながる場合もありますが、そのようなデータはめずらしいと思います。ネットワーク作図ライブラリは豊富ですが、描くべきデータがなければ活用できません。
では、どうすればよいのか?答えは簡単です。「ないなら作る!」
具体的には、推薦ロジックを作成し、アイテム間の関係を定義します。ユーザーの閲覧履歴や購入履歴をもとに類似アイテムを推定し、それを可視化すれば、自分だけの「かっこいいグラフ・ネットワーク図」が完成します。ここではPythonのpyvisを使い、Google Colaboratoryで実行する方法を紹介します。
行動データの取得
まず、閲覧履歴や購入履歴などの行動データを用意します。行動データは「誰が(ユーザー)」「何を(アイテム)」という情報を記録したものです。以下のようなタブ区切りの log.txt を例に説明します。
user item
1 A
1 B
2 C
2 A
3 B
3 C
アイテムの共起をカウント
ユーザーがアイテムAとBを選んだ場合、それらは何らかの共通点がある可能性があります。

このアイテム同士の共起回数をカウントするSQLコードを示します。log.txt の内容が log テーブルに格納されているとし、以下のように直積(クロス結合)を取ることで共起を求めます。
-- code SQL_1
-- アイテム同士の組み合わせをカウントする。
SELECT
log1.item AS item1
,log2.item AS item2
,COUNT(*) AS combination_count
FROM log log1 CROSS JOIN log log2
WHERE log1.user = log2.user AND log1.item < log2.item
GROUP BY
log1.item
,log2.item

このコードこそが本記事の核心です。これほど簡単なSQLで、行動データからアイテム同士の関係を抽出できるのです。
アイテムの類似度を計算
さらに、アイテム同士のコサイン類似度を計算します。ユーザーがアイテムを選んだ場合を1、選ばなかった場合を0とし、アイテムごとにユーザーの選択をベクトル化します。
例えば、アイテムAとBの選択ベクトルを以下のように表現できます。
$$
\bm{A}=(1, 1, 0)^\mathsf{T}, \bm{B}=(1, 0, 1)^\mathsf{T}
$$
このベクトルの内積を求めると
$$
\bm{A}\cdot\bm{B}=1\cdot1+1\cdot0+0\cdot1=1
$$
コサイン類似度は次の式で定義されます。
$$
cos(\bm{x}, \bm{y})=\dfrac{\bm{x}\cdot\bm{y}}{|\bm{x}||\bm{y}|}
$$
これを求めるためのSQLコードが SQL_2 です。同じ処理をPythonで実装したものが Python_1 になります。
-- code SQL_2
-- アイテム同士のコサイン類似度を求める。
WITH combination AS (
SELECT
log1.item AS item1
,log2.item AS item2
,COUNT(*) AS combination_count
FROM log log1 CROSS JOIN log log2
WHERE log1.user = log2.user AND log1.item < log2.item
GROUP BY
log1.item
,log2.item
)
,summary as (
SELECT
item
,COUNT(*) AS item_count
FROM log
GROUP BY
item
)
SELECT
c.item1
,c.item2
,c.combination_count / sqrt(s1.item_count) / sqrt(s2.item_count) as similarity
FROM combination c
INNER JOIN summary s1
OM c.item1 = s1.item
INNER JOIN summary s2
ON c.item2 = s2.item;

# code Pyhton_1
# 類似度を求めるコードPython版
import pandas as pd
import numpy as np
# データの読み込み
log = pd.read_csv('log.txt', sep='\t')
# log同士の共起回数カウント(内積の計算)
combination = (
log.merge(log, on='user')
.query('item_x < item_y')
.groupby(['item_x', 'item_y'])
.size()
.reset_index(name='combination_count')
)
# 各アイテムの出現回数
summary = log.groupby('item').size().reset_index(name='item_count')
# 類似度の計算
result = (
combination
.merge(summary, left_on='item_x', right_on='item')
.rename(columns={'item_count': 'item_count_x'})
.drop(columns=['item']) # 重複カラムを削除
.merge(summary, left_on='item_y', right_on='item')
.rename(columns={'item_count': 'item_count_y'})
.drop(columns=['item']) # 重複カラムを削除
)
result['similarity'] = result['combination_count'] / np.sqrt(result['item_count_x']) / np.sqrt(result['item_count_y'])
# 必要なカラムを選択
result = result[['item_x', 'item_y', 'similarity']]
print(result)
result.to_csv('result.csv', index=False)

可視化
次に、pyvisを使ってネットワーク図を可視化します。Google Colaboratory環境では、以下のコマンドでpyvisをインストールできます。

pyvisの公式チュートリアル「Example: Visualizing a Game of Thrones character network」を参考に Python_2 を作成しました。
# code Python_2
# pyvisで可視化する
from pyvis.network import Network
import pandas as pd
df = pd.read_csv("result.csv")
sources = df['item_x']
targets = df['item_y']
weights = df['similarity']
edge_data = zip(sources, targets, weights)
net = Network()
for e in edge_data:
src = e[0]
dst = e[1]
w = e[2]
net.add_node(src, label=str(src))
net.add_node(dst, label=str(dst))
net.add_edge(src, dst, value=w)
net.show("items.html", notebook=False)
このコードを実行すると items.html というファイルが生成されます。このファイルをブラウザで開くことで、アイテム同士の関係が可視化されたネットワーク図を確認できます。Google Colaboratory環境では、ファイルをダウンロードしてから開いてください。

まとめ
この記事では、行動データをもとにアイテム同士の関係を抽出し、ネットワーク図として可視化する手法を紹介しました。データが直接入手できなくても、関係性を定義することで分析の幅が広がります。
さらに、アイテムとユーザーの場所を入れ替えて同じコードを実行すると、ユーザー同士の関係も可視化できます。共通のアイテムを選ぶユーザー同士にどんな共通点があるのか、調べるのも面白いでしょう。
最後に、本記事のもう一つの主張は「SQLの記述力の高さ」です。SQL_1 や SQL_2 のコードを見直してみてください。雑音が少なくて、まるで数式のように読みやすいと思いませんか。単純かつ直感的にデータの関係を抽出できる点がSQLの強みです。
データ分析に興味のある方募集中!
コグラフ株式会社データアナリティクス事業部ではPythonやSQLの研修を行った後、実務に着手します。
研修内容の充実はもちろん、経験者に相談できる環境が備わっています。
このようにコグラフの研修には、実務を想定し着実にスキルアップを目指す環境があります。
興味がある方は、下記リンクよりお問い合わせください。
Xもやってます!
コグラフデータ事業部ではXでも情報を発信しています。
データ分析に興味がある、データアナリストになりたい人など、ぜひフォローお願いします!
📢Wantedly新掲載!
— コグラフ株式会社 | データアナリティクス事業部 (@CographData) July 14, 2023
「データに興味がある」
「データに携わる仕事がしたい」
そこのあなた!
私たちと一緒にデータ分析しませんか?
IT業界未経験の方も大歓迎です☺️#エンジニア転職 #データ分析 #駆け出しエンジニアと繋がりたいhttps://t.co/S9o7VSjGRt