NumPyでmatrixなら行列の積を*演算子で書けるしPython3.5以上なら@演算子で書ける

Pythonの数値計算ライブラリであるNumPyについて、2014年くらいからちゃんと調査せずに使っていたが、@演算子で行列の積を計算できることを知って色々調べてみた。

そもそも、影響を受けてるはずのMATLABでも*演算子は行列の積だし、C++のEigenでも*演算子は行列の積なのに、何故NumPyだけ*演算子が要素積なんだろう。

なお、NumPyのmatrixについて紹介するが、公式ドキュメントには以下のように書かれており、基本的には使うべきでないかもしれない。

It is no longer recommended to use this class, even for linear algebra. Instead use regular arrays. The class may be removed in the future.

numpy.matrix — NumPy v1.16 Manual

普通の積はnp.dot()

import numpy as np

X = np.array([[0, 1],
             [2, 3]])
print(type(X))
# <class 'numpy.ndarray'>
print(np.dot(X, X))
# [[ 2  3]
#  [ 6 11]]

# メソッドでも可能
print(X.dot(X))
# [[ 2  3]
#  [ 6 11]]

matrixなら*演算子で書ける

X_mat = np.matrix([[0, 1], 
                 [2, 3]])
print(type(X_mat))
# <class 'numpy.matrixlib.defmatrix.matrix'>
print(X_mat * X_mat)
# [[ 2  3]
#  [ 6 11]]

# もちろんnp.dot()の可能
print(np.dot(X_mat, X_mat))
# [[ 2  3]
#  [ 6 11]]

Python3.5以上なら@演算子で書ける

X = np.array([[0, 1],
             [2, 3]])
print(X @ X)
# [[ 2  3]
#  [ 6 11]]

X_mat = np.matrix([[0, 1], 
                 [2, 3]])
print(X_mat @ X_mat)
# [[ 2  3]
#  [ 6 11]]

回帰分析と機械学習で中央線の高コスパ物件を探す(コスパ高物件導出)

前回は、家賃予測モデルの生成を行いました。

pompom168.hatenablog.com

今回は、Random Forestで生成した家賃予測モデルを使って、コスパ高物件を見つけます。

予測された家賃より実際の家賃が安いほうが、コスパが高いとします。

結果

1位〜5位を掲載します。

f:id:pompom168:20171204183529p:plain

最もコスパが高い物件は、三鷹駅の物件で予測家賃より実際の家賃が65,626円も安くなりました。

その物件が以下のものです。 f:id:pompom168:20171204185656p:plain

三鷹の2LDKの家賃相場が約16万(2017/12/4ホームズ調べ)の中では、かなりお買い得なのではないでしょうか。

これで終わりです。

これからも、色々なデータを扱って遊んでみたいと思います。

回帰分析と機械学習で中央線の高コスパ物件を探す(家賃予測モデル生成)

前回は、データの可視化と変数選択を行いました。

pompom168.hatenablog.com

今回は、本格的に家賃予測モデルを生成します。 スクレイピングした物件の、8割を学習に、2割を評価のテスト用に使用することにします。

使用する変数

説明変数

部屋数、間取りK有無、間取りL有無、間取りS有無、築年数、建物高さ、部屋のある階、徒歩時間、駅(中野)、駅(阿佐ヶ谷)、駅(高円寺)、駅(お荻窪)、駅(西荻窪)、駅(吉祥寺)、駅(三鷹)、駅(武蔵境)、駅(東小金井)、駅(武蔵小金井)、駅(国分寺)、駅(西国分寺)、駅(国立)

応答変数

家賃+管理費

重回帰分析による回帰モデル

初めに重回帰分析です。

pythonのライブラリである、scikit-learnを使って実装しました。

以下、ソースコードです。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn import linear_model

df = pd.read_csv('input_bukken_chuoline.csv', sep = '\t',encoding='utf-16')

#不要な列を削除
df.drop(['Unnamed: 0'], axis=1, inplace=True)

