読者です 読者をやめる 読者になる 読者になる

データサイエンティスト(仮)

元素粒子理論屋。いっぱしのデータ分析屋になるために修行中です。

アソシエーション分析のちょい復習

R アソシエーション分析

動機

最近こんな本を購入しました。
www.shoeisha.co.jp
第I部の「データアナリティクスの基礎」は、データ分析のプロジェクトの全体像に触れていて、データ分析のプロジェクトをどう進めていくかについて整理するのには良いのかな、と思いました*1。後半部分の分析内容は結構あっさりしていて、すでに専門的なスキルを持っている方向けではなさそうです。

この書籍の第8章で、Rを使ったアソシエーション分析を取り扱っています。これは、マーケティングの分野でよく使用される手法です。顧客の購買履歴から、「商品Aを買った人は商品Bを買う確率が高い」といった関係性を見つけ出します。自分は、業務では予測分析がメインなので、そういえば久しくこういった手法に触れてなかったな…と感じました。というわけで、復習の一環で簡単にデモしてみようと思います*2

アソシエーション分析の説明は、例えば以下のサイトが参考になるかと思います。
sinhrks.hatenablog.com
sinhrks.hatenablog.com
実務では、今回使用するサンプルデータのような形式に簡単にまとめられない場合があるので、前処理も含めた解説は非常にためになります。

本当はPythonでやろうと思ったけど、自分の環境ではうまく構築できなかった

Pythonでは、Orangeというものでアソシエーション分析が可能ということで、pipでOrange(何も指定しないとVer. 2.7.8)をインストールしようとしました。しかし、setup.pyのビルドでコケてしまい、結局解消せず*3。大本のページ(Orange Data Mining)を見ると、最新版はVer. 3.3.7とのこと。pip経由だと、Orange3と指定するとインストールできそうなので実行。こちらはインストールに成功しましたが、アソシエーション分析に必要なメソッドが無い。OrangeでPythonからAprioriを動かす - Qiitaを見ると、どうやら本当に無い模様。

というわけで、いったんPythonで進めるのは保留にして、Rでデモをやるだけにします。

Rの{arules} + {arulesVIz}を使って分析

Rでアソシエーション分析を行う場合、例えば{arules}というパッケージがあります。今回は、このパッケージを使用していきます。

{arules}のインストール

if(!require(arules)){
  install.packages("arules", dependencies = TRUE)
}
require(arules)

データセットの読み込み

店舗の30日間のPOSデータを使用します。9835トランザクション、196カテゴリからなるデータで、「milk(牛乳)」、「root vegitables(根菜類)」といった商品の併売情報が入っています。

data(Groceries)
Groceries

# 出力
transactions in sparse format with
 9835 transactions (rows) and
 169 items (columns)

先頭の数行を確認してみます。併売情報を出力するのに、inspect()を用います。

inspect(head(Groceries))

# 出力
  items                     
1 {citrus fruit,            
   semi-finished bread,     
   margarine,               
   ready soups}             
2 {tropical fruit,          
   yogurt,                  
   coffee}                  
3 {whole milk}              
4 {pip fruit,               
   yogurt,                  
   cream cheese ,           
   meat spreads}            
5 {other vegetables,        
   whole milk,              
   condensed milk,          
   long life bakery product}
6 {whole milk,              
   butter,                  
   yogurt,                  
   rice,                    
   abrasive cleaner}

このようなデータを作ったり、アクセスしたりするのは一筋縄ではいかないのですが、ここではうまくトランザクションデータが手に入っているという状況で先に進みます。

summaryで基本統計量の確認

summary(Groceries)

# 出力
transactions as itemMatrix in sparse format with
 9835 rows (elements/itemsets/transactions) and
 169 columns (items) and a density of 0.02609146 

most frequent items:
      whole milk other vegetables       rolls/buns             soda           yogurt 
            2513             1903             1809             1715             1372 
         (Other) 
           34055 

element (itemset/transaction) length distribution:
sizes
   1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17 
2159 1643 1299 1005  855  645  545  438  350  246  182  117   78   77   55   46   29 
  18   19   20   21   22   23   24   26   27   28   29   32 
  14   14    9   11    4    6    1    1    1    1    3    1 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   2.000   3.000   4.409   6.000  32.000 

includes extended item information - examples:
       labels  level2           level1
1 frankfurter sausage meat and sausage
2     sausage sausage meat and sausage
3  liver loaf sausage meat and sausage

出力結果の意味は以下です。

  • most frequent items : 頻度(=商品が売れた数)
  • element (itemset/transaction) length distribution : 1トランザクションで購入した商品の数
  • Min, 1st Qu., Median, Mean, 3rd Qu., Max : 最小値、第一四分位数、中央値、平均値、第三四分位数、最大値

商品ごとのヒストグラム

先程の要約だと、商品ごとの頻度の情報が十分ではありませんでした。ここでは、購入された商品に対して相対出現頻度によるヒストグラムを出力します。これは、itemFrequencyPlot()で実行できます。全商品を可視化すると複雑になるので、引数のsupportで支持度4%以上の商品のみに限定しています。

# 目盛りを無駄にメイリオに設定
quartzFonts(Hir = c("HiraMaruPro-W4", "HiraMaruProN-W4", 
                    "Meiryo", "HiraMinProN-W3"))
itemFrequencyPlot(Groceries, support = 0.04, cex.names = 0.7, 
                  col = "red", family = "Hir", font = 3)

