統計学、機械学習などを使って身近な世界を分析したりするブログです

機械学習を使って東京23区のお買い得賃貸物件を探してみた 〜前処理編〜

さて、前回のブログで足立区の賃貸物件を取得しました。

www.analyze-world.com

同様にして、東京都23区の物件も取得してしまいましょう。今回僕が取得したところ、合計で200,060件になりました。csvファイルで70MBほどですので、Excelだと重すぎて処理スピートがかなり遅いです。いい感じにビッグなデータが手に入ったんじゃないでしょうか。

物件情報を取得したはいいものの、このままだと分析をまわせないので、前処理を施します。今回の最終目標はお買い得物件を探すことなので、応答変数は賃料が主な指標になりそうです。賃料に対して、それぞれのカラムがどのように効いてくるかを考慮しながら処理していきます。

今回使うライブラリはpandasとnumpyだけです。それぞれ区別に保存しておいたファイルを結合して一つのファイルを形成します。

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

#東京都23区の物件を個別に保存しておいたものを読み込み、結合
df_adachi = pd.read_csv('suumo_adachi.csv', sep='\t', encoding='utf-16')
df_arakawa = pd.read_csv('suumo_arakawa.csv', sep='\t', encoding='utf-16')
df_bunkyo = pd.read_csv('suumo_bunkyo.csv', sep='\t', encoding='utf-16')
df_chiyoda = pd.read_csv('suumo_chiyoda.csv', sep='\t', encoding='utf-16')
df_chuo = pd.read_csv('suumo_chuo.csv', sep='\t', encoding='utf-16')
df_edogawa = pd.read_csv('suumo_edogawa.csv', sep='\t', encoding='utf-16')
df_itabashi = pd.read_csv('suumo_itabashi.csv', sep='\t', encoding='utf-16')
df_katsushika = pd.read_csv('suumo_katsushika.csv', sep='\t', encoding='utf-16')
df_kita = pd.read_csv('suumo_kita.csv', sep='\t', encoding='utf-16')
df_koto = pd.read_csv('suumo_koto.csv', sep='\t', encoding='utf-16')
df_meguro = pd.read_csv('suumo_meguro.csv', sep='\t', encoding='utf-16')
df_minato = pd.read_csv('suumo_minato.csv', sep='\t', encoding='utf-16')
df_nakano = pd.read_csv('suumo_nakano.csv', sep='\t', encoding='utf-16')
df_nerima = pd.read_csv('suumo_nerima.csv', sep='\t', encoding='utf-16')
df_ota = pd.read_csv('suumo_ota.csv', sep='\t', encoding='utf-16')
df_setagaya = pd.read_csv('suumo_setagaya.csv', sep='\t', encoding='utf-16')
df_shibuya = pd.read_csv('suumo_shibuya.csv', sep='\t', encoding='utf-16')
df_shinagawa = pd.read_csv('suumo_shinagawa.csv', sep='\t', encoding='utf-16')
df_shinjuku = pd.read_csv('suumo_shinjuku.csv', sep='\t', encoding='utf-16')
df_suginami = pd.read_csv('suumo_suginami.csv', sep='\t', encoding='utf-16')
df_sumida = pd.read_csv('suumo_sumida.csv', sep='\t', encoding='utf-16')
df_taito = pd.read_csv('suumo_taito.csv', sep='\t', encoding='utf-16')
df_toshima = pd.read_csv('suumo_toshima.csv', sep='\t', encoding='utf-16')

df = pd.concat([df_adachi,df_arakawa,df_bunkyo,df_chiyoda,df_chuo,df_edogawa,
               df_itabashi,df_katsushika,df_kita,df_koto,df_meguro,df_minato,
               df_nakano,df_nerima,df_ota,df_setagaya,df_shibuya,df_shinagawa,
               df_shinjuku,df_suginami,df_sumida,df_taito,df_toshima], axis=0,
              ignore_index=True)

はい、入りました。余分なindex列が入っているので、削除しておきましょう。

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

「立地」と「敷/礼/保証/敷引,償却」、それぞれ一つのカラムに複数の情報が入っているので、これらを分析しやすいように分割しておきましょう。

「立地」は、「JR常盤線/亀有駅 歩25分」という表示に統一されています。「路線」「駅」「徒歩〜分」に三分割するのが良さそうですが、まずは「徒歩〜分」の部分を切り分けます。この後すぐ、「路線」と「駅」も分割します。

また、「敷/礼/保証/敷引,償却」はスラッシュで分割しますが、「敷引,償却」はドットで区切られているので、別々に処理します。

#立地を「路線+駅」と「徒歩〜分」に分割
splitted1 = df['立地1'].str.split(' 歩', expand=True)
splitted1.columns = ['立地11', '立地12']
splitted2 = df['立地2'].str.split(' 歩', expand=True)
splitted2.columns = ['立地21', '立地22']
splitted3 = df['立地3'].str.split(' 歩', expand=True)
splitted3.columns = ['立地31', '立地32']

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

#「敷引,償却」をさらに「敷引」「償却」に分割
splitted5 = df['敷引,償却'].str.split('・', expand=True)
splitted5.columns = ['敷引', '償却']

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

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

さて、それでは「路線」と「駅」も分割しておきましょう。基本的にはスラッシュで分割すればいいのですが、稀に、スラッシュが2個以上使われており、3つ以上に分割されてしまう行があります。これがあるとエラーになるので、あらかじめ削除しておきましょう。20万件ほどある中のたった数件(今回は1件のみ)なので、無視しても大勢に影響はなさそうです。

#路線と駅が3つ以上に分かれてしまう行を削除
drop_idx = [98764] #手動でindexを確認
df = df.drop(drop_idx, axis=0)

