Yamamotoの日記

Yale SOM MBA、金融工学、技術関係の記事を書きたいです

機械学習を使ってお買い得中古物件を探す~前処理編~

前回はこちら maxonblog.hatenablog.com

前回同様、本日はこちらのサイトにアイデアを頂いて進めていきます。 www.analyze-world.com

データ読み込み&確認

まずはなにはともあれデータの読み込み、確認です。pandas便利ですね。

方法

Pandas:Seriesのattributionである、strを使っていきます。正規表現も使えます。列全体に対して処理を行うことができるので便利です。

  • split(x, expand) xを検索、ヒットしたらxで左右に分ける。expand=Trueとすると、複数列を持つDataFrameとしてくれます。expand=Falseとすると単列の中にすべて入れこまれます。逆に、どういう用途でどうやって使うんだろう。。。
  • replace(x,y) xを検索、yに入れ替えます。xに正規表現を使っています。正規表現()で囲んで使いましょう。
  • contains(x) xを含む行をindex(かな?)のSeriesで返します。DataFrameの列指定に入れ込むことで、xを含む行だけにすることができます。
  • extract(x) 各要素からxを検索して、matchしたものを各要素に返します。Seriesで返ってきます。
  • pandas.to_datetime() 文字列で記述された日付をdatetime型に変換します。
import pandas as pd
import datetime as dt
from pandas import DataFrame 
df = pd.read_csv("./Data/suumoData.csv", encoding="utf-8")
df.head()
name price location station size floor terrace construction
0 シャトレーイン東京・笹塚 770万円 東京都渋谷区笹塚1 京王線「笹塚」徒歩5分 14.52m2(4.39坪)(壁芯) ワンルーム - 1987年3月
1 高幡台住宅 26号棟 780万円 東京都日野市三沢 京王線高幡不動」徒歩11分 48.85m2(14.77坪)(壁芯) 2LDK 6m2 1970年7月
2 ライオンズマンション西日暮里北 880万円 東京都荒川区東尾久2-44-16 日暮里・舎人ライナー「赤土小学校前」徒歩3分 19.6m2(壁芯) 1K 3.36m2 1989年10月
3 ライオンズガーデン町田の丘 900万円 東京都町田市図師町 JR横浜線「町田」バス20分停歩3分 51.87m2(壁芯) 3LDK 7.41m2 1993年9月
4 武蔵小金井フラワーホーム 980万円 東京都小金井市緑町5 JR中央線「武蔵小金井」徒歩12分 26.22m2(壁芯) ワンルーム 7m2 1977年7月

データ整形

路線・駅名・駅までの距離

路線、駅名、徒歩時間を切り分けます。
"歩"、"「"といったところで切れるので、splitで分けた後replaceで不要な文字("」"以降、"分")を削除していきます。他の方法があったかな。最後に大元dfにくっつけることを忘れないようにしましょう。
尚、今更ながら、Jupyterだと、大元のデータを処理してしまうと微調整して再処理するとエラーを吐くので、input用変数とoutput用変数は分けるべきだったようです。
いらないデータはdropで削除し、最後にconcatでもとのデータ(df)にくっつけます。

df_split_1 = df["station"].str.split("歩", expand=True)
df_split_1 = pd.concat([df_split_1[0].str.split("「", expand=True), df_split_1[1]], axis=1)
df_split_1.columns = ["line", "station", "time"]
df_split_1["station"] = df_split_1["station"].str.replace("」(.)", "")  
df_split_1["time"] = df_split_1["time"].str.replace("分", "")
df.drop(['station'], axis=1, inplace=True)  
df = pd.concat((df, df_split_1),axis=1)
df.head()
name price location size floor terrace construction line station time
0 シャトレーイン東京・笹塚 770万円 東京都渋谷区笹塚1 14.52m2(4.39坪)(壁芯) ワンルーム - 1987年3月 京王線 笹塚 5
1 高幡台住宅 26号棟 780万円 東京都日野市三沢 48.85m2(14.77坪)(壁芯) 2LDK 6m2 1970年7月 京王線 高幡不動 11
2 ライオンズマンション西日暮里北 880万円 東京都荒川区東尾久2-44-16 19.6m2(壁芯) 1K 3.36m2 1989年10月 日暮里・舎人ライナー 赤土小学校前 3
3 ライオンズガーデン町田の丘 900万円 東京都町田市図師町 51.87m2(壁芯) 3LDK 7.41m2 1993年9月 JR横浜線 町田ス20分停 3
4 武蔵小金井フラワーホーム 980万円 東京都小金井市緑町5 26.22m2(壁芯) ワンルーム 7m2 1977年7月 JR中央線 武蔵小金井 12

住所を修正。東京23区内のみの分析にする。

