pdf形式のレポート作成をPythonで自動化してみる

#はじめに
・この記事では、家計簿アプリZaimからダウンロードしたデータを使います。もっと言えば、pythonを用いて、データから１年間の収入と支出をまとめたレポートをpdf形式で自動生成します。

・ソースコードと記事は、たぶん書き方が下手くそで美しくないところが多いと思いますが、許してください。

・筆者は「pythonを使いこなせば自動化できる！」って謳い文句につられてpythonを勉強し始めました。
　でも、初心者に優しい実践的な自動化の具体例があまりないと思っています。（大きな偏見あり）
　そこで、特に初心者の方に、pythonで自動化できるものには、こんなのがあるんだと知ってもらえると嬉しいです。

#家計簿アプリZaimってなに？
↓ とりあえず公式のホームページ
https://zaim.co.jp/

筆者が愛用している家計簿アプリ。(2020年5月ぐらいから有料会員。）
月ごとの収入と支出等を、円グラフや積立棒グラフ等を使って分析できるのですごい便利。



そして、Zaimの有料会員になると、集めてきたデータをcsv形式でダウンロードすることが出来る。（ファイルをQiitaの記事に載せる方法が分からなかったので、写真だけ。）

![zaim_data.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659578/a6f052be-bc49-c22b-a49c-35d23d46557e.jpeg)



#作成した各ファイル，フォルダの概要
↓ 作業フォルダの構造
![Zaim_tree.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659578/9c816f9f-f3e2-76b2-f9c8-e441ad89f81d.jpeg)

簡単に言うと、
・dataフォルダの中に、Zaimからダウンロードしたデータファイルが入ってます。（ファイル名：Zaim.2020.csv）

・font_dataフォルダは、pythonでpdfに書き込むためのフォントファイルが入ってます。（ファイル名：GenShinGothic-Monospace-Medium.ttf）

・analyze.pyでは、まず、Zaimからのデータファイル（Zaim.2020.csv）をpandas等で読み取ります。次に、pandas、numpyなどを使ってゴネゴネとデータを整形し、図を作ったり整形したデータを新しいファイルに出力したりします。

・format_dataフォルダでは、analyze.pyで整形したデータが、csv形式で入っています。（ファイル名：diff_money.csv）

・graphフォルダでは、analyze.pyで作成した画像データが、jpg形式で入っています。（ファイル名： in_money.jpg,　out_money.jpg,　money.jpg,　difference_money.jpg）

・report_pdf.pyでは、まず、作成するpdfレポートの細かな設定をします。次に、graphフォルダ内のmoney.jpg,　difference_money.jpgの画像をpdfに貼り付けます。そして、format_dataフォルダ内のdiff_money.csvから、１年間でどれだけの収支になったのかを読み取りpdfに書き込みます。その後、pdfファイルとして出力します。

・reportフォルダでは、report_pdf.pyで出力されたpdfデータが、pdfファイルとして入っています。（ファイル名：balance report.pdf）


#analyze.pyのソースコード
↓ ソースコード

```python:analyze.py　ライブラリのインポートとグラフの体裁

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from matplotlib import rcParams
# グラフの体裁を整える
rcParams['font.family'] = 'sans-serif' #使用するフォント
rcParams['font.sans-serif'] = ['Hiragino Maru Gothic Pro', 'Yu Gothic', 'Meirio', 'Takao', 'IPAexGothic', 'IPAPGothic', 'VL PGothic', 'Noto Sans CJK JP']
rcParams['xtick.direction'] = 'in'#x軸の目盛線が内向き('in')か外向き('out')か双方向か('inout')
rcParams['ytick.direction'] = 'in'#y軸の目盛線が内向き('in')か外向き('out')か双方向か('inout')
rcParams['xtick.major.width'] = 1.0#x軸主目盛り線の線幅
rcParams['ytick.major.width'] = 1.0#y軸主目盛り線の線幅
rcParams['font.size'] = 15 #フォントの大きさ
rcParams['axes.linewidth'] = 1.0# 軸の線幅edge linewidth。囲みの太さ

```
グラフの体裁は良い記事がいっぱいあるから美味しいとこをもらおう。
https://qiita.com/qsnsr123/items/325d21621cfe9e553c17


```python:analyze.py　関数の定義

 # 年月日を月ごとに使いやすい形へ変形
def devide_month(data):
    data = np.array(data)
    # 各月ごとのデータを入れるためのリストを用意
    month_01, month_02, month_03, month_04 = list(), list(), list(), list()
    month_05, month_06, month_07, month_08 = list(), list(), list(), list()
    month_09, month_10, month_11, month_12 = list(), list(), list(), list()
    # dataから月ごとの日付とkeyを取得
    for i in range(len(data.T[1])):
        # 1月のデータをmonth_01に格納
        if data.T[1][i] == "01":
            # 日付とdataframeのkeyをmonth_1に格納
            month_01.append([data.T[2][i], data.T[3][i]])
        elif data.T[1][i] == "02":
            month_02.append([data.T[2][i], data.T[3][i]])
        elif data.T[1][i] == "03":
            month_03.append([data.T[2][i], data.T[3][i]])
        elif data.T[1][i] == "04":
            month_04.append([data.T[2][i], data.T[3][i]])
        elif data.T[1][i] == "05":
            month_05.append([data.T[2][i], data.T[3][i]])
        elif data.T[1][i] == "06":
            month_06.append([data.T[2][i], data.T[3][i]])
        elif data.T[1][i] == "07":
            month_07.append([data.T[2][i], data.T[3][i]])
        elif data.T[1][i] == "08":
            month_08.append([data.T[2][i], data.T[3][i]])
        elif data.T[1][i] == "09":
            month_09.append([data.T[2][i], data.T[3][i]])
        elif data.T[1][i] == "10":
            month_10.append([data.T[2][i], data.T[3][i]])
        elif data.T[1][i] == "11":
            month_11.append([data.T[2][i], data.T[3][i]])
        else:
            month_12.append([data.T[2][i], data.T[3][i]])
    # 月ごとのデータをまとめる
    date_month = [ month_01, month_02, month_03, month_04,
                   month_05, month_06, month_07, month_08,
                   month_09, month_10, month_11, month_12, ]
    return date_month

# dataframeから特定の種別のデータを引っ張って来る
def collect_data_from_dataframe(dataframe, name, date_month):
    monthly_data = list()
    for i in range(len(date_month)):
        # 月の中にデータがあれば、dataframeからデータを取得
        if date_month[i] != []:
            monthly_data.append(dataframe[name][int(date_month[i][0][1]):int(date_month[i][-1][1])+1].tolist())
        else:
            monthly_data.append(None)

    return monthly_data

# 月ごとの支出の合計金額をリストに格納する
def monthly_sum_money(money):
    sum_list = list()
    for i in range(len(money)):
        if money[i] == None:
            sum_list.append(0)
        else:
            sum_list.append(np.sum(money[i]))
    return sum_list

```

```python:analyze.py　グラフのためのデータの準備


# Zaimからダウンロードしたデータのパス
read_data_path = "data/Zaim.2020.csv"
# データを読み込んでpandasに格納。
df = pd.read_csv(read_data_path, encoding="shift-jis")



key = 0
date_2020 = list()

# 日付のデータを取り出す
for date in df["日付"]:
    # 日付を年、月、日ごとに分割してリストに格納
    date = date.split("-")
    # 日付と元々のデータフレームを対応させるために、keyを付け加える
    date.append(str(key))
    # 日付とkeyを合わせたものをリストに加える
    date_2020.append(date)
    key += 1

# date_month.shape: [月][月のデータの番号][日,key]
date_month_2020 = devide_month(date_2020)

# 支出を月ごとにまとめて、リストで保持。
out_money = collect_data_from_dataframe(df, "支出", date_month_2020)
# 支出の各月の合計を計算し、リストで保持。
out_money_monthly = monthly_sum_money(out_money)

# 収入を月ごとにまとめて、リストで保持。
in_money = collect_data_from_dataframe(df, "収入", date_month_2020)
# 収入の各月の合計を計算し、リストで保持。
in_money_monthly = monthly_sum_money(in_money)

# 収支を計算、リスト形式で保持
difference_money = (np.array(in_money_monthly) - np.array(out_money_monthly)).tolist()

```

```python:グラフの作成と保存
# グラフの保存先
save_file_path = "./graph/"
# 年間の支出のグラフ
# 去年の3月より前は完全にデータがないので省く（4月から12月まで）
plt.plot(range(4, 13), np.array(out_money_monthly[3:])/10000, color="blue", label="支出")
plt.xlabel('月')
plt.ylabel('支出 [万円]')
plt.grid(linestyle= '--')
plt.xlim(1,12)
plt.ylim(0,20)
# 軸の目盛間隔などの設定。この場合、x軸は1から12まで1ずつの間隔となる。
plt.xticks(np.arange(1, 13, 1))
plt.yticks(np.arange(0, 21, 1))
# グラフの保存
plt.savefig(save_file_path + "out_money.jpg")
plt.cla()

# 年間の収入のグラフ
plt.plot(range(4, 13), np.array(in_money_monthly[3:])/10000, color="red", label="収入")
plt.xlabel('月')
plt.ylabel('収入 [万円]')
plt.grid(linestyle= '--')
plt.xlim(1,12)
plt.ylim(0,20)
plt.xticks(np.arange(1, 13, 1))
plt.yticks(np.arange(0, 21, 1))

plt.savefig(save_file_path + "in_money.jpg")
plt.cla()


# 年間の支出と収入のグラフ
# 収入は赤線で、支出は青線で表示
plt.plot(range(4, 13), np.array(in_money_monthly[3:])/10000, color="red", label="収入")
plt.plot(range(4, 13), np.array(out_money_monthly[3:])/10000, color="blue", label="支出")
plt.legend(loc="lower right")
plt.xlabel('月')
plt.ylabel('金額 [万円]')
plt.grid(linestyle= '--')
plt.xlim(1,12)
plt.ylim(0,20)
plt.xticks(np.arange(1, 13, 1))
plt.yticks(np.arange(0, 21, 1))

plt.savefig(save_file_path + "money.jpg")
plt.cla()


# 年間の収支のグラフ
# 棒グラフで表現する際に、各月の収支がプラスかマイナスで色分けしたいため、データを二つに分ける。
positive_money = list()
negative_money = list()
positive_x = list()
negative_x = list()
# 収入と支出の差がプラスかマイナスでデータを分ける。
for i in range(len(difference_money)):
    if difference_money[i] >= 0:
        positive_money.append(difference_money[i])
        positive_x.append(i+1)
    else:
        negative_money.append(difference_money[i])
        negative_x.append(i+1)

# 収支がプラスは赤色、収支がマイナスの月は青色で表現。
plt.bar(positive_x, np.array(positive_money)/10000, color="red", label="収支＋")
plt.bar(negative_x, np.array(negative_money)/10000, color="blue", label="収支－")
plt.legend(loc="upper right")
plt.xlabel('月')
plt.ylabel('収支 [万円]')
plt.grid(linestyle= '--')
plt.xlim(1,12)
plt.ylim(-8,8)
plt.xticks(np.arange(1, 13, 1))
plt.yticks(np.arange(-8, 9, 1))

plt.savefig(save_file_path + "difference_money.jpg")
plt.cla()

# pdfレポートの自動作成のために、データを外部に出力
format_data_path = "format_data/"
np.savetxt(format_data_path + "diff_money.csv", difference_money)

```

#report_pdf.pyのソースコード

この記事読んでないとpdfに出力する（書き込む？）やり方分からなくて出来ていなかったと思う。圧倒的感謝！
https://watlab-blog.com/2020/03/21/reportlab-pdf/

```python:

import numpy as np

from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.pdfbase.pdfmetrics import registerFont
from reportlab.pdfbase.ttfonts import TTFont


# フォントを登録
registerFont(TTFont('GenShinGothic',
                    './font_data/GenShinGothic-Monospace-Medium.ttf'))

file_path = 'report/balance report.pdf'  # 出力ファイル名を設定
graph_path_transition = "graph/money.jpg"
graph_path_balance = "graph/difference_money.jpg"


# １年間の収支レポートを作成
paper = canvas.Canvas(file_path)  # 白紙のキャンバスを用意
paper.saveState()  # 初期化
paper.setFont('GenShinGothic', 20)  # フォントを設定

# 横wと縦hの用紙サイズを設定
w = 210 * mm
h = 297 * mm

paper.setPageSize((w, h))  # 用紙のサイズをセット
paper.drawString(w/2 - 90, h - 50,  # テキストの書き込み
                 '１年間の支出と収入')


# 画像を埋め込み(画像ファイルのパス, 横位置, 縦位置, 画像横サイズ, 画像縦サイズ)
paper.drawInlineImage(graph_path_transition, 31*mm, h-130*mm, 148*mm, 111*mm)
paper.setFont('GenShinGothic', 15)  # フォントを設定
paper.drawString(w/2 - 112.5, 160*mm, '図１　１年間の収入と支出の推移')

# ２つ目の画像を埋め込み(画像ファイルのパス, 横位置, 縦位置, 画像横サイズ, 画像縦サイズ)
paper.drawInlineImage(graph_path_balance, 31*mm, h-250*mm, 148*mm, 111*mm)
paper.setFont('GenShinGothic', 15)  # フォントを設定
paper.drawString(w/2 - 67.5, 40*mm, '図２　１年間の収支')

# 収支の結果を記入
difference_money = np.loadtxt('format_data/diff_money.csv', delimiter=',') # 収支のデータを読み込む
paper.setFont('GenShinGothic', 13)  # フォントを設定
paper.drawString(w/2-100, 23*mm,
                 f'１年間全体の収支は、{int(np.sum(difference_money))}円です。')
paper.drawString(w/2-100, 14 * mm,
                 f'つまり、１ヶ月あたり、{int(np.sum(difference_money)/len(difference_money))}円です。')

paper.save()  # PDFを保存


```

#実行結果

###analyze.pyの実行結果

analyze.pyを実行した結果グラフは、in_money.jpg,　out_money.jpg,　money.jpg,　difference_money.jpgの４つが作成されるが、内２つ（in_money.jpg,　out_money.jpg）は、money.jpgと内容が被っているので割愛する。

![money.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659578/1ee285ef-de5b-e3e1-d3b6-c79da0f99466.jpeg)
　　　　　　図１　１年間の収入と支出の推移（money.jpg）


![difference_money.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659578/022e7eb6-bfbc-cca1-cd75-3dbf9687c100.jpeg)
　　　　　　図２　１年間の収支の推移（difference_money.jpg）



###report_pdf.pyの実行結果
report_pdf.pyにより自動作成された家計簿レポートは次の通りになる。

![家計簿レポート.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659578/d44b8737-2dda-f733-3066-fc13708cd8fa.jpeg)