GIS奮闘記

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

スポンサーリンク

Python と気象庁の天気予報 JSON を使って全国の天気予報を取得してみる

さて、本日は気象庁の天気予報 JSON で遊んでみようと思います。気象庁のデータに関しては以下のエントリーでも扱いましたが、この時はスクレイピングを行いました。今回は公開されている JSON を活用して天気情報を取得してみたいと思います。

www.gis-py.com

気象庁天気予報 JSON とは

気象庁のWebサイトのリニューアルに伴い、天気予報情報がJSON形式で取得できるようになりました。正式公開の WebAPI ではないのですが、「政府標準利用規約に準拠して利用可能」なようです。

気象庁天気予報 JSON の種類

  1. エリアコード
  2. コンテンツ種別
  3. 各地の気象情報

今回は「3.各地の気象情報」を利用します。以下のようなURLにリクエストを投げるとエリアコードに応じた結果が取得できます。

# 東京の場合はhttps://www.jma.go.jp/bosai/forecast/data/forecast/130000.json
https://www.jma.go.jp/bosai/forecast/data/forecast/エリアコード.json 

東京のエリアコードでリクエストを投げると以下のような結果になります。東京の各エリアに関して三日間(以下の場合3/30,3/30,4/1)の天気情報を取得することができます。

[{'publishingOffice': '気象庁',
  'reportDatetime': '2022-03-30T17:00:00+09:00',
  'timeSeries': [{'timeDefines': ['2022-03-30T17:00:00+09:00',
     '2022-03-31T00:00:00+09:00',
     '2022-04-01T00:00:00+09:00'],
    'areas': [{'area': {'name': '東京地方', 'code': '130010'},
      'weatherCodes': ['201', '212', '201'],
      'weathers': ['くもり\u3000夜のはじめ頃\u3000晴れ',
       'くもり\u3000夜\u3000雨',
       'くもり\u3000時々\u3000晴れ'],
      'winds': ['南の風\u300023区西部\u3000では\u3000南の風\u3000やや強く',
       '南の風\u3000後\u3000北東の風\u300023区西部\u3000では\u3000後\u3000北東の風\u3000やや強く',
       '北の風'],
      'waves': ['1メートル\u3000後\u30000.5メートル',
       '0.5メートル\u3000後\u30001メートル',
       '1メートル']},
     {'area': {'name': '伊豆諸島北部', 'code': '130020'},
      'weatherCodes': ['200', '212', '313'],
      'weathers': ['くもり\u3000所により\u3000雨',
       'くもり\u3000夜遅く\u3000雨',
       '雨\u3000後\u3000くもり'],
      'winds': ['南の風\u3000後\u3000南西の風',
       '南西の風\u3000後\u3000やや強く',
       '北東の風\u3000やや強く'],
      'waves': ['2メートル', '2メートル', '2.5メートル']},
     {'area': {'name': '伊豆諸島南部', 'code': '130030'},
      'weatherCodes': ['200', '212', '313'],
      'weathers': ['くもり\u3000所により\u3000雨',
       'くもり\u3000夜遅く\u3000雨',
       '雨\u3000後\u3000くもり'],
      'winds': ['南の風', '西の風\u3000後\u3000やや強く', '北東の風\u3000やや強く'],
      'waves': ['2.5メートル\u3000ただし\u3000三宅島\u3000では\u30002メートル',
       '2メートル',
       '2.5メートル\u3000後\u30004メートル\u3000ただし\u3000三宅島\u3000では\u30002.5メートル\u3000後\u30003メートル']},
--- 省略 ---

今回のゴール

全国の気象庁天気予報 JSON の中から「天気」、「風」、「波」の情報を CSVとして出力(降水確率や気温などは今回は対象外としています)

実行環境

macOS 11.3.1
Python 3.9.6

サンプルコード

# -*- coding: utf-8 -*-
import requests
import json
import csv

# エリアコード
area_dic = {'北海道/釧路':'014100',
            '北海道/旭川':'012000',
            '北海道/札幌':'016000',
            '青森県':'020000',
            '岩手県':'030000',
            '宮城県':'040000',
            '秋田県':'050000',
            '山形県':'060000',
            '福島県':'070000',
            '茨城県':'080000',
            '栃木県':'090000',
            '群馬県':'100000',
            '埼玉県':'110000',
            '千葉県':'120000',
            '東京都':'130000',
            '神奈川県':'140000',
            '新潟県':'150000',
            '富山県':'160000',
            '石川県':'170000',
            '福井県':'180000',
            '山梨県':'190000',
            '長野県':'200000',
            '岐阜県':'210000',
            '静岡県':'220000',
            '愛知県':'230000',
            '三重県':'240000',
            '滋賀県':'250000',
            '京都府':'260000',
            '大阪府':'270000',
            '兵庫県':'280000',
            '奈良県':'290000',
            '和歌山県':'300000',
            '鳥取県':'310000',
            '島根県':'320000',
            '岡山県':'330000',
            '広島県':'340000',
            '山口県':'350000',
            '徳島県':'360000',
            '香川県':'370000',
            '愛媛県':'380000',
            '高知県':'390000',
            '福岡県':'400000',
            '佐賀県':'410000',
            '長崎県':'420000',
            '熊本県':'430000',
            '大分県':'440000',
            '宮崎県':'450000',
            '鹿児島県':'460100',
            '沖縄県/那覇':'471000',
            '沖縄県/石垣':'474000'
            }

# CSV出力先
output_file = "weather_report.csv"

# CSVヘッダー
header = ["都道府県","データ配信元","報告日時",
          "地方名","予報日時","天気","風","波"]

def main():
    make_csv()

def make_csv():
    with open(output_file, 'w', encoding='utf-8') as f:
        writer = csv.writer(f, lineterminator="\n")
        writer.writerow(header)

        # JSONから情報を取得
        write_lists = get_info()

        # CSV書き込み
        writer.writerows(write_lists)

def get_info():
    write_lists = []
    base_url = "https://www.jma.go.jp/bosai/forecast/data/forecast/"
    for k, v in area_dic.items():

        if k.find("/"):
            prefecture = k[0:k.find("/")]
        else:
            prefecture = k

        url = base_url + v + ".json"

        res = requests.get(url).json()

        for re in res:
            publishingOffice = re["publishingOffice"]
            reportDatetime = re["reportDatetime"]

            timeSeries = re["timeSeries"]

            for time in timeSeries:
                #降水確率など今回のターゲット以外は除外する
                if 'pops' in time["areas"][0]:
                    pass
                elif 'temps' in time["areas"][0]:
                    pass
                elif 'tempsMax' in time["areas"][0]:
                    pass
                else:
                    for i in range(len(time["areas"])):

                        local_name = time["areas"][i]["area"]["name"]

                        for j in range(len(timeSeries[0]["timeDefines"])):

                            if 'weathers' not in time["areas"][i]:
                                weather = ""
                            else:
                                weather = time["areas"][i]["weathers"][j]

                            if 'winds' not in time["areas"][i]:
                                wind = ""
                            else:
                                wind = time["areas"][i]["winds"][j]

                            if 'waves' not in time["areas"][i]:
                                wave = ""
                            else:
                                wave = time["areas"][i]["waves"][j]

                            timeDefine = time["timeDefines"][j]

                            # 各情報をリストに格納
                            write_list = []
                            write_list.append(prefecture)
                            write_list.append(publishingOffice)
                            write_list.append(reportDatetime)
                            write_list.append(local_name)
                            write_list.append(timeDefine)
                            write_list.append(weather)
                            write_list.append(wind)
                            write_list.append(wave)

                            write_lists.append(write_list)
    return write_lists

if __name__ == '__main__':
    main()

結果は以下のようになりました。今回のゴールである「天気」、「風」、「波」の情報の取得に成功しました。 f:id:sanvarie:20220331152133p:plain

さいごに

このJSONを使えば簡単に天気予報情報を取得できることがわかりました。今回は省略しましたが、気温や降水確率なども取得できるので興味のある方はぜひチャレンジしてみてください。本日は以上です。

Python で気象庁防災情報XMLの地震情報をCSV出力する方法

さて、本日は気象庁防災情報XMLの地震情報をCSV出力しようと思います。

気象庁防災情報XMLとは

気象庁は過去長年にわたり、それぞれの防災情報毎に情報の性質・利用形態などを考慮し、気象庁独自の電文形式(フォーマット)を作成してきました。この方式は、防災情報の種類が少なく、情報の伝達がFAXや低速の通信回線の時代はそれぞれの情報に適していましたが、高度にICT化された社会において、より詳細で高度化された防災情報をより効果的に活用していただくために、新たな防災情報の提供様式を検討すべきと考え、「気象庁防災情報XMLフォーマット」を策定することとし、平成23年5月12日より運用を開始しました。(出典:気象庁

気象庁防災情報XMLの仕様について

気象庁のサイトにまとまっているのでこちらをご参照ください。

また、地震情報に関してはこちらにもまとまっています。

今回取得するデータ

こちらのサイトから地震情報を取得します。(今回は長期フィードを使用)

f:id:sanvarie:20220320070157p:plain

この中には火山情報も入っており、そこを抜いて地震情報のみを取得します。以下画像の赤枠が地震情報となっており、同じようにURLに「VXSE」を含んだXMLから地震情報を取得したいと思います。

f:id:sanvarie:20220321101929p:plain

取得する項目

さまざまな項目があるのですが、今回は重要そうな以下の項目を取得しようと思います。

  • イベントID
  • 地震発生場所
  • 地震発生時刻
  • 観測点で地震を検知した時刻
  • 緯度
  • 経度
  • マグニチュード
  • 県名称
  • 市町村名称
  • 最大震度
  • 震度観測点の情報(一つの地震に対して複数の情報があるため、セル内で半角スペースで区切ります)

実行環境

macOS 11.3.1
Python 3.9.6

サンプルコード

気象庁防災情報XMLから地震情報を取得するサンプルです。

# -*- coding: utf-8 -*-
import re
import csv
import urllib.request
from bs4 import BeautifulSoup

# CSV出力先
output_file = "eathquake.csv"

# CSVヘッダー
header = ["イベントID","地震発生場所","地震発生時刻","観測点で地震を検知した時刻","緯度","経度",
          "マグニチュード","県名称","市町村名称","最大震度","震度観測点の情報"]

with open(output_file, 'w', encoding='utf-8') as f:
    writer = csv.writer(f, lineterminator="\n")
    writer.writerow(header)

    # 対象URLにリクエストを送る
    url = "https://www.data.jma.go.jp/developer/xml/feed/eqvol_l.xml"
    res = urllib.request.urlopen(url).read()
    soup = BeautifulSoup(res)

    # 地震に関する情報のみを抽出
    search = re.compile('.*VXSE.*')

    for vxse in soup.find_all(text=search):

        # 対象URLにリクエストを送る
        res = urllib.request.urlopen(vxse).read()
        soup = BeautifulSoup(res)

        # イベントIDを取得
        event_id = soup.find("eventid").text

        for content in soup.find_all("body"):
            earthquake = content.find("earthquake")

            if earthquake is not None: # earthquakeの情報がない地震情報は対象外とする
                # 地震の各種情報を取得
                location = earthquake.find("name").text # 地震発生場所
                origintime = earthquake.find("origintime").text # 地震の発生した時刻
                arrivaltime = earthquake.find("arrivaltime").text # 観測点で地震を検知した時刻
                
                coordinate = earthquake.find("jmx_eb:coordinate").text # "北緯37.8度 東経141.6度 深さ 50km" +37.8+141.6-50000
                lat = coordinate[1:coordinate[1:].find("+") + 1]
                lon = coordinate[coordinate[1:].find("+") + 2:coordinate[1:].find("+") + 7]
                lat = int(lat[0:2])+(int(lat[3:])/60)+(0/3600)
                lon = int(lon[0:3])+(int(lon[4:])/60)+(0/3600)
                magnitude = earthquake.find("jmx_eb:magnitude").text 

            intensity = content.find("intensity")
            if intensity is not None: # intensityの情報がない地震情報は対象外とする:
                for pref in intensity.find_all("pref"):
                    
                    prefectuer = pref("name")[0].text                  

                    for city in pref.find_all("city"):
                        city_name = city("name")[0].text
                        maxint = city("maxint")[0].text #地震情報(震源・震度に関する情報)の各市町村での震度観測点の最大震度。

                        write_list = []
                        write_list.append(event_id)
                        write_list.append(location)
                        write_list.append(origintime)
                        write_list.append(arrivaltime)
                        write_list.append(lat)
                        write_list.append(lon)
                        write_list.append(magnitude)
                        write_list.append(prefectuer)
                        write_list.append(city_name)
                        write_list.append(maxint)

                        intensity_station = ""
                        for intensitystation in pref.find_all("intensitystation"): #地震情報(震源・震度に関する情報)の震度観測点の情報 *印は気象庁以外の震度観測点についての情報
                            intensity_station = intensity_station + intensitystation("name")[0].text + " " # 半角スペースで区切る
                        
                        write_list.append(intensity_station)

                        # CSV書き込み
                        writer.writerow(write_list)

出力結果は以下のようになりました。 f:id:sanvarie:20220321102555p:plain

さいごに

いかがでしたでしょうか?位置情報を含んでいるので出力結果を可視化してみてもいいかもしれないですね。また、気象庁防災情報XMLは地震情報以外にも様々な情報を提供しているので今後は他の情報でも遊んでみようと思います。本日は以上です。

Elasticsearch と Python を使って空間検索をしてみる

皆様、お久しぶりです。前回のエントリーから間が空いてしまいましたが、また少しずつブログの更新をしていきたいと思っています。今回は Elasticsearch と Python を使って空間検索をしてみようと思います。

以前にも Elasticsearch に関して以下のエントリーを書いているのでぜひ読んでみてください。

www.gis-py.com

なお、Elasticsearch のインストールや設定などについては本エントリーでは割愛しています。

実行環境

macOS 11.3.1
Python 3.9.6
Elasticsearch 7.17.0
elasticsearch 8.0.0(Elasticsearch の Python クライアント)

手順

  1. インデックスの作成
  2. 位置情報データを Elasticsearch に格納
  3. 空間検索

1.インデックスの作成

Elasticsearch 上にインデックスを作成します。詳細については以下をご参照ください。

www.elastic.co

PUT culture_sports_park
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_point"
      }
    }
  }
}

2.位置情報データを Elasticsearch に格納

今回は「東京都オープンデータカタログサイト」から文化・スポーツ施設・公園の geojsonデータをダウンロードしてみたいと思います。

catalog.data.metro.tokyo.lg.jp

Elasticsearch の Python クライアント である elasticsearch を使って以下のようにデータを格納します。

import geojson
from elasticsearch import Elasticsearch, helpers

with open("culture_sports_park.geojson") as f:
    gj = geojson.load(f)

    es = Elasticsearch("http://localhost:9200")

    k = ({
        "_index": "culture_sports_park",
        "_source": {
            "name": feature["properties"]["施設名称"],
            "address": feature["properties"]["所在地"],
            "note": feature["properties"]["備考"],
            "location": {
                "lat":feature["geometry"]["coordinates"][1],
                "lon": feature["geometry"]["coordinates"][0]
            }
        }

    } for feature in gj['features'])

    helpers.bulk(es, k)

以下のコマンドでデータが格納されているか確認します。

GET culture_sports_park/_search

ばっちりですね。

f:id:sanvarie:20220314075335p:plain

これを Kibana 上で可視化してみると以下のようになります。

f:id:sanvarie:20220314075904p:plain

3.空間検索

蒲田駅から半径5kmにある文化・スポーツ施設・公園を検索してみます。

from elasticsearch import Elasticsearch

es = Elasticsearch("http://localhost:9200")

# 蒲田駅から半径5kmにあるポイントを検索
query = {
  "query": {
    "geo_distance": {
      "distance": "5km",
      "location": {
        "lat": 35.562479,
        "lon": 139.716073
      }
    }
  }
}

# 検索
result = es.search(index="culture_sports_park", body=query, size=100)

# 結果表示
for document in result["hits"]["hits"]:
    print(document["_source"])

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

{'name': '旗の台文化センター', 'address': '東京都品川区旗の台5-19-5', 'note': '所在地:旗の台5-19-5電話:3786-5191 施設内容:第1〜2会議室 レクリエーションホール スポーツ室 グループ室 休館日:年末年始・毎月第4日曜日', 'location': {'lat': 35.603845, 'lon': 139.700253}}
{'name': '南大井文化センター', 'address': '東京都品川区南大井1-12-6', 'note': '所在地:南大井1-12-6 電話:3764-6511 施設内容:第1〜3講習室\u3000美術工芸室\u3000レクリエーションホール\u3000スポーツ室\u3000託児室\u3000グループ室 休館日:年末年始・毎月第4日曜日', 'location': {'lat': 35.596235, 'lon': 139.737582}}
{'name': '南大井図書館', 'address': '東京都品川区南大井3-7-13保育園等と併設', 'note': '品川区南大井3-7-13併設南大井児童センター南大井保育園南大井シルバーセンター', 'location': {'lat': 35.591586, 'lon': 139.734398}}
{'name': '源氏前図書館', 'address': '東京都品川区中延4-14-17保育園と併設', 'note': '品川区中延4-14-17併設源氏前保育園', 'location': {'lat': 35.605478, 'lon': 139.709904}}
{'name': '大井図書館', 'address': '東京都品川区大井5-19-14滝王子児童センターと併設', 'note': '品川区大井5-19-14併設滝王子児童センター', 'location': {'lat': 35.598196, 'lon': 139.729134}}
{'name': '品川歴史館', 'address': '東京都品川区大井6-11-1', 'note': '品川区大井6-11-1電話:3777-4060 交通:大井町駅から東急バス「鹿島神社前」下車徒歩1分、JR大森駅山王北口下車徒歩10分 開館時間:午前9時〜午後5時(入館は4時30分まで) 休館日:年末年始、月曜日および祝日', 'location': {'lat': 35.596791, 'lon': 139.730102}}
{'name': 'メイプルセンター', 'address': '東京都品川区西大井1-4-25', 'note': '(公財)品川文化振興事業団が運営するカルチャーセンター。 多様な講座を開催。所在地:西大井1-4-25電話:3774-5050 交通:JR横須賀線西大井駅 休館日:毎月第4日曜日、年末年始 開館時間:午前9時〜午後9時30分(土・日・祝日は午後5時まで)', 'location': {'lat': 35.602061, 'lon': 139.722429}}
{'name': 'しながわ水族館', 'address': '東京都品川区勝島3-2-1しながわ区民公園内', 'note': '品川区勝島3-2-1しながわ区民公園内電話:3762-3431 交通: JR大井町駅から無料バス開館時間: 午前10時〜午後5時(入館は午後4時30分まで) 休館日: 毎週火曜日、1月1日 *春休み、ゴールデンウィーク、夏休みは営業', 'location': {'lat': 35.588634, 'lon': 139.737364}}
{'name': '大井ふ頭中央海浜公園野球場', 'address': '東京都品川区八潮4-1-19', 'note': '品川区八潮4-1-19', 'location': {'lat': 35.590939, 'lon': 139.752439}}
{'name': 'しながわ区民公園野球場', 'address': '東京都品川区勝島3-2-2', 'note': '品川区勝島3-2-2 京浜急行\u3000立会川駅、大森海岸駅から徒歩約5分公園内には、しながわ水族館、野球場、庭球場、ディキャンプ場、プール(夏季)あり。', 'location': {'lat': 35.594479, 'lon': 139.740205}}
{'name': 'しながわ区民公園庭球場', 'address': '東京都品川区勝島3-2-2', 'note': '品川区勝島3-2-2 野球場、庭球場ディキャンプ場あり', 'location': {'lat': 35.593656, 'lon': 139.739353}}
{'name': '大井ふ頭中央海浜公園', 'address': '東京都品川区八潮4-1-19', 'note': '品川区八潮4-1-19', 'location': {'lat': 35.592503, 'lon': 139.752289}}
{'name': '大井ふ頭中央海浜公園陸上競技場・球技場', 'address': '東京都品川区八潮4-1-19', 'note': '品川区八潮4-1-19', 'location': {'lat': 35.594031, 'lon': 139.753818}}
{'name': 'しながわ区民公園デイキャンプ場', 'address': '東京都品川区勝島3-2-2', 'note': '品川区勝島3-2-2 野球場、テニス場ディキャンプ場あり', 'location': {'lat': 35.592863, 'lon': 139.738629}}
{'name': '大井海岸公園', 'address': '東京都品川区南大井3-27-5', 'note': '品川区南大井3-27-5', 'location': {'lat': 35.588463, 'lon': 139.733647}}
{'name': '大井水神公園', 'address': '東京都品川区南大井5-16-1', 'note': '品川区南大井5-16-1、南大井6-14-2、南大井6-15-2', 'location': {'lat': 35.595355, 'lon': 139.732493}}
{'name': '鈴ケ森公園', 'address': '東京都品川区南大井4-18-14', 'note': '品川区南大井4-18-14', 'location': {'lat': 35.59351, 'lon': 139.734935}}
{'name': '大井中央公園', 'address': '東京都品川区大井1-46-8', 'note': '品川区大井1-46-8', 'location': {'lat': 35.604517, 'lon': 139.732352}}
{'name': '大井坂下公園', 'address': '東京都品川区南大井6-23-11', 'note': '品川区南大井6-23-11', 'location': {'lat': 35.589516, 'lon': 139.730786}}
{'name': '金子山公園', 'address': '東京都品川区西大井4-11-6', 'note': '品川区西大井4-11-6', 'location': {'lat': 35.59583, 'lon': 139.720733}}
{'name': '新浜川公園', 'address': '東京都品川区東大井2-26-18', 'note': '品川区東大井2-26-18', 'location': {'lat': 35.597859, 'lon': 139.740928}}
{'name': 'わかくさ公園', 'address': '東京都品川区勝島1-6-1', 'note': '品川区勝島1-6-1', 'location': {'lat': 35.598043, 'lon': 139.743905}}
{'name': '原っぱ公園', 'address': '東京都品川区西大井6-1-14', 'note': '品川区西大井6-1-14', 'location': {'lat': 35.601099, 'lon': 139.714796}}
{'name': '宮下公園', 'address': '東京都品川区大井2-7-16', 'note': '品川区大井2-7-16', 'location': {'lat': 35.605213, 'lon': 139.728399}}
{'name': '浜川公園', 'address': '東京都品川区南大井4-8-22', 'note': '品川区南大井4-8-22', 'location': {'lat': 35.597232, 'lon': 139.735757}}
{'name': '滝王子公園', 'address': '東京都品川区大井5-19-5', 'note': '品川区大井5-19-5', 'location': {'lat': 35.598389, 'lon': 139.728806}}
{'name': '出石公園', 'address': '東京都品川区西大井3-16-27', 'note': '品川区西大井3-16-27', 'location': {'lat': 35.594866, 'lon': 139.722095}}
{'name': '浜川北公園', 'address': '東京都品川区東大井3-26-6', 'note': '品川区東大井3-26-6', 'location': {'lat': 35.599143, 'lon': 139.737533}}
{'name': '桐畑公園', 'address': '東京都品川区南大井6-1-17', 'note': '品川区南大井6-1-17', 'location': {'lat': 35.593052, 'lon': 139.73167}}
{'name': '西の森公園', 'address': '東京都品川区西大井4-2-1', 'note': '品川区西大井4-2-1', 'location': {'lat': 35.597725, 'lon': 139.721316}}
{'name': '関ケ原公園', 'address': '東京都品川区東大井6-12-21', 'note': '品川区東大井6-12-21', 'location': {'lat': 35.602174, 'lon': 139.735626}}
{'name': '倉田公園', 'address': '東京都品川区大井4-29-23', 'note': '品川区大井4-29-23', 'location': {'lat': 35.59851, 'lon': 139.73236}}
{'name': '西大井広場公園', 'address': '東京都品川区西大井1-4-10', 'note': '品川区西大井1-4-10、二葉2-19-7', 'location': {'lat': 35.602258, 'lon': 139.723426}}
{'name': '元芝公園', 'address': '東京都品川区東大井3-7-18', 'note': '品川区東大井3-7-18', 'location': {'lat': 35.602744, 'lon': 139.739034}}
{'name': '西大井緑地公園', 'address': '東京都品川区西大井6-10-16', 'note': '品川区西大井6-10-16', 'location': {'lat': 35.600333, 'lon': 139.720565}}
{'name': 'しおじ公園', 'address': '東京都品川区八潮5-8-1', 'note': '品川区八潮5-8-1、八潮5-6-9', 'location': {'lat': 35.596166, 'lon': 139.750656}}
{'name': '大井鹿島公園', 'address': '東京都品川区大井6-8-2', 'note': '品川区大井6-8-2', 'location': {'lat': 35.596954, 'lon': 139.732443}}
{'name': '谷垂公園', 'address': '東京都品川区西大井6-13-10', 'note': '品川区西大井6-13-10', 'location': {'lat': 35.599954, 'lon': 139.717892}}
{'name': 'やまなか公園', 'address': '東京都品川区大井3-22-3', 'note': '品川区大井3-22-3', 'location': {'lat': 35.601039, 'lon': 139.727831}}
{'name': '大森貝塚遺跡庭園', 'address': '東京都品川区大井6-21-6', 'note': '品川区大井6-21-6', 'location': {'lat': 35.593454, 'lon': 139.729945}}
{'name': '旗の台公園', 'address': '東京都品川区旗の台5-19-9', 'note': '品川区旗の台5-19-9', 'location': {'lat': 35.604026, 'lon': 139.700415}}
{'name': '旗の台南公園', 'address': '東京都品川区旗の台5-11-11', 'note': '品川区旗の台5-11-11', 'location': {'lat': 35.603715, 'lon': 139.70271}}
{'name': '東中延公園', 'address': '東京都品川区東中延2-10-2', 'note': '品川区東中延2-10-2', 'location': {'lat': 35.606891, 'lon': 139.712651}}
{'name': '荏原町公園', 'address': '東京都品川区中延5-14-5', 'note': '品川区中延5-14-5', 'location': {'lat': 35.602276, 'lon': 139.708936}}
{'name': '大原公園', 'address': '東京都品川区戸越6-14-1', 'note': '品川区戸越6-14-1', 'location': {'lat': 35.60706, 'lon': 139.71428}}
{'name': '豊町公園', 'address': '東京都品川区豊町6-16-3', 'note': '品川区豊町6-16-3', 'location': {'lat': 35.604645, 'lon': 139.716402}}
{'name': '二葉公園', 'address': '東京都品川区二葉4-13-5', 'note': '品川区二葉4-13-5', 'location': {'lat': 35.603668, 'lon': 139.718124}}
{'name': '旗の台北公園', 'address': '東京都品川区旗の台3-10-15', 'note': '品川区旗の台3-10-15', 'location': {'lat': 35.605695, 'lon': 139.704672}}
{'name': '源氏前公園', 'address': '東京都品川区中延6-4-15', 'note': '中延 6-4-15', 'location': {'lat': 35.602198, 'lon': 139.712407}}
{'name': '旗の台なか公園', 'address': '東京都品川区旗の台3-9-12', 'note': '品川区旗の台3-9-12', 'location': {'lat': 35.604708, 'lon': 139.705797}}
{'name': '戸越南公園', 'address': '東京都品川区戸越6-8-8', 'note': '品川区戸越6-8-8', 'location': {'lat': 35.607305, 'lon': 139.718587}}
{'name': '旗の台広場公園', 'address': '東京都品川区旗の台3-1-5', 'note': '品川区旗の台3-1-5', 'location': {'lat': 35.606235, 'lon': 139.705977}}
{'name': '二鳳公園', 'address': '東京都品川区豊町4-19-20', 'note': '品川区豊町4-19-20', 'location': {'lat': 35.605414, 'lon': 139.721282}}
{'name': '鹿島庚塚児童遊園', 'address': '東京都品川区大井7-29-11', 'note': '品川区大井7-29-11', 'location': {'lat': 35.59392, 'lon': 139.72937}}
{'name': '富士見ケ丘児童遊園', 'address': '東京都品川区西大井5-7', 'note': '品川区西大井5-7-3', 'location': {'lat': 35.599427, 'lon': 139.716358}}
{'name': '北浜川児童遊園', 'address': '東京都品川区東大井2-25-22', 'note': '品川区東大井2-25-22', 'location': {'lat': 35.597968, 'lon': 139.739288}}
{'name': '出石児童遊園', 'address': '東京都品川区西大井3-1-5', 'note': '品川区西大井3-1-5', 'location': {'lat': 35.597804, 'lon': 139.723842}}
{'name': '関ケ原児童遊園', 'address': '東京都品川区南大井5-2-15', 'note': '品川区南大井5-2-15', 'location': {'lat': 35.600409, 'lon': 139.734917}}
{'name': '東大井三丁目児童遊園', 'address': '東京都品川区東大井3-29-6', 'note': '品川区東大井3-29-6', 'location': {'lat': 35.598928, 'lon': 139.737048}}
{'name': '西大井ちびっこ児童遊園', 'address': '東京都品川区西大井2-24-9', 'note': '品川区西大井2-24-9', 'location': {'lat': 35.597834, 'lon': 139.72503}}
{'name': '作守児童遊園', 'address': '東京都品川区大井4-12-14', 'note': '品川区大井4-12-14', 'location': {'lat': 35.601587, 'lon': 139.733211}}
{'name': '桜橋児童遊園', 'address': '東京都品川区東大井3-22-1', 'note': '品川区東大井3-22-1', 'location': {'lat': 35.599858, 'lon': 139.735899}}
{'name': '西大井二丁目児童遊園', 'address': '東京都品川区西大井2-22-7', 'note': '品川区西大井2-22-7', 'location': {'lat': 35.598816, 'lon': 139.724691}}
{'name': '大井倉田児童遊園', 'address': '東京都品川区大井4-22-30', 'note': '品川区大井4-22-30', 'location': {'lat': 35.600254, 'lon': 139.732213}}
{'name': '鈴ケ森道路児童遊園', 'address': '東京都品川区南大井1-22-1', 'note': '品川区南大井1-22-1', 'location': {'lat': 35.594342, 'lon': 139.736671}}
{'name': 'みなみ児童遊園', 'address': '東京都品川区南大井1-13-8', 'note': '品川区南大井1-13-8、南大井1-12-9', 'location': {'lat': 35.595655, 'lon': 139.737085}}
{'name': '旗岡児童遊園', 'address': '東京都品川区旗の台3-6-12', 'note': '品川区旗の台3-6-12', 'location': {'lat': 35.604515, 'lon': 139.708511}}
{'name': '上神明児童遊園', 'address': '東京都品川区二葉4-26-11', 'note': '品川区二葉4-26-11、二葉4-3-15', 'location': {'lat': 35.602209, 'lon': 139.714168}}
{'name': '豊町5丁目児童遊園', 'address': '東京都品川区豊町5-14-3', 'note': '品川区豊町5-14-3', 'location': {'lat': 35.604875, 'lon': 139.720592}}
{'name': '立会川児童遊園(その4)', 'address': '東京都品川区旗の台3-7', 'note': '品川区旗の台3-7地先、旗の台3-7-3-9地先', 'location': {'lat': 35.604264, 'lon': 139.706422}}
{'name': '立会川児童遊園(その2)', 'address': '東京都品川区旗の台2-7', 'note': '品川区旗の台2-7地先、旗の台2-7-2-1地先', 'location': {'lat': 35.606224, 'lon': 139.704371}}
{'name': '立会川児童遊園(その3)', 'address': '東京都品川区旗の台3-11', 'note': '品川区旗の台3-11地先、旗の台3-11-3-10地先', 'location': {'lat': 35.60551, 'lon': 139.704621}}
{'name': '豊町児童遊園', 'address': '東京都品川区豊町4-1-12', 'note': '品川区豊町4-1-12', 'location': {'lat': 35.60722, 'lon': 139.7192}}
{'name': '伊藤児童遊園', 'address': '東京都品川区西大井6-17-9', 'note': '品川区西大井6-17-9', 'location': {'lat': 35.600127, 'lon': 139.715824}}
{'name': '京浜運河緑道公園', 'address': '東京都品川区八潮1', 'note': '品川区八潮1、八潮5', 'location': {'lat': 35.597029, 'lon': 139.748997}}
{'name': 'しながわ区民公園屋外プール', 'address': '東京都品川区勝島3-2-2', 'note': '野球場、庭球場ディキャンプ場あり', 'location': {'lat': 35.593391, 'lon': 139.739778}}
{'name': 'しながわ区民公園', 'address': '東京都品川区勝島3-2-2', 'note': '品川区南大井2-3-17品川区勝島3-2-2', 'location': {'lat': 35.592575, 'lon': 139.738882}}
{'name': '森前公園', 'address': '東京都品川区西大井1-1-10', 'note': '品川区西大井1-1-10', 'location': {'lat': 35.600512, 'lon': 139.721299}}
{'name': '弁天通り公園', 'address': '東京都品川区中延5-3-8', 'note': '品川区中延5-3-8', 'location': {'lat': 35.603752, 'lon': 139.708745}}
{'name': '庚申公園', 'address': '東京都品川区中延5-13-17', 'note': '品川区中延5-13-17', 'location': {'lat': 35.60169, 'lon': 139.708685}}
{'name': '都立京浜運河緑道公園', 'address': '東京都品川区八潮1-', 'note': '品川区八潮1、八潮5', 'location': {'lat': 35.596782, 'lon': 139.748972}}
{'name': '都立大井ふ頭中央海浜公園', 'address': '東京都品川区八潮4-1-19', 'note': '東京都品川区八潮4-1-19', 'location': {'lat': 35.592501, 'lon': 139.752291}}
{'name': '旗の台東広場', 'address': '東京都品川区旗の台4-12-16', 'note': '品川区旗の台4-12-16', 'location': {'lat': 35.60214, 'lon': 139.70387}}
{'name': 'ごこう公園', 'address': '東京都品川区中延5-9-23', 'note': '品川区中延5-9-23区へ寄付された用地を公園として整備。周辺に花壇を施したシンプルなつくり。', 'location': {'lat': 35.602062, 'lon': 139.706797}}
{'name': 'ゆたか南公園', 'address': '東京都品川区豊町6-29-2', 'note': '品川区豊町6-29-2住宅の密集著しい地区の中にあるため、広場スペースが広く、防災機能を多く取り入れている。', 'location': {'lat': 35.604056, 'lon': 139.716339}}
{'name': '西大井四丁目特定児童遊園', 'address': '東京都品川区西大井4-23-12', 'note': '品川区西大井4-23-12', 'location': {'lat': 35.592907, 'lon': 139.719381}}
{'name': '南大井四丁目特定児童遊園', 'address': '東京都品川区南大井4-6', 'note': '品川区南大井4-6-20', 'location': {'lat': 35.596968, 'lon': 139.736822}}
{'name': '源氏前特定児童遊園', 'address': '東京都品川区中延6-4', 'note': '品川区中延6-4-8', 'location': {'lat': 35.602272, 'lon': 139.712272}}
{'name': '大井二丁目特定児童遊園', 'address': '東京都品川区大井2-5-15', 'note': '品川区大井2-5-15', 'location': {'lat': 35.604237, 'lon': 139.727564}}
{'name': '西大井六丁目特定児童遊園', 'address': '東京都品川区西大井6-5-5', 'note': '西大井6-5-5', 'location': {'lat': 35.60112, 'lon': 139.718687}}
{'name': '西大井三丁目特定児童遊園', 'address': '東京都品川区西大井3-4-14', 'note': '品川区西大井3-4-14', 'location': {'lat': 35.596942, 'lon': 139.723904}}
{'name': '豊四中央防災広場', 'address': '東京都品川区豊町4-17-1', 'note': '品川区豊町4-17-1', 'location': {'lat': 35.606713, 'lon': 139.720686}}
{'name': 'しながわ花海道水辺広場', 'address': '東京都品川区東大井1-13', 'note': '品川区東大井1-13先外勝島運河周辺の防潮堤上部。', 'location': {'lat': 35.598598, 'lon': 139.741246}}
{'name': '上蛇広場', 'address': '東京都品川区二葉4-3-10', 'note': '二葉4-3-10', 'location': {'lat': 35.602658, 'lon': 139.714435}}
{'name': 'ゆたか防災広場', 'address': '東京都品川区豊町6-11-1', 'note': '豊町6-11-1', 'location': {'lat': 35.605595, 'lon': 139.717657}}
{'name': '西大井六丁目ふれあい広場', 'address': '東京都品川区西大井6-3-1', 'note': '西大井6-3-1', 'location': {'lat': 35.601176, 'lon': 139.716261}}
{'name': '中延みちしるべ防災広場', 'address': '東京都品川区中延5-12-11', 'note': '住所\u3000中延5-12-11', 'location': {'lat': 35.601184, 'lon': 139.70821}}
{'name': '二葉中央のんき通り広場', 'address': '東京都品川区二葉3-17-14', 'note': '住所\u3000二葉3-17-14', 'location': {'lat': 35.604129, 'lon': 139.721404}}

ちなみに条件を3.5キロにすると以下のような結果になり、きちんと空間検索の条件がきいていることがわかります。

{'name': 'しながわ水族館', 'address': '東京都品川区勝島3-2-1しながわ区民公園内', 'note': '品川区勝島3-2-1しながわ区民公園内電話:3762-3431 交通: JR大井町駅から無料バス開館時間: 午前10時〜午後5時(入館は午後4時30分まで) 休館日: 毎週火曜日、1月1日 *春休み、ゴールデンウィーク、夏休みは営業', 'location': {'lat': 35.588634, 'lon': 139.737364}}
{'name': '大井海岸公園', 'address': '東京都品川区南大井3-27-5', 'note': '品川区南大井3-27-5', 'location': {'lat': 35.588463, 'lon': 139.733647}}
{'name': '大井坂下公園', 'address': '東京都品川区南大井6-23-11', 'note': '品川区南大井6-23-11', 'location': {'lat': 35.589516, 'lon': 139.730786}}
{'name': '西大井四丁目特定児童遊園', 'address': '東京都品川区西大井4-23-12', 'note': '品川区西大井4-23-12', 'location': {'lat': 35.592907, 'lon': 139.719381}}

さいごに

いかがでしたでしょうか?Elasticsearch を使えば 位置情報データの可視化や検索などが簡単にできます。Elasticsearch は年々人気が増しているサービスなので、今後のアップデートに関しても注目ですね。本日は以上です。

FIFA 20 のプレイヤーをクラスタリングしてみよう!

さて、本日は機械学習のクラスタリングという手法を使ってFIFA 20 のプレイヤーをグルーピングしてみようと思います。

FIFA20 とは

『FIFA 20』は、EAスポーツが開発し、エレクトロニック・アーツより2019年9月27日に世界同時発売されたサッカーゲーム。(出典:Wikipedia)

使用するデータデータ

Kaggle の「FIFA 20 complete player dataset」にある「players_20.csv」を使用します。

www.kaggle.com

Kaggle とは

Kaggleは企業や研究者がデータを投稿し、世界中の統計家やデータ分析家がその最適モデルを競い合う、予測モデリング及び分析手法関連プラットフォーム及びその運営会社である。 (出典:Wikipedia)

クラスタリングとは

(統計学)データ解析手法の1つ。「クラスタ解析」、「クラスター分析」とも。機械学習やデータマイニング、パターン認識、イメージ解析やバイオインフォマティックスなど多くの分野で用いられる(データ・クラスタリングを参照)。クラスタリングではデータの集合を部分集合(クラスタ)に切り分けて、それぞれの部分集合に含まれるデータが(理想的には)ある共通の特徴を持つようにする。この特徴は多くの場合、類似性や、ある定められた距離尺度に基づく近さで示される。(出典:Wikipedia)

使用するクラスタリング手法

k-meansクラスタリングを使用します。

関連エントリー

機械学習関連のエントリーです。興味のある方はぜひ読んでみてください。

www.gis-py.com

www.gis-py.com

www.gis-py.com

環境

Windows10 64bit
Python3.8.5

手順

  1. データ分析
  2. クラスタリング
  3. 可視化

1.データ分析

データ読込

プレイヤーのデータを読み込みます。

import pandas as pd
data = pd.read_csv(r"players_20.csv")
data.head()

なんと104カラムもありますね。

f:id:sanvarie:20210725054512p:plain

データ探索

カラムの型や基本統計量などを確認します。

data.shape

data.info()

data.describe()

f:id:sanvarie:20210725055033p:plain

カラム選定

クラスタリングを行うにあたって必要なカラムを選択します。また、フィールドプレイヤーとゴールキーパーによって使用するカラムを分けます。

################################################
# フィールドプレイヤーの分析に必要なカラムをセット #
################################################

data_field_players = data[data.player_positions != "GK"]
field_players_features = ['short_name','overall','potential', 'skill_moves',
                          'pace','shooting','passing','dribbling','defending','physic']

# 名前ありデータフレーム
data_field_players = data_field_players[field_players_features]                       

# 名前なしデータフレーム
data_field_players_drop = data_field_players.drop(columns = ['short_name'])

################################################
# ゴールキーパーの分析に必要なカラムをセット       #
################################################
data_goal_keepers = data[data.player_positions == "GK"]
goal_keepers_features = ['short_name','overall','potential', 'skill_moves',
                         'gk_diving','gk_handling','gk_kicking','gk_reflexes','gk_speed','gk_positioning']

# 名前ありデータフレーム
data_goal_keepers = data_goal_keepers[goal_keepers_features]

# 名前なしデータフレーム
data_goal_keepers_drop = data_goal_keepers.drop(columns = ['short_name'])

2.クラスタリング

まずはスケーリングをします。

スケーリング(フィールドプレイヤー)
# スケーリング(フィールドプレイヤー)
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
scaler.fit(data_field_players_drop)

data_field_players_drop_scaled = scaler.transform(data_field_players_drop)

field_players_df = pd.DataFrame(data_field_players_drop_scaled)

field_players_features.remove('short_name')

field_players_df.columns = field_players_features
field_players_df

f:id:sanvarie:20210725084637p:plain

スケーリング(ゴールキーパー)
# スケーリング(ゴールキーパー)
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
scaler.fit(data_goal_keepers_drop)

data_goal_keepers_drop_scaled = scaler.transform(data_goal_keepers_drop)

goal_keepers_df = pd.DataFrame(data_goal_keepers_drop_scaled)

goal_keepers_features.remove('short_name')

goal_keepers_df.columns = field_players_features
goal_keepers_df

f:id:sanvarie:20210725084703p:plain

エルボー法(フィールドプレイヤー)

エルボー法で最適なクラスター数を検討します。

import matplotlib.pyplot as plt
distortions = []
for i  in range(1,11):                
    km = KMeans(n_clusters=i,
                init='k-means++',     
                n_init=10,
                max_iter=16242,
                random_state=0)
    km.fit(field_players_df)                         
    distortions.append(km.inertia_)   

plt.plot(range(1,11),distortions,marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('SSE')
plt.show()

「クラスター数3が最適なクラスター数」と判断します。

f:id:sanvarie:20210725084352p:plain

エルボー法(ゴールキーパー)
import matplotlib.pyplot as plt
distortions = []
for i  in range(1,11):                
    km = KMeans(n_clusters=i,
                init='k-means++',     
                n_init=10,
                max_iter=2036,
                random_state=0)
    km.fit(data_goal_keepers_drop)                         
    distortions.append(km.inertia_)   

plt.plot(range(1,11),distortions,marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.show()

こちらも「クラスター数3が最適なクラスター数」と判断します。

f:id:sanvarie:20210725084413p:plain

さて、いよいよクラスタリングですね。

クラスタリング(フィールドプレイヤー)
from sklearn.cluster import KMeans

kmeans = KMeans(n_clusters = 3)
kmeans.fit(field_players_df)
result = kmeans.predict(field_players_df)

# 名前ありデータフレームにクラスタリングの結果を格納
data_field_players['cluster_id'] = result

# 名前なし、かつ、スケーリング済みのデータフレームにクラスタリングの結果を格納
field_players_df['cluster_id'] = result

field_players_df.head()

f:id:sanvarie:20210725065946p:plain

クラスタリング(ゴールキーパー)
from sklearn.cluster import KMeans

kmeans = KMeans(n_clusters = 3)
kmeans.fit(goal_keepers_df)
result = kmeans.predict(goal_keepers_df)

# 名前ありデータフレームにクラスタリングの結果を格納
data_goal_keepers['cluster_id'] = result

# 名前なし、かつ、スケーリング済みのデータフレームにクラスタリングの結果を格納
goal_keepers_df['cluster_id'] = result

goal_keepers_df.head()

f:id:sanvarie:20210725093222p:plain

3.可視化

クラスタリングの結果を可視化します。

フィールドプレイヤー
# 可視化
import matplotlib.pyplot as plt

clusterinfo = pd.DataFrame()
for i in range(3):
    clusterinfo['cluster' + str(i)] = field_players_df[field_players_df['cluster_id'] == i].mean()
clusterinfo = clusterinfo.drop('cluster_id')
 
my_plot = clusterinfo.T.plot(kind='bar', stacked=True, title="Mean Value of 3 Clusters")
my_plot.set_xticklabels(my_plot.xaxis.get_majorticklabels(), rotation=0)
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0, fontsize=18)

結果が出ました。以下のような傾向があることがわかります。

  • cluster = 0 のプレイヤー:全体的に能力が高い。スタープレイヤーと考えられる。
  • cluster = 1 のプレイヤー:スピードとドリブル能力が高い。ウイングのようなタイプと考えられる。
  • cluster = 2 のプレイヤー:フィジカルとディフェンス能力が高い。ディフェンダーと考えられる。

f:id:sanvarie:20210725090022p:plain

それぞれのクラスターに属する選手です。cluster = 0 はメッシやロナウドなどが所属しており、スタープレイヤーのグループという推測はあってそうですね。

f:id:sanvarie:20210725090755p:plain

f:id:sanvarie:20210725090811p:plain

f:id:sanvarie:20210725090827p:plain

ゴールキーパー
# 可視化
import matplotlib.pyplot as plt

clusterinfo = pd.DataFrame()
for i in range(3):
    clusterinfo['cluster' + str(i)] = goal_keepers_df[goal_keepers_df['cluster_id'] == i].mean()
clusterinfo = clusterinfo.drop('cluster_id')
 
my_plot = clusterinfo.T.plot(kind='bar', stacked=True, title="Mean Value of 3 Clusters")
my_plot.set_xticklabels(my_plot.xaxis.get_majorticklabels(), rotation=0)
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0, fontsize=18)

結果が出ました。以下のような傾向があることがわかります。

  • cluster = 0 のプレイヤー:全体的に能力が低い。若手やレベルの低いリーグのキーパーと考えられる。
  • cluster = 1 のプレイヤー:全体的に能力が中くらい。中堅レベルのキーパーと考えられる。
  • cluster = 2 のプレイヤー:全体的に能力が高い。スタープレイヤーと考えられる。

f:id:sanvarie:20210725091224p:plain

それぞれのクラスターに属する選手です。知っている選手がいないのですが、能力を見る限り推測は正しそうな気がします。

f:id:sanvarie:20210725091419p:plain

f:id:sanvarie:20210725091440p:plain

f:id:sanvarie:20210725091456p:plain

さいごに

いかがでしたでしょうか?クラスタリングを使用するとデータセットの中から類似データごとにグループ分けをしてくれます。とても便利ですね。大量のデータの中から何かしらの法則を見つける方法はクラスタリング以外にもありますので、今後はそういったものも紹介していこうと思います。本日は以上です。

Amazon Location Service を使ってみよう!

さて、本日は Amazon Location Service を使用してみようと思います。とうとう AWS が GIS の世界に進出してきましたね。サービス開始時からどんな感じかずっと気になっていたので今回紹介したいと思います。

Amazon Location Service とは

Amazon Location Service とは、デベロッパーがマップ、特定のポイント (POI)、ジオコーディング、ルーティング、トラッキング、ジオフェンシングなどの位置情報機能を、データセキュリティ、ユーザーのプライバシー、データ品質、コストを犠牲にすることなくアプリケーションに簡単に追加できるフルマネージドサービスです。(出典:AWS)

機能

  • Maps・・・開発するアプリにマップを表示したり、マップにデータを追加したりすることができます。
  • Place indexes・・・場所検索、ジオコーディング、逆ジオコーディングなどができます。
  • Route calculators・・・ルート検索ができます。
  • Geofence collections・・・ジオフェンスを使用することができます。
  • Trackers・・・デバイスのトラッキングなどができます。

f:id:sanvarie:20210723213418p:plain

今回試してみること

Amazon Location Service で作成したマップをブラウザ上で表示してみようと思います。

手順

  1. マップの作成
  2. AWS Cognito の設定
  3. マップの表示

1.マップの作成

Esri と HERE のマップが使えるみたいです。今回は Esri Light を使用してみます。

f:id:sanvarie:20210723213935p:plain

f:id:sanvarie:20210723214007p:plain

マップを作成するとコンソール上でマップを使うことができるようになります。

f:id:sanvarie:20210723214225p:plain

2.AWS Cognito の設定

マップの表示には Cognito Identity Pool ID というものをコード内にセットする必要があります。以下のようなサイトを参照し設定をしてください。

qiita.com

qiita.com

3.マップの表示

マップの表示をするためのサンプルコードです。コード内の以下の変数にそれぞれ値を設定してください。

  • identityPoolId ・・・AWS Cognito を設定し取得してください。ap-northeast-1:--***** のような形式です。
  • mapName ・・・作成したマップ名をセットしてください。
<!-- index.html -->
<html>
  <head>
    <link
      href="https://unpkg.com/maplibre-gl@1.14.0/dist/maplibre-gl.css"
      rel="stylesheet"
    />
    <style>
      body {
        margin: 0;
      }
 
      #map {
        height: 100vh;
      }
    </style>
  </head>
 
  <body>
    <!-- map container -->
    <div id="map" />
    <!-- JavaScript dependencies -->
    <script src="https://unpkg.com/maplibre-gl@1.14.0/dist/maplibre-gl.js"></script>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.784.0.min.js"></script>
    <script src="https://unpkg.com/@aws-amplify/core@3.7.0/dist/aws-amplify-core.min.js"></script>
    <script>
      // use Signer from @aws-amplify/core
      const { Signer } = window.aws_amplify_core;
 
      // configuration
      const identityPoolId = "";
      const mapName = ""; // Amazon Location Service Map Name
 
      // extract the region from the Identity Pool ID
      AWS.config.region = identityPoolId.split(":")[0];
 
      // instantiate a Cognito-backed credential provider
      const credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: identityPoolId,
      });
 
      /**
       * Sign requests made by MapLibre GL JS using AWS SigV4.
       */
      function transformRequest(url, resourceType) {
        if (resourceType === "Style" && !url.includes("://")) {
          // resolve to an AWS URL
          url = `https://maps.geo.${AWS.config.region}.amazonaws.com/maps/v0/maps/${url}/style-descriptor`;
        }
 
        if (url.includes("amazonaws.com")) {
          // only sign AWS requests (with the signature as part of the query string)
          return {
            url: Signer.signUrl(url, {
              access_key: credentials.accessKeyId,
              secret_key: credentials.secretAccessKey,
              session_token: credentials.sessionToken,
            }),
          };
        }
 
        // don't sign
        return { url };
      }
 
      /**
       * Initialize a map.
       */
      async function initializeMap() {
        // load credentials and set them up to refresh
        await credentials.getPromise();
 
        // Initialize the map
        const map = new maplibregl.Map({
          container: "map",
          center: [139.716073, 35.562479], // initial map centerpoint
          zoom: 17, // initial map zoom
          style: mapName,
          transformRequest,
        });
 
        map.addControl(new maplibregl.NavigationControl(), "top-left");
      }
 
      initializeMap();
    </script>
  </body>
