【政府統計】pythonによる政府統計e-StatのAPI機能の使い方
「政府統計の総合窓口(e-Stat)」の統計データは、APIで取得可能です。ユーザ登録(無料)を行えば、誰でもAPIを利用できます。
Pythonを使って、API経由で人口動態のデータを取得し、matplotlibでグラフ表示しました。
- [1] ユーザ登録
- [2] アプリケーションIDの取得
- [3] 取得できるデータと統計表IDを確認する
- [4] メタ情報を取得する
- [5] 統計データの取得
- [6] matplotlibでグラフ表示
- 終わりに
- [付録1] APIで統計表情報取得
[1] ユーザ登録
利用ガイドにしたがって、ユーザ登録します。メールアドレスを入力し仮登録します。
仮登録後メールに記載されているURLにアクセスし、本登録を行います。
利用する機能で「API機能」「地図で見る統計」をチェックします。チェックを外す理由は特に無いでしょう。
パスワードを入力するか、ソーシャルアカウント連携を行えば本登録完了です。
[2] アプリケーションIDの取得
ユーザ登録が終わったら、マイページからアプリケーションIDを取得します。
名称、URL、概要を入力します。以下は公式の引用です。
アプリケーションID取得時に入力する名称、URL、概要は後から変更しても構いません。また、URLについては、公開サイトで利用しない場合は、ローカルアドレス(「http://test.localhost/」等)を入力してください。
URLは公開サイトで利用しないので、ガイド通りのURLとしました。名称と概要は適当です。
[発行]ボタンを押すと、appId欄にアプリケーションIDが表示されます。
[3] 取得できるデータと統計表IDを確認する
e-Statの全てのデータがAPIで取得できるわけではありません。それでも数多くのデータが取得可能です。
また、APIで特定のデータにアクセスするには「統計表ID」が必要です。統計表IDは各データでユニークなIDです。ブラウザで取得したいデータの統計表IDを確認します。
[3-1] ブラウザで確認する方がAPIより楽
API機能でも確認できるのですが、結論としてはブラウザ「データベース | 統計データを探す | 政府統計の総合窓口」から確認する方が早いです。
データの数が1万を軽く超える数あるので、APIで取得しても見るのが大変です。
[3-2] ブラウザで統計表IDを確認する
ブラウザからデータベース | 統計データを探す | 政府統計の総合窓口にアクセスします。今回は分野から「人口・世帯」を選択し、最新の「人口動態調査」を探していきました。
最下層まで辿っていくと、[DB]と[API]が表示されます。[API]を押したら目的は達せられるのですが、いったん[DB]を選択してデータの中身を確認します。
データの中身が確認できたので、[API]を選択します。
API機能でアクセスするためのURLが表示されます。statsDataId
の後ろの数字が「統計表ID」です。appId
(アプリケーションID)は空欄になっています。APIを使用する際は取得した自身のIDをセットします。
また、ファイル形式はXML,JSON,CSVのいずれかが選択できます。ファイル形式を変更すると、URLも自動的に変更してくれます。
appId=
の後ろに取得したアプリケーションIDをコピーし、ブラウザでアクセスします。すると、データがXML形式で表示されました。
[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
[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
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年ごろを境に死亡率が出生率を上回り、人口が減少していることが分かります。
[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になるタイミングが一致しています。
終わりに
そこまで大きな苦労はなく、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で取得
# 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からの画像