このチュートリアルでは、Pythonを使用して住宅データセットを調査します。まず、必要に応じてデータセットのプルーニングを行います。その後、データセットに適合する機械学習モデルを構築し、将来の予測を行う方法を見ます。
チュートリアルの概要は以下の通りです。
- 前提条件
- データセットについて
- ライブラリのインポート
- データセットの読み込み
- データの前処理
- データの正規化
- データセットの分割
- モデルの構築
- 予測する
- モデルの評価
- 結論
1. 前提条件
このチュートリアルは、Jupyter Notebooks (Anaconda version 4.8.3) と Python version 3.8 on Windows 10 Operating system を使用して実行されています。実行前に以下のパッケージがインストールされている必要があります。
Anaconda を使用している場合、パッケージはユーザーインターフェース、コマンドライン、または Jupyter Notebooks などの複数の方法でインストールすることができます。Python パッケージをインストールする最も一般的な方法は pip
を使用する方法です。コマンドラインやターミナルを使用している場合は、pip install package-name
と入力してください。パッケージをインストールするもう一つの方法は、Anaconda 環境内で conda install package-name
を実行することです。
また、Python 環境でデータセットをロードする方法として、Pandas
と GridDB
を使用する方法を紹介します。Python環境でGridDBを使用するためには、以下のパッケージが必要です。
- GridDB Cクライアント
- SWIG (Simplified Wrapper and Interface Generator)
- 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
ライブラリのインストールに成功した場合、上記のセルは何も出力されずに実行されるはずです。万が一、エラーが発生した場合は、以下を試してみてください。
- インストールが成功したかどうか再確認してください。うまくいかない場合は、もう一度
pip install package-name
を実行してみてください。 - お使いのシステムが、パッケージのバージョンと互換性があるかどうか確認します。
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の間にあります。次に、データセットを train
と test
に分割します。
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によるリアルタイム予測について詳しく知りたい方はこちらのブログもご覧ください。