はじめに
就職や進学を機に上京する人がたくさんいます。私もその中のひとりでした。そこで考えなければならないのが「住む家」です。東京は家賃が高い、地域によって相場が違う、六本木や麻布十番、乃木坂、赤坂、青山などの坂道聖地巡礼スポットのある港区は特に高いなど、よく耳にします。そこで、賃貸情報サイトより東京都内23区の賃貸情報のデータを収集してそのデータで家賃の予測を行いました。
開発環境
・ MacOS Monterey 12.5.1
・ Anaconda
・ Jupyter Notebook
手順
- データ収集
- データ前処理
- データ分析
- モデル構築(パラメータチューニングなし)
- モデル構築(パラメータチューニングあり)
- モデルの評価
1. データ収集
緑のキャラクターでお馴染みの賃貸情報サイトSUUMOより東京都内23区の賃貸情報のデータを収集しました。
東京都内23区賃貸情報 from SUUMO
2022年10月現在では約130万件のデータが掲載されてました。
このデータを収集するためにrequestsとBeautifulSoupを使いました。
requestsはURLを読み込んで、サイトにアクセスするモジュールです。
$ pip install requests
requests 2.27.1
BeautifulSoupはサイトのHTMLファイルを解析するモジュールです。
タグやクラスを指定することでサイト内の情報を収集することが可能です。
$pip install beautifulsoup4
beautifulsoup4 4.11.1
データを収集するコードに関しては、こちらの記事が非常にわかりやすかったです。
是非参考にしてみてください。
https://qiita.com/tomyu/items/a08d3180b7cbe63667c9
上記のコードで収集したデータは全部で222923件, 特徴量は16でした。SUUMO側でアクセスの制限がかかっているせいか、130万件のデータ収集とはいけませんでした。今回はこちらのデータを使用します。
2. データ前処理
収集したデータの内容を確認します。
住所がひとまとまりになっている、最寄りの駅もひとまとまりになっている、さらにそのほかの変数には単位がついているなど、このままではモデル構築どころかデータの分析すらできません。そこでデータの前処理を行います。
【住所】:23区、都市名に分割。
【最寄駅】:最寄り駅1のみ採用し、最寄駅、路線、徒歩に分割。
【築年数】:単位なしのint型に変換。
【階数】:単位なしのint型に変換。
【階】:単位なしのint型に変換。
【賃料】:単位なしのfloat型に変換。
【管理費】:単位なしのfloat型に変換。
【敷金】:単位なしのfloat型に変換。
【礼金】:単位なしのfloat型に変換。
【専有面積】:単位なしのfloat型に変換。
前処理を行うコードはこちらです。
def preprocessing(df):
df_new=df.copy()
wards=[]
citys=[]
lines=[]
stations=[]
distances=[]
results=[]
ages=[]
builds=[]
Floors=[]
prices=[]
kanrihis=[]
sikikins=[]
reikins=[]
areas=[]
for i in range(0,len(df_new)):
###住所を'23区'、'都市名'に分ける
pattern='.*区'
repatter = re.compile(pattern)
result = repatter.match(df['住所'][i])
wards.append(result.group()[3:])
citys.append(df['住所'][i].split('区')[1])
access=dataset['最寄り駅1'][i]
#路線
line=access.split('/')[0]
#駅と徒歩
station_distance=access.split('/')[1]
#駅
station=re.findall(r'.*駅', station_distance)
#徒歩
distance=re.findall(r'\d+',access)
lines.append(line)
if len(station)==1:
stations.append(station[0])
else:
stations.append(station_distance.split(' ')[0])
distances.append(int(distance[0]))
###築年数はint型に変換
age=dataset['築年数']
if age[i][0]=='新':
ages.append(0)
else:
ages.append(int(age[i].split('年')[0][1:]))
###構造
m=re.findall(r'\d+', dataset['階数'][i])
if len(m)==1:
builds.append(int(m[0]))
elif len(m)==2:
builds.append(int(m[1]))
else:
builds.append(1)
###部屋の階数
floor=dataset['階'][i]
f=re.findall(r'\d+',floor)
if len(f)==1:
Floors.append(int(f[0]))
else:
Floors.append(random.randint(1,int(builds[i])))
###賃料
price=dataset['賃料']
prices.append(float(price[i].split('万')[0])*10000)
###管理費
kanrihi=dataset['管理費']
if kanrihi[i]!='-':
kanrihis.append(int(kanrihi[i].split('円')[0]))
else:
kanrihis.append(float(0))
###敷金
sikikin=dataset['敷金']
if sikikin[i]!='-':
sikikins.append(float(sikikin[i].split('万')[0])*10000)
else:
sikikins.append(float(0))
###礼金
reikin=dataset['礼金']
if reikin[i]!='-':
reikins.append(float(reikin[i].split('万')[0])*10000)
else:
reikins.append(float(0))
###面積
area=dataset['専有面積']
areas.append(float(area[i].split('m')[0]))
###変換したカラムの追加
df_new['23区']=wards
df_new['都市名']=citys
df_new['路線']=lines
df_new['最寄駅']=stations
df_new['徒歩']=distances
df_new['築年数']=ages
df_new['階数']=builds
df_new['階']=Floors
df_new['賃料']=prices
df_new['管理費']=kanrihis
df_new['敷金']=sikikins
df_new['礼金']=reikins
df_new['専有面積']=areas
###不要なカラムを削除
df_new=df_new.drop('住所',axis=1)
df_new=df_new.drop(['最寄り駅1', '最寄り駅2', '最寄り駅3'],axis=1)
return df_new
df_new=preprocessing(dataset)
df_new.head()
3. データ分析
目的変数の賃料を予測するためには、データの特徴について調べる必要があります。
ここでの内容は人それぞれです。私が分析した内容は下記の通りです。
【各特徴量の相関係数の確認】
・部屋の階数が高い物件ほど、賃料が高い
・敷金と礼金が高い物件ほど、賃料が高い
(実際、賃料が高いエリアの物件は敷金と礼金を払わなければならない)
・専有面積が大きい物件ほど、賃料が高い
SUUMOを見ていると、同じ建物でも回数が高いと間取り、専有面積が大きくなっている物件が存在していました。そこで、階数と専有面積の関係も確認したところ正の相関がありました。
【23区別の賃貸件数の確認】
都心へのアクセスがいい住宅街という印象の世田谷区、大田区などが多い。一方で商業ビルが立ち並ぶ印象の千代田区などは少ない。
【23区別の家賃の平均の確認】
大物芸能人の方や社長さんなどのイケてる人たち(お金持ち)が住んでいる港区がダントツで高い。一方で、千葉県、埼玉県など東京都の隣県に近い葛飾区、足立区は比較的低い。全体的に都心部は高く、郊外は低い印象。
【23区別の家賃の箱ひげ図の確認】
家賃の平均が高い区が安いワンルームアパートから高級タワーマンションがあるため、ばらつきが大きい傾向にある。一方で、平均が低い区のばらつきは比較的小さい。
【専有面積と家賃の関係の確認】
やはり、物件の専有面積が大きいほどその分賃料は高くなる傾向にある。東京で家族で住むとなると、相当稼いでないと厳しい。
4. モデル構築
データの特徴を見ることができたので、次はモデルの構築を行いました。使用したモデルはlightGBMです。使用した理由は過去に中古マンションの価格予測タスクに挑戦した際に使用したところ、高い精度で予測することができたためです。今回は4つの手法で予測を行いました。
・パラメータチューニングなし
・パラメータチューニングあり
・パラメータチューニング+目的変数を対数変換
・パラメータチューニング+目的変数を対数変換+専有面積を対数変換
【パラメータチューニングなし】
デフォルトのハイパーパラメータでlightGBMで学習を行いました。
【パラメータチューニングあり】
OptunaでハイパーパラメータをチューニングしたlightGBMで学習を行いました。
$ pip install optuna
optuna 2.10.1
【パラメータチューニング+目的変数を対数変換】
対数変換した賃料を目的変数として、Optunaでパラメータのチューニングを行い、そのモデルで学習を行いました。
【パラメータチューニング+1平米当たりの賃料を対数変換】
対数変換した賃料と専有面積より、1平米あたりの賃料を算出。それを目的変数としてOptunaでパラメータのチューニングを行い、そのモデルで学習を行いました。予測結果に対数変換した専有面積を加えることで、最終的に求めたい賃料を算出しました。
(例)家賃が10万円、専有面積が30平米
普通に計算すると、1平米あたりの賃料は100,000/30=3333.333...[円/m^2]
対数変換を使って計算すると、
$$a=\log\frac{100000}{30}=\log100000-\log30$$
※モデルの実装のコードですが、長くなるのでgithubをご覧ください。
5. モデルの評価
4つの手法の評価を表にまとめました。評価指標はMAE(平均絶対誤差)です。
またモデルがどれくらい予測できているかを示す、決定係数も表に含まれています。
4つ目の賃料と専有面積を対数変換する手法が最も精度が高かったです。
まとめ
データ分析コンペや練習問題で用意されているデータがいかに整理されているかを実感することができました。正規表現でデータの前処理を行う際にマッチしない文字列が出てきたときは何度も心が折れそうになりました。しかし、実際の現場ではこのような整理されていないデータを扱うことになるので、今回の経験はプラスになりました。変数重要度が高い変数を使った形で変換した目的変数を予測する手法は非常に効果的でした。よく利用するサイトだったこともあり、どんな賃貸情報が掲載されているのかをじっくり見ることができて楽しかったです。都内散策の楽しみ方のバリエーションが増えた気がしました。
【参考資料】
・SUUMOの物件情報を自動取得(スクレイピング)したのでコードを解説する。
→SUUMOから賃貸情報を収集する手順が丁寧にまとめられています。
・LightGBM Tunerを使ったハイパーパラメータ最適化
→Optunaを使ってハイパーパラメータをチューニングする内容がまとめられています。
・ 回帰分析で変数の対数を取る理由はなんでだっけ?
→目的変数を対数変換することでどのような利点があるか書かれています。
【ソースコード】