</html>

上手くマップが表示されました!初期起動時の場所は蒲田駅にしてみました。

f:id:sanvarie:20210723215007p:plain

さいごに

まだサービスが公開されてから日が浅いので機能はこれからどんどん充実してくるのではないかと思います。また、料金も他のサービスと比べると安いですしアプリ内でちょっと地図を使うくらいだったら Amazon Location Service がファーストチョイスになる日も近いかもしれませんね。次回以降のエントリーで Amazon Location Service を使用したジオコーディングの仕方なども紹介してみようと思います。本日は以上です。

Amazon Comprehend と Python で自然言語処理をしてみよう!

さて、本日は自然言語処理について書いてみようと思います。様々な自然言語処理サービスがありますが、今回は Amazon Comprehend を使ってみようと思います。このサービスを使うと簡単にテキストの感情分析などができるようになります。

Amazon Comprehendとは

Amazon Comprehend は、機械学習を使用して構造化されていないデータから情報を見つける自然言語処理 (NLP) サービスです。ドキュメントを詳細に調べる必要がなく、プロセスが簡単で、隠れている情報を容易に把握できます。(出典:AWS)

主な特徴

Amazon Comprehend には以下のような特徴があります。

キーフレーズ抽出

キーフレーズ抽出 API は、キーフレーズまたは会話のポイント、およびそれがキーフレーズであることを裏付ける信頼性スコアを返します。

