0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

e-Stat の統計表を Python で取得して整形する:Excel の取り方・API の使い方・よくある落とし穴(秘匿・単位・桁ずれ)

0
Posted at

公的統計のポータルサイト e-Stat から表を取ってきて、分析に使える形(縦持ち)に直すまでの手順をまとめます。これまで100本以上の分析記事で e-Stat のデータを使ってきましたが、時間がかかったのは分析よりも「表の読み方」でした。

この記事で扱うこと:

  • 「ファイル」画面の Excel を statInfId で取る方法
  • API(アプリケーションID が必要)で取る方法の概要
  • 横持ちの表を縦持ちにする小さな関数
  • 「-」「…」「x」などの記号、単位、桁ずれを点検する関数

1. Excel ファイルを statInfId で取る

e-Stat の「統計データを探す → ファイル」の画面では、表ごとに Excel や CSV が置かれています。ダウンロードボタンのリンク先は、次の形です。

https://www.e-stat.go.jp/stat-search/file-download?statInfId=<13桁の番号>&fileKind=0

statInfId は表ごとの番号で、fileKind=0 が Excel です(CSV が用意されている表は fileKind=1)。番号さえ控えておけば、同じ表をいつでも取り直せます。分析のフォルダには、この番号と取得日を必ず書いておくようにしています。

import requests

FILE_URL = "https://www.e-stat.go.jp/stat-search/file-download?statInfId={sid}&fileKind=0"

def download(stat_inf_id: str, path: str) -> str:
    """e-Stat の「ファイル」画面にある Excel を statInfId で取得する"""
    r = requests.get(FILE_URL.format(sid=stat_inf_id), timeout=60)
    r.raise_for_status()
    with open(path, "wb") as f:
        f.write(r.content)
    return path

私の作業環境では、サーバーから政府のサイトに直接つなげない場面があったため、ブラウザの中で同じ URL を fetch し、JavaScript の Excel ライブラリ(SheetJS)で読んでから保存したこともあります。どちらの方法でも、取ってくるファイルは同じです。

2. API で取る(アプリケーションID が必要)

e-Stat には API もあります。ユーザー登録をしてアプリケーションID(appId)を発行すると、統計表ID(statsDataId)を指定して JSON で値を取れます。

import requests

API = "https://api.e-stat.go.jp/rest/3.0/app/json/getStatsData"
params = {"appId": "<あなたのappId>", "statsDataId": "<統計表ID>", "limit": 100000}
js = requests.get(API, params=params, timeout=60).json()
values = js["GET_STATS_DATA"]["STATISTICAL_DATA"]["DATA_INF"]["VALUE"]
# values は {"@tab": ..., "@cat01": ..., "@area": ..., "@time": ..., "$": "1234"} のリスト

API の値はもともと縦持ちなので、整形は楽です。その代わり、項目のコード(@cat01 など)を名前に直す対応表(CLASS_INF)を自分でつなぐ必要があります。また、API で出ていない表(古い年次の Excel だけの表など)もあります。私は「API があれば API、なければ Excel」という順で使い分けています。

3. 横持ちの表を縦持ちにする

Excel の表は、たいてい「行が地域、列が項目や年」の横持ちです。分析では「地域・項目・値」の縦持ちのほうが扱いやすいので、最初に変換します。ここで大事なのは、数値に直せなかった記号を捨てずに、別の列に残すことです。

import numpy as np
import pandas as pd

# 記号の意味は表ごとに違う。ここでは「消さずに記録する」だけにして、扱いは呼び出し側で決める
SYMBOLS = {"-", "-", "…", "...", "x", "X", "***"}

def to_number(s: pd.Series, dash_as: str = "na"):
    """数値に直す。戻り値は(数値, 記号)"""
    raw = s.astype(str).str.strip().str.replace(" ", "", regex=False)
    sym = raw.where(raw.isin(SYMBOLS))
    num = pd.to_numeric(raw.str.replace(",", "", regex=False), errors="coerce")
    if dash_as == "zero":
        num = num.mask(raw.isin(["-", "-"]), 0.0)
    return num, sym

def tidy(df: pd.DataFrame, id_cols: list[str], dash_as: str = "na") -> pd.DataFrame:
    """横持ちを縦持ちにする。value と symbol の2列を持たせる"""
    long = df.melt(id_vars=id_cols, var_name="item", value_name="raw")
    long["value"], long["symbol"] = to_number(long["raw"], dash_as)
    return long