#分析後出力される変数
coef = np.zeros((21,1))
intercept = np.zeros(1)
score = np.zeros(1)

#root mean square error(RMSE)
rmse = np.zeros(1)

#8割を学習、2割をテストに使用
dfTrain = df[0:int(len(df.index) * 0.8)]
dfTest = df[int(len(df.index) * 0.8):len(df.index)]
    
#モデルの定義
clf = linear_model.LinearRegression()
 
# 説明変数に "賃料+管理費","間取りK", "専有面積"以外 を利用
dependentVar = dfTrain.drop(["賃料+管理費","間取りK", "専有面積"], axis=1)
dependentVar = dependentVar.apply(lambda x: (x - np.mean(x)) / np.std(x))
dependentVar.head()
X = dependentVar.as_matrix()
 
# 目的変数に "賃料+管理費”を利用
Y = dfTrain['賃料+管理費'].as_matrix()
 
# 予測モデルを作成
clf.fit(X, Y)

# 回帰係数
coef[:,0] = clf.coef_
 
# 切片 (誤差)
intercept[0] = clf.intercept_
 
# 決定係数
score[0] = clf.score(X, Y)

#テストの説明変数
dependentVarTest = dfTest.drop(["賃料+管理費","間取りK", "専有面積"], axis=1)
dependentVarTest = dependentVarTest.apply(lambda x: (x - np.mean(x)) / np.std(x))
dependentVarTest.head()
testX = dependentVarTest.as_matrix()
 
#テストの正解
ansY = dfTest['賃料+管理費'].as_matrix()

#テストの予測結果
testY = clf.predict(testX)

#RMSE計算
rmse[0] = np.sqrt(np.mean(np.square(np.array(testY - ansY))))

#結果可視化
idx = np.argsort(np.array(ansY))
plt.plot(np.arange(0,len(ansY)),np.array(ansY)[idx], color='blue')
plt.plot(np.arange(0,len(testY)),np.array(testY)[idx], alpha=0.4)
plt.savefig("正規化_予測&正解")
plt.show()

結果を見てみます。

まずは、作成したモデルの偏回帰係数を見てみます。 f:id:pompom168:20171204161939p:plain

部屋数が増えれば10486円家賃が上がる、築年数が1年経てば家賃が4866円下がるなど、妥当なように感じます。

駅に関しては、武蔵境より以西の物件は家賃が下がるようです。

次に、テストデータの実際の家賃と予測された家賃を重ねてプロットしてみます。

横軸が物件のindex(各物件)、縦軸が家賃、青線が実際の家賃、赤線が予測された家賃を示します。 f:id:pompom168:20171204163030p:plain

ある程度は当たっている気もしますが、特に安い物件と特に高い物件では予測に失敗しています。

Random Forestによる回帰モデル

機械学習の内、アンサンブル学習の1つであるRandom Forestを使用して予測モデルを作成します。

こちらも、pythonのライブラリであるscikit-learnを使って実装しました。

以下のパラメーターをグリッドサーチしました。

決定木の数(n_estimators):[5, 10, 30, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]

各決定木で使用する説明変数の最大数(max_features):['auto'(=sqrt(説明変数の数))]

ノード分割に必要な最小サンプル数(min_sample_split):[2, 3, 5, 10]

決定木の最大深さ(max_depth):[None(=設定無), 3, 5, 10]

以下、ソースコードです。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV

df = pd.read_csv('input_bukken_chuoline.csv', sep = '\t',encoding='utf-16')

#不要な列を削除
df.drop(['Unnamed: 0'], axis=1, inplace=True)

#グリッドサーチするパラメーター
parameters = {
    'n_estimators'      : [5, 10, 30, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000],
    'max_features'      : ['auto'],
    'random_state'      : [0],
    'min_samples_split' : [2, 3, 5, 10],
    'max_depth'         : [None, 3, 5, 10]
}

#root mean square error(RMSE)
rmse = np.zeros(1)

