PythonとGridDBを用いて住宅データの線形回帰モデルを構築する

このチュートリアルでは、Pythonを使用して住宅データセットを調査します。まず、必要に応じてデータセットのプルーニングを行います。その後、データセットに適合する機械学習モデルを構築し、将来の予測を行う方法を見ます。

チュートリアルの概要は以下の通りです。

1.  前提条件
2.  データセットについて
3.  ライブラリのインポート
4.  データセットの読み込み
5.  データの前処理
6.  データの正規化
7.  データセットの分割
8.  モデルの構築
9.  予測する
10. モデルの評価
11. 結論

## 1\. 前提条件

このチュートリアルは、Jupyter Notebooks (Anaconda version 4.8.3) と Python version 3.8 on Windows 10 Operating system を使用して実行されています。実行前に以下のパッケージがインストールされている必要があります。

1.  [Pandas][1]
2.  [scikit-learn][2]

Anaconda を使用している場合、パッケージはユーザーインターフェース、コマンドライン、または Jupyter Notebooks などの複数の方法でインストールすることができます。Python パッケージをインストールする最も一般的な方法は `pip` を使用する方法です。コマンドラインやターミナルを使用している場合は、`pip install package-name`と入力してください。パッケージをインストールするもう一つの方法は、Anaconda 環境内で `conda install package-name` を実行することです。

また、Python 環境でデータセットをロードする方法として、`Pandas` と `GridDB` を使用する方法を紹介します。Python環境でGridDBを使用するためには、以下のパッケージが必要です。

1.  [GridDB Cクライアント][3]
2.  SWIG (Simplified Wrapper and Interface Generator)
3.  [GridDB Pythonクライアント][4]

## 2\. データセットについて

我々は、公共のリソースから収集され、現在[Kaggle][5]で利用可能であるMelbourne Housing Datasetのスナップショットを使用する予定です。このデータセットはある程度前処理されており、合計13580個のインスタンスが含まれています。データセットに存在する属性の数は21です。従属変数は物件の価格であり、他の20属性は独立変数です。では、コードを書き始めましょう。

## 3\. ライブラリのインポート

```python
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split 
from sklearn.metrics import mean_absolute_error
```

ライブラリのインストールに成功した場合、上記のセルは何も出力されずに実行されるはずです。万が一、エラーが発生した場合は、以下を試してみてください。

1.  インストールが成功したかどうか再確認してください。うまくいかない場合は、もう一度 `pip install package-name` を実行してみてください。
2.  お使いのシステムが、パッケージのバージョンと互換性があるかどうか確認します。

## 4\. データセットの読み込み

### GridDBを利用する

[GridDB][6] は、大量のデータを扱うために設計されたオープンソースの時系列データベースです。IoTに最適化されており、インメモリアーキテクチャを採用しているため、高い効率性を誇ります。ローカルでファイルを扱うと、プロフェッショナルな環境では統合の問題が発生するため、信頼性の高いデータベースを使うことが重要になる。GridDBはその信頼性とフォールトトレランスによるスケーラビリティを提供します。

さらに、GridDBの[pythonクライアント][4]により、データベースをインクルードしてコーディング環境内で直接操作することが非常に容易になりました。GriDBのWebAPIについては[こちら][7]をご覧ください。

では、データセットをロードしてみましょう。

```python
import griddb_python as griddb

sql_statement = ('SELECT * FROM melb_data')
dataset = pd.read_sql_query(sql_statement, container
```

変数 `dataset` には、pandas の dataframe 形式でデータが格納されます。GridDBを初めて使う場合は、[こちら][8] のチュートリアルが参考になるかもしれません。

### Pandasの使用

データセットを読み込むもう一つの方法は、pandasを直接使用することです。

```python
dataset = pd.read_csv("melb_data.csv")
```

## 5\. データ前処理

素晴らしい！さて、データセットができたので、実際にどのように見えるか見てみましょう。

```python
dataset.head()
```

