商品購買のアソシエーションルールをネットワークで可視化する
アソシエーション分析の概略
アソシエーション分析は、商品の何と何が一緒に買われやすいのか?を示すための分析手法です。
「アソシエーション分析」とか「アソシエーションルール」とかをググると大量の記事が出てきますので、詳細はそちらの記事を参考にしてください。*1
以下、本記事中で重要になる点だけ記載します。
支持度(support)
総購買ユーザーNのうち、商品Aと商品Bを同時に買っている人の割合です。
信頼度(confidence)
商品Aを買った人のうち、商品Bを購入した人の割合です。
リフト(lift)
商品Aを買った人のうち商品Bを購入する人の割合(信頼度)が、
そもそも総購買ユーザーNにおける商品Bの購入率より高いか低いか、を表します。
一般的には、ある程度の支持度がある購入パターンのみで線引きし、リフト値を見て商品ごとの連関強弱を計ります。
今回の目的は、アソシエーションルールをネットワークで可視化し、商品ごとの連関を分かりやすく表現できるか検証します。
ネットワークは、Pythonのnetworkxを利用し、Tableauで描写することにします。
SQLによるデータ抽出
自身のお仕事にも活かせるよう購買データを使います。
postgresqlが提供してくれているDVDレンタルのサンプルデータを使用することにします。
ただし、バスケット(同レシート内購買)単位での分析は難しいので、ユーザー単位で集計することとします。
サンプルデータとER図はここで確認できます。
これを自身のDB環境に入れます。
以下、SQLで上記指標に必要なA∩B
, A
, B
, N
を集計します。*2
with item as ( select inventory_id , title , name from --- 中略------------------------------------------ inner join( select tran.title , count (distinct customer_id) as "B" from tran group by tran.title ) as target_table on fromto.target_item = target_table.title ;
※正直汚いSQLです。すみません。
気になる方は以下GitHubをご確認いただければと思います。
こんな感じのテーブルに仕上がりました。
ここから、support, confidence, liftの算出とネットワーク座標の生成はPythonに渡します。
Python・networkxによる node座標データの作成
ここは、先般のブログどおり、netowrokxでTableauに取り込むテーブルデータを準備します。
まず、support , confidence , lift を計算します。
さらにAandB
の出現UUが少ない組み合わせは除外してしまいましょう。
import pandas as pd edge_df = pd.read_csv("output.csv") # 以下、support , confidence , liftを計算します。 edge_df["support"] = edge_df["AandB"] / edge_df["N"] edge_df["confidence"] = edge_df["AandB"] / edge_df["A"] edge_df["lift"] = edge_df["confidence"] / ( edge_df["B"] / edge_df["N"] ) # supportが低い組み合わせを除外します。 edge_df = edge_df[edge_df["AandB"]>3] display(edge_df) print(edge_df.shape)
本来はconfidenceとliftの両方を考察すべきですが、
一旦liftだけをweightとしてみました。
edge_df_lift = edge_df[["source_item","target_item","confidence"]] col_name = ["source", "target","weight"] edge_df_lift.columns = col_name import networkx as nx def make_network(df): G = nx.from_pandas_edgelist(df,edge_attr=True) pos = nx.spring_layout(G) edges = G.edges() weights = [G[u][v]['weight'] for u,v in edges] nx.draw(G, pos, edges=edges, width=weights, node_size=10, node_color="c",with_labels=False) node = [] x = [] y = [] for k,v in pos.items(): node.append(k) x.append(v[0]) y.append(v[1]) node_df=pd.DataFrame({ "item":node, "X":x, "Y":y }) return node_df node_df = make_network(edge_df_lift)
・・・一応netowrokxでの画像出力していますが、完全に意味不明な感じになっています。
結果が見える戦いをするのはつらいですが、Tableau化まで以前と同じ手順で進めます。
result_df_1 = pd.merge(edge_df_lift,node_df,how="left",left_on="source",right_on="item") result_df_2 = pd.merge(edge_df_lift,node_df,how="left",left_on="target",right_on="item") result_df = pd.concat([result_df_1, result_df_2]) result_df["edge_name"] = result_df["source"] + "_" + result_df["target"]
Tableau上での可視化
手順は上にあげた過去記事とおりですので省略します。
なんとなく、lift(weight)が明らかに高い組み合わせもありますが、全体のネットワークの傾向としては解釈が難しいですね。
networkx側でもう少し手を加えるべきなのでしょうか・・・?
そもそも購買データをネットワークグラフにするのが適切ではない・・・?
あるいはサンプルデータだからランダマイズされてるとか・・・?
技術・知識的な未熟さが原因か、そもそもデータの話かは検証する余地がありそうですが、 面白い試みだったかなぁと思います。