感情分析

感情分析 API は、テキストの全体的な感情 (肯定的、否定的、中立的、または混在) を返します。

構文解析

Amazon Comprehend Syntax API を使用すれば、お客様は、トークン分割や品詞 (PoS) を使用してテキストを分析したり、テキスト内の名詞や形容詞などの単語境界やラベルを識別したりできます。

※2021/7/22で日本語未対応のため、本エントリーでは取り上げません。早く日本語対応になってほしいですね。

エンティティ認識

エンティティ認識 API は、提供されたテキストに基づいて自動的に分類される、名前付きエンティティ (「人」、「場所」、「位置」など) を返します。

言語検出

言語検出 API は、100 を超える言語で書かれたテキストを自動的に識別し、主要言語と、言語が主要であることを裏付ける信頼性スコアを返します。

関連エントリー

自然言語処理に関するエントリーです。興味のある方はぜひ読んでみてください。

www.gis-py.com

www.gis-py.com

使用するテキスト

富士通さんのTwitterアカウントのつぶやきを使用します。なぜ富士通さんかは秘密です。

f:id:sanvarie:20210722185101p:plain

テキスト内容

「明日は海の日ですね🌊⛵離れた場所から水中を体験することができる 水族館と教室を繋いだ #遠隔教育 をご紹介します✨5Gによる映像伝送や水中ドローンにVR技術などを活用し、校外学習に行かなくても子供たちはリアルタイムで #美ら海水族館 のサメを観察しました🙋」

