pandasのpivot_tableで比較的簡単に処理できます。本記事では、データ処理の例として、愛知県の次のリンク先https://www.pref.aichi.jp/site/covid19-aichi/の中程にある新型コロナの「愛知県内の発生事例」のPDFファイル「8月まで [PDFファイル/357KB]」を使用しました。このPDFファイルは下図のような表が99ページに渡って複数あります。
本コードの実施例を以降に示します。本コードを実行すると、上記pdfファイルを読み取って、下図のようにcsvファイルを作成します。これを行うためのPythonライブラリは「PyPDF2 」と「camelot」を使用しています。
そして、下図のように感染者数が多い市町村順に横棒グラフ化します。これにより、視覚的に比較確認できます。
次に、下図のようにクロス集計を行ってcsvファイルにします。これを行うには、「pandas」のpivot_tableを使用すると簡単に作成できます。
最後に、配列(リスト)で指定した複数の市町村を下図のように時系列でグラフ化します。これにより、感染者数の変化を可視化できて比較できます。
■本プログラム
# -*- coding: utf-8 -*- # conda install -c conda-forge camelot-py # pip install japanize-matplotlib import warnings from pathlib import Path from tqdm import tqdm from PyPDF2 import PdfFileReader from PyPDF2 import PdfFileWriter import pandas as pd import camelot import os import numpy as np import matplotlib.pyplot as plt import matplotlib.ticker as ticker import japanize_matplotlib import datetime now = datetime.datetime.now() now = now.strftime("%y%m%d") import time ### pdfをページ毎に分割してpdfを生成する。またそのファイル名をリストで返す ### PyPDF2を使用 def my_split_pdf_func(input_pdf): file_name_list = [] with open(input_pdf, 'rb') as f1: input = PdfFileReader(f1) pgnum = input.getNumPages() for i in range(pgnum): file_name = str(i+1) + "page.pdf" output = PdfFileWriter() output.addPage(input.getPage(i)) outputfile = open(file_name, 'wb') output.write(outputfile) outputfile.close() file_name_list.append(file_name) print(file_name_list) return file_name_list ### ランキング作成して横棒グラフ化する関数。引数Xはカウントする列名、引数Yは数えた数 def create_ranking_csv_img_func(DF, X_column, Y_column): df = DF.dropna(subset=[X_column]) df = df.dropna(axis=1) df = df.groupby([X_column]).size() df = df.reset_index(name=Y_column) df = df.sort_values(by=Y_column, ascending=True) print(df) df_x = df[X_column] df_y = df[Y_column] y_np = np.array(df_y) plt.figure(figsize=(8,10)) plt.barh(range(len(df_x)), df_y, tick_label=df_x, align="center", color="magenta", height=0.8) df_x_r = df_x.reset_index(drop=True) # インデックスをリセットして振り直す with open(save_name + '_ranking.csv', 'a') as f: for i, j in enumerate(y_np): plt.text(j, (i+0.5), str(int(j)), ha='left', va='top') mystr_list = [df_x_r[i], str(int(j)), '\n'] mystr = ','.join(mystr_list) f.write(mystr) #plt.title() plt.xlabel('count', fontsize=10) if range_X: plt.xlim([range_X[0], range_X[1]]) plt.grid(which="major", axis="x", color="black", alpha=0.8, linestyle="-", linewidth=1) plt.tight_layout() #plt.show() plt.savefig(save_name + '_ranking.png') plt.close() ### ピボットテーブル処理 def create_time_series_plotgraph_func(DF, TIME, LEGEND, COUNT, Y_list): print(DF) print(TIME) # 日付を(文字列型でなくて)datetime型に変換する DF[TIME] = DF.apply(lambda a: f"{str(2020)}-{a[TIME]}", axis=1) # 西暦2020年を追記 DF[TIME] = DF[TIME].str.replace('月', '-') # 月を-に置換 DF[TIME] = DF[TIME].str.strip('日') # 日を削除 DF[TIME] = pd.to_datetime(DF[TIME]) # datetime型へ変換(これで後で、日付順に並び替え出来る) print(DF) # pivot_tableで、日付をインデックスに、凡例を列に、クロス集計 df = pd.pivot_table(DF, index = TIME, # 発表日 columns = LEGEND, # 住居地 values = COUNT, # count aggfunc='sum') print(df) # インデックス(日付)でソートして、csvファイルで保存 df_sorted = df.sort_index() cross_data_file_name = save_name + '_' + TIME + '_' + LEGEND + '_cross.csv' df_sorted.to_csv(cross_data_file_name, header = True, index = True, encoding = 'cp932', sep = ',') # 保存したcsvファイルを読み出す(前処理不要なcsvファイルを読み込む場合はここからスタート) df = pd.read_csv(cross_data_file_name, encoding='cp932') ### 時系列グラフを作成する # 並び替えた日付の型を文字列へ変換する(横軸として表記するため) #df = df_sorted.astype({TIME: 'object'}) fig = plt.figure(figsize=(15,6)) plt.style.use('bmh') # グラフの見栄え 'bmh' 'dark_background' 'fivethirtyeight' ax = fig.add_subplot(1,1,1) for i, Y in enumerate(Y_list): print(Y) try: df.plot(x=TIME, y=Y, kind='line', ax=ax, color=cm(i)) ax = plt.gca() x_ticklabels = ax.get_xticklabels() # デフォルト目盛り表記をゲット plt.setp(x_ticklabels, rotation=45, fontsize=14) # 目盛り表記を45度回転 tick_spacing = 15 # 目盛り表示する間隔 ax.xaxis.set_major_locator(ticker.MultipleLocator(tick_spacing)) except KeyError: pass y_ticklabels = ax.get_yticklabels() # デフォルト目盛り表記をゲット plt.setp(y_ticklabels, rotation=0, fontsize=16) # 目盛りのフォントサイズを変更 ax.set_xlabel(None) # 軸ラベルを表示しない場合は,Noneを指定 ax.set_ylabel(COUNT, fontsize=16) ax.grid(True) plt.legend(bbox_to_anchor=(1.01, 1), loc='upper left', borderaxespad=0, fontsize=16) plt.rcParams['font.size'] = 18 plt.subplots_adjust(right=0.7) plt.tight_layout() # 文字はみだしを防ぎグラフレイアウトを微修正する fig.savefig(cross_data_file_name + '.png') plt.close() def main(): ### pdfをページ毎に分割してpdfを生成する。またそのファイル名をリストで返す page_pdf_path_list = my_split_pdf_func(pdf_path) ### csvファイルへ変換する # 1頁毎にDataFrame形式に変換後、リストへ追記してゆく for page_pdf_path in tqdm(page_pdf_path_list): # pdfファイルの1頁から最終頁までリストで取得する table = camelot.read_pdf(page_pdf_path, pages='1-end', split_text=True, strip_text='\n', encoding='cp932') # データフレーム化 try: df = table[0].df print(df) except IndexError: print('read error of pdf2df -> pass') # csvファイルへ出力 # cp932でファイルオブジェクトを作成する。この時、エラー文字は無視する with open(now + '_' + file_name + '.csv', mode="a", newline='', encoding="cp932", errors="ignore") as f: df.to_csv(f, index = False, header = False, mode = 'a', encoding='utf-8') # 書き出す文字コードを指定 'utf-8' 'shift_jis' 'cp932' os.remove(page_pdf_path) # csvファイルを読み出し(pdfでなくてcsv読み出す時はここからスタート) df_csv = pd.read_csv(now + '_' + file_name + '.csv', header=0, index_col=0, encoding='cp932') # ランキングを作成する関数 create_ranking_csv_img_func(df_csv, target_column, 'count') # ピボットテーブルの前処理 df_new = df_csv.assign(count=1) # 各行に個数の列がない場合に追加 # ピボットテーブルで処理 create_time_series_plotgraph_func(df_new, x_time, target_column, 'count', y_list) if __name__ == '__main__': pdf_path = '345284_愛知県8月まで.pdf' #pdf_path = '345533_愛知県9月.pdf' file_name = Path(pdf_path).stem #ファイルパスから、拡張子抜きファイル名を取得 save_name = now + "_" + file_name # ランキング作成用 target_column = '住居地' range_X = () # ピボットテーブルでクロス集計後の時系列グラフ化用 x_time = '発表日' y_list = [ '名古屋市', '豊田市', '一宮市', '岡崎市', '刈谷市', '日進市', ] # グラフにプロットするデータの色マップの指定 cm_name = 'gist_rainbow' # 'flag' 'jet' 'gist_rainbow' 'spring' 'bwr' 'cool' cm = plt.get_cmap(cm_name, len(y_list)) # わかっている警告(表示しなくてよい警告)は非表示にする warnings.resetwarnings() warnings.simplefilter('ignore', UserWarning) start = time.time() main() print('run time: ', time.time() - start) # 処理時間を表示する print('finished')
●関連記事
以上
<広告>
リンク