過去に
Mezzanine の
他のレコメンド方式での
以下、
前提
OS 、
OS
% sw_vers ProductName: Mac OS X ProductVersion: 10.13.2 BuildVersion: 17C88
Python
% python3 -V Python 3.6.2
Python3 の アソシエーション分析の ライブラリに ついて
検索した
2つ
pyfpgrowth
Python3でバスケット分析 - Qiita Orange3-Associate
Python でアソシエーション分析 - Orange3-Associate - なんとなくな Developer の メモ
分析に 使用する データと、 分析して 得たい 結果に ついて
分析に 使用する データ
以下の
* ga:dimension8
GA の
* Page
閲覧ページの
* Date
閲覧した
* Pageviews
閲覧回数
分析して 得たい 結果に ついて
基本的には、
協調フィルタリングでも
閲覧した
pyfpgrowth を 使って、 アソシエーション分析(バスケット分析を 行う)
必要な ライブラリの インストール
pandas
、pyfpgrowth
を
!python3 -m pip install pandas
!python3 -m pip install pyfpgrowth
TSV ファイルを 読み込む
TSVファイルを
import pandas as pd df = pd.read_table("GA_dataset.tsv") # client_id と日付の昇順でソート df = df.sort_values(by=["ga:dimension8", "Date"], ascending=True) # データ件数が多く後続の計算に時間を擁するのでデータを減らす df = df[1:3000]
pyfpgrowth の input の 形式に 変換
pyfpgrowth は、
transactions = [] strs = [] previous_client_id = "" for idx in df.index: elems = df.ix[idx] client_id = elems["ga:dimension8"] if(previous_client_id != ""): if (previous_client_id != client_id): transactions.append(strs) strs = [] strs.extend(((elems["Page"] + ",") * elems["Pageviews"]).rstrip(',').split(',')) else: strs.extend(((elems["Page"] + ",") * elems["Pageviews"]).rstrip(',').split(',')) else: strs.extend(((elems["Page"] + ",") * elems["Pageviews"]).rstrip(',').split(',')) previous_client_id = client_id if strs: transactions.append(strs)
pyfpgrowth で アソシエーション分析を 実行
import pyfpgrowth patterns = pyfpgrowth.find_frequent_patterns(transactions, 3) rules = pyfpgrowth.generate_association_rules(patterns, 0.7)
parttens には、
辞書が
patterns
{('/blog/django-templatesyntaxerror-xxx-is-not-a-registered-tag-library-must-be-one-of/',): 3, ('/blog/django-templatesyntaxerror-xxx-is-not-a-registered-tag-library-must-be-one-of/', ('/blog/Block-multiple-requests-of-Wicket-Ajax/', '/blog/category/wicket/', '/blog/category/wicket/', '/blog/category/wicket/?page=2', '/blog/invisible-markup-on-wicket/', '/blog/using-resource-on-wicket-application/', '/blog/wicket-javalangruntimeexception-could-not-deserialize-object-from-byte-エラーについて考える/'): (('/blog/apache-wicketでrestapiを使う/', '/blog/search-resutls-wicket-models/', '/blog/wicket-about-wicketheader-items/'), 1.0), ('/blog/Block-multiple-requests-of-Wicket-Ajax/', '/blog/apache-wicketでrestapiを使う/', '/blog/category/wicket/', '/blog/category/wicket/?page=2', '/blog/search-resutls-wicket-models/', '/blog/wicket-javalangruntimeexception-could-not-deserialize-object-from-byte-エラーについて考える/'): (('/blog/wicket-about-wicketheader-items/',), 5.0), ...}
rules
キーと
rules.get(('/blog/Block-multiple-requests-of-Wicket-Ajax/', '/blog/apache-wicketでrestapiを使う/','/blog/category/wicket/?page=2', '/blog/category/wicket/?page=2', '/blog/category/wicket/?page=2', '/blog/wicket-scriptタグ-を-body-閉じタグの直前に出力する/'))
(('/blog/category/wicket/', '/blog/search-resutls-wicket-models/', '/blog/wicket-about-wicketheader-items/', '/blog/wicket-javalangruntimeexception-could-not-deserialize-object-from-byte-エラーについて考える/'), 1.0)
試してみた 感想
PageView 数を
商品購入数的な 扱いにはしない ほうが いい 感じになるかもしれない。
1つのページの PageView数が 多い (1ページを 何回も 見て、 他の ページも 少し見る)と いう client_id が 多数なので、 PageView数を 商品購入数的な 扱いに すると、 いい 感じの 結果に ならない 気が しました。 回数が 多くても 1回と するか、 多少係数で 補正した ほうが、 いいのかもしれません。 1つの
ページだけ 閲覧した client_idは 除外する
1ページだけを見て 離脱する client_idが ほとんどなので、 1ページだけの client_id は 除外して おくのが、 計算に 時間も かからずよさそうです。 そも
そも 上手く いっているのか、 わからない
確率が、1.0を 超えるので、 ちょっとうまく いっているのか わかりませんでした。
閲覧数は、1より 大きくなるので、 その 影響でしょうか?
orange3-associate を 使って アソシエーション分析を 行う
必要な ライブラリの インストール
orange3-associate
を
!python3 -m pip install orange3 !python3 -m pip install orange3-associate
csvファイルの 作成
basket
と
lines = [] line = "" previous_client_id = "" for idx in df.index: elems = df.ix[idx] client_id = elems["ga:dimension8"] if(previous_client_id != ""): if (previous_client_id != client_id): lines.append(line) line = "" line = line + ((elems["Page"] + ",") * elems["Pageviews"]).rstrip(',') else: line = line + ((elems["Page"] + ",") * elems["Pageviews"]).rstrip(',') else: line = line + ((elems["Page"] + ",") * elems["Pageviews"]).rstrip(',') previous_client_id = client_id if (line != ""): lines.append(line)
file = open("dataset.basket","w") for line in lines: file.write(line + "\n")
orange3-associate で アソシエーション分析を 実行
import Orange from orangecontrib.associate.fpgrowth import * tbl = Orange.data.Table('dataset.basket')
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-58-2ce7644e98cc> in <module>() 1 import Orange 2 from orangecontrib.associate.fpgrowth import * ----> 3 tbl = Orange.data.Table('dataset.basket') ~/Library/Python/3.6/lib/python/site-packages/Orange/data/table.py in __new__(cls, *args, **kwargs) 212 return cls.from_url(args[0], **kwargs) 213 else: --> 214 return cls.from_file(args[0]) 215 elif isinstance(args[0], Table): 216 return cls.from_table(args[0].domain, args[0]) ~/Library/Python/3.6/lib/python/site-packages/Orange/data/table.py in from_file(cls, filename, sheet) 606 reader = FileFormat.get_reader(absolute_filename) 607 reader.select_sheet(sheet) --> 608 data = reader.read() 609 610 # Readers return plain table. Make sure to cast it to appropriate ~/Library/Python/3.6/lib/python/site-packages/Orange/data/io.py in read(self) 899 900 X, Y, metas, attr_indices, class_indices, meta_indices = \ --> 901 _io.sparse_read_float(self.filename.encode(sys.getdefaultencoding())) 902 903 attrs = constr_vars(attr_indices) Orange/data/_io.pyx in Orange.data._io.sparse_read_float() ValueError: b'dataset.basket':17:21: invalid value
basket
の
対象行の
/translate_c?depth=1&hl=en&prev=search&rurl=translate.google.com&sl=ja&sp=nmt4&u=https://www.monotalk.xyz/blog/google-apps-script-で電子署名をする/&usg=ALkJrhhsVbHe1cSGBqRWm5dF2OEu936DqQ
=
、?
、&
等がlines = [] line = "" previous_client_id = "" for idx in df.index: elems = df.ix[idx] client_id = elems["ga:dimension8"] if(previous_client_id != ""): if (previous_client_id != client_id): lines.append(line.rstrip(',').replace("?","").replace("=","").replace("&","")) line = "" line = line + ((elems["Page"] + ",") * elems["Pageviews"]) else: line = line + ((elems["Page"] + ",") * elems["Pageviews"]) else: line = line + ((elems["Page"] + ",") * elems["Pageviews"]) previous_client_id = client_id if (line != ""): lines.append(line.rstrip(',').replace("?","").replace("=","").replace("&",""))
file = open("dataset.basket","w") for line in lines: file.write(line + "\n")
import Orange from orangecontrib.associate.fpgrowth import * tbl = Orange.data.Table('dataset.basket')
今度は
ルール と、 信頼度の 計算、 出力
実装は、
と
def decode_onehot(d): items = OneHot.decode(d, tbl, mapping) # ContinuousVariable の name 値を取得 return list(map(lambda v: v[1].name, items)) X, mapping = OneHot.encode(tbl) # 2 は組み合わせごとの発生回数、2件以上のものを取得する itemsets = dict(frequent_itemsets(X, 2)) # アソシエーションルールの抽出 # 信頼度(確信度)が0.7以上のものを抽出する rules = association_rules(itemsets, 0.7) for P, Q, support, confidence in rules: lhs = decode_onehot(P) rhs = decode_onehot(Q) print(f"lhs = {lhs}, rhs = {rhs}, support = {support}, confidence = {confidence}")
lhs = ['/blog/Default-PMD-Rules-In-Github-Repositories/', '/blog/Default-FindBugs-Rules-In-Github-Repositories/'], rhs = ['/blog/lombokのfindbugs警告を抑制する/'], support = 2, confidence = 1.0 lhs = ['/blog/lombokのfindbugs警告を抑制する/', '/blog/Default-PMD-Rules-In-Github-Repositories/'], rhs = ['/blog/Default-FindBugs-Rules-In-Github-Repositories/'], support = 2, confidence = 1.0 lhs = ['/blog/python-folinum-を使い都道府県の夫婦年齢差をプロットする/', '/blog/python-folium-指定できる-地図の-タイル-について/'], rhs = ['/blog/python-folium-で都内の公園にまつわる情報を地図上に描画する/'], support = 3, confidence = 1.0 .... lhs = ['/blog/category/wicket/'], rhs = ['/blog/apache-wicketでrestapiを使う/'], support = 2, confidence = 1.0
LHS、 RHS に ついて
LHS、
LHS は
RHS は
アソシエーション分析(1) に
発生回数ではなく、 確率で 計算する
def decode_onehot(d): items = OneHot.decode(d, tbl, mapping) # ContinuousVariable の name 値を取得 return list(map(lambda v: v[1].name, items)) X, mapping = OneHot.encode(tbl) # .001 は組み合わせごとの発生確率、0.1%以上のものを取得する # 結果が出力されるのが、0.1%以下でのみ出力されました。 itemsets = dict(frequent_itemsets(X, .001)) # アソシエーションルールの抽出 # 信頼度(確信度)が0.7以上のものを抽出する rules = association_rules(itemsets, 0.7) for P, Q, support, confidence in rules: lhs = decode_onehot(P) rhs = decode_onehot(Q) print(f"lhs = {lhs}, rhs = {rhs}, support = {support}, confidence = {confidence}")
lhs = ['/blog/python-folinum-を使い都道府県の夫婦年齢差をプロットする/', '/blog/python-folium-指定できる-地図の-タイル-について/'], rhs = ['/blog/python-folium-で都内の公園にまつわる情報を地図上に描画する/'], support = 3, confidence = 1.0 lhs = ['/blog/htmlcompressor-maven-plugin-を使ってhtml-を圧縮する/'], rhs = ['/blog/jar-ファイル作成時にminify-maven-plugin-を使ってcssjavascript-を圧縮結合する/'], support = 3, confidence = 0.75 lhs = ['/blog/django-templatesyntaxerror-xxx-is-not-a-registered-tag-library-must-be-one-of/'], rhs = ['/blog/get-single-result-on-django-model-filter/'], support = 3, confidence = 1.0
リフト値 の 計算
stats = rules_stats(rules, itemsets, len(X)) for s in sorted(stats, key = lambda x: x[6], reverse = True): lhs = decode_onehot(s[0]) rhs = decode_onehot(s[1]) support = s[2] confidence = s[3] lift = s[6] print(f"lhs = {lhs}, rhs = {rhs}, support = {support}, confidence = {confidence}, lift = {lift}")
リフト値の
pyfpgrowth と、 orange-associate3 の 比較
それぞれ
データ量が
多いと パフォーマンスが 結構つらい
20000万レコード以上で処理した 際、 結果が pyfpgrowth
はjupyter上での 応答が なくなり、 処理対象の データ件数を 3000件に しました。
それなりに長い 間使っている MACですが、 データを もっと 絞るなりしないと、 実際には 使えないかもしれません。 処理結果は、
orange-associate3 の ほうが それらしい 結果が 出ている
処理結果を見る 限りは、 orange-associate3
のほうが それらしい 結果が 出ていました。
処理結果から推測すると、 以下のような 動作を していそうに 思いました。 orange-associate3
は、商品の 重複カウントは、 考慮していないが、 pyfpgrowth
は重複カウントを 考慮していそう。 orange-associate3
は、商品の 順序性を 考慮していないが、 pyfpgrowth
は商品の 順序性を 考慮していそう。
再度分析する 際の データ加工に ついて
再度実施する
- PageView数ではなく、
ユーザー数、 セッション数を カウントする。 - 2ページ以上の
閲覧が ない ユーザーは 除外する。 - Page閲覧時の
滞在時間が 長い ユーザーのみ 抽出する。
以上です。
コメント