環境

Windows10 64bit
Python3.6.6

サンプルコード

キーフレーズ抽出

import boto3

comprehend = boto3.client(service_name='comprehend', 
                                             region_name='ap-northeast-1',
                                             aws_access_key_id="***",
                                             aws_secret_access_key="***")

text = """明日は海の日ですね🌊⛵離れた場所から水中を体験することができる 水族館と教室を繋いだ 
          #遠隔教育 をご紹介します✨5Gによる映像伝送や水中ドローンにVR技術などを活用し、
          校外学習に行かなくても子供たちはリアルタイムで #美ら海水族館 のサメを観察しました🙋"""

result = comprehend.detect_key_phrases(Text = text, LanguageCode='ja')
for r in result["KeyPhrases"]:
    print(r)

このような結果になりました。スコアの高いキーフレーズを見ると確かにと思える結果ですね。

f:id:sanvarie:20210722190313p:plain

感情分析

import boto3

comprehend = boto3.client(service_name='comprehend', 
                                             region_name='ap-northeast-1',
                                             aws_access_key_id="***",
                                             aws_secret_access_key="***")

text = """明日は海の日ですね🌊⛵離れた場所から水中を体験することができる 水族館と教室を繋いだ 
          #遠隔教育 をご紹介します✨5Gによる映像伝送や水中ドローンにVR技術などを活用し、
          校外学習に行かなくても子供たちはリアルタイムで #美ら海水族館 のサメを観察しました🙋"""
 
