はじめてのデータ分析　重回帰分析

この記事はNTTテクノクロス Advent Calendar 2024　シリーズ2の13日目です。

NTTテクノクロスの大澤と申します。
普段はPythonを用いてデータ分析業務を担当しており、去年はデータ分析初学者向けに、Pythonを使ってデータ前処理から回帰直線で数値を予測するところまでを記事にしました。

今年は説明変数を増やして重回帰分析をして、簡単ではありますが評価するところまでを記事にしました。

## 使用するデータの紹介

今回使用するデータはscikit-learnから提供されている、カリフォルニアの住宅価格データセットを使用して、住宅の価格を予測するモデルを構築してみます。

カラム構成も併せて記載します。

- カリフォルニアの住宅価格データセット
  - https://scikit-learn.org/dev/modules/generated/sklearn.datasets.fetch_california_housing.html

- カラム構成
    - MedInc：所得の中央値
    - HouseAge：築年数の中央値
    - AveRooms：1世帯当たりの平均部屋数
    - AveBedrms：1世帯当たりの平均寝室数
    - Population：地区の人口
    - AveOccup：平均世帯人員数
    - Latitude：緯度
    - Longitude：経度

## 可視化

予測したいのは住宅価格なので、その他の各カラムとどのような関係を持っているかを可視化してみます。

```python
# ライブラリのインポート
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
import japanize_matplotlib
```
```python
# カリフォルニアの住宅価格データセットをロード
california_housing = fetch_california_housing()

# データをデータフレームに格納
df = pd.DataFrame(california_housing.data, columns=california_housing.feature_names)
df['Price'] = california_housing.target

# データの確認
df.head(5)
```


||MedInc|HouseAge|AveRooms|AveBedrms|Population|AveOccup|Latitude|Longitude|Price|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|0|8.3252|41.0|6.984127|1.023810|322.0|2.555556|37.88|-122.23|4.526|
|1|8.3014|21.0|6.238137|0.971880|2401.0|2.109842|37.86|-122.22|3.585|
|2|7.2574|52.0|8.288136|1.073446|496.0|2.802260|37.85|-122.24|3.521|
|3|5.6431|52.0|5.817352|1.073059|558.0|2.547945|37.85|-122.25|3.413|
|4|3.8462|52.0|6.281853|1.081081|565.0|2.181467|37.85|-122.25|3.422|


```python
# 各特徴量と目的変数の散布図を描画
# サイズの関係上、一部抜粋して添付
sns.set(style="whitegrid")
for column in df.columns:
    if column == 'Price':
        continue  # "Price"カラムの場合は散布図の描画をスキップ
    plt.figure(figsize=(8, 6))
    sns.scatterplot(data=df, x=column, y='Price')
    plt.title(f'{column}_Price')
    plt.xlabel(column)
    plt.ylabel('Price')
    plt.show()
```

![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3626384/7d421a4a-6b6b-724c-1c46-4830f530e2af.png)


