Pythonデータ分析(重回帰分析②)
SIGNATEの練習問題:民泊サービスの宿泊価格予測をやってみました。
SIGNATEのGymでscikit-learn道場をクリアしたので重回帰分析以外の手法も試してみたいのですが、パラメーターチューニングや K-分割交差検証の概念を得たので再度重回帰分析モデルを使用しました。
データ読込
28列55583行と特徴量が多く、把握に時間がかかりました。
最終的に使用したのは下記のカラムです。
accommodates:収容可能人数
bathrooms:風呂数
bed_type:ベッドの種類
bedrooms:ベッドルーム数
beds:ベッド数
cleaning_fee:クリーニング料金を含むか
instant_bookable:即時予約可能か
latitude:緯度
longitude:経度
number_of_reviews:レビュー数
review_scores_rating:レビュースコア
room_type:部屋の種類
y:宿泊価格 ※目的変数
可視化
#ヒストグラム(緯度と経度を除く)
df2.drop(columns=['latitude','longitude']).hist(figsize=(20,20))
目的変数を始め、結構偏りが見られます。回帰の場合、目的変数の値が正規分布に従っていることが重要であると何かの記事で見かけたのですが、今の私のスキルでは思うように処理できなかったので一旦このまま分析します。
他にも、収容可能人数・ベッド数等も数が少ない方が件数が多く、偏りがあります。こういった場合は欠損値は平均値ではなく最頻値等で埋める方が適切な気がします。
#箱ひげ図
sns.boxplot(data=df2, x='accommodates', y='y')
plt.ylim(0,1750)# 表示範囲を制限
plt.xticks(rotation=90)
plt.show()
sns.boxplot(data=df2, x='bathrooms', y='y')
plt.xticks(rotation=90)
plt.show()
sns.boxplot(data=df2, x='bedrooms', y='y')
plt.xticks(rotation=90)
plt.show()
accommodates、bathrooms、bedroomsを箱ひげ図で見てみました。
全体的に収容可能人数や部屋が増える方が価格は高くなるようです。
Questにもありましたが、大人数で価格が低いのはシェアルームかもしれません。
ヒストグラム上で件数が多かったのは一家族単位と思われる人数の物件件数でした。
欠損値処理とラベルエンコーディング
◆bathrooms (147) 、bedrooms (71) 、beds(96)、 review_scores_rating( 12556)の欠損値があり、最頻値で補完します。
◆ユニークな要素数が少ないbed_type、cleaning_fee、room_type、instant_bookableをラベルエンコーディングします。
◆yの値を見ると1と5が一件しか無く、10以降が複数件あったので入力誤りと考え削除します。
#分析用カラム選定
select = ['accommodates','bathrooms','bedrooms','beds','bed_type','cleaning_fee','instant_bookable','latitude','longitude','number_of_reviews',
'review_scores_rating','room_type']
#欠損値の確認
df2 = df[select]
df2.isna().sum()
#最頻値で補完
df2['bathrooms'] = df2['bathrooms'].fillna(df2['bathrooms'].mode()[0])
df2['bedrooms'] = df2['bedrooms'].fillna(df2['bedrooms'].mode()[0])
df2['beds'] = df2['beds'].fillna(df2['beds'].mode()[0])
df2['review_scores_rating'] = df2['review_scores_rating'].fillna(df2['review_scores_rating'].mode()[0])
# ラベルエンコーディング
from sklearn.preprocessing import LabelEncoder
cols = ('bed_type','cleaning_fee','room_type','instant_bookable')
for c in cols:
lbl = LabelEncoder()
lbl.fit(list(df2[c].values))
df2[c] = lbl.transform(list(df2[c].values))
#yの値で1と5は一件しかない 入力誤りと考え削除
df2 = df2[df2['y'] >= 10]
重回帰分析
K-分割交差検証・ハイパーパラメーターチューニング
X = df2.drop(columns=['y']).values
y = df2['y'].values
from sklearn.model_selection import KFold
from sklearn.linear_model import LinearRegression as LR
from sklearn.model_selection import GridSearchCV
kf = KFold(n_splits=10,random_state=0,shuffle=True)#10回分繰り返し
for train_index, test_index in kf.split(X):
# トレーニングデータとテストデータを分割する
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
# ハイパーパラメータの候補を設定
params = {'copy_X': [True, False], 'fit_intercept': [True, False], 'n_jobs': [1, 2, 3, 4, 5], 'positive': [True, False]}
# グリッドサーチインスタンスを作成
grid_search = GridSearchCV(LR(), params, cv=kf, scoring='neg_mean_squared_error')
# グリッドサーチを実行
grid_search.fit(X_train, y_train)
# 最適なハイパーパラメータを取得
best_params = grid_search.best_params_
# 最適なモデルを構築
model = LR(**best_params)
model.fit(X_train, y_train)
# モデル評価
score = model.score(X_test, y_test)
print(score)
--結果(R2値)--
0.4088382010189122
0.4028050032323732
0.42233559081901384
0.39014584178335965
0.39386563697581156.....
◆トレーニングデータとテストデータをK-分割交差検証(KFold)で10分割。
(KFold:データセットをK個のグループに分割し、各グループを1回ずつテストデータとして使用し、残りのK-1個のグループをトレーニングデータとして使用。インスタンスには、データセットを分割する回数、ランダム化の種類、データセットをシャッフルするかどうかなどのパラメータを指定)
◆for文の各ループでは、トレーニングデータとテストデータの各インデックスを取得。X・yのデータフレームを.valuesでnumpy配列に変換する必要あり。
◆GridSearchCVでLinearRegressionのパラメータの候補を試行し、最適なハイパーパラメータを取得。
◆最適なハイパーパラメータを使用してモデル構築・評価。
※コードが実行される所要時間1分
教材で勉強したLinearRegression()の引数とBardが教えてくれる引数が違って、よくよく調べてみたらscikit-learnのバージョンの違いによるものでした…。Googleで検索して出てくる記事で学習する際も、古い記事を参照する時は要注意です。
私の環境では ver.1.2.2 なのですが、Bard曰く1.2.3があるらしいです。アップグレードしても2023年6月25日現在1.2.2以上にはならなかったので、「AIのもっともらしい嘘」になるのでしょうか。
(因みに存在しない引数'alpha'のおかげで盛大に迷子になりました)
ともあれ、これからはバージョンを指定しながらBardにも聞いていこうと思います。
結果はR2で0.4前後とかなり低い気がしましたが取り敢えずこれで提出データを作成します。
(R2:決定係数と呼ばれる指標です。決定係数は、モデルがデータの分散をどれだけ説明できるかを示す指標です。決定係数は、0から1までの値を取ります。決定係数が1に近いほど、モデルはデータの分散をよく説明できます。)
提出データ作成
#テストデータ読込
test_df = pd.read_csv('test.csv')
test_df2=test_df[['accommodates','bathrooms','bedrooms','beds','bed_type','cleaning_fee','instant_bookable','latitude','longitude','number_of_reviews',
'review_scores_rating','room_type']]
#欠損値を最頻値で補完
test_df2['bathrooms'] = test_df2['bathrooms'].fillna(test_df2['bathrooms'].mode()[0])
test_df2['bedrooms'] = test_df2['bedrooms'].fillna(test_df2['bedrooms'].mode()[0])
test_df2['beds'] = test_df2['beds'].fillna(test_df2['beds'].mode()[0])
test_df2['review_scores_rating'] = test_df2['review_scores_rating'].fillna(test_df2['review_scores_rating'].mode()[0])
#ラベルエンコーディング
from sklearn.preprocessing import LabelEncoder
cols = ('bed_type','cleaning_fee','room_type','instant_bookable')
for c in cols:
lbl = LabelEncoder()
lbl.fit(list(test_df2[c].values))
test_df2[c] = lbl.transform(list(test_df2[c].values))
#提出データ作成
test_df['y'] = model.predict(test_df2)
test_df[['id', 'y']].to_csv('./submit.csv', header=False, index=False)
評価はRMSE: 172.9132723 でした。
特徴量を目視で指定してダミー変数化加工のみで提出したものが 238.5873928 だったので精度がかなり上がりました。ここまで分かりやすく精度が上がると楽しくなってきます。
しかしトレーニングデータではR2がかなり低かったので、その辺りの理解が追い付いてません…RMSEも確認できたら良かったのですが、調べきれませんでした。
今回もBardにたくさん手伝ってもらいました。
色々な方が生成系AIについて分かりやすく解説してくださっていますが、基礎知識が無い段階で単純な質問ばかりしていると求めている回答にたどり着かないことがよくわかりました。
引き続き学習を続けていきたいと思います。