最後に実際のデータを使った実践編をご紹介します。ぜひ自分で行ってみた結果と照らし合わせてみてください。
全編はこちらから

物件データの収集
まず、データ分析に必要なライブラリをインポートします。
import pandas as pd # データフレーム操作
import numpy as np # 数値計算
次に、CSVファイルを読み込み、pandasのデータフレームとして格納します。
df = pd.read_csv("宿泊価格について_demo.csv")
# 読み込んだデータフレームの最初の5行を表示して確認
print(df.head())
データフレームの各列に欠損値(NaN)がどれだけ含まれているかを確認します。
# 各列の欠損値の数を表示
print(df.isnull().sum())
欠損値がどの行にあるかを確認します。
# 欠損値を含む行を抽出
missing_values_rows = df[df.isnull().any(axis=1)]
print(missing_values_rows)
欠損値が見つかった列について、適切な方法で修正します。今回のデータでは bedrooms と beds 列に欠損値があります。
ここでは以下の戦略をとります。
- bedrooms 列の欠損値は、同じ room_type で、かつ accommodates (収容人数) が同じ値の行の bedrooms の最頻値(一番多い値)で補完します。
- beds 列の欠損値は、同じ room_type で、かつ accommodates (収容人数) が同じ値の行の beds の最頻値(一番多い値)で補完します。
def fill_missing_with_mode(df, column_to_fill, group_columns):
"""
指定されたグループ列に基づいて、欠損値を最頻値で埋める関数。
"""
df[column_to_fill] = df.groupby(group_columns)[column_to_fill].transform(lambda x: x.fillna(x.mode()[0] if not x.mode().empty else np.nan))
return df
# bedrooms列の欠損値を補完
df = fill_missing_with_mode(df, 'bedrooms', ['room_type','accommodates'])
# beds列の欠損値を補完
df = fill_missing_with_mode(df, 'beds', ['room_type','accommodates'])
# 補完後のデータフレームの最初の5行を表示して確認
print(df.head())
#再度欠損値を確認
print(df.isnull().sum())
再度、欠損値の有無を確認し、修正が成功したかどうかを確認します。
print(df.isnull().sum())
補足事項
- 最頻値補完の注意点: 最頻値補完は、データに偏りがある場合に不適切な結果になる可能性があります。今回は該当するデータがないため、問題ありませんが、他のデータセットでは注意が必要です。
- 欠損値の種類: 今回は単純な欠損値(NaN)でしたが、データによっては「0」や「-1」などの特別な値が欠損を表している場合があります。その場合は、データの意味を理解した上で適切な処理を行う必要があります。
- fillnaメソッド: pandasのfillnaメソッドを使うことでも欠損値の補完は可能です。平均値や中央値で補完することもできます。
探索的データ分析
まず、データフレームの基本的な情報を確認します。
print(df.info())
print(df.describe())
y(宿泊価格)の分布を確認します。
import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(8, 6))
sns.histplot(df['y'], kde=True)
plt.title('Distribution of Price (y)')
plt.xlabel('Price')
plt.ylabel('Frequency')
plt.show()
次に、各特徴量と目的変数yの関係を分析します。
3-1: 数値データの特徴量
数値データの特徴量(accommodates, bathrooms, number_of_reviews, review_scores_rating, bedrooms, beds)とyの関係を散布図で確認します。
numerical_features = ['accommodates', 'bathrooms', 'number_of_reviews', 'review_scores_rating', 'bedrooms', 'beds']
for feature in numerical_features:
plt.figure(figsize=(8, 6))
sns.scatterplot(x=feature, y='y', data=df)
plt.title(f'{feature} vs Price')
plt.xlabel(feature)
plt.ylabel('Price')
plt.show()
3-2: カテゴリデータの特徴量
カテゴリデータの特徴量(property_type, room_type, bed_type, cleaning_fee, city, host_identity_verified, host_response_rate, instant_bookable)とyの関係を箱ひげ図で確認します。
categorical_features = ['property_type', 'room_type', 'bed_type', 'cleaning_fee', 'city', 'host_identity_verified', 'host_response_rate', 'instant_bookable']
for feature in categorical_features:
plt.figure(figsize=(10, 6))
sns.boxplot(x=feature, y='y', data=df)
plt.title(f'{feature} vs Price')
plt.xlabel(feature)
plt.ylabel('Price')
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
plt.show()
数値データ間の相関関係をヒートマップで確認します。
plt.figure(figsize=(10, 8))
sns.heatmap(df[numerical_features + ['y']].corr(), annot=True, cmap='coolwarm')
plt.title('Correlation Matrix')
plt.show()
ここまでのEDAの結果から、y(宿泊価格)に影響を与える可能性のある特徴量を6つ程度考えます。
- accommodates (収容人数): 散布図から、収容人数が増えるほど価格が上がる傾向が見られます。相関も比較的高いため、重要な要素と考えられます。
- bedrooms (ベッドルーム数): 散布図から、ベッドルーム数が増えるほど価格が上がる傾向が見られます。相関も比較的高いため、重要な要素と考えられます。
- beds (ベッド数): 散布図から、ベッド数が増えるほど価格が上がる傾向が見られます。相関も比較的高いため、重要な要素と考えられます。
- room_type (部屋タイプ): 箱ひげ図から、部屋タイプによって価格帯が大きく異なることがわかります。特に「Entire home/apt」は他のタイプよりも高価格帯にあるため、重要な要素と考えられます。
- property_type (物件タイプ): 箱ひげ図から、物件タイプによって価格帯が異なることがわかります。特に「Townhouse」や「House」などは高価格帯となる傾向がみられます。
- review_scores_rating (レビュー評価): 散布図から、レビュー評価が高いほど価格が上がる傾向が見られます。相関も比較的高いため、重要な要素と考えられます。
これらの特徴量は、宿泊価格を予測する上で重要な役割を果たす可能性があります。もちろん、これ以外にも影響を与える特徴量があるかもしれませんが、まずはこれらの特徴量に着目してモデル構築を進めていくのが良いでしょう。
物件データの加工
まず、y(宿泊価格)に影響を与える可能性が高いと仮定した6つの特徴量を再確認します。
- accommodates (収容人数)
- bedrooms (ベッドルーム数)
- beds (ベッド数)
- room_type (部屋タイプ)
- property_type (物件タイプ)
- review_scores_rating (レビュー評価)
まず、数値特徴量 (accommodates, bedrooms, beds, review_scores_rating) について、必要に応じてスケーリングや変換を行います。
from sklearn.preprocessing import StandardScaler
# StandardScaler をインスタンス化
scaler = StandardScaler()
# 数値特徴量を標準化
numerical_features = ['accommodates', 'bedrooms', 'beds', 'review_scores_rating']
df[numerical_features] = scaler.fit_transform(df[numerical_features])
print(df[numerical_features].head())
次に、カテゴリ特徴量 (room_type, property_type) をone-hotエンコーディングで数値に変換します。
# One-Hot Encoding
df = pd.get_dummies(df, columns=['room_type', 'property_type'], drop_first=True)
print(df.head())
cleaning_fee、host_identity_verified、instant_bookableについてもone-hotエンコーディングをします。
host_response_rateは数値データですが、ここではカテゴリデータとして扱い、binningしてからone-hotエンコーディングをします。
# 'cleaning_fee', 'host_identity_verified', 'instant_bookable'のone-hotエンコーディング
df = pd.get_dummies(df, columns=['cleaning_fee', 'host_identity_verified', 'instant_bookable'], drop_first=True)
# 'host_response_rate'をbinning
bins = [0, 70, 80, 90, 100]
labels = ['70%以下', '70%~80%', '80%~90%', '90%以上']
df['host_response_rate_binned'] = pd.cut(df['host_response_rate'].str.replace('%', '').astype(int), bins=bins, labels=labels, right=False)
# 'host_response_rate_binned'のone-hotエンコーディング
df = pd.get_dummies(df, columns=['host_response_rate_binned'], drop_first=True)
# 不要になった'host_response_rate'列を削除
df = df.drop('host_response_rate', axis=1)
print(df.head())
加工後の特徴量とyとの相関を再度確認し、仮説を検証します。
# 相関行列の確認
features_for_corr = numerical_features + [col for col in df.columns if 'room_type_' in col or 'property_type_' in col or 'cleaning_fee_' in col or 'host_identity_verified_' in col or 'instant_bookable_' in col or 'host_response_rate_binned_' in col ] + ['y']
plt.figure(figsize=(15, 10))
sns.heatmap(df[features_for_corr].corr(), annot=True, cmap='coolwarm')
plt.title('Correlation Matrix After Feature Engineering')
plt.show()
最後に、加工後のデータフレームの情報を確認します。
print(df.info())
print(df.head())
データ加工の結果、不要になった特徴量(‘id’,’city’,’name’)を削除します。
df = df.drop(['id','city','name'],axis=1)
print(df.head())
- 数値特徴量のスケーリング: 数値特徴量を標準化することで、スケールが異なっていた特徴量を同じスケールで扱うことができるようになりました。これにより、モデルが特定のスケールの特徴量に偏って学習することを防ぎます。
- カテゴリ特徴量のOne-Hot Encoding: カテゴリ特徴量をOne-Hot Encodingで数値に変換することで、モデルがこれらの特徴量を扱えるようになりました。これにより、カテゴリ特徴量の情報をモデルの学習に活用できます。
- 相関の確認: 加工後の特徴量とyの相関を再度確認することで、どの特徴量がyに対してより強く影響を与えるか、より正確に把握できました。
- その他の特徴量: cleaning_fee、host_identity_verified、instant_bookable、host_response_rateを数値化することでモデルが利用可能になり、より詳細な分析に活用できます。
予測モデルの作成
まず、データを特徴量(X)と目的変数(y)に分割します。
from sklearn.model_selection import train_test_split
# 特徴量と目的変数に分割
X = df.drop('y', axis=1) # 'y' 列以外を特徴量とする
y = df['y'] # 'y' 列を目的変数とする
# 学習データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print("X_train shape:", X_train.shape)
print("y_train shape:", y_train.shape)
print("X_test shape:", X_test.shape)
print("y_test shape:", y_test.shape)
LinearRegressionモデルを作成し、学習データで学習させます。
from sklearn.linear_model import LinearRegression
# モデルのインスタンス化
model = LinearRegression()
# モデルの学習
model.fit(X_train, y_train)
テストデータを使ってモデルの性能を評価します。
from sklearn.metrics import mean_squared_error, r2_score
# テストデータで予測
y_pred = model.predict(X_test)
# モデルの評価
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"Mean Squared Error: {mse:.2f}")
print(f"R-squared: {r2:.2f}")
学習したモデルを使って、新しいデータの予測を行います。
# 新しいデータ(例)
new_data = pd.DataFrame({
'accommodates': [3],
'bedrooms': [1],
'beds': [2],
'review_scores_rating': [4.5],
'room_type_Private room': [0],
'room_type_Shared room': [0],
'property_type_Condominium': [0],
'property_type_House': [0],
'property_type_Townhouse': [0],
'cleaning_fee_TRUE':[1],
'host_identity_verified_TRUE':[1],
'instant_bookable_TRUE':[0],
'host_response_rate_binned_70%~80%':[1],
'host_response_rate_binned_80%~90%':[0],
'host_response_rate_binned_90%以上':[0]
})
# 数値特徴量のスケーリングを適用
new_data[numerical_features] = scaler.transform(new_data[numerical_features])
# 予測
predicted_price = model.predict(new_data)
print(f"Predicted price: {predicted_price[0]:.2f}")
モデルの学習結果から、各特徴量の係数(重み)を確認します。
# 係数 (重み) を表示
coefficients = pd.DataFrame({'feature': X.columns, 'coefficient': model.coef_})
print(coefficients)
テストデータを使って予測を行い、RMSEを計算します。
# テストデータで予測
y_pred = model.predict(X_test)
# 平均二乗誤差(MSE)を計算
mse = mean_squared_error(y_test, y_pred)
# RMSEを計算
rmse = np.sqrt(mse)
print(f"Root Mean Squared Error (RMSE): {rmse:.2f}")
まとめ
- データの準備: データを特徴量と目的変数に分割し、学習用とテスト用に分けました。
- モデルの学習: LinearRegression モデルを作成し、学習データを使って学習させました。
- モデルの評価: テストデータを使ってモデルの性能を評価しました(MSEとR2)。
- モデルの予測: 学習したモデルを使って、新しいデータの予測を行いました。
- 係数の確認: モデルの各特徴量に対する係数(重み)を確認しました。
モデルの改善
- 多項式特徴量の追加: 特徴量同士の交互作用を捉えるために、多項式特徴量を追加します。
- 正則化の導入: 過学習を抑制するために、Ridge回帰を試します。
- 交差検証による評価: モデルの汎化性能をより正確に評価するために、交差検証を行います。
まず、必要なライブラリをインポートします。
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, cross_val_score, KFold
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
データを特徴量(X)と目的変数(y)に分割し、学習データとテストデータに分割します。
# 特徴量と目的変数に分割
X = df.drop('y', axis=1) # 'y' 列以外を特徴量とする
y = df['y'] # 'y' 列を目的変数とする
# 学習データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
PolynomialFeaturesを使って、多項式特徴量を追加します。
# 多項式特徴量を追加
poly = PolynomialFeatures(degree=2, include_bias=False)
X_train_poly = poly.fit_transform(X_train)
X_test_poly = poly.transform(X_test)
print("Shape of X_train_poly:", X_train_poly.shape)
print("Shape of X_test_poly:", X_test_poly.shape)
多項式特徴量を追加した後、特徴量を標準化します。
# 特徴量の標準化
scaler = StandardScaler()
X_train_poly_scaled = scaler.fit_transform(X_train_poly)
X_test_poly_scaled = scaler.transform(X_test_poly)
Ridge回帰モデルを作成し、学習データで学習させます。
# Ridge回帰モデルの作成
ridge = Ridge(alpha=1.0) # alpha は正則化の強さを調整するハイパーパラメータ
ridge.fit(X_train_poly_scaled, y_train)
テストデータを使ってモデルの性能を評価します。
# テストデータで予測
y_pred_ridge = ridge.predict(X_test_poly_scaled)
# 平均二乗誤差(MSE)を計算
mse_ridge = mean_squared_error(y_test, y_pred_ridge)
# RMSEを計算
rmse_ridge = np.sqrt(mse_ridge)
print(f"Root Mean Squared Error (RMSE) with Ridge: {rmse_ridge:.2f}")
交差検証を使って、モデルの汎化性能をより正確に評価します。
# 交差検証の実施
cv = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(ridge, X_train_poly_scaled, y_train, cv=cv, scoring='neg_mean_squared_error')
rmse_scores = np.sqrt(-scores)
print(f"Cross-validation RMSE scores: {rmse_scores}")
print(f"Mean cross-validation RMSE: {rmse_scores.mean():.2f}")
学習したモデルを使って、新しいデータの予測を行います。
# 新しいデータ(例)
new_data = pd.DataFrame({
'accommodates': [3],
'bedrooms': [1],
'beds': [2],
'review_scores_rating': [4.5],
'room_type_Private room': [0],
'room_type_Shared room': [0],
'property_type_Condominium': [0],
'property_type_House': [0],
'property_type_Townhouse': [0],
'cleaning_fee_TRUE':[1],
'host_identity_verified_TRUE':[1],
'instant_bookable_TRUE':[0],
'host_response_rate_binned_70%~80%':[1],
'host_response_rate_binned_80%~90%':[0],
'host_response_rate_binned_90%以上':[0]
})
# 新しいデータに多項式特徴量を適用
new_data_poly = poly.transform(new_data)
# スケーリングを適用
new_data_poly_scaled = scaler.transform(new_data_poly)
# 予測
predicted_price = ridge.predict(new_data_poly_scaled)
print(f"Predicted price: {predicted_price[0]:.2f}")
- 多項式特徴量: 特徴量同士の交互作用を捉えることで、モデルの表現力が向上しました。
- Ridge回帰: 正則化を導入することで、過学習を抑制し、汎化性能が向上しました。
- 交差検証: モデルの汎化性能をより正確に評価できるようになりました。
- RMSE: Ridge回帰を適用することにより、RMSEが改善しました。(改善幅はデータによります。)
この結果を基に、さらにモデルの改善を試みることができます。
例えば、Ridge回帰のハイパーパラメータalphaの調整や、他の正則化手法(Lasso回帰)を試すことも有効です。
また、より複雑なモデル(例:Gradient Boosting Machinesなど)を検討するのも良いでしょう。
コメント