LoginSignup
3
4

More than 3 years have passed since last update.

【SIGNATE初心者】【練習問題】民泊サービスの宿泊価格予測を実践

Last updated at Posted at 2020-12-15

はじめに

私が機械学習を勉強するにあたり、SIGANATEの「【練習問題】民泊サービスの宿泊価格予測」を実践しました。SIGNATEとは、日本版Kaggleと言われるとおり、コンペティションに参加したり、練習用データセットが提供されています。
ここではその実践における分析過程と結果について共有することを目的としています。

誤りがあればご意見いただきたい。。。
※同じ初心者の方と勉強できればいいなと思っております。

またSIGNATEでは実行環境であるカーネルが存在しないため、
以下では実行環境としてKaggleのNotebookを使用している。
参考サイト:https://www.codexa.net/kaggle-notebook-for-beginners/

課題

民泊サービスの物件データを使って、宿泊価格を予測するモデルを構築する。
近年、個人物件を貸し出す民泊サービスが流行っている。
民泊では物件オーナーが、部屋の広さや立地をもとに宿泊価格を決めていますが、妥当な料金設定を行うのは容易ではない。
そこで今回は、民泊サービスであるAirbnbの掲載物件データを使って、宿泊価格を予測するモデルの構築を実施する。
【練習問題】民泊サービスの宿泊価格予測 | https://signate.jp/competitions/266

データ概要

課題種別:回帰
データ種別:多変量
学習データサンプル数:55583
説明変数の数:16
欠損値:有り

カラム ヘッダ名称 データ型 説明
0 id int インデックスとして使用
1 accommodates int 収容可能人数
2 amenities char アメニティ
3 bathrooms float 風呂数
4 bed_type char ベッドの種類
5 bedrooms float ベッドルーム数
6 beds float ベッド数
7 cancellation_policy char キャンセルポリシー
8 city char 都市
9 cleaning_fee int クリーニング料金を含むか
10 description char 説明
11 first_review char 最初のレビュー日
12 host_has_profile_pic int ホストの写真があるかどうか
13 host_identity_verified int ホストの身元確認が取れているか
14 host_response_rate char ホストの返信率
15 host_since char ホストの登録日
16 instant_bookable char 即時予約可能か
17 last_review char 最後のレビュー日
18 latitudes float 緯度
19 longitude float 経度
20 name char 物件名
21 neighbourhood char 近隣情報
22 number_of_reviews int レビュー数
23 property_type char 物件の種類
24 review_scores_rating float レビュースコア
25 room_type char 部屋の種類
26 thumbnail_url char サムネイル画像リンク
27 zipcode int 郵便番号
28 y float 宿泊価格

データの登録

  1. Kaggle上にはすでにデータセットが登録されているため、本来はデータを自分で登録することは不要ですが必要に応じて学習用データ(train.csv)、評価用データ(test.csv)のダウンロード・登録を行った。
    ※ここでは公開されているものではなく自分で実施する手順を実施。 データセット.png
  2. Kaggleのノートブックにアップロードを実施する。 データセット登録.png ※すでにPublicで公開されているデータと同じであるため、SKipDuplicate⇒IncludeDuplicatesにする必要あり。 データセット登録2.png

データの読込

以下で訓練用データとテストデータの読み込みを実施する。

# データの読み込み
df = pd.read_csv("/kaggle/input/houseprice/train.csv")
# ターゲットデータの読み込み
df_target = pd.read_csv("/kaggle/input/houseprice/test.csv")

データの確認

以下で訓練用データとテストデータの読み込みを実施する。

# データの表示
df.head()
# ターゲットデータの表示
df_target.head()
# データの中身の確認
df.info()
# ターゲットデータの中身の確認
df_target.info()

分析に使用するデータの選定

表示されたデータから以下のように宿泊価格との関係性を予測した。
分析対象として影響が軽微のものや、言語データであるものはなしとした。
下記予測から分析対象の列は15となった。

