ほら、あの名前が分からないけど "棒グラフが横にずれていくプロット"。
名前が分からないのでそれらしいキーワードで Google 検索して見つけました。どうやら「滝グラフ」とか「Water Fall チャート」とか「マリオグラフ」とかと呼ばれるらしいです。別の Water Fall チャートと区別するために「Consulting Water Fall」とも呼ぶとか。(Wikipedia: Waterfall Chart)
滝グラフ(たきグラフ、英語:Waterfall chart)は、正負の値の累積的影響を判断する際に役立つ*可視化用グラフである。 レンガが宙に浮いているように列が表示されることから、飛行レンガグラフ(英語:flying bricks chart)やマリオ (ゲームキャラクター)グラフ(英語:Mario chart)として知られている。金融用語で「橋」と呼ばれることも多い。戦略的コンサルティング会社であるマッキンゼー・アンド・カンパニーにより、顧客向けプレゼンテーション手法として普及した。[1][2] インベントリ分析や性能解析などの多様な定量分析にて利用されている。
(Wikipedia: 滝グラフから引用)
今回やったこと
名前はともかく、残念ながら MATLAB にはそれを描く関数がない(R2022b 時点)ので bar 関数を使って描いてみようとしたお話です。
ちなみに waterfall という関数はありますが、こんなプロットです。これは Wikipedia: waterfall plot で紹介されているタイプのプロットですね。ややこしい!
[X,Y] = meshgrid(-3:.125:3);
Z = peaks(X,Y);
waterfall(X,Y,Z)
いや、きっと誰かが作っているだろう・・
プロットの名前がわかれば検索できますね。まずは File Exchange でみてみると出てきたのは File Exchange: Waterfall Chart。MathWorks スタッフが作成した関数の様です。
こんな感じで使います。
x = [1 2 4 3 6 5 3];
waterfallchart(x);
waterfallchart(x, 'width', 0.4);
各バーを線で繋いでいるところにこだわりが見られます。基点と終点は青、プラスの変化は緑、マイナスの変化は・・と色も分けられるようです。
もう一つ Qiita にも【MATLAB】Waterfall図の作成 という投稿が見つかりました。ここで @moko_middo さんは wfall という自作関数を作成されています。
y=[10, 2, -3, -3, 5]; % 基点の値,要因A/B/C/Dによる変動,終点の値は省略
xlabel={'基点','要因A','要因B','要因C','要因D','終点'};
wfall(y,xlabel);
こんな具合です。終点の値は自動で入る模様。そしてラベルも入れられるの点も使い勝手がよさそう!
どうやって描くのか
とはいえ、MATLAB のプロット機能を深掘りするチャンス、ということで描き方を探ってきます。基本的には棒グラフということで bar
関数をベースに考えてみます。
x = [1 3 4 2 5];
bar(x);
ここから各バーの開始位置を変更すれば基本形ができそうな気がしますね。参考:棒グラフのベースラインの変更
大きな制約
ただ、、バーのベースの位置は、 bar
関数のプロパティ BaseValue
で設定することができます。ただ1つの座標軸上に1つの BaseValue
しか持てない・・(R2022b 時点)
- MATLAB Anwers: bar graph : How to show each bar with different base value ?
回避策
なので、まずは簡単な回避策として、ベース値自体も棒グラフの一部としてとして積み上げ棒グラフにしてみることにします。各バーの開始位置は cumsum
関数で計算。
basex = cumsum(x);
basex = [0, basex(1:end-1)] % 最初の開始位置は 0 なので・・
basex = 1x5
0 1 4 8 10
そして元の値と組み合わせて "stacked" オプションで積み上げ棒グラフにしてみる。
xWithbase = [basex; x]
xWithbase = 2x5
0 1 4 8 10
1 3 4 2 5
handle_bars = bar(xWithbase',"stacked"); % 転置に注意
これでベース部分を見えなくすれば・・
handle_bars(1).Visible = "off";
できました・・。あと各バーに名前を付けるなら Axes オブジェクトの XTickLabel ですね。
handle_axes = gca;
handle_axes.XTickLabel = ["A","B","C","D","E"];
あとは必要に応じて終点など追加すればいいですね。
そうは問屋が卸さない
ここまで結構簡単に行きましたが、マイナスの変化があるデータでも試してみると・・
x = [1 3 4 -2 5];
basex = cumsum(x);
basex = [0, basex(1:end-1)];
xWithbase = [basex; x];
handle_bars = bar(xWithbase',"stacked"); % 転置に注意
マイナスの値がちゃんとマイナス側に表示されている。これは bar
関数の本来の用途を考えるとその通りなのですが、今回の用途だととっても残念。
ということで、マイナスの値が入っている場合は少し工夫します。
- 絶対値を使用する
- ベース(バーの開始位置)を下げる
idx_neg = x < 0; % マイナス値の位置特定
basex(idx_neg) = basex(idx_neg) + x(idx_neg); % ベースをマイナス値分下げる
absx = abs(x);
absxWithbase = [basex; absx];
handle_bars = bar(absxWithbase',"stacked");
あとはベースを見えなくして、マイナス値の部分を別の色にすればそれらしくなりそうです。
handle_bars(1).Visible = "off";
% マイナスの値が複数合った時の為、その数にあった色行列を用意する。
handle_bars(2).CData(idx_neg,:) = repelem([0,0,1],sum(idx_neg),1);
色があかんがな・・
棒グラフのバーの色を個別に指定するには CData プロパティを変更すればよかったはずなのですが・・bar
関数の CData に関するヘルプページを見ると
既定では、棒グラフを作成するときに CData
プロパティに RGB 3 成分から成る 3 列の行列が含まれます。行列内の対応する行を変更することで、特定のバーの色を変更できます。
このプロパティは、FaceColor
または EdgeColor
プロパティが 'flat'
に設定されている場合にのみ適用されます。
との記載が。'flat'
にする必要があるらしい。
x = [1 3 4 -2 5];
basex = cumsum(x);
basex = [0, basex(1:end-1)];
idx_neg = x < 0; % マイナス値の位置特定
basex(idx_neg) = basex(idx_neg) + x(idx_neg); % ベースをマイナス値分下げる
absx = abs(x);
absxWithbase = [basex; absx];
handle_bars = bar(absxWithbase','stacked','FaceColor','flat');
handle_bars(1).Visible = 'off';
handle_bars(2).CData(idx_neg,:) = repelem([0,0,1],sum(idx_neg),1);
できた。
さらなるハードルが・・
もしベースの値が常にプラスであればうまく行くのですが・・あくまで bar 関数は 0 スタートのバーを描こうとするため、ベース値までもがマイナスになってしまうと、ここまでの方法ではうまく行きません。
x = [1 -3 4 -2 5];
basex = cumsum(x);
basex = [0, basex(1:end-1)];
idx_neg = x < 0; % マイナス値の位置特定
basex(idx_neg) = basex(idx_neg) + x(idx_neg); % ベースをマイナス値分下げる
absx = abs(x);
absxWithbase = [basex; absx];
handle_bars = bar(absxWithbase','stacked','FaceColor','flat');
今回の回避策ではだめそうです。そして冒頭でも触れた通り、バーのベースの位置はプロパティ BaseValue
で設定することができますが、1つの座標軸上に1つの BaseValue
しか持てないので、各バーで個別の BaseValue
を持たせることができない(R2022b 時点)・・詰みました。
参考:MATLAB Anwers bar graph : How to show each bar with different base value ?
まとめ
ということで、もしベースの値がマイナスにならないのであればという条件付きで** **bar 関数 1 つで描くことができました。
ただその条件を満たさない場合は、各バー毎に別々の座標軸(Axes オブジェクト)を用意して、個別に BaseValue を設定し棒グラフを描くという力技に頼るしかないようです。
その方法をまさに実行しているのが @moko_middo さんの wfall 関数(詳細:【MATLAB】Waterfall図の作成)です。スゴイ。
ぜひ詳細も確認してみてください。