f:id:tekenuko:20160924005258p:plain

アソシエーション分析の実行

apriori()で実行できます。support(支持度)とconfidence(確信度)は、それぞれ手で0.5%以上、1%以上をしきい値としました。

rules <- apriori(Groceries, parameter = list(support = 0.005, confidence = 0.01))

# 表示結果
Apriori

Parameter specification:
 confidence minval smax arem  aval originalSupport support minlen maxlen target   ext
       0.01    0.1    1 none FALSE            TRUE   0.005      1     10  rules FALSE

Algorithmic control:
 filter tree heap memopt load sort verbose
    0.1 TRUE TRUE  FALSE TRUE    2    TRUE

Absolute minimum support count: 49 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[169 item(s), 9835 transaction(s)] done [0.00s].
sorting and recoding items ... [120 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 done [0.00s].
writing ... [2138 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].

この条件だと併売に関する2138個のルールが出来上がります。

特定の商品に注目した場合

アソシエーションのルールは、「X ならば Y」という形になっています。作成したルールを用いて、Yを固定したときにXがどのようなものがあるかを確認してみます。

# Y = "onions"(たまねぎ)にしてみる
onionRules <- subset(rules, subset = rhs %in% "onions")
# リフト値が高い順に表示
inspect(sort(onionRules, by = "lift"))

# 出力
     lhs                                   rhs      support     confidence lift     
1304 {root vegetables,other vegetables} => {onions} 0.005693950 0.12017167 3.8750440
1307 {other vegetables,whole milk}      => {onions} 0.006609049 0.08831522 2.8478038
316  {root vegetables}                  => {onions} 0.009456024 0.08675373 2.7974523
324  {other vegetables}                 => {onions} 0.014234875 0.07356805 2.3722681
308  {whipped/sour cream}               => {onions} 0.005083884 0.07092199 2.2869434
310  {citrus fruit}                     => {onions} 0.005592272 0.06756757 2.1787771
314  {tropical fruit}                   => {onions} 0.005693950 0.05426357 1.7497776
312  {bottled water}                    => {onions} 0.005897306 0.05335787 1.7205725
320  {yogurt}                           => {onions} 0.007219115 0.05174927 1.6687019
326  {whole milk}                       => {onions} 0.012099644 0.04735376 1.5269647
322  {rolls/buns}                       => {onions} 0.006812405 0.03703704 1.1942927
47   {}                                 => {onions} 0.031011693 0.03101169 1.0000000
318  {soda}                             => {onions} 0.005287239 0.03032070 0.9777183

これより「root vegetables(根菜類), other vegetables(他の野菜)」やroot vegetables(根菜類), whole vegetables(牛乳)」といった条件がonions(たまねぎ)に結びつくものがリフト値が高い(単に玉ねぎを買うよりよりもXを買ってさらにYを買う確率が高い)ことがわかります。

結果の可視化

{arulesViz}のインストール

if(!require(arulesViz)){
  install.packages("arulesViz", dependencies = TRUE)
}
require(arulesViz)

(図が単純になるように)ルールを再設定

rules <- apriori(Groceries, parameter = list(support = 0.004, 
                                             confidence = 0.5))

以下では、有用そうな図をいくつか紹介していきます。

散布図

散布図は、method = "scatterplot"で指定します。

graphics::plot(rules, method = "scatterplot")

f:id:tekenuko:20160924011554p:plain

横軸が支持度、縦軸が確信度で、点の色が赤に近いほどリフト値が高くなっています。これより、例えばリフト値が高い領域がわかり、パラメータ設定の参考にすることができます。

バブルチャート

method = "grouped"と指定することで、XとYの関係を直観的(丸の大きさと色)で表現できます。

graphics::plot(rules, method = "grouped")

f:id:tekenuko:20160924011607p:plain

横軸がアソシエーションの左辺(X)、縦軸が右辺(Y)、支持度の大きさは丸のサイズ、リフト値の大きさは色の濃さで表されています。

アソシエーショングラフ

有向グラフ形式(矢印で関係が表現されているグラフ)で表現することも可能です。表示を簡単にするため、リフト値の上位10項目のみに着目します。method = "graph"で有向グラフを表現できます。

# リフト値の高い順に10項目
rules_high_lift <- head(sort(rules, by = "lift"), 10)
graphics::plot(rules_high_lift, method = "graph", control = list(type = "items"))

f:id:tekenuko:20160924012017p:plain

herbsを購入した人は、root vegetablesを購入する可能性が高い(?)などが表示されます。
実はWindowとMacでこの表示が異なるそうで、Windowsはマウス操作で移動ができるそうです。

おわりに

アソシエーション分析を用いると、複雑なトランザクションデータだけを見ている場合にはわからなかったインサイトが得られる可能性があります。しかしながら、ルールがわかってもそれがビジネス戦略とあっているか*4はまた別の問題です*5。アソシエーション分析で課題解決を図る際は、きちんと目的を明確にし、分析が目的に沿っているかを精査しておく必要があります。自分が業務で携わるときは、このあたりを肝に命じて進めていこうと思います。

*1:一応自分はコンサルタントなので、たびたび全体感を整理しとくのは業務でも有用かなと思っています。

*2:というわけで非常にクオリティが低い内容です。。

*3:自分はPython3.5を使っているのが原因の一つっぽいですが、setup.pyをPython3に合うように書き換えても成功には至らず。。

*4:売上を上げたいなど。

*5:ルールが利益に結びつかない可能性があります。