ヘッダ名称 説明 宿泊価格との関係性予測 備考
id インデックスとして使用 なし
accommodates 収容可能人数 あり
amenities アメニティ あり 処理が複雑化するため対象外
bathrooms 風呂数 あり
bed_type ベッドの種類 あり 数にする必要あり
bedrooms ベッドルーム数 あり
beds ベッド数 あり
cancellation_policy キャンセルポリシー あり 数にする必要あり
city 都市 あり 数にする必要あり
cleaning_fee クリーニング料金を含むか あり 数にする必要あり
description 説明 なし 言語データのため分析対象外
first_review 最初のレビュー日 なし
host_has_profile_pic ホストの写真があるかどうか なし
host_identity_verified ホストの身元確認が取れているか あり
host_response_rate ホストの返信率 なし
host_since ホストの登録日 なし
instant_bookable 即時予約可能か あり 数にする必要あり
last_review 最後のレビュー日 なし
latitude 緯度 なし
longitude 経度 なし
name 物件名 なし
neighbourhood 近隣情報 あり 数にする必要あり
number_of_reviews レビュー数 あり
property_type 物件の種類 あり 数にする必要あり
review_scores_rating レビュースコア あり
room_type 部屋の種類 あり
thumbnail_url サムネイル画像リンク あり
zipcode 郵便番号 なし

分析対象外列データの削除

上記で分析対象外と予測したデータをデータから削除する。

#不要なデータをドロップ
drop_id = ["id","amenities","description","first_review","host_has_profile_pic","host_response_rate","host_since","last_review","latitude","longitude","name","zipcode"]
df = df.drop(drop_id, axis=1)
df.info()

分析対象列の加工(欠損値補完)

行数が55583であるのに対し、欠損が存在するカラムは以下である。
- bathrooms(欠損数:147)
- bedrooms(欠損数:71)
- beds(欠損数:96)
- host_identity_verified(欠損数:1)
- neighbourhood(欠損数:5,160)
- review_scores_rating(欠損数:12,556)
- thumbnail_url(欠損数:6,145)

今回は以下の対策を実施した。
①欠損数が少ない場合は行を削除する

  • bathrooms
  • bedrooms
  • beds
  • host_identity_verified
# 欠損値を含むデータを削除
df = df.dropna(subset=['bathrooms',"bedrooms","beds","host_identity_verified"])

②欠損数が多く補完が難しいため分析対象外とする。

  • neighbourhood
# 不要なデータをドロップ
df = df.drop("neighbourhood", axis=1)

③別の値で補完する。

  • review_scores_rating
# レビュースコアの確認
print(df["review_scores_rating"].describe())
print(df["review_scores_rating"].unique())
print(df["review_scores_rating"].value_counts())

# 中央値による補完
df["review_scores_rating"] = df["review_scores_rating"].fillna(df["review_scores_rating"].median())

④分析対象の加工にて変換処理の実施対象とする。(ここでは対象外)

  • thumbnail_url

分析対象列の加工(変換)

①True,Falseのデータを1,0に変更することで数値データとする。
対象列は「cleaning_fee」、「instant_bookable」、「host_identity_verified」の3列である。
以下の関数を適用することでデータの加工処理を実施する。

# 変換関数の定義
def checkTF(x):
    if x == "t":
        return 1
    elif x == "f":
        return 0

# 対象カラムに関数を適応して変換実施
df["cleaning_fee"] = df["cleaning_fee"].apply(checkTF)
df["host_identity_verified"] = df["host_identity_verified"].apply(checkTF)
df["instant_bookable"] = df["instant_bookable"].apply(checkTF)

②サムネイル画像リンク(thumbnail_url)の有無を0と1に変換する。

# 変換関数の定義
def checkNull(x):
    if x != "":
        return 1
    else:
        return 0

# 対象カラムに関数を適応して変換実施
# 対象カラムに関数を適応して変換実施
df["thumbnail_url"] = df["thumbnail_url"].apply(checkNull)

③質的データをダミー変数に変換
以下の項目は質的データであるため変換処理を実施
'bed_type','cancellation_policy','city','property_type','room_type'

