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()
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()メソッドにより、量的データに対する平均、標準偏差、最大値、最小値、の要約統計量を確認。
取引価格(総額)は平均4000万円程、中央値2000万円程度となっている。
不動産というと高額なイメージがあるが、取引価格の最小値が100円は意外であった。先ずは全体をざっくり見ていく。
2-4. データ型の変換
データの内容をざっくりとく見たところ、一見、数値であり量的データに見えるデータであるが、実はobject型である質的データが存在していたため、対象カラムを数値型へと変換する。具体的には以下のデータを対象に変換を行う。
対象のカラム = [最寄駅:距離(分)、面積(㎡)、間口、延床面積(㎡)]
df['最寄駅:距離(分)'] = df['最寄駅:距離(分)'].astype(float)
エラーが発生、エラー文の内容からデータの一部に文字が入っている事が原因であると確認。どのような文字が存するのか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)
続いて面積(㎡)について見てみる。最寄駅:距離(分)同様、文字列が存在しエラーとなったので変換を行う。
面積(㎡)の型変換
続いて間口の変換を行う。
間口の型変換
最後に延床面積(㎡)。
延床面積(㎡)の型変換
これまでと同様の手順で確認、10m^2未満と2000㎡以上が確認できた為、変換を行う。
floor_area = {
"10m^2未満":9,
"2000㎡以上":2000,
}
df["延床面積(㎡)"] = df["延床面積(㎡)"].replace(floor_area).astype(float)
一通り変換を終えたので基本統計量を確認する。
df.describe()
最寄駅:距離(分)、面積(㎡)、間口、延床面積(㎡)を量的データに変換できた。
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()
各取引価格の合計額を比較。
df_tmp = df.groupby("種類").sum().reset_index()
sns.barplot(x="種類", y="取引価格(総額)", data=df_tmp)
plt.show()
宅地(土地、土地と建物)と中古マンション等でほとんどを占めており、おおよそ思っていたとおりであった。
農地と林地が非常に低い割合であった事が気になり内容を見ていきたいが、一旦、それぞれの合計値、平均値、最小値、最大値、標準偏差を見てみる。
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
中古マンション等、宅地(土地)、宅地(土地と建物)については、それぞれに関係するカラム以外は0となっており(例えば、宅地(土地)の延床面積(㎡)、中古マンション等の間口)、レコードにはほぼ満遍なく値が入っている。
林地と農地については、合計値が0となったカラムが目立った。
中でも最寄駅:距離(分)の合計が0となっている点が気になった。全体における割合が非常に低い事、値が0であるカラムがおおよそ共通している事からこの2種類は類似した性質の取引として扱われているのではないか?と仮定し中身を見ていく。
3-2. 農地と林地の内容確認
林地と農地について、全体のボリューム感を順に見てみる。
先ずは農地から。
df[df["種類"] == "農地"]
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["種類"] == "林地"]
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://cityzone.mapexpert.net/TownMap?L=27
以上より、農地と林地については、中古マンションや宅地の売買と同様に取引が行われているとは考えにくく、特殊な性格を持つ取引であると判断した。
また、都市部よりも地方に存在する傾向にある事が最寄り駅や最寄り駅からの距離の値が存在いない原因の一つであった。
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()
#宅地(土地)
df_l["取引価格(総額)"].hist(bins=1000,range=(0, 18000000000))
plt.title('土地(宅地)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()
それぞれ外れ値になっていそうな値を見てみる。
(そぞれの取引金額のおおよその真ん中より上の範囲を決め打ちで採用。)
df_ap["取引価格(総額)"].hist(bins=100,range=(400000000, 800000000))
plt.title('中古マンション等(4億〜8億)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()
df_l["取引価格(総額)"].hist(bins=100,range=(8000000000, 18000000000))
plt.title('土地(宅地) (80億〜180億)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()
外れ値らしきデータの内容を見てみる。
外れ値らしきもの取引金額合計を確認すると、宅地(土地)と中古マンションでは約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()
取引件数の確認。
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()
df_l["取引価格(総額)"].hist(bins=100,range=(10000000, 400000000))
plt.title('土地(宅地) (1,000万〜4億)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()
df_l["取引価格(総額)"].hist(bins=100,range=(10000000, 100000000))
plt.title('土地(宅地) (1,000万〜1億)')
plt.xlabel('取引金額')
plt.ylabel('件数')
plt.show()
土地(宅地)、中古マンション等共に、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種類の取引種別が存在していた。
これまで内容を見てきたところ、それぞれ異なる性質でありそうな為、全種別を一括して目的変数と設定するよりも種類を絞り込む事とする。
取引の性質に強い特殊性が存在する林地と農地は、除外する。また、宅地(土地と建物)については、建物価格と土地価格が明確に区分されていない事から、除外する。
宅地(土地)、中古マンション等のいずれかに絞る(または両方を使用する)事とするが、中古マンション等と比べ宅地(土地)の方が外れ値と考えられる値が多く存在していた事から、今回は宅地(土地)を除外する。
以上より、本分析で目的変数として使用する取引価格(総額)の種類については、中古マンション等を基準とする。
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
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()
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
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
5.外れ値の除去
ざっくりと外れ値らしき値は確認したが、正式に外れ値を定義して除去を行う。
箱ひげ図を描写し外れ値を定義。
今回は、第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()
面積は予想どおり、築年数もやや弱い負の相関が見られたが古い建物の方が価値は下がりそうなのでおおよそ納得はできた。
正の相関が見られた面積の分布を見てみる。
外れ値が存在、山頂付近を見てみる。
df["面積(㎡)"].hist(bins=100,range=(0, 150))
plt.title('面積(〜150)')
plt.show()
外れ値は存在するものの、20㎡あたりと70㎡あたりに山ができている。単身者向け、ファミリー向けの物件であろうかと考えられる。取引価格(総額)との散布図を見てみる。
sns.jointplot(x="面積(㎡)", y="取引価格(総額)", data=df)
plt.show()
外れ値であろう400㎡以上の取引価格を見てみる。
店舗が多いかと予測していたが意外に住宅が多かった。対個人であるが対法人であるかは不明であるが、この規模の住宅取引が多数存在していた事は意外であった。
築年数についても見てみる。
元データで全ての内容を見てみると、大阪市内の駅近物件が目立った。未改装物件が多数占めていた事が意外であった。(改装の内容をどう解釈するかにもよるが。)
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()
おおよそ予想通り。
築年数毎に取引価格はどういった分布となっているのか見てみる。
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()
概ね取引件数の分布と似た形となったが、50あたりを取引件数と比較すると小さい山となっている。件数の割には取引単価が低くなっていた事が原因と考えられる。
今回は、機会学習の一連の流れをまとめる事に注力したいのでEDAはここまでとする。
整形したデータを別データとして保存。
df.to_csv('mid.csv',encoding ='UTF-8', index=False)
続いて、モデル構築、分析、評価を行う。
(続く)