LoginSignup
3
4

More than 1 year has passed since last update.

pythonによるデータ分析_前編(不動産取引価格予測_EDA編)

Last updated at Posted at 2023-03-16

1. はじめに

プログラミング未経験者がプログラミングスクールで約5月間、pythonによるデータ分析を学んだの経過を記事にまとめました。
記事が長い為、コードやデータについては後編のモデル構築・分析・評価の記事でまとめて公開しています。

1.1 データ分析を中心に取り組んだ理由

アプリ開発やウェブサイト制作についても興味はありましたが、自分がプログラミングを学んで世の中にある問題をどのように解決していきたいのか?と考えた際に、ざっくりと業務効率化や課題解決という視点が思い浮かびました。
そのため、データに潜む知見や価値を明らかにするデータサイエンスに強い興味を持ちました。

また、学習にあたってはそもそもデータ分析は手法であり、分析結果をどのように実際のビジネスに落とし込み最もインパクトのある意思決定に繋げるのか?という点を意識し取り組みました。

1.2 不動産取引データの選定理由

過去に不動産取引や管理運用に関わる経験があり、そもそも不動産関係のデータに興味があった事。また、予測モデル作成にあたりある程度の規模のデータが存在し取得できる事。といった点から選定しました。

今回分析に使用するデータは、国土交通省の不動産取引価格情報検索システムから大阪府の2005年第3四半期から2022年第第3四半期までの約17年分の不動産取引データです。

大阪府に絞った点については、三大都市圏内にあり、2022年第三四半期において3,200件のデータが存在し全都道府県別に見ても上位のデータ数であること、また、個人的に馴染みが深い地域の為です。

では、以下より分析に進みます。なお、文書についてはスッキリと仕上げたい為、いわゆる「である調」でまとめました。

データセットの引用元 → https://www.land.mlit.go.jp/webland/servlet/MainServlet

2. データ概要

データの概要を確認、各特徴量を見ていく。

2-1. ライブラリとデータの読み込み

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import warnings
pip install japanize-matplotlib
import japanize_matplotlib
df = pd.read_csv("osaka_real_estate.csv",encoding ='UTF-8')
df.head()

スクリーンショット 2023-02-14 18.52.48.png

2-1. 概要の確認

レコード数(行数)、カラム数(列数)、各カラムの名称、欠損値の有無、格納されているデータの型を確認。

df.shape
(295880, 30)
info()メソッドで内容を表示。
df.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 295880 entries, 0 to 295879
Data columns (total 30 columns):
 #   Column      Non-Null Count   Dtype  
---  ------      --------------   -----  
 0   No          295880 non-null  int64  
 1   種類          295880 non-null  object 
 2   地域          204790 non-null  object 
 3   市区町村コード     295880 non-null  int64  
 4   都道府県名       295880 non-null  object 
 5   市区町村名       295880 non-null  object 
 6   地区名         295779 non-null  object 
 7   最寄駅:名称      294395 non-null  object 
 8   最寄駅:距離(分)   292340 non-null  object 
 9   取引価格(総額)    295880 non-null  int64  
 10  坪単価         58075 non-null   float64
 11  間取り         84751 non-null   object 
 12  面積(㎡)       295880 non-null  object 
 13  取引価格(㎡単価)   58075 non-null   float64
 14  土地の形状       204441 non-null  object 
 15  間口          193618 non-null  object 
 16  延床面積(㎡)     141625 non-null  object 
 17  建築年         224722 non-null  object 
 18  建物の構造       230085 non-null  object 
 19  用途          222937 non-null  object 
 20  今後の利用目的     142914 non-null  object 
 21  前面道路:方位     204316 non-null  object 
 22  前面道路:種類     201916 non-null  object 
 23  前面道路:幅員(m)  200307 non-null  float64
 24  都市計画        293642 non-null  object 
 25  建ぺい率(%)     291055 non-null  float64
 26  容積率(%)      291055 non-null  float64
 27  取引時点        295880 non-null  object 
 28  改装          78489 non-null   object 
 29  取引の事情等      31845 non-null   object 
dtypes: float64(5), int64(3), object(22)
memory usage: 67.7+ MB

