公開日: 2024/5/20
最終更新: 2024/5/20
この記事では、マーケティングキャンペーン管理やタスク管理の文脈で使用されるようなGantt Chartを、より視覚的に分かりやすくする方法について書きます。 具体的には、下図上側のような典型的なGantt Chartを下側のように並び替える方法について解説します。
上図にはそれぞれ50個のGanttが表示されています。 典型的なGantt Chartで表示した場合、50個全てが縦に並ぶことから1つ1つのGanttが小さくなります。また結果的に対角線上のスペースのみを使用する形になり、画面の一部しか使用できていません。 このため限られたスペースにおける可読性に難があり、またマウス等での選択やホバーも難しいかもしれません。
(スクロールバーを表示させることも可能ですが、その場合は全体を見ることが難しくなります)
加えて、この状態では期間の重なりを把握しにくく、例えばキャンペーン管理の文脈で言えば、並行して何個のキャンペーンが実施されたのか分かりにくくなっています。
一方で再配置されたGantt Chartでは画面を効果的に使用できており、それに伴い可読性も向上しています。またキャンペーンがどの程度並行しているかが分かりやすく、Ganttの密度感を可視化することにも成功しています。 並行しているキャンペーン数やそれらの性質・対象を把握することができれば、予算の最適化や実施可否の検討など、現状把握を超えて改善に繋げることが出来るかもしれません。
このような効果的なGantt Chartを作成するための方法を、この記事では順を追って解説していきます。
本記事で使用したワークブックはこちらからダウンロードできます。
また使用したダミーデータはこちらからダウンロードできます。
準備1: データを準備する
最初に使用するデータについて説明します。
Campaign Master.csvは、CampaignIDの1行ごとに開始日と終了日が記載されている、キャンペーンマスタを想定したデータです。 一般的なGantt Chart自体はこのデータからも作成できますが、今回作成するGantt Chartは開始日と終了日の間の日付についても計算に使用したいため、以下のようにCalendar.csvと結合します。
この結合により、キャンペーン開始日から終了日までの日付を埋めるようにデータが作成されます。
ちなみにこのデータでは、簡単のためにCampaign IDは開始日で事前に採番されています。 もしお使いになりたい実務データでIDが開始日順に採番されていない場合は、Tableau上でIDディメンションを開始日順にソートすると良いと思います。
また、ここでは各キャンペーンは開始日の0時0分から開始され、終了日の23時59分に終了していると想像ください。つまり今回の想定では、終了日もキャンペーン期間に含みます。 実際のキャンペーン施策では開始時刻や終了時刻は任意であり、またマスタ定義によっては終了日をキャンペーン期間に含まないこともあると思いますが、今回のデータは上記の想定とします。
準備2: 表計算について簡単な復習
今回取り上げる内容では表計算を多用します。 ここでは表計算自体について深く取り上げず、この記事の内容を理解する上で必要な点に絞って言及します。 表計算について詳細に解説されている記事は多くあるため、必要あれば調べてみてください。
表計算の計算方向について
今回取り上げる内容では、以下のようなCampaign IDとDateのクロス集計を念頭に進めます。
これから様々な表計算を作成していきますが、全ての操作において重要なことは、このクロス集計表に対する表計算の計算方向を想像することです。
例えば以下はDate方向に表計算を実施した結果です。表計算の方向を可視化するため、ここではINDEX()を用います。
同様にCampaign ID方向に実施した結果です。
最後に(Campaign ID, Date)方向、つまり各Campaign ID内でDate方向に計算させ、その後に次のCampaign IDに移動する方向に計算させた結果です。
全て同じINDEX()という関数を使用していますが、計算させる方向によって結果が異なります。 以降で出現する表計算については、それぞれどの方向に実施する必要があるのかを意識しながら読み進めて頂くと、今回の内容が理解しやすくなると思います。
PREVIOUS_VALUE()を使った累計のリセットについて
今回の内容ではPREVIOUS_VALUE()が重要になります。
PREVIOUS_VALUE()は、それが使用される計算フィールドの出力結果を、指定の値に戻すことを可能にします。
見た方が早いと思いますので、以下2つの計算フィールドの結果を比較してみましょう。
RUNNING_SUM(1)
RUNNING_SUM(1)
RUNNING_SUM(1)_Reset_at_5x
IF MIN([Campaign ID])%5 = 0
THEN
0
ELSE
1 + PREVIOUS_VALUE(0)
END
PREVIOUS_VALUE()を用いた計算フィールドは、Campaign IDが5の倍数で0、それ以外で1 + PREVIOUS_VALUE(0)を返します。 この実装により5の倍数で累計が0にリセットされ、それ以降は次の5の倍数まで累計が再開されます。
この記事で扱う実装では、この累計リセットを活用します。
まずはリセットしながら積み上げる
この章では、以下のGantt Chartを作成します。
このGantt Chartでは以下の操作により、典型的なGantt Chartよりも積み上げ幅が小さくなっています。
Ganttの開始日が他のGanttの期間に含まれていない場合、縦軸の位置を0にリセットする。
Ganttの開始日が直前のGanttの期間に含まれている場合のみ積み上げる。
この可視化を作成するための手順を見ていきましょう。
必要な計算を準備する
まずは以降の計算に必要になる、以下の計算フィールドを作成します。
0_Is_NULL_ID
ISNULL([Campaign ID])
0_INDEX
INDEX()
0_Is_Gantt_Start
INDEX() = 1
0_Is_Gantt_Start_Overlap
MIN([Date]) <= LOOKUP(MIN([Date]), -1)
0_Is_NULL_IDについて、以降全てのシートでこの計算フィールドをフィルターに入れ、FALSEのみ使用されるようにします。 表計算を使う過程でNULL値のCampaign IDが出てきてしまうため、おまじない的に使用します。
0_Is_Gantt_Start および 0_Is_Gantt_Start_Overlap については、実際に意味するところを視覚的に見てみましょう。
まずは0_Is_Gantt_Startを見てみます。これは「Ganttの開始日が他のGanttの期間に含まれていない場合、縦軸の位置を0にリセットする」操作に使用されます。
表計算はDate方向に実施しています。Date方向についてINDEX() = 1を満たすのは、図中で赤色になっている各Ganttの最初のマークです。 したがって、この計算フィールドは各Ganttの一番最初かどうかを判定しています。 次に0_Is_Gantt_Start_Overlapを見てみます。 これは「Ganttの開始日が直前のGanttの期間に含まれている場合のみ積み上げる」操作に使用されます。
この表計算では、視覚的には以下のように現在のGanttの開始点と、1つ前のGanttの終了点を比較しています。 現在の開始点 <= 1つ前の終了点 ならばTRUEを返します。
積み上げ判定の計算フィールドを作る
作成したいGantt Chartでは「Ganttの開始日が直前のGanttの期間に含まれている」ときに積み上げます。 この判定自体は 0_Is_Gantt_Start_Overlap により実装されていますが、この真偽判定を数字に変換するため、以下の計算フィールドを作成します。
1_Delta_1
IF [0_Is_Gantt_Start_Overlap]
THEN
1
ELSE
0
END
まずはここまでの積み上がり方を理解するために、以下の計算フィールドを作成してみましょう。
tmp_Gantt_Placeholder_0.5
RUNNING_SUM([1_Delta_1])
ここまでの内容により「Ganttの開始日が直前のGanttの期間に含まれている場合のみ積み上げる」ことが出来ました。
ただし作成したいGantt Chartには「Ganttの開始日が他のGanttの期間に含まれていない場合、縦軸の位置を0にリセットする」ことが必要です。
例えばCampaign ID = 7のGanttはこの条件を満たすので、このGanttの位置を0にリセットしたいです。
この要件を満たすため、以下の計算フィールドを作成します。
2_Gantt_Placeholder_1
IF [0_Is_Gantt_Start] AND [0_INDEX] = 1
THEN
0
ELSE
[1_Delta_1] + PREVIOUS_VALUE(0)
END
この計算フィールドでは、そのGanttの開始日付において、そのGanttの下に何もマークが存在しない場合に0にリセットし、それ以外では 1_Delta_1 を累計します。
実際の結果を見てみましょう。
上記までは表示するキャンペーン数を絞っていました。改めて50個全てのキャンペーンを見てみましょう。
ここまでの内容で積み上がりが多少改善したものの、まだ改善の余地はありそうです。特に右半分は多くのキャンペーン期間が重なっているため、ほぼ右上に向かって積みあがるような形になっています。
これは累計に使用した 1_Delta_1 の取る値が、0または1のみであることに起因します。
リセット分を除けば、Ganttの高さは増加することしかできません。
次の章では 1_Delta_1 に負の値を取らせることを考えます。
位置が減少するようにする
この章では、以下のGantt Chartを作成します。
このGantt Chartでは、以下の条件を追加で満たすときに 1_Delta_1 の結果を-1に変更することで、Gantt Chartの積み上がり方を変更します。
高さ2以上のものについて、そのGanttの2つ下が空いている
Ganttをマイナス方向に移動させた場合に、その次のGanttが他のGanttと被らない
具体的には、下図で先頭が赤色になっているGanttが移動します。
この可視化を作成するための手順を見ていきましょう。
移動先が空いているものを見つける
まずは、2個下が空いているGanttを見つけます。
ただし高さ1のものは必ず2個下、つまり-1は空いているため、ここでは除外します。
3_Is_Having_Room_to_Move
[2_Gantt_Placeholder_1] > 1
AND (
[2_Gantt_Placeholder_1] - LOOKUP([2_Gantt_Placeholder_1], -2) > 2
OR ISNULL(LOOKUP([2_Gantt_Placeholder_1], -2))
)
この条件を満たすGanttは以下です。
ただし、これだけの条件では不都合が生じます。 例えば14番のGanttですが、このGanttを13番の真下に移動させると、14番の上にある15番のGanttが13番のGanttと重なってしまいます。 これは14番から見て「直下」のGanttである13番の終了日が、「直後」のGanttである15番の開始日よりも未来にあることが原因です。 逆に20番を見てみると、「直下」の19番の終了日は「直後」の21番の開始日よりも過去にあるため、20番の位置を19番の直下に移動しても、21番のGanttは19番のものと重なりません。
ということで、周囲のGanttを加味した条件を追加しましょう。
移動させても問題ないものを見つける
上述の通り、それぞれのGanttの「直下」のGanttの終了日と、「直後」のGanttの開始日を比較する必要があります。
これを計算フィールドに落とし込みましょう。
4_Campaign_Length
SIZE()
4_Start_Date_of_Next_Gantt
LOOKUP(MIN([Start Date]), [0_Campaign_Length])
4_End_Date_of_Below_Gantt
LOOKUP(MIN([End Date]), -1)
5_Is_No_Overlapping
[3_Start_Date_of_Next_Gantt] > [3_End_Date_of_Below_Gantt]
OR
ISNULL([3_Start_Date_of_Next_Gantt])
先ほどの「2個下が空いている」条件に 5_Is_No_Overlapping の条件を加えたものが以下です。両方を満たすGanttの先頭が赤く表示されています。
実際に移動させ、高さを調整した結果を見てみましょう。
積み上げ方を調整する
ここまでの議論を元に、積み上げ方を調整する計算フィールドを作成していきましょう。
6_Delta_2
IF [0_Is_Gantt_Start_Overlap]
AND [3_Is_Having_Room_to_Move]
AND [5_Is_No_Overlapping]
THEN
-1
ELSE
[1_Delta_1]
END
この計算フィールドは、要は直前の画像の赤い点では-1を返し、それ以外では 1_Delta_1 を返します。
つまり赤い点では積み上がるのではなく、高さを1つ下げます。
実際の結果を見るために、以下の計算フィールドを作成してみましょう。
tmp_Gantt_Placeholder_2_PosNeg
IF [0_Is_Gantt_Start] AND [0_INDEX] = 1
THEN
0
ELSE
ZN([6_Delta_2]) + PREVIOUS_VALUE(0)
END
上記の計算フィールドを縦軸に使用したものが下図です。 ラベルには 6_Delta_2 の値を表示しています。
ここまでの操作により、元々は最大高さが24まであったGantt Chartが、最大高さが8まで抑えられました。
ただし図をみると、右側ではマイナスが続いているため、負軸方向にGanttが向かい続けてしまっています。
これを調整するために、ABS()を用いて高さは必ず0以上になるようにします。
7_Gantt_Placeholder_2
ABS(
IF [0_Is_Gantt_Start] AND [0_INDEX] = 1
THEN
0
ELSE
ZN([6_Delta_2]) + PREVIOUS_VALUE(0)
END
)
ここまでの内容により、以下のGantt Chartを作成することができました。
以下は縦軸に 7_Gantt_Placeholder_2 を使用した図です。
ここまでの内容でこの記事で作成したかったGantt Chartは概ね出来ています。
また画面効率の改善やGanttの密度を見る目的は、十分に達成されているように思います。
ただしよく見ると、いくつかのGanttは「浮いていた」ことが分かります。 一番顕著なのは17番で、真下に何もなく、Gantt全体がまるで宙に浮いているようです。
また5番や30番のように、Ganttの開始点の真下には何もないので、これも位置を下げる余地がありました。
ということで、次の章で最後の調整をしていきましょう。
浮いたものを落とす
この章では、以下のGantt Chartを作成します。
といっても簡単なロジックで実現可能なので、本題にそのまま入ります。
先ほどの議論と同様に、6_Delta_2 について調整することを考えます。
以下、上述の図にある17番を例にします。
17番の下には、高さ2にある長いGanttが1本あります。
17番をこの長いGanttの真上に移動させるには、両者間の高さの差分だけ現在の高さから引いてやり、その後1つ上に動かしてあげれば良いことになります。
ただし、この操作は真下にGanttがある場合は行いたくありません。
また、上記のような浮いているGantt以外については、そのまま 6_Delta_2 で計算されている値を使えば良いです。
ここまでの操作を計算フィールドに落とし込みます。
8_Delta_3
IF [0_Is_Gantt_Start]
AND [7_Gantt_Placeholder_2] - LOOKUP([7_Gantt_Placeholder_2], -1) > 1
THEN
-(
[7_Gantt_Placeholder_2]
- LOOKUP([7_Gantt_Placeholder_2], -1)
) + 1
ELSE
[6_Delta_2]
END
9_Gantt_Placeholder_3
ABS(
IF [0_Is_Gantt_Start] AND [0_INDEX] = 1
THEN
0
ELSE
ZN([8_Delta_3]) + PREVIOUS_VALUE(0)
END
)
以下は縦軸に 9_Gantt_Placeholder_3 を使用した図です。 より画面を効果的に使用できるようになりました。
ここまでの内容を比較してみましょう。
最初の典型的なGantt Chartから順に、画面をより効果的に使えるようになっていることが分かります。
それに伴い、Ganttの密度も視覚的に分かりやすくなりました。
ただし最終形に近づくにつれ、計算フィールドや表計算を設定する手間が煩雑になってきます。
計算を最後まで追い、手元で実施された方は実感を得ていると思いますが、表計算のネストが大変なことになっています。
お手元のデータに合わせて適切な方法を合わせてお使いください。
実務上の多くの場合では、1番のリセット付き単純積み上げでも十分なのかなーと思います。
おまけ:見た目の調整などについての雑記
最後に何点かつぶやきます。
もっとGantt Chartっぽい見た目にしたい
上記までの内容では、簡単のための線マークを使用した、疑似的なGantt Chartを作成してきました。 ただしこの方法には懸念があり、線マークなので両端が丸みを帯びるため、横幅によっては重なっているように見えてしまう箇所が生じるかもしれません。 ということで、Ganttマークを使用する場合についてワークブックに含めておきました。
そこまで大変では無いので、詳細は中身を確認ください。
Ganttを隣接させたくない
あるGanttの開始日が直前のGanttの終了日のまさに翌日の場合、どうしても真横に隣接してしまうため、すこし可読性が低くなるかもしれません。 横方向に動かすとGanttの開始と終了を歪めてしまうため、この点を解決するためには、上下方向に動かす必要があります。
ただし、軽く試行錯誤していた限りでは、ちょっと順を追って検証・改修する必要がありそうでした。データの結合自体にも変更が必要になる可能性もあります。
こちらについては、また続編として記事化すると思います。
今回の方法が適さない場合
最後に、今回の方法を使うべきではないと思われる場合に言及します。
今回の方法では画面を効果的に使うことに焦点を当てました。 そのためにGanttの並び順を上下色々と動かしたわけですが、このためにGanttの順番の分かりやすさは犠牲になりました。
例えばウォーターフォール型プロジェクトの管理に使用しているGantt Chartの場合、各タスクの順番が重要だと思われるので、今回の方法は必ずしも適切でないかもしれません。
画面を効果的に使うことを優先するか、Ganttの順番の見やすさを優先するか、要件に応じて使い分けください。
シートに不必要なディメンションを追加しない方が良いという話
今回の解説では、シートに使用されていたディメンションは日付とIDの2つを想定していました。
実はシートには開始日や終了日の情報も入っていますが、それらは全て集計(ATTRなど)で入っています。これらがディメンションとして追加された場合、おそらく表計算の設定を再度調整する必要が出てきます。
追加するディメンションが表計算に使用されず、また必要なデータの粒度の作成に寄与しないのであれば、特に表計算を使用しているシートには、ディメンションではなく集計として追加した方が良いと思います。
例えば今回のデータにはありませんが、シートにキャンペーン名を追加したい場合、IDでデータ粒度は十分だと思いますので、こちらは集計として追加することを推奨します。
最後に
Gantt Chartの密度を上げ、より効果的な可視化を作成することを目的に、その手順を記載しました。
この記事では説明のために計算フィールドを分けて実装しましたが、表計算は計算させる方向が同じものは1つの計算フィールドにまとめることも可能です。 必要に応じて計算をまとめても良いかもしれません。
表計算を何度もネストするため実装は大変なのですが、Gantt Chartの可読性を大きく向上するアプローチだと思いますので、ぜひ活用してみて下さい。
質問などありましたら、XかLinkedinまでお願いします。