"1,234" のようなカンマ入りの文字列は、カンマを外さないと NaN になります。PDF の表を機械で読んだときも、カンマや小数点の扱いを間違えると「4,479」が「4」と「479」に分かれるなど、静かに値が壊れました(エラーは出ません)。

4. 記号の意味を、合計で確かめる

「-」が 0 なのか、欠測なのか、秘匿なのかは、表によって違います。私が見た5つの表では、次のようになっていました。

表 「-」の意味(確かめた方法)
感染症の定点報告 0(「-」を0とみなすと、都道府県の合計=全国が全週で一致)
国勢調査の基本集計 0(恒等式と「男+女=総数」が全行で一致)
社会・人口統計体系(市区町村) 0ではない(同じ列に数字の0と「-」が両方ある。区域がない年・調査の対象外などが混ざる)
家計調査の増減率 「計算できない」(比べる前の年がない期間に集まる)

表の注記を読むのが基本ですが、内訳の合計=総数で確かめると確実です。そのための関数です。

def check_sum(long, key, total_label, parts, col="item", tol=0.5):
    """内訳の合計=総数 を確かめる。内訳に欠けがあれば「確認できない」"""
    w = long.pivot_table(index=key, columns=col, values="value", aggfunc="first", dropna=False)
    diff = w[parts].sum(axis=1, min_count=len(parts)) - w[total_label]
    w = w.assign(diff=diff)
    w["status"] = np.select([diff.isna(), diff.abs() <= tol], ["確認できない", "一致"], "不一致")
    return w

小さな例で動かすと、「-」の扱いで結果が変わるのが分かります。

df = pd.DataFrame({"地域": ["A市", "B町", "C村"],
                   "総数": ["1,234", "30", "…"], "男": ["600", "30", "…"], "女": ["634", "-", "…"]})
print(check_sum(tidy(df, ["地域"]), ["地域"], "総数", ["男", "女"]))
print(check_sum(tidy(df, ["地域"], dash_as="zero"), ["地域"], "総数", ["男", "女"]))
item      女      男      総数  diff  status
地域
A市    634.0  600.0  1234.0   0.0      一致
B町      NaN   30.0    30.0   NaN  確認できない
C村      NaN    NaN     NaN   NaN  確認できない
item      女      男      総数  diff  status
地域
A市    634.0  600.0  1234.0   0.0      一致
B町      0.0   30.0    30.0   0.0      一致
C村      NaN    NaN     NaN   NaN  確認できない

「-」を0とみなすと B町 の合計が合うので、この表の「-」は0と読むのが妥当、と判断できます。C村の「…」はどちらでも確認できないので、欠測として扱います。

5. 単位と桁ずれ

単位:表の見出しに【千円】【千人】と書かれていることがよくあります。課税対象所得【千円】を「円」と読み違えると、1人あたり所得の中央値は約282万円ではなく約2,800円になります。計算したあとに「ありうる範囲に入っているか」を必ず見ます。

桁ずれ:前の時点からの比が極端なセルを探します。

def check_jump(long, key, order, ratio=3.0):
    """前の時点から ratio 倍以上・1/ratio 以下に動いたセル(桁ずれの候補)"""
    d = long.sort_values(key + [order]).copy()
    d["prev"] = d.groupby(key)["value"].shift()
    r = d["value"] / d["prev"]
    return d[(r >= ratio) | (r <= 1 / ratio)]

引っかかったセルが、すべて入力の誤りとは限りません。市町村の総人口で3倍以上の変化を探したときは、見つかったのはすべて原発事故の避難指示区域で、実際の変化でした。検出は機械に任せ、判断は中身を見て人がする、という分担にしています。

まとめ

  • Excel は statInfId で取り直せる形にしておく。API があれば API が楽
  • 数値に直すときは、記号を捨てずに別の列に残す
  • 記号の意味は表ごとに違う。注記を読み、合計で確かめる
  • 単位と桁ずれは、計算したあとに「ありうる範囲か」で点検する

ここに載せた関数は、どの表にも使えるように小さくしてあります。実際の分析では、表ごとに SYMBOLS の扱いと点検の閾値を決めて、README に書いています。

関連リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?