本記事はこちらの記事の続きです。
前回のコードを元にして、EDINETからダウンロードしたCSV形式の有価証券報告書から次のような図を作成できるようなコードを作成しました。
作成される図
図は財務三表図解分析法を参考にしてmatplotlibで作成しました。
図の作成をしているコードはplot.pyです。
main.pyではCSVからデータを抽出し、それをjson形式にして保存、plotオブジェクトを作成して図の表示を指示しています。
コードは次のとおりです。
コード(main.py)
import os
import json
import pandas as pd
from plot import Plot
class CSVToJSONConverter:
def __init__(self, file_path):
self.file_path = file_path
self.ID_expression_dict = {
('jpcrp_cor:CompanyNameCoverPage', 'FilingDateInstant'): ['会社名', -1, '単位'],
('jpcrp030000-asr_E02144-000:OperatingRevenuesIFRSKeyFinancialData', 'CurrentYearDuration'): ['売上高(IFRS)', -1, '単位'],
('jpigp_cor:OperatingProfitLossIFRS', 'CurrentYearDuration'): ['営業利益(IFRS)', -1, '単位'],
('jpcrp_cor:ProfitLossAttributableToOwnersOfParentIFRSSummaryOfBusinessResults', 'CurrentYearDuration'): ['当期純利益(IFRS)', -1, '単位'],
('jpigp_cor:AssetsIFRS', 'CurrentYearInstant'): ['資産(IFRS)', -1, '単位'],
('jpigp_cor:LiabilitiesIFRS', 'CurrentYearInstant'): ['負債(IFRS)', -1, '単位'],
('jpigp_cor:CurrentAssetsIFRS', 'CurrentYearInstant'): ['流動資産(IFRS)', -1, '単位'],
('jpigp_cor:NonCurrentAssetsIFRS', 'CurrentYearInstant'): ['固定資産(IFRS)', -1, '単位'],
('jpdei_cor:CurrentPeriodEndDateDEI', 'FilingDateInstant'): ['当会計期間終了日', -1, '単位'],
('jpigp_cor:EquityIFRS', 'CurrentYearInstant'): ['資本(IFRS)', -1, '単位'],
('jpigp_cor:TotalCurrentLiabilitiesIFRS', 'CurrentYearInstant'): ['流動負債(IFRS)', -1, '単位'],
('jpigp_cor:NonCurrentLabilitiesIFRS', 'CurrentYearInstant'): ['固定負債(IFRS)', -1, '単位'],
('jpigp_cor:InterestBearingLiabilitiesCLIFRS', 'CurrentYearInstant'): ['有利子流動負債(IFRS)', -1, '単位'],
('jpigp_cor:InterestBearingLiabilitiesNCLIFRS', 'CurrentYearInstant'): ['有利子固定負債(IFRS)', -1, '単位']
}
self.df = None
self.data = {}
self.json_file_path = ''
def load_csv(self):
try:
self.df = pd.read_csv(self.file_path, encoding='utf-16le', delimiter='\t')
except Exception as e:
print(f"読み込みエラー: {e}")
exit()
def process_data(self):
for i in range(len(self.df)):
row = self.df.iloc[i]
key = (row['要素ID'], row['コンテキストID'])
if key in self.ID_expression_dict:
try:
self.ID_expression_dict[key][1] = int(row['値'])
except ValueError:
self.ID_expression_dict[key][1] = row['値']
self.ID_expression_dict[key][2] = row['単位'] if row['単位'] != '-' else ''
def create_json_data(self):
self.data = {
'CompanyName': self.ID_expression_dict[('jpcrp_cor:CompanyNameCoverPage', 'FilingDateInstant')],
'Sales': self.ID_expression_dict[('jpcrp030000-asr_E02144-000:OperatingRevenuesIFRSKeyFinancialData', 'CurrentYearDuration')],
'OperatingProfits': self.ID_expression_dict[('jpigp_cor:OperatingProfitLossIFRS', 'CurrentYearDuration')],
'NetIncome': self.ID_expression_dict[('jpcrp_cor:ProfitLossAttributableToOwnersOfParentIFRSSummaryOfBusinessResults', 'CurrentYearDuration')],
'Assets': self.ID_expression_dict[('jpigp_cor:AssetsIFRS', 'CurrentYearInstant')],
'Liabilities': self.ID_expression_dict[('jpigp_cor:LiabilitiesIFRS', 'CurrentYearInstant')],
'CurrentAssets': self.ID_expression_dict[('jpigp_cor:CurrentAssetsIFRS', 'CurrentYearInstant')],
'NonCurrentAssets': self.ID_expression_dict[('jpigp_cor:NonCurrentAssetsIFRS', 'CurrentYearInstant')],
'EndDate': self.ID_expression_dict[('jpdei_cor:CurrentPeriodEndDateDEI', 'FilingDateInstant')],
'NetAssets': self.ID_expression_dict[('jpigp_cor:EquityIFRS', 'CurrentYearInstant')],
'CurrentLiabilities': self.ID_expression_dict[('jpigp_cor:TotalCurrentLiabilitiesIFRS', 'CurrentYearInstant')],
'NonCurrentLiabilities': self.ID_expression_dict[('jpigp_cor:NonCurrentLabilitiesIFRS', 'CurrentYearInstant')],
'Interest-bearingCurrentLiabilities': self.ID_expression_dict[('jpigp_cor:InterestBearingLiabilitiesCLIFRS', 'CurrentYearInstant')],
'Interest-bearingNonCurrentLiabilities': self.ID_expression_dict[('jpigp_cor:InterestBearingLiabilitiesNCLIFRS', 'CurrentYearInstant')]
}
def save_to_json(self):
self.json_file_path = f'json_file/{self.data["CompanyName"][1]}{self.data["EndDate"][1]}.json'
# ディレクトリが存在しなければ作成
json_dir = os.path.dirname(self.json_file_path)
if not os.path.exists(json_dir):
os.makedirs(json_dir)
try:
with open(self.json_file_path, 'w', encoding='utf-8') as json_file:
json.dump(self.data, json_file, ensure_ascii=False, indent=4)
print(f"JSONファイルを保存しました: {self.json_file_path}")
except Exception as e:
print(f"JSONファイル保存エラー: {e}")
exit()
def main():
file_path = input("CSV file path: ")
converter = CSVToJSONConverter(file_path)
converter.load_csv()
converter.process_data()
converter.create_json_data()
converter.save_to_json()
chart = Plot(converter.json_file_path)
chart.plot()
if __name__ == "__main__":
main()
main.pyの変更点(特に大きな点)
抽出したデータをjson形式で保存するようにしました。
有利子負債など、図の作成に必要なデータを追加しました。
オブジェクト指向を採用して今後に向けた拡張性可読性を向上させました。
図を作成するコード(plot.py)
import json
import matplotlib.pyplot as plt
import matplotlib_fontja
import matplotlib.ticker as ticker
class Plot():
def __init__(self, json_file_path: str) -> None:
self.json_file_path = json_file_path
def plot(self) -> None:
"""
jsonファイルの読み込みからグラフの作成まで行う関数
"""
# y軸の単位を十億円に変更するフォーマッター.
def billions(y, pos):
return f'{y * 1e-8:,.0f}億円'
# JSONファイルを読み込む.
with open(self.json_file_path, 'r', encoding='utf-8') as json_file:
data = json.load(json_file)
# figとaxオブジェクトを作成.
fig, ax = plt.subplots(figsize=(9, 7))
# y軸にフォーマッターを適用.
ax.yaxis.set_major_formatter(ticker.FuncFormatter(billions))
# グラフのタイトルを設定.
ax.set_title(f"{data['CompanyName'][1]} 決算締日: {data['EndDate'][1]}", fontsize=16, loc='center')
# 色の指定.
colors: dict = {
'Assets': '#1f77b4',
'NonCurrentAssets': '#468cb3',
'CurrentAssets': '#aec7e8',
'NetAssets': '#2ca02c',
'Liabilities': '#d62728',
'NonCurrentLiabilities': '#b41028',
'CurrentLiabilities': '#ff9896',
'Interest-bearingNonCurrentLiabilities': '#9467bd',
'Interest-bearingCurrentLiabilities': '#c5b0d5',
'Sales': '#ff7f0e',
'OperatingProfits': '#d874ea',
'NetIncome': '#f63ad6'
}
# 貸借対照表(借方)の棒グラフ.
bar_Assets = ax.bar(1, data['Assets'][1], width=1, color=colors['Assets'])[0]
bar_NonCurrentAssets = ax.bar(1.25, data['NonCurrentAssets'][1], width=0.5, color=colors['NonCurrentAssets'])[0]
bar_CurrentAssets = ax.bar(1.25, data['CurrentAssets'][1], bottom=data['NonCurrentAssets'][1], width=0.5, color=colors['CurrentAssets'])[0]
# 貸借対照表(貸方)の棒グラフ.
bar_NetAssets = ax.bar(2, data['NetAssets'][1], width=1, color=colors['NetAssets'])[0]
bar_Liabilities = ax.bar(2, data['Liabilities'][1], bottom=data['NetAssets'][1], width=1, color=colors['Liabilities'])[0]
bar_NonCurrentLiabilities = ax.bar(1.75, data['NonCurrentLiabilities'][1], bottom=data['NetAssets'][1], width=0.5, color=colors['NonCurrentLiabilities'])[0]
bar_CurrentLiabilities = ax.bar(1.75, data['CurrentLiabilities'][1], bottom=data['NonCurrentLiabilities'][1]+data['NetAssets'][1], width=0.5, color=colors['CurrentLiabilities'])[0]
# 有利子負債の棒グラフ.
bar_InterestbearingNonCurrentLiabilities = ax.bar(2.75, data['Interest-bearingNonCurrentLiabilities'][1], bottom=bar_CurrentLiabilities.get_y()-data['Interest-bearingNonCurrentLiabilities'][1], width=0.5, color=colors['Interest-bearingNonCurrentLiabilities'])[0]
bar_InterestbearingCurrentLiabilities = ax.bar(2.75, data['Interest-bearingCurrentLiabilities'][1], bottom=bar_CurrentLiabilities.get_y(), width=0.5, color=colors['Interest-bearingCurrentLiabilities'])[0]
# 損益計算書の棒グラフ.
bar_Sales = ax.bar(4, data['Sales'][1], width=1, color=colors['Sales'])[0]
bar_OperatingProfits = ax.bar(3.75, data['OperatingProfits'][1], width=0.5, color=colors['OperatingProfits'])[0]
bar_NetIncome = ax.bar(4.25, data['NetIncome'][1], width=0.5, color=colors['NetIncome'])[0]
# 貸借対照表(借方)につけるテキスト.
ax.text(bar_Assets.get_x() + bar_Assets.get_width() / 2, bar_Assets.get_height() / 2,
data['Assets'][0], ha='center', va='center', color='white', fontsize=12)
ax.text(bar_NonCurrentAssets.get_x() + bar_NonCurrentAssets.get_width() / 2, bar_NonCurrentAssets.get_height() / 2,
data['NonCurrentAssets'][0], ha='center', va='center', color='black', fontsize=10)
ax.text(bar_CurrentAssets.get_x() + bar_CurrentAssets.get_width() / 2,
bar_CurrentAssets.get_y() + bar_CurrentAssets.get_height() / 2,
data['CurrentAssets'][0], ha='center', va='center', color='black', fontsize=10)
# 貸借対照表(貸方)につけるテキスト.
ax.text(bar_NetAssets.get_x() + bar_NetAssets.get_width() / 2, bar_NetAssets.get_height() / 2,
data['NetAssets'][0], ha='center', va='center', color='white', fontsize=12)
ax.text(bar_Liabilities.get_x() + bar_Liabilities.get_width() / 2,
bar_Liabilities.get_y() + bar_Liabilities.get_height() / 2,
data['Liabilities'][0], ha='center', va='center', color='white', fontsize=12)
ax.text(bar_NonCurrentLiabilities.get_x() + bar_NonCurrentLiabilities.get_width() / 2,
bar_NonCurrentLiabilities.get_y() + bar_NonCurrentLiabilities.get_height() / 2,
data['NonCurrentLiabilities'][0], ha='center', va='center', color='black', fontsize=10)
ax.text(bar_CurrentLiabilities.get_x() + bar_CurrentLiabilities.get_width() / 2,
bar_CurrentLiabilities.get_y() + bar_CurrentLiabilities.get_height() / 2,
data['CurrentLiabilities'][0], ha='center', va='center', color='black', fontsize=10)
# 有利子負債につけるテキスト.
ax.text(bar_InterestbearingNonCurrentLiabilities.get_x() + bar_InterestbearingNonCurrentLiabilities.get_width() / 2,
bar_InterestbearingNonCurrentLiabilities.get_y() + bar_InterestbearingNonCurrentLiabilities.get_height() / 2,
data['Interest-bearingNonCurrentLiabilities'][0], ha='center', va='center', color='black', fontsize=10)
ax.text(bar_InterestbearingCurrentLiabilities.get_x() + bar_InterestbearingCurrentLiabilities.get_width() / 2,
bar_InterestbearingCurrentLiabilities.get_y() + bar_InterestbearingCurrentLiabilities.get_height() / 2,
data['Interest-bearingCurrentLiabilities'][0], ha='center', va='center', color='black', fontsize=10)
# 損益計算書につけるテキスト.
ax.text(bar_Sales.get_x() + bar_Sales.get_width() / 2, bar_Sales.get_height() / 2,
data['Sales'][0], ha='center', va='center', color='white', fontsize=12)
ax.text(bar_NetIncome.get_x() + 0.25, bar_NetIncome.get_height() / 2,
data['NetIncome'][0], ha='center', va='center', color='black', fontsize=10)
ax.text(bar_OperatingProfits.get_x(), bar_OperatingProfits.get_height() / 2,
data['OperatingProfits'][0], ha='center', va='center', color='black', fontsize=10)
# B/SからP/Lに線を引く.
plt.plot([bar_Liabilities.get_x() + bar_Liabilities.get_width(), bar_Sales.get_x()],
[bar_Liabilities.get_y() + bar_Liabilities.get_height(), bar_Sales.get_height()],
color='#751d1f')
# x軸の目盛りを消す.
ax.get_xaxis().set_visible(False)
# y軸のグリッドラインを追加
ax.yaxis.grid(True, linestyle='--', color='gray', alpha=0.7)
# グラフを表示.
plt.show()
plot.pyにおけるmatplotlibの書き方について
matplotlibにはMATLABに似た使い方ができる書き方と、オブジェクト指向な書き方があるようです。
詳しくはこちらの記事が非常に参考になります。
私はMATLABを使ったことがない(今学期から使うらしい)からか、MATLABベースの書き方はとてもわかりにくかったので、オブジェクト指向な書き方を使いました。
今後の課題
トヨタ自動車の有報にしか対応していない
一番大きな問題です。図の作成部分(plot.py)を作るより先にこの課題を解決しようとも考えましたが、おそらくかなり時間がかかってしまうので先に図の作成部分を作りました。
正直今も解決策が思いついていません、地道に対応する要素IDを全ての企業のCSVから見てmain.pyの辞書に追加していくしかないのか...
とりあえず図の作成はできたのでこの問題が解決できたらまた記事を書きます。
追記(10/22)
IFRS適用済企業, 国内会計基準適用企業どちらにも対応したコードを公開しました。次のレポジトリに公開しています。
国内会計基準を採用している企業の中に、一部データを自動的に取り出せないようなフォーマットになっている企業もありましたが、多くの企業は自動的に取り出せました。
https://github.com/SushiLovinnn/EDINETCSVToChart
記事もいずれ出そうと思います。