FF-Dataで訪日外国人流動分析

### はじめに
本記事は[国土交通データプラットフォームアドベントカレンダー](https://qiita.com/advent-calendar/2023/mlit-dpf)13日目の記事です。

みなさん　こんにちは!

先日、原宿や浅草を訪れたのですが訪日外国人の方々が多くて驚きました。
着実にインバウンドは回復してますね！

日本政府観光局(JNTO)のウェブサイトに掲載されているグラフを見ると今年の９月以降はコロナ禍前の水準に戻りそうです。

![DB_1. 訪日外客数の推移_月別1. グラフ1. 日本語 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/34856d08-60d2-3ba8-73f2-a125638b8ee8.png)
[日本政府観光局（JNTO） 月別 訪日外客数の推移](https://public.tableau.com/shared/PDBFFH358?:toolbar=n&:display_count=n&:origin=viz_share_link&:embed=y)

そうなると気になるのがオーバーツーリズム。
これから年末年始を迎えるにあたり帰省や旅行で混雑を避けたいと考える方も多いのではないでしょうか。

そこで公開されているデータを活用して訪日外国人の動きを分析したいと思います。

訪日外国人のデータについては前述の日本政府観光局が公開しているものの他に
[RESAS 地域経済分析システム](https://opendata.resas-portal.go.jp/)でも以下の情報をAPIで取得することができます。

* 指定地域への国籍別訪問者数
* 指定国籍からの訪問者数
* 入出国空港間で訪問した地域の訪問者数
* 入国空港・出国空港内訳
* 滞在地域内訳

また国土交通省ではFF-Data（Flow of Foreigners-Data：訪日外国人流動データ）として訪日外国人の都道府県間流動表を作成・公表を行っています。
FF-Dataは国土交通データプラットフォームにも一部連携されており、交通機関別の国内訪問地間の流動量をAPIでデータを取得できることができます。
今回はこのFF-DataとPythonを使って訪日外国人の動きを分析します。

### 取得できるデータの確認
まずどのような情報が取得できるか確認するため、GraphiQLで```dataCatalog```APIを実行します。（QraphiQLって？という方は6日目の記事[API紹介①APIって使ったことある？ #1](https://qiita.com/dpf_hirometai/items/6a594e8dfe85da0bb499)をご確認ください！）
カタログIDに```ffd```を指定し次のリクエストを送信します。

```json
query {
  dataCatalog(IDs: "ffd") {
    id
    title
    datasets{
      id
      title
      data_count
      datatype_desc {
        name
        attributes {
          name
          display_name
          description
        }
      }
    }
  }
}
```

上記のリクエストを送信すると次のような結果が返ってきます。
```json
{
  "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を用いてデータの取得・集計を行っていきます。

### ライブラリの読み込み
まず必要なライブラリを読み込みます。

```python
import requests
import json
import pandas as pd
from typing import Union
```

### データを取得する関数の作成
次に国土交通データプラットフォームにリクエストを送信する関数を定義します。
リクエストボディに設定するGraphQLを引数で受け取り、結果を辞書型で返します。

```python
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```フィールドのうち必要な項目のみ返すようにしています。

```python
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年度のデータを取得します。

```python
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 |

無事にデータを取得することができました！
列名が長くて使いにくいのでリネームします。

```python
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 |

交通手段が横持ちのため集計しずらいため縦持ちに変換します。

```python
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 |

集計しやすくなりました！

### 京都の来訪者はどれくらい増えてるの？

私は実家が京都のため、京都を訪れる訪日外国人がどれくらい増えているかが気になります。。。
そのため目的地が京都のデータに絞り込みます。

```python
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 |

年度毎の来訪者数を集計します。

```python
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 |

グラフにすると次のようになります。

```python
fig = px.bar(grouped_data, x="year", y="flow")
fig.show()
```

![newplot (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/6c7b23c9-1416-556c-5815-5987b343e98a.png)

単位は千人なので年間で100万人近く増えてきているようです。コロナ禍前の2018年の水準に戻り、円安を考慮すると今年はもっと増えているかもしれないですね。。。

### どこから京都に来ているの？

続いて京都に来訪した外国人が京都に訪れる前にどこにいたか確認します。

```python
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()
```

![newplot (2) (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/fe613dc5-4825-40c3-65d4-b3f324c636b7.png)

一番多いのは大阪、次いで東京、奈良となっていますね。その他、新幹線沿いの都道府県が多いようです。

### 何で京都に来てるの？

最後に京都に来訪した外国人がどの交通手段で訪れているか確認します。

```python
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()
```

![newplot (3) (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/9935335c-3d96-95cb-f9e4-9bdd8c88333e.png)

鉄道が最も多く、次いでバスといった順位は変わらないようです。鉄道は新幹線、バスは観光バスで移動される方が多いのでしょうか。

### まとめ

以上、国土交通DPFのAPIを使って訪日外国人の動きを確認しました。
[FF-Data（訪日外国人流動データ）の概要と利用例](https://www.mlit.go.jp/sogoseisaku/soukou/soukou-magazine/1702ffdata.pdf)のような分析や[Kepler.gl](https://kepler.gl/)を使った可視化にも取り組みたかったですがPython力がなさすぎた・・・精進します。
（```ff_data.to_csv("data.csv")```でCSVに出力して細かい集計はExcelでやればよかったかな・・・）

今回確認したデータは年間の統計値のため冒頭に記載した目的である年末年始の混雑回避には全く役に立たないのですが、「こんなことができるよ！」といった参考になれば幸いです。

それでは明日もお楽しみに！