| |Suburb|Address|Rooms|Type|Price|Method|SellerG|Date|Distance|Postcode|...|Bathroom|Car|Landsize|BuildingArea|YearBuilt|CouncilArea|Lattitude|Longtitude|Regionname|Propertycount|
|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|
|          0|Abbotsford|85 Turner St|2|h|1480000.0|S|Biggin|3/12/2016|2.5|3067.0|...|1.0|1.0|202.0|NaN|NaN|Yarra|-37.7996|144.9984|Northern Metropolitan|4019.0|
|          1|Abbotsford|25 Bloomburg St|2|h|1035000.0|S|Biggin|4/02/2016|2.5|3067.0|...|1.0|0.0|156.0|79.0|1900.0|Yarra|-37.8079|144.9934|Northern Metropolitan|4019.0|
|          2|Abbotsford|5 Charles St|3|h|1465000.0|SP|Biggin|4/03/2017|2.5|3067.0|...|2.0|0.0|134.0|150.0|1900.0|Yarra|-37.8093|144.9944|Northern Metropolitan|4019.0|
|          3|Abbotsford|40 Federation La|3|h|850000.0|PI|Biggin|4/03/2017|2.5|3067.0|...|2.0|1.0|94.0|NaN|NaN|Yarra|-37.7969|144.9969|Northern Metropolitan|4019.0|
|          4|Abbotsford|55a Park St|4|h|1600000.0|VB|Nelson|4/06/2016|2.5|3067.0|...|1.0|2.0|120.0|142.0|2014.0|Yarra|-37.8072|144.9941|Northern Metropolitan|4019.0|

5 rows × 21 columns

```python
len(dataset)
```

```sh
    13580
```

列がたくさんあるのがわかるので、独立属性と従属属性のイメージをつかむために、列名をプリントアウトしてみましょう。

```python
dataset.columns
```

```sh
    Index(['Suburb', 'Address', 'Rooms', 'Type', 'Price', 'Method', 'SellerG',
           'Date', 'Distance', 'Postcode', 'Bedroom2', 'Bathroom', 'Car',
           'Landsize', 'BuildingArea', 'YearBuilt', 'CouncilArea', 'Lattitude',
           'Longtitude', 'Regionname', 'Propertycount'],
          dtype='object')
```

```python
dataset.describe()
```

| |Rooms|Price|Distance|Postcode|Bedroom2|Bathroom|Car|Landsize|BuildingArea|YearBuilt|Lattitude|Longtitude|Propertycount|
|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|
|          count|13580.000000|1.358000e+04|13580.000000|13580.000000|13580.000000|13580.000000|13518.000000|13580.000000|7130.000000|8205.000000|13580.000000|13580.000000|13580.000000|
|          mean|2.937997|1.075684e+06|10.137776|3105.301915|2.914728|1.534242|1.610075|558.416127|151.967650|1964.684217|-37.809203|144.995216|7454.417378|
|          std|0.955748|6.393107e+05|5.868725|90.676964|0.965921|0.691712|0.962634|3990.669241|541.014538|37.273762|0.079260|0.103916|4378.581772|
|          min|1.000000|8.500000e+04|0.000000|3000.000000|0.000000|0.000000|0.000000|0.000000|0.000000|1196.000000|-38.182550|144.431810|249.000000|
|          25%|2.000000|6.500000e+05|6.100000|3044.000000|2.000000|1.000000|1.000000|177.000000|93.000000|1940.000000|-37.856822|144.929600|4380.000000|
|          50%|3.000000|9.030000e+05|9.200000|3084.000000|3.000000|1.000000|2.000000|440.000000|126.000000|1970.000000|-37.802355|145.000100|6555.000000|
|          75%|3.000000|1.330000e+06|13.000000|3148.000000|3.000000|2.000000|2.000000|651.000000|174.000000|1999.000000|-37.756400|145.058305|10331.000000|
|          max|10.000000|9.000000e+06|48.100000|3977.000000|20.000000|8.000000|10.000000|433014.000000|44515.000000|2018.000000|-37.408530|145.526350|21650.000000|

`describe` 関数の出力は、各属性の値が異なるスケールを持っていることを伝えています。そのため、モデルを構築する前に正規化する必要があります。

正規化する前に、価格と直接的に相関があると思われる属性のサブセットを取ることになります。

```python
dataset = dataset[["Rooms", "Price", "Bedroom2", "Bathroom","Landsize", "BuildingArea", "YearBuilt"]]
```

