どこにでもいる30代SEの学習ブログ

主にプログラミング関連の学習内容。読んだ本の感想や株式投資についても書いてます。

pythonによる政府統計e-StatのAPI機能の使い方

f:id:predora005:20210107001006p:plain
*1

政府統計の総合窓口(e-Stat)」の統計データは、APIで取得可能です。ユーザ登録(無料)を行えば、誰でもAPIを利用できます。

Pythonを使って、API経由で人口動態のデータを取得し、matplotlibでグラフ表示しました。

f:id:predora005:20210131100819p:plain

[1] ユーザ登録

利用ガイドにしたがって、ユーザ登録します。メールアドレスを入力し仮登録します。

f:id:predora005:20210127185110p:plain

仮登録後メールに記載されているURLにアクセスし、本登録を行います。

利用する機能で「API機能」「地図で見る統計」をチェックします。チェックを外す理由は特に無いでしょう。

f:id:predora005:20210127185232p:plain

パスワードを入力するか、ソーシャルアカウント連携を行えば本登録完了です。

f:id:predora005:20210127185259p:plain

[2] アプリケーションIDの取得

ユーザ登録が終わったら、マイページからアプリケーションIDを取得します。

f:id:predora005:20210128212911p:plain

f:id:predora005:20210128212934p:plain

名称、URL、概要を入力します。以下は公式の引用です。

