こちらのWebサーバーログのデータセットに対して、非常にわかりやすい分析をされている方がいらっしゃいました。
こちらです。他のノートブックも興味深いので後で動かしてみます。
Databricksで動かすので一部修正して、翻訳しながらウォークスルーします。
データの取得
はじめに詰まったのがここでした。KaggleのデータセットをどのようにDatabricksに持ってくれば…。以前はデータのサイズが小さかったので、ダウンロード→ボリュームへのアップロードで済みました。しかし、今回は数GBのログということもあり、以前のアプローチですとなかなかボリュームにアップロードできず。
ということで、Kaggle Public APIを使わせていただきました。
使う際には、事前にKaggleのサイトでトークンを作成しておく必要があります。トークを作成するとkaggle.json
がダウンロードされます。こちらは後で使います。
Databricksクラスターを起動して以下を実行していきます。
%pip install kaggle
dbutils.library.restartPython()
Webターミナルを開きます。以下を実行します。
mkdir /root/.config/kaggle
vi /root/.config/kaggle/kaggle.json
エディタが開くので、ダウンロードされたkaggle.json
の中身を貼り付けて保存します。ターミナルは閉じてしまっても構いません。
ノートブックで以下を実行します。
import kaggle
kaggle.api.authenticate()
認証が通ったら、以下を実行してボリュームにデータをダウンロードして解凍します。
kaggle.api.dataset_download_files('eliasdabbas/web-server-access-logs', path='/Volumes/users/takaaki_yayoi/data', unzip=True)
Webサーバーログの分析
import numpy as np # 線形代数
import pandas as pd # データ処理、CSVファイルのI/O(例: pd.read_csv)
# 以下を実行することでdataディレクトリの全ファイルを表示します
import os
for dirname, _, filenames in os.walk('/Volumes/users/takaaki_yayoi/data'):
for filename in filenames:
print(os.path.join(dirname, filename))
/Volumes/users/takaaki_yayoi/data/access.log
/Volumes/users/takaaki_yayoi/data/client_hostname.csv
データの前処理
このログファイルは、ユーザーのインタラクション、クローラーのアクティビティ、ビジネストレンドに関する包括的なビューを提供する、イランのeコマースであるzanbil.irから抽出された3.3GBのウェブサーバーのログから構成されています。このログファイルは、2019年にZakerとFarzinによってコンパイルされ、研究、分析目的としてHarvard Dataverse経由で利用できます。
データフレームにログファイルをロード
それぞれのログの行から、クライアントIP、ユーザーID、タイムスタンプ、HTTPメソッド、リクエスト、ステータスコード、サイズ、リファラ、ユーザーエージェントのような適切な情報を抽出しました。
import pandas as pd
import re
# ログファイルパスの定義
log_file_path = '/Volumes/users/takaaki_yayoi/data/access.log'
# ログの行から情報を抽出するための正規表現パターンの定義
regex_pattern = r'^(?P<client>\S+) \S+ (?P<userid>\S+) \[(?P<datetime>[\w:/]+\s[+\-]\d{4})\] "(?P<method>[A-Z]+) (?P<request>[^ "]+)? HTTP/[0-9.]+" (?P<status>[0-9]{3}) (?P<size>[0-9]+|-) "(?P<referer>[^"]*)" "(?P<user_agent>[^"]*)"'
# カラム名の定義
columns = ['client', 'userid', 'datetime', 'method', 'request', 'status', 'size', 'referer', 'user_agent']
# 正規表現パターンパッチングを用いて、ログファイルの最初の10000行をディクショナリーのリストに読み込み
log_data = []
with open(log_file_path, 'r') as file:
for i, line in enumerate(file):
if i >= 10000:
break
match = re.match(regex_pattern, line)
if match:
log_data.append({
'client': match.group('client'),
'userid': match.group('userid'),
'datetime': match.group('datetime'),
'method': match.group('method'),
'request': match.group('request'),
'status': match.group('status'),
'size': match.group('size'),
'referer': match.group('referer'),
'user_agent': match.group('user_agent')
})
else:
print("Error: Line does not match regex pattern:", line)
# ディクショナリーのリストからデータフレームを作成
logs_df = pd.DataFrame(log_data, columns=columns)
# データフレームの最初の5行を表示
logs_df.head()
データセットの理解と処理
# データフレームの概要をチェック
logs_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 client 10000 non-null object
1 userid 10000 non-null object
2 datetime 10000 non-null object
3 method 10000 non-null object
4 request 10000 non-null object
5 status 10000 non-null object
6 size 10000 non-null object
7 referer 10000 non-null object
8 user_agent 10000 non-null object
dtypes: object(9)
memory usage: 703.2+ KB
from datetime import datetime
import pytz
# datetimeをパースする関数 (クラスセッションの練習)
def parse_datetime(x):
'''
Parses datetime with timezone formatted as:
`[day/month/year:hour:minute:second zone]`
Example:
`>>> parse_datetime('13/Nov/2015:11:45:42 +0000')`
`datetime.datetime(2015, 11, 3, 11, 45, 4, tzinfo=<UTC>)`
Due to problems parsing the timezone (`%z`) with `datetime.strptime`, the
timezone will be obtained using the `pytz` library.
'''
try:
dt = datetime.strptime(x[1:-7], '%d/%b/%Y:%H:%M:%S')
dt_tz = int(x[-6:-3])*60+int(x[-3:-1])
return dt.replace(tzinfo=pytz.FixedOffset(dt_tz))
except ValueError:
return '-'
logs_df['status'] = logs_df['status'].astype(int)
logs_df['size'] = logs_df['size'].astype(int)
logs_df['datetime'] = logs_df['datetime'].apply(parse_datetime)
logs_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 client 10000 non-null object
1 userid 10000 non-null object
2 datetime 10000 non-null datetime64[ns, pytz.FixedOffset(33)]
3 method 10000 non-null object
4 request 10000 non-null object
5 status 10000 non-null int64
6 size 10000 non-null int64
7 referer 10000 non-null object
8 user_agent 10000 non-null object
dtypes: datetime64[ns, pytz.FixedOffset(33)](1), int64(2), object(6)
memory usage: 703.2+ KB
ユーザーIDカラムの削除
これは単なるハイフンのみだからです。
users = logs_df['userid'].unique()
print(users)
['-']
logs_df.drop(columns=['userid'], inplace=True)
重複の排除
分析に価値をもたらさない重複が存在します。
# データフレームの重複をカウント
duplicate_count = logs_df.duplicated().sum()
# 重複の数を表示
print("Number of duplicates:", duplicate_count)
Number of duplicates: 49
# 重複の排除
logs_df = logs_df.drop_duplicates()
処理データのサンプル
# データフレームの最初の5行を表示
logs_df.head()
プロンプトへの回答
Q1. サイトに頻繁に訪れるトップ10の人たち
最も頻繁に訪れるユーザーを取得するには、クライアントのIPアドレス(client)とユーザーエージェント(user_agent)でグルーピングすることによるユーザー特定が必要です。
# clientとuser_agentでグルーピングし、出現数をカウント、降順でソートします
frequent_visitors = logs_df.groupby(['client', 'user_agent']).size().reset_index(name='count').sort_values(by='count', ascending=False)
# トップ10の頻繁に訪問するユーザーを選択
top_10 = frequent_visitors.head(10)
index = 0
# トップ10を表示
for i, row in top_10.iterrows():
print(f"{index + 1}. Client: {row['client']}, User Agent: {row['user_agent']}, Count: {row['count']}\n")
index += 1
1. Client: 66.249.66.194, User Agent: Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html), Count: 778
2. Client: 66.249.66.91, User Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html), Count: 739
3. Client: 130.185.74.243, User Agent: Mozilla/5.0 (Windows NT 6.1; rv:42.0) Gecko/20100101 Firefox/42.0, Count: 660
4. Client: 66.249.66.194, User Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html), Count: 558
5. Client: 5.211.97.39, User Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.0 Mobile/14F89 Safari/602.1, Count: 474
6. Client: 207.46.13.136, User Agent: Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm), Count: 416
7. Client: 194.94.127.7, User Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36\x09Chrome 65.0, Count: 225
8. Client: 23.101.169.3, User Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; Trident/5.0), Count: 204
9. Client: 5.121.43.23, User Agent: Mozilla/5.0 (Linux; Android 7.0; FRD-L09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36, Count: 165
10. Client: 40.77.167.170, User Agent: Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm), Count: 164
Q2. それぞれのセッションごとのセッションとページビュー
基本的なセッション化(skip)****
# セッションを特定するためにclientとuser_agentでグルーピングし、セッションごとのページビューをカウント
sessions = logs_df.groupby(['client', 'user_agent'])
# セッション情報を格納するための空のリストを初期化
session_info = []
# セッションごとの繰り返し
for (client, user_agent), session_data in sessions:
# セッションのタイムスタンプとページビューの抽出
timestamps = session_data['datetime'].tolist()
pages = session_data['request'].tolist()
# タプルにセッション情報を格納
session_info.append((client, user_agent, timestamps, pages))
session_info[1:4]
[('104.194.24.33',
'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36',
[Timestamp('2019-01-02 03:57:00+0033', tz='pytz.FixedOffset(33)')],
['/amp-helper-frame.html?appId=a624a1c1-0c93-466a-a546-e146710f97e6&parentOrigin=https://www-zanbil-ir.cdn.ampproject.org']),
('104.194.24.54',
'Dalvik/2.1.0 (Linux; U; Android 6.0.1; SM-G900H Build/MMB29K)',
[Timestamp('2019-01-02 04:24:00+0033', tz='pytz.FixedOffset(33)'),
Timestamp('2019-01-02 04:26:04+0033', tz='pytz.FixedOffset(33)')],
['/image/33888?name=model-b2048u-1-.jpg&wh=200x200',
'/image/11947?name=11947-1-fw.jpg&wh=200x200']),
('104.194.25.207',
'Dalvik/2.1.0 (Linux; U; Android 5.0.2; P01V Build/LRX22G)',
[Timestamp('2019-01-02 04:06:04+0033', tz='pytz.FixedOffset(33)'),
Timestamp('2019-01-02 04:06:05+0033', tz='pytz.FixedOffset(33)'),
Timestamp('2019-01-02 04:06:05+0033', tz='pytz.FixedOffset(33)')],
['/image/33888?name=model-b2048u-1-.jpg&wh=200x200',
'/image/11947?name=11947-1-fw.jpg&wh=200x200',
'/image/11926?name=sm812aaa.jpg&wh=200x200'])]
# 最低でも5セッションをそのページビューと共に表示
for i, (client, user_agent, timestamps, pages) in enumerate(session_info[:5], start=1):
print(f"Session {i} - Client: {client}, User Agent: {user_agent}")
for timestamp, page in zip(timestamps, pages):
print(f" Timestamp: {timestamp}, Page: {page}")
print()
Session 1 - Client: 104.156.210.196, User Agent: Dalvik/2.1.0 (Linux; U; Android 8.0.0; SM-A720F Build/R16NW)
Timestamp: 2019-01-02 04:20:00+00:33, Page: /image/32768?name=24xs450-33.jpg&wh=200x200
Session 2 - Client: 104.194.24.33, User Agent: Mozilla/5.0 (Linux; Android 8.0.0; SM-G955F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36
Timestamp: 2019-01-02 03:57:00+00:33, Page: /amp-helper-frame.html?appId=a624a1c1-0c93-466a-a546-e146710f97e6&parentOrigin=https://www-zanbil-ir.cdn.ampproject.org
Session 3 - Client: 104.194.24.54, User Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; SM-G900H Build/MMB29K)
Timestamp: 2019-01-02 04:24:00+00:33, Page: /image/33888?name=model-b2048u-1-.jpg&wh=200x200
Timestamp: 2019-01-02 04:26:04+00:33, Page: /image/11947?name=11947-1-fw.jpg&wh=200x200
Session 4 - Client: 104.194.25.207, User Agent: Dalvik/2.1.0 (Linux; U; Android 5.0.2; P01V Build/LRX22G)
Timestamp: 2019-01-02 04:06:04+00:33, Page: /image/33888?name=model-b2048u-1-.jpg&wh=200x200
Timestamp: 2019-01-02 04:06:05+00:33, Page: /image/11947?name=11947-1-fw.jpg&wh=200x200
Timestamp: 2019-01-02 04:06:05+00:33, Page: /image/11926?name=sm812aaa.jpg&wh=200x200
Session 5 - Client: 104.248.138.218, User Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1
Timestamp: 2019-01-02 04:35:01+00:33, Page: /m/browse/sewing-machine/%DA%86%D8%B1%D8%AE-%D8%AE%DB%8C%D8%A7%D8%B7%DB%8C
Timestamp: 2019-01-02 04:35:01+00:33, Page: /favicon.ico
Timestamp: 2019-01-02 04:35:01+00:33, Page: /static/images/guarantees/goodShopping.png
Timestamp: 2019-01-02 04:35:02+00:33, Page: /static/css/font/wyekan/font.woff
Timestamp: 2019-01-02 04:35:02+00:33, Page: /static/images/guarantees/bestPrice.png
Timestamp: 2019-01-02 04:35:02+00:33, Page: /static/images/guarantees/warranty.png
Timestamp: 2019-01-02 04:35:02+00:33, Page: /static/images/guarantees/support.png
Timestamp: 2019-01-02 04:35:02+00:33, Page: /static/images/guarantees/fastDelivery.png
Timestamp: 2019-01-02 04:35:03+00:33, Page: /m/browse/dishwasher/%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D8%B8%D8%B1%D9%81%D8%B4%D9%88%DB%8C%DB%8C
Timestamp: 2019-01-02 04:36:00+00:33, Page: /m/browse/sewing-machine/%DA%86%D8%B1%D8%AE-%D8%AE%DB%8C%D8%A7%D8%B7%DB%8C
Timestamp: 2019-01-02 04:36:02+00:33, Page: /m/browse/sewing-machine/%DA%86%D8%B1%D8%AE-%D8%AE%DB%8C%D8%A7%D8%B7%DB%8C
時間の経験則を活用
セッションを特定し、セッション情報を格納するために、ソートしたデータフレームのそれぞれの行に対して、クライアント、ユーザーエージェント、閾値を超えた時間のギャップを追跡します。
# 秒でセッションの閾値を定義 (10分)
SESSION_THRESHOLD_SECONDS = 10 * 60
# client, user_agent, datetimeでlogs_dfをソート
logs_df_sorted = logs_df.sort_values(by=['client', 'user_agent', 'datetime'])
# セッション情報を格納するためのからのリストを初期化
session_info = []
# セッションを追跡するための変数の初期化
current_client = None
current_user_agent = None
current_session_start = None
current_session_end = None
current_session_pages = []
# ソートされたデータフレームのそれぞれの行に対する繰り返し
for index, row in logs_df_sorted.iterrows():
# clientあるいはuser_agentが変化、あるいは時間のギャップが閾値を上回った
if (row['client'] != current_client or row['user_agent'] != current_user_agent or
(current_session_start and (row['datetime'] - current_session_end).seconds > SESSION_THRESHOLD_SECONDS)):
# この場合、現在のセッション情報を格納
if current_session_start:
session_info.append((current_client, current_user_agent, current_session_start, current_session_end, current_session_pages))
# 最低でも5つのセッションを取得したら、ループを抜ける
if len(session_info) >= 5:
break
# 新規セッションをスタート
current_client = row['client']
current_user_agent = row['user_agent']
current_session_start = row['datetime']
current_session_end = row['datetime']
current_session_pages = [(row['datetime'], row['request'])]
else:
# そうではない場合、現在のセッション二ページを追加
current_session_pages.append((row['datetime'], row['request']))
# セッション終了時間を更新
current_session_end = row['datetime']
# 最低でも5つのセッションのセッション情報を表示
index = 0
for session in session_info:
print(f"Session {index+1}")
print("Client:", session[0])
print("User Agent:", session[1])
print("Session Start Time:", session[2])
print("Session End Time:", session[3])
print("Pages Visited:")
for timestamp, page in session[4]:
print(f" {timestamp}: {page}")
index +=1
print("\n\n")
Session 1
Client: 104.156.210.196
User Agent: Dalvik/2.1.0 (Linux; U; Android 8.0.0; SM-A720F Build/R16NW)
Session Start Time: 2019-01-02 04:20:00+00:33
Session End Time: 2019-01-02 04:20:00+00:33
Pages Visited:
2019-01-02 04:20:00+00:33: /image/32768?name=24xs450-33.jpg&wh=200x200
Session 2
Client: 104.194.24.33
User Agent: Mozilla/5.0 (Linux; Android 8.0.0; SM-G955F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36
Session Start Time: 2019-01-02 03:57:00+00:33
Session End Time: 2019-01-02 03:57:00+00:33
Pages Visited:
2019-01-02 03:57:00+00:33: /amp-helper-frame.html?appId=a624a1c1-0c93-466a-a546-e146710f97e6&parentOrigin=https://www-zanbil-ir.cdn.ampproject.org
Session 3
Client: 104.194.24.54
User Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; SM-G900H Build/MMB29K)
Session Start Time: 2019-01-02 04:24:00+00:33
Session End Time: 2019-01-02 04:26:04+00:33
Pages Visited:
2019-01-02 04:24:00+00:33: /image/33888?name=model-b2048u-1-.jpg&wh=200x200
2019-01-02 04:26:04+00:33: /image/11947?name=11947-1-fw.jpg&wh=200x200
Session 4
Client: 104.194.25.207
User Agent: Dalvik/2.1.0 (Linux; U; Android 5.0.2; P01V Build/LRX22G)
Session Start Time: 2019-01-02 04:06:04+00:33
Session End Time: 2019-01-02 04:06:05+00:33
Pages Visited:
2019-01-02 04:06:04+00:33: /image/33888?name=model-b2048u-1-.jpg&wh=200x200
2019-01-02 04:06:05+00:33: /image/11947?name=11947-1-fw.jpg&wh=200x200
2019-01-02 04:06:05+00:33: /image/11926?name=sm812aaa.jpg&wh=200x200
Session 5
Client: 104.248.138.218
User Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1
Session Start Time: 2019-01-02 04:35:01+00:33
Session End Time: 2019-01-02 04:36:02+00:33
Pages Visited:
2019-01-02 04:35:01+00:33: /m/browse/sewing-machine/%DA%86%D8%B1%D8%AE-%D8%AE%DB%8C%D8%A7%D8%B7%DB%8C
2019-01-02 04:35:01+00:33: /favicon.ico
2019-01-02 04:35:01+00:33: /static/images/guarantees/goodShopping.png
2019-01-02 04:35:02+00:33: /static/css/font/wyekan/font.woff
2019-01-02 04:35:02+00:33: /static/images/guarantees/bestPrice.png
2019-01-02 04:35:02+00:33: /static/images/guarantees/warranty.png
2019-01-02 04:35:02+00:33: /static/images/guarantees/support.png
2019-01-02 04:35:02+00:33: /static/images/guarantees/fastDelivery.png
2019-01-02 04:35:03+00:33: /m/browse/dishwasher/%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D8%B8%D8%B1%D9%81%D8%B4%D9%88%DB%8C%DB%8C
2019-01-02 04:36:00+00:33: /m/browse/sewing-machine/%DA%86%D8%B1%D8%AE-%D8%AE%DB%8C%D8%A7%D8%B7%DB%8C
2019-01-02 04:36:02+00:33: /m/browse/sewing-machine/%DA%86%D8%B1%D8%AE-%D8%AE%DB%8C%D8%A7%D8%B7%DB%8C
時間間隔が10分のセッションのデータフレームを作成
# セッション情報の配列からセッションデータフレームの作成
session_df = pd.DataFrame(session_info)
# カラムの設定
session_df.columns = ['client', 'user_agent', 'start_time', 'end_time', 'pages']
# タプルからページのみを抽出
session_df['pages'] = session_df['pages'].apply(lambda x: [page[1] for page in x])
# データフレームの最初の2行を表示
session_df.head(2)
session_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 client 5 non-null object
1 user_agent 5 non-null object
2 start_time 5 non-null datetime64[ns, pytz.FixedOffset(33)]
3 end_time 5 non-null datetime64[ns, pytz.FixedOffset(33)]
4 pages 5 non-null object
dtypes: datetime64[ns, pytz.FixedOffset(33)](2), object(3)
memory usage: 328.0+ bytes
Q3. 5つの最も頻繁なリファラーウェブサイト
リファラーのない行を削除し、不正なURLをフィルタリングし、最後にベースURLを抽出することで、リファラーのカラムをきれいにします。
from urllib.parse import urlparse
# 'referer' が NaN である行を削除
referer_df = logs_df.dropna(subset=['referer'])
# well-formedではないURLを除外
referer_df = referer_df[referer_df['referer'].apply(lambda x: re.match(r'^https?://', x) is not None)]
# ベースURLを抽出するためにリファラーURLをパース
referer_df['base_url'] = referer_df['referer'].apply(lambda x: urlparse(x.replace('"', '')).scheme + "://" + urlparse(x.replace('"', '')).netloc)
# トップ5の最頻リファラーウェブサイトの特定
counting_referer_occurence = referer_df['base_url'].value_counts().sort_values(ascending=False)
# トップ5の取得
top_5_referers = counting_referer_occurence.head(5)
# トップ5の最頻リファラーウェブサイトの表示
for i, (referer, count) in enumerate(top_5_referers.items(), start=1):
print(f"{i}. {referer} - Count: {count}")
1. https://www.zanbil.ir - Count: 3886
2. https://znbl.ir - Count: 141
3. https://torob.com - Count: 91
4. https://www-zanbil-ir.cdn.ampproject.org - Count: 72
5. http://www.zanbil.ir - Count: 50
# 切り取りなしにそれぞれのカラムの完全なコンテンツを表示するために、最大カラム幅をNoneに設定
pd.set_option('display.max_colwidth', None)
Q4. サポート率が25%越えの一緒に訪問されるページ
頻繁に一緒に訪問されるというには、それらは同じセッションではなくてはならないため、セッションでグルーピングされたデータフレームを使います
%pip install mlxtend
import pandas as pd
from mlxtend.preprocessing import TransactionEncoder
from mlxtend.frequent_patterns import apriori, association_rules
# セッションごとに訪問されたページをリストのリストフォーマットに変換
pages_accessed = session_df['pages'].tolist()
# TransactionEncoderの初期化
te = TransactionEncoder()
# データをone-hotエンコードされたデータフレームにフィット、変換
onehot = te.fit_transform(pages_accessed)
# one-hotエンコードされたデータフレームをデータフレームに変換
df = pd.DataFrame(onehot, columns=te.columns_)
# Aprioriを用いて最低でもサポートが0.25の頻繁なアイテムセットを特定
frequent_itemsets = apriori(df, min_support=0.25, use_colnames=True)
一緒に訪問されたページを探しており、これはアイテムセットに2つ以上のページがなくてはならないことを意味するので、1より多いアイテムを持つアイテムセットにフィルタリングします。
# 1より多いアイテムを持つアイテムセットになるように高頻度アイテムセットをフィルタリング
filter_frequent_itemsets = frequent_itemsets[frequent_itemsets['itemsets'].apply(lambda x: len(x)) > 1]
# 高頻度アイテムセットを表示
print("Frequent Itemsets:")
filter_frequent_itemsets
洞察: /image/11947?name=11947-1-fw.jpg&wh=200x200 と /image/33888?name=model-b2048u-1-.jpg&wh=200x200 はともに一緒に訪問されています。
Q5. リフト値が2.05を超えるアソシエーションルール
# リフトが2.05を超えるルールの取得
rules = association_rules(frequent_itemsets, metric='lift', min_threshold=2.05)
# ルールを整形して表示
print("Association Rules with Lift > 2.05:\n")
for index, rule in rules.iterrows():
antecedents = ', '.join(list(rule['antecedents']))
consequents = ', '.join(list(rule['consequents']))
support = rule['support']
confidence = rule['confidence']
lift = rule['lift']
print(f"Rule {index+1}: {antecedents} -> {consequents}")
print(f"Support: {support:.4f}, Confidence: {confidence:.4f}, Lift: {lift:.4f}\n")
Association Rules with Lift > 2.05:
Rule 1: /image/33888?name=model-b2048u-1-.jpg&wh=200x200 -> /image/11947?name=11947-1-fw.jpg&wh=200x200
Support: 0.4000, Confidence: 1.0000, Lift: 2.5000
Rule 2: /image/11947?name=11947-1-fw.jpg&wh=200x200 -> /image/33888?name=model-b2048u-1-.jpg&wh=200x200
Support: 0.4000, Confidence: 1.0000, Lift: 2.5000
Q6. 10の高頻度シーケンシャルパターン
GSPアルゴリズムは、ユーザーのナビゲーションのシーケンスで頻繁に生じるパターンを特定し、共通するブラウジング挙動の理解の助けとなります。
%pip install gsppy
import argparse
import logging
import random
from gsppy.gsp import GSP
logging.basicConfig(level=logging.DEBUG)
result = GSP(pages_accessed).search(0.25)
result
[{('/image/33888?name=model-b2048u-1-.jpg&wh=200x200',): 2,
('/image/11947?name=11947-1-fw.jpg&wh=200x200',): 2},
{('/image/33888?name=model-b2048u-1-.jpg&wh=200x200',
'/image/11947?name=11947-1-fw.jpg&wh=200x200'): 2}]
max_length = 3
# 最大長に基づき、高頻度のシーケンシャルパターンをフィルタ
filtered_patterns = [pattern for pattern in result if len(pattern) <= max_length]
# 高頻度のシーケンシャルパターンを表示
for pattern in filtered_patterns:
print(pattern)
{('/image/33888?name=model-b2048u-1-.jpg&wh=200x200',): 2, ('/image/11947?name=11947-1-fw.jpg&wh=200x200',): 2}
{('/image/33888?name=model-b2048u-1-.jpg&wh=200x200', '/image/11947?name=11947-1-fw.jpg&wh=200x200'): 2}
INFO:py4j.clientserver:Received command c on object id p0
0.25より少なくすると、GSPの実行に数時間を要しRAMを食い尽くしました
Q7. 類似したナビゲーションパターンを持つユーザーのクラスターを示すグラフ
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
# 分析に適したフォーマットにパターンを設定
patterns = onehot
# K-meansを用いたユーザーのクラスター
kmeans = KMeans(n_clusters=3, n_init=10)
clusters = kmeans.fit_predict(patterns)
# クラスターの可視化
plt.figure(figsize=(10, 6))
plt.scatter(patterns[:, 0], patterns[:, 1], c=clusters, cmap='viridis')
plt.title('Clusters of Users with Similar Navigational Patterns')
plt.colorbar(label='Cluster')
plt.show()
import networkx as nx
import matplotlib.pyplot as plt
# グラフの作成
G = nx.Graph()
# グラフにノード(ユーザー)を追加
for i in range(len(patterns)):
# ノードの属性としてクラスターを割り当て
G.add_node(i, label=f"User {i}", cluster=clusters[i])
# (同じクラスターで)類似したユーザー間にエッジを追加
for i in range(len(patterns)):
for j in range(i + 1, len(patterns)):
if clusters[i] == clusters[j]:
G.add_edge(i, j)
# グラフの可視化
plt.figure(figsize=(10, 8))
# グラフのレイアウト
pos = nx.spring_layout(G)
# クラスターごとに色付けしてノードを描写
node_color = [clusters[node] for node in G.nodes()]
nx.draw_networkx_nodes(G, pos, node_color=node_color, cmap=plt.cm.tab10, node_size=200)
# エッジの描画
nx.draw_networkx_edges(G, pos, alpha=0.5)
plt.title('Clusters of Users with Similar Navigational Patterns')
# plt.axis('off')
plt.show()