#8割を学習、2割をテストに使用    
dfTrain = df[0:int(len(df.index) * 0.8)]
dfTest = df[int(len(df.index) * 0.8):len(df.index)]
    
#モデルの定義
clf = GridSearchCV(RandomForestRegressor(), parameters)
 
# 説明変数に "賃料+管理費","間取りK", "専有面積"以外 を利用
dependentVar = dfTrain.drop(["賃料+管理費","間取りK", "専有面積"], axis=1)
X = dependentVar.as_matrix()
 
# 目的変数に "賃料+管理費”を利用
Y = dfTrain['賃料+管理費'].as_matrix()
 
# 予測モデルを作成
clf.fit(X, Y)

print(clf.best_estimator_)

#テストの説明変数
dependentVarTest = dfTest.drop(["賃料+管理費","間取りK", "専有面積"], axis=1)
testX = dependentVarTest.as_matrix()
 
#テストの正解
ansY = dfTest['賃料+管理費'].as_matrix()

#テストの予測結果
testY = clf.predict(testX)

#RMSE計算
rmse[0] = np.sqrt(np.mean(np.square(np.array(testY - ansY))))

#結果可視化
idx = np.argsort(np.array(ansY))
plt.plot(np.arange(0,len(ansY)),np.array(ansY)[idx], color='blue')
plt.plot(np.arange(0,len(testY)),np.array(testY)[idx], alpha=0.4)
plt.savefig("randomForest_正規化_予測&正解")
plt.show()

scikit-leranを使うと、パラメーターのグリッドサーチも一行で書けてしまいます。

結果を見てみます。

グリッドサーチの結果、最適なパラメーターは以下のようでした。

max_depth=None,

max_features='auto',

min_samples_split=2,

n_estimators=1000,

n_estimatorsがサーチした範囲の最大値になりましたが、100以降はサチっていたのでこれ以上やってもあまり効果がないと思います。

次に、重回帰分析と同様にテストデータの実際の家賃と予測された家賃を重ねてプロットしてみます。

横軸が物件のindex(各物件)、縦軸が家賃、青線が実際の家賃、赤線が予測された家賃を示します。

f:id:pompom168:20171204165133p:plain

ブレが重回帰分析より小さいですし、家賃が特に低い/高いところも重回帰分析よりは良くなっている気がします。

最後に、重回帰分析とRandom Forestの結果をRoot Mean Square Error(RMSE)で定量的に評価します。

f:id:pompom168:20171204165550p:plain

RMSEによる評価でも、Random Forestによるモデルの方が良いものであると言えます。

これで、家賃予測モデルの生成が終わりました。

次は最後に、今回作成したモデルを使用してコスパ高物件を導出しようと思います。

回帰分析と機械学習で中央線の高コスパ物件を探す(データ可視化+変数選択)

前回の記事の続きです。

pompom168.hatenablog.com

今回は、具体的にデータの中身を見ていくために可視化を行います。

また、主に多重共線性をなくすために変数選択を行います。

データの可視化

物件数

まずは、各駅ごとの物件数を見ます。 f:id:pompom168:20171204150607p:plain

なんとなく納得する物件数です。 強いて言えば、阿佐ヶ谷駅武蔵境駅が思っていたより少ないです。

家賃

次は各駅ごとの家賃の分布です。

箱ひげ図を描いてみます。 f:id:pompom168:20171204150932p:plain

外れ値が多いです。ただ、すごい高い物件があってもおかしくはないと思うので、外れ値検定を行わずに描画してみます。

f:id:pompom168:20171204151054p:plain

高円寺、吉祥寺、国立に高級物件があります。

国立は富裕層が住んでいるイメージがありますし、吉祥寺は住みたい街上位ですから何となくわかるものの、高円寺にも高級物件があるんですね。

また、中央値で比較できるように、描画範囲を絞ります。

f:id:pompom168:20171204151303p:plain

中野〜三鷹(阿佐ヶ谷以外)までが家賃高めです。

また、三鷹以西だと立川が高めです。

