GIS奮闘記

元GISエンジニアの技術紹介ブログ。主にPythonを使用。

スポンサーリンク

Python で四分位数を計算する方法

さて、本日は Python で四分位数を計算する方法を紹介します。

四分位数とは

統計では、四分位数は、データポイントの数をほぼ等しいサイズの4つの部分、つまり4分の1に分割する分位数の一種です。(出典:Wikipedia)

つまり、データを四等分する三つのデータのことですね。それぞれ第1四分位数Q1(下から25%番目のデータ)、第2四分位数Q2(上から50%番目のデータ。中央値)、第3四分位数Q3(上から25%番目のデータ)と言います。

使用するライブラリ

pandas を使用します。

使用するデータ

以下で収集したSUUMOの中古物件の販売価格を使用します。

www.gis-py.com

使用するレコード

以下の10件を使用します。

import pandas as pd
df = pd.read_csv(r"D:\data\csv\property.csv")
df = df.head(10)
df

f:id:sanvarie:20210519111300p:plain

環境

Windows10 64bit
Python3.8.5

サンプルコード

四分位数を計算するサンプルです。pandas の quantile メソッドを使用します。

quantile1,quantile2,quantile3 = df["販売価格"].quantile([0.25, 0.5, 0.75])
print("第1四分位数" + str(quantile1))
print("第2四分位数" + str(quantile2))
print("第3四分位数" + str(quantile3))

このような結果になりました。

f:id:sanvarie:20210519113107p:plain

参考

外れ値を計算する場合は以下のようにすれば大丈夫です。

# 四分位範囲
iqr = quantile3 - quantile1

lower_bound = quantile1 - (iqr * 1.5)
upper_bound = quantile3 + (iqr * 1.5)

print("小さいほうの外れ値" + str(lower_bound))
print("大きい方の外れ値" + str(upper_bound))

小さいほうの外れ値より小さい値、もしくは、大きい方の外れ値より大きい値があれば外れ値とします。

f:id:sanvarie:20210519113201p:plain

さいごに

いかがでしょうか。pandas を使用すれば簡単に四分位数の計算ができます。興味のある方はぜひ使ってみてください。本日は以上です。

SUUMO の中古物件情報を Tableau で分析してみる ~データ分析編~

さて、本日は 「SUUMO の中古物件情報を Tableau で分析してみる ~データ分析編~」です。前回のエントリーで SUUMO の情報をスクレイピングしましたが、今回はTableau を使ってそのデータを分析してみようと思います。

なお、本シリーズは以下3エントリーにわたって SUUMO の中古物件情報を扱います。本エントリーはデータ分析編です。

使用するデータ

  • 前回のエントリーで作成した property.csv
  • 全国市区町村界データ
    • ESRIジャパンさんが公開しているデータです。最近は本ブログのメインテーマである GIS 要素が少なかったので今回は GIS 的に分析しようと思います。

環境

Windows10 64bit
Tableau Desktop Public Edition 2021.1.0

分析

区ごとの販売価格傾向(平均)

上段が中古マンション、下段が中古一戸建てです。また、価格が高いほど色がオレンジ色に近くなっています。

f:id:sanvarie:20210515170227p:plain

傾向
  • 中古マンション
    • 以外にも区ごとに大きな差はありませんでした。ただ、やはり西区や中区などの繁華街が他の区と比べると価格が高い傾向にありますね。
  • 中古一戸建て
    • 中古マンションに比べると差が出ましたね。中区に加えて東京寄りの区が他の区と比べると価格が高い傾向にありますね。西区の価格が低いことが少し意外でした。

ちなみに中央値で見てみると以下のようになります。 中古マンションの平均販売価格で分析した傾向(西区や中区では価格が高い)が見れなくなりました。一部の高額なマンションが平均を押し上げていた可能性が高いですね。

f:id:sanvarie:20210515170746p:plain

間取りを4LDK以上(私が望む間取り)に絞る

中古マンション

一気に平均価格が上がりましたね。 f:id:sanvarie:20210515172623p:plain

中古一戸建て

むむむ、一戸建てだと平均5000万円以上の区が多くなりましたね。中古なのになんという値段でしょう・・・

f:id:sanvarie:20210515172958p:plain

中央値で見たら少し現実的な値段になった気がします。ここからの分析はすべて中央値で見てみようと思います。それでも西区などの繁華街、青葉区や都筑区などのハイソなエリアに家を買うのは現実的ではないことがわかりました。

f:id:sanvarie:20210515173233p:plain

世帯人数を確認

世帯人数が多いほど色がオレンジ色に近くなっています。オレンジ色の区はファミリー層が多く住んでいると考えられますね。私が狙うべきは以下の条件でしょうか。

  • ファミリー層が多い区
  • 価格が安めの物件が多い区
中古マンション

泉区や瀬谷区など郊外の区にファミリー層が多く住んでいることがわかります。

f:id:sanvarie:20210515175803p:plain

中古一戸建て