result = comprehend.detect_sentiment(Text=text, LanguageCode='ja'),
for k, v in comprehend_result["SentimentScore"].items():
    print(f"    {k}: {v}")

今回選んだテキストは中立的な感情という結果になりました。

f:id:sanvarie:20210722190701p:plain

エンティティ認識

import boto3

comprehend = boto3.client(service_name='comprehend', 
                                             region_name='ap-northeast-1',
                                             aws_access_key_id="***",
                                             aws_secret_access_key="***")

text = """明日は海の日ですね🌊⛵離れた場所から水中を体験することができる 水族館と教室を繋いだ 
          #遠隔教育 をご紹介します✨5Gによる映像伝送や水中ドローンにVR技術などを活用し、
          校外学習に行かなくても子供たちはリアルタイムで #美ら海水族館 のサメを観察しました🙋"""

result = comprehend.detect_entities(Text=text, LanguageCode='ja')
for r in result["Entities"]:
    print(r)

以下のような単語た抽出されました。「ドローン」などもう少し多く抽出してほしいですね。

f:id:sanvarie:20210722204005p:plain

言語検出

import boto3

comprehend = boto3.client(service_name='comprehend', 
                                             region_name='ap-northeast-1',
                                             aws_access_key_id="***",
                                             aws_secret_access_key="***")