また、モデル構築に進む前に、データにヌル値が含まれていないことを確認する必要があります。

```python
dataset.isna().sum()
```

```sh
    Rooms              0
    Price              0
    Bedroom2           0
    Bathroom           0
    Landsize           0
    BuildingArea    6450
    YearBuilt       5375
    dtype: int64
```

見てわかるように、この2つの属性にはいくつかのNULL値が含まれています。それらを取り除いてから先に進みましょう。

```python
dataset = dataset.dropna()
```

```python
len(dataset)
```

```sh
    6858
```

ここで、`HouseAge`という新しい属性を作成します。この属性の値は、`YearBuilt`属性から現在の年を差し引くことで得られます。これは、もう日付を扱う必要がないため便利です。すべての属性が数値で表されるようになったので、後の機械学習で役に立ちそうです。

```python
dataset['HouseAge'] = 2022 - dataset["YearBuilt"].astype(int)
```

```python
dataset.head()
```

| |Rooms|Price|Bedroom2|Bathroom|Landsize|BuildingArea|YearBuilt|HouseAge|
|:----|:----|:----|:----|:----|:----|:----|:----|:----|
|          1|2|1035000.0|2.0|1.0|156.0|79.0|1900.0|122|
|          2|3|1465000.0|3.0|2.0|134.0|150.0|1900.0|122|
|          4|4|1600000.0|3.0|1.0|120.0|142.0|2014.0|8|
|          6|3|1876000.0|4.0|2.0|245.0|210.0|1910.0|112|
|          7|2|1636000.0|2.0|1.0|256.0|107.0|1890.0|132|

素晴らしい! `YearBuilt` 属性はもう必要ないので削除しましょう。

```python
dataset = dataset.drop("YearBuilt", axis=1)
```

```python
dataset.head()
```

| |Rooms|Price|Bedroom2|Bathroom|Landsize|BuildingArea|HouseAge|
|:----|:----|:----|:----|:----|:----|:----|:----|
|          1|2|1035000.0|2.0|1.0|156.0|79.0|122|
|          2|3|1465000.0|3.0|2.0|134.0|150.0|122|
|          4|4|1600000.0|3.0|1.0|120.0|142.0|8|
|          6|3|1876000.0|4.0|2.0|245.0|210.0|112|
|          7|2|1636000.0|2.0|1.0|256.0|107.0|132|

## 6\. データ正規化

先に見たように、属性の値は異なるスケールを持っているため、値の大きい特徴が小さい特徴よりも優勢になり、格差が生じる可能性があります。したがって、すべての値を1つのスケールにすることが重要です。そのために、「Min-Max Normalization」を使用することになります。これは最も一般的な手法の一つで、最小値は0に、最大値は1に変換されます。他のすべての値は0と1の間に広がります。

正規化のための直接的な方法は存在しますが、それらはデータフレームをNumPyの配列に変換します。したがって、我々は、列名を失います。そのため、我々は、データフレームを取り込み、新しい正規化されたデータフレームを返す独自のメソッドを定義します。

```python
def normalize(df):
    result = df.copy()
    for feature_name in df.columns:
        max_value = df[feature_name].max()
        min_value = df[feature_name].min()
        result[feature_name] = (df[feature_name] - min_value) / (max_value - min_value)
    return result
```

```python
df = normalize(dataset)
```

正規化されたデータフレームを見てみましょう。

```python
df.head()
```
  
| |Rooms|Price|Bedroom2|Bathroom|Landsize|BuildingArea|HouseAge|
|:----|:----|:----|:----|:----|:----|:----|:----|
|          1|0.142857|0.101928|0.222222|0.000000|0.004216|0.025386|0.143552|
|          2|0.285714|0.150412|0.333333|0.142857|0.003622|0.048201|0.143552|
|          4|0.428571|0.165633|0.333333|0.000000|0.003243|0.045630|0.004866|
|          6|0.285714|0.196753|0.444444|0.142857|0.006622|0.067481|0.131387|
|          7|0.142857|0.169692|0.222222|0.000000|0.006919|0.034383|0.155718|