こちらも傾向は中古マンションと同じですね。

f:id:sanvarie:20210515180310p:plain

交通の便なども考えると「港南区」「戸塚区」あたりが落としどころな気がしますね。

バスを使用しない物件に絞る

今までの分析結果に加えてバスを使わない物件に絞ってみます。やはりバスは使用したくないですしね。

中古マンション

以外にも価格はあまり上がりませんでした。

f:id:sanvarie:20210515185021p:plain

中古一戸建て

こちらも同じで価格はあまり上がりませんでした。

f:id:sanvarie:20210515185200p:plain

最寄駅徒歩10分以内に絞る

今までの分析結果に加えて最寄駅徒歩10分以内の物件に絞ってみます。日常生活や売却のことを考えたらやはり駅近がいいですしね。

中古マンション

価格がぐっと上がりました。しかし、西区のデータが見えなくなってしまいました。データ不足ですね。

f:id:sanvarie:20210515185720p:plain

中古一戸建て

こちらも価格がぐっと上がりました。こちらもデータ不足で栄区のデータが見えなくなってしまいました。

f:id:sanvarie:20210515185852p:plain

価格的に駅近物件を手にするのは少しハードルが高そうですね。

路線ごとの販売価格傾向(中央値)

これ以降は地図上ではなくグラフでデータ分析を行います。

中古マンション

人気の東横線エリアの価格が高いことがわかります。

f:id:sanvarie:20210515183617p:plain

中古一戸建て

みなとみらい線が圧倒的すぎます。ちょっとこの辺に家を買うことは現実的とは思えないですね。

f:id:sanvarie:20210515183642p:plain

駅ごとの販売価格傾向(中央値)

中古マンション

石川町の価格が圧倒的に高いですね。さすが横浜随一の高級住宅街といったところでしょうか。

f:id:sanvarie:20210515183858p:plain

中古一戸建て

みなとみらい線の価格を押し上げていたのは元町・中華街のようですね。そして、東白楽や生麦の価格が高いのが不思議です。外れ値が入ってしまっているのしれませんね。

f:id:sanvarie:20210515184028p:plain

さいごに

淡々と分析結果を貼っただけですが、いかがでしたでしょうか?データさえあれば Tableau を使って今回紹介したような分析が簡単にできます。また、以下のようなデータを追加するとより面白い結果を得ることができる気がします。

  • 各区の病院数、学校数、交番数
  • 各区の企業数
  • 各区の平均年収
  • 地盤データ
  • etc

次回はいよいよ本シリーズの最後であるデータ予測編です。どんな結果になるかわかりませんが、楽しみにしててください。本日は以上です。

SUUMO の中古物件情報を Tableau で分析してみる ~データ収集編~

さて、本日は 「SUUMO の中古物件情報を Tableau で分析してみる ~データ収集編~」です。私が中古物件を探しているのですが、条件(最寄りの路線や駅近など)によって価格の変動やどういった傾向にあるのかを知りたかったため、Tableau を使って傾向を分析してみようと思います。

なお、本シリーズは以下3エントリーにわたって SUUMO の中古物件情報を扱います。本エントリーはデータ収集編です。

  • データ収集編
    • 分析に必要なデータを収集します。
  • データ分析編
    • 収集したデータの分析を行います。
  • データ予測編
    • 機械学習を使用して物件価格の推論を行います。

データ取得方法

Beautiful Soup を使ったスクレイピングでデータを取得します。関連エントリーを以下に記載しますので、興味がある方はぜひ読んでみてください。

www.gis-py.com

スクレイピング対象データ

f:id:sanvarie:20210508130543p:plain

f:id:sanvarie:20210508130624p:plain

対象エリア

横浜市

理由

横浜市で中古物件の購入を検討しているため

アウトプット

以下のような形でデータを取得し、CSV に出力します。

フィールド名 説明
カテゴリ 中古マンション or 中古一戸建て
販売価格 販売価格
所在地 物件所在地
沿線 物件最寄沿線
最寄駅 物件最寄駅
徒歩(分) 物件から最寄駅までの徒歩時間。バスの場合は物件からバス停までの時間
バス(分) バス乗車時間。バスを使用しない場合は0
土地面積 土地面積。中古マンションは建物面積=土地面積とする
建物面積 中古一戸建てもしくは中古マンションの建物面積
バルコニー バルコニー面積。中古マンションのみ使用
間取り 物件の間取り
築年数 築年月から計算

欲しかったデータ

以下のようなデータも取得できればより詳細な分析ができそうですが、簡単に取得はできなさそうなので今回は諦めます・・・

  • 向き(日当たり)
  • 駐車場有無
  • 庭有無
  • 周辺環境

データ加工内容

