akatak blog

プログラム初心者の50代ビジネスマンがセカンドキャリアを目指して働きながらPythonを中心に独学していましたが、昨年IT系企業に転職。新規事業開発の仕事をすることになりました。自らの覚え書きや成果物、感じたことなどを脈絡もなく書き連ねるブログです。

Pythonによる不動産情報のデータ取得&分析(5)【売却物件/データ前処理編】

akatak.hatenadiary.jp

さて、前回記載したスクリプトでSUUMOさんのサイトからスクレイピングしましたデータを、分析しやすいようにきれいにしていきましょう。このデータの前処理は、データの状況にもよりますので、なかなか完全な自動化は難しいですよね。自動化できる良い方法があれば、どなたか是非教えて下さい!

さて、例によって、Jupyter Notebookにてデータ前処理を行っていきましょう。データの状況を都度確認しながら、データ前処理を実行していくには、Jupyter Notebookは非常に有効だと思います。まだ、お使いでない方は是非試していただきたいと思います。

まず、データを取り込みましょう。pandas numpy reをインポートします。re正規表現を利用して文字列を操作するために必要なモジュールです。

前回保存しておいたcsvファイルをpandasのdataframeとしてdfに読み込みます。index_col=0として、ファイルの0列目(pythonでは0から始まる)をindex列として指定します。引数でこれを指定しないと、pandasが自動的に0から数値をindexとして割り付けてしまいます。

import pandas as pd
import numpy as np
import re

df = pd.read_csv('data/suumo_used_mansion.csv', index_col = 0)

info()にてdataframeの概要を見てみます。また、head()にて最初の5行もどんな感じか見ておきましょう。

f:id:akatak:20180915061142p:plain f:id:akatak:20180915061350p:plain

約23000行のデータ系列ですね。ところが、データ系列をExcel等に取り込んで眺めてみると分かるのですが、同じ物件のデータが場合によっては3個も4個もあるいはもっとあります。異なる業者が同じ物件をSUUMOさんのホームページに登録しているようです。

そこで重複行を削除してしまいましょう。pandasには「重複行を抽出」したり、「重複行を削除」するメソッドが準備されています。前者がduplicated()、後者がdrop_duplicates()です。

まず、duplicated()を利用して、どの位重複があるのかを確認しましょう。引数subsetで重複を確認する列を指定できます。また、keep=Falseと指定すると、重複行全てTrueを返します(keep='first'の指定で、重複行のうち最初に出てくる行がFalseとなり、それ以外がTrue。keep='last'とすると最後の行がFalseとなり、それ以外の行がTrueとなります)。sum()で合計すると重複行をカウントできます。

f:id:akatak:20180915075911p:plain

9216も重複していますね。 物件名が「オリエンタル大森」のもののみ抽出して確認すると、以下の通り。

f:id:akatak:20180915080122p:plain

重複行の最初の行のみ残して、後は削除してしまいましょう。

df.drop_duplicates(subset=['物件名', '価格', '専有面積', '間取り'], keep='first', inplace=True)

ただし、Excel等に取り込んだデータをよく見てみると物件名が「ザ・パークハウス小石川春日」と「◆ザ・パークハウス小石川春日◆2駅4路線利用可!」と異なっていますが、その他の列を見ると専有面積もバルコニー面積も価格も住所も一緒のものがあります。

完全ではないにしろ、ある程度きれいにしておきたいところですので、これらも除外しておきましょう。

f:id:akatak:20180915081308p:plain

さて、相当数を削除した結果、indexが歯抜け状態になっていますので、indexを付け直しましょう。

df.reset_index(drop=True, inplace=True)

ここで、データ系列の概要を見てみると、以下の通り、データ数は14631個になりました。

f:id:akatak:20180915081840p:plain

さて、ここから、賃貸物件と同様に、データを加工していきます。

# 文字列を複数文字で split。住所から「都県」列・「市区」列を新たに作成
df['都県'] = df['住所'].apply(lambda x: re.split('[都県]', x)[0]) 
df['市区'] = df['住所'].apply(lambda x: re.split('[都県市区]', x)[1]) 

次に、築年数を計算するために以下の加工・計算を行います。

from datetime import datetime

df['築年'] = df['築年月'].apply(lambda x: x.split('年')[0])
df['築月'] = df['築年月'].apply(lambda x: x.split('年')[1].strip('月'))
df['築月'] = df['築月'].apply(lambda x: x if x != '' else '6')

df['築年月'] = df['築年'] +'/'+ df['築月'] +'/15'  # 一律に日付を15日に設定
df['築年月'] = pd.to_datetime(df['築年月'])  #  stringからdatetimeオブジェクトに変換
df['築日数'] = df['築年月'].apply(lambda x:(datetime.today() - x).days) #今日までの日数計算
df['築年数'] = df['築日数']/365.25 # 年数(概算)を計算(うるう年も考慮)

計算の過程で作った「築年」「築月」「実日数」の列は分析でも不要なので、削除してしまいます。

