春闘における一時金の支給状況PDFの加工

2023/11/14に公開

連合は春季に賃金交渉の結果を公表している。
しかし公表形式はPDFとなっており、時系列での評価をするためには手間がかかる。
このため、今回は特に一時金の支給状況に限定して、pdfの表をpythonを用いて加工する。

なお、今回の作業は環境構築を含め、githubにあるリポジトリから再現可能。
https://github.com/kigasudayooo/pdf_to_df

まず、今回の階層構造は以下の通り。

.
├── code
│   ├── pdf_list.json
│   └── test.ipynb
└── data

pdf_list.jsonは以下のように、pdfのリンクとなっている。なお、冬季の2023年は本稿執筆時点でまだ第一回しか公表されていないため、過去についても第一回を用いている。

{

    "summer":{

        "y2017" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2017/yokyu_kaito/kaito_no7_ichiji_20170705.pdf?2718",
        "y2018" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2018/yokyu_kaito/kaito/no7/kaito_no7_ichiji.pdf?6213",
        "y2019" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2019/yokyu_kaito/kaito/no6/kaito_no6_ichiji.pdf?5180",
        "y2020" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2020/yokyu_kaito/kaito/no7/kaito_no7_ichiji.pdf?9029",
        "y2021" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2021/yokyu_kaito/kaito/kaito_no7_ichiji.pdf?890",
        "y2022" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2022/yokyu_kaito/kaito/kaito_no7_ichiji.pdf?9996",
        "y2023" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2023/yokyu_kaito/kaito/kaito_no7_ichiji.pdf?6229"
    
    },

    "winter":{
        "y2017" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2017/yokyu_kaito/nenmatsu_ichiji_01.pdf?1851",
        "y2018" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2018/yokyu_kaito/kaito/nenmatsu/ichiji_01.pdf?3961",
        "y2019" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2019/yokyu_kaito/kaito/nenmatsu/ichiji_01.pdf?7778",
        "y2020" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2020/yokyu_kaito/kaito/nenmatsu/ichiji_01.pdf?1209",
        "y2021" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2021/yokyu_kaito/kaito/nenmatsu/ichiji_01.pdf?1586",
        "y2022" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2022/yokyu_kaito/kaito/nenmatsu/ichiji_01.pdf?6298",
        "y2023" : "https://www.jtuc-rengo.or.jp/activity/roudou/shuntou/2023/yokyu_kaito/kaito/nenmatsu/ichiji_01.pdf?6229"

    }
}

まずはパッケージを読み込む。tabulaの使用にはJAVAがインストールされていることが必要なので注意。

import tabula
import pandas as pd
import json

f = json.load(open('pdf_list.json', 'r'))

jsonファイル中、'summer'のリンク先のデータを処理する。

colnames = ['属性', '組合数', '組合人員数', '加重平均‗要求', '加重平均‗回答', '加重平均‗前年実績', '単純平均‗要求', '単純平均‗回答', '単純平均‗前年実績']


# for文で得られたdfをすべて縦に結合する為の空のdfを作成する
master_df = pd.DataFrame()


for i in f['summer']:

    pdfpath = f['summer'][i]

    dfs = tabula.read_pdf(pdfpath, 
                    pages="all",
                    lattice=True
                    )

    # dfsの中身をすべて縦に結合する
    df = pd.concat(dfs, axis=0)


    # 業種別がNaNである時、構成組織の値をとる
    df['業種別'] = df['業種別'].fillna(df['構成組織'])

    # 業種別がNaNである行を削除する
    df = df.dropna(subset=['業種別'])
    # 構成組織の列を削除する
    df = df.drop(columns=['構成組織'])

    # index列を削除してから、先頭にyear列を挿入する
    df = df.reset_index(drop=True)
    # 列名をcolnamesに変更する
    df.columns = colnames
    df.insert(0, '集計年', i[1:])

    # 加重平均‗要求列を、','を削除してからfloat型に変換する
    df['加重平均‗要求'] = df['加重平均‗要求'].str.replace(',', '').astype(float)

    # '集計年'列の一つ後ろに、加重平均‗要求列の値が100以下であれば'月数'を、そうでなければ'金額'をとる列を挿入する
    df.insert(2, '集計カテゴリ', df.apply(lambda x: '月数' if x['加重平均‗要求'] <= 100 else '金額', axis=1))

    # '属性'と'集計カテゴリ'の両方からなるグループを作って、それぞれのグループごとにソートする。その後、グループの中でindexの小さい順に数字を1から順番に振る。
    df = df.sort_values(['属性', '集計カテゴリ']).reset_index(drop=True)
    df.insert(0, 'id', df.groupby(['属性', '集計カテゴリ']).cumcount() + 1)
    # 'id'が1の時は'年間',2の時は'夏季'という文字列を'集計カテゴリ'の後ろに追加する
    df['集計カテゴリ'] = df.apply(lambda x: x['集計カテゴリ'] + '(年間)' if x['id'] == 1 else x['集計カテゴリ'] + '(夏季)', axis=1)

    df = df.drop(columns=['id'])

    # dfをdf‗masterに追加していく
    master_df = pd.concat([master_df, df], axis=0)


master_df.to_csv('../data/summer_bonus.csv', index=False, encoding='utf-8-sig')

同様に、'winter'のリンク先のデータを処理する。


    colnames = ['属性', '組合数', '組合人員数', '加重平均‗回答', '加重平均‗前年実績','単純平均‗回答', '単純平均‗前年実績']


    # for文で得られたdfをすべて縦に結合する為の空のdfを作成する
    master_df = pd.DataFrame()


    for i in f['winter']:

        pdfpath = f['winter'][i]

        dfs = tabula.read_pdf(pdfpath, 
                        pages="all",
                        lattice=True
                        )

        # dfsの中身をすべて縦に結合する
        df = pd.concat(dfs, axis=0)


        # 業種別がNaNである時、構成組織の値をとる
        df['業種別'] = df['業種別'].fillna(df['構成組織'])

        # 業種別がNaNである行を削除する
        df = df.dropna(subset=['業種別'])
        # 構成組織の列を削除する
        df = df.drop(columns=['構成組織'])    

        # もし変数の数が9個であれば,前の2つの変数を補完してからdfの後ろ2列を削除する
        if len(df.columns) == 9:
            df[df.columns[1]] = df[df.columns[1]].fillna(df[df.columns[-2]])
            df[df.columns[2]] = df[df.columns[2]].fillna(df[df.columns[-1]])
            df = df.drop(columns=[df.columns[-1], df.columns[-2]])

        # index列を削除してから、先頭にyear列を挿入する
        df = df.reset_index(drop=True)
        # 列名をcolnamesに変更する
        df.columns = colnames
        df.insert(0, '集計年', i[1:])

        # 加重平均‗回答列を、','を削除してからfloat型に変換する
        df['加重平均‗回答'] = df['加重平均‗回答'].str.replace(',', '').astype(float)

        # '集計年'列の一つ後ろに、加重平均‗要求列の値が100以下であれば'月数'を、そうでなければ'金額'をとる列を挿入する
        df.insert(2, '集計カテゴリ', df.apply(lambda x: '月数(冬季)' if x['加重平均‗回答'] <= 100 else '金額(冬季)', axis=1))

        # dfをdf‗masterに追加していく
        master_df = pd.concat([master_df, df], axis=0)

    master_df.to_csv('../data/winter_bonus.csv', index=False, encoding='utf-8_sig')

Discussion