見てわかるように、すべての値は0と1の間にあります。次に、データセットを `train` と `test` に分割します。

## 7\. データセットの分割

ここでは、「70-30」の割合で train と test に分割することにします。より小さなデータセットの場合は、「80-20」のようにすることも可能です。

```python
train, test = train_test_split(df, test_size=0.3)
```

```python
len(train)
```

```sh
    4800
```

```python
len(test)
```

```sh
    2058
```

ここで、従属変数と独立変数を分離してみましょう。

```python
train_y = train[["Price"]]
train_x = train.drop(["Price"], axis=1)
test_y = test[["Price"]]
test_x = test.drop(["Price"], axis=1)
```

```python
train_x.head()
```
  
| |Rooms|Bedroom2|Bathroom|Landsize|BuildingArea|HouseAge|
|:----|:----|:----|:----|:----|:----|:----|
|          4860|0.285714|0.333333|0.142857|0.222054|0.041774|0.027981|
|          3434|0.285714|0.333333|0.142857|0.018459|0.064267|0.027981|
|          6048|0.285714|0.333333|0.285714|0.005973|0.049807|0.008516|
|          9918|0.142857|0.222222|0.000000|0.007135|0.031170|0.131387|
|          7855|0.142857|0.222222|0.000000|0.000000|0.030848|0.155718|

```python
train_y.head()
```
  
| |Price|
|:----|:----|
|          4860|0.103619|
|          3434|0.064494|
|          6048|0.055136|
|          9918|0.174879|
|          7855|0.053219|

## 8\. モデルの構築

今回は、「線形回帰」モデルを使用します。これは単純なデータセットなので、線形回帰モデルで十分でしょう。より洗練されたモデルを作るには、決定木（Decision Trees）を使うこともできます。

GridDBとPythonによる線形回帰の詳細は[こちら][9]をご覧ください。

```python
model =  LinearRegression()
model.fit(train_x, train_y)
```

```sh
    LinearRegression()
```

## 9\. 予測する

それでは、`test`データセットに対して予測を行ってみましょう。

```python
predictions = model.predict(test_x)
```

```python
predictions
```

```sh
    array([[0.0890521 ],
           [0.06244483],
           [0.13166691],
           ...,
           [0.09182388],
           [0.20981148],
           [0.1077662 ]])
```

## 10\. モデル評価

予測がどの程度優れているかを定量化するために、`sklearn`ライブラリが提供するいくつかの[メトリクス][10]があります。ここでは、線形回帰モデルでよく使われる[mean_absolute_error][11]メトリクスを使用することにします。

```python
mean_absolute_error(predictions, test_y)
```

```sh
    0.035125149637253696
```

素晴らしい！我々のモデルは平均絶対誤差が`0.03`で、線形回帰モデルとしては悪いスタートではありません。

## 11\. 結論

このチュートリアルでは、住宅データセットに対してどのように機械学習モデルを構築することができるかを見てきました。最初に、データセットを環境に読み込むための2つの方法、`GridDB`とPandasを取り上げました。また、必要に応じてデータセットのプルーニングを行いました。その後、`sklearn`ライブラリが提供する `Linear Regression` 関数を使用して、データセットにフィットさせました。

GridDBとPythonによるリアルタイム予測について詳しく知りたい方は[こちらのブログ][12]もご覧ください。

 [1]: https://pandas.pydata.org/docs/getting_started/install.html
 [2]: https://pypi.org/project/scikit-learn/
 [3]: https://github.com/griddb/c_client
 [4]: https://github.com/griddb/python_client
 [5]: https://www.kaggle.com/dansbecker/melbourne-housing-snapshot
 [6]: https://griddb.net/ja/
 [7]: https://griddb.net/ja/blog/griddb-webapi/
 [8]: https://griddb.net/ja/blog/using-pandas-dataframes-with-griddb/
 [9]: https://griddb.net/ja/blog/create-a-machine-learning-model-using-griddb/
 [10]: https://scikit-learn.org/stable/modules/model_evaluation.html
 [11]: https://scikit-learn.org/stable/modules/model_evaluation.html#mean-squared-error
 [12]: https://griddb.net/ja/blog/performing-real-time-predictions-using-machine-learning-griddb-and-python/