アプリケーションID取得時に入力する名称、URL、概要は後から変更しても構いません。また、URLについては、公開サイトで利用しない場合は、ローカルアドレス(「http://test.localhost/」等)を入力してください。

URLは公開サイトで利用しないので、ガイド通りのURLとしました。名称と概要は適当です。

f:id:predora005:20210128213035p:plain

[発行]ボタンを押すと、appId欄にアプリケーションIDが表示されます。

f:id:predora005:20210128213050p:plain

[3] 取得できるデータと統計表IDを確認する

e-Statの全てのデータがAPIで取得できるわけではありません。それでも数多くのデータが取得可能です。

また、APIで特定のデータにアクセスするには「統計表ID」が必要です。統計表IDは各データでユニークなIDです。ブラウザで取得したいデータの統計表IDを確認します。

[3-1] ブラウザで確認する方がAPIより楽

API機能でも確認できるのですが、結論としてはブラウザ「データベース | 統計データを探す | 政府統計の総合窓口」から確認する方が早いです。

データの数が1万を軽く超える数あるので、APIで取得しても見るのが大変です。

[3-2] ブラウザで統計表IDを確認する

ブラウザからデータベース | 統計データを探す | 政府統計の総合窓口にアクセスします。今回は分野から「人口・世帯」を選択し、最新の「人口動態調査」を探していきました。

f:id:predora005:20210130150401p:plain

f:id:predora005:20210130150407p:plain f:id:predora005:20210130150413p:plain f:id:predora005:20210130150417p:plain

最下層まで辿っていくと、[DB]と[API]が表示されます。[API]を押したら目的は達せられるのですが、いったん[DB]を選択してデータの中身を確認します。

f:id:predora005:20210130150422p:plain

データの中身が確認できたので、[API]を選択します。

f:id:predora005:20210130150428p:plain

API機能でアクセスするためのURLが表示されます。statsDataIdの後ろの数字が「統計表ID」です。appId(アプリケーションID)は空欄になっています。APIを使用する際は取得した自身のIDをセットします。

f:id:predora005:20210130150435p:plain

また、ファイル形式はXML,JSON,CSVのいずれかが選択できます。ファイル形式を変更すると、URLも自動的に変更してくれます。

appId=の後ろに取得したアプリケーションIDをコピーし、ブラウザでアクセスします。すると、データがXML形式で表示されました。

f:id:predora005:20210130150438p:plain

[4] メタ情報を取得する

「統計表ID」を用いてメタ情報を取得します。メタ情報は公式のAPI仕様によると次の通りです。

指定した統計表IDに対応するメタ情報(表章事項、分類事項、地域事項等)を取得します。

先程の人口動態調査ですと、行(年度:2018年, 2017年...)と列(出生率, 死亡数...)が取得できます。

[4-1] ブラウザで取得

まずは、ブラウザで確認します。下記URLをブラウザに入力します。

https://api.e-stat.go.jp/rest/3.0/app/getMetaInfo?appId={アプリケーションID}&statsDataId=0003411561&explanationGetFlg=N

f:id:predora005:20210130212013p:plain f:id:predora005:20210130212018p:plain

[4-2] Pythonで取得

Pythonで取得する際は、JSON形式で取得します。ブラウザではXML形式で取得しましたが、PythonではJSON形式の方が扱いやすいからです。

import requests

app_id = {アプリケーションID}
stats_data_id = 0003411561 # 年次別にみた人口動態総覧

# メタ情報取得のURL
url = 'https://api.e-stat.go.jp/rest/3.0/app/json/getMetaInfo?'
url += 'appId={0:s}&'.format(app_id) 
url += 'statsDataId={0:s}&'.format(stats_data_id)
url += 'explanationGetFlg=N&'   # 解説情報有無:無し

# メタ情報取得
json = requests.get(url).json()
print(json)
# {'GET_META_INFO': {'RESULT': {'STATUS': 0, 'ERROR_MSG': '正常に終了しました。', 'DATE': '2021-01-30T21:26:13.043+09:00'}, 
# 'PARAMETER': {'LANG': 'J', 'STATS_DATA_ID': '0003411561', 'EXPLANATION_GET_FLG': 'N', 'DATA_FORMAT': 'J'}, 
# 'METADATA_INF': {'TABLE_INF': ...(省略)...
# 'CLASS_INF': {'CLASS_OBJ': [{'@id': 'cat01', '@name': '人口動態総覧', 
# 'CLASS': [{'@code': '00110', '@name': '出生数', '@level': '1', '@unit': '人'}, ...(省略)...

取得したJSONから、行と列の情報を抽出します。

# メタ情報から各表のCLASS(行や列)を抽出
class_objs = json['GET_META_INFO']['METADATA_INF']['CLASS_INF']['CLASS_OBJ']
print(class_objs)
# [ {'@id': 'cat01', '@name': '人口動態総覧', 
#   'CLASS': [{'@code': '00110', '@name': '出生数', '@level': '1', '@unit': '人'}, ...(省略)...
#   {'@id': 'time', '@name': '時間軸(年次)', 
#   'CLASS': [{'@code': '2018000000', '@name': '2018年', '@level': '1'}, ...(省略)...

[5] 統計データの取得

APIの統計データ取得を用いて、データの中身(値)を取得します。

[5-1] ブラウザで確認

まずはブラウザで、どのようなデータ構造になっているのか確認します。

https://api.e-stat.go.jp/rest/3.0/app/getMetaInfo?appId={アプリケーションID}&explanationGetFlg=N

f:id:predora005:20210130224945p:plain

VALUEタグの中に値が格納されており、属性に何のデータかが示されています。属性はメタ情報と対応しています。「cat01」は「人口動態総覧」のカテゴリを示しており、「00110」は人口動態総覧のうち「出生数」であることを示しています。

[5-2] Pythonで確認

メタ情報の際と同様にJSON形式で取得します。

app_id = {アプリケーションID}
stats_data_id = 0003411561 # 年次別にみた人口動態総覧

# 統計データ取得のURL
url = 'https://api.e-stat.go.jp/rest/3.0/app/json/getStatsData?'
url += 'appId={0:s}&'.format(app_id) 
url += 'statsDataId={0:s}&'.format(stats_data_id)
url += 'metaGetFlg=N&'          # メタ情報有無
url += 'explanationGetFlg=N&'   # 解説情報有無
url += 'annotationGetFlg=N&'    # 注釈情報有無

# 統計データ取得
json = requests.get(url).json()

# 統計データからデータ部取得
values = json['GET_STATS_DATA']['STATISTICAL_DATA']['DATA_INF']['VALUE']

# jsonからDataFrameを作成
df = pd.DataFrame(values)
print(df)
#      @cat01       @time @unit        $
# 0     00110  2018000000     人   918400
# 1     00110  2017000000     人   946146
# 2     00110  2016000000     人   977242
# 3     00110  2015000000     人  1005721
# 4     00110  2014000000     人  1003609
# ...     ...         ...   ...      ...
# 3154  00430  1903000000  人口千対     1.44
# 3155  00430  1902000000  人口千対     1.43
# 3156  00430  1901000000  人口千対     1.43
# 3157  00430  1900000000  人口千対     1.46
# 3158  00430  1899000000  人口千対     1.53
# 
# [3159 rows x 4 columns]

JSON形式で取得したデータは、後で扱いやすいようにDataFrameに変換しました。

[5-3] IDやCODEを意味のわかる内容に置き換える

これは必須ではありませんが、データの可読性のためにやりました。

DataFrameの列名'@cat01', '@time'を一目見ただけだと意味が理解づらいですし、値も'00110'や'2018000000'となっており分かりづらいです。メタ情報を用いて、意味の分かる内容に置き換えます。

# 統計データのカテゴリ要素をID(数字の羅列)から、意味がわかる名称に変更する
for class_obj in meta_info:

    # メタ情報の「@id」の先頭に'@'を付与'した文字列が、
    # 統計データの列名と対応している
    column_name = '@' + class_obj['@id']
    
    # 統計データの列名を「@code」から「@name」に置換するディクショナリを作成
    id_to_name_dict = {}
    for obj in class_obj['CLASS']:
        id_to_name_dict[obj['@code']] = obj['@name']
    
    # ディクショナリを用いて、指定した列の要素を置換 
    df[column_name] = df[column_name].replace(id_to_name_dict)

# 統計データの列名を変換するためのディクショナリを作成
col_replace_dict = {'@unit': '単位', '$': '値'}
for class_obj in meta_info:
    org_col = '@' + class_obj['@id']
    new_col = class_obj['@name']
    col_replace_dict[org_col] = new_col

# ディクショナリに従って、列名を置換する
new_columns = []
for col in stats_data:
    if col in col_replace_dict:
        new_columns.append(col_replace_dict[col])
    else:
        new_columns.append(col)
        
df.columns = new_columns
print(df)
#      人口動態総覧 時間軸(年次)    単位        値
# 0       出生数   2018年     人   918400
# 1       出生数   2017年     人   946146
# 2       出生数   2016年     人   977242
# 3       出生数   2015年     人  1005721
# 4       出生数   2014年     人  1003609
# ...     ...     ...   ...      ...
# 3154    離婚率   1903年  人口千対     1.44
# 3155    離婚率   1902年  人口千対     1.43
# 3156    離婚率   1901年  人口千対     1.43
# 3157    離婚率   1900年  人口千対     1.46
# 3158    離婚率   1899年  人口千対     1.53
# 
# [3159 rows x 4 columns]

[6] matplotlibでグラフ表示

APIで取得した「年次別にみた人口動態総覧」のデータを、matplotlibでグラフ表示します。

[6-1] 数値に変換する

年が文字列になっているので、整数値に変換します。変換値は「年度」という新しい列に格納します。

# 時間軸(年次)を整数に変換
df['年度'] = df['時間軸(年次)'].map(lambda year: int(year.replace('年','')))

次は「値」列について、有効値以外をNaNに変換します。例えば、戦前には調査していなかったデータなどは"…"といった値が格納されています。NaNに変換後、列のデータタイプをfloat64に変換します。

import numpy as np

# 有効値以外をNaNに置換する
df['値'] = df['値'].replace(['***', '-', '.', '…'], np.nan)
df['値'] = df['値'].astype(np.float64)

[6-2] 出生率と死亡率を表示

出生率と死亡率をDataFrameから取り出し、折れ線グラフで表示します。

import matplotlib.pyplot as plt

# 出生率と死亡率を取得する
birth_rate = df[df['人口動態総覧'] == '出生率']
mortality_rate = df[df['人口動態総覧'] == '死亡率']

# 図と座標軸を取得
fig = plt.figure()
ax = fig.add_subplot(1,1,1)

# 折れ線グラフをセット
ax.plot(birth_rate['年度'], birth_rate['値'], label='出生率(人口千対)')
ax.plot(mortality_rate['年度'], mortality_rate['値'], label='死亡率(人口千対)')

# Y軸の範囲設定
ymax = max([ birth_rate['値'].max(), mortality_rate['値'].max() ])
ax.set_ylim([0, ymax])

# 凡例表示
ax.legend()

# 折れ線グラフを表示
fig.show()

2005年ごろを境に死亡率が出生率を上回り、人口が減少していることが分かります。

f:id:predora005:20210131100755p:plain

[6-3] 自然増減率を第二軸に追加

第二軸に自然増減率を第二軸に追加します。自然増減率が0以下になると、人口が減少することを意味します。

# 自然増減率を取得する
natutal_id_rate = df[df['人口動態総覧'] == '自然増減率']

# 自然増減率を第二軸にプロット
ax2 = ax.twinx()
ax2.plot(natutal_id_rate['年度'], natutal_id_rate['値'], 'C2', ls=':', label='自然増減率(人口千対)')
ax2.set_ylabel('自然増減率(人口千対)')
ax2.grid(axis='y', color='gray', ls=':')

出生率と死亡率が交わるタイミングと、自然増減率が0になるタイミングが一致しています。

f:id:predora005:20210131100819p:plain

終わりに

そこまで大きな苦労はなく、APIで統計データを取得できました。今後、他のデータと組み合わせたり、色々と有効活用できそうです。

[付録1] APIで統計表情報取得

個人的には、APIではなくブラウザで確認する方をオススメします。

[付1-1] ブラウザで試しに確認

ブラウザで次のURLを直接入力して確認します。

API機能のうち「統計表情報取得」を使用して確認します。

https://api.e-stat.go.jp/rest/3.0/app/getStatsList?appId={アプリケーションID}&limit=2

  • limit=2では、取得するデータを2データまでに限定します。

上記URLにアクセスすると、次の結果が得られます。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GET_STATS_LIST xsi:noNamespaceSchemaLocation="https://api.e-stat.go.jp/rest/3.0/schema/GetStatsList.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <RESULT>
        <STATUS>0</STATUS>
        <ERROR_MSG>正常に終了しました。</ERROR_MSG>
        <DATE>2021-01-27T22:44:59.685+09:00</DATE>
    </RESULT>
    <PARAMETER>
        <LANG>J</LANG>
        <DATA_FORMAT>X</DATA_FORMAT>
        <LIMIT>1</LIMIT>
    </PARAMETER>
    <DATALIST_INF>
        <NUMBER>183998</NUMBER>
        <RESULT_INF>
            <FROM_NUMBER>1</FROM_NUMBER>
            <TO_NUMBER>1</TO_NUMBER>
            <NEXT_KEY>2</NEXT_KEY>
        </RESULT_INF>
        <TABLE_INF id="0003288322">
            <STAT_NAME code="00020111">民間企業の勤務条件制度等調査</STAT_NAME>
            <GOV_ORG code="00020">人事院</GOV_ORG>
            <STATISTICS_NAME>民間企業の勤務条件制度等調査(民間企業退職給付調査) 統計表 1 定年制と定年退職者の継続雇用の状況</STATISTICS_NAME>
            <TITLE no="1">(推計値) 定年制の状況</TITLE>
            <CYCLE>年次</CYCLE>
            <SURVEY_DATE>201601-201612</SURVEY_DATE>
            <OPEN_DATE>2019-03-20</OPEN_DATE>
            <SMALL_AREA>0</SMALL_AREA>
            <COLLECT_AREA>該当なし</COLLECT_AREA>
            <MAIN_CATEGORY code="03">労働・賃金</MAIN_CATEGORY>
            <SUB_CATEGORY code="02">賃金・労働条件</SUB_CATEGORY>
            <OVERALL_TOTAL_NUMBER>25</OVERALL_TOTAL_NUMBER>
            <UPDATED_DATE>2019-03-30</UPDATED_DATE>
            <STATISTICS_NAME_SPEC>
                <TABULATION_CATEGORY>民間企業の勤務条件制度等調査(民間企業退職給付調査)</TABULATION_CATEGORY>
                <TABULATION_SUB_CATEGORY1>統計表</TABULATION_SUB_CATEGORY1>
                <TABULATION_SUB_CATEGORY2>1 定年制と定年退職者の継続雇用の状況</TABULATION_SUB_CATEGORY2>
            </STATISTICS_NAME_SPEC>
            <DESCRIPTION/>
            <TITLE_SPEC>
                <TABLE_CATEGORY>(推計値)</TABLE_CATEGORY>
                <TABLE_NAME>定年制の状況</TABLE_NAME>
                <TABLE_EXPLANATION>1 事務・技術関係職種の従業員がいる企業41,314社について集計した。2 「定年年齢」内の数値は定年制がある企業を100とした場合の割合を示す。</TABLE_EXPLANATION>
            </TITLE_SPEC>
        </TABLE_INF>
        <TABLE_INF id="0003288323">
            ...(省略)...
        </TABLE_INF>
    </DATALIST_INF>
</GET_STATS_LIST>

[付1-2] Pythonで取得

Pythonで取得する際は、json形式で取得します。

# coding: utf-8

import sys
import requests

# エントリーポイント
if __name__ == '__main__':
    
    # コマンドライン引数からアプリケーションID取得
    if len(sys.argv) < 2:
        print('Usage:')
        print('  python3 {0:s} [appId]'.format(sys.argv[0]))
        sys.exit(1)
    app_id = sys.argv[1]
    
    # 統計表情報取得のURL
    url = 'https://api.e-stat.go.jp/rest/3.0/app/json/getStatsList?'
    url += 'appId={0:s}&'.format(app_id)
    url += 'limit=2'

    # 統計表情報取得
    json = requests.get(url).json()
    print(json)
# {'GET_STATS_LIST': {'RESULT': 
# {'STATUS': 0, 'ERROR_MSG': '正常に終了しました。', 'DATE': '2021-01-27T22:52:49.687+09:00'}, 
# 'PARAMETER': {'LANG': 'J', 'DATA_FORMAT': 'J', 'LIMIT': 1}, 
# 'DATALIST_INF': {'NUMBER': 183998, 'RESULT_INF': ...(以下略)...

アプリケーションIDをソースに直打ちしたくないので、コマンドライン引数で指定しています。

[付1-3] pandasのDataFrame形式に変換

json形式のままだと見づらいので、pandasのDataFrame形式に変換します。いったんディクショナリのリストにした後、DataFrameに変換します。

import pandas as pd

# 統計表情報から各表のデータ部取得
datalist = json['GET_STATS_LIST']['DATALIST_INF']['TABLE_INF']

# ディクショナリ形式にし、pandasのDataFrameに変換
dict_list = []
for data in datalist:
    dict = {}

    # 統計表ID
    dict['id'] = data['@id']
    
    # 政府統計コードと統計名
    dict['stat_id'] = data['STAT_NAME']['@code']
    dict['stat_name'] = data['STAT_NAME']['$']
    
    #タイトル
    if '$' in data['TITLE']:
        dict['title'] = data['TITLE']['$']
    else:
        dict['title'] = data['TITLE']
    
    # 担当機関
    dict['gov_code'] = data['GOV_ORG']['@code']
    dict['gov_name'] = data['GOV_ORG']['$']
    
    # ディクショナリをリストに追加
    dict_list.append(dict)

df = pd.DataFrame(dict_list)
print(df)
#    id          stat_id   stat_name                 title                           gov_code  gov_name
# 0  0003288322  00020111  民間企業の勤務条件制度等調査  (推計値) 定年制の状況               00020     人事院
# 1  0003288323  00020111  民間企業の勤務条件制度等調査  (推計値) 定年制の今後の変更予定の状況  00020     人事院

[付1-4] CSVファイルに出力

URLのlimit=1000に変更して、1,000件の統計表情報を取得します。そして、取得したデータをCSVファイルに出力します。

df.to_csv('list.csv')

CSVファイルを確認すると、各データのIDやタイトルが確認できます。

id stat_id stat_name title gov_code gov_name
0003288322 00020111 民間企業の勤務条件制度等調査 (推計値) 定年制の状況 00020 人事院
... ... ... ... ...
0003354588 00100101 情報化社会と青少年に関する調査 親 Q6 [カード4] 携帯電話もPHSも使わない理由は何ですか。次の中から、あてはまるものをいくつでもあげてください。(M.A.) 家族の就労状況 00100 内閣府

データは1,000件だけでなく、まだまだ沢山あります。APIでキーワード検索等の絞り込みも行えますが、ブラウザで確認した方が楽です。

*1:200 DegreesによるPixabayからの画像