ラベル choosec の投稿を表示しています。 すべての投稿を表示
ラベル choosec の投稿を表示しています。 すべての投稿を表示

カテゴリ数が少ない場合の頻出アイテムセットの単純集計をする話

僕は医薬系でSASを使ってきましたが、そんな僕でも流石にビールと紙おむつの話ぐらいは聞いたことがあります。

スーパーの買い物データを、Aの商品とBの商品を一緒に購入している客数という見方で分析したところ、ビールと紙おむつを一緒に買っている人の数が多かったので、売り場の位置を隣にしたら売上が上がって良かったね!バスケット分析だね、データマイニングの始まりだねって例の話です。
なんか事実かどうかは諸説あったりするらしいので興味のある方は調べてみて下さい。

今回はそういう同時に購入されている商品ペア(アイテムセット)の単純カウントの話です。

以前、メールで質問があって、

「いつも見てます。すみませんけど、売上データから、2つの商品を同時購入している件数を出したいです。支持度とか信頼度とかの計算はいいので、とりあえず件数を降順に出せればいいです。対象商品の種類は多くて15種類程度です。当方、SQLはわからないので使わないでください。IMLもありません。商品マスタは使わずに、売上データだけで、かつデータが更新されてもコードを変える必要がないものにしてください」(割と原文ママ)

という内容でした。

ちょっとその時期、時間が無かったので、一応ぱっと思いついたものを書きました。

まずは以下の感じで、売上データを作りました。

data Q1;
call streaminit(1234);
do NAME='A','B','C','D','E','F';
 do i=1 to rand('table',0.1,0.1,0.3,0.3);
   ITEM=choosec(rand('table',0.2,0.2,0.2,0.2),'オムツ','ビール','パン','牛乳','りんご');
   output;
 end;
end;
drop i;
run;
proc sort data=Q1 nodupkey;
 by NAME ITEM;
run; 






















NAMEは顧客IDでITEMが購入商品です。
Aさんが買ったのは、[りんご]と[ビール]と[牛乳]です。
2つで1カウントのアイテムセットで考えると
[りんご,ビール] [りんご,牛乳] [ビール,牛乳]の3つになります。

さてこの顧客データから、一番多いアイテムセットは何でしょう?という問題です。

proc transpose data=Q1 out=Q2 prefix=I_;
 var ITEM;
 by NAME;
run;

data Q3;
 set Q2;
   array AI{*} $ I_:;
   do i=1 to comb(dim(AI),2);
      call allcomb(i, 2, of AI(*));
 if I_1^='' and I_2^='' then output;
   end;
 keep NAME I_1 I_2;
run;

proc freq data=Q3 noprint;
 tables I_1*I_2/out=A1(drop=PERCENT);
run;

proc sort data=A1;
 by descending COUNT;
run;


で結果は

















となって、良し、ビールと牛乳並べて売っとくか!ってことです。(データも結果の解釈も適当なのでそこは当てにしないでください)

この方法はとりあえず転置して、購入商品の情報を横持ちにしてから
call allcombで組み合わせを作成しています。

「n個の変数からm個の変数を選択する、組み合わせのデータセットを作成する方法」
http://sas-tumesas.blogspot.jp/2014/01/m.html

を参考にしてください。

しかし、このコードの最大の欠陥はcall allcombが引数を20個までしか持てないということです。
つまり売り上げデータにおいて商品の種類が20種類以内じゃないとダメなわけです。
ちょっと実践的じゃないですね。

で後日、調べてみると色んなプログラムが既に発表されていました。

2011年のSASユーザー総会で「SASによる頻出アイテムセットの抽出」とタイトルものが発表されていて、まさしくばっちりな内容でした。
著者の谷岡田出男さんが代表取締役をされているデータマインテック株式会社のWebページに
プログラム全てが公開されていました。
http://www.dataminetech.co.jp/soft/disallow/SAS_PGM20110613.sas

自分で書く前に調べとけばよかったです。

ちなみについ最近から、1982年から2013年までの全ての論文がPDF形式で閲覧可能になりました。
SASのプレミアムラウンジにユーザー登録して、「ダウンロード」タブから「SASユーザー総会資料」を選択すれば、なんと全部無料で見放題です。
素晴らしいですね。








CHOOSEN(C)関数とCALL SORTN(C)ルーチンで、横に可変的に増える変数をソートして指定した順番にくる変数を取得する

固定されたデータが手元に完全にある状態で、プログラムを書けることは幸せです。
実際のデータをみて取りうる範囲から固定した処理がかけるからです。

しかし往々にして、手元にこないと実際どんなデータかわからない、あるいは見てはいけない、という状況で、どんなデータが来ても可変的に対応できるプログラムを書くこともあると思います。

今

data Q1;
X_1='か';X_2='あ';X_3='さ';X_4='た';output;
X_1='B';X_2='C';X_3='A';X_4='Z';output;
run;






こんなデータがあったとします。

そして非常識なことに、なぜか、このデータは横にも増える可能性があるとします。
今プログラムを組んでも、次に実行する時のデータにはX_5やX_6があるかもしれません。

そして、求められている処理は、1行ごとに、横に辞書的に並び替えて、一番後ろから2番目に並ぶ
値を特定するという、これまたよくわからない処理だとします。

縦に増えていくデータは余裕ですが、横に変数が増えていくデータはやっかいです。
ありえないと思っていても、なぜか現実にそういうデータに当たることもあるものです。

転置して縦にしてから、降順ソートして、2番をとるような処理でもいいですが、できれば構造を
変えず単純にできないかと考え、以下のコードを考えました。


data A1;
set Q1;
call sortc(of X_:);
A=choosec(-2,of X_:);
run;






一行目は「さ」がお尻から2番目の文字で、2行目はCです。

横にいくら変数が追加されてもX_という接頭語が崩れない限りはX_100までいこうが動きます。
仮に変数がX_1の一つだけでもAがnullになるだけで、エラーにはなりません。

call sortcルーチンで変数間で値をソート順に交換させて、からchoosec関数で、指定した順番にいる変数を取得します。-2とすることで、右端から2個目(つまりソートされた最大の値の一つ前)をとることができます。

たった4行で書けたので自分的には大満足です。

もし対象が数値型なら

call sortcはcall sortn
choosec はchoosenに変えてください。


/*追記*/
てか、数値型なら、そもそも smallest関数とlargest関数で、何番目の最小値、最大値とれるからソートの必要ないですね、すみません。