text = """明日は海の日ですね🌊⛵離れた場所から水中を体験することができる 水族館と教室を繋いだ 
          #遠隔教育 をご紹介します✨5Gによる映像伝送や水中ドローンにVR技術などを活用し、
          校外学習に行かなくても子供たちはリアルタイムで #美ら海水族館 のサメを観察しました🙋"""

result = comprehend.detect_dominant_language(Text = text)
print(result['Languages'])

日本語と判定されました。しかし、スコアは思ったよりも高くないですね。英文とかが混じった日本語とかの判断は難しそうですね。

f:id:sanvarie:20210722185330p:plain

さいごに

自分で自然言語処理などを開発しようとするとかなり大変だと思いますが、Amazon Comprehend を使えばそんな苦労をすることもなく簡単に実行できてしまいます。また、おそらく精度も相当高いと思われるのでテキストの感情分析などをしたいという案件が来た場合は開発するよりも Amazon Comprehend のようなサービスを使用するのがベターだと思います。本日は以上です。

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

さて、本日は 「SUUMO の中古物件情報を Tableau で分析してみる ~データ予測編~」です。データ収集編で SUUMO の情報をスクレイピング、データ分析編でその情報の分析をしましたが、今回は機械学習を使って販売価格の予測を行ってみようと思います。

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

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

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