中央特快の停まる国分寺が安いのは意外です。

徒歩時間と家賃の関係

駅からの徒歩距離が長いほど、家賃は安くなると思うので、横軸が徒歩時間で縦軸が家賃の散布図を描いてみます。

f:id:pompom168:20171204151749p:plain

ここで気づきましたが、徒歩60分とかの物件は最寄りとは言わない気がします。

よって、徒歩30分より遠い物件は今回の解析から除去することにします。

前回の記事の前処理の部分に以下のコードを追加しておきます。

#徒歩30分より遠い物件は除去
df = df[(df['徒歩'] <= 31)]

また、徒歩ほぼ0分で異常に家賃高い物件がありますが、一応確認しておきます。

f:id:pompom168:20171204152050p:plain

うーん、高円寺でこんなに高いのか?

でも入力ミスでもなさそうなので、このままにしておきます。

変数選択

次に、多重共線性をなくすため変数選択を行います。

具体的には、各変数同士の相関係数を確認します。

下図の、”間取り”は部屋数、”間取りDK”は間取りにDKを含むか(”間取りK”なども同様)です。

f:id:pompom168:20171204152359p:plain

部屋数が多ければ面積も大きそうなので、間取りと専有面積の相関が高いです。

DKが有ればKが無いのが普通なので、間取りDKと間取りKの負の相関も高いです。

他の変数との相関も考慮し、専有面積と間取りKをモデルの入力変数から除去することにします。

この辺は、触発されたブログと同じです。

www.analyze-world.com

ここまでで、データの特徴と変数選択ができたので、次は予測モデルを作ります。

回帰分析と機械学習で中央線の高コスパ物件を探す(スクレイピング+前処理)

みんなが大好き中央線沿いで、コスパ高い物件を探してみます。

完全に以下のブログに触発されたものです。

www.analyze-world.com

やったこと

  • webから中央線沿いの物件情報をスクレイピング
  • モデルへの入力のため前処理
  • データの可視化と変数選択
  • 家賃予測モデルの生成
  • モデルを使用した家賃の予測と実際の家賃を比較したコスパの導出

やってみてよかったこと・気づいたこと

  • Pythonの扱い方を思い出した
  • Webスクレピングの練習になった
  • Pandasの練習になった
  • 改めて回帰分析の勉強をできた
  • 学生の時以来に機械学習した
  • 会社入ってから身につけたHTML+CSSとSQL周りのスキルが役にたった

Webからのスクレイピング

まず初めにデータ収集です。

Suumoさんからスクレイピングさせてもらいます。

今回対象とする駅は、The・中央線という感じの中野駅〜立川駅とします。 f:id:pompom168:20171204135004p:plain

これらの物件データをスクレイピングして、Pandasのデータフレームにまとめます。

まずHTMLの構造を確認します。

ブラウザのディベロッパーツールを使用するとやりやすいです。

Chromeでは、Windowsの場合:「F12」、Macの場合:「Command + Option + I」で表示できます。

Suumoのサイトでは、検索された物件リストの情報が id=js-bukkenListのdivタグの中にあり f:id:pompom168:20171204140643p:plain 各物件の情報が class=cassetteitemのdivタグの中にあるようです。 f:id:pompom168:20171204140742p:plain

以下、スクレイピングを行ったコードです。

#必要なライブラリをインポート
from bs4 import BeautifulSoup
import requests
import pandas as pd
from pandas import Series, DataFrame
import time

url = "http://suumo.jp/jj/chintai/ichiran/FR301FC001/?ar=030&bs=040&pc=30&smk=&po1=12&po2=99&shkr1=03&shkr2=03&shkr3=03&shkr4=03&rn=0305&ek=030527280&ek=030513930&ek=030500640&ek=030506640&ek=030528500&ek=030511640&ek=030536880&ek=030538740&ek=030531920&ek=030538710&ek=030514690&ek=030528740&ek=030512780&ek=030523100&ra=013&cb=0.0&ct=9999999&ts=1&ts=2&et=9999999&mb=0&mt=9999999&cn=9999999&fw2="