#立地を「路線」「駅」「徒歩〜分」に分割
splitted7 = df['立地11'].str.split('/', expand=True)
splitted7.columns = ['路線1', '駅1']
splitted7['徒歩1'] = df['立地12']
splitted8 = df['立地21'].str.split('/', expand=True)
splitted8.columns = ['路線2', '駅2']
splitted8['徒歩2'] = df['立地22']
splitted9 = df['立地31'].str.split('/', expand=True)
splitted9.columns = ['路線3', '駅3']
splitted9['徒歩3'] = df['立地32']

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

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

賃料がNAの行があるので、削除しておきます。

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

分析するときに数値として扱いたい変数を、文字列から数値に変換する必要があります。先に、文字列をエンコードcp932に変更しておき、不要な文字列を削除しましょう。数値に変換する前に、ハイフンやNoneを0にしておいてください。

金額の変数では、管理費以外は単位が「万円」になっているので、これらを10000倍しておきます。

#エンコードを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['築年数'].str.encode('cp932')
df['専有面積'].str.encode('cp932')
df['立地12'].str.encode('cp932')
df['立地22'].str.encode('cp932')
df['立地32'].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'')
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['立地12'] = df['立地12'].str.replace(u'分', u'')
df['立地22'] = df['立地22'].str.replace(u'分', u'')
df['立地32'] = df['立地32'].str.replace(u'分', u'')

#「-」を0に変換
df['管理費'] = df['管理費'].replace('-',0)
df['敷金'] = df['敷金'].replace('-',0)
df['礼金'] = df['礼金'].replace('-',0)
df['保証金'] = df['保証金'].replace('-',0)
df['敷引'] = df['敷引'].replace('-',0)
df['敷引'] = df['敷引'].replace('実費',0) #「実費」と文字列が入っている場合がある
df['償却'] = df['償却'].replace('-',0)

#Noneを0に変換
df['償却'] = [0 if x is None else x for x in 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['償却'])
df['築年数'] = pd.to_numeric(df['築年数'])
df['専有面積'] = pd.to_numeric(df['専有面積'])
df['立地12'] = pd.to_numeric(df['立地12'])
df['立地22'] = pd.to_numeric(df['立地22'])
df['立地32'] = pd.to_numeric(df['立地32'])

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

管理費は通常、毎月支払うもので、交渉で下げられる余地があるなど多少の違いはありますが、実質的には賃料と同じ性質のものであるため、賃料と管理費の合計を家賃を見る指標とします。

また、初期費用についてですが、通常、敷金/礼金パターンか、もしくは保証金パターンとなります。どちらも支払うということはありません。したがって、敷金、礼金、保証金、これらの合計を初期費用を見る指標として計算しておきます。

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

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

住所は、後に区別に見ることが多くなりそうなので、「東京都」「〜区」「市町村番地」に分割しておきましょう。おそらくもっとシンプルなやり方があると思いますが、とりあえずこれで走ります。

#住所を「東京都」「〜区」「市町村番地」に分割
splitted6 = df['住所'].str.split('区', expand=True)
splitted6.columns = ['区', '市町村']
splitted6['区'] = splitted6['区'] + '区'
splitted6['区'] = splitted6['区'].str.replace('東京都','')

ここまでは、コードは違えど、おそらく誰にやらせても同じ処理になるでしょう。残る変数は、「階」「建物高さ」「間取り」の3つです。

まずは「階」。一般的に上層階の部屋の方が家賃は高いと思われるので、これも数値化してしまう方が良いでしょう。「1-2階」のような表示の場合は、低い方(この場合は1階)に合わせます。また、地下階の扱いは若干迷うところですが、地下は窓も無く、あまり人気がないだろうという推測の元、今回はマイナスとします。例えば、地下二階は-2となります。

#階を数値化。地下はマイナスとして扱う
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)

次は「建物高さ」。これも、同じ2階の部屋であっても、マンションが高層になるほど家賃が高い傾向がありそうなので、数値化します。この場合も地下の扱いが迷いポイントですが、今回は無視します。地下と地上を合わせて建物高さとする(例えば地下2地上3階だと5階建)方法でもいいかもしれません。平屋がありますが、これは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['建物高さ'])

最後の変数、「間取り」です。これは若干厄介ですね。そのまま名義変数としてもいいかもしれませんが、今回は、「部屋数」「DK有無」「K有無」「L有無」「S有無」に分割しました。

まず、部屋数ですが、1Kや3LDKなど、最初の数字は部屋数を示します。この部屋数はダイレクトに家賃に影響するので、最終的に数値として切り離します。DKはダイニングキッチンですが、一般的には、DKとKはどちらかしかありません。そこで、DKがある物件を確認し、DKがない物件に対してKの有無を確認します。続いて、Lの有無、Sの有無を確認し、変数に0 or 1で格納しておきます。

この操作により、後に重回帰分析をした際に、一部屋、DK、K、L、Sそれぞれの値段が把握できるはずです。

#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['間取り'])

これで全ての前処理が済んだので、カラムを入れ替えて、csvファイルを出力します。

#カラムを入れ替えて、csvファイルとして出力
df = df[['マンション','住所','区','市町村','間取り','間取りDK','間取りK','間取りL','間取りS','築年数','建物高さ','階1','専有面積','賃料+管理費','敷/礼/保証金',
                '路線1','駅1','徒歩1','路線2','駅2','徒歩2','路線3','駅3','徒歩3','賃料','管理費',
                '敷金','礼金','保証金','敷引','償却']]

df.to_csv('suumo_tokyo.csv', sep = '\t',encoding='utf-16')

長かった前処理も終わり、次はいよいよ楽しい楽しい分析です!

www.analyze-world.com