以下にデータ加工内容を記載します。ただスクレイピングするだけなら楽なのですが、分析用となるとデータをかなり加工しなければならないのでそれなりに手間がかかりますね。

  • 69.98m2(登記)
  • 2LDK+S(納戸)
  • 103.51m2(実測)
  • 50.11m2(壁芯)
    • ()とその内容を除去する。また、m2を除去する
  • JR
    • JR に変換する
  • ◯◯万円
    • 数値に変換する
  • バルコニーの「-」の表記
    • 0に変換する
  • ◯◯万円~◯◯万円
    • 大きい方の値を使用する(間取りや専有面積も同様)
  • ◯◯万円※権利金含む◯◯万円
    • ※ 以降を除去する
  • LDK+S
    • +S を除去する
  • LK
    • LDKに変換する
  • LLDDKK
    • LDKに変換する(LDDKのように似たようなものも同様)
  • ワンルーム
    • 1Kに変換する
  • 湘南新宿ライン
    • 湘南新宿ライン高海と湘南新宿ライン宇須を湘南新宿ラインに変換する

加工しきれなかったデータ

存在しない沿線(神奈川中央交通など)や駅名(横浜パークタウンなど)の記載があり、そのあたりのデータはとりあえず今回収集して分析時に除外しようと思います。(SUUMO さんには正確なデータを入力してほしいものです)

環境

Windows10 64bit
Python3.8.5

サンプルコード

思ったよりもデータの加工が必要でコードが長くなってしまいました。もう少し改善の余地はあると思いますが、とりあえずサンプルとしては以下です。

# -*- coding: utf-8 -*-
import urllib.request
from bs4 import BeautifulSoup
import pandas as pd

# 中古一戸建てと中古マンションのurl
url_list = ["https://suumo.jp/jj/bukken/ichiran/JJ010FJ001/?ar=030&bs=021&ta=14"  \
            "&jspIdFlg=patternShikugun&sc=14101&sc=14102&sc=14103&sc=14104&sc=14105&sc=14106"  \
            "&sc=14107&sc=14108&sc=14109&sc=14110&sc=14111&sc=14112&sc=14113&sc=14114"  \
            "&sc=14115&sc=14116&sc=14117&sc=14118&kb=1&kt=9999999&tb=0&tt=9999999&hb=0"  \
            "&ht=9999999&ekTjCd=&ekTjNm=&tj=0&cnb=0&cn=9999999&srch_navi=1&pn={}",

            "https://suumo.jp/jj/bukken/ichiran/JJ010FJ001/?ar=030&bs=011&ta=14" \
            "&jspIdFlg=patternShikugun&sc=14101&sc=14102&sc=14103&sc=14104&sc=14105&sc=14106" \
            "&sc=14107&sc=14108&sc=14109&sc=14110&sc=14111&sc=14112&sc=14113&sc=14114" \
            "&sc=14115&sc=14116&sc=14117&sc=14118&kb=1&kt=9999999&mb=0&mt=9999999" \
            "&ekTjCd=&ekTjNm=&tj=0&cnb=0&cn=9999999&srch_navi=1&pn={}"]

# 収集するデータのカラム
cols = ["カテゴリ", "販売価格", "所在地", "区", "沿線", "最寄駅", "徒歩(分)", "バス(分)",
        "土地面積", "建物面積", "バルコニー", "間取り", "築年数"]

def identify_floor_plan(floor_plan):
    # 間取りの種類が多すぎるのでまとめる
    if floor_plan.find("ワンルーム") > -1:
        floor_plan = "1K"
    if floor_plan.find("+") > -1:
        floor_plan = floor_plan[:floor_plan.find("+")]
    if floor_plan.find("LK") > -1:
        floor_plan = floor_plan[0:1] + "LDK"
    if floor_plan.find("LL") > -1 or \
       floor_plan.find("DD") > -1 or \
       floor_plan.find("KK") > -1:
        floor_plan = floor_plan = floor_plan[0:1] + "LDK"

    return floor_plan

def convert_price(price):
    # 万円※権利金含むの処理
    if price.find("※") > -1:
        price = price[:price.find("※") ]

    # ○○万円~○○万円の処理
    if price.find("~") > -1:
        price = price[price.find("~") + 1: ]

        if price.find("億") > -1:
            # 1億円ジャストのような場合の処理
            if price.find("万") == -1:
                price = int(price[:price.find("億")]) * 100000000
            else:
                oku = int(price[:price.find("億")]) * 100000000
                price = oku + int(price[price.find("億") + 1:-2]) * 10000
        else:
            price = int(price[:price.find("万円")]) * 10000
    else:
        if price.find("億") > -1:
            # 1億円ジャストのような場合の処理
            if price.find("万") == -1:
                price = int(price[:price.find("億")]) * 100000000
            else:
                oku = int(price[:price.find("億")]) * 100000000
                price = oku + int(price[price.find("億") + 1:-2]) * 10000
        else:
            price = int(price[:-2]) * 10000

    return price

def remove_brackets(data):
    # ○○~○○の処理
    if data.find("~") > -1:
        data = data[data.find("~") + 1:]

    # m2以降を除去
    if data.find("m2") > -1:
        data = data[:data.find("m")]

    # ()を除去
    if data.find("(") > -1:
        data = data[:data.find("(")]

    return data

