plotter.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #!/usr/bin/env python3
  2. import math
  3. import matplotlib as mpl
  4. # mpl.rc('font', **{'family': 'sans-serif', 'sans-serif': ['Helvetica']})
  5. # mpl.rc('font', **{'family': 'serif', 'serif': ['Palatino']})
  6. mpl.rc('text', usetex=True)
  7. mpl.rc('figure', dpi=200)
  8. mpl.rc('savefig', dpi=200)
  9. def add_decorations(axes, luminosity, energy):
  10. cms_prelim = r'{\raggedright{}\textsf{\textbf{CMS}}\\ \emph{Preliminary}}'
  11. axes.text(0.01, 0.98, cms_prelim,
  12. horizontalalignment='left',
  13. verticalalignment='top',
  14. transform=axes.transAxes)
  15. lumi = ""
  16. energy_str = ""
  17. if luminosity is not None:
  18. lumi = r'${} \mathrm{{fb}}^{{-1}}$'.format(luminosity)
  19. if energy is not None:
  20. energy_str = r'({} TeV)'.format(energy)
  21. axes.text(1, 1, ' '.join([lumi, energy_str]),
  22. horizontalalignment='right',
  23. verticalalignment='bottom',
  24. transform=axes.transAxes)
  25. def to_bin_list(th1):
  26. bins = []
  27. for i in range(th1.GetNbinsX()):
  28. bin_ = i+1
  29. center = th1.GetBinCenter(bin_)
  30. width = th1.GetBinWidth(bin_)
  31. content = th1.GetBinContent(bin_)
  32. error = th1.GetBinError(bin_)
  33. bins.append((center-width/2, center+width/2, (content, error)))
  34. return bins
  35. def histogram(th1, include_errors=False):
  36. edges = []
  37. values = []
  38. bin_list = to_bin_list(th1)
  39. for (l_edge, _, val) in bin_list:
  40. edges.append(l_edge)
  41. values.append(val)
  42. edges.append(bin_list[-1][1])
  43. return values, edges
  44. def histogram_slice(hist, range_):
  45. bins, edges = hist
  46. lim_low, lim_high = range_
  47. bins_new = []
  48. edges_new = []
  49. for i, (bin_, low, high) in enumerate(zip(bins, edges, edges[1:])):
  50. if low >= lim_low and high <= lim_high:
  51. bins_new.append(bin_)
  52. if edges_new:
  53. edges_new.pop() # pop off last high edge
  54. edges_new.append(low)
  55. edges_new.append(high)
  56. return bins_new, edges_new
  57. def plot_histogram(h1, *args, axes=None, norm=None, include_errors=False,
  58. log=False, xlim=None, ylim=None, **kwargs):
  59. """ Plots a 1D ROOT histogram object using matplotlib """
  60. import numpy as np
  61. if isinstance(h1, tuple):
  62. bins, edges = h1
  63. else:
  64. bins, edges = histogram(h1, include_errors=True)
  65. scale = 1. if norm is None else norm/np.sum(bins)
  66. bins = [(bin_*scale, err*scale) for (bin_, err) in bins]
  67. bins, errs = list(zip(*bins))
  68. left, right = np.array(edges[:-1]), np.array(edges[1:])
  69. X = np.array([left, right]).T.flatten()
  70. Y = np.array([bins, bins]).T.flatten()
  71. if axes is None:
  72. import matplotlib.pyplot as plt
  73. axes = plt.gca()
  74. axes.set_xlabel(kwargs.pop('xlabel', ''))
  75. axes.set_ylabel(kwargs.pop('ylabel', ''))
  76. axes.set_title(kwargs.pop('title', ''))
  77. if xlim is not None:
  78. axes.set_xlim(xlim)
  79. if ylim is not None:
  80. axes.set_ylim(ylim)
  81. # elif not log:
  82. # axes.set_ylim((0, None))
  83. axes.plot(X, Y, *args, linewidth=1, **kwargs)
  84. if include_errors:
  85. axes.errorbar(0.5*(left+right), bins, yerr=errs,
  86. color='k', marker=None, linestyle='None',
  87. barsabove=True, elinewidth=.7, capsize=1)
  88. if log:
  89. axes.set_yscale('log')
  90. def histogram2d(th2, include_errors=False):
  91. """ converts TH2 object to something amenable to
  92. plotting w/ matplotlab's pcolormesh
  93. """
  94. import numpy as np
  95. nbins_x = th2.GetNbinsX()
  96. nbins_y = th2.GetNbinsY()
  97. xs = np.zeros((nbins_y, nbins_x), np.float64)
  98. ys = np.zeros((nbins_y, nbins_x), np.float64)
  99. zs = np.zeros((nbins_y, nbins_x), np.float64)
  100. for i in range(nbins_x):
  101. for j in range(nbins_y):
  102. xs[j][i] = th2.GetXaxis().GetBinLowEdge(i+1)
  103. ys[j][i] = th2.GetYaxis().GetBinLowEdge(j+1)
  104. zs[j][i] = th2.GetBinContent(i+1, j+1)
  105. return xs, ys, zs
  106. def plot_histogram2d(th2, *args, axes=None, **kwargs):
  107. """ Plots a 2D ROOT histogram object using matplotlib """
  108. if axes is None:
  109. import matplotlib.pyplot as plt
  110. axes = plt.gca()
  111. axes.set_xlabel(kwargs.pop('xlabel', ''))
  112. axes.set_ylabel(kwargs.pop('ylabel', ''))
  113. axes.set_title(kwargs.pop('title', ''))
  114. axes.pcolormesh(*histogram2d(th2))
  115. # axes.colorbar() TODO: Re-enable this
  116. class StackHist:
  117. def __init__(self, title=""):
  118. self.title = title
  119. self.xlabel = ""
  120. self.ylabel = ""
  121. self.xlim = (None, None)
  122. self.ylim = (None, None)
  123. self.logx = False
  124. self.logy = False
  125. self.backgrounds = []
  126. self.signal = None
  127. self.signal_stack = True
  128. self.data = None
  129. def add_mc_background(self, th1, label, lumi=None, plot_color=''):
  130. self.backgrounds.append((label, lumi, to_bin_list(th1), plot_color))
  131. def set_mc_signal(self, th1, label, lumi=None, stack=True, scale=1, plot_color=''):
  132. self.signal = (label, lumi, to_bin_list(th1), plot_color)
  133. self.signal_stack = stack
  134. self.signal_scale = scale
  135. def set_data(self, th1, lumi=None, plot_color=''):
  136. self.data = ('data', lumi, to_bin_list(th1), plot_color)
  137. self.luminosity = lumi
  138. def _verify_binning_match(self):
  139. bins_count = [len(bins) for _, _, bins, _ in self.backgrounds]
  140. if self.signal is not None:
  141. bins_count.append(len(self.signal[2]))
  142. if self.data is not None:
  143. bins_count.append(len(self.data[2]))
  144. n_bins = bins_count[0]
  145. if any(bin_count != n_bins for bin_count in bins_count):
  146. raise ValueError("all histograms must have the same number of bins")
  147. self.n_bins = n_bins
  148. def save(self, filename, **kwargs):
  149. import matplotlib.pyplot as plt
  150. plt.ioff()
  151. fig = plt.figure()
  152. ax = fig.gca()
  153. self.do_draw(ax, **kwargs)
  154. fig.savefig("figures/"+filename, transparent=True)
  155. plt.close(fig)
  156. plt.ion()
  157. def do_draw(self, axes):
  158. self.axeses = [axes]
  159. self._verify_binning_match()
  160. bottoms = [0]*self.n_bins
  161. if self.logx:
  162. axes.set_xscale('log')
  163. if self.logy:
  164. axes.set_yscale('log')
  165. def draw_bar(label, lumi, bins, plot_color, scale=1, stack=True, **kwargs):
  166. if stack:
  167. lefts = []
  168. widths = []
  169. heights = []
  170. for left, right, content in bins:
  171. lefts.append(left)
  172. widths.append(right-left)
  173. if lumi is not None:
  174. content *= self.luminosity/lumi
  175. content *= scale
  176. heights.append(content)
  177. axes.bar(lefts, heights, widths, bottoms, label=label, color=plot_color, **kwargs)
  178. for i, (_, _, content) in enumerate(bins):
  179. if lumi is not None:
  180. content *= self.luminosity/lumi
  181. content *= scale
  182. bottoms[i] += content
  183. else:
  184. xs = [bins[0][0] - (bins[0][1]-bins[0][0])/2]
  185. ys = [0]
  186. for left, right, content in bins:
  187. width2 = (right-left)/2
  188. if lumi is not None:
  189. content *= self.luminosity/lumi
  190. content *= scale
  191. xs.append(left-width2)
  192. ys.append(content)
  193. xs.append(right-width2)
  194. ys.append(content)
  195. xs.append(bins[-1][0] + (bins[-1][1]-bins[-1][0])/2)
  196. ys.append(0)
  197. axes.plot(xs, ys, label=label, color=plot_color, **kwargs)
  198. if self.signal is not None and self.signal_stack:
  199. label, lumi, bins, plot_color = self.signal
  200. if self.signal_scale != 1:
  201. label = r"{}$\times{:d}$".format(label, self.signal_scale)
  202. draw_bar(label, lumi, bins, plot_color, scale=self.signal_scale, hatch='/')
  203. for background in self.backgrounds:
  204. draw_bar(*background)
  205. if self.signal is not None and not self.signal_stack:
  206. # draw_bar(*self.signal, stack=False, color='k')
  207. label, lumi, bins, plot_color = self.signal
  208. if self.signal_scale != 1:
  209. label = r"{}$\times{:d}$".format(label, self.signal_scale)
  210. draw_bar(label, lumi, bins, plot_color, scale=self.signal_scale, stack=False)
  211. axes.set_title(self.title)
  212. axes.set_xlabel(self.xlabel)
  213. axes.set_ylabel(self.ylabel)
  214. axes.set_xlim(*self.xlim)
  215. # axes.set_ylim(*self.ylim)
  216. if self.logy:
  217. axes.set_ylim(None, math.exp(math.log(max(bottoms))*1.4))
  218. else:
  219. axes.set_ylim(None, max(bottoms)*1.2)
  220. axes.legend(frameon=True, ncol=2)
  221. add_decorations(axes, self.luminosity, self.energy)
  222. def draw(self, axes, save=False, filename=None, **kwargs):
  223. self.do_draw(axes, **kwargs)
  224. if save:
  225. if filename is None:
  226. filename = "".join(c for c in self.title if c.isalnum() or c in (' ._+-'))+".png"
  227. self.save(filename, **kwargs)
  228. class StackHistWithSignificance(StackHist):
  229. def __init__(self, *args, **kwargs):
  230. super().__init__(*args, **kwargs)
  231. def do_draw(self, axes, bin_significance=True, low_cut_significance=False, high_cut_significance=False):
  232. bottom_box, _, top_box = axes.get_position().splity(0.28, 0.30)
  233. axes.set_position(top_box)
  234. super().do_draw(axes)
  235. axes.set_xticks([])
  236. rhs_color = '#cc6600'
  237. bottom = axes.get_figure().add_axes(bottom_box)
  238. bottom_rhs = bottom.twinx()
  239. bgs = [0]*self.n_bins
  240. for (_, _, bins, _) in self.backgrounds:
  241. for i, (left, right, value) in enumerate(bins):
  242. bgs[i] += value
  243. sigs = [0]*self.n_bins
  244. if bin_significance:
  245. xs = []
  246. for i, (left, right, value) in enumerate(self.signal[2]):
  247. sigs[i] += value
  248. xs.append(left)
  249. xs, ys = zip(*[(x, sig/(sig+bg)) for x, sig, bg in zip(xs, sigs, bgs) if (sig+bg) > 0])
  250. bottom.plot(xs, ys, '.k')
  251. if high_cut_significance:
  252. # s/(s+b) for events passing a minimum cut requirement
  253. min_bg = [sum(bgs[i:]) for i in range(self.n_bins)]
  254. min_sig = [sum(sigs[i:]) for i in range(self.n_bins)]
  255. min_xs, min_ys = zip(*[(x, sig/math.sqrt(sig+bg)) for x, sig, bg in zip(xs, min_sig, min_bg)
  256. if (sig+bg) > 0])
  257. bottom_rhs.plot(min_xs, min_ys, '->', color=rhs_color)
  258. if low_cut_significance:
  259. # s/(s+b) for events passing a maximum cut requirement
  260. max_bg = [sum(bgs[:i]) for i in range(self.n_bins)]
  261. max_sig = [sum(sigs[:i]) for i in range(self.n_bins)]
  262. max_xs, max_ys = zip(*[(x, sig/math.sqrt(sig+bg)) for x, sig, bg in zip(xs, max_sig, max_bg)
  263. if (sig+bg) > 0])
  264. bottom_rhs.plot(max_xs, max_ys, '-<', color=rhs_color)
  265. bottom.set_ylabel(r'$S/(S+B)$')
  266. bottom.set_xlim(axes.get_xlim())
  267. bottom.set_ylim((0, 1.1))
  268. if low_cut_significance or high_cut_significance:
  269. bottom_rhs.set_ylabel(r'$S/\sqrt{S+B}$')
  270. bottom_rhs.yaxis.label.set_color(rhs_color)
  271. bottom_rhs.tick_params(axis='y', colors=rhs_color, size=4, width=1.5)
  272. # bottom.grid()
  273. if __name__ == '__main__':
  274. import matplotlib.pyplot as plt
  275. from utils import ResultSet
  276. rs_TTZ = ResultSet("TTZ", "../data/TTZToLLNuNu_treeProducerSusyMultilepton_tree.root")
  277. rs_TTW = ResultSet("TTW", "../data/TTWToLNu_treeProducerSusyMultilepton_tree.root")
  278. rs_TTH = ResultSet("TTH", "../data/TTHnobb_mWCutfix_ext1_treeProducerSusyMultilepton_tree.root")
  279. rs_TTTT = ResultSet("TTTT", "../data/TTTT_ext_treeProducerSusyMultilepton_tree.root")
  280. sh = StackHist('B-Jet Multiplicity')
  281. sh.add_mc_background(rs_TTZ.b_jet_count, 'TTZ', lumi=40)
  282. sh.add_mc_background(rs_TTW.b_jet_count, 'TTW', lumi=40)
  283. sh.add_mc_background(rs_TTH.b_jet_count, 'TTH', lumi=40)
  284. sh.set_mc_signal(rs_TTTT.b_jet_count, 'TTTT', lumi=40, scale=10)
  285. sh.luminosity = 40
  286. sh.energy = 13
  287. sh.xlabel = 'B-Jet Count'
  288. sh.ylabel = r'\# Events'
  289. sh.xlim = (-.5, 9.5)
  290. sh.signal_stack = False
  291. fig = plt.figure()
  292. sh.draw(fig.gca())
  293. plt.show()
  294. # sh.add_data(rs_TTZ.b_jet_count, 'TTZ')