価格vs所在地
前回までに収集された不動産価格の分布を改めて以下に示します。
この価格はすべての年代における、大阪全土の物件が含まれています。 全体の傾向を知るにはこれで構いませんが、もっと詳細に、特に今回は物件価格と物件所在地の関係を見ていきます。
とにかくはじめます🍛
物件を地図上にプロット
まずは取引が活発に行われている場所を知りたいので、各物件を地図上にプロットしてみましょう。
とは言ったのものの、少なくとも以下の手順が必要です。
- 大阪の地図を描画する
- 物件の緯度・経度を求める
- 物件の位置を地図にプロットする
大阪の地図の描画
地図の描画方法にもいろいろ方法がありますが、今回はGeoPandas
を使用してみます。
GeoPandas
はPythonで地理データを扱いやすくするために開発されたpandas
の拡張パッケージです。
GeoPandas
に大阪の地理データを放り込めば、簡単に地図を描画できるようなります。
地図データ
地図情報は e-Stat から取得しました。
政府統計の総合窓口(e-Stat)は、各府省が公表する統計データを一つにまとめ、統計データを検索したり、地図上に表示できるなどの、たくさんの便利な機能を備えた政府統計のポータルサイトです。
e-Statは他にも面白そうなデータがありそうですが、今回使用したのはこちらのShapefileです。
Shapefileとは地理情報を扱うデータタイプの一種です。 例えば mapshaper というWebサービスを利用すれば、簡単に地形情報を確認することができます。
GeoPandasで描画
# 必要なモジュールを事前にインストールしておく
pip install geopandas descartes mapclassify
さっそくダウンロードしたShapefile
をGeoPandas
に放り込みましょう。
と思ったのですが、Shapefile
は評判が悪いようなので1、私は上記サイトで GeoJSON に変換してから読み込みを行いました。
import geopandas as gpd
osaka = gpd.read_file("osaka.geojson")
osaka[osaka.HCODE != 8154].plot()
いいですね。
(色や座標系を変えているのでmapshaper
の結果と少し違っていますが)
なおHCODE
が8154
の部分は水面調査区と定義されている場所のため、除外してプロットするようにしています。
各カラムや値の意味はこちらの 定義書 に記載されている通りで、面積や人口なんかも記録されていますね。
大阪市だけ描画
大阪市だけを描画したい場合は以下のようになります。
city = osaka[osaka.GST_NAME == '大阪市']
city.plot(linewidth=0.1)
# アノテーション例
pos = (city.centroid.x.mean(), city.centroid.y.mean())
ax.annotate('大阪市', pos, horizontalalignment='center')
データには町や丁目まで含まれているため、そのすべてが細かく描画されていることが確認できます。 ただし細かすぎるので、もっとざっくり、例えば大阪府を市単位で集約したい場合はどうすれば良いのでしょうか?
通常のpandas
であればgroupby
を使用しますが、GeoPandas
ではdissolve
を使って、市単位で集約してやるようです。
osaka_diss = osaka.dissolve(by='GST_NAME', aggfunc='sum')
面積や人口といった情報が集約(加算)されています。
GST_NAME | AREA | JINKO |
---|---|---|
(都市名) | (面積) | (人口) |
大阪市 | 220,137,838 | 2,691,185 |
堺市 | 149,490,353 | 839,310 |
東大阪市 | 61,668,858 | 502,784 |
枚方市 | 65,164,856 | 404,152 |
豊中市 | 36,222,932 | 395,479 |
吹田市 | 36,166,799 | 374,468 |
: | : | : |
それだけでなく、地理情報も集計されているので、以下のようにすれば大阪市だけをシンプルに描画できます。
osaka_diss.loc[['大阪市']].plot()
物件の緯度・経度を求める
先ほどの地図上に各不動産の位置をプロットするためには、物件所在地に対応する緯度・経度が必要です。
しかしながら、前回収集したデータには緯度・経度は含まれていないので、物件所在地に対応する緯度・経度を自分で求める必要があります。
緯度・経度を求める
緯度・経度の取得には Google Maps Platform の Geocoding API を使用します。
このサービスは住所をインプットとして、それに対応する緯度・経度を返してくれるGoogleのWEBサービスです。
利用料金は以下が分かりやすいとかと思いますが、1,000リクエストで5ドルとなっています😕
料金表 | Google Maps Platform | Google Cloud
しかし毎月$200分は無料で利用できるので、月40,000リクエスト以内であれば無料で利用可能です😄
なお物件は60,000件近くあるため無料枠を超えそうですが、ユニークな住所は高々2,000件ほどしかありません。 というのも取引価格情報提供制度により収集されたデータは、物件を特定できないように詳細な番地レベルまでは記載されていないためです。
なお実際の取得ロジックは公式のサンプルを適当に修正すればすぐに使えるかと思います。
def geocoding(address:str):
'''Geocoding API を呼び出す'''
maps_key = 'APIキーを取得して貼り付けてください'
base_url = 'https://maps.googleapis.com/maps/api/geocode/json'
# This joins the parts of the URL together into one string.
url = base_url + '?' + urllib.parse.urlencode({
'address': address,
'key': maps_key,
})
# Get the API response.
response = urllib.request.urlopen(url).read()
# If we didn't get an IOError then parse the result.
result = json.loads(response)
if result['status'] == 'OK':
return result
elif result['status'] != 'UNKNOWN_ERROR':
# Many API errors cannot be fixed by a retry, e.g. INVALID_REQUEST or
# ZERO_RESULTS. There is no point retrying these requests.
raise Exception(result)
def get_lat_lng(address:str):
'''緯度経度を取り出す'''
res = geocoding(address)
latlng = res['results'][0]['geometry']['location']
return latlng['lat'], latlng['lng']
物件の位置を地図にプロットする
あとはpandas
をGeoPandas
に変換してからプロットするだけです。2
# 変換
lon = df.lon.values
lat = df.lat.values
geo_df = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(lon, lat))
# 描画(同じaxに)
_, ax = plt.subplots()
osaka_diss.plot(ax=ax)
geo_df.plot(ax=ax)
これで取引されている位置をだいたい把握することができました。
物件価格を表現
応用編として物件の色で価格帯を表現してみました。 さらに同じ位置に物件が多く重なってしまうので(詳細な住所情報がないため)、正規分布で少し散らしています。
なんと無く地域による価格帯の違いが分かりますが、ちょっとわかりにくいですね。
高額物件はどこに?
ちなみに1億円越えの物件だけプロットすると、大阪市に集中しているのが良く分かったりします。
コロプレス図で表現
地域による物件価格の違いを表現するにはコロプレス図を使う方が分かりやすいです。
例えばコロプレス図で人口密度を描くと、先ほどの物件位置のプロットと対応しており、人口密度が高いほど取引も活発なことが良く分かりますね。 (色が白い部分ほど人口密度が高いエリアです)
平均物件価格
コロプレス図により物件価格に応じて、各地域を塗り分けてみると価格分布の特徴が分かりやすくなりました。 (グレー部分は取引が無いためデータ無しを意味しています)
これぞコロプレス図です。
平均が2,000万円を超えている地域を並べると、こんな様子です。 (ただしかなり裾の長い分布であることを思い出してください)
都市名 | 平均価格[円] |
---|---|
大阪市阿倍野区 | 24,036,978 |
大阪市北区 | 23,656,365 |
大阪市福島区 | 23,099,096 |
大阪市天王寺区 | 22,858,523 |
豊中市 | 22,804,468 |
高槻市 | 21,883,620 |
吹田市 | 21,873,886 |
池田市 | 21,703,939 |
大阪市鶴見区 | 21,113,947 |
大阪市西区 | 20,937,001 |
大阪市中央区 | 20,921,137 |
大阪市都島区 | 20,600,894 |
茨木市 | 20,595,151 |
箕面市 | 20,542,278 |
なお基本的にはcolumn
引数に色分けしたい対象のカラム名を渡すだけで、コロプレス図は描けてしまいます。
bukken.plot(column='price', scheme='FisherJenks', k=5, cmap="YlOrRd_r", legend=True)
10年以上前だと…
多少の違いはありますが、だいたいの傾向は同じようです。 (この微妙な違いに、何か面白い気配を感じるものの今回はスキップ)
平均上昇幅
次に各地域での伸び率を知りたくなったので、2006年から2019年までで上昇幅をプロットしてみます。 ここでいう上昇幅とは、線形回帰した際の傾きを意味します。 ただし次の記事で見るように、必ずしも線形的に上昇しているわけでは無いので、あくまで参考程度で。
平均上昇幅が50万以上の都市は以下となりました。
都市名 | 平均上昇[円] |
---|---|
大阪市天王寺区 | 886,317 |
大阪市福島区 | 808,060 |
摂津市 | 649,487 |
大阪市西区 | 590,089 |
高槻市 | 572,935 |
大阪市中央区 | 513,152 |
大阪市阿倍野区 | 506,711 |
まとめ
今回は地図上へのプロットを中心に行ってみましたが、GeoPandas
を使用すると比較的簡単に地図が扱えるとことが分かりました。
実は地図の座標系などをちゃんと考えるとかなり面倒なのですが、今回はあまり気にする必要はないかと思います。