def get_line_station(line_station):

    # JRをJRに変換
    if line_station.find("JR") > -1:
        line_station = line_station.replace("JR", "JR")

    # バスと徒歩の時間を取得
    if line_station.find("バス") > -1:
        bus_time = line_station[line_station.find("バス") + 2 : line_station.find("分")]

        # バスの場合は徒歩時間=(バス時間×10) + バス停までの徒歩時間とする
        walk_time = line_station[line_station.find("停歩") + 2 : line_station.rfind("分")]

    else:
        bus_time = 0
        walk_time = line_station[line_station.find("徒歩") + 2 : line_station.rfind("分")]

    # 沿線と駅を取得
    line = line_station[ : line_station.find("「") ]

    if line.find("湘南新宿ライン高海") > -1 or \
       line.find("湘南新宿ライン宇須") > -1:
        line = "湘南新宿ライン"

    station = line_station[line_station.find("「") + 1 : line_station.find("」")]

    return line, station, bus_time, walk_time

def get_page_count(hit_count):

    # データ整形
    hit_count = hit_count.strip()
    hit_count = hit_count.replace(",", "")
    hit_count = hit_count.replace("件", "")

    # ページ数計算
    page_count = divmod(int(hit_count), 30)
    if page_count[1] == 0:
        page_count = page_count[0]
    else:
        page_count = page_count[0] + 1

    return page_count

def main():

    df = pd.DataFrame(index=[], columns=cols)

    for i, url_base in enumerate(url_list):
        # 対象urlのデータを取得
        url = url_base.format(1)
        html = urllib.request.urlopen(url).read()
        soup = BeautifulSoup(html)
        hit_count = soup.find("div", class_="pagination_set-hit").text

        # 各urlのページ数計算
        page_count = get_page_count(hit_count)

        data = {}
        for page in range(1, page_count + 1):

            # ページごとにリクエスト
            if page != 1:
                url = url_base.format(page)
                html = urllib.request.urlopen(url).read()
                soup = BeautifulSoup(html)

            for s in soup.findAll('div', 'dottable-line'):

                if i == 0:
                    data["カテゴリ"] = "中古一戸建て"

                else:
                    data["カテゴリ"] = "中古マンション"

                if len(s.findAll('dt')) == 1:
                    if s.find('dt').text == "販売価格":

                        # ○○万円を数字に変換
                        price = convert_price(s.find("span").text)
                        data["販売価格"] = price

                if len(s.findAll('dt')) == 2:

                    if s.findAll('dt')[0].text == "所在地":
                        area = s.findAll("dd")[0].text
                        data["所在地"] = area
                        data["区"] = area[area.find("市") + 1:area.find("区") + 1]

                    if s.findAll('dt')[1].text == "沿線・駅":

                        # データ加工
                        line, station, bus_time, \
                            walk_time = get_line_station(s.findAll("dd")[1].text)

                        data["沿線"] = line
                        data["最寄駅"] = station
                        data["徒歩(分)"] = walk_time
                        data["バス(分)"] = bus_time

                if s.find('table', class_ = 'dottable-fix') != None:
                    if s.findAll('dt')[0].text == "土地面積":

                        land_area = remove_brackets(s.findAll("dd")[0].text)
                        data["土地面積"] = land_area

                    if s.findAll('dt')[1].text == "間取り":

                        floor_plan = remove_brackets(s.findAll("dd")[1].text)

                        # 間取りをまとめる
                        floor_plan = identify_floor_plan(floor_plan)
                        data["間取り"] = floor_plan

                    if s.findAll('dt')[0].text == "建物面積":

                        house_area = remove_brackets(s.findAll("dd")[0].text)
                        data["建物面積"] = house_area

                    if s.findAll('dt')[0].text == "専有面積":

                        house_area = remove_brackets(s.findAll("dd")[0].text)
                        data["建物面積"] = house_area

                        # 中古マンションは建物面積=土地面積とする
                        data["土地面積"] = house_area

                    if s.findAll('dt')[0].text == "バルコニー":

                        if s.findAll("dd")[0].text.find("-") > -1:
                            data["バルコニー"] = 0
                        else:
                            balcony_area = remove_brackets(s.findAll("dd")[0].text)
                            data["バルコニー"] = balcony_area

                    else: # 一戸建ての場合は0
                        data["バルコニー"] = 0

                    if s.findAll('dt')[1].text == "築年月":

                        # 築年数を算出
                        built_year = 2021 - int(s.findAll("dd")[1].text[:4])
                        data["築年数"] = built_year

                # データフレームに1物件ずつデータを格納
                if len(data) == 13:
                    df = df.append(data, ignore_index=True)
                    data = {}

    # CSV 出力
    df.to_csv(r"D:\data\csv\property.csv", index=False, encoding = "utf-8")

if __name__ == '__main__':
    main()

出力結果を確認

