まえがき
qiita初投稿です。本記事はデータサイエンスをほんの先端だけ齧りKaggleのtitanicの次に何を自己学習するべきかわからず、とりあえず好きなものを好きなようにやってみた結果を書き残した日記になります。間違っている点等あればご指摘お願いします。
また本記事では私の大好きな大人気MMORPG「ファイナルファンタジーXIV」(以下FF14とする)に関する分析を行っており、ドメイン知識を全て説明しきれていない点についてもご了承ください。
背景
FF14のLodestoneという公式サイトにはゲーム中の情報は勿論、お役立情報を確認したり、日記を書いてプレイヤー間で交流することが出来ます。その中にはプレイヤーの今までの旅の足跡が記録されており公開範囲を設定していればゲームをプレイしていない人でも情報を取得することが出来ます。
↑このような感じで基本的なプロフィールは勿論、アチーブメント(称号)を取得した日やミニオン(一緒に連れて歩くことができる「ペット」のようなもの)やマウント(チョコボに代表されるプレイヤーキャラクター専用の乗り物)の取得状況も確認できます。
まさに足跡であるのがThe Lodestone
このうち、アチーブメントはデフォルトでは非公開になっているので誰でも見ることが出来るわけではないのですが、マウントとミニオンはどのキャラクターの情報も取得出来ます。
そこで、基本的なプロフィールの情報とマウントの取得状況から何か二値分類出来ないか!という背景のもと、この後の分析を進めていきます。
前提条件
データ分析を行う上で大事な前提条件の確認と設定を行っていきます。
何を二値分類するのか
今回はマウントの一つである「トルガル」の取得状況を分類していきたいと思います。
トルガルを分類する理由について...
このトルガルというマウントは2024年4月2日17:00 ~ 2024年5月8日23:59頃まで行われていたFF16とのコラボレーションイベントで取得出来たマウントです。
このコラボレーションイベントは参加やクリアに難しい条件は無いので簡単に入手出来ます。かつ直近で入手出来るマウントの中では最新のものになります。(課金マウントを除く)
そのため
トルガルを取得している→直近でゲームをプレイしている
トルガルを取得していない→直近ではゲームをプレイしていない
という近似的なアクティブを予測出来ます。
当然近似的なデータであり運営の持っている真値とは誤差がある上に1ユーザーがその情報を知ったところでビジネス的価値は存在しません。好奇心と自己満足です。
抽出する標本について
分類する対象が決まったところで次に標本を抽出していきたいのですが、公式発表では3000万人を超える登録者がいるそうです。また既に引退して10年経つようなキャラクターもアカウント削除していない限りLodestoneにはデータが残っています。
そのため全数調査ではなくある程度の条件を絞ってデータスクレイピングでキャラクター情報を抽出していきます。
そこでスクレイピングの方法は上記のLodestone国勢調査を行っているつよつよ光の戦士様の記事を参考に標本の抽出を行いました。具体的には条件を切り替えながら2000件ずつクローリングを行い対象となるユーザーIDを合計300000件ほど取得しました。詳しい条件等気になる方は上の記事を参照ください。データスクレイピングのコードについては、公開したいところですが胸にしまっておきます。
データスクレイピングについての注意
FF14公式ではデータスクレイピングの許可はされていますが適切な時間間隔(1.5~2s)で公式サイトに負荷のかからないようにすること
取得するデータについて
上記の対象となったIDについて抽出するデータの項目としては
・ID
・種族/部族/性別
・誕生日
・開始都市
・所属グランドカンパニー (FF14の世界には3つの大きな組織がありその中から一つ選んではいることができます)
・所属フリーカンパニー(上のグランドカンパニーとは別にユーザー間で組織を作ることもできます、別ゲームだとギルド、同盟、チームにあたる存在)
・各ジョブのレベル(FF14には戦闘系ジョブ、製作系ジョブ、採集系ジョブがありそれぞれレベル90まで上げることが出来ます)
・マウントの取得可否
対象としたマウントはチョコボ、エターナルチョコボ、魔導アーマー、メガロアンビストマ、トルガル、SDSフェンリルの6つです。
マウント選定理由
チョコボ、魔導アーマー...ストーリー上入手できるマウント これを取得していない場合は流石にトルガルも持っていないだろうという予測が立てられる
エターナルチョコボ...エターナルバンド(ゲーム内での結婚)をするともらえるマウント(正確にはエタバンのプランによって変わる)
エタバンの有無がアクティブの有無に繋がるか気になる
メガロアンビストマ...記事執筆時点の最新高難易度レイドで入手出来るかわいいウーパールーパー
高難易度のクリアがアクティブの有無に繋がるか気になる
SDSフェンリル...課金すると手に入るマウント、この課金マウントは他のマウントよりもスピードが速いという唯一無二(ただし最近もう一つ早いマウントが追加された)
課金マウントの有無がアクティブの有無に繋がるか気になる
そしてこれらを取得したcsvデータを作成しました!(個人情報保護のためにIDとフリーカンパニーはぼかしています)
データの前処理
では前提条件で取得したcsvデータを使って早速機械学習...という訳にはいかず、データの前処理を行っていきたいと思います。
①データクレンジング
取得しておいてなんですが、誕生日と開始都市がアクティブに影響するとは考えにくいので消去してしまいます。
グランドカンパニーも役職まで乗っているのですがここでは一旦消します
import pandas as pd
df = pd.read_csv("csvのパス") #csvファイルの読みこみ
df.drop(columns=["誕生日","開始都市","グランドカンパニー"],inplace=True) #不要な列の削除
②二値化
フリーカンパニーの部分、フリーカンパニーに所属しているかどうかが知りたいわけで名前なんてどうでもいいですよね、なので所属していれば1,所属していない(nan)なら0に変えてしまいましょう
df['フリーカンパニー'] = df['フリーカンパニー'].apply(lambda x: 1 if isinstance(x, str) else 0)
③欠損値処理
ジョブのレベルの所でnanがありますね、ジョブを解放していない場合には-が出力され上位のジョブを取得している場合にはnanが出力されるようなので、どちらも0に置き換えてしまいましょう。
# 11行目から40行目にジョブの情報が入っています
df.iloc[:,10:40] = df.iloc[:,10:40].replace('-', 0).fillna(0)
④ビニング
各キャラクターに紐づいているIDは1からゲームを始めた順に割り振られているので名義尺度ではなく間隔尺度です。そのまま特徴量として使っても問題なさそうですがゲームをある程度始めた時期に分類します。
bins = [0, 12000000, 17500000, 26000000, 40000000, float('inf')]
labels = ['AR', 'HW', 'SB', 'SHB', 'EW']
# ビニングの適用
df['User ID'] = pd.cut(df['User ID'], bins=bins, labels=labels, right=True, include_lowest=True)
ラベルについて
AR...FF14新生エオルゼア 2013年リリース
HW...蒼天のイシュガルド 2015年夏発売拡張パッケージ
SB...紅蓮のリベレーター 2017年夏発売拡張パッケージ
SHB...漆黒のヴィランズ 2019年夏発売拡張パッケージ
EW...暁月のフィナーレ 2021年冬発売拡張パッケージ
フレンド様のユーザーIDと開始時期からある程度の開始時期と拡張パッケージの時期を推定しましたがどうしても蒼天辺りは推定になってしまいました!詳しい方情報ください!
とりあえず機械学習させてみる
さて、前処理も終わったところで実際に機械学習させてみましょう!
現在のcsvデータはこのようになっています。
白魔導士より下にもジョブが続いています
今回は5000データ取得したのでランダムフォレスト分類器とk分割交差検証を使ってモデルを作成・訓練していきたいと思います。
import pandas as pd
import numpy as np
from sklearn.model_selection import KFold
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score
#csvの読み込み
df = pd.read_csv("drive/My Drive/Colab Notebooks/character_info.csv")
X = df.drop(columns=['トルガル'])
y = df['トルガル']
# カテゴリカルデータ(UserID)をエンコード
label_encoders = {}
for column in X.select_dtypes(include=['object']).columns:
le = LabelEncoder()
X[column] = le.fit_transform(X[column].astype(str))
label_encoders[column] = le
# K分割交差検証の設定
kf = KFold(n_splits=5, shuffle=True, random_state=42)
# 各分割における正解率を保存するリスト
accuracy_scores = []
# K分割交差検証の実行
for train_index, test_index in kf.split(X):
X_train, X_test = X.iloc[train_index], X.iloc[test_index]
y_train, y_test = y.iloc[train_index], y.iloc[test_index]
# モデルの作成
model = RandomForestClassifier(random_state=42)
# モデルの訓練
model.fit(X_train, y_train)
# テストデータの予測
y_pred = model.predict(X_test)
# 正解率の計算
accuracy = accuracy_score(y_test, y_pred)
accuracy_scores.append(accuracy)
# 平均正解率の計算
average_accuracy = sum(accuracy_scores) / len(accuracy_scores)
print(f'Cross-Validation Accuracy: {average_accuracy:.2f}')
結果
Cross-Validation Accuracy: 0.77
平均正解率0.77という結果はトルガル以外のcsvの情報からトルガルを持っているかいないか分類して学習したときに77%の確率で正解しているということを示しています。
次回はデータの可視化、特徴量の追加やデータの可視化を行い更に別データを抽出して平均正解率の増加を目指します。
今回の要約
FF14公式サイトのLodestoneからデータを取得して近似的にアクティブを分類するモデルを作成した。
ここまで読んでくれて
ありがとうございました。