最近キャッシュフロー計算書を眺めていて、各社のキャッシュフローをウォーターフォールチャートで作りたくなった。
またも非常に長いコードになったが一応求めていた仕上がりにはなったと思う。
これを作るために2週間くらいかかってしまった・・・。これでようやく企業間の比較が出来る・・・
import numpy as np import pandas as pd import matplotlib.pyplot as plt import japanize_matplotlib import matplotlib.patheffects as patheffects ##設定値 #文字サイズ設定 s_text_s = 12#文字サイズ小 s_text_m = 15#文字サイズ大 #色設定 color_r = "#dd3333"#赤色 color_b = "#0077bb"#青色 color_gr = "#aaaaaa"#グレー color_gr2 = "#555555"#濃いグレー #グラフ設定 fig_x = 10#グラフ横幅 fig_y = 5#グラフ縦幅 font = 'Meiryo' #棒グラフ設定 bar_w = 0.15#棒グラフ幅 space = 1.2#棒グラフ間スペース label_offset = 80#データラベルのオフセット #線の太さ w_spine = 1#グラフの枠線 w_plot = 1#棒グラフを繋ぐ線の太さ #y軸の設定 y_ticks = [0,1000,2000]#y軸の補助目盛位置 y_ticks_l = [0,1000,2000]#y軸の補助目盛ラベル ##関数定義 #ラベルを縦書きにするコード def tategaki(labels): new_labels = [] for n in labels: n2 = '\n'.join(n.replace("ー", "|"))#"-"を"|"に置換して、一文字ごとに"\n"を挟む new_labels.append(n2) return new_labels ##入力データ data = { '先期末残高': [1000, 500], '営業CF': [500, -300], '投資CF': [300, -100], '財務CF': [-200, 200], '今期末残高': [1600, 300] } index = ['A社', 'B社'] df = pd.DataFrame(data, index=index)#元データのDataFrame ind = np.arange(len(df.columns)) #要素の数 #棒グラフの下側のy座標 bottom_data = { '先期末残高': 0, '営業CF': df['先期末残高'], '投資CF': df['先期末残高'] + df['営業CF'], '財務CF': df['先期末残高'] + df['営業CF'] + df['投資CF'], '今期末残高': 0 } index = ['A社', 'B社'] bottom_df = pd.DataFrame(bottom_data, index=index) #棒グラフの上側のy座標 top_data = { '先期末残高': df['先期末残高'], '営業CF': df['先期末残高'] + df['営業CF'], '投資CF': df['先期末残高'] + df['営業CF'] + df['投資CF'], '財務CF': df['先期末残高'] + df['営業CF'] + df['投資CF'] + df['財務CF'], '今期末残高': df['今期末残高'] } index = ['A社', 'B社'] top_df = pd.DataFrame(top_data, index=index) #棒グラフのx座標を格納したDataFrame bar_x_df = pd.DataFrame(index=df.index, columns=df.columns) for i in range(len(bar_x_df)): for j in range(len(bar_x_df.columns)): bar_x_df.iloc[i, j] = i + (j -2) * bar_w * space #棒グラフの色を格納したDataFrame colors_df = df.applymap(lambda x: color_b if x >= 0 else color_r)#データがプラスなら青、マイナスなら赤 colors_df.iloc[:, [0]] = color_gr #先期末残高は濃いグレー colors_df.iloc[:, [-1]] = color_gr2 #今期末残高は濃いグレー #データラベルのオフセットを格納したDataFrame offset_df = df.applymap(lambda x: 1 if x >= 0 else -1) * label_offset ##グラフの描画 fig = plt.figure(figsize=(fig_x,fig_y), facecolor="white")#グラフの作成 ax1 = fig.add_subplot(111)#subplotの作成 plt.rcParams['font.family'] = font#フォント指定 ##グラフの描画 for n in df.columns: #棒グラフ ax1.bar(bar_x_df[n],#x座標 df[n],#上側y座標 bottom=bottom_df[n],#下側y座標 width=bar_w,#太さ color=colors_df[n]#色 ) #データラベル for m in np.arange(len(df[n])): ax1.text(bar_x_df[n][m],#x座標 top_df[n][m] + offset_df[n][m],#y座標 f"{int(df[n][m]):,}",#テキスト ha='center', va='center',#水平・垂直の中心位置 color=colors_df[n][m],#文字色 fontsize=s_text_s,#文字サイズ path_effects=[patheffects.withStroke(linewidth=4, foreground='white', capstyle="round")]#文字外周を白色に ) for n in np.arange(len(df.columns)-1): #棒グラフを繋ぐ線 ax1.plot([bar_x_df.iloc[:,n] - bar_w * 0.5, bar_x_df.iloc[:,n+1] + bar_w * 0.5],#x座標 [top_df.iloc[:,n], top_df.iloc[:,n]],#y座標 color=color_gr,#文字色 linewidth=w_plot#線の太さ ) ##x軸 #x軸ラベルの位置を格納するDataFrame list_xpos = bar_x_df.values.flatten().tolist()#上段の軸ラベルの座標 list_xpos.extend(np.arange(len(df))+0.000001)#下段の軸ラベルの座標。上段と同じ座標だと上書きされるので少しだけずらす。 list_xlabel_text=list([])#x軸ラベルのテキストを格納するDataFrame list_xlabel_size=list([])#x軸ラベルのサイズを格納するDataFrame for n in np.arange(len(df)): list_xlabel_text.extend(df.columns)#上段のラベルを追加 list_xlabel_size.extend([s_text_s]*len(df.columns))#上段の文字サイズを追加 list_xlabel_text.extend("\n\n\n"+df.index)#下段のラベルを追加 list_xlabel_size.extend([s_text_m]*len(df.index))#下段の文字サイズを追加 list_xlabel_text = tategaki(list_xlabel_text)#縦書きに変換 #x軸の設定 ax1.tick_params(axis='x', which='both', bottom=False, top=False)#xticksの目盛を消す ax1.set_xticks(list_xpos)#x軸の補助目盛設定 xticklabels = ax1.set_xticklabels(list_xlabel_text, color=color_gr2)#x軸のラベルの設定 # 各ラベルに個別の文字サイズを適用 for label, size in zip(xticklabels, list_xlabel_size): label.set_fontsize(size) ##y軸 ax1.set_yticks(y_ticks, color=color_gr)#y軸の補助目盛設定 ax1.set_yticklabels(y_ticks_l, fontsize=s_text_m, color=color_gr2)#y軸のラベル設定 ax1.grid(axis="y")#補助目盛あり ax1.tick_params(axis='both', which='both', bottom=False, top=False, left=False, right=False)#yticksの目盛を消す ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, loc: "{:,}".format(int(x))))#y軸を3桁ごとにカンマを入れる fig.text(0.09, 0.93, '(百万円)', ha='center', va='center', color=color_gr2 , fontsize= s_text_m)#y軸単位 ##枠線を消す ax1.spines['top'].set_linewidth(0) ax1.spines['bottom'].set_linewidth(w_spine)#bottomだけは線を引く ax1.spines['bottom'].set_color(color_gr)#bottomだけは線を引く ax1.spines['left'].set_linewidth(0) ax1.spines['right'].set_linewidth(0) ##軸を背面に移動 ax1.set_axisbelow(True) plt.show()