全部で8329件でした。一部抜粋したものを以下に載せます。いい感じにデータを取得できたと思います。

f:id:sanvarie:20210513113825p:plain

f:id:sanvarie:20210513113855p:plain

さいごに

細かいデータの加工がなかなか上手くいかず以外とコードを書くのに手こずってしまいましたが、何とか SUUMO から物件情報を取得することができました。このデータを使って次回のエントリーでTableau を使ったデータ分析をしようと思います。本日は以上です。

VSCode Map Preview を使って テキストエディタ上で地図を表示してみよう!

さて、本日はテキストエディタ上で地図を表示してみようと思います。使用するテキストエディタは Visual Studio Code です。

Visual Studio Code とは

Visual Studio CodeはMicrosoftが開発しているWindows、Linux、macOS用のソースコードエディタである。デバッグ、埋め込みGitコントロールとGitHub、シンタックスハイライト、インテリジェントなコード補完 、スニペット、コードリファクタリングのサポートが含まれる。(出典:Wikipedia)

VSCode Map Preview とは

Visual Studio Code で使用できるプラグインで、様々な地図データを扱うことができます。

marketplace.visualstudio.com

対応フォーマット

  • CSV files (as of 0.5.0)
  • GPX
  • GeoJSON
  • IGC
  • KML
  • TopoJSON
  • WFS
  • GML
  • GML2
  • GML3
  • WKT

使い方

まずは VSCode Map Preview をインストールします。

f:id:sanvarie:20210512111345p:plain

今回はこちらのサイトでダウンロードした日本地図の GeoJSON を使用してみます。対象の地図データをドラッグアンドドロップし、「Map Preview」ボタンを押します。

f:id:sanvarie:20210512112100p:plain

このように地図が表示されます。

f:id:sanvarie:20210512112202p:plain

属性を表示させることもできますね。

f:id:sanvarie:20210512112246p:plain

また、背景地図を変えることもできます。

f:id:sanvarie:20210512112322p:plain

さいごに

一昔前は地図データを扱うのは少し敷居が高かった気がしますが、最近は本当に色々なソフトで地図データの可視化を行うことができるようになっている気がします。これからさらにリッチな機能が追加されていくでしょうし、少し地図データを使うだけなら高価な GIS ソフトを買う必要は無くなりましたね。

機械学習で行った定期預金申し込み推論結果を Tableau で可視化してみよう!

さて、本日は機械学習で行った定期預金申し込み推論結果を Tableau で可視化してみようと思います。機械学習を行うことも難しいのですが、その結果をわかりやすく表示することも多くの方が頭を抱える悩みではないかと思います。Tableau を使えばそんな悩みもすぐに解決するのではないかと思うので、ぜひ一緒に使ってみましょう!

今回使用する機械学習アルゴリズム

今回はランダムフォレストを使用します。

ランダムフォレストは2001年に Leo Breiman によって提案された[1]機械学習のアルゴリズムであり、分類、回帰、クラスタリングに用いられる。決定木を弱学習器とするアンサンブル学習アルゴリズムであり、この名称は、ランダムサンプリングされたトレーニングデータによって学習した多数の決定木を使用することによる。ランダムフォレストをさらに多層にしたアルゴリズムにディープ・フォレストがある。対象によっては、同じくアンサンブル学習を用いるブースティングよりも有効とされる。(出典:Wikipedia)

関連エントリー

興味がある方はぜひ読んでみてください。

www.gis-py.com

www.gis-py.com

今回使用するデータ

2008年から2011年の間のポルトガルの銀行顧客の行動履歴、経済指標と、その顧客が実際に定期預金を申し込んだかどうかの情報を使用します。以下サイトの「 bank-additional-full.csv」を使用します。

archive.ics.uci.edu

開くとこんな感じになっています。データがすごくきれいに整っているので、加工の必要は無さそうですね。

f:id:sanvarie:20210508193647p:plain

注意

このデータですが、「;」が区切り文字になっています。色々都合が悪かったので、「,」に変換しました。

フィールド

データに関しては以下を参照してください。全てのフィールドは使用せずに必要なもののみをピックアップして機械学習を行おうと思います。

フィールド名 説明
age 年齢
job 職業
marital 未婚・既婚
education 教育水準
default 債務不履行があるか
housing 住宅ローン有無
loan 個人ローン有無
contact 連絡方法
month 最終コンタクト月
day_of_week 最終コンタクト日
duration 最終コンタクト時間(秒)
campaign 現キャンペーンにおけるコンタクト回数
pdays 経過日数:全キャンペーンコンタクト後の日数
previous 現キャンペーン前のコンタクト回数
poutcome 前回キャンペーンの成果
emp.var.rate 雇用変動率
cons.price.idx 消費者物価指数
cons.conf.idx 消費者信頼感指数
euribor3m 欧州銀行間取引金利
nr.employed 被雇用者数
y 定期預金申し込み有無

環境

Windows10 64bit
Python3.8.5
Tableau Desktop Public Edition 2021.1.0

