マイナンバーカード交付状況のPDFファイルをデータラングリング

# 参考

+ [マイナンバーカード普及状況ダッシュボード](https://mynumbercard.code4japan.org/)

# インストール
```shell
apt install ghostscript
pip install camelot-py[cv]
pip install pandas
pip install requests
pip install beautifulsoup4
pip install japanmap
pip install tqdm
```

# プログラム

[BADオープンデータの供養の過程](https://mynumbercard.code4japan.org/chant)の

1. PDF から CSV ファイルを作る
2. カンマや%を取り除いて文字列を数字データに変換する

```python
tables = camelot.read_pdf(
    link, pages="all", split_text=True, strip_text=" 　,%％\n", line_scale=40
)
```

PDFから表を抽出時に「strip_text=" 　,%％\n"」で不要な文字を削除

```python
import csv
import datetime
import pathlib
import re
from urllib.parse import urljoin

import camelot
import pandas as pd
import requests
from bs4 import BeautifulSoup
from japanmap import pref_code
from tqdm.notebook import tqdm

# 和暦から西暦に変換

def wareki2date(s):

    m = re.search("(H|R|平成|令和)([0-9元]{1,2})[.年]([0-9]{1,2})[.月]([0-9]{1,2})日?", s)

    year, month, day = [1 if i == "元" else int(i) for i in m.group(2, 3, 4)]

    if m.group(1) in ["平成", "H"]:
        year += 1988
    elif m.group(1) in ["令和", "R"]:
        year += 2018

    return datetime.date(year, month, day)

def df_conv(df, col_name, population_date, delivery_date, criteria_date):

    df.set_axis(col_name, axis=1, inplace=True)

    df["人口算出基準日"] = population_date
    df["交付枚数算出基準日"] = delivery_date
    df.insert(0, "算出基準日", criteria_date)

    return df

url = "https://www.soumu.go.jp/kojinbango_card/"

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
}

cjk = str.maketrans("⻲⻑黑戶⻯⻄⻘⻤", "亀長黒戸竜西青鬼")

df_code = pd.read_csv(
    "https://docs.google.com/spreadsheets/d/e/2PACX-1vSseDxB5f3nS-YQ1NOkuFKZ7rTNfPLHqTKaSag-qaK25EWLcSL0klbFBZm1b6JDKGtHTk6iMUxsXpxt/pub?gid=0&single=true&output=csv",
    dtype={"団体コード": int, "都道府県名": str, "郡名": str, "市区町村名": str},
)

df_code["市区町村名"] = df_code["郡名"].fillna("") + df_code["市区町村名"]
df_code.drop("郡名", axis=1, inplace=True)

r = requests.get(url, headers=headers)
r.raise_for_status()

soup = BeautifulSoup(r.content, "html.parser")

links = soup.select("ul.normal > li a[href$=pdf]")

for i in tqdm(links):

    creation_date = wareki2date(i.find_parent("li").get_text(strip=True))
    link = urljoin(url, i.get("href"))

    # フォルダ作成
    path_dir = pathlib.Path(creation_date.strftime("%Y%m%d"))
    path_dir.mkdir(parents=True, exist_ok=True)

    # PDFから表を抽出
    tables = camelot.read_pdf(
        link, pages="all", split_text=True, strip_text=" 　,%％\n", line_scale=40
    )

    # 団体区分別

    df_tmp = tables[0].df

    df_tmp.iat[0, 1] = "区分"
    df_tmp.iat[1, 1] = "全国"

    population_date = wareki2date(df_tmp.iat[0, 2]).strftime("%Y/%m/%d")
    delivery_date = wareki2date(df_tmp.iat[0, 3]).strftime("%Y/%m/%d")

    df0 = df_conv(
        df_tmp.iloc[1:, 1:].reset_index(drop=True),
        ["区分", "人口", "交付枚数", "人口に対する交付枚数率"],
        population_date,
        delivery_date,
        creation_date.strftime("%Y/%m/%d"),
    )

    df0 = df0.astype({"人口": int, "交付枚数": int, "人口に対する交付枚数率": float})

    df0.to_csv(
        pathlib.Path(path_dir, "summary_by_types.csv"),
        index=False,
        quoting=csv.QUOTE_NONNUMERIC,
        encoding="utf_8_sig",
    )

    # 都道府県一覧

    dfs = []

    for table in tables[3:5]:

        df_tmp = table.df

        population_date = wareki2date(df_tmp.iat[0, 1]).strftime("%Y/%m/%d")

        # マイナンバーカード交付状況（平成31年4月1日現在）の日付調整
        if creation_date == datetime.date(2019, 4, 1):
            delivery_date = wareki2date(
                df_tmp.iat[0, 2].replace("H31.41", "H31.4.1")
            ).strftime("%Y/%m/%d")
        else:
            delivery_date = wareki2date(df_tmp.iat[0, 2]).strftime("%Y/%m/%d")

        df = df_conv(
            df_tmp.iloc[1:].reset_index(drop=True),
            ["都道府県名", "総数（人口）", "交付枚数", "人口に対する交付枚数率"],
            population_date,
            delivery_date,
            creation_date.strftime("%Y/%m/%d"),
        )

        dfs.append(df)

    df3 = pd.concat(dfs)

    df3["都道府県名"] = df3["都道府県名"].str.normalize("NFKC")
    df3["都道府県名"] = df3["都道府県名"].apply(lambda s: s.translate(cjk))

    # 都道府県番号でソート
    df3.index = df3["都道府県名"].apply(lambda s: pref_code(s))
    df3.sort_index(inplace=True)

    df3 = df3.astype({"総数（人口）": int, "交付枚数": int, "人口に対する交付枚数率": float})

    df3.to_csv(
        pathlib.Path(path_dir, "all_prefectures.csv"),
        index=False,
        quoting=csv.QUOTE_NONNUMERIC,
        encoding="utf_8_sig",
    )

    df3

    # 男女・年齢別

    n = 5

    if creation_date > datetime.date(2017, 3, 8):

        n = 6

        df_tmp = tables[5].df

        population_date = wareki2date(df_tmp.iat[0, 1]).strftime("%Y/%m/%d")
        delivery_date = wareki2date(df_tmp.iat[0, 4]).strftime("%Y/%m/%d")

        df5 = df_conv(
            df_tmp.iloc[2:].reset_index(drop=True),
            [
                "年齢",
                "人口(男)",
                "人口(女)",
                "人口(計)",
                "交付件数(男)",
                "交付件数(女)",
                "交付件数(計)",
                "交付率(男)",
                "交付率(女)",
                "交付率(計)",
                "全体に対する交付件数割合(男)",
                "全体に対する交付件数割合(女)",
                "全体に対する交付件数割合(計)",
            ],
            population_date,
            delivery_date,
            creation_date.strftime("%Y/%m/%d"),
        )

        df5 = df5.astype(
            {
                "人口(男)": int,
                "人口(女)": int,
                "人口(計)": int,
                "交付件数(男)": int,
                "交付件数(女)": int,
                "交付件数(計)": int,
                "交付率(男)": float,
                "交付率(女)": float,
                "交付率(計)": float,
                "全体に対する交付件数割合(男)": float,
                "全体に対する交付件数割合(女)": float,
                "全体に対する交付件数割合(計)": float,
            }
        )

        df5.to_csv(
            pathlib.Path(path_dir, "demographics.csv"),
            index=False,
            quoting=csv.QUOTE_NONNUMERIC,
            encoding="utf_8_sig",
        )

        df5

    # 市区町村別一覧

    dfs = []

    for table in tables[n:]:

        df_tmp = table.df

        population_date = wareki2date(df_tmp.iat[0, 2]).strftime("%Y/%m/%d")
        delivery_date = wareki2date(df_tmp.iat[0, 3]).strftime("%Y/%m/%d")

        df = df_conv(
            df_tmp.iloc[1:].reset_index(drop=True),
            ["都道府県名", "市区町村名", "総数（人口）", "交付枚数", "人口に対する交付枚数率"],
            population_date,
            delivery_date,
            creation_date.strftime("%Y/%m/%d"),
        )

        dfs.append(df)

    df6 = pd.concat(dfs)

    df6["都道府県名"] = df6["都道府県名"].str.normalize("NFKC")
    df6["都道府県名"] = df6["都道府県名"].apply(lambda s: s.translate(cjk))

    # 全国を除外
    df6 = df6[df6["都道府県名"] != "全国"].copy()

    df6["市区町村名"] = df6["市区町村名"].str.normalize("NFKC")
    df6["市区町村名"] = df6["市区町村名"].apply(lambda s: s.translate(cjk))

    df6["市区町村名"] = df6["市区町村名"].mask(df6["都道府県名"] + df6["市区町村名"] == "兵庫県篠山市", "丹波篠山市")
    df6["市区町村名"] = df6["市区町村名"].mask(
        df6["都道府県名"] + df6["市区町村名"] == "高知県高岡郡梼原町", "高岡郡檮原町"
    )
    df6["市区町村名"] = df6["市区町村名"].mask(
        df6["都道府県名"] + df6["市区町村名"] == "福岡県糟屋郡須惠町", "糟屋郡須恵町"
    )

    if creation_date < datetime.date(2018, 10, 1):
        df6["市区町村名"] = df6["市区町村名"].mask(
            df6["都道府県名"] + df6["市区町村名"] == "福岡県那珂川市", "筑紫郡那珂川町"
        )
    else:
        df6["市区町村名"] = df6["市区町村名"].mask(
            df6["都道府県名"] + df6["市区町村名"] == "福岡県筑紫郡那珂川町", "那珂川市"
        )

    df6 = pd.merge(df6, df_code, on=["都道府県名", "市区町村名"], how="left")

    df6 = df6.astype(
        {"総数（人口）": int, "交付枚数": int, "人口に対する交付枚数率": float, "団体コード": "Int64"}
    )

    df6.to_csv(
        pathlib.Path(path_dir, "all_localgovs.csv"),
        index=False,
        quoting=csv.QUOTE_NONNUMERIC,
        encoding="utf_8_sig",
    )
```