ROOTでヒストグラムの体裁を整える

# 概要

ヒストグラムやグラフなどを他の人に見せるときってありますよね。
欧州原子核研究機構[CERN](https://home.cern)が開発している[ROOT](https://root.cern.ch)で、プロットの体裁を整えてみようと思います。
この記事で扱う内容は以下の通りです。
- 軸を整える
- ヒストグラムを整える
- 凡例をつける

ROOTの[クラスページ](https://root.cern.ch/root/html/HIST_HISTPAINTER_Index.html)を参考にしています。

# 軸を整える

プロットを見る際に、まず横軸と縦軸を見ると思います。
そこで最初に軸のラベル(目盛)とタイトル(軸名)について一工夫してみたいと思います。

## デフォルトの状態

簡単なコードを描いてみます。

````test4-1.py 
from ROOT import TCanvas, TH1F, TRandom, TColor
import ROOT

def fillRandomData( h1, h2 ):
	rdm = TRandom()
	rdm.SetSeed(1)
	for i in range(100):
		h1.Fill( rdm.Gaus( 5, 1) )
		h2.Fill( rdm.Gaus( 1, 2) )

def setHistoStyle( h, lc ):
	h.SetLineColor(lc)

if __name__ == '__main__':

	ROOT.gROOT.SetBatch()

	xmin = 0.
	xmax = 10.
	nbins = 10
	title = ";Variable X;Number of entries" 

	c = TCanvas("c", "c", 600, 600)

	h1 = TH1F("h1", title, nbins, xmin, xmax)
	h2 = TH1F("h2", title, nbins, xmin, xmax)

	# ヒストグラムの体裁を整える
	setHistoStyle(h1, ROOT.kRed+1)
	setHistoStyle(h2, ROOT.kBlue+1)

	# ヒストグラムにデータを詰める
	fillRandomData(h1, h2)

	# ヒストグラムを描画する
	c.cd()
	h1.Draw()
	h2.Draw("SAME")

	c.SaveAs("plot4-1.png")
````

これを実行すると、下図が出力されます。

![plot4-1.png](https://qiita-image-store.s3.amazonaws.com/0/329888/aca689ea-5a1d-7706-40e0-d85171979168.png)

このようなプロットを他の人に見せると

- 軸の変数名が小さくて見えにくい
- メモリの数字が小さい
- 上と右にも目盛りをつけてほしい

などなど言われることが多々あったので、これらを改善してみようと思います。

## 改善後

上に挙げた3点を主に改善していきたいと思います。

### 余白と追加の目盛り

軸のタイトルやラベルの大きさを調整しやすくするために、まず余白を調整していきます。

````test4-2.py 
def setCanvasStyle( c ):
    c.SetLeftMargin(0.15)
    c.SetRightMargin(0.05)
    c.SetTopMargin(0.05)
    c.SetBottomMargin(0.15)
    c.SetTicks()
````

ここではキャンバスの右側と上側は使わないため、できるだけ描画範囲を右上に広げています。
最後の`c.SetTicks()`で、キャンバスの右側と上側に軸の目盛りを追加しています。
その上で軸の体裁を整えます。今回はヒストグラムのクラスに用意されている関数を使用します。

````test4-2.py 
def setHistoStyle( h, lc ):
    size = 0.045
    h.SetLineColor(lc)
    h.SetTitleOffset( 1.3, "X" )
    h.SetTitleOffset( 1.3, "Y" )
    h.SetTitleSize( size, "X" )
    h.SetTitleSize( size, "Y" )
    h.SetLabelSize( size, "X" )
    h.SetLabelSize( size, "Y" )
````
ここにあるように、`SetTitleOffset`などで軸名の文字の大きさや、軸からの距離、目盛りの数値の大きさを調整しています。
この結果、出力される図は次のようになります。

![plot4-2.png](https://qiita-image-store.s3.amazonaws.com/0/329888/d6c621b8-f7a0-f4c7-6240-45ba0203a923.png)

これで少し軸が見やすくなりました。
なお、最初の図の右上に表示されているヒストグラムの基礎統計情報は`SetOptStat`という関数で消しています。

````
ROOT.gStyle = ROOT.gROOT.GetGlobal("gStyle", 1)
ROOT.gStyle.SetOptStat(0)
````

# ヒストグラムを整える

では次にヒストグラムの体裁を整えてみようと思います。
上の図だと次のようなコメントをもらうかもしれません。

- 線が細い
- 白黒で印刷した際に2つのプロットが区別できない
- 誤差棒がない

これらについて改善してみようと思います。

## 改善後

### ヒストグラムの線の太さ

こちらはヒストグラムのクラスで用意されている、`SetLineWidth`を使うことで変更できます。
デフォルトでは`SetLineWidth(1)`となっていますが、`SetLineWidth(2)`というように線を太くしています。

````test4-4.py
def setHistoStyle( h, lc ):
    size = 0.045
    h.SetLineColor(lc)
    h.SetLineWidth(2)
````

### 白黒だと区別できない & 誤差棒

「白黒だと区別できない」というコメントにはいくつか対処の仕方があると思います。例えば、

- 実線と破線を使い分ける
- ヒストグラムの内側の塗りつぶし方を変える
- 種類が異なるマーカーを使う

といった方法が挙げられます。今回は3つ目のマーカーを使ってみようと思います。
この場合、マーカーの設定が必要になるので、`setHistoStyle`の中身を修正します。

````test4-4.py
def setHistoStyle( h, lc, ms ):
    size = 0.045
    h.SetLineColor(lc)
    h.SetLineWidth(2)
    h.SetMarkerStyle(ms)
    h.SetMarkerColor(lc)
````

ヒストグラムを描画する際に、描画オプションを指定します。
今回は線とマーカー、誤差棒をつけるので、次のように指定しています。

````test4-4.py
c.cd()
h1.Draw("HIST E1")
h1.Draw("P SAME")
h2.Draw("HIST E1 SAME")
h2.Draw("P SAME")
````
`h1.Draw("HIST E1")`で線と誤差棒を、さらに`h1.Draw("P SAME")`でマーカーを上書きしています。
（もうちょっとうまいやり方があるかもしれません...）
マーカーの種類については[こちら](https://root.cern.ch/doc/master/classTAttMarker.html)を参照してください。
描画オプションの詳細については[こちら](https://root.cern.ch/doc/v608/classTHistPainter.html)を参照してください。

これらの修正によって次のような図が得られます。

![plot4-3.png](https://qiita-image-store.s3.amazonaws.com/0/329888/37be14d0-fdb5-66f8-bc99-22728640babc.png)

今回は色を`kRed+1`のように指定しています。もちろん`kRed`だけでもいいのですが、原色だと明るすぎてプロジェクターなどで写すと見えづらいことがあったので、+1のように少し色を調整しています。
詳しくは[こちら](https://root.cern.ch/doc/master/classTColor.html)。

# 凡例をつける

このままだと青と赤の2つのヒストグラムが何を示しているのかわからないので、凡例をつけます。
凡例は`TLegend`というクラスを使います。

````test4-4.py
lg = TLegend(0.57, 0.6, 0.95, 0.90)
SetLegendStyle(lg)
lg.SetHeader("Data ID=123 (N=100)")
lg.AddEntry( h1, "Group A", "lp")
lg.AddEntry( h1, "Mean = " + format(h1.GetMean(), '.2f'), "")
lg.AddEntry( h1, "Std. Dev. = " + format(h1.GetStdDev(), '.2f'), "")
lg.AddEntry( h2, "Group B", "lp")
lg.AddEntry( h2, "Mean = " + format(h2.GetMean(), '.2f'), "")
lg.AddEntry( h2, "Std. Dev. = " + format(h2.GetStdDev(), '.2f'), "")
#
c.cd()
h1.Draw("HIST E1")
h1.Draw("P SAME")
h2.Draw("HIST E1 SAME")
h2.Draw("P SAME")
lg.Draw()
````
最初にTLegendを定義しています。4つの引数は、凡例をどこに描くかを指定するための座標を表しています。
最初の2つは凡例の左下の端の位置(x1, y1)、最後の2つは右上の端の位置(x2, y2)です。

そのあと、ヒストグラムを凡例に登録しています。
ヒストグラムの特徴であるMeanなどを一緒に書くように設定していますが、必須ではありません。
詳細は公式の[クラスリファレンス](https://root.cern.ch/doc/master/classTLegend.html#a2aab62ab311496d0631e54558270e778)を見てください。

最後にDrawすることで、プロットの中に描画されます。

ここで、`SetLegendStyle`の中身は次のように書いています。

````test4-4.py
def SetLegendStyle(l):
    l.SetTextSize(0.04);
    l.SetTextFont(42);
    l.SetFillStyle(0);
    l.SetFillColor(0);
    l.SetBorderSize(0);
````

もちろんデフォルトのままでもいいですが、多少見た目をよくするためにこのように指定しています。

最終的な図は下のようになります。

![plot4-4.png](https://qiita-image-store.s3.amazonaws.com/0/329888/6e386ffb-2f57-5f45-e499-88d270979ba0.png)

これで初見でも何が描かれているのか分かりやすくなったのかなと思います。
このようにROOTで図を見やすくするのには少し工夫が必要ですが、このようなコードを毎回書かなくてもよいよう、マクロとして定義しておくこともできます。
そうすると次回以降、同じようなコードを書かなくてもよくなります。その内容はここでは割愛します。

最終的なコードを下に載せておきます。

````test4-4.py
from ROOT import TCanvas, TH1F, TRandom, TColor, TLegend
import ROOT

def fillRandomData( h1, h2 ):
	rdm = TRandom()
	rdm.SetSeed(1)
	for i in range(100):
		h1.Fill( rdm.Gaus( 5, 1) )
		h2.Fill( rdm.Gaus( 1, 2) )

def setHistoStyle( h, lc, ms ):
    size = 0.045
    h.SetLineColor(lc)
    h.SetLineWidth(2)
    h.SetMarkerStyle(ms)
    h.SetMarkerColor(lc)
    h.SetTitleOffset( 1.3, "X" )
    h.SetTitleOffset( 1.3, "Y" )
    h.SetTitleSize( size, "X" )
    h.SetTitleSize( size, "Y" )
    h.SetLabelSize( size, "X" )
    h.SetLabelSize( size, "Y" )

def setCanvasStyle( c ):
    c.SetTicks()
    c.SetLeftMargin(0.15)
    c.SetRightMargin(0.05)
    c.SetTopMargin(0.05)
    c.SetBottomMargin(0.15)

def SetLegendStyle(l):
    l.SetTextSize(0.04);
    l.SetTextFont(42);
    l.SetFillStyle(0);
    l.SetFillColor(0);
    l.SetBorderSize(0);

if __name__ == '__main__':

	ROOT.gROOT.SetBatch()
        ROOT.gStyle = ROOT.gROOT.GetGlobal("gStyle", 1)
        ROOT.gStyle.SetOptStat(0)

	xmin = 0.
	xmax = 12.
	nbins = 12
	title = ";Variable X;Number of entries" 

	c = TCanvas("c", "c", 600, 600)
        setCanvasStyle( c )

	h1 = TH1F("h1", title, nbins, xmin, xmax)
	h2 = TH1F("h2", title, nbins, xmin, xmax)
	#
	setHistoStyle(h1, ROOT.kRed+1, 20)
	setHistoStyle(h2, ROOT.kBlue+1,25)

	#
	fillRandomData(h1, h2)

        #
        lg = TLegend(0.57, 0.6, 0.95, 0.90)
        SetLegendStyle(lg)
        lg.SetHeader("Data ID=123 (N=100)")
        lg.AddEntry( h1, "Group A", "lp")
        lg.AddEntry( h1, "Mean = " + format(h1.GetMean(), '.2f'), "")
        lg.AddEntry( h1, "Std. Dev. = " + format(h1.GetStdDev(), '.2f'), "")
        lg.AddEntry( h2, "Group B", "lp")
        lg.AddEntry( h2, "Mean = " + format(h2.GetMean(), '.2f'), "")
        lg.AddEntry( h2, "Std. Dev. = " + format(h2.GetStdDev(), '.2f'), "")

	#
	c.cd()
	h1.Draw("HIST E1")
	h1.Draw("P SAME")
	h2.Draw("HIST E1 SAME")
	h2.Draw("P SAME")
        lg.Draw()

	c.SaveAs("plot4-4.png")
````