手順

  1. データ理解
  2. モデルの作成
  3. 推論の実施

1.データ理解

住宅ローン有無ごとの定期預金申し込み比率を確認しましたが、あまり差はなさそうですね。

f:id:sanvarie:20210509083647p:plain

コンタクト時間での申し込み比率を確認しました。コンタクト時間が長くなれば申し込み比率があがることがわかります。

f:id:sanvarie:20210509084306p:plain

コンタクト回数とコンタクト時間の相関関係を確認します。コンタクト回数が少なくコンタクト時間が長いと申し込みされる傾向にあることがわかります。

f:id:sanvarie:20210509084507p:plain

2.モデルの作成

上記で確認したデータを使用して学習モデルを作成します。

import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split

df = pd.read_csv(r"D:\data\machine-learning\bank-additional\bank-additional-full.csv")

# 出力変数
y = df["y"]

# 入力変数
x = df.iloc[:,0:-1]

# ダミー変数化
x = pd.get_dummies(x)
y = pd.get_dummies(y)

# 訓練データと検証データに分割
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=0)

# モデル作成
forest = RandomForestClassifier(max_depth = 10, n_estimators=50, max_features = 25, random_state=0)

# 訓練データをモデルに学習させる
forest.fit(x_train, y_train)

訓練データに対する精度とテストデータに対する精度を確認します。

print(forest.score(x_train, y_train))
print(forest.score(x_test, y_test))

訓練データ「0.9458094590657473」、テストデータ「0.9205593862289987」という結果でした。かなりいい感じだと思います。

ハイパーパラメータの調整

今回使用する RandomForestClassifier に対するパラメータ「max_depth」「 n_estimators」「max_features」ですが、「GridSearchCV」を使用してベストな値を確認しました。

  • max_depth・・・一つ一つの木の深さ
  • n_estimators・・・ランダムフォレストで使用する決定木の数
  • max_features・・・使用する特徴量
from sklearn.model_selection import GridSearchCV
estimator =RandomForestClassifier()

param_grid = {"max_depth": [1,10,25,50],
              "n_estimators": [1,10,25,50],
              "max_features": [1,10,25,50]}

cv =5

x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=0)
tuned_model =GridSearchCV(estimator=estimator, param_grid=param_grid)

tuned_model.fit(x_train, y_train)
pd.DataFrame(tuned_model.cv_results_).T

このような表で最適なハイパーパラメータの組み合わせを確認することができます。

f:id:sanvarie:20210509094847p:plain

ただ、表で確認せずとも best_params_ という値に最適なハイパーパラメータが入っているので、確認してみましょう。

tuned_model.best_params_

以下のような結果になりました。この結果を上記の「モデルの作成」のコードで使用しています。

f:id:sanvarie:20210509094955p:plain

後続の処理で使用するので、best_estimator_ に入っている最適なモデルを引き継ぎましょう。

best_model = tuned_model.best_estimator_ 

3.推論の実施

作成されたモデルを使って新しいデータに対して推論を実施します。

サンプルデータ作成

推論用にサンプルデータを作成します。

import pandas as pd

df_original = pd.read_csv(r"D:\data\machine-learning\bank-additional\bank-additional-full.csv")
df_original.iloc[:, :] = df_original.iloc[:,:].sample(frac=1).reset_index(drop=True)
df_original.to_csv(r"D:\data\machine-learning\bank-additional\sample.csv")

元データ f:id:sanvarie:20210509103446p:plain

サンプルデータ f:id:sanvarie:20210509103503p:plain

サンプルデータから予測値を算出、結果をCSVとして出力し、Tableau で結果を確認します。

# サンプルデータ読込
sample = pd.read_csv(r"D:\data\machine-learning\bank-additional\sample.csv")

# 入力変数
sample_x = sample.iloc[:,1:-1]

# ダミー変数化
sample_x = pd.get_dummies(sample_x)

# 最適なモデルから予測値を取得
y_new = best_model.predict_proba(sample_x)

a = []
for i in y_new:
    for ii in i:
        a.append(ii[1])

df_y_new = pd.DataFrame(a, columns=["predict"])

sample_predict = pd.concat([sample,df_y_new],axis=1)

# CSV出力
sample_predict.to_csv(r"D:\data\machine-learning\bank-additional\sample_predict.csv")

Tableau で結果を確認

職業別ごとの申し込み確率

admin が申し込み確率が高いことがわかります。

f:id:sanvarie:20210509121000p:plain

コンタクト時間ごとの申し込み確率

コンタクト時間はおおよそ5分以内にした方がよいことがわかります。

f:id:sanvarie:20210509121528p:plain

個人ローン有無ごとの申し込み確率

個人ローンを組んでいない顧客の方が申し込み確率が高くなることがわかります。

f:id:sanvarie:20210509121734p:plain

住宅ローン有無ごとの申し込み確率

住宅ローンを組んでいる方がやや申し込み確率が高くなることがわかります。

f:id:sanvarie:20210509122002p:plain