本当は23区外も分析の対象外にしたかったのですが、規則性が薄そうで手間がかかりそうだったのでまずは東京23区を対象とすべく、"区"を含む行だけにして処理を進めます。エリアまでほしかったので、区以降の住所の中の地域名をaddressとして残します。
r"([^\d]*)"は、正規表現で数字「以外」を抜き出しています。

df = df[df["location"].str.contains("区")]
temp_1 = df["location"].str.replace("東京都", "").str.split("区", expand=True)
temp_1.columns = ["ku", "address"]
temp_1 = temp_1["address"].str.extract(r"([^\d]*)")
df = pd.concat((df, temp_1), axis=1)
df.head()
name price location size floor terrace construction line station time address
0 シャトレーイン東京・笹塚 770万円 東京都渋谷区笹塚1 14.52m2(4.39坪)(壁芯) ワンルーム - 1987年3月 京王線 笹塚 5 笹塚
2 ライオンズマンション西日暮里北 880万円 東京都荒川区東尾久2-44-16 19.6m2(壁芯) 1K 3.36m2 1989年10月 日暮里・舎人ライナー 赤土小学校前 3 東尾久
8 プレール新中野 1280万円 東京都中野区中央4 17.45m2(5.27坪)(壁芯) 1K 2.5m2 1990年3月 東京メトロ丸ノ内線 新中野 3 中央
9 ニュー荻窪フラワーホーム 1280万円 東京都杉並区桃井3 31.44m2(9.51坪)(壁芯) 1DK 5.51m2 1972年2月 JR中央線 荻窪 18 桃井
10 ライオンズマンション西日暮里北 1380万円 東京都荒川区東尾久2 44.62m2(壁芯) 2DK 11.01m2 1989年10月 日暮里・舎人ライナー 赤土小学校前 3 東尾久

広さのm2以降を削除する

数字として扱いたいので、いらない文字は削除しましょう。

df["size"] = df["size"].replace("m2(.*)", "", regex=True)
df["terrace"] = df["terrace"].replace("m2(.*)", "", regex=True)

価格を使えるデータに整形する

注釈とか幅をもたせたデータ(~、・、※を含むデータ)は削除し、x億円以上の部分は10,000かけて単位をずらして万円とつなげる、その後、intに変換します。

df = df[~df["price"].str.contains("~|〜|・|〜|~|※")]
df_split_1 = pd.DataFrame(df["price"].str.extract('(.*億)').str.replace("億", "").fillna(0).astype(int) * 10000)
df_split_2 = pd.DataFrame(df["price"].str.replace('(.*億)', "").str.replace("万|円", "").str.extract(r"(\d+)").fillna(0).astype(int))
df["price"] = df_split_1 + df_split_2
df.head()
name price location size floor terrace construction line station time address
0 シャトレーイン東京・笹塚 770 東京都渋谷区笹塚1 14.52 ワンルーム - 1987年3月 京王線 笹塚 5 笹塚
2 ライオンズマンション西日暮里北 880 東京都荒川区東尾久2-44-16 19.6 1K 3.36 1989年10月 日暮里・舎人ライナー 赤土小学校前 3 東尾久
8 プレール新中野 1280 東京都中野区中央4 17.45 1K 2.5 1990年3月 東京メトロ丸ノ内線 新中野 3 中央
9 ニュー荻窪フラワーホーム 1280 東京都杉並区桃井3 31.44 1DK 5.51 1972年2月 JR中央線 荻窪 18 桃井
10 ライオンズマンション西日暮里北 1380 東京都荒川区東尾久2 44.62 2DK 11.01 1989年10月 日暮里・舎人ライナー 赤土小学校前 3 東尾久

建築日を使えるデータにする

まずは建築日(完工日?)を日付データに変換します。年月までの情報しかないので、各月の1日に完工したと設定し、日付データとします。
次に、完工日からデータ取得日までの日数のデータとするため、引き算。日付のまま保持するより、int型に変換したほうがよかったかな、と思いはじめました。

df["construction"] = pd.to_datetime(df["construction"] + "1日", format="%Y年%m月%d日")
temp_1 = pd.to_datetime(dt.date.today()) - df["construction"]
temp_1 = temp_1.rename("age")
df = pd.concat((df, temp_1), axis=1)
df.head()
name price location size floor terrace construction line station time address age
0 シャトレーイン東京・笹塚 770 東京都渋谷区笹塚1 14.52 ワンルーム - 1987-03-01 京王線 笹塚 5 笹塚 12401 days
2 ライオンズマンション西日暮里北 880 東京都荒川区東尾久2-44-16 19.6 1K 3.36 1989-10-01 日暮里・舎人ライナー 赤土小学校前 3 東尾久 11456 days
8 プレール新中野 1280 東京都中野区中央4 17.45 1K 2.5 1990-03-01 東京メトロ丸ノ内線 新中野 3 中央 11305 days
9 ニュー荻窪フラワーホーム 1280 東京都杉並区桃井3 31.44 1DK 5.51 1972-02-01 JR中央線 荻窪 18 桃井 17908 days
10 ライオンズマンション西日暮里北 1380 東京都荒川区東尾久2 44.62 2DK 11.01 1989-10-01 日暮里・舎人ライナー 赤土小学校前 3 東尾久 11456 days