2-3. 基本統計量の確認

describe()メソッドにより、量的データに対する平均、標準偏差、最大値、最小値、の要約統計量を確認。

describe()メソッドで内容を表示。
df.describe()

スクリーンショット 2023-02-14 19.10.20.png

一応、カテゴリカルデータ(質的データ)に対しても確認。

df.describe(include=['O'])

スクリーンショット 2023-02-14 19.28.03.png

取引価格(総額)は平均4000万円程、中央値2000万円程度となっている。
不動産というと高額なイメージがあるが、取引価格の最小値が100円は意外であった。先ずは全体をざっくり見ていく。

2-4. データ型の変換

データの内容をざっくりとく見たところ、一見、数値であり量的データに見えるデータであるが、実はobject型である質的データが存在していたため、対象カラムを数値型へと変換する。具体的には以下のデータを対象に変換を行う。

対象のカラム = [最寄駅:距離(分)、面積(㎡)、間口、延床面積(㎡)]

df['最寄駅:距離(分)'] = df['最寄駅:距離(分)'].astype(float)

スクリーンショット 2023-02-14 19.41.02.png
エラーが発生、エラー文の内容からデータの一部に文字が入っている事が原因であると確認。どのような文字が存するのかvalue_counts()メソッドを用いて確認する。

df['最寄駅:距離(分)'].value_counts()

30分?60分が20,030個、1H?1H30が1497個、2H?が839個、1H30?2Hが417個確認できた。

辞書に対象となるデータを定義して、.replace().とastype()を利用し、str型を数値に変換し、objectをfloatへ型変換を行う。

dicto = {
    "30分?60分":45,
    "1H?1H30":75,
    "2H?":120,
    "1H30?2H":105
}
df['最寄駅:距離(分)'] = df['最寄駅:距離(分)'].replace(dicto).astype(float)

続いて面積(㎡)について見てみる。最寄駅:距離(分)同様、文字列が存在しエラーとなったので変換を行う。

面積(㎡)の型変換
df["面積(㎡)"].astype(float)

スクリーンショット 2023-02-14 19.52.10.png
2000㎡以上が含まれているためエラー。最寄駅:距離(分)と同様に数値型を確認すると、5000㎡以上も存在していたので同様に辞書に格納し変換。

area = {
    "2000㎡以上":2000,
    "5000㎡以上":5000
}
df["面積(㎡)"] = df["面積(㎡)"].replace(area).astype(float)

続いて間口の変換を行う。

間口の型変換
df["間口"].astype(float)

スクリーンショット 2023-02-14 19.57.38.png
50.0㎡が存在するためエラー、他の文字は確認できなかったので変換を行う。

df["間口"] = df["間口"].replace("50.0m以上",50).astype(float)

最後に延床面積(㎡)。

延床面積(㎡)の型変換

これまでと同様の手順で確認、10m^2未満と2000㎡以上が確認できた為、変換を行う。

floor_area = {
    "10m^2未満":9,
    "2000㎡以上":2000,
}
df["延床面積(㎡)"] = df["延床面積(㎡)"].replace(floor_area).astype(float)

一通り変換を終えたので基本統計量を確認する。

df.describe()

スクリーンショット 2023-02-14 20.06.42.png

最寄駅:距離(分)、面積(㎡)、間口、延床面積(㎡)を量的データに変換できた。

3. 取引場面の想定と目的変数の絞り込み

3-1. 不動産取引場面の想定

どのような場面での不動産取引を想定して分析を進めていくのかを想定する。

身近な例として先ず思い浮かぶのは、住宅購入等自らが使用する目的で取引が行われるケースと、収益を目的に取引が行われるケースではないだろうか。

また、主に不動産売買で取引される不動産の種別については、マンション、一戸建て、土地、分譲(マンション、土地、一戸建て)事業用物件といったものがメインとなっているようであった。(不動産ジャパンのサイトを参照)

不動産の性質や規模毎に取引の場面は多岐に渡ると想定できるが、今回は購入者が個人、販売者が不動産取り扱い業者。また、購入者販売者共に不動産取り扱い業者といった2パターンを前提に分析を進める。
個人の住宅購入や、収益用物件の売買といった身近なイメージしやすい場合を想定した。