#データ取得
result = requests.get(url)
c = result.content

#HTMLを元に、オブジェクトを作る
soup = BeautifulSoup(c)

#物件リストの部分を切り出し
summary = soup.find("div",{'id':'js-bukkenList'})

#ページ数を取得
body = soup.find("body")
pages = body.find_all("div",{'class':'pagination pagination_set-nav'})
pages_text = str(pages)
pages_split = pages_text.split('</a></li>\n</ol>')
pages_split0 = pages_split[0]
pages_split1 = pages_split0[-3:]
pages_split2 = pages_split1.replace('>','')
pages_split3 = int(pages_split2)

#URLを入れるリスト
urls = []

#1ページ目を格納
urls.append(url)

#2ページ目から最後のページまでを格納
for i in range(pages_split3-1):
    pg = str(i+2)
    url_page = url + '&pn=' + pg
    urls.append(url_page)
    
name = [] #マンション名
address = [] #住所
locations = [] #立地1つ目(最寄駅/徒歩~分)
age = [] #築年数
height = [] #建物高さ
floor = [] #階
rent = [] #賃料
admin = [] #管理費
others = [] #敷/礼/保証/敷引,償却
floor_plan = [] #間取り
area = [] #専有面積


#各ページで以下の動作をループ
count = 0
for url in urls:
    #物件リストを切り出し
    result = requests.get(url)
    c = result.content
    soup = BeautifulSoup(c)
    summary = soup.find("div",{'id':'js-bukkenList'})
    
    if isinstance(summary,type(None)):
        continue
    
    #マンション名、住所、立地(最寄駅/徒歩~分)、築年数、建物高さが入っているcassetteitemを全て抜き出し
    cassetteitems = summary.find_all("div",{'class':'cassetteitem'})

    #各cassetteitemsに対し、以下の動作をループ
    for i in range(len(cassetteitems)):
        #各建物から売りに出ている部屋数を取得
        tbodies = cassetteitems[i].find_all('tbody')

        
        #マンション名取得
        subtitle = cassetteitems[i].find_all("div",{
            'class':'cassetteitem_content-title'})
        subtitle = str(subtitle)
        subtitle_rep = subtitle.replace(
            '[<div class="cassetteitem_content-title">', '')
        subtitle_rep2 = subtitle_rep.replace(
            '</div>]', '')

        #住所取得
        subaddress = cassetteitems[i].find_all("li",{
            'class':'cassetteitem_detail-col1'})
        subaddress = str(subaddress)
        subaddress_rep = subaddress.replace(
            '[<li class="cassetteitem_detail-col1">', '')
        subaddress_rep2 = subaddress_rep.replace(
            '</li>]', '')
        
        #部屋数だけ、マンション名と住所を繰り返しリストに格納(部屋情報と数を合致させるため)
        for y in range(len(tbodies)):
            name.append(subtitle_rep2)
            address.append(subaddress_rep2)

        #立地を取得
        sublocations = cassetteitems[i].find_all("li",{
            'class':'cassetteitem_detail-col2'})
    
        #立地は、1つ目だけを取得
        for x in sublocations:
            cols = x.find_all('div')
            for i in range(len(cols)):
                text = cols[i].find(text=True)
                for y in range(len(tbodies)):
                    if i == 0:
                        locations.append(text)
                        
        #築年数と建物高さを取得
        col3 = cassetteitems[i].find_all("li",{
            'class':'cassetteitem_detail-col3'})

        
        
        for x in col3:
            cols = x.find_all('div')
            for i in range(len(cols)):
                text = cols[i].find(text=True)
                for y in range(len(tbodies)):
                    if i == 0:
                        age.append(text)
                    else:
                        height.append(text)

    #階、賃料、管理費、敷/礼/保証/敷引,償却、間取り、専有面積が入っているtableを全て抜き出し
    tables = summary.find_all('table')

    #各建物(table)に対して、売りに出ている部屋(row)を取得
    rows = []
    for i in range(len(tables)):
        rows.append(tables[i].find_all('tr'))

    #各部屋に対して、tableに入っているtext情報を取得し、dataリストに格納
    data = []
    for row in rows:
        for tr in row:
            cols = tr.find_all('td')
            for td in cols:
                text = td.find(text=True)
                data.append(text)

    #dataリストから、階、賃料、管理費、敷/礼/保証/敷引,償却、間取り、専有面積を順番に取り出す
    index = 0
    for item in data:
        if '階' in item:
            floor.append(data[index])
            rent.append(data[index+1])
            admin.append(data[index+2])
            others.append(data[index+3])
            floor_plan.append(data[index+4])
            area.append(data[index+5])
        index +=1
    
    #プログラムを1秒間停止する(スクレイピングマナー)
    time.sleep(1)
    
    count += 1
    
    print(str(count) + 'ページ終了 / 全' + str(len(urls)) + 'ページ')