ランダムフォレストを使用します。以下エントリーでもランダムフォレストを使用していますが、この時は分類で使用しました。今回は回帰で使用します。

www.gis-py.com

関連エントリー

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

www.gis-py.com

www.gis-py.com

使用するデータ

データ を加工

処理の都合上、以下加工を行います。

  • ID 列追加
  • 販売価格列を一番最後に移動
  • ㎡ をを空白に置換(データ収集時に取り切れてなかったものがあったようです・・・)
  • 徒歩(分)、バス(分)が空白なレコードを削除
  • バルコニーが空白な場合、0に置換

f:id:sanvarie:20210522095137p:plain

フィールド

販売価格以外を学習に使用します。

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

予測するフィールド

販売価格を予測して、実際のデータと比較してみようと思います。

  • 販売価格

手順

  1. データ理解
  2. モデルの作成
  3. モデルの評価

環境

Windows10 64bit
Python3.8.5
Tableau Desktop Public Edition 2021.1.0

1.データ理解

販売価格を一億円以下に絞って確認します(その方がわかりやすいので)。

区ごとの販売価格分布

中古マンション

港北区の販売価格分布がやや高いことがわかります。

f:id:sanvarie:20210523075642p:plain

中古一戸建て

都筑区の販売価格分布が高いことがわかります。

