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
  2. scikit-learn

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

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

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

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

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

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

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

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

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

import griddb_python as griddb

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

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

Pandasの使用

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

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

5. データ前処理

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

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

len(dataset)
    13580

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

dataset.columns
    Index(['Suburb', 'Address', 'Rooms', 'Type', 'Price', 'Method', 'SellerG',
           'Date', 'Distance', 'Postcode', 'Bedroom2', 'Bathroom', 'Car',
           'Landsize', 'BuildingArea', 'YearBuilt', 'CouncilArea', 'Lattitude',
           'Longtitude', 'Regionname', 'Propertycount'],
          dtype='object')
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 関数の出力は、各属性の値が異なるスケールを持っていることを伝えています。そのため、モデルを構築する前に正規化する必要があります。

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

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

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

dataset.isna().sum()
    Rooms              0
    Price              0
    Bedroom2           0
    Bathroom           0
    Landsize           0
    BuildingArea    6450
    YearBuilt       5375
    dtype: int64

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

dataset = dataset.dropna()
len(dataset)
    6858

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

dataset['HouseAge'] = 2022 - dataset["YearBuilt"].astype(int)
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 属性はもう必要ないので削除しましょう。

dataset = dataset.drop("YearBuilt", axis=1)
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の配列に変換します。したがって、我々は、列名を失います。そのため、我々は、データフレームを取り込み、新しい正規化されたデータフレームを返す独自のメソッドを定義します。

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
df = normalize(dataset)

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

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の間にあります。次に、データセットを traintest に分割します。

7. データセットの分割

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

train, test = train_test_split(df, test_size=0.3)
len(train)
    4800
len(test)
    2058

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

train_y = train[["Price"]]
train_x = train.drop(["Price"], axis=1)
test_y = test[["Price"]]
test_x = test.drop(["Price"], axis=1)
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
train_y.head()
Price
4860 0.103619
3434 0.064494
6048 0.055136
9918 0.174879
7855 0.053219

8. モデルの構築

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

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

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

9. 予測する

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

predictions = model.predict(test_x)
predictions
    array([[0.0890521 ],
           [0.06244483],
           [0.13166691],
           ...,
           [0.09182388],
           [0.20981148],
           [0.1077662 ]])

10. モデル評価

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

mean_absolute_error(predictions, test_y)
    0.035125149637253696

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

11. 結論

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

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

ブログの内容について疑問や質問がある場合は Q&A サイトである Stack Overflow に質問を投稿しましょう。 GridDB 開発者やエンジニアから速やかな回答が得られるようにするためにも "griddb" タグをつけることをお忘れなく。 https://stackoverflow.com/questions/ask?tags=griddb

Leave a Reply

Your email address will not be published. Required fields are marked *