Pythonで不動産価格予測

●データ分析のワークフロー
問題の定義
教師あり学習により、東京都足立区と神奈川県川崎市の中古マンション価格を予測する。

訓練およびテストデータの取得
国土交通省土地総合情報システム
https://www.land.mlit.go.jp/webland/

学習データ
・東京都足立区(2010年第1四半期から2020年第3四半期まで)
・神奈川県川崎市(2010年第1四半期から2020年第3四半期まで)

データの項目(特徴量)を見る・・・[欠損数][欠損無し数][unique数][unique項目]
(1) No. [0][14432][14432] int64★★★
(2) 種類 [0][14432][3]['宅地(土地と建物)', '宅地(土地)', '中古マンション等'] object
(3) 地域 [5117][9315][5]['住宅地', '商業地', nan, '工業地', '宅地見込地'] object
(4) 市区町村コード [0][14432][1] int64★★★
(5) 都道府県名 [0][14432][1] object
(6) 市区町村名 [0][14432][1] object
(7) 地区名 [0][14432][83] object
(8) 最寄駅：名称 [1][14431][37] object
(9) 最寄駅：距離（分）[12][14420][34] object★★★⇒floatに
(10) 取引価格（総額）[0][14432][255] float64★★★★目的変数
(11) 坪単価 [11635][2797][127] float64★★★
(12) 間取り [9474][4958][22] object
(13) 面積（㎡） [0][14432][128] object★★★⇒floatに
(14) 取引価格（㎡単価）[11635][2797][132] float64★★★
(15) 土地の形状 [8091][9310][9] object
(16) 間口 [][8166][267] object
(17) 延床面積（㎡）[][6341][121] object★★★⇒floatに
(18) 建築年 [3120][11312][74] object★★★★★★★★★★★★★★★★
(19) 建物の構造 [3066][11366][14] object
(20) 用途 [3699][10733][68] object
(21) 今後の利用目的 [6461][7971][6] object
(22) 前面道路：方位 [5117][9315][9] object
(23) 前面道路：種類 [5194][9238][12] object
(24) 前面道路：幅員（ｍ） [5227][9205][199] float64★★★
(25) 都市計画 [12][14420][12] object
(26) 建ぺい率（％）[43][14389][6] float64★★★
(27) 容積率（％）[43][14389][9] float64★★★
(28) 取引時点 [0][14432][43] object
(29) 改装 [9718][4714][2] object
(30) 取引の事情等 [13252][1180][12] object/私道を含む取引/nan/調停・競売等/調停・競売等、私道を含む取引/隣地の購入/その他事情有り/関係者間取引/隣地の購入、私道を含む取引/関係者間取引、私道を含む取引/隣地の購入、関係者間取引/古屋付き・取壊し前提/瑕疵有りの可能性/他の権利・負担付き/

### 必要なライブラリのインポート

```python
import os
import re
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import sklearn
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
from sklearn.linear_model import ElasticNet
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Activation,Dense,Dropout,Input,BatchNormalization
```

### データ読込

```python
path_data = r"C:\Users\take_\OneDrive\デスクトップ\work\008_python\080_不動産価格解析\00_データ\01_東京都足立区\13121_20101_20203\13121_20101_20203data.csv"
df_data = pd.read_csv(path_data,encoding="cp932")
```

### データ前処理

```python
#df_data #データの概要を見る。
#df_data.sample(frac=0.01).head(10) #ランダムに1%のデータを抽出し、先頭から10件データを表示。
#df_data.columns #特徴量を見る。
#df_data.nunique() #Unique数を見る。

#df_data['改装'].unique() #Uniqueの項目を見る

#df_data['取引価格（総額）'].describe() #販売価格の統計量の表示
#sns.distplot(df_data['取引価格（総額）']) #ヒストグラムの表示
#df_data.isnull().sum() #各項目に対し、欠損数を確認
#sns.boxplot(y=df_data['取引価格（総額）']) #外れ値の確認(一次元
#sns.jointplot('面積（㎡）', '取引価格（総額）', data=df_data) #外れ値の確認(二次元)

df_data['最寄駅：距離（分）'] = df_data['最寄駅：距離（分）'].astype(float)
df_data['面積（㎡）'] = df_data['面積（㎡）'].astype(float)


#df_data.dtypes
df_data['種類'].unique() #Uniqueの項目を見る
df_data['地域'].unique() #Uniqueの項目を見る
```