ヘッダ名称 説明
bed_type ベッドの種類
cancellation_policy キャンセルポリシー
city 都市
property_type 物件の種類
room_type 部屋の種類
# 質的データをダミー変数化
select_columns = ['bed_type','cancellation_policy','city','property_type','room_type']
dummy_df = pd.get_dummies(df[select_columns],drop_first=True)

# 元データとダミー変数化したものを結合
df =  pd.concat([df, dummy_df], axis=1)

#不要なデータをドロップ
drop_id2 = ['bed_type','cancellation_policy','city','property_type','room_type']
df = df.drop(drop_id2, axis=1)

モデルと評価

今回は線形回帰モデルを使用して分析を実施した。
参考:https://qiita.com/0NE_shoT_/items/08376b08783cd554b02e

また評価については、RMSEを使用した。
参考:https://mathwords.net/rmsemae

①分析データの分割と学習を実施。

# データを訓練用とテスト用に分割するモジュールのインポート
from sklearn.model_selection import train_test_split
# LinearRegression(線形回帰)モデルモジュールのインポート
from sklearn.linear_model import LinearRegression

#目的変数の退避
y = df["y"]
df = df.drop("y", axis=1)
# データの分割を実施
X_train,X_test,y_train,y_test = train_test_split(df, y, test_size=0.3,random_state = 1000)
# モデルインスタンス準備
lr = LinearRegression()
# モデルを学習
lr.fit(X_train, y_train)
# X_trainに対して予測を実施
y_pred_train = lr.predict(X_train)


②テストデータに対する予測と評価を実施

# X_testに対する予測値を算出
y_pred_test = lr.predict(X_test)

# RMSEを求め、rmse_trainに代入してください。
mse_test = MSE(y_pred_test,y_test)
rmse_test = np.sqrt(mse_test)

# RMSEの表示
print(rmse_test)

125.88739952642386

予測データに対する前処理(欠損値の補完)

予測データに対しても欠損値に対して、前処理を実施する必要がある。
以下の手順で前処理を実施した。

①予測不要データの削除と、欠損値の確認

#不要なデータをドロップ
drop_id = ["id","amenities","description","first_review","host_has_profile_pic","host_response_rate","host_since","last_review","latitude","longitude","name","zipcode","neighbourhood"]
df_target = df_target.drop(drop_id, axis=1)
df_target.info()

実行結果

 #   Column                  Non-Null Count  Dtype  
---  ------                  --------------  -----  
 0   accommodates            18528 non-null  int64  
 1   bathrooms               18475 non-null  float64
 2   bed_type                18528 non-null  object 
 3   bedrooms                18508 non-null  float64
 4   beds                    18493 non-null  float64
 5   cancellation_policy     18528 non-null  object 
 6   city                    18528 non-null  object 
 7   cleaning_fee            18528 non-null  object 
 8   host_identity_verified  18488 non-null  object 
 9   instant_bookable        18528 non-null  object 
 10  number_of_reviews       18528 non-null  int64  
 11  property_type           18528 non-null  object 
 12  review_scores_rating    14362 non-null  float64
 13  room_type               18528 non-null  object 
 14  thumbnail_url           16457 non-null  object 

上記の結果から以下カラムに対して欠損値の確認ができる。
それぞれの欠損値に対して、対策を実施した。

  • bathrooms(欠損数:53)
  • bedrooms(欠損数:20)
  • beds(欠損数:35)
  • host_identity_verified(欠損数:40)
  • review_scores_rating(欠損数:4166)
  • thumbnail_url(欠損数:2071)

①欠損数が少ないものに対しては最頻値で保管する処理を実施

  • bathrooms(欠損数:53)
  • bedrooms(欠損数:20)
  • beds(欠損数:35)
# 各値の確認
print(df_target["bathrooms"].value_counts())
print(df_target["bedrooms"].value_counts())
print(df_target["beds"].value_counts())

#中央値による補完
df_target["bathrooms"] = df_target["bathrooms"].fillna(df_target["bathrooms"].mode())
df_target["bedrooms"] = df_target["bedrooms"].fillna(df_target["bedrooms"].mode())
df_target["beds"] = df_target["beds"].fillna(df_target["beds"].mode())

