Crombie Tools
src/include/crombie/Plotter.h
Go to the documentation of this file.
1 #ifndef CROMBIE_PLOTTER_H
2 #define CROMBIE_PLOTTER_H
3 
4 #include <iomanip>
5 #include <string>
6 #include <vector>
7 #include <map>
8 #include <list>
9 #include <mutex>
10 
11 #include "crombie/Types.h"
12 #include "crombie/Selection.h"
13 #include "crombie/PlotConfig.h"
14 #include "crombie/FileConfig.h"
15 #include "crombie/LoadTree.h"
16 #include "crombie/Hist.h"
17 #include "crombie/Uncertainty.h"
18 
19 #include "TH1.h"
20 #include "THStack.h"
21 #include "TLegend.h"
22 #include "TCanvas.h"
23 #include "TLatex.h"
24 
25 namespace crombie {
26  namespace Plotter {
27 
28  /// This class has everything needed to draw a plot
29  class Plot {
30  public:
31  /// One of the messier functions which draws the plot at the desired location
32  void draw (const std::string& filebase);
33  /// Add a plot to the inner store. Processes are merged together
34  void add (unsigned isub, const FileConfig::DirectoryInfo& info, const Hist::Hist& hist);
35  /// Scale the MC plots to match the given luminosity
36  void scale (double lumi);
37  private:
38  struct PlotInfo {
39  PlotInfo(const Hist::Hist& hist = {}, double xsec = {},
40  std::string entry = {}, FileConfig::Type type = {},
41  short style = {})
42  : hist{hist}, xsec{xsec}, entry{entry}, type{type}, style{style} {}
44  double xsec;
45  std::string entry;
47  short style;
48  };
49  /// First key is directory, then label
51  /// The last luminosity scaled to. 0 if not set yet.
52  double currentlumi {0.0};
53  };
54 
55 
56  namespace {
57 
58  class CutReader {
59  public:
60  CutReader(double& cut, double& expr, double& weight, double& sub, Hist::Hist& hist)
61  : cut{cut}, expr{expr}, weight{weight}, sub{sub}, hist{hist} {}
62 
63  void eval () {
64  if (cut and sub)
65  hist.fill(expr, weight * sub);
66  }
67  private:
68  double& cut; // For the selection
69  double& expr;
70  double& weight;
71  double& sub; // For the subprocess
73  };
74 
75  }
76 
77  /**
78  This is the output running over a single file.
79  The key corresponds to a "selection_plotname", and the different hists are different process cuts.
80  */
82 
83 
84  /**
85  Constructs a function that runs over a single file and produces all the necessary histograms
86  @param plots A list of all the plots in a configuration file read by PlotConfig::read()
87  @param selections A global Selection::SelectonConfig object returned by Selection::read()
88  @param unc Uncertainty info that can be empty or filled by the >> operator from a config file
89  @param unblind If true, ignores the :b switch in the plots configuration
90  @param plotstodo If filled, can select a subset of plots to work through.
91  First element is selection key. Second is the plot name.
92  */
95  const std::vector<PlotConfig::Plot>& plots,
96  const Selection::SelectionConfig& selections,
98  const bool unblind = false,
99  const std::vector<std::pair<std::string, std::string>>& plotstodo = {}) {
100 
101  auto mchistname = selections.mchistname;
102 
103  // The selections that plotstodo accesses
104  Selection::Selections filtered_sels;
105  // The plots that plotstodo enumerates. Key is expression name
107  FilteredPlots filtered_plots;
108 
109  // If limiting plotstodo
110  if (plotstodo.size()) {
111 
112  // Quickly build a map to plot indices to pull out if needed
113  Types::map<unsigned> p_indices;
114  for (unsigned p_index = 0; p_index != plots.size(); ++p_index)
115  p_indices[plots[p_index].name] = p_index;
116 
117  for (auto& todo : plotstodo) {
118  auto sel_pair = std::make_pair(todo.first, selections.selections.at(todo.first));
119  // Check if this is in our filtered selections
120  if (filtered_sels.find(todo.first) == filtered_sels.end())
121  filtered_sels.insert(sel_pair);
122 
123  auto f_plot = filtered_plots.find(todo.second);
124  // If the plot is not in the existing list, insert new element into filtered_plots
125  if (f_plot == filtered_plots.end())
126  filtered_plots.insert(
127  {todo.second,
128  {plots[p_indices.at(todo.second)],
129  {sel_pair}
130  }
131  });
132  // Otherwise, just insert element into selection
133  else
134  f_plot->second.second.insert(sel_pair);
135  }
136  }
137  // Otherise, just copy everything (yes, "expensive", but negligble compared to running plotter)
138  else {
139  filtered_sels = selections.selections;
140  for (auto& plot : plots)
141  filtered_plots.insert({plot.name, {plot, filtered_sels}});
142  }
143 
144  // Now return this long functional for the FileConfig to run
145 
147  [plots{std::move(filtered_plots)},
148  sels{std::move(filtered_sels)},
149  mchistname, unblind, &unc] (const FileConfig::FileInfo& info) {
150  // Clear uncertainties for data
151  Uncertainty::UncertaintyInfo this_unc {};
152  if (info.type != FileConfig::Type::Data)
153  this_unc = unc;
154 
155  // Build the formulas and plots to use
156  using ExprIter = FilteredPlots::value_type;
157  auto get_expr = (info.type == FileConfig::Type::Data) ?
158  [] (const PlotConfig::Plot& iter) { return iter.data_var; } :
159  [] (const PlotConfig::Plot& iter) { return iter.mc_var; };
160 
161  auto exprs = Misc::comprehension<std::string>(plots,
162  [&get_expr] (const ExprIter& iter) {
163  return get_expr(iter.second.first);
164  });
165 
166  using SelIter = Selection::Selections::value_type;
167  auto get_weight = (info.type == FileConfig::Type::Data) ?
168  [] (const SelIter& iter) { return iter.second.data; } :
169  [] (const SelIter& iter) { return iter.second.mc; };
170 
171  auto get_cut = [&info, unblind] (const SelIter& iter) {
172  // If data, not unblinded, and selection calls for blinding, give "0" for the cut
173  return (info.type == FileConfig::Type::Data and
174  not unblind and iter.second.blinded) ?
175  std::string{"0"} : iter.second.cut;
176  };
177 
178  auto weights = Misc::comprehension<std::string>(sels, get_weight);
179  auto cuts = Misc::comprehension<std::string>(sels, get_cut);
180 
181  // Cover bases here
183  for (auto& expr : exprs) {
184  for (auto& cut : cuts) {
185  if (cut.find(expr) != std::string::npos)
186  nminus1.push_back(Selection::nminus1(expr, cut));
187  }
188  }
189 
190  LoadTree::Tree loaded{info.name, this_unc.exprs(exprs, weights, cuts, info.cuts, nminus1)};
191 
192  auto* weighthist = loaded.get<TH1>(mchistname);
193 
194  SingleOut output {};
195 
196  std::list<CutReader> readers {};
197 
198  auto add_reader = [&readers, &loaded, weighthist] (const auto& sel, const auto& expr, const auto& weight,
199  const auto& sub, auto& hist, unsigned bin = 1) {
200  hist.set_total(weighthist->GetBinContent(bin));
201  readers.emplace_back(loaded.result(sel), loaded.result(expr),
202  loaded.result(weight), loaded.result(sub),
203  hist);
204  };
205 
206  const auto& systematics = this_unc.full_systematic_infos();
207 
208  for (auto& p_iter : plots) {
209  auto& plot = p_iter.second.first;
210  for (auto& sel : p_iter.second.second) {
211  auto& plotvec = output[sel.first + "_" + p_iter.first];
212  // We want to reserve the location for the vector because we want the references to stay valid
213  plotvec.reserve(info.cuts.size());
214  for (auto& sub : info.cuts) {
215  plotvec.push_back(plot.get_hist());
216  auto& lastplot = plotvec.back();
217  auto cut = get_cut(sel);
218  auto expr = get_expr(plot);
219  auto selection = Selection::nminus1(expr, cut);
220  auto weight = get_weight(sel);
221  add_reader(selection, expr, weight, sub, lastplot);
222  // Map all the systematics to the systematics container in the Hist
223  for (auto& sys : systematics) {
224  auto uncexpr = [&this_unc, &sys] (const auto& expr) {
225  return this_unc.expr(sys.key, expr, sys.suff);
226  };
227  auto sysexpr = uncexpr(expr);
228  auto sysselection = uncexpr(selection);
229  auto sysweight = uncexpr(weight);
230  // Only make a systematics reader if one of these does not agree
231  if (sysexpr != expr or
232  sysselection != selection or
233  sysweight != weight)
234  add_reader(sysselection, sysexpr, sysweight, sub,
235  (sys.suff == "Up" or sys.suff == "Down" ?
236  lastplot.get_unc_hist(sys.key + sys.suff) :
237  lastplot.get_env_hist(sys.key)),
238  sys.bin);
239  }
240  }
241  }
242  }
243 
244  // Loop through the tree and fill the histograms
245  while (loaded.next()) {
246  for (auto& reader : readers)
247  reader.eval();
248  }
249 
250  return output;
251  }
252  };
253  }
254 
255 
256  /// The key is a combination of "selection_plotname"
258 
259 
260  /// The FileConfig::MergeFunc instantiation for this namespace
262 
263 
264  /**
265  Gets a function that merges the output of the SingleFile functional
266  */
268  // Put lumi search here so that the "missing lumi" error is thrown early
269  double lumi = files.has_mc() ? std::stod(Misc::env("lumi")) : 0.0;
270  return MergeFunc { [&files, lumi] (auto& outputs) {
271  MergeOut output {};
272  for (auto& dir : files.get_dirs()) {
273  // Each of plots is a SingleOut
274  for (auto& plots : outputs.at(dir.name)) {
275  // Key of "plot" is key of "output", value of "plot" is list of histograms for processes
276  for (auto& plot : plots) {
277  auto& outputplot = output[plot.first];
278  for (unsigned iproc = 0; iproc != plot.second.size(); ++iproc)
279  outputplot.add(iproc, dir, plot.second.at(iproc));
280  }
281  }
282  }
283  for (auto& plot : output)
284  plot.second.scale(lumi);
285  return output;
286  }
287  };
288  }
289 
290 
291  void Plot::add(unsigned isub, const FileConfig::DirectoryInfo& info, const Hist::Hist& hist) {
292  // Process name
293  auto& proc = info.processes.at(isub);
294  auto& dir = plotstore[info.name];
295  if (dir.find(proc.treename) == dir.end())
296  dir[proc.treename] = PlotInfo(hist, info.xs, proc.legendentry, info.type, proc.style);
297  else {
298  auto& toadd = dir[proc.treename];
299  toadd.hist.add(hist);
300  }
301  }
302 
303 
304  namespace {
305 
306  // Stylize a histogram based on its type
307  TH1* style(TH1* hist, FileConfig::Type type, short style) {
308  hist->SetLineColor(kBlack);
309  switch(type) {
310  case(FileConfig::Type::Data) :
311  Debug::Debug(__PRETTY_FUNCTION__, "New data hist");
312  hist->SetMarkerStyle(8);
313  hist->SetMarkerColor(style);
314  hist->SetLineColor(style);
315  break;
317  hist->SetLineStyle(style);
318  break;
320  hist->SetFillStyle(1001);
321  hist->SetFillColor(style);
322  break;
323  default: // Don't know what you would want to do here
324  throw;
325  }
326  return hist; // Return for chaining
327  }
328 
329  TLegend legend(const Hist::Hist& hist, unsigned numlabels) {
330  auto bins = hist.get_maxbin_outof();
331  // The upper left corner of the legend;
332  double x_left = ((bins.first * 2)/bins.second) ? 0.15 : 0.65;
333 
334  Debug::Debug(__PRETTY_FUNCTION__, "max bin", bins.first, bins.second, (bins.first * 2)/bins.second, x_left);
335 
336  // Height determined by number of anticipated legend entries
337  TLegend leg{x_left, 0.875 - std::min(0.5, 0.075 * numlabels), x_left + 0.25, 0.875};
338  leg.SetBorderSize(0);
339  leg.SetFillStyle(0);
340  return leg;
341  }
342 
343  }
344 
345 
346  void Plot::scale(double lumi) {
347  for (auto& dir : plotstore) {
348  for (auto& proc : dir.second) {
349  if (proc.second.type != FileConfig::Type::Data) {
350  // Need to do a quick check that scale hasn't been called before
351  if (currentlumi)
352  proc.second.hist.scale(lumi/currentlumi);
353  else
354  proc.second.hist.scale(lumi, proc.second.xsec);
355  }
356  }
357  }
358  currentlumi = lumi;
359  }
360 
361 
362  void Plot::draw(const std::string& filebase) {
363 
364  // Legend label is the key of the map
365  std::map<FileConfig::Type, Types::map<TH1D*>> hists;
366  // Use this to store sums for ratios
367  Hist::Hist bkg_hist {};
368  // Both final style and histogram
369  using StyledHist = std::pair<short, Hist::Hist>;
370  StyledHist data_hist {};
371  StyledHist signal_hist {};
372  for (auto& dir : plotstore) {
373  for (auto& proc : dir.second) {
374  // Scale the histogram
375  switch(proc.second.type) {
377  data_hist.first = proc.second.style;
378  data_hist.second.add(proc.second.hist);
379  break;
381  bkg_hist.add(proc.second.hist);
382  // Add backgrounds to signal sums too
384  signal_hist.first = proc.second.style;
385  signal_hist.second.add(proc.second.hist);
386  }
387  auto* newhist = proc.second.hist.roothist();
388  auto& histcol = hists[proc.second.type];
389  if (histcol.find(proc.second.entry) != histcol.end())
390  histcol[proc.second.entry]->Add(newhist);
391  else {
392  // Styling for each type
393  style(newhist, proc.second.type, proc.second.style);
394  histcol[proc.second.entry] = newhist;
395  }
396  }
397  }
398 
399  // Now stuff MC into a vector and sort
400  auto sorted_vec = [] (auto& hists) {
401  std::vector<std::pair<std::string, TH1D*>> sortvec;
402  sortvec.insert(sortvec.end(), hists.begin(), hists.end());
403  std::sort(sortvec.begin(), sortvec.end(), [] (auto& a, auto& b) {
404  Debug::Debug(__PRETTY_FUNCTION__, a.second->Integral());
405  return a.second->Integral() > b.second->Integral();
406  });
407  return sortvec;
408  };
409 
410  auto mcvec = sorted_vec(hists[FileConfig::Type::Background]);
411  auto sigvec = sorted_vec(hists[FileConfig::Type::Signal]);
412 
413  THStack hs{"hs", ""};
414  // Get the maximum value
415  auto max = bkg_hist.max_w_unc();
416  // Check the data histogram(s)
417  for(auto& dat : hists[FileConfig::Type::Data])
418  max = std::max(dat.second->GetMaximum(), max); // Doesn't include the uncertainties
419 
420  hs.SetMaximum(max);
421  // Need to add to stack in reverse order
422  auto mc = mcvec.end();
423  while(mc != mcvec.begin())
424  hs.Add((--mc)->second);
425 
426  unsigned numlabels = 0;
427  for (auto& type : hists)
428  numlabels += type.second.size();
429 
430  // Make the legend, determining location summed histograms
431  TLegend leg{legend(bkg_hist, numlabels)};
432 
433  // MC entries
434  for (auto& mc : mcvec)
435  leg.AddEntry(mc.second, mc.first.data(), "f");
436 
437  // Draw everything
438  TCanvas canv{"canv", "canv", 600, 700};
439  canv.cd();
440  // Top pad
441  const double bottom = mcvec.size() ? 0.3 : 0.0;
442 
443  TPad pad1{"pad1", "pad1", 0.0, bottom, 1.0, 1.0};
444  Debug::Debug(__PRETTY_FUNCTION__, "Pad number", pad1.GetNumber(), pad1.GetMother(), &canv);
445  pad1.SetBottomMargin(bottom ? 0.025 : 0.1);
446  pad1.Draw();
447  pad1.cd();
448 
449  const double nomfont = 0.03; // Target font size for plot labels
450  const double titleoff = 1.55; // Title offset
451 
452  if (mcvec.size()) {
453  hs.Draw("hist");
454 
455  hs.GetYaxis()->SetLabelSize(nomfont/(1 - bottom));
456  hs.GetYaxis()->SetTitleSize(nomfont/(1 - bottom));
457  hs.GetYaxis()->SetTitleOffset(titleoff);
458  hs.GetYaxis()->SetTitle("Events/Bin");
459 
460  hs.GetXaxis()->SetLabelSize(0);
461  hs.GetXaxis()->SetTitleSize(0);
462 
463  auto* bkg_sum = bkg_hist.roothist();
464  bkg_sum->SetFillStyle(3001);
465  bkg_sum->SetFillColor(kGray);
466  bkg_sum->Draw("e2,same");
467 
468  for (auto& sig : sigvec) {
469  leg.AddEntry(sig.second, sig.first.data(), "lp");
470  sig.second->Add(bkg_sum);
471  sig.second->Draw("hist,same");
472  }
473  }
474 
475  for (auto& data : hists[FileConfig::Type::Data]) {
476  Debug::Debug(__PRETTY_FUNCTION__, "Drawing data hist with", data.second->Integral(), "entries");
477  leg.AddEntry(data.second, data.first.data(), "lp");
478  data.second->Draw("PE,same");
479  }
480 
481  canv.cd();
482  TPad pad2{"pad2", "pad2", 0.0, 0.0, 1.0, bottom};
483  Debug::Debug(__PRETTY_FUNCTION__, "Pad number", pad2.GetNumber(), pad2.GetMother(), &canv);
484 
485  if (bottom) {
486  Debug::Debug(__PRETTY_FUNCTION__, "Making bottom pad");
487  pad2.SetTopMargin(0.025);
488  pad2.SetBottomMargin(0.4);
489  pad2.cd();
490 
491  auto bkg_ratio = bkg_hist.ratio(bkg_hist);
492  auto data_ratio = data_hist.second.ratio(bkg_hist);
493 
494  auto set_yaxis = [bottom, titleoff] (auto* hist) {
495  auto* axis = hist->GetYaxis();
496  axis->SetNdivisions(504, true);
497  axis->SetTitle("Data/Pred.");
498  axis->SetTitleOffset((bottom)/(1-bottom) * titleoff);
499  axis->CenterTitle();
500  return hist;
501  };
502 
503  auto* bhist = set_yaxis(bkg_ratio.roothist());
504 
505  for (auto* axis : {bhist->GetXaxis(), bhist->GetYaxis()}) {
506  axis->SetTitleSize(nomfont/bottom);
507  axis->SetLabelSize(nomfont/bottom);
508  }
509 
510  bhist->SetFillStyle(3001);
511  bhist->SetFillColor(kGray);
512 
513  static double minratio {std::stod(Misc::env("minratio", "0.0"))};
514  bhist->SetMinimum(std::max(std::min(bkg_ratio.min_w_unc(), data_ratio.min_w_unc(false)), minratio));
515 
516  static double maxratio {std::stod(Misc::env("maxratio", "2.0"))};
517  bhist->SetMaximum(std::min(std::max(bkg_ratio.max_w_unc(), data_ratio.max_w_unc()), maxratio));
518  bhist->Draw("e2");
519 
520  style(signal_hist.second.ratio(bkg_hist).roothist(), FileConfig::Type::Signal, signal_hist.first)->Draw("hist,same");
521  style(data_ratio.roothist(), FileConfig::Type::Data, data_hist.first)->Draw("PE,same");
522 
523  pad2.SetGridy(1);
524 
525  canv.cd();
526  pad2.Draw();
527  }
528 
529  pad1.cd();
530  leg.Draw();
531 
532  canv.cd();
533  // Labels
534  TLatex latex{};
535  constexpr double toplocation = 0.96;
536  latex.SetTextSize(0.035);
537  if (currentlumi) {
538  latex.SetTextAlign(31);
539 
540  std::stringstream lumistream;
541  lumistream << std::setprecision(3) << currentlumi/1000.0;
542  std::string lumilabel;
543  lumistream >> lumilabel;
544  lumilabel += " fb^{-1} (13 TeV)";
545 
546  latex.DrawLatex(0.95, toplocation, lumilabel.data());
547  }
548  latex.SetTextFont(62);
549  latex.SetTextAlign(11);
550  latex.DrawLatex(0.12, toplocation, "CMS");
551  latex.SetTextSize(0.030);
552  latex.SetTextFont(52);
553  latex.SetTextAlign(11);
554 
555  latex.DrawLatex(0.2, toplocation, "Preliminary");
556 
557  // Save everything
558  for (auto& suff : {".pdf", ".png", ".C"}) {
559  auto output = filebase + suff;
560  canv.SaveAs(output.data());
561  }
562 
563  }
564 
565  }
566 }
567 
568 #endif