Floor(部屋数)の修正

Floor(部屋数)を使えるデータに変換します。ここは割と工夫をした点です。2LDKとか3LDKとかだけでなく、11LDKとか、S(納戸)とか変なデータが多かったので、まずはどういうフレームワークで分析するか考えました。
結論として、Room、Living、Dining、Kitchen、Service、及び部屋数のデータを残すことに。手順は以下の通りです。

  1. 部屋数の数字をextract:2LDKの"2"の部分ですね。
  2. L、D、Kの数を数える。2つLivingがある部屋は、2LLDKという表記になっています。
  3. Sについては、"S"の前だけ抜き出して、その文字列に含む数字をSの数とします。+2S(納戸)とかいう不思議な表記になっているためです。
  4. 最後に、全部の部屋数を足した部屋数列を作成。

ついでに、今日の日付の情報も入れておきましょう。
これらを元のデータにくっつけておしまいです。

df = df.reset_index(drop=True)
temp_1 = pd.Series(df["floor"]).str.replace("(.*ワンルーム)", "1")
rldks: DataFrame = pd.DataFrame([
    temp_1.str.extract(r"(\d\d*)"),
    temp_1.str.count("L"),
    temp_1.str.count("D"),
    temp_1.str.count("K"),
    temp_1.str.extract(r"(\+.*S)").fillna("0").str.extract(r"(\d+)").fillna(1)],
    index=["room", "Living", "Dining", "Kitchen", "Service"]
    ).astype(int).T
temp2 = pd.DataFrame({"No.ofRooms": rldks.sum(axis=1)})
today = pd.Series([dt.date.today() for i in range(len(df))])
df = pd.concat((df, rldks, temp2,today), axis=1)
df.head()
name price location size floor terrace construction line station time ... Service No.ofRooms 0 room Living Dining Kitchen Service No.ofRooms 0
0 シャトレーイン東京・笹塚 770 東京都渋谷区笹塚1 14.52 ワンルーム - 1987-03-01 京王線 笹塚 5 ... 0 1 2021-02-11 1 0 0 0 0 1 2021-02-11
1 ライオンズマンション西日暮里北 880 東京都荒川区東尾久2-44-16 19.6 1K 3.36 1989-10-01 日暮里・舎人ライナー 赤土小学校前 3 ... 0 2 2021-02-11 1 0 0 1 0 2 2021-02-11
2 プレール新中野 1280 東京都中野区中央4 17.45 1K 2.5 1990-03-01 東京メトロ丸ノ内線 新中野 3 ... 0 2 2021-02-11 1 0 0 1 0 2 2021-02-11
3 ニュー荻窪フラワーホーム 1280 東京都杉並区桃井3 31.44 1DK 5.51 1972-02-01 JR中央線 荻窪 18 ... 0 3 2021-02-11 1 0 1 1 0 3 2021-02-11
4 ライオンズマンション西日暮里北 1380 東京都荒川区東尾久2 44.62 2DK 11.01 1989-10-01 日暮里・舎人ライナー 赤土小学校前 3 ... 0 4 2021-02-11 2 0 1 1 0 4 2021-02-11

5 rows × 26 columns

終わり

さて、次は楽しい分析です!Pythonだとデータ分析は難しいのでRでやるかなー。

df.head(5)
df.tail(5)
name price location size floor terrace construction line station time address age room Living Dining Kitchen Service No.ofRooms 0
17970 フォレセーヌ赤坂檜坂 24000 東京都港区赤坂6 88.13 2LDK 8.49 2016-01-01 東京メトロ日比谷線 六本木 6 赤坂 1868 days 2 1 1 1 0 5 2021-02-11
17971 パークコート青山ザ・タワー 25800 東京都港区南青山2 82.79 2LDK - 2017-12-01 東京メトロ銀座線 青山一丁目 3 南青山 1168 days 2 1 1 1 0 5 2021-02-11
17972 パークハウス多摩川 29000 東京都大田区下丸子4 307.68 1LDK 56.4 1993-02-01 東急多摩川線 鵜の木 7 下丸子 10237 days 1 1 1 1 0 4 2021-02-11
17973 ブランズ六本木ザ・レジデンス 30000 東京都港区六本木4 101.97 2LDK 5.22 2019-01-01 都営大江戸線 六本木 3 六本木 772 days 2 1 1 1 0 5 2021-02-11
17974 パークコート乃木坂ザ・タワー 33000 東京都港区南青山1 100.65 2LDK 11.42 2019-02-01 東京メトロ千代田線 乃木坂 2 南青山 741 days 2 1 1 1 0 5 2021-02-11