0. 神奈川県下の中古マンション市場を分析してみる 前処理編
前回、スクレイピングによってデータを収集の手順を紹介しました。収集結果をcsvに保存しておいたので、これを読み込むところから始めることにします。
1. 収集したデータの概要
早速、データを読み込みから、どんな情報を収集したのかを紹介しましょう。
In [2]: suumo_df = readcsv('data'+os.sep+'suumo_kanagawa.csv')
In [3]: suumo_df.head()
Out[3]:
販売スケジュール イベント情報 販売戸数 最多価格帯 価格 管理費 修繕積立金 修繕積立基金 諸費用 間取り 専有面積 その他面積 入居時期 完成時期(築年月) 所在階 向き リフォーム その他制限事項 所在地 交通 総戸数 構造・階建て 敷地面積 敷地の権利形態 用途地域 駐車場 url
藤掛城山コーポ 【マンション】 - - 1戸 - 550万円 2500円/月(自主管理) 無 - - 2DK 40.04m<sup>2</sup>(12.11坪)(壁芯) バルコニー面積:5m<sup>2</sup> - 1976年2月 4階 南 - - 神奈川県横須賀市小矢部1 JR横須賀線「衣笠」歩10分:: 17戸 RC4階建 - 所有権 1種住居 無 https://suumo.jp/ms/chuko/kanagawa/sc_yokosuka...
ライオンズマンション東海大学前 【マンション】 - - 1戸 - 780万円 1万4090円/月(委託(巡回)) 1万2580円/月 - - 2LDK 50.32m<sup>2</sup>(壁芯) バルコニー面積:6.86m<sup>2</sup> 相談 1987年10月 2階 南東 2018年7月完了予定<br/> 水回り設備交換:キッチン・浴室・トイレ<br/> 内装リフ... - 神奈川県秦野市南矢名 小田急小田原線「東海大学前」歩23分:: 33戸 RC7階建 - 所有権 1種住居 - https://suumo.jp/ms/chuko/kanagawa/sc_hadano/n...
エミネンス浅間台 【マンション】 - - 1戸 - 780万円 4200円/月(自主管理) 1万800円/月 - - 1DK 29.81m<sup>2</sup>(壁芯) バルコニー面積:9.91m<sup>2</sup> 即入居可 1978年12月 2階 南東 2016年3月完了<br/> 水回り設備交換:キッチン・浴室・トイレ<br/> 内装リフォー... - 神奈川県横浜市西区浅間台 JR東海道本線「横浜」歩19分:: 12戸 RC4階建 - 所有権 1種低層 無 https://suumo.jp/ms/chuko/kanagawa/sc_yokohama...
東建座間ハイツ2号棟 【マンション】 - - 1戸 - 880万円 6400円/月(委託(通勤)) 1万908円/月 - 自治会費:200円/月、組合費、連合管理費、他:3585円/月 3LDK 69.3m<sup>2</sup>(壁芯) バルコニー面積:7.06m<sup>2</sup> 相談 1977年11月 14階 南 - - 神奈川県座間市入谷4 小田急小田原線「座間」歩10分:: 219戸 SRC16階建 - 所有権 1種中高 空無 https://suumo.jp/ms/chuko/kanagawa/sc_zama/nc_...
ダイアナマンション 【マンション】 - - 1戸 - 920万円 9720円/月(委託(巡回)) 5740円/月 - 借地料:1500円/月 2DK 40m<sup>2</sup>(12.09坪)(壁芯) バルコニー面積:5m<sup>2</sup> 相談 1969年11月 5階 東 2018年4月完了<br/> 水回り設備交換:キッチン・浴室・トイレ<br/> 内装リフォー... ※その他制限事項がある場合はこちらに入力してください 神奈川県横浜市保土ケ谷区月見台10-28 JR横須賀線「保土ヶ谷」歩7分::湘南新宿ライン宇須「保土ヶ谷」歩7分::相鉄本線「天王町」... 57戸 SRC7階建 - 所有権 1種低層 空無 https://suumo.jp/ms/chuko/kanagawa/sc_yokohama...
これは骨が折れそうですね。。。。
ちなみに、カラム名はこのようになっています。
In [4]: suumo_df.columns
Out[4]:
Index(['販売スケジュール', 'イベント情報', '販売戸数', '最多価格帯', '価格', '管理費', '修繕積立金', '修繕積立基金', '諸費用', '間取り', '専有面積', 'その他面積', '入居時期', '完成時期(築年月)', '所在階', '向き', 'リフォーム', 'その他制限事項', '所在地', '交通', '総戸数', '構造・階建て', '敷地面積', '敷地の権利形態', '用途地域', '駐車場', 'url'], dtype='object')
ここから、なんとかしてデータクレンジングをしていくことにしましょう。
2. データのクレンジング開始
2.1 マンション名を修正する
必ず、マンション名の後に 【マンション】
が付いているので、これを取ります。
import re
def repMeg(pat, rep, mlist):
return [ pat.sub(rep, m) for m in mlist if isinstance(m, str) ]
def extMeg(pat, mlist):
return [ pat.search(str(m)).group(1) for m in mlist ]
suumo_df.index = repMeg( re.compile(u' \u3000【マンション】'), "",suumo_df.index.values)
2.2 不要なカラムの削除
個々のマンションの分析には使用しなさそうなカラムを削除しておきます。
suumo_df.drop(["販売スケジュール","イベント情報","販売戸数", "最多価格帯","修繕積立基金","入居時期","その他制限事項","敷地面積","諸費用"], axis=1, inplace=True)
2.3 専有面積
こんな感じに表記されているので、数字部分だけを取り出します。以下の場合、40.04
だけを取り出します。
40.04m2(12.11坪)(壁芯)
正規表現で一気に変換することにしましょう。
In [19]: p = re.compile('([\d\.]{3,6})m.+')
...: def getArea(n):
...: try:
...: return float(p.search(str(n)).group(1))
...: except:
...: return 0.0
...: suumo_df["専有面積"] = [ getArea(s) for s in suumo_df["専有面積"] ]
...:
In [20]: suumo_df["専有面積"][:2]
Out[20]:
0 40.04
1 50.32
Name: 専有面積, dtype: float64
できているようですね。
2.4 築年数
築年数を示すカラムは存在しませんでしたが、マンションの完成年月は完成時期(築年月)
カラムに格納されています。ここから、データ取得時点の2018/8/1までの年数に変換します。
一部、年だけで月が記載されていなかったりしたので、その場合は1月と仮定することにしました。
In [21]: def editEstablishDate(d):
...: if(d.endswith("年")):
...: return d+"1月"
...: elif(d.endswith("月")):
...: return d
...: tlist = [ editEstablishDate(d) for d in suumo_df["完成時期(築年月)"]]
...: suumo_df["完成時期(築年月)"] = pd.to_datetime(tlist, format='%Y年%m月')
...: hoge = pd.to_datetime("2018-8-1") - suumo_df["完成時期(築年月)"]
...: suumo_df["築年数"] = hoge.apply(lambda x: x.days)/365.
...:
1年の日数を一律365日として割り算していますが、厳密なものじゃないし、それでいいことにします。
2.5 構造・階建て
こちらも正規表現をつかって一気に処理します。
In [22]: suumo_df["構造・階建て"] = repMeg( re.compile('一部.+'), "", suumo_df["構造・階建て"])
...: p = re.compile('(S?RC)?(木造)?(鉄筋コンクリート造)?(\d{1,2})階(地下\d{1}階建)?')
...: def structure(s):
...: #print(s)
...: for i in range(1,4):
...: try:
...: st = p.search(str(s)).group(i)
...: #print(s,st)
...: if not isinstance(st,type(None)):
...: #print(st)
...: return st
...: except:
...: return ""
...: return "-"
...: suumo_df["構造"] = [ structure(s) for s in suumo_df["構造・階建て"]]
...: def floor(s):
...: #print(s)
...: try:
...: f = p.search(str(s)).group(4)
...: if not isinstance(f,type(None)):
...: return f
...: else:
...: return "0"
...: except:
...: return "0"
...: suumo_df["階建て"] = [ floor(s) for s in suumo_df["構造・階建て"]]
...: suumo_df["階建て"] = suumo_df["階建て"].astype(int)
...: suumo_df.drop(["構造・階建て"], axis=1, inplace=True)
...:
中古マンションの情報を取得したはずなのですが、構造が木造って。。。。。
気になるのであとで個別にみることにします。
なお、階が取得できなかった場合は、0とするようにしました。
2.6 価格
いよいよ、一番重要な価格です。
基本的な単位は万円、億がついたりつかなかったりします。
さらに、価格幅が記載されている物件もありました。
どういうことでしょうね。同じマンション内の別の部屋など、複数物件の紹介がされているのでしょうか。
詳しく見ていないので分かりませんが、今回分析するにあたって、一つの物件で一つの価格が決まっていないと意味がないので、そうした表記の物件は除外
することにします。
In [23]: suumo_df.drop(suumo_df[suumo_df["価格"].str.contains("~")].index.values, axis=0, inplace=True)
...: suumo_df = suumo_df.reset_index(drop=True)
...: p = re.compile('(\d{1,2}億)?(\d{1,4})(万円)')
...: def convCost(s):
...: on = p.search(str(s)).group(1)
...: sn = int(p.search(str(s)).group(2))
...: if isinstance(on,type(None)):
...: return sn
...: else:
...: return int(on.replace("億",""))*10000 + sn
...: suumo_df["価格"] = [ convCost(s) for s in suumo_df["価格"] ]
...:
2.7 所在階
だんだん、処理内容を解説するのが面倒になってきました。
所在階が、地価の物件があったことに驚きました。B○階という表記だったので、地下の場合は負数で表現することにしました。
In [24]: suumo_df = suumo_df[~suumo_df["所在階"].str.contains("-")]
...: suumo_df["所在階"] = [str(s).replace("階","") for s in suumo_df["所在階"]]
...: suumo_df["所在階"] = [str(s).replace("B","-") for s in suumo_df["所在階"]]
...: suumo_df["所在階"] = suumo_df["所在階"].astype(int)
2.8 向き
向きは、方角が記載されていました。
ただ、これまた複数の物件の情報なのか、複数の方角が書かれている場合がありました。
この場合、"-"
で方角間が連結されていたので、まずこれを除外。そのあとに、one-hot表現へ変換しました。
In [25]: suumo_df = suumo_df[~suumo_df["向き"].str.contains("-")]
...: df = pd.get_dummies(suumo_df["向き"])
...: df.columns = ["向き-{}".format(s) for s in df.columns]
...: suumo_df = pd.concat([suumo_df,df],axis=1)
...: suumo_df.drop(["向き"],axis=1,inplace=True)
2.9 交通手段
最寄り駅からの交通手段です。
利用駅が複数ある場合、最大3つまで記載があるようです。3つの最寄り駅の情報は、もとはhtmlなので改行の<br/>
で区切られています。
これを改行部分を::
へ置換してみます。
In [27]: print("交通")
...: tlist = []
...: suumo_df["交通"] = [str(s).replace("<br/>","::") for s in suumo_df["交通"]]
すると、このような結果が得られます。
ブルーライン「上永谷」歩15分::京急本線「上大岡」バス21分水田歩2分::ブルーライン「上大岡」バス21分水田歩2分::
ここから、
- 路線名
- 最寄り駅
- 交通手段(歩/バス/その他)
- 移動時間
を分解します。さらに、バスを使用する場合はバス停名も含まれます。
これも、正規表現を使って処理していきます。
In [28]: p1 = re.compile('(.+)「(.+)」([^\d]*)([\d¥.]{1,4})')
...: p2 = re.compile('「?([^「^」]+)」?歩(\d{1,2})')
...: for s in suumo_df["交通"]:
...: st = [x for x in s.split("::") if x]
...: tmp = {}
...: for i,t in enumerate(st):
...: #print(t)
...: u = [x for x in re.split('[分km]', t) if x]
...: try:
...: tmp["交通{}_最寄路線".format(i)] = p1.search(str(u[0])).group(1)
...: tmp["交通{}_最寄駅".format(i)] = p1.search(str(u[0])).group(2)
...: tmp["交通{}_交通手段".format(i)] = p1.search(str(u[0])).group(3)
...: tmp["交通{}_時間".format(i)] = float(p1.search(str(u[0])).group(4))
...: if len(u)==2:
...: tmp["交通{}_バス停".format(i)] = p2.search(str(u[1])).group(1)
...: tmp["交通{}_バス停迄歩時間".format(i)] = p2.search(str(u[1])).group(2)
...: elif len(u)==3:
...: u[1] = u[1]+"分"+u[2]
...: tmp["交通{}_バス停".format(i)] = p2.search(str(u[1])).group(1)
...: tmp["交通{}_バス停迄歩時間".format(i)] = p2.search(str(u[1])).group(2)
...: except:
...: print(t)
...: #print(tmp)
...: tlist.append(tmp)
...: ndf = pd.DataFrame(tlist)
...: suumo_df = pd.concat([suumo_df, ndf],axis=1)
...: suumo_df.drop(["交通"],axis=1,inplace=True)
...: del(tmp,ndf,tlist)
2.10 所在地
物件の住所が取得できています。ただ、住所のままでは、ちょっと使いづらい形です。
そこで、geocodingすることにしちゃいましょう。
from geopy.geocoders import ArcGIS
from geopy.exc import GeocoderTimedOut
geolocator = ArcGIS(timeout=None)
llon = []
llat = []
Fails = []
for i,a in enumerate(suumo_df["所在地"]):
if i<len(llon):
continue
print(i,a)
try:
res = geolocator.geocode(a)
except GeocoderTimedOut as e:
geolocator = ArcGIS(timeout=None)
time.sleep(60)
res = geolocator.geocode(a)
if res == None:
print("\terror", a)
Fails.append[a]
else:
llat.append(res[1][0])
llon.append(res[1][1])
addressdf = pd.DataFrame({"所在地":suumo_df["所在地"], "lat":llat, "lon":llon})
addressdf.to_csv("data"+os.sep+'suumo_kanagawa_address.tsv', sep='\t', encoding='utf-8')
これで、住所の一覧から緯度経度の一覧を作り、そのDaraFrameを作ることができました。
2.11 その他の項目
駐車場とか権利形態とか、他にもいろいろカラムがあります。
が、ここまで記事を書いていたら、ちょっとしんどくなってしまいましたorz
3. before-after
データのクレンジングを行った結果のbefore-afterを、見た目をわかりやすくするためだけにexcelを使用してみてみます。
excelってこういう時しか活躍しませんよね。
before
※画像が小さいので、クリックして大きくしてみてください
after
※画像が小さいので、クリックして大きくしてみてください
4. まとめ
なので、そのほかのカラムの処理は、適当に済ませたということで、次回からは実際の分析に入っていきたいと思います。