②host_identity_verifiedはTrue,Falseがランダムである可能性が高いため、直前の値で埋める方式を採用

# 直前の値を使って埋めていく
print(df_target["host_identity_verified"].value_counts())
df_target["host_identity_verified"] = df_target["host_identity_verified"].fillna(method='ffill') 
print(df_target["host_identity_verified"].value_counts())

実行結果からおおむね均等に欠損値を埋めることができたことを確認した。

t    12484
f     6004
Name: host_identity_verified, dtype: int64
t    12509
f     6019
Name: host_identity_verified, dtype: int64

③review_scores_ratingは訓練データと同様に中央値で埋める処理を実施

#中央値による補完
df_target["review_scores_rating"] = df_target["review_scores_rating"].fillna(df_target["review_scores_rating"].median())

④分析対象の加工にて変換処理の実施対象とする。(ここでは対象外)

予測データに対する前処理(変換)

①True,Falseのデータを1,0に変更することで数値データとする。
対象列は「cleaning_fee」、「instant_bookable」、「host_identity_verified」の3列である。

# 対象カラムに関数を適応して変換実施
df_target["cleaning_fee"] = df_target["cleaning_fee"].apply(checkTF)
df_target["host_identity_verified"] = df_target["host_identity_verified"].apply(checkTF)
df_target["instant_bookable"] = df_target["instant_bookable"].apply(checkTF)

②サムネイル画像リンク(thumbnail_url)の有無を0と1に変換する。

# 対象カラムに関数を適応して変換実施
df_target["thumbnail_url"] = df_target["thumbnail_url"].apply(checkNull)

③質的データをダミー変数に変換
以下の項目は質的データであるため変換処理を実施
'bed_type','cancellation_policy','city','property_type','room_type'

ヘッダ名称 説明
bed_type ベッドの種類
cancellation_policy キャンセルポリシー
city 都市
property_type 物件の種類
room_type 部屋の種類
# 質的データをダミー変数化
dummy_df_target = pd.get_dummies(df_target[select_columns],drop_first=True)

# 元データとダミー変数化したものを結合
df_target =  pd.concat([df_target, dummy_df_target], axis=1)

#不要なデータをドロップ
df_target = df_target.drop(drop_id2, axis=1)

最終データの確認実施

以下を実施して訓練データとの齟齬がないかを確認した。

print(df.info())
print(df_target.info())

実施結果から大きく差分が発生したため、この差分を埋めるために以下の処理を実施した。
①df_targetのみに存在する情報の削除

#不要なデータをドロップ
df_target = df_target.drop("property_type_Lighthouse", axis=1)
df_target.info()

①dfのみに存在する情報の追加

df_target.insert(29, "property_type_Casa particular",0)
df_target.insert(35, "property_type_Earth House",0)
df_target.insert(42, "property_type_Island",0)
df_target.insert(48, "property_type_Tipi",0)
df_target.insert(50, "property_type_Train",0)
df_target.insert(51, "property_type_Treehouse",0)
df_target.insert(52, "property_type_Vacation home",0)

予測とファイルの出力

# モデルインスタンス準備
lr_result = LinearRegression()
# モデルを学習
lr_result.fit(df, y)
# 目的変数yの予測を実施
y_predict = lr_result.predict(df_target)
y_predict = pd.DataFrame(y_predict)
# ターゲットデータの読み込み
df_tmp = pd.read_csv("/kaggle/input/houseprice/test.csv")
df_id = df_tmp["id"]
# 特徴量を抽出(idのみ)
df_final = pd.concat([df_id,y_predict],axis=1)
# 並び替えを行う
df_final.to_csv('/kaggle/working/submit.csv', header=False, index=False)

提出ファイルの評価

上記で出力したファイルをSIGNATEに提出した。
170.4177160で133位であった。
この結果に対し他の手法で改善が見込めるかを検討することを今後の課題とする。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4