### 和暦を西歴に変換

```python
#nanとnone、空白の違いに注意。正規表現で探せなくなる。
#print(df_data['建築年'])
df=df_data['建築年'].fillna('')
#print(df)

kenchikunen_list=df.tolist()

#kenchikunen_list = ['平成2年','平成10年','昭和40年','平成元年','令和2年']
#print(kenchikunen_list)

for i in range(len(kenchikunen_list)):
    if re.match('昭和元年', kenchikunen_list[i]):
        kenchikunen_list[i] = 1926
    elif re.match('昭和',kenchikunen_list[i]):
        kenchikunen_list[i] = 1925 + int(re.sub(r'\D','',kenchikunen_list[i]))
    elif re.match('平成元年', kenchikunen_list[i]):
        kenchikunen_list[i] = 1989
    elif re.match('平成',kenchikunen_list[i]):
        kenchikunen_list[i] = 1988 + int(re.sub(r'\D','',kenchikunen_list[i]))     
    elif re.match('令和元年',kenchikunen_list[i]):
        kenchikunen_list[i] = 2019     
    elif re.match('令和',kenchikunen_list[i]):
        kenchikunen_list[i] = 2018 + int(re.sub(r'\D','',kenchikunen_list[i]))
    elif re.match('戦前',kenchikunen_list[i]):
        kenchikunen_list[i] = 1945

    else:
        continue

df_temp = pd.DataFrame({'建築年(西暦)':kenchikunen_list})
df_data2 = pd.concat([df_data,df_temp],axis=1)
df_data2['建築年(西暦)'].replace('', np.nan, inplace=True)
df_data2['建築年(西暦)'] = df_data2['建築年(西暦)'].astype(float)

df_data2.info()
```

```python
df_data_rich = df_data2.loc[df_data['取引価格（総額）'] >= 100000000]
#df_data_rich
df_data_normal = df_data2.loc[df_data['取引価格（総額）'] < 60000000]
#df_data_normal
#sns.boxplot(y=df_data_normal['取引価格（総額）']) #外れ値の確認(一次元)
#sns.jointplot('面積（㎡）', '取引価格（総額）', data=df_data_normal) #外れ値の確認(二次元)
#特徴量をピボットテーブルで確認
#df_data_normal[["面積（㎡）", "取引価格（総額）"]].groupby(["面積（㎡）"], as_index=False).mean().sort_values(by="面積（㎡）", ascending=False)
```


### データ型の確認

```python
df_data_normal.info()
```

### ヒストグラムの表示¶

```python
sns.set(font='Yu Gothic')
#sns.distplot(df_data_normal[['取引価格（総額）']]) #ヒストグラムの確認
sns.distplot(df_data_normal[['最寄駅：距離（分）']]) #ヒストグラムの確認
#sns.distplot(df_data_normal[['取引価格（㎡単価）']]) #ヒストグラムの確認
#sns.distplot(df_data_normal[['坪単価']]) #ヒストグラムの確認
#sns.distplot(df_data_normal[['面積（㎡）']]) #ヒストグラムの確認
#sns.distplot(df_data_normal[['延床面積（㎡）']]) #ヒストグラムの確認
#sns.distplot(df_data_normal[['前面道路：幅員（ｍ）']]) #ヒストグラムの確認
#sns.distplot(df_data_normal[['建ぺい率（％）']]) #ヒストグラムの確認
#sns.distplot(df_data_normal[['容積率（％）']]) #ヒストグラムの確認
#sns.distplot(df_data_normal[['建築年(西暦)']]) #ヒストグラムの確認
```