最終コンタクト月ごとの申し込み確率

なぜか最終コンタクト月が5月だと申し込み確率が高くなっています。興味深いですね。

f:id:sanvarie:20210509122403p:plain

さいごに

いかがでしたでしょうか。ランダムフォレストを使うことによってかなり高い精度で定期預金申し込みの推論ができたかと思います。ここに詳細な顧客データなどを結合して Tableau で確認してみるともっと面白い結果が見れたのではないかと思います。機械学習については引き続き紹介していきたいと思います。本日は以上です。

Python での探索的データ解析には pandas-profiling を使おう

さて、本日は pandas-profiling について紹介してみようと思います。機械学習をやっていると避けて通れないのが探索的データ解析ですが、かなり面倒な作業ですよね。ただ、pandas-profilign を使えばすごく簡単に探索的データ解析を行うことができます。

探索的データ解析とは

統計では、探索的データ分析は、データセットを分析して主な特性を要約するアプローチであり、多くの場合、統計グラフィックスやその他のデータ視覚化手法を使用します。(出典:Wikipedia)

pandas-profiling とは

Pandas のデータフレームを読み込んでそのデータに対して探索的データ解析を行ってくれるライブラリです。詳細は以下を参照してください。

github.com

必要なライブラリ

pandas と pandas-profiling をインストールしてください。

環境

Windows10 64bit
Python3.8.5

使用するデータ

2008年から2011年の間のポルトガルの銀行顧客の行動履歴、経済指標と、その顧客が実際に定期預金を申し込んだかどうかのデータを使用します。以下サイトの「 bank-additional-full.csv」を使用します。

archive.ics.uci.edu

開くとこんな感じになっています。このデータですが、「;」が区切り文字になっていたので「,」に変換しました。

f:id:sanvarie:20210508193647p:plain

サンプルコード

import pandas as pd
import pandas_profiling as pdp

df = pd.read_csv('bank-additional-full.csv')
pdp.ProfileReport(df, title="Pandas Profiling Report")

これだけです。めちゃめちゃ簡単ですね。

概要

f:id:sanvarie:20210508202738p:plain

各フィールドの情報

f:id:sanvarie:20210508202811p:plain

f:id:sanvarie:20210508202834p:plain

選択したカラムのヒートマップ

f:id:sanvarie:20210508203700p:plain

f:id:sanvarie:20210508203719p:plain

相関関係

f:id:sanvarie:20210508203152p:plain

サンプルデータ

f:id:sanvarie:20210508203251p:plain

さいごに

pandas-profiling を使うことでこんなに簡単に探索的データ解析ができることがわかりました。ぜひ使ってみてください。本日は以上です。

Prophet で時系列解析を行い電力需要の推論値を Tableau で可視化してみよう!

さて、本日は Prophet を利用して電力需要の推論値を可視化してみようと思います。前回に引き続き今回も機械学習に関するエントリーを書いてみました。機械学習に関してはまだまだ学習中の身で拙い内容かもしれませんがこれから機械学習を始めてみようという方の一助になれたら幸いです。

今回チャレンジすること

気象情報を利用した東京都の電力需要推論

使用する機械学習ライブラリ

Prophet というライブラリを使用します。Prophet は Facebook が公開してるライブラリで、簡単に時系列予測が行え、トレンドや季節性などが構造化された結果を得ることができます。 なお、インストール前に PyStan というライブラリもインストールする必要があります。Windows 環境でのインストール方法は以下エントリーで紹介していますので、興味のある方はぜひ読んでみてください。

www.gis-py.com

使用するデータ

f:id:sanvarie:20210505060552p:plain

f:id:sanvarie:20210506105040p:plain

  • 気象データ(気象庁) ・・・以下エントリーでスクレイピングした東京都の2016年1月1日~2020年12月31日までのデータを使用します(weather.csv)。

www.gis-py.com

f:id:sanvarie:20210506104218p:plain

環境

Windows10 64bit
Python3.8.5
Tableau Desktop Public Edition 2021.1.0

手順

  1. データ加工
    1. 電力データに対する処理
      1. 電力データを一つのCSVにまとめる
      2. 「DATE」「TIME」カラムを結合して「DATE_TIME」カラムを作成
    2. 気象データに対する処理
      1. 24時を0時に変換
      2. weather.csv の 「年月日」「時間」カラムを結合して「年月日_時間」カラムを作成し weather_data.csv として CSV 出力
  2. データの確認
  3. モデルの作成
  4. モデルの評価

1.データ加工

電力データに対する処理

# -*- coding: utf-8 -*-
import os
import glob
import pandas as pd

data_dir = r"D:\data\machine learning"
file_name = "union_electricity_data.csv"

df_list = []

# 電力データ検索
for data in glob.glob(r"D:\data\machine learning/?????????.*"):
    df = pd.read_csv(data, encoding = "cp932")
    df_list.append(df)

# CSVをマージ
df_concat = pd.concat(df_list)