![ダウンロード (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3626384/b62d1a8e-1260-959d-bf5e-013dc40256dd.png)

## カラムを絞って価格の予測

すべての散布図を見ていただくとわかると思いますが、
テストデータということもありまんべんなく分布されているような散布図になっており選定しづらいです。
本来はこのように可視化をした結果からどのようなカラムと相関があるかを確認したり、新しくデータを作成したり、加工したデータと見比べてみたりして説明変数の選定をしていきます。

今回は、一般的に住宅の価格に影響を与えていそうなカラムを主観で選定してみてモデルを作ってみようと思います。
- HouseAge：築年数の中央値
- AveRooms：1世帯当たりの平均部屋数
- AveBedrms：1世帯当たりの平均寝室数
- Population：地区の人口

```python
# カリフォルニアの住宅価格データセットをロード
california_housing = fetch_california_housing()

# データをデータフレームに格納
df = pd.DataFrame(california_housing.data, columns=california_housing.feature_names)
df['Price'] = california_housing.target

# 説明変数と目的変数に分割
selected_features = ['HouseAge', 'AveRooms', 'AveBedrms', 'Population']
X = df[selected_features]
y = df['Price']
```

```python
# データをトレーニングセットとテストセットに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 線形回帰モデルを作成
model = LinearRegression()

# モデルをトレーニングデータにフィット
model.fit(X_train, y_train)

# テストデータを用いて予測
y_pred = model.predict(X_test)
```

## 評価

これでモデルの構築は完了しましたが、このモデルがどれくらい信用できるかを確認する必要があります。

去年実施した単回帰の場合はグラフを作成することができたので、直感的にこのモデルは良い、悪いの判断ができましたが、重回帰の場合グラフを図示するのは難しいです。

このような場合に（単回帰でも使用できますが）、モデルの良し悪しを判断できる数値化した指標がいくつかあるのですが、今回は二つの指標をざっくり紹介します。
詳細はscikit-learnの公式ドキュメントなどのサイトをご覧ください。


  - MSE
    - 値が小さいほうが精度がいい
    ```math
    \displaylines{
    MSE = \frac{1}{n} \sum_{k=1}^{n} (y_i - \hat{y_i})^2\
    }
    ```
    ```math
    y_i:実際の値  \quad \hat{y_i}:予測値
    ```

    - [MSE scikit-learn公式ドキュメント](https://scikit-learn.org/1.5/modules/generated/sklearn.metrics.mean_squared_error.html)

  - 決定係数
    - 0-1をとり、0に行くほど精度悪く、1に行くほど精度がいいが、1に近すぎると過学習が疑われる。

     ```math
      R^2 = 1- \frac{\sum_{k=1}^{n} (y_i - \hat{y_i})^2}{\sum_{k=1}^{n} (y_i - \bar{y_i})^2}
      ```
    ```math
       \bar{y_i}:実際の値全体の平均値
    ```

    -  [決定係数 scikit-learn公式ドキュメント](https://scikit-learn.org/1.5/modules/generated/sklearn.metrics.r2_score.html)


```python
# モデルの評価
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)

print(f"Mean Squared Error: {mse}")
print(f"R^2 Score: {r2}")

# 回帰係数を表示
coefficients = pd.DataFrame(model.coef_, X.columns, columns=['Coefficient'])
print(coefficients)
```
```python
Mean Squared Error: 1.125648161565992
R^2 Score: 0.1409947731912915
            Coefficient
HouseAge       0.015889
AveRooms       0.349354
AveBedrms     -1.700036
Population     0.000033
```

結果として回帰係数も表示していますが、MSEは1.12、決定係数は0.14と算出されました。
どちらの評価軸でも精度はあまりよくなさそうなので、次はすべての説明変数を使用してモデルを構築してみます。

## すべてのカラムを使用して予測

```python
# 説明変数と目的変数に分ける
X = df.drop('Price', axis=1)
y = df['Price']

# データをトレーニングセットとテストセットに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
```

```python
# 線形回帰モデルを作成
model = LinearRegression()

# モデルをトレーニングデータにフィット
model.fit(X_train, y_train)

# テストデータを用いて予測
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}")
print(f"R^2 Score: {r2}")

# 回帰係数を表示
coefficients = pd.DataFrame(model.coef_, X.columns, columns=['Coefficient'])
print(coefficients)
```

```python
Mean Squared Error: 0.5558915986952444
R^2 Score: 0.5757877060324508
            Coefficient
MedInc         0.448675
HouseAge       0.009724
AveRooms      -0.123323
AveBedrms      0.783145
Population    -0.000002
AveOccup      -0.003526
Latitude      -0.419792
Longitude     -0.433708
```

結果として、説明変数を絞るよりはすべて使った方が精度が良い結果になりました。
今回もテストデータのため、説明変数の選定を順序だててできませんでしたが、実際のデータの場合は目的変数との相関を見ながら複数のモデルを構築することで良い結果が得られるかと思います。
重回帰だけでなく、様々な回帰の手法もあるため、うまくいかない場合は手法を変えるのも念頭に入れながら試行錯誤してみてください。