![ヒストグラム_v1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1304222/50e04a57-0ebe-6ca8-27bc-59562495c6cb.png)

### ヒートマップの表示

```python
k = 12
cols = corrmat.nlargest(k, '取引価格（総額）')['取引価格（総額）'].index
cm = np.corrcoef(df_data_normal[cols].values.T) # .T(転置行列)を行う理由は、corrcoefで相関を算出する際に、各カラムの値を行毎にまとめなければならない為
sns.set(font_scale=1.0) # ヒートマップのフォントサイズを指定
sns.set(font='Yu Gothic')
# 算出した相関データをヒートマップで表示
sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 10}, yticklabels=cols.values, xticklabels=cols.values)
```

### 相関の強い項目の散布図の表示

```python
sns.set(font='Yu Gothic')
cols = ['取引価格（総額）', '面積（㎡）', '最寄駅：距離（分）','建築年(西暦)'] #
sns.pairplot(df_data_normal[cols], height = 2.5)
```

![散布図v1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1304222/348aff11-f79c-b4af-04c7-322da766d502.png)




### 単回帰分析と散布図

```python
# 最寄駅：距離（分）を使用してモデルを学習
# Xに最寄駅：距離（分）、yに取引価格（総額）をセット
X = df_data_normal[["最寄駅：距離（分）"]].values
y = df_data_normal["取引価格（総額）"].values
slr = LinearRegression() # アルゴリズムに線形回帰
slr.fit(X,y) # fit関数で学習
# 偏回帰係数を出力
print('傾き：{0}'.format(slr.coef_[0]))
# y切片(直線とy軸との交点)を出力
print('y切片: {0}'.format(slr.intercept_))
plt.scatter(X,y)
plt.plot(X,slr.predict(X),color='red')
```

![単回帰分析.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1304222/bd97d658-fde4-6513-7f03-8ace594a86dd.png)


### 線形回帰分析

```python
# 取引価格（総額）と相関の高い面積（㎡）と建築年(西暦)を説明変数に使用
X = df_data_normal[["面積（㎡）", "最寄駅：距離（分）","建築年(西暦)"]].values
y = df_data_normal["取引価格（総額）"].values


train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)


max_score = 0
best_model = ""

# 線形回帰
model = LinearRegression() # アルゴリズムに線形回帰(Linear Regression)を採用
model.fit(train_X,train_y) # 学習
score = model.score(test_X, test_y)

# リッジ回帰
model = Ridge()
model.fit(train_X, train_y)
score = model.score(test_X, test_y)
if max_score < score:
    max_score = score
    best_model = "リッジ回帰"

# ラッソ回帰
model = Lasso()
model.fit(train_X, train_y)
score = model.score(test_X, test_y)
if max_score < score:
    max_score = score
    best_model = "ラッソ回帰"

#  ElasticNet回帰
model = ElasticNet(l1_ratio=0.5)
model.fit(train_X, train_y)
score = model.score(test_X, test_y)
if max_score < score:
    max_score = score
    best_model = "ElasticNet回帰"


print("モデル:{}".format(best_model))
print("決定係数:{}".format(max_score))
    
# 偏回帰係数はscikit-learnのcoefで取得
print('傾き：{0}'.format(model.coef_))
a1, a2 ,a3= model.coef_

# y切片(直線とy軸との交点)を出力
print('y切片: {0}'.format(model.intercept_))
b = model.intercept_

pred_y = model.predict(test_X)
#print(pred_y)

# test_X, test_yに対する決定係数を出力
print(model.score(test_X, test_y))
```


### 感想

今回は線形回帰モデルを使って中古マンションの価格を予測するモデルを作成しました。
一連の流れをつめたと思うので、他のデータにも適用するということを行って、型を身につけたいと思いました。ランダムフォレスト、XGBoost、LightGBMなどで特徴量解析などにもつなげていきたいと思います。