はじめに
本記事は国土交通データプラットフォームアドベントカレンダー13日目の記事です。
みなさん こんにちは!
先日、原宿や浅草を訪れたのですが訪日外国人の方々が多くて驚きました。
着実にインバウンドは回復してますね!
日本政府観光局(JNTO)のウェブサイトに掲載されているグラフを見ると今年の9月以降はコロナ禍前の水準に戻りそうです。
そうなると気になるのがオーバーツーリズム。
これから年末年始を迎えるにあたり帰省や旅行で混雑を避けたいと考える方も多いのではないでしょうか。
そこで公開されているデータを活用して訪日外国人の動きを分析したいと思います。
訪日外国人のデータについては前述の日本政府観光局が公開しているものの他に
RESAS 地域経済分析システムでも以下の情報をAPIで取得することができます。
- 指定地域への国籍別訪問者数
- 指定国籍からの訪問者数
- 入出国空港間で訪問した地域の訪問者数
- 入国空港・出国空港内訳
- 滞在地域内訳
また国土交通省ではFF-Data(Flow of Foreigners-Data:訪日外国人流動データ)として訪日外国人の都道府県間流動表を作成・公表を行っています。
FF-Dataは国土交通データプラットフォームにも一部連携されており、交通機関別の国内訪問地間の流動量をAPIでデータを取得できることができます。
今回はこのFF-DataとPythonを使って訪日外国人の動きを分析します。
取得できるデータの確認
まずどのような情報が取得できるか確認するため、GraphiQLでdataCatalog
APIを実行します。(QraphiQLって?という方は6日目の記事API紹介①APIって使ったことある? #1をご確認ください!)
カタログIDにffd
を指定し次のリクエストを送信します。
query {
dataCatalog(IDs: "ffd") {
id
title
datasets{
id
title
data_count
datatype_desc {
name
attributes {
name
display_name
description
}
}
}
}
}
上記のリクエストを送信すると次のような結果が返ってきます。
{
"data": {
"dataCatalog": [
{
"id": "ffd",
"title": "FF-Data(訪日外国人流動データ)",
"datasets": [
{
"id": "ffd_2014",
"title": "2014年",
"data_count": "2304",
"datatype_desc": {
"name": "2014年メタデータ情報",
"attributes": [
{
"name": "FFD:departure_point",
"display_name": "出発地",
"description": null
},
{
"name": "FFD:destination_point",
"display_name": "目的地",
"description": null
},
{
"name": "FFD:all",
"display_name": "全機関【千人/年】",
"description": null
},
{
"name": "FFD:bus",
"display_name": "バス【千人/年】",
"description": null
},
{
"name": "FFD:railway",
"display_name": "鉄道【千人/年】",
"description": null
},
{
"name": "FFD:taxi_n_chauffeur",
"display_name": "タクシー・ハイヤー【千人/年】",
"description": null
},
{
"name": "FFD:rental_car",
"display_name": "レンタカー【千人/年】",
"description": null
},
{
"name": "FFD:other_car",
"display_name": "その他の乗用車【千人/年】",
"description": null
},
{
"name": "FFD:domestic_flight",
"display_name": "国内線飛行機【千人/年】",
"description": null
},
{
"name": "FFD:other_transportation",
"display_name": "その他【千人/年】",
"description": null
},
{
"name": "FFD:transportation_unknown",
"display_name": "不明【千人/年】",
"description": null
},
{
"name": "DPF:title",
"display_name": "タイトル",
"description": null
},
{
"name": "DPF:prefecture_code",
"display_name": "都道府県コード",
"description": null
},
{
"name": "DPF:longitude",
"display_name": "経度",
"description": null
},
{
"name": "DPF:latitude",
"display_name": "緯度",
"description": null
},
{
"name": "DPF:completion_datetime",
"display_name": "完成日時",
"description": null
},
{
"name": "DPF:completion_datetime_originalform",
"display_name": "完成日時元フォーマット",
"description": null
},
{
"name": "DPF:year",
"display_name": "年度",
"description": null
},
{
"name": "DPF:year_originalform",
"display_name": "年度元フォーマット",
"description": null
},
{
"name": "DPF:catalog_id",
"display_name": "カタログ",
"description": null
},
{
"name": "DPF:dataset_id",
"display_name": "データセット",
"description": null
},
{
"name": "DPF:prefecture_name",
"display_name": "都道府県",
"description": null
},
{
"name": "DPF:municipality_name",
"display_name": "市区町村",
"description": null
},
{
"name": "DPF:dpf_update_date",
"display_name": "連携更新日",
"description": null
}
]
}
},
/// 略 ///
]
}
]
}
}
年度ごとに出発地・目的地間の交通手段別流動量を取得できることが分かりました。
ではPythonを用いてデータの取得・集計を行っていきます。
ライブラリの読み込み
まず必要なライブラリを読み込みます。
import requests
import json
import pandas as pd
from typing import Union
データを取得する関数の作成
次に国土交通データプラットフォームにリクエストを送信する関数を定義します。
リクエストボディに設定するGraphQLを引数で受け取り、結果を辞書型で返します。
def send_request(query: str) -> dict:
"""_summary_
Args:
query (str): QraphQL query string
Returns:
dict: MLIT DPF API response
"""
END_POINT: str = "https://www.mlit-data.jp/api/v1/"
API_KEY: str = "enter your api ke"
response: requests.models.Response = requests.post(
END_POINT,
headers={
"Content-type": "application/json",
"apikey": API_KEY,
},
json={"query": query},
)
result: dict = json.loads(response.content)["data"]
return result
最後にFF-Dataを取得する関数を定義します。
FF-DataのデータセットIDはffd_2014~ffd_2019のため年度を引数で受け取り、結果をデータフレームで返します。
国土交通データプラットフォームのsearchAPIはDataClass
を返すのですがJSONObjectとして定義されているmetadata
フィールドについては取得するsubfields
を指定できないようです。(attributeFilter
の条件としては使用できるのに・・・)
そのためmetadata
フィールドのうち必要な項目のみ返すようにしています。
def get_ffdata(year: Union[str, int]) -> pd.DataFrame:
"""_summary_
Args:
year (str, int): Fiscal Year
Returns:
pd.DataFrame: FF-Data DataFrame
"""
graphql_query: str = """
query {
search(
term:"",
attributeFilter:{
attributeName: "DPF:dataset_id"
is: "ffd_%d"
}
first:0,
size:3000,
) {
totalNumber
searchResults {
id
metadata
shape
}
}
}
""" % (
year
)
data: dict = send_request(query=graphql_query)
df: pd.DataFrame = pd.json_normalize(data["search"]["searchResults"])
selected_columns: list = [
"id",
"shape.coordinates",
"metadata.DPF:year",
"metadata.FFD:departure_point",
"metadata.FFD:destination_point",
"metadata.FFD:bus",
"metadata.FFD:railway",
"metadata.FFD:taxi_n_chauffeur",
"metadata.FFD:rental_car",
"metadata.FFD:other_car",
"metadata.FFD:domestic_flight",
"metadata.FFD:other_transportation",
"metadata.FFD:transportation_unknown",
]
return df[selected_columns]
データ取得
データを取得する準備ができたので2017~2019年度のデータを取得します。
ff_data: pd.DataFrame = pd.concat(
map(get_ffdata, [2017, 2018, 2019]), ignore_index=True
)
ff_data.head()
id | shape.coordinates | metadata.DPF:year | metadata.FFD:departure_point | metadata.FFD:destination_point | metadata.FFD:bus | metadata.FFD:railway | metadata.FFD:taxi_n_chauffeur | metadata.FFD:rental_car | metadata.FFD:other_car | metadata.FFD:domestic_flight | metadata.FFD:other_transportation | metadata.FFD:transportation_unknown | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0ce25d45-3efe-4419-9b5d-78b37a19e2b6 | [[141.34694444444446, 43.06444444444444], [139.8836111111111, 36.56583333333333]] | 2016 | 北海道 | 栃木 | 0 | 1.812 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | b652b574-58ba-4f40-b874-35abf1b343b8 | [[141.34694444444446, 43.06444444444444], [133.935, 34.66166666666666]] | 2016 | 北海道 | 岡山 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 6a16865c-dd09-4a40-bfa4-26a7f9f2313e | [[141.34694444444446, 43.06444444444444], [130.29916666666668, 33.24944444444444]] | 2016 | 北海道 | 佐賀 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | d8269d74-0fab-4666-8814-b804466b1875 | [[141.34694444444446, 43.06444444444444], [135.86833333333334, 35.004444444444445]] | 2016 | 北海道 | 滋賀 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | bccff60a-8538-4978-aab5-165d3e753e44 | [[141.34694444444446, 43.06444444444444], [136.62555555555556, 36.594722222222224]] | 2016 | 北海道 | 石川 | 0 | 0.995 | 0 | 0 | 0 | 0 | 0 | 0 |
無事にデータを取得することができました!
列名が長くて使いにくいのでリネームします。
renamed_data = ff_data.rename(
columns={
"shape.coordinates": "geometry",
"metadata.DPF:year": "year",
"metadata.FFD:departure_point": "dept",
"metadata.FFD:destination_point": "dest",
"metadata.FFD:bus": "bus",
"metadata.FFD:railway": "railway",
"metadata.FFD:taxi_n_chauffeur": "taxi",
"metadata.FFD:rental_car": "rental_car",
"metadata.FFD:other_car": "other_car",
"metadata.FFD:domestic_flight": "domestic_flight",
"metadata.FFD:other_transportation": "other",
"metadata.FFD:transportation_unknown": "unknown",
}
)
renamed_data.head()
id | geometry | year | dept | dest | bus | railway | taxi | rental_car | other_car | domestic_flight | other | unknown | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0ce25d45-3efe-4419-9b5d-78b37a19e2b6 | [[141.34694444444446, 43.06444444444444], [139.8836111111111, 36.56583333333333]] | 2016 | 北海道 | 栃木 | 0 | 1.812 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | b652b574-58ba-4f40-b874-35abf1b343b8 | [[141.34694444444446, 43.06444444444444], [133.935, 34.66166666666666]] | 2016 | 北海道 | 岡山 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 6a16865c-dd09-4a40-bfa4-26a7f9f2313e | [[141.34694444444446, 43.06444444444444], [130.29916666666668, 33.24944444444444]] | 2016 | 北海道 | 佐賀 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | d8269d74-0fab-4666-8814-b804466b1875 | [[141.34694444444446, 43.06444444444444], [135.86833333333334, 35.004444444444445]] | 2016 | 北海道 | 滋賀 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | bccff60a-8538-4978-aab5-165d3e753e44 | [[141.34694444444446, 43.06444444444444], [136.62555555555556, 36.594722222222224]] | 2016 | 北海道 | 石川 | 0 | 0.995 | 0 | 0 | 0 | 0 | 0 | 0 |
交通手段が横持ちのため集計しずらいため縦持ちに変換します。
longer_data = pd.melt(
renamed_data,
id_vars=["id", "geometry", "year", "dept", "dest"],
value_vars=[
"bus",
"railway",
"taxi",
"rental_car",
"other_car",
"domestic_flight",
"other",
"unknown",
],
var_name="mode",
value_name="n",
)
id | geometry | year | dept | dest | mode | n | |
---|---|---|---|---|---|---|---|
0 | 0ce25d45-3efe-4419-9b5d-78b37a19e2b6 | [[141.34694444444446, 43.06444444444444], [139.8836111111111, 36.56583333333333]] | 2016 | 北海道 | 栃木 | bus | 0 |
1 | b652b574-58ba-4f40-b874-35abf1b343b8 | [[141.34694444444446, 43.06444444444444], [133.935, 34.66166666666666]] | 2016 | 北海道 | 岡山 | bus | 0 |
2 | 6a16865c-dd09-4a40-bfa4-26a7f9f2313e | [[141.34694444444446, 43.06444444444444], [130.29916666666668, 33.24944444444444]] | 2016 | 北海道 | 佐賀 | bus | 0 |
3 | d8269d74-0fab-4666-8814-b804466b1875 | [[141.34694444444446, 43.06444444444444], [135.86833333333334, 35.004444444444445]] | 2016 | 北海道 | 滋賀 | bus | 0 |
4 | bccff60a-8538-4978-aab5-165d3e753e44 | [[141.34694444444446, 43.06444444444444], [136.62555555555556, 36.594722222222224]] | 2016 | 北海道 | 石川 | bus | 0 |
集計しやすくなりました!
京都の来訪者はどれくらい増えてるの?
私は実家が京都のため、京都を訪れる訪日外国人がどれくらい増えているかが気になります。。。
そのため目的地が京都のデータに絞り込みます。
filtered_data = longer_data[longer_data["dest"] == "京都"]
filtered_data.head()
id | geometry | year | dept | dest | mode | n | |
---|---|---|---|---|---|---|---|
14 | 40a5a037-cc24-4156-9231-0ebd472c4bff | [[141.34694444444446, 43.06444444444444], [135.75555555555556, 35.02111111111111]] | 2016 | 北海道 | 京都 | bus | 0 |
81 | 14dae62e-3649-4b0f-94c8-7f6cccbc1666 | [[140.73999999999998, 40.824444444444445], [135.75555555555556, 35.02111111111111]] | 2016 | 青森 | 京都 | bus | 0 |
128 | 72f2571a-9ea1-497b-82dd-85fde15cfe08 | [[141.15277777777777, 39.703611111111115], [135.75555555555556, 35.02111111111111]] | 2016 | 岩手 | 京都 | bus | 0 |
162 | f0c7dd66-457b-4ca0-ad1c-2a3e4790b527 | [[140.87222222222223, 38.26888888888889], [135.75555555555556, 35.02111111111111]] | 2016 | 宮城 | 京都 | bus | 0 |
208 | a630ae32-d3c8-4ad5-ace1-9c7635270acd | [[140.1025, 39.718611111111116], [135.75555555555556, 35.02111111111111]] | 2016 | 秋田 | 京都 | bus | 0 |
年度毎の来訪者数を集計します。
grouped_data = filtered_data.groupby("year").agg(flow=("n", "sum")).reset_index()
grouped_data
year | flow | |
---|---|---|
0 | 2016 | 5399.22 |
1 | 2017 | 5893.33 |
2 | 2018 | 6760.05 |
グラフにすると次のようになります。
fig = px.bar(grouped_data, x="year", y="flow")
fig.show()
単位は千人なので年間で100万人近く増えてきているようです。コロナ禍前の2018年の水準に戻り、円安を考慮すると今年はもっと増えているかもしれないですね。。。
どこから京都に来ているの?
続いて京都に来訪した外国人が京都に訪れる前にどこにいたか確認します。
grouped_data = filtered_data.groupby(['year', 'dept']).agg(flow=('n', 'sum')).reset_index()
fig = px.treemap(grouped_data, path=['year', 'dept'], values='flow')
fig.show()
一番多いのは大阪、次いで東京、奈良となっていますね。その他、新幹線沿いの都道府県が多いようです。
何で京都に来てるの?
最後に京都に来訪した外国人がどの交通手段で訪れているか確認します。
grouped_data = filtered_data.groupby(["year", "mode"]).agg(flow=("n", "sum")).reset_index()
fig = px.treemap(grouped_data, path=["year", "mode"], values="flow")
fig.show()
鉄道が最も多く、次いでバスといった順位は変わらないようです。鉄道は新幹線、バスは観光バスで移動される方が多いのでしょうか。
まとめ
以上、国土交通DPFのAPIを使って訪日外国人の動きを確認しました。
FF-Data(訪日外国人流動データ)の概要と利用例のような分析やKepler.glを使った可視化にも取り組みたかったですがPython力がなさすぎた・・・精進します。
(ff_data.to_csv("data.csv")
でCSVに出力して細かい集計はExcelでやればよかったかな・・・)
今回確認したデータは年間の統計値のため冒頭に記載した目的である年末年始の混雑回避には全く役に立たないのですが、「こんなことができるよ!」といった参考になれば幸いです。
それでは明日もお楽しみに!