# DATE_TIME列作成
df_concat["DATE_TIME"] = pd.to_datetime(df_concat["DATE"].str.cat(df_concat["TIME"], sep=" "))

# 出力
df_concat.to_csv(os.path.join(data_dir, file_name), index=False, encoding = "utf-8")

以下のようになりました。

f:id:sanvarie:20210506115500p:plain

f:id:sanvarie:20210506115528p:plain

気象データに対する処理

# -*- coding: utf-8 -*-
import os
import pandas as pd

data_dir = r"D:\data\machine learning"
file_name = "weather.csv"

# CSV 読込
df = pd.read_csv(os.path.join(data_dir, file_name), encoding = "cp932")

# 24時を0時に変換
df["時間"] = df["時間"].replace(24, 0)

# 年月日_時間カラム作成
df["年月日_時間"] = df["年月日"].str.cat(df["時間"].astype(str), sep=" ") + ":00:00"
df["年月日_時間"] = pd.to_datetime(df["年月日_時間"])

# 出力
df.to_csv(os.path.join(data_dir, "weather_data.csv"), index=False, encoding = "utf-8")

以下のようになりました。

f:id:sanvarie:20210506122043p:plain

2.データの確認

加工したデータを Tableau で確認します。

f:id:sanvarie:20210507072157p:plain

2018年のデータに絞って表示してみました。これを見ると夏場と冬場に電力消費量が高くなるのがわかります。

f:id:sanvarie:20210507073319p:plain

f:id:sanvarie:20210507073452p:plain

曜日と時間帯でもデータを確認してみると、平日の日中に電力使用量が多いことがわかります。

f:id:sanvarie:20210507073806p:plain

上段が電力使用量、下段が気温です。気温が低いもしくは高いときに電力使用量が多くなる傾向にある気がしますね。

f:id:sanvarie:20210507074144p:plain

3.モデルの作成

Prophet では時刻や日時を表すカラムをds、推論したい値をyという名前に変更する必要があるので、まずはカラム名を変更します。また、ここで不要なカラムの削除と2016年~2019年のデータを学習データとして抽出します。

import pandas as pd

# 電力データを読み込み
df = pd.read_csv(r"D:\data\machine learning\union_electricity_data.csv")

# 予測に不要なカラムを削除
df = df.drop(["DATE", "TIME"], axis=1)

# 2016年~2019年のデータに絞る
df = df.query("DATE_TIME >= '2016-04-01' and DATE_TIME <='2019-12-31'")

# カラム名変更(Prophet対応)
df = df.rename(columns={"DATE_TIME":"ds","実績(万kW)":"y"})

df.head()

f:id:sanvarie:20210507075226p:plain

上記で抽出した2016年~2019年のデータを使用して2020年の電力使用量を推論し結果を CSV 出力します。

from fbprophet import Prophet

data_dir = r"D:\data\machine learning"
model = Prophet()
model.fit(df)

# 24サンプル×365日(予測したい2020年の日数)
future = model.make_future_dataframe(24*365, freq="H")

# 推論
forecast = model.predict(future)

# CSVに出力
forecast.to_csv(r"D:\data\machine learning\future.csv", index=False, encoding = "utf-8")

4.モデルの評価

前項で出力した future.csv を Tableau で可視化してみます。電力データ、気象データと結合します。

f:id:sanvarie:20210507082925p:plain

オレンジが2020年の推論値で青が実績値です。夏場と冬場に電力使用量が多くなる傾向はしっかり推論できている感じですね。

f:id:sanvarie:20210507083649p:plain

誤差の確認のため、計算フィールドとして「MAPE」を作成します。

f:id:sanvarie:20210507084046p:plain

MAPE とは

平均絶対パーセント誤差は、平均絶対パーセント偏差とも呼ばれ、統計における予測方法の予測精度の尺度です。通常、精度は次の式で定義される比率として表されます(出典:Wikipedia)。

シートのタイトルに MAPE を表示させました。MAPE=8.416%とということがわかりました。まぁ悪くはない結果でしょうか。

f:id:sanvarie:20210507092333p:plain

もう少し詳細を確認するために計算フィールドとして「誤差」を作成します。

f:id:sanvarie:20210507092657p:plain

下段が誤差の表示です。夏と冬を中心に大きな誤差が出てしまっていますね。

f:id:sanvarie:20210507092754p:plain

猛暑日や真冬日などのデータを追加して学習すればもう少し誤差を小さくすることができそうな気がします。

さいごに

Prophet を使うことで時系列解析がすごく簡単にできることがわかりました。難しいのはそこまでにどのようにデータを整備すればという部分でしょうか。今回は簡単な分析になってしまったのですが、祝日やお盆など不定期なイベントのデータを Prophet に読み込ませた方がより正確な推論ができるかと思います。あとはもっと詳細な気象データを使うことで今まで見えてこなかった傾向が見えるかもしれません。機械学習に関しては今後も本ブログで紹介したいと思います。本日は以上です。