#各リストをシリーズ化
name = Series(name)
address = Series(address)
locations = Series(locations)
age = Series(age)
height = Series(height)
floor = Series(floor)
rent = Series(rent)
admin = Series(admin)
others = Series(others)
floor_plan = Series(floor_plan)
area = Series(area)

#各シリーズをデータフレーム化
suumo_df = pd.concat([name, address, locations, age, height, floor, rent, admin, others, floor_plan, area], axis=1)

#カラム名
suumo_df.columns=['マンション名','住所','立地','築年数','建物高さ','階','賃料','管理費', '敷/礼/保証/敷引,償却','間取り','専有面積']

#csvファイルとして保存
suumo_df.to_csv('bukken_chuoline.csv', sep = '\t',encoding='utf-16')  

前処理

次は、モデルに入力する変数とするための前処理です。 上記で紹介したブログと同じことをやっている部分の説明は割愛します。

#必要なライブラリをインポート
import pandas as pd
import numpy as np

#csv読み込み                
df = pd.read_csv("bukken_chuoline.csv", sep='\t', encoding='utf-16')

#不要な列を削除
df.drop(['Unnamed: 0'], axis=1, inplace=True)

#立地を「路線+駅」と「徒歩〜分」に分割
splitted1 = df['立地'].str.split(' 歩', expand=True)
splitted1.columns = ['立地11', '立地12']


#その他費用を、「敷金」「礼金」「保証金」「敷引,償却」に分割
splitted4 = df['敷/礼/保証/敷引,償却'].str.split('/', expand=True)
splitted4.columns = ['敷金', '礼金', '保証金', '敷引,償却']

#分割したカラムを結合
df = pd.concat([df, splitted1, splitted4], axis=1)

#分割前のカラムは分析に使用しないので削除しておく
df.drop(['立地','敷/礼/保証/敷引,償却','敷引,償却'], axis=1, inplace=True)


#立地を「路線」「駅」「徒歩〜分」に分割
splitted7 = df['立地11'].str.split('/', expand=True)
splitted7.columns = ['路線', '駅']
splitted7['徒歩'] = df['立地12']

#結合
df = pd.concat([df, splitted7], axis=1)

#不要なカラムを削除
df.drop(['立地11','立地12'], axis=1, inplace=True)

#「賃料」がNAの行を削除
df = df.dropna(subset=['賃料'])

#エンコードをcp932に変更しておく(これをしないと、replaceできない)
df['賃料'].str.encode('cp932')
df['敷金'].str.encode('cp932')
df['礼金'].str.encode('cp932')
df['保証金'].str.encode('cp932')
df['管理費'].str.encode('cp932')
df['築年数'].str.encode('cp932')
df['専有面積'].str.encode('cp932')


