mlxtendを利用したAprioriアルゴリズムとFP-Growthアルゴリズムの実装 (アソシエーション分析)
アソシエーション分析は、特に小売セクターにおいて、顧客の購入パターンを把握しマーケティング計画を策定する際に活用されるデータマイニングの手法です。
アソシエーション分析の目標は、アイテム間の相関やパターンを特定することにあります。
例えば、「オムツとビール」という有名な話は、アソシエーション分析の重要性を示す事例で、それにより、父親がオムツとビールを同時に購入する傾向があることが分かりました。 (この話は事実ではないようですが、アソシエーション分析の重要性を表す有名な逸話です。)
この分析結果は、店舗の商品配置やプロモーションの策略に役に立ちます。
アソシエーション分析のプロセスは以下の通りです。
- 消費者の行動を分析し、よく一緒に購入される商品の組み合わせを特定
- アソシエーションルールを作成し、評価
これらのアイテムセットの頻繁な出現を特定するために、
- Apriori
- FP-Growth
といったアルゴリズムが用いられます。
これらのアルゴリズムは製品間の関連を分析し、一緒に頻繁に購入される製品のセットを識別するために使われます。
アソシエーションルールの効果は、サポート、信頼度、リフトの3つの指標を用いて評価されます。
サポート
サポートとは、特定のアイテムセットが全トランザクションの中でどれだけ頻繁に現れるかを示す割合です。
サポートは数式で表すと、サポートは以下のように計算されます。
$$ \begin{equation} \operatorname{Supp}(X)=\frac{\text{トランザクション内でXが出現する回数}}{\text{全トランザクション数}}=P(X) \end{equation} $$
複数の商品の組み合わせに対しても計算可能で以下のように定義されます。
$$ \begin{equation} \operatorname{Supp}(X, Y)=P(X \cap Y) \end{equation} $$
信頼度
信頼度は特定のアソシエーションルールの強さを測るための指標です。
一つのアイテムセットが購入された際、別のアイテムセットがどれぐらいの確率で購入されるか定量的に定義した値です。
これは、アイテム間の条件付き確率として解釈されます。商品$X$が購入された際に、商品$Y$も同時に購入される信頼度$(X \Rightarrow Y)$は以下のように計算されます。
$$ \begin{equation} \operatorname{Conf}(X \Rightarrow Y)=\frac{\operatorname{Supp}(X, Y)}{\operatorname{Supp}(X)}=\frac{P(X \cap Y)}{P(X)}=P(Y | X) \end{equation} $$
リフト
リフトは特定のアソシエーションルールがどの程度有効であるかを示す指標です。
リフトは、アイテムセット間の関連性の強さを測定し、特定のルールが偶然によるものか、実際に意味のある関連性を持つかを判断するのに役立ちます。リフトは以下のように計算されます。
$$ \begin{equation} \operatorname{Lift}(X \Rightarrow Y)=\frac{\operatorname{cont}(X \Rightarrow Y)}{\operatorname{supp}(Y)}=\frac{P(X \cap Y)}{P(X) P(Y)} \end{equation} $$
リフト値は、2つのアイテムセット間の関連性を示す指標であり、リフト値が1より大きい場合、アイテムセット間には正の関連性があり、リフト値が1の場合は独立していることを意味し、1未満であれば負の関連性があるとされます。
アソシエーション分析は推薦システムにも適用され、個々の顧客に適したカスタマイズされた商品推薦、関連商品の提案、商品の組み合わせ販売、在庫管理とマーケティング計画の最適化などに活用されています。
ソースコードと実行環境
github
- jupyter notebook形式のファイルはこちら
google colaboratory
- google colaboratory で実行する場合はこちら
実行環境
OSはmacOSです。LinuxやUnixのコマンドとはオプションが異なりますので注意してください。
!sw_vers
ProductName: macOS
ProductVersion: 13.5.1
BuildVersion: 22G90
!python -V
Python 3.9.17
基本的なライブラリをインポートし watermark を利用してそのバージョンを確認しておきます。 ついでに乱数のseedの設定をします。
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import random
import scipy
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
seed = 123
random_state = 123
random.seed(seed)
np.random.seed(seed)
from watermark import watermark
print(watermark(python=True, watermark=True, iversions=True, globals_=globals()))
Python implementation: CPython
Python version : 3.9.17
IPython version : 8.17.2
numpy : 1.25.2
matplotlib: 3.8.1
scipy : 1.11.2
Watermark: 2.4.3
データの読み込み
サンプル用のデータとして、Kaggleで公開されているデータセットを利用します。データセットは以下のURLからダウンロードできます。
“./data/” というディレクトリに保存することにします。
とりあえず、pandasを利用してDataFrame型で読み込みます。
import pandas as pd
# ファイルの指定
# ./data/ というディレクトリに保存。
file_name = "./data/GroceryProductsPurchase.csv"
# pandasを利用して、DataFrame型で読み込む
df = pd.read_csv(file_name)
df.head()
Product 1 | Product 2 | Product 3 | Product 4 | Product 5 | Product 6 | Product 7 | Product 8 | Product 9 | Product 10 | ... | Product 23 | Product 24 | Product 25 | Product 26 | Product 27 | Product 28 | Product 29 | Product 30 | Product 31 | Product 32 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | citrus fruit | semi-finished bread | margarine | ready soups | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1 | tropical fruit | yogurt | coffee | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2 | whole milk | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3 | pip fruit | yogurt | cream cheese | meat spreads | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
4 | other vegetables | whole milk | condensed milk | long life bakery product | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
5 rows × 32 columns
カラム名が「Product1」、「Product2」・・・となっており、購入した商品名が各行に記載されています。
たとえば、1行目はあるユーザーが4つの商品を同時に購入し、その商品名は「citrus fruit」、「semi-finished bread」、「margarine」、「ready soups」である事を意味します。また、購入した商品は4つだけなので、「Product5」以上はNaNと表示されています。
これは一般的に利用されるトランザクションデータの形式に修正します。
すなわち、2次元の配列(リスト)で、それぞれのサブリストに購入した商品名が格納されている形式です。
# 各行からNaNを削除して、リスト型に変換
df["transaction_list"] = df.apply(lambda x: x.dropna().to_list(), axis=1)
transactions = df.transaction_list.to_list()
# 最初の5行を表示
transactions[:5]
[['citrus fruit', 'semi-finished bread', 'margarine', 'ready soups'],
['tropical fruit', 'yogurt', 'coffee'],
['whole milk'],
['pip fruit', 'yogurt', 'cream cheese', 'meat spreads'],
['other vegetables',
'whole milk',
'condensed milk',
'long life bakery product']]
このトランザクションデータを利用して、mlxtendが読み込む形式(ワンホットエンコーディング)に変換します。
import pandas as pd
from mlxtend.preprocessing import TransactionEncoder
# ワンホットエンコーディング処理
te = TransactionEncoder()
te_ary = te.fit(transactions).transform(transactions)
df = pd.DataFrame(te_ary, columns=te.columns_)
df.head()
Instant food products | UHT-milk | abrasive cleaner | artif. sweetener | baby cosmetics | baby food | bags | baking powder | bathroom cleaner | beef | ... | turkey | vinegar | waffles | whipped/sour cream | whisky | white bread | white wine | whole milk | yogurt | zwieback | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | False | False | False |
1 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | False | True | False |
2 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | True | False | False |
3 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | False | True | False |
4 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | True | False | False |
5 rows × 169 columns
このDataFrameをmlxtendのAprioriに読み込ませて実行します。
Aprioriアルゴリズム
Aprioriアルゴリズムは、アソシエーション分析の中で最も有名で基本的なアルゴリズムで、大規模データセット内の頻繁に発生するアイテムセットを特定します。
Aprioriアルゴリズムは、最初に全ての単一アイテムセットを生成し、サポートを計算して最小サポート閾値未満のアイテムセットを削除、次に残ったアイテムセットを組み合わせて大きなアイテムセットを形成します。それを繰り返すことで、探索空間を制限しつつ、効率的に頻繁に購入されるアイテムセットを抽出します。
Aprioriアルゴリズムはアソシエーション分析の基本であり、大規模なデータセットから有用な情報を抽出するのに役立つが、一般的に計算コストが高いという欠点があります。
Aprioriアルゴリズムをmlxtendを利用して実装します。最小サポートを0.02に設定します。
# apriori モジュールの読み込み
from mlxtend.frequent_patterns import apriori
# 共起性の高いアイテムセットの抽出
frequent_itemsets = apriori(df, min_support=0.02, use_colnames=True)
frequent_itemsets.head()
support | itemsets | |
---|---|---|
0 | 0.033452 | (UHT-milk) |
1 | 0.052466 | (beef) |
2 | 0.033249 | (berries) |
3 | 0.026029 | (beverages) |
4 | 0.080529 | (bottled beer) |
Aprioriアルゴリズムで抽出されたアイテムセットから、アソシエーションルールを満たすアイテムセットの組み合わせをフィルタリングします。
ここでは、confidenceが0.02以上のアイテムセットを抽出します。
from mlxtend.frequent_patterns import association_rules
rules_df = association_rules(
frequent_itemsets,
metric="confidence",
min_threshold=0.02,
)
# 利用するカラム名の設定
columns = [
"antecedents",
"consequents",
"support",
"confidence",
"lift",
]
# support, confidence, lift の順番に降順でソート
rules_df.sort_values(by=["support", "confidence", "lift"], ascending=False)[columns].reset_index(drop=True).round(
4
).head()
antecedents | consequents | support | confidence | lift | |
---|---|---|---|---|---|
0 | (other vegetables) | (whole milk) | 0.0748 | 0.3868 | 1.5136 |
1 | (whole milk) | (other vegetables) | 0.0748 | 0.2929 | 1.5136 |
2 | (rolls/buns) | (whole milk) | 0.0566 | 0.3079 | 1.2050 |
3 | (whole milk) | (rolls/buns) | 0.0566 | 0.2216 | 1.2050 |
4 | (yogurt) | (whole milk) | 0.0560 | 0.4016 | 1.5717 |
FP-Growthアルゴリズム
FP-Growthアルゴリズム(Frequent Pattern Growth)は、アソシエーション分析で用いられる高効率なアルゴリズムで、大規模なデータセットでの高速処理が特徴です。
このアルゴリズムはAprioriアルゴリズムのように候補となるアイテムセット生成プロセスを経ずにし、FPツリー(Frequent Pattern Tree)という特殊な木構造を利用してデータを圧縮し、頻繁に購入されるアイテムセットを効率的に抽出します。
FPツリーはアイテムセットの共通部分を共有し、条件付きパターンベースを用いて新たなFPツリーを生成し、再帰的に頻繁なアイテムセットを構築します。
FP-Growthアルゴリズムは大規模なデータセットにおける頻繁なアイテムセットの抽出を簡易にし、一般的に、Aprioriアルゴリズムに比べて大規模なデータセットの分析に優位性があると言われています。
from mlxtend.frequent_patterns import fpgrowth
frequent_itemsets = fpgrowth(df, min_support=0.02, use_colnames=True)
frequent_itemsets.head()
support | itemsets | |
---|---|---|
0 | 0.082766 | (citrus fruit) |
1 | 0.058566 | (margarine) |
2 | 0.139502 | (yogurt) |
3 | 0.104931 | (tropical fruit) |
4 | 0.058058 | (coffee) |
Aprioriアルゴリズムと同様に、FP-Growthアルゴリズムで抽出されたアイテムセットから、アソシエーションルールを満たすアイテムセットの組み合わせをフィルタリングします。
ここでも、confidenceが0.02以上のアイテムセットを抽出します。
from mlxtend.frequent_patterns import association_rules
rules_df = association_rules(
frequent_itemsets,
metric="confidence",
min_threshold=0.02,
)
# 利用するカラム名の設定
columns = [
"antecedents",
"consequents",
"support",
"confidence",
"lift",
]
# support, confidence, lift の順番に降順でソートして小数点4桁で表示
rules_df.sort_values(by=["support", "confidence", "lift"], ascending=False)[columns].reset_index(drop=True).round(
4
).head()
antecedents | consequents | support | confidence | lift | |
---|---|---|---|---|---|
0 | (other vegetables) | (whole milk) | 0.0748 | 0.3868 | 1.5136 |
1 | (whole milk) | (other vegetables) | 0.0748 | 0.2929 | 1.5136 |
2 | (rolls/buns) | (whole milk) | 0.0566 | 0.3079 | 1.2050 |
3 | (whole milk) | (rolls/buns) | 0.0566 | 0.2216 | 1.2050 |
4 | (yogurt) | (whole milk) | 0.0560 | 0.4016 | 1.5717 |
結論
アソシエーション分析は、トランザクションデータからアイテム間の関連を見つけるためのデータマイニング技術です。
この記事では、この分野でよく使われるAprioriアルゴリズムとFP-Growthアルゴリズムを、Pythonのmlxtend(Machine Learning Extensions)ライブラリを用いて実装しました。