2023/7/10
イントロダクション
インフォグラフィックスなど情報可視化の分野で先駆者とされるTufte(1983)の著書,The Visual Display of Quantitative Informationで提示された「スロープグラフ」。
この頁では,そのTufte自身によるグラフ(リンク先の最上段の図)の構造をある程度斟酌しつつ,VBAで次のスロープグラフを描くことを目的とします。
データの中身は後述するとして,「スロープグラフ」の存在を知らなければ,上の図は外形的には単純な線グラフと識別されることもないかもしれません。そして,それ以上にスロープグラフは描画するのがなかなかに面倒,とくれば,日常で目にする機会もまずありません。しかし,スロープグラフにはときにそれを補って余りある利点2時点間の変化を説明するに類を見ないほど容易な構造があります。
免責事項:
- 掲載のコードは一例として提示しています。諸般のデータに対応するものでも,すべての環境で正常に動作することを保証するものでもありません。
- このコードおよびマクロを利用されたこと,あるいはそれに付随する行為により生じた損害,トラブル等につきまして筆者は一切の責任を負いません。
以下,サブスクリプション版Excel(ver.2004)を使った具体的な手続きです。
元データ
元のデータです。
あるお店に死蔵された,6年にわたる回収済みポイントカードがあります。今回,その自由記入欄に記入された内容を解析し,前半の3年,後半の3年と2期に分けて頻出語(上位20語)を集計しました。
下のシートの左側が前半,右側が後半のそれです。このシート名は「Source」としています。
表を統合する
先の結果をスロープグラフにして,この変化を説明する材料(=いかに変化したか,を言うときの「いかに」の部分)を探そうと思います。
ということでまずは先の結果を統合し,1つの表にしたいと思います。あたらしいシート「Consolidation」を挿入し,任意のセル(結果を出力をはじめる場所; 基本的にはA1を推奨)をアクティブにしておきます。
データタブデータツールグループの統合をクリックします。
統合の設定ダイアログが表示されます。集計の方法が合計になっていることを確認し,統合元範囲に,「Source」シートの左側の表(下図・ノイズ掛けの領域)を指定してから,追加ボタンをクリックします。
右側の表も同様に追加します。これを終えた時点でのダイアログの状態は,下の図のとおりです。
先に指定した統合元は行・列ともに集計キーを含むので,統合の基準の上端行左端列ともにチェックを入れ,OKを返すと
下図のとおり,前半・後半のそれぞれの表をひとつにすることができました。
棒グラフと比べてみて見えてくること
ここでいったん進行を休んで,常識的な選択肢たちと比較をしてみたいと思います。
2つの時点で何かの量を比較したく可視化を考えたとき,おそらく第一に浮かぶのは,2種の異なる色の柱が連なる棒グラフでしょうか。
この場合,どちらかの時点でのみ値の降順,あるいは昇順で並べ替えることができます。ことばを変えれば,両時点いっしょに要素の降順あるいは昇順を保つことはできません。
したがって,この棒グラフから
- ワード「アマトリチャーナ」の前半(freq A)の出現数は?
- ワード「アマトリチャーナ」は増えたか減ったか? そして,その増減の程度は?
といった個別の要素を覗くのは好適ですが,
- 前半の3位のワードは,後半(freq B)では何位に?
- 前半から後半の間に強く伸びたワードは?
- 同,勢いを失ったワードは?
- 上位にありながら後半には消えたワード,あるいは後半になって上位に初登場したワードは?
- 要素の伸びた・減ったに偏りは?
といった全体を俯瞰する必要のある問いに素早く答えるためには,ある意味たいへんな集中力が必要となってきます。
こうした点を鑑みるなら,やはり双方の時点で順的秩序が保たれることを優先してグラフの構成を図った方が都合がよいでしょう。したがって,これをマーカー付き折れ線グラフに変更し,行/列の切り替えを経て,下の図のようにかたちを変えることとします。
すると今度は,あらたな問題点どの線がどの要素を指すか,はげしく識別しづらい問題と直面します。冒頭のリンク先で示されたTufteによるスロープグラフは,上図のような凡例には依拠することなく,要素の名前とその値を示すラベルを両サイドのデータポイント付近に直接付置しているのが特徴です。これによって,“どの線がどの要素を指すかはげしくわかりづらい問題”を無視できますし,線色散漫なありようを甘受する必要もなくなります。
VBEを起動する
リボンの開発タブコードグループにあるVisual Basicボタンをクリックします。
VBE(Visual Basic Editor)が起動します。
メニューの挿入から標準モジュールをクリックすると,エディタにコードウインドウが表示されます。
コードをコピペする
下のコードをすべて選択し,コピーします。
Option Explicit
Option Base 1
Sub DrawSlopeGraph_Main()
' SLOPEGRAPH 2020.0526
' hitorimarketing.net/tools/vba/slope-graph.html
' by hawcas, 2020
Dim target
Dim x As Long, y As Long
Set target = Selection
ActiveSheet.Shapes.AddChart(xlLine).Select
ActiveChart.PlotBy = xlRows
For y = 2 To target.Rows.Count
If target.Range("b1").Offset(y - 1, 0).Value = "" Or _
target.Range("c1").Offset(y - 1, 0).Value = "" Then ' AorB=""
Call SetMarker(y)
Else
Call FillLine(y)
End If
Next y
Call DrawSlopeGraph_Sub1(target)
Call DrawSlopeGraph_Sub2(target)
End Sub
Sub DrawSlopeGraph_Sub1(ByVal t)
' COMPOSE DATALABEL-TEXT
Dim y As Long
Dim size As Long
size = (t.Rows.Count - 1) * 2
For y = 1 To size
t.Range("c1").Offset(y, 2).Value = y
Next y
Dim str As String
str = "=IF(MOD(ROW(),2)=0, " & _
"CONCATENATE(OFFSET($A$1,INT(ROW()/2),0), " & _
""" "", OFFSET($B$1,INT(ROW()/2),0)), " & _
"CONCATENATE(OFFSET($C$1,INT(ROW()/2),0), " & _
""" "", OFFSET($A$1,INT(ROW()/2),0)))"
With t.Range("c1").Offset(1, 3)
.Formula = str
.AutoFill Destination:=Range(t.Range("c1").Offset(1, 3), _
t.Range("c1").Offset(size, 3))
End With
End Sub
Sub DrawSlopeGraph_Sub2(ByVal t)
' PUT DATALABELS
Dim y
Dim size As Long
Dim gObj As ChartObject
Set gObj = ActiveChart.Parent
size = (t.Rows.Count - 1) * 2
Dim cnt As Long
Dim str As String
For y = 1 To size Step 2
cnt = Application.WorksheetFunction.RoundUp(y / 2, 0)
str = "='" & ActiveSheet.Name & "'!" & _
t.Range("c1").Offset(y, 3).Address & ":" & _
t.Range("c1").Offset(y + 1, 3).Address
With gObj.Chart.SeriesCollection(cnt)
.HasDataLabels = True
With .DataLabels
.Format.TextFrame2.TextRange. _
InsertChartField msoChartFieldRange, str, 0
.ShowValue = False
.ShowRange = True
.Format.TextFrame2.WordWrap = msoFalse
End With
End With
' フォントサイズ・フォント(日欧)を変更する場合
With gObj.Chart.SeriesCollection(cnt).DataLabels. _
Format.TextFrame2.TextRange.Font
.size = 9
.NameFarEast = "BIZ UDゴシック"
.Name = "Myriad Pro"
End With
Next y
size = t.Rows.Count - 1
For y = 1 To size
gObj.Chart.SeriesCollection(y).Points(1).DataLabel.Position = _
xlLabelPositionLeft
'gObj.Chart.SeriesCollection(y).DataLabels.Format.TextFrame2.AutoSize = _
'msoAutoSizeNone
Next y
End Sub
Private Sub SetMarker(ByVal i As Long)
With ActiveChart.FullSeriesCollection(i - 1)
.ChartType = xlLineMarkers
.MarkerStyle = xlMarkerStyleDot ' 円の場合:xlMarkerStyleCircle
.MarkerSize = 5 ' マーカーのサイズ
.Format.Fill.ForeColor.RGB = RGB(0, 0, 0) ' マーカーの色
.Format.Line.Visible = msoFalse
End With
End Sub
Private Sub FillLine(ByVal i As Long)
With ActiveChart.FullSeriesCollection(i - 1).Format.Line
.ForeColor.RGB = RGB(0, 0, 0) ' 線の色
.Weight = 1 ' 線の太さ
End With
End Sub
コピーしたコードを,コードウインドウに貼り付けます。
コードが導入出来たので,VBEを閉じます。
では,ここからマクロの具体的な使い方です。
データ範囲を指定する
マクロの実行前に,「Consolidation」シートのデータ範囲を選択しておきます(行・列のラベルも含む)。
マクロを実行する
つづけて,リボンの開発タブコードグループのマクロボタンをクリックします。
マクロダイアログが表示されます。
マクロ名に「DrawSlopeGraph_Main」(このマクロの名前)が表示されていることを確認して,実行ボタンをクリックします。
このマクロは,元表のすぐ右側にデータラベル用に加工した文字列を吐き出してから,目的のグラフを描画します。
もっともグラフは原形にすぎませんので,ここからいくらかの調整を加えていきます。
細部を調整しつつ,「スロープグラフ」を目指す
原形の状態ではあまたのラベルが衝突していて,何とも込み入った感があります。
したがってそれをある程度緩和できる状態になるまで,グラフエリアを縦方向に伸長させていきます。
つづいて,凡例を削除します。これによりプロットエリアが横に広がります。
直前の状態で,もし,下の図にいう赤線の部分でのラベルの揃えができていなかったり,デザイン上の観点からプロットエリアとグラフエリアとの間に若干の余白を挟みたいといった場合は,あわせてプロットエリアの幅についてもbetterを探りながら調整を加えていきます。
Tufteの言う「非データインク(non-data ink)」の一部,具体的には縦軸・横軸や目盛り線を不可視にします。
現状でも残ってしまったラベルの混雑部分(下の図の黄色の部分)を手作業で解消させます。具体的には,マウスを使ってラベルを個々に重ならないよう(SHIFTキーを押したままにし,左右方向をロックしつつ上下方向に)移動させていきます。このとき,ワークシートを一時的に拡大しての作業が有効でしょう。
できる限り整合を保ちつつ,すべての混雑を解消させたのが下の図です。
完成
その他,横軸ラベルをわかりやすい名に変え,ラベル位置を上に持ってくるなど,任意の書式設定を加えて完成です。
上の図を使用すると,先に掲げたいくつかの問い,再掲して
- 前半の3位のワードは,後半では何位に?
- 前半から後半の間に強く伸びたワードは?
- 同,勢いを失ったワードは?
- 上位にありながら後半には消えたワード,あるいは後半になって上位に初登場したワードは?
- 要素の伸びた・減ったに偏りは?
といったQuestionにも答えやすくなっていることがわかるかと思います。