#数値として扱いたいので、不要な文字列を削除
df['賃料'] = df['賃料'].str.replace(u'万円', u'')
df['敷金'] = df['敷金'].str.replace(u'万円', u'')
df['礼金'] = df['礼金'].str.replace(u'万円', u'')
df['保証金'] = df['保証金'].str.replace(u'万円', u'')
df['管理費'] = df['管理費'].str.replace(u'円', u'')
df['築年数'] = df['築年数'].str.replace(u'新築', u'0') #新築は築年数0年とする
df['築年数'] = df['築年数'].str.replace(u'築', u'')
df['築年数'] = df['築年数'].str.replace(u'年', u'')
df['専有面積'] = df['専有面積'].str.replace(u'm', u'')
df['徒歩'] = df['徒歩'].str.replace(u'分', u'')

#「-」を0に変換
df['管理費'] = df['管理費'].replace('-',0)
df['敷金'] = df['敷金'].replace('-',0)
df['礼金'] = df['礼金'].replace('-',0)
df['保証金'] = df['保証金'].replace('-',0)


#文字列から数値に変換
df['賃料'] = pd.to_numeric(df['賃料'])
df['管理費'] = pd.to_numeric(df['管理費'])
df['敷金'] = pd.to_numeric(df['敷金'])
df['礼金'] = pd.to_numeric(df['礼金'])
df['保証金'] = pd.to_numeric(df['保証金'])
df['築年数'] = pd.to_numeric(df['築年数'])
df['専有面積'] = pd.to_numeric(df['専有面積'])
df['徒歩'] = pd.to_numeric(df['徒歩'])

#単位を合わせるために、管理費以外を10000倍。
df['賃料'] = df['賃料'] * 10000
df['敷金'] = df['敷金'] * 10000
df['礼金'] = df['礼金'] * 10000
df['保証金'] = df['保証金'] * 10000


#管理費は実質的には賃料と同じく毎月支払うことになるため、「賃料+管理費」を家賃を見る指標とする
df['賃料+管理費'] = df['賃料'] + df['管理費']

#敷金/礼金と保証金は同じく初期費用であり、どちらかが適用されるため、合計を初期費用を見る指標とする
df['敷/礼/保証金'] = df['敷金'] + df['礼金'] + df['保証金']

#階を数値化。地下はマイナスとして扱う
splitted10 = df['階'].str.split('-', expand=True)
splitted10.columns = ['階1', '階2']

splitted10['階1'].str.encode('cp932')
splitted10['階1'] = splitted10['階1'].str.replace(u'階', u'')
splitted10['階1'] = splitted10['階1'].str.replace(u'B', u'-')
splitted10['階1'] = pd.to_numeric(splitted10['階1'])
df = pd.concat([df, splitted10], axis=1)


#建物高さを数値化。地下は無視。
df['建物高さ'].str.encode('cp932')
df['建物高さ'] = df['建物高さ'].str.replace(u'地下1地上', u'')
df['建物高さ'] = df['建物高さ'].str.replace(u'地下2地上', u'')
df['建物高さ'] = df['建物高さ'].str.replace(u'地下3地上', u'')
df['建物高さ'] = df['建物高さ'].str.replace(u'地下4地上', u'')
df['建物高さ'] = df['建物高さ'].str.replace(u'地下5地上', u'')
df['建物高さ'] = df['建物高さ'].str.replace(u'地下6地上', u'')
df['建物高さ'] = df['建物高さ'].str.replace(u'地下7地上', u'')
df['建物高さ'] = df['建物高さ'].str.replace(u'地下8地上', u'')
df['建物高さ'] = df['建物高さ'].str.replace(u'地下9地上', u'')
df['建物高さ'] = df['建物高さ'].str.replace(u'平屋', u'1')
df['建物高さ'] = df['建物高さ'].str.replace(u'階建', u'')
df['建物高さ'] = pd.to_numeric(df['建物高さ'])


#indexを振り直す(これをしないと、以下の処理でエラーが出る)
df = df.reset_index(drop=True)