df.drop(['築年','築月','築日数'], axis=1, inplace=True)

続きまして、「1億円」「1080万円」等のStringとなっているものを、数値に変換していきます。まずは、「4998万円※権利金含む」などの記載があるものの処理から。

df['価格'] = df['価格'].apply(lambda x: x.split('※')[0] if '※権利金' in x else x)

次に「億円」表示のものを「万円」に変換。また、「1億800万円」のような表示を「万円」に変換します。

df['価格'] = df['価格'].apply(lambda x: x.strip('億円') + '0000万円' if '億円' in x else x)
df['価格'] = df['価格'].apply(lambda x: str(int(x.split('億')[0]) *10000 + int(x.split('億')[1].split('万円')[0])) + '万円' if '億' in x else x)

念のため、下2文字が「万円」でないものがあるか確認すると、 f:id:akatak:20180915083628p:plain

一つだけだけありました。仕方が無いので、個別で調整します。

df.iloc[2761,2] = '2335.6万円'

後は、範囲で指定しているものもありますが、これも個別で上下の平均値でも入れておきましょう。 f:id:akatak:20180915083937p:plain f:id:akatak:20180915083947p:plain

価格の仕上げとして、数値化しておきます。

df['価格'] = df['価格'].apply(lambda x: int(float(x.strip('万円')) * 10000))

間取り、部屋数、路線、駅、手段、分は賃貸物件の場合と同様に処理します。

df['間取り'] = df['間取り'].str.replace('ワンルーム', '1RM')
df['部屋数'] = df['間取り'].apply(lambda x: int(re.search('[0-9]+', x).group(0)))
df['路線'] = df['最寄駅'].apply(lambda x: x.split('「')[0])
df['駅'] = df['最寄駅'].apply(lambda x: x.split('「')[1].split('」')[0])
df['手段'] = df['最寄駅'].apply(lambda x: x.split('「')[1].split('」')[1][0])
df['手段'] = df['手段'].apply(lambda x: x.replace('徒', '歩')) # 手段の「徒」を「歩」に変えます。
df['分'] = df['最寄駅'].apply(lambda x: re.search('[0-9]+',x.split('「')[1].split('」')[1]).group(0))

あと、今回は物件階の影響も調べたいので、加工していきましょう。 データを一覧すると f:id:akatak:20180915084551p:plain

うわー、仕方ないですね。一つ一つ変換していきましょう。

df['物件階'] = df['物件階'].str.replace('B2階','-2階')
df['物件階'] = df['物件階'].str.replace('B1階','-1階')
df['物件階'] = df['物件階'].str.replace('2-3階','2.5階')
df['物件階'] = df['物件階'].str.replace('5-6階','5.5階')
df['物件階'] = df['物件階'].str.replace('3-4階','3.5階')
df['物件階'] = df['物件階'].str.replace('B1-1階','-0.5階')
df['物件階'] = df['物件階'].str.replace('1-2階','1.5階')
df['物件階'] = df['物件階'].str.replace('4-5階','4.5階')
df['物件階'] = df['物件階'].str.replace('6-7階','6.5階')
df['物件階'] = df['物件階'].str.replace('1-3階','2階')
df['物件階'] = df['物件階'].str.replace('B1-2階','1階')
df['物件階'] = df['物件階'].str.replace('8-9階','8.5階')
df['物件階'] = df['物件階'].str.replace('1--1階','0.5階')
df['物件階'] = df['物件階'].str.replace('B1-3階','1階')
df['物件階'] = df['物件階'].str.replace('1-1階','1階')
df['物件階'] = df['物件階'].str.replace('46-47階','46.5階')
df['物件階'] = df['物件階'].str.replace('37-38階','37.5階')
df['物件階'] = df['物件階'].str.replace('B1.5階','-1.5階')
df['物件階'] = df['物件階'].str.replace('B2階','-2階')
df['物件階'] = df['物件階'].apply(lambda x: float(x.strip('階')) if x != '-' else x.replace('-', ''))

総戸数も数値に変換します。

df['総戸数'] = df['総戸数'].apply(lambda x: int(x.strip('戸')) if x !='-' else x.replace('-', ''))

リフォームは有無のみ「リフォームflag」に入れます。有り=True・無し=Falseです。

df['リフォームflag'] = df['リフォーム'] != "['-\\r\\n\\t\\t\\t']"

今回は、単価を分析の対象としたいので、追加します。

df['単価'] = df['価格'] / df['専有面積'] #単価(単位):円/㎡

権利形態としてとして各種「借地権」の物件もありますが、分析の対象は「所有権」のものに絞りたいと思います。最後に加工後のデータを保存します。

df_owner = df[df['権利形態'] == '所有権']
df_owner.to_csv('data/suumo_used_mansion_mod_owner.csv')

以上、駆け足でしたがいかがでしたか。結構骨が折れる作業かと思います。慣れてくると、あーでもないこーでもないと考えることを楽しみながら、加工できますので、作業自体はそんなに苦痛ではないですよ。

分析は次回にしましょう。それでは。