また、不動産についてはいわゆる買回品に該当し製品単価が高い為、当事者間の一番の関心については取引価格である判断し、目的変数については取引価格(総額)を設定し分析を進める。

これまでの取引価格(総額)の実測値を基に予測値を知る事で、相場価格を加味した価格を知ることができ購入者販売者双方に対し、取引時の有効な指標となると考えた為である。

3-2. 目的変数の確認

ここで今回、目的変数として使用する取引価格の種別を見てみる。

5つの取引種別と件数、割合が確認できた。

取引種別と件数

df["種類"].value_counts()

宅地(土地と建物)     146715
中古マンション等       89996
宅地(土地)          58075
農地                 643
林地                 451
Name: 種類, dtype: int64

取引データの割合。

df["種類"].value_counts()/ len(df["種類"])

宅地(土地と建物)    0.495860
中古マンション等      0.304164
宅地(土地)         0.196279
農地              0.002173
林地              0.001524
Name: 種類, dtype: float64

取引金額の割合を見てみる。

df_tmp = df.groupby("種類").sum()
df_tmp["取引価格(総額)"].plot.pie(y="取引価格(総額)")
plt.show()

スクリーンショット 2023-02-16 14.09.08.png

各取引価格の合計額を比較。

df_tmp = df.groupby("種類").sum().reset_index()
sns.barplot(x="種類", y="取引価格(総額)", data=df_tmp)
plt.show()

スクリーンショット 2023-02-16 13.36.22.png

宅地(土地、土地と建物)と中古マンション等でほとんどを占めており、おおよそ思っていたとおりであった。

農地と林地が非常に低い割合であった事が気になり内容を見ていきたいが、一旦、それぞれの合計値、平均値、最小値、最大値、標準偏差を見てみる。

df_tmp = df.groupby("種類")["取引価格(総額)"].agg(["mean", "min", "max", "median", "std"])
df_tmp 
	                 mean	   min	       max	     median	            std
種類					
中古マンション等	1.939985e+07   450	  780000000	  17000000.0	1.427766e+07
宅地(土地)	    5.401939e+07   160	17000000000	  22000000.0	1.995484e+08
宅地(土地と建物)	4.916418e+07   250	45000000000   27000000.0	2.391150e+08
林地	            1.191373e+07   100	  850000000	   2400000.0	4.528307e+07
農地	            1.121543e+07 10000	  160000000	   6000000.0	1.635727e+07

各種毎の合計値でカラムを見てみる

df_tmp = df.groupby("種類").sum()
df_tmp        

スクリーンショット 2023-02-17 17.21.35.png

中古マンション等、宅地(土地)、宅地(土地と建物)については、それぞれに関係するカラム以外は0となっており(例えば、宅地(土地)の延床面積(㎡)、中古マンション等の間口)、レコードにはほぼ満遍なく値が入っている。

林地と農地については、合計値が0となったカラムが目立った。
中でも最寄駅:距離(分)の合計が0となっている点が気になった。全体における割合が非常に低い事、値が0であるカラムがおおよそ共通している事からこの2種類は類似した性質の取引として扱われているのではないか?と仮定し中身を見ていく。

3-2. 農地と林地の内容確認

林地と農地について、全体のボリューム感を順に見てみる。

先ずは農地から。
df[df["種類"] == "農地"]

スクリーンショット 2023-02-17 17.26.08.png