#間取りを「部屋数」「DK有無」「K有無」「L有無」「S有無」に分割
df['間取りDK'] = 0
df['間取りK'] = 0
df['間取りL'] = 0
df['間取りS'] = 0
df['間取り'].str.encode('cp932')
df['間取り'] = df['間取り'].str.replace(u'ワンルーム', u'1') #ワンルームを1に変換

for x in range(len(df)):
    if 'DK' in df['間取り'][x]:
        df.loc[x,'間取りDK'] = 1
df['間取り'] = df['間取り'].str.replace(u'DK',u'')

for x in range(len(df)):
    if 'K' in df['間取り'][x]:
        df.loc[x,'間取りK'] = 1        
df['間取り'] = df['間取り'].str.replace(u'K',u'')

for x in range(len(df)):
    if 'L' in df['間取り'][x]:
        df.loc[x,'間取りL'] = 1        
df['間取り'] = df['間取り'].str.replace(u'L',u'')

for x in range(len(df)):
    if 'S' in df['間取り'][x]:
        df.loc[x,'間取りS'] = 1        
df['間取り'] = df['間取り'].str.replace(u'S',u'')

df['間取り'] = pd.to_numeric(df['間取り'])


#カラムを入れ替え
df = df[['マンション名','間取り','間取りDK','間取りK','間取りL','間取りS','築年数','建物高さ','階1','専有面積','賃料+管理費','敷/礼/保証金',
                '駅','徒歩','賃料','管理費',
                '敷金','礼金','保証金']]

また、一番の最寄りが中央線の駅以外のデータが入っていましたので、それらを除去します。

#中央線以外の駅が入っているので除去
df = df.query('駅 in ["中野駅", "阿佐ヶ谷駅", "高円寺駅", "荻窪駅", "西荻窪駅", "吉祥寺駅", "三鷹駅", "武蔵境駅", "東小金井駅", "武蔵小金井駅", "国分寺駅", "西国分寺駅", "国立駅", "立川駅"]')

ここで一旦、データを保存しておきます。(後で見やすいため)

#csvで保存
df.to_csv('bukken_chuoline_prepro.csv', sep = '\t',encoding='utf-16')

次に、最寄り駅の情報は数値データでないため(質的変数)、ダミー変数を導入してモデルで扱えるようにします。

ダミー変数への変換方法は主に2つあります。

  1. 中野駅に0、阿佐ヶ谷駅に1、高円寺駅に2のように各駅に一意のインデックスを付ける

  2. 中野駅、阿佐ヶ谷駅などそれぞれを表す変数を用意し、対応するものを1、それ以外を0とする。(例:中野駅の物件なら中野駅を表す変数に1、それ以外を表す変数を0)とする。

通常は、2の手法を用います。

なぜなら1の方法では、意味のない数字の大小の情報が生じてしまうからです。

また、2の手法でも注意しなければならないことがあります。

それは、カテゴリの数(今回でいう駅の数)分ダミー変数を用意してしまうと、線形従属の関係になる変数が生じてしまい、多重共線性の問題が生じます。

よって通常は、カテゴリの数-1個のダミー変数を生成します。

pandasの場合、以下の一行で出来てしまいます。

#駅をダミー変数に変換
dummy_df = pd.get_dummies(df[['駅']], drop_first = True)

ダミー変数のデータフレームと、これまでのデータフレームをマージします。

#dfのマージ
df2 = pd.merge(df, dummy_df,left_index=True, right_index=True)

後は、モデルの入力に使用しないデータを取り除き、テストのため順番をランダムにして保存します。

#不要なカラムの削除
df2.drop(['マンション名','駅','敷金','礼金','保証金','管理費','賃料','敷/礼/保証金'], axis=1, inplace=True)

#テストのため順番ランダムに
df2 = df2.reindex(np.random.permutation(df2.index))

#モデルへの入力特徴量をcsvで保存
df2.to_csv('input_bukken_chuoline.csv', sep='\t', encoding='utf-16')

長くなってしまいましたが、スクレイピングと前処理はここまでです。

次は、解析に使用するデータの可視化と変数の選択を行います。