f:id:sanvarie:20210523075749p:plain

建物面積と価格の散布図

中古マンション

あまり相関関係がないですね。 f:id:sanvarie:20210523075859p:plain

中古一戸建て

こちらもあまり相関関係がないですね。 f:id:sanvarie:20210523075953p:plain

築年数と価格の散布図

中古マンション

やや負の相関がありますね。 f:id:sanvarie:20210523080106p:plain

中古一戸建て

こちらはほとんど相関がないことがわかります。単純に築年数が高いと価格が安くなるとは言えないですね。

f:id:sanvarie:20210523080157p:plain

2.モデルの作成

以下のようにモデルを作成しました。

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

# データ読込
df = pd.read_csv(r"D:\data\csv\property.csv")

df = df.astype({'建物面積': 'float64'})

# 出力変数
y = df["販売価格"]

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

# ダミー変数化
x = pd.get_dummies(x)
y = df["販売価格"]

x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=50)

# モデル作成
rfr = RandomForestRegressor(n_estimators=100)
model = rfr.fit(x_train, y_train)

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

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

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

f:id:sanvarie:20210523081050p:plain

予測値の計算

作成したモデルを使用して価格を推論します。

predict = model.predict(x)
predict = pd.DataFrame(predict, columns=["predict"])
predict.head()

このように価格が推論されました。

f:id:sanvarie:20210523083005p:plain

CSVを読み込んだデータフレームの右端に推論結果を追加します。

results = pd.concat([df,predict],axis=1)
results.head()

この結果をCSVで出力してTableau で確認します。

f:id:sanvarie:20210523083236p:plain

3.モデルの評価

予測値と実際の販売価格を比較して誤差の確認を行います。

「誤差」フィールド作成

f:id:sanvarie:20210523083926p:plain

推論値と実際の価格の散布図

推論値をX軸に、実際の価格をY軸にして散布図を書きます。おおむねY=Xの線上に分布しているようですね。これは推論が的中していることを示します。

中古マンション

f:id:sanvarie:20210523091059p:plain

中古一戸建て

f:id:sanvarie:20210523091142p:plain

区別誤差の分布

中古マンション

誤差の分布はかなり狭いですね。

f:id:sanvarie:20210523084319p:plain

しかし、一部のデータで大きく誤差が出ているものもあります。

f:id:sanvarie:20210523085009p:plain

f:id:sanvarie:20210523085055p:plain

中古一戸建て

こちらも誤差の分布はかなり狭く、中古マンションと同じように一部のデータで大きく誤差が出ていました。

f:id:sanvarie:20210523084841p:plain

沿線別誤差の分布

中古マンション

誤差の分布はかなり狭いですね。

f:id:sanvarie:20210523085722p:plain

中古一戸建て

こちらはみなとみらい線にやや大きな分布がみられますね。

f:id:sanvarie:20210523085759p:plain

さいごに

本シリーズ最後のエントリーでしたが、いかがでしたでしょうか。一部のデータを除き実際の価格と予測値の誤差はかなり小さい結果になりました(それでも数十万円~300万円くらいの誤差が出ているものも多かったですが)。学習に使用するデータをもう少し精査することによってより精度の高い結果が出るかと思いますが、その辺は色々勉強していきたいと思います。本日は以上です。