df[df["種類"] == "農地"].info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 643 entries, 40392 to 295830
Data columns (total 30 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   No          643 non-null    int64  
 1   種類          643 non-null    object 
 2   地域          0 non-null      object 
 3   市区町村コード     643 non-null    int64  
 4   都道府県名       643 non-null    object 
 5   市区町村名       643 non-null    object 
 6   地区名         643 non-null    object 
 7   最寄駅:名称      0 non-null      object 
 8   最寄駅:距離(分)   0 non-null      float64
 9   取引価格(総額)    643 non-null    int64  
 10  坪単価         0 non-null      float64
 11  間取り         0 non-null      object 
 12  面積(㎡)       643 non-null    float64
 13  取引価格(㎡単価)   0 non-null      float64
 14  土地の形状       0 non-null      object 
 15  間口          0 non-null      float64
 16  延床面積(㎡)     0 non-null      float64
 17  建築年         0 non-null      object 
 18  建物の構造       0 non-null      object 
 19  用途          0 non-null      object 
 20  今後の利用目的     10 non-null     object 
 21  前面道路:方位     0 non-null      object 
 22  前面道路:種類     0 non-null      object 
 23  前面道路:幅員(m)  0 non-null      float64
 24  都市計画        0 non-null      object 
 25  建ぺい率(%)     0 non-null      float64
 26  容積率(%)      0 non-null      float64
 27  取引時点        643 non-null    object 
 28  改装          0 non-null      object 
 29  取引の事情等      22 non-null     object 
dtypes: float64(9), int64(3), object(18)
memory usage: 155.7+ KB

続いて林地。
df[df["種類"] == "林地"]

スクリーンショット 2023-02-17 17.26.47.png

df[df["種類"] == "林地"].info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 451 entries, 46103 to 295875
Data columns (total 30 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   No          451 non-null    int64  
 1   種類          451 non-null    object 
 2   地域          0 non-null      object 
 3   市区町村コード     451 non-null    int64  
 4   都道府県名       451 non-null    object 
 5   市区町村名       451 non-null    object 
 6   地区名         451 non-null    object 
 7   最寄駅:名称      0 non-null      object 
 8   最寄駅:距離(分)   0 non-null      float64
 9   取引価格(総額)    451 non-null    int64  
 10  坪単価         0 non-null      float64
 11  間取り         0 non-null      object 
 12  面積(㎡)       451 non-null    float64
 13  取引価格(㎡単価)   0 non-null      float64
 14  土地の形状       0 non-null      object 
 15  間口          0 non-null      float64
 16  延床面積(㎡)     0 non-null      float64
 17  建築年         0 non-null      object 
 18  建物の構造       0 non-null      object 
 19  用途          0 non-null      object 
 20  今後の利用目的     8 non-null      object 
 21  前面道路:方位     0 non-null      object 
 22  前面道路:種類     0 non-null      object 
 23  前面道路:幅員(m)  0 non-null      float64
 24  都市計画        0 non-null      object 
 25  建ぺい率(%)     0 non-null      float64
 26  容積率(%)      0 non-null      float64
 27  取引時点        451 non-null    object 
 28  改装          0 non-null      object 
 29  取引の事情等      24 non-null     object 
dtypes: float64(9), int64(3), object(18)
memory usage: 109.2+ KB

共に11カラムの情報しか存在せず、似たようなデータ構造であった。

農地と林地の定義について確認と考察

データの引用元の説明を確認したが、詳しい説明は記載されていなかったため以下の通り考察した。

  • 不動産登記における土地の地目に着目。
  • 土地の登記事項の一つである地目は不動産登記規により定められており、地目は土地の主たる用途を表わすための名称である。本データで用いる農地については地目名が田および畑。また、林地については山林であると仮定する。
  • 一般的に農地・林地については都市部よりも地方、いわゆる市街化調整区域に多く存在する傾向にある。
  • 農地は売買にあたり農地法による規制が存在している。また、林地については宅地と比べ需要が少なく売買しにくい物件である。

https://www.sokuryo.or.jp/db/chimoku

https://map.maff.go.jp/

https://cityzone.mapexpert.net/TownMap?L=27

https://www.maff.go.jp/j/keiei/koukai/wakariyasu.html

https://www.lvnmatch.jp/column/sell/13260/#2

以上より、農地と林地については、中古マンションや宅地の売買と同様に取引が行われているとは考えにくく、特殊な性格を持つ取引であると判断した。
また、都市部よりも地方に存在する傾向にある事が最寄り駅や最寄り駅からの距離の値が存在いない原因の一つであった。

3-3.宅地(土地) 等の内容確認

残る3種類について見ていく。

取引の定義についてはデータの引用元より以下のとおり。

  • 宅地(土地) = 土地のみの取引。
  • 宅地(土地と建物) = 土地と建物等を一括した取引。
  • 中古マンション等 = 区分所有物件(戸単位)を取引。いわゆる分譲マンション・店舗・事務所・倉庫等。

各取引の構成比率。

宅地(土地と建物)    0.495860
中古マンション等     0.304164
宅地(土地)        0.196279
農地             0.002173
林地             0.001524
Name: 種類, dtype: float64

個々の取引合計金額。

df_tmp = df.groupby("種類").sum()["取引価格(総額)"].sort_values(ascending=False).reset_index()
df_tmp

	種類	取引価格総額
0	宅地(土地と建物)	7213122311710
1	宅地(土地)	    3137175899060
2	中古マンション等	1745908999380
3	農地	               7211522000
4	林地	               5373094100
種類 構成比率 取引価格(総額)
1位 宅地(土地と建物) 宅地(土地と建物)
2位 中古マンション等 宅地(土地)
3位 宅地(土地) 中古マンション等

構成比率、取引価格(総額)共に宅地(土地と建物)が一番大きい。おおよそ予想通り。

中古マンション等と宅地(土地)を比べると、構成比率では中古マンション等が宅地(土地)を上回っているが、取引価格(総額)では、宅地(土地)が中古マンション等を上回っている。

要因について考察する。
宅地(土地)の取引単価が中古マンション等と比べて相対的に高い、もしくは、宅地に大きな外れ値が多数存在している事が考えられるので、それぞれの内容を見ていく。

#取引の種類別にデータフレームを定義
df_ap = df[df["種類"] == "中古マンション等"]
df_l = df[df["種類"] == "宅地(土地)"]
df_lap = df[df["種類"] == "宅地(土地と建物)"]

ヒストグラムで分布を可視化して見ていく。

df_ap["取引価格(総額)"].hist(bins=1000,range=(0, 800000000))
plt.title('中古マンション等')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()

スクリーンショット 2023-02-16 18.09.27.png

#宅地(土地)
df_l["取引価格(総額)"].hist(bins=1000,range=(0, 18000000000))
plt.title('土地(宅地)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()

スクリーンショット 2023-02-16 18.10.36.png

それぞれ外れ値になっていそうな値を見てみる。

(そぞれの取引金額のおおよその真ん中より上の範囲を決め打ちで採用。)

df_ap["取引価格(総額)"].hist(bins=100,range=(400000000, 800000000))
plt.title('中古マンション等(4億〜8億)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()

スクリーンショット 2023-02-16 18.11.44.png

df_l["取引価格(総額)"].hist(bins=100,range=(8000000000, 18000000000))
plt.title('土地(宅地) (80億〜180億)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()

スクリーンショット 2023-02-16 18.13.07.png

外れ値らしきデータの内容を見てみる。
df_ap_h = df_ap[df_ap["取引価格(総額)"]  > 400000000]
df_ap_h

スクリーンショット 2023-02-16 16.31.52.png

#宅地(土地)
df_l_h = df_l[df_l["取引価格(総額)"]  > 8000000000]
df_l_h 

スクリーンショット 2023-02-16 16.28.45.png

外れ値らしきもの取引金額合計を確認すると、宅地(土地)と中古マンションでは約16倍の差があった。(別途、Excelで計算)

外れ値の存在が効いていそうである。また、これだけでも宅地(土地)の方が取引規模が大きそうであると考えられるので、もう少し深掘りしていく。

中古マンション等の400,000,000円以上の取引と、宅地(土地)の400,000,000円以上の取引を比較する。(宅地(土地)の範囲を中古マンション等のおおよそ真ん中の値に合わせて比較)

df_l["取引価格(総額)"].hist(bins=100,range=(400000000, 8000000000))
plt.title('土地(宅地) (4億〜80億)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()

スクリーンショット 2023-02-16 18.17.19.png

取引件数の確認。

df_ap_h = df_ap[df_ap["取引価格(総額)"]  > 400000000]
df_ap_h.shape[0]
#6
df_l_h = df_l[df_l["取引価格(総額)"]  > 400000000]
df_l_h.shape[0]
#878

それぞれ、6件、878件存在。宅地では高額取引が多数存在している事が要因の一部のようである。

各分布の山頂付近を確認。
df_ap["取引価格(総額)"].hist(bins=200,range=(0, 50000000))
plt.title('中古マンション等(〜5,000万)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()

スクリーンショット 2023-02-16 18.33.44.png

df_l["取引価格(総額)"].hist(bins=100,range=(10000000, 400000000))
plt.title('土地(宅地) (1,000万〜4億)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()

スクリーンショット 2023-02-16 18.35.00.png

df_l["取引価格(総額)"].hist(bins=100,range=(10000000, 100000000))
plt.title('土地(宅地) (1,000万〜1億)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()

スクリーンショット 2023-02-16 18.35.48.png

土地(宅地)、中古マンション等共に、1,000万から2,000万のあたりに分布の山頂が存在。

中古マンション等については2,000万円を上回る取引件数は次第に減少していき1億円付近で収束。
同じく土地についても1億円付近で取引件数が収束傾向にあるものの、1億円以上取引についても山が見られた。
1億円を基準に、それ以上の取引件数の割合を見てみる。

#中古マンション等
df_ap_h = df_ap[df_ap["取引価格(総額)"]  > 100000000]
df_ap_h.shape[0] / len(df_ap)
#0.0017778567936352727
#宅地(土地)
df_l_h = df_l[df_l["取引価格(総額)"]  > 100000000]
df_l_h.shape[0] / len(df_l)
#0.08998708566508824

中古マンション等は全体の0.2%弱、宅地(土地)は全体の9%程度であった。
このことから、土地については中古マンション等と比べ高額取引の割合が多く存在していると言える。
また、外れ値と仮定した取引金額の合計を比べても、中古マンションの約16倍近くになっており取引の規模も大きい。

よって、これらの要因によって構成比率と取引金額の順位の逆転が発生したと考えられる。

3-4. 目的変数の絞り込み

本分析の目的変数は、取引価格(総額)と設定したが、5種類の取引種別が存在していた。

これまで内容を見てきたところ、それぞれ異なる性質でありそうな為、全種別を一括して目的変数と設定するよりも種類を絞り込む事とする。

取引の性質に強い特殊性が存在する林地と農地は、除外する。また、宅地(土地と建物)については、建物価格と土地価格が明確に区分されていない事から、除外する。

宅地(土地)、中古マンション等のいずれかに絞る(または両方を使用する)事とするが、中古マンション等と比べ宅地(土地)の方が外れ値と考えられる値が多く存在していた事から、今回は宅地(土地)を除外する。

以上より、本分析で目的変数として使用する取引価格(総額)の種類については、中古マンション等を基準とする。

カラムの絞り込み
df = df[df["種類"] == "中古マンション等"]
df.head()

スクリーンショット 2023-02-17 17.40.45.png

df.shape
(86538, 31)

4.カラムの確認

4-1. 概要

絞り込んだ目的変数に関係のないデータや重複データを削除
df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 86538 entries, 0 to 295705
Data columns (total 31 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   No          86538 non-null  int64  
 1   種類          86538 non-null  object 
 2   地域          0 non-null      object 
 3   市区町村コード     86538 non-null  int64  
 4   都道府県名       86538 non-null  object 
 5   市区町村名       86538 non-null  object 
 6   地区名         86535 non-null  object 
 7   最寄駅:名称      86393 non-null  object 
 8   最寄駅:距離(分)   84807 non-null  float64
 9   取引価格(総額)    86538 non-null  int64  
 10  坪単価         0 non-null      float64
 11  間取り         81421 non-null  object 
 12  面積(㎡)       86538 non-null  float64
 13  取引価格(㎡単価)   0 non-null      float64
 14  土地の形状       0 non-null      object 
 15  間口          0 non-null      float64
 16  延床面積(㎡)     0 non-null      float64
 17  建築年         85649 non-null  object 
 18  建物の構造       85427 non-null  object 
 19  用途          78688 non-null  object 
 20  今後の利用目的     44802 non-null  object 
 21  前面道路:方位     0 non-null      object 
 22  前面道路:種類     0 non-null      object 
 23  前面道路:幅員(m)  0 non-null      float64
 24  都市計画        85829 non-null  object 
 25  建ぺい率(%)     85367 non-null  float64
 26  容積率(%)      85367 non-null  float64
 27  取引時点        86538 non-null  float64
 28  改装          75347 non-null  object 
 29  取引の事情等      1790 non-null   object 
 30  築年数         85649 non-null  float64
dtypes: float64(11), int64(3), object(17)
memory usage: 21.1+ MB

特徴量が0となっているカラムが存在。
データの引用元を確認すると、宅地(土地と建物)と宅地(土地)にのみ関係するデータであり、中古マンション等について関係のないデータであった為、削除。

df.drop(["地域", "坪単価", "取引価格(㎡単価)", "土地の形状", "間口", "延床面積(㎡)", "前面道路:方位", "前面道路:種類", "前面道路:幅員(m)"],axis=1)
df

スクリーンショット 2023-02-17 17.56.31.png

df.shape
(89996, 21)

特徴量が21個まで減少。

続いてカラムの内容について見ていく。
市区町村コードの内容がよく分からなかったため内容を確認。

https://www.soumu.go.jp/denshijiti/code.html

年度によりバラツキはあるが、実質的に市町村名と同じデータであるようでる。
実際に確認するとそうであった為、市区町村コードについては削除する。

df["市区町村コード"].value_counts()
27128    5611
27205    5191
27123    5148
27127    5036
27203    4506
         ... 
27366      74
27232      66
27321      45
27341      31
27147       2
Name: 市区町村コード, Length: 67, dtype: int64
df["市区町村名"].value_counts()
大阪市中央区    5611
吹田市       5191
大阪市淀川区    5148
大阪市北区     5036
豊中市       4506
          ... 
泉南郡岬町       74
阪南市         66
豊能郡豊能町      45
泉北郡忠岡町      31
堺市美原区        2
Name: 市区町村名, Length: 67, dtype: int64
df = df.drop("市区町村コード",axis=1)
df.head()

スクリーンショット 2023-02-17 18.00.13.png

4-2. 数値型への変換

数値型にできそうなデータを変換する。
建築年について築年数として数値型へ変換する。

y_list = {}

for i in df['建築年'].value_counts().keys(): 
    if "昭和" in i: 
      num = float(i.split('昭和')[1].split('')[0]) 
      year = 98 - num
    if "平成" in i: 
      num = float(i.split('平成')[1].split('')[0]) 
      year = 35 - num
    if "令和" in i:
      num = float(i.split('令和')[1].split('')[0]) 
      year = 5 - num
    if "戦前" in i:
      num = i
      year = 78
    y_list[i] = year  

df['築年数'] = df['建築年'].replace(y_list).astype(float)
df = df.drop('建築年', axis=1)
df

スクリーンショット 2023-02-17 18.08.53.png
取引時点についても変換。

year ={
    "年第1四半期": ".25",
    "年第2四半期": ".5",
    "年第3四半期": ".75",
    "年第4四半期": ".99"
}

year_list = {}

for i in df["取引時点"].value_counts().keys():
    for k, j in year.items():
        if k in i:
          year_rep = i.replace(k, j)
    year_list[i] = year_rep
year_list

df['取引時点'] = df['取引時点'].replace(year_list).astype(float)
df

スクリーンショット 2023-02-17 18.10.25.png

単なる通し番号のNoカラムを、削除
df = df.drop("No",axis=1)

スクリーンショット 2023-02-19 12.39.27.png

5.外れ値の除去

ざっくりと外れ値らしき値は確認したが、正式に外れ値を定義して除去を行う。

箱ひげ図を描写し外れ値を定義。
plt.figure(figsize=(5, 8))
sns.boxplot(y='取引価格(総額)', data=df)
plt.title('箱ひげ図 [取引価格(総額)]')
plt.show()

スクリーンショット 2023-02-18 11.17.58.png

続いて数値を確認。

df.describe()

re.png

今回は、第3四分位数にIQRの1.5倍の値を加えた46,000,000円以下の物件に限定する。(≒標準偏差+-3σ)

df = df[df["取引価格(総額)"]  < 46000000]
df.shape
(86538, 20)

6.相関の確認

ヒートマップを用いて各カラムの相関を見てみる。
直感的に面積については強い相関がありそうである。

cor = df.corr()
plt.figure(figsize=(10,10))
sns.heatmap(cor, cmap= sns.color_palette('coolwarm', 10), annot=True,fmt='.2f', vmin = -1, vmax = 1)
plt.show()

スクリーンショット 2023-02-19 12.43.31.png

面積は予想どおり、築年数もやや弱い負の相関が見られたが古い建物の方が価値は下がりそうなのでおおよそ納得はできた。

正の相関が見られた面積の分布を見てみる。

全体
df["面積(㎡)"].hist(bins=100,range=(0, 800))
plt.title('面積(全体)')
plt.show()

スクリーンショット 2023-02-18 16.20.25.png

外れ値が存在、山頂付近を見てみる。

df["面積(㎡)"].hist(bins=100,range=(0, 150))
plt.title('面積(〜150)')
plt.show()

スクリーンショット 2023-02-18 16.21.20.png

外れ値は存在するものの、20㎡あたりと70㎡あたりに山ができている。単身者向け、ファミリー向けの物件であろうかと考えられる。取引価格(総額)との散布図を見てみる。

sns.jointplot(x="面積(㎡)", y="取引価格(総額)", data=df)
plt.show()

スクリーンショット 2023-02-19 12.46.39.png

外れ値であろう400㎡以上の取引価格を見てみる。
df_a = df[df["面積(㎡)"]  > 400].copy()
df_a.sort_values(by="面積(㎡)", ascending=False)
df_a

スクリーンショット 2023-02-21 18.19.15.png

店舗が多いかと予測していたが意外に住宅が多かった。対個人であるが対法人であるかは不明であるが、この規模の住宅取引が多数存在していた事は意外であった。

築年数についても見てみる。
df["築年数"].hist(bins=100,range=(0, 100))
plt.title('築年数')
plt.show()

スクリーンショット 2023-02-18 16.29.57.png

15年前から30年前あたりに大きな山が見られる。平成の時代に建てられたマンションが多いようである。
それ以降にも波があり50年前付近で最も大きな山が存在した。50年前というと高度成長期の終わりから安定成長期に入る時期のようであるが、ここでは外れ値の値を見てみる。

df_b = df[df["築年数"]  > 70].copy() 
df_b.head()

スクリーンショット 2023-02-21 18.24.46.png

元データで全ての内容を見てみると、大阪市内の駅近物件が目立った。未改装物件が多数占めていた事が意外であった。(改装の内容をどう解釈するかにもよるが。)

7. データ内容の確認

分析に使えそうなデータに整形できたところで、改めてデータを見てみる。

市町村名毎に取引価格(総額)の多い順で見てみる、直感的に規模の大きい自治体順に並んでそうである。

df_tmp = df.groupby("市区町村名").sum().reset_index()
df_tmp.sort_values(by="取引価格(総額)",ascending=False,inplace=True)

plt.figure(figsize=(25, 13))
sns.barplot(x="取引価格(総額)", y="市区町村名", data=df_tmp)
plt.show()

スクリーンショット 2023-02-18 15.07.52.png

おおよそ予想通り。

築年数毎に取引価格はどういった分布となっているのか見てみる。

df_tmp = df.groupby("築年数").sum().reset_index()
df_tmp.sort_values(by="取引価格(総額)",ascending=False,inplace=True)
plt.figure(figsize=(30, 13))
sns.barplot(x="築年数", y="取引価格(総額)", data=df_tmp)
plt.show()

スクリーンショット 2023-02-21 18.44.19.png
概ね取引件数の分布と似た形となったが、50あたりを取引件数と比較すると小さい山となっている。件数の割には取引単価が低くなっていた事が原因と考えられる。

今回は、機会学習の一連の流れをまとめる事に注力したいのでEDAはここまでとする。

整形したデータを別データとして保存。

df.to_csv('mid.csv',encoding ='UTF-8', index=False)

続いて、モデル構築、分析、評価を行う。
(続く)

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4