def plotHistos(histoData=None, histoSignal=None, histoTotBkg=None, histosBkg={}, statErrBand=None, systErrBand=None, # these are TGraphAsymmErrors canvasName='canvas', outdir='./', options=None, stack_order=[], topLabel='', drawStatErr=False, drawSystErr=False, drawYieldAndError=False, printYieldSummary=False) : "Note: blinding can be required for only a subrange of the histo, so it is taken care of when filling" verbose = options.verbose formatAuxMaterial = options.format_aux setAtlasStyle() padMaster = histoData if verbose : print "plotting ",padMaster.GetName(),' (',padMaster.GetEntries(),' entries)' can = r.TCanvas(canvasName, padMaster.GetTitle(), 800, 800) botPad, topPad = buildBotTopPads(can, squeezeMargins=False) can.cd() topPad.Draw() # draw top topPad.cd() topPad._hists = [padMaster] if formatAuxMaterial: for ax in [padMaster.GetXaxis(), padMaster.GetYaxis()]: ax.SetTitle(prettify(ax.GetTitle())) padMaster.Draw('axis') topPad.Update() # necessary to fool root's dumb object ownership of the stack stack = r.THStack('stack_'+padMaster.GetName(), '') r.SetOwnership(stack, False) topPad._hists.append(stack) leg = topRightLegend(can, 0.300 if formatAuxMaterial else 0.225, 0.400 if formatAuxMaterial else 0.325, shiftX=-0.1 if formatAuxMaterial else 0.0, shiftY=-0.1 if formatAuxMaterial else 0.0) topPad._leg = leg leg.SetBorderSize(0) leg.SetTextFont(padMaster.GetTitleFont()) leg._reversedEntries = [] def integralWou(h): "Integral with underflow and overflow" return h.Integral(0, h.GetNbinsX()+1) for group, histo in sortedAs(histosBkg, stack_order) : histo.SetFillColor(getGroupColor(group)) histo.SetLineWidth(2) histo.SetLineColor(r.kBlack) stack.Add(histo) topPad._hists.append(histo) leg._reversedEntries.append((histo, prettify(group) if formatAuxMaterial else "{0}: {1:.2f}".format(group, integralWou(histo)), 'F')) if not formatAuxMaterial: leg._reversedEntries.append((dummyHisto(), "{0}, {1:.2f}".format('bkg', sum([integralWou(h) for h in stack.GetHists()])), 'l')) if statErrBand and drawStatErr : statErrBand.SetFillStyle(3006) leg._reversedEntries.append((statErrBand, 'stat', 'f')) if systErrBand and drawSystErr : systErrBand.SetFillStyle(3007) leg._reversedEntries.append((systErrBand, 'syst', 'f')) leg._reversedEntries.append((histoData, "Data {0}".format(prettify(topLabel)) if formatAuxMaterial else "{0}, {1:.2f}".format('data', integralWou(histoData)), 'p')) for h, g, o in leg._reversedEntries[::-1] : leg.AddEntry(h, g, o) # stack goes b-t, legend goes t-b stack.Draw('hist same') histoData.SetMarkerStyle(r.kFullCircle) histoData.SetLineWidth(2) dataGraph = graphWithPoissonError(histoData) dataGraph.Draw('same p') isRightSignal = False if histoSignal: histoSignal.SetFillStyle(0) histoSignal.SetFillColor(0) histoSignal.SetLineColor(getGroupColor('signal')) histoSignal.SetLineWidth(3) isRightSignal = (all(s in histoSignal.GetName() for s in ['signaltaumu', 'sr_mue']) or all(s in histoSignal.GetName() for s in ['signaltaue', 'sr_emu'])) if isRightSignal: histoSignal.Draw('histo same') if statErrBand and drawStatErr : statErrBand.SetFillStyle(3006) statErrBand.Draw('E2 same') leg.AddEntry(statErrBand, 'stat', 'f') if systErrBand and drawSystErr : systErrBand.SetFillStyle(3007) systErrBand.Draw('E2 same') leg.AddEntry(systErrBand, 'syst', 'f') totErrBand = systUtils.addErrorBandsInQuadrature(statErrBand, systErrBand) if totErrBand : totErrBand.Draw('E2 same') totErrBand.SetFillStyle(3005) leg.AddEntry(totErrBand, 'stat+syst', 'f') if printYieldSummary: print_total_error = False print ("{}:".format(topLabel if topLabel else dataGraph.GetName())+ (" expected {:.2f}^{{+{:.2f}}}_{{{:.2f}}}".format(totErrBand.GetY()[0], totErrBand.GetErrorYhigh(0), totErrBand.GetErrorYlow(0)) if print_total_error else " expected {:.2f}^{{+{:.2f}}}_{{{:.2f}}} (stat) ^{{+{:.2f}}}_{{{:.2f}}} (syst)" .format(totErrBand.GetY()[0], statErrBand.GetErrorYhigh(0) if statErrBand else 0.0, statErrBand.GetErrorYlow(0) if statErrBand else 0.0, systErrBand.GetErrorYhigh(0) if systErrBand else 0.0, systErrBand.GetErrorYlow(0) if systErrBand else 0.0) )+ " obs {:.2f}".format(dataGraph.GetY()[0])) if histoSignal and isRightSignal: hname = histoSignal.GetName() label = ('H#rightarrow#mu#tau' if 'signaltaumu' in hname else 'H#rightarrowe#tau' if 'signaltaue' in hname else 'signal') label = ("{}, BR=1%".format(label) if formatAuxMaterial else "{}, BR=1%, {:.2f}".format(label, integralWou(histoSignal))) leg.AddEntry(histoSignal, label, 'l') leg.Draw('same') topPad.Update() tex = r.TLatex() tex.SetTextSize(0.5 * tex.GetTextSize()) tex.SetNDC(True) label = "%s tot bkg : "%(can.GetName()) label += "%.3f #pm %.3f (stat)"%(integralAndError(histoTotBkg)) if systErrBand : sysUp, sysDo = systUtils.totalUpDownVariation(systErrBand) label += "#pm #splitline{%.3f}{%.3f} (syst)"%(sysUp, sysDo) if drawYieldAndError : tex.DrawLatex(0.10, 0.95, label) topPad.SetTopMargin(2.0*topPad.GetTopMargin()) atlasLabel = drawAtlasLabel(can, align=13, scale=0.75, xpos=0.175 if formatAuxMaterial else 0.125, ypos=0.945 if formatAuxMaterial else None, public=True) if topLabel and not formatAuxMaterial : topRightLabel(can, topLabel, ypos=1.0) yMin, yMax = getMinMax([histoData, dataGraph, histoTotBkg, histoSignal, totErrBand]) padMaster.SetMinimum(0.0) padMaster.SetMaximum(1.1 * yMax) padMaster.GetXaxis().SetLabelSize(0) padMaster.GetXaxis().SetTitleSize(0) increaseAxisFont(padMaster.GetXaxis()) increaseAxisFont(padMaster.GetYaxis()) topPad.RedrawAxis() topPad.Update() # force stack to create padMaster # draw bot (ratio) can.cd() botPad.Draw() botPad.cd() ratio = systUtils.buildAsymmRatioGraph(dataGraph, totErrBand) ratioPadMaster = padMaster.Clone(padMaster.GetName()+'_ratio') ratioPadMaster.SetDirectory(0) ratioPadMaster.Clear() yMin, yMax = 0.0, 2.0 ratioPadMaster.SetMinimum(yMin) ratioPadMaster.SetMaximum(yMax) ratioPadMaster.SetStats(0) ratioPadMaster.Draw('axis') x_lo, x_hi = getXrange(ratioPadMaster) refLines = [referenceLine(x_lo, x_hi, y, y) for y in [0.5, 1.0, 1.5]] for l in refLines : l.Draw() err_band_r = systUtils.buildErrBandRatioGraph(totErrBand) err_band_r.Draw('E2 same') ratio.Draw('ep') xA, yA = ratioPadMaster.GetXaxis(), ratioPadMaster.GetYaxis() textScaleUp = 0.75*1.0/botPad.GetHNDC() # if xaxis_title : xA.SetTitle(xaxis_title) yA.SetNdivisions(-104) yA.SetTitle('Data/Bkg') yA.CenterTitle() yA.SetTitleOffset(yA.GetTitleOffset()/textScaleUp) xA.SetTitleSize(yA.GetTitleSize()) # was set to 0 for padmaster, restore it xA.SetLabelSize(yA.GetLabelSize()) for a in [xA, yA] : a.SetLabelSize(a.GetLabelSize()*textScaleUp) a.SetTitleSize(a.GetTitleSize()*textScaleUp) botPad._graphical_objects = [ratio, ratioPadMaster, err_band_r] + refLines # avoid garbage collection botPad.Update() can.Update() for ext in ['png','eps'] : can.SaveAs(outdir+'/'+can.GetName()+'.'+ext)
def main(): parser = argparse.ArgumentParser( description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) add_arg = parser.add_argument add_arg('-o', '--output-dir', default="./") add_arg('-p', '--process', help='one physics process, eg. ttw') add_arg('-s', '--systematic', help='one of the systematic variations') add_arg('-v', '--verbose', action='store_true') add_arg('-d', '--debug', action='store_true') args = parser.parse_args() set_log(args.verbose, args.debug) outdir = args.output_dir process = args.process systematic = args.systematic available_processes = get_input_samples().keys() if process not in available_processes: raise StandardError("invalid process %s, should be one of %s" % (process, str(available_processes))) available_systematics = get_input_samples()[process].keys() if systematic not in available_systematics: raise StandardError("invalid systematic %s, should be one of %s" % (systematic, str(available_systematics))) file_label = process + '_sys_' + systematic plot_label = process + ' sys. ' + systematic normalize_to_unity = False # True luminosity = 1.0 combiner = HistogramCombiner() combiner.build_samples(process=process, systematic=systematic) histogram_names = [ 'h_meff', 'h_jetN', # 'h_electronPt', 'h_muonPt', # 'h_bjetEmulN', 'h_bjetN', # 'h_bjetEmulN_sr3b', 'h_bjetN_sr3b', # 'h_bjetEmulN_sr1b', 'h_bjetN_sr1b', # 'h_bjetEmulN_cr2bttV', 'h_bjetN_cr2bttV', # 'h_meff_sr3b', 'h_jetN_sr3b', 'h_met_sr3b', # 'h_meff_sr1b', 'h_jetN_sr1b', 'h_met_sr1b', # 'h_meff_sr0b5j', 'h_jetN_sr0b5j', 'h_met_sr0b5j', # 'h_meff_sr0b4j', 'h_jetN_sr0b4j', 'h_met_sr0b4j', # 'h_meff_sr0b3j', 'h_jetN_sr0b3j', 'h_met_sr0b3j', # 'h_meff_cr2bttV', 'h_jetN_cr2bttV', 'h_met_cr2bttV' ] combiner.compute_normalization_factors() output_pdf_name = outdir + '/' + file_label + '.pdf' c_summary = R.TCanvas('c_summary', 'plotExplicitSamples sampes summary ') combiner.print_sample_summary_to_pdf( c_summary, label="%s: nominal vs. %s systematic" % (process, systematic)) c_summary.SaveAs(output_pdf_name + '(') for histogram_name in histogram_names: rebin = 'meff' in histogram_name # and '_sr' in histogram_name # non-inclusive histos: low stats rebin_factor = (2 if 'meff' in histogram_name else 2 if 'jetN' in histogram_name else 1) if rebin else 1 histograms = combiner.get_histograms(histogram_name=histogram_name) h_nom = histograms['nominal'] h_up = histograms['up'] h_dn = histograms['down'] if 'h_jetFlavorMultiplicity' in histogram_name: h_nom = emulate_btag_multiplicity_from_truth_flavor(h_nom, 'nom') h_up = emulate_btag_multiplicity_from_truth_flavor(h_up, 'up') h_dn = emulate_btag_multiplicity_from_truth_flavor(h_dn, 'dn') histos = [h_nom, h_up, h_dn] for h in set(histos): # set: avoid rebinning twice when up==down h.Rebin(rebin_factor) h_nom.SetLineWidth(2 * h_nom.GetLineWidth()) h_nom.SetLineColor(R.kBlack) h_up.SetLineColor(R.kBlue) h_dn.SetLineColor(R.kRed) pad_master = h_nom pad_master.SetMaximum(1.1 * max([h.GetMaximum() for h in histos])) pad_master.SetMinimum(1.0 * min([0.0] + [h.GetMinimum() for h in histos])) pad_master.GetYaxis().SetTitle('Arbitrary Units') pad_master.SetStats(0) can = R.TCanvas('c_ttV_syst_' + histogram_name, 'ttV explicit variations ' + pad_master.GetTitle(), 700, 700) botPad, topPad = ru.buildBotTopPads(can, squeezeMargins=False) # top can.cd() topPad.Draw() topPad.cd() topPad._po = [pad_master] # persistent objects pad_master.GetXaxis().SetTitleSize(0) pad_master.GetXaxis().SetLabelSize(0) pad_master.Draw('axis') # ru.topRightLabel(topPad, pad_master.GetTitle(), xpos=0.5) ru.topRightLabel(topPad, "#bf{#it{ATLAS}} Simulation Preliminary", xpos=0.85, ypos=0.9) ru.topRightLabel(topPad, "#sqrt{s} = 13 TeV", xpos=0.85, ypos=0.8) leg = ru.topRightLegend(can, legWidth=0.225, legHeight=0.300, hShift=-0.10, vShift=-0.25) leg.SetBorderSize(0) # leg.SetHeader(plot_label+ ("(norm=1)" if normalize_to_unity else "(lumi %.1f)"%luminosity)) topPad._po.append(leg) def format_legend_label(h, l): return "{0}: {1:.2E} ({2:.0f})".format(l, h.Integral(), h.GetEntries()) def pretty_scale_legend_label(h, l): return ("nominal" if l is 'nom' else "#mu = 2.0 #mu_{0}" if l is 'up' else "#mu = 0.5 #mu_{0}" if l is 'dn' else 'unknown') for h, l in [(h_nom, 'nom'), (h_up, 'up'), (h_dn, 'dn')]: h.Draw('hist same') leg.AddEntry(h, pretty_scale_legend_label(h, l), 'l') topPad._po.append(h) leg.Draw('same') def integral_and_error(h): error = R.Double(0.0) integral = h.IntegralAndError(1, h.GetNbinsX() + 1, error) return integral, error def ratio_and_error(ave=(1.0, 0.01), bve=(2.0, 0.001)): a, sa = ave b, sb = bve if a and b: r = a / b e = r * sqrt((sa / a) * (sa / a) + (sb / b) * (sb / b)) return r, e else: return 0.0, 0.0 print_normalization_summary = histogram_name.startswith('h_meff') if print_normalization_summary: nom_int = h_nom.Integral() up_int = h_up.Integral() dn_int = h_dn.Integral() nom_int, nom_err = integral_and_error(h_nom) up_int, up_err = integral_and_error(h_up) dn_int, dn_err = integral_and_error(h_dn) rup, rupe = ratio_and_error((up_int, up_err), (nom_int, nom_err)) rdn, rdne = ratio_and_error((dn_int, dn_err), (nom_int, nom_err)) # print ("normalization change: " # +"{} up {:.1%} down {:.1%} (nom {:.1f}, up {:.1f}, do {:.1f})".format(h_nom.GetName(), # 1.0-up_int/nom_int if nom_int else 1.0, # 1.0-dn_int/nom_int if nom_int else 1.0, # nom_int, # up_int, # dn_int)) print( "normalization change: " + "{} up {:.1%} +/- {:.1%} down {:.1%} +/- {:.1%} ".format( h_nom.GetName(), 1.0 - rup, rupe, 1.0 - rdn, rdne) + "(integral: " + "nom {:.2E} +/- {:.2E}, up {:.2E} +/- {:.2E}, do {:.2E} +/- {:.2E})" .format(nom_int, nom_err, up_int, up_err, dn_int, dn_err) + " (entries nom {:.2E} up {:.2E} do {:.2E}".format( h_nom.GetEntries(), h_up.GetEntries(), h_dn.GetEntries())) print( "tex normalization change: " + "{} up ${:.1%} \pm {:.1%}$ down ${:.1%} \pm {:.1%}$ ".format( h_nom.GetName(), 1.0 - rup, rupe, 1.0 - rdn, rdne)) def bc(h): return [ h.GetBinContent(i) for i in range(1, 1 + h.GetNbinsX()) ] def max_frac_variation(h1, h2): "maximum bin-by-bin fractional variation; h1 is denominator, empty bins skipped" bc1 = bc(h1) bc2 = bc(h2) return max( [abs(b2 / b1) for b1, b2 in zip(bc1, bc2) if b1 and b2]) def max_frac_variation_within10(h1, h2): """maximum bin-by-bin fractional variation; h1 is denominator. Bins with <0.1*peak are skipped""" bc1 = bc(h1) bc2 = bc(h2) m1 = max(bc1) m2 = max(bc2) return max([ abs(b2 / b1) for b1, b2 in zip(bc1, bc2) if b1 > 0.1 * m1 and b2 > 0.1 * m2 ]) # print ("shape change: " # +"{} up {:.1%} down {:.1%} ".format(h_nom.GetName(), # 1.0-max_frac_variation_within10(h_up, h_nom), # 1.0-max_frac_variation_within10(h_dn, h_nom))) topPad.Update() # bottom can.cd() botPad.SetTopMargin(1.25 * botPad.GetTopMargin()) botPad.Draw() botPad.cd() ratio_up = ru.buildRatioHistogram(h_up, h_nom) ratio_dn = ru.buildRatioHistogram(h_dn, h_nom) yMin, yMax = 0.5, 1.5 ratioPadMaster = pad_master.Clone(pad_master.GetName() + '_ratio') ratioPadMaster.SetMinimum(yMin) ratioPadMaster.SetMaximum(yMax) ratioPadMaster.SetStats(0) ratioPadMaster.Draw('axis') x_lo, x_hi = ru.getXrange(ratioPadMaster) refLines = [ ru.referenceLine(x_lo, x_hi, y, y) for y in [0.5, 1.0, 1.5] if y > yMin and y < yMax ] for l in refLines: l.Draw() ratio_up.Draw('same') ratio_dn.Draw('same') xA, yA = ratioPadMaster.GetXaxis(), ratioPadMaster.GetYaxis() textScaleUp = 0.75 * 1.0 / botPad.GetHNDC() yA.SetNdivisions(-102) yA.SetTitle('Ratio') yA.CenterTitle() yA.SetTitleOffset(yA.GetTitleOffset() / textScaleUp) xA.SetTitleSize( yA.GetTitleSize()) # x- was set to 0 for padmaster, restore it xA.SetLabelSize(yA.GetLabelSize()) xA.SetTitle(prettify_title(xA.GetTitle())) for a in [xA, yA]: a.SetLabelSize(a.GetLabelSize() * textScaleUp) a.SetTitleSize(a.GetTitleSize() * textScaleUp) botPad._graphical_objects = [ratio_up, ratio_dn, ratioPadMaster ] + refLines # avoid garbage collection botPad.Update() can.Update() first_histo = histogram_name is histogram_names[0] last_histo = histogram_name is histogram_names[-1] can.SaveAs(outdir + '/' + can.GetName() + '.png') can.SaveAs(outdir + '/' + can.GetName() + '.eps') can.SaveAs(output_pdf_name + (')' if last_histo else ''))
def plotHistos( histoData=None, histoSignal=None, histoTotBkg=None, histosBkg={}, statErrBand=None, systErrBand=None, # these are TGraphAsymmErrors canvasName='canvas', outdir='./', options=None, stack_order=[], topLabel='', drawStatErr=False, drawSystErr=False, drawYieldAndError=False, printYieldSummary=False): "Note: blinding can be required for only a subrange of the histo, so it is taken care of when filling" verbose = options.verbose formatAuxMaterial = options.format_aux setAtlasStyle() padMaster = histoData if verbose: print "plotting ", padMaster.GetName(), ' (', padMaster.GetEntries( ), ' entries)' can = r.TCanvas(canvasName, padMaster.GetTitle(), 800, 800) botPad, topPad = buildBotTopPads(can, squeezeMargins=False) can.cd() topPad.Draw() # draw top topPad.cd() topPad._hists = [padMaster] if formatAuxMaterial: for ax in [padMaster.GetXaxis(), padMaster.GetYaxis()]: ax.SetTitle(prettify(ax.GetTitle())) padMaster.Draw('axis') topPad.Update( ) # necessary to fool root's dumb object ownership of the stack stack = r.THStack('stack_' + padMaster.GetName(), '') r.SetOwnership(stack, False) topPad._hists.append(stack) leg = topRightLegend(can, 0.300 if formatAuxMaterial else 0.225, 0.400 if formatAuxMaterial else 0.325, shiftX=-0.1 if formatAuxMaterial else 0.0, shiftY=-0.1 if formatAuxMaterial else 0.0) topPad._leg = leg leg.SetBorderSize(0) leg.SetTextFont(padMaster.GetTitleFont()) leg._reversedEntries = [] def integralWou(h): "Integral with underflow and overflow" return h.Integral(0, h.GetNbinsX() + 1) for group, histo in sortedAs(histosBkg, stack_order): histo.SetFillColor(getGroupColor(group)) histo.SetLineWidth(2) histo.SetLineColor(r.kBlack) stack.Add(histo) topPad._hists.append(histo) leg._reversedEntries.append( (histo, prettify(group) if formatAuxMaterial else "{0}: {1:.2f}".format(group, integralWou(histo)), 'F')) if not formatAuxMaterial: leg._reversedEntries.append((dummyHisto(), "{0}, {1:.2f}".format( 'bkg', sum([integralWou(h) for h in stack.GetHists()])), 'l')) if statErrBand and drawStatErr: statErrBand.SetFillStyle(3006) leg._reversedEntries.append((statErrBand, 'stat', 'f')) if systErrBand and drawSystErr: systErrBand.SetFillStyle(3007) leg._reversedEntries.append((systErrBand, 'syst', 'f')) leg._reversedEntries.append( (histoData, "Data {0}".format(prettify(topLabel)) if formatAuxMaterial else "{0}, {1:.2f}".format('data', integralWou(histoData)), 'p')) for h, g, o in leg._reversedEntries[::-1]: leg.AddEntry(h, g, o) # stack goes b-t, legend goes t-b stack.Draw('hist same') histoData.SetMarkerStyle(r.kFullCircle) histoData.SetLineWidth(2) dataGraph = graphWithPoissonError(histoData) dataGraph.Draw('same p') isRightSignal = False if histoSignal: histoSignal.SetFillStyle(0) histoSignal.SetFillColor(0) histoSignal.SetLineColor(getGroupColor('signal')) histoSignal.SetLineWidth(3) isRightSignal = (all(s in histoSignal.GetName() for s in ['signaltaumu', 'sr_mue']) or all(s in histoSignal.GetName() for s in ['signaltaue', 'sr_emu'])) if isRightSignal: histoSignal.Draw('histo same') if statErrBand and drawStatErr: statErrBand.SetFillStyle(3006) statErrBand.Draw('E2 same') leg.AddEntry(statErrBand, 'stat', 'f') if systErrBand and drawSystErr: systErrBand.SetFillStyle(3007) systErrBand.Draw('E2 same') leg.AddEntry(systErrBand, 'syst', 'f') totErrBand = systUtils.addErrorBandsInQuadrature(statErrBand, systErrBand) if totErrBand: totErrBand.Draw('E2 same') totErrBand.SetFillStyle(3005) leg.AddEntry(totErrBand, 'stat+syst', 'f') if printYieldSummary: print_total_error = False print( "{}:".format(topLabel if topLabel else dataGraph.GetName()) + (" expected {:.2f}^{{+{:.2f}}}_{{{:.2f}}}".format( totErrBand.GetY()[0], totErrBand.GetErrorYhigh(0), totErrBand.GetErrorYlow(0)) if print_total_error else " expected {:.2f}^{{+{:.2f}}}_{{{:.2f}}} (stat) ^{{+{:.2f}}}_{{{:.2f}}} (syst)" .format(totErrBand.GetY()[0], statErrBand.GetErrorYhigh(0) if statErrBand else 0.0, statErrBand.GetErrorYlow(0) if statErrBand else 0.0, systErrBand.GetErrorYhigh(0) if systErrBand else 0.0, systErrBand.GetErrorYlow(0) if systErrBand else 0.0)) + " obs {:.2f}".format(dataGraph.GetY()[0])) if histoSignal and isRightSignal: hname = histoSignal.GetName() label = ('H#rightarrow#mu#tau' if 'signaltaumu' in hname else 'H#rightarrowe#tau' if 'signaltaue' in hname else 'signal') label = ("{}, BR=1%".format(label) if formatAuxMaterial else "{}, BR=1%, {:.2f}".format(label, integralWou(histoSignal))) leg.AddEntry(histoSignal, label, 'l') leg.Draw('same') topPad.Update() tex = r.TLatex() tex.SetTextSize(0.5 * tex.GetTextSize()) tex.SetNDC(True) label = "%s tot bkg : " % (can.GetName()) label += "%.3f #pm %.3f (stat)" % (integralAndError(histoTotBkg)) if systErrBand: sysUp, sysDo = systUtils.totalUpDownVariation(systErrBand) label += "#pm #splitline{%.3f}{%.3f} (syst)" % (sysUp, sysDo) if drawYieldAndError: tex.DrawLatex(0.10, 0.95, label) topPad.SetTopMargin(2.0 * topPad.GetTopMargin()) atlasLabel = drawAtlasLabel(can, align=13, scale=0.75, xpos=0.175 if formatAuxMaterial else 0.125, ypos=0.945 if formatAuxMaterial else None, public=True) if topLabel and not formatAuxMaterial: topRightLabel(can, topLabel, ypos=1.0) yMin, yMax = getMinMax( [histoData, dataGraph, histoTotBkg, histoSignal, totErrBand]) padMaster.SetMinimum(0.0) padMaster.SetMaximum(1.1 * yMax) padMaster.GetXaxis().SetLabelSize(0) padMaster.GetXaxis().SetTitleSize(0) increaseAxisFont(padMaster.GetXaxis()) increaseAxisFont(padMaster.GetYaxis()) topPad.RedrawAxis() topPad.Update() # force stack to create padMaster # draw bot (ratio) can.cd() botPad.Draw() botPad.cd() ratio = systUtils.buildAsymmRatioGraph(dataGraph, totErrBand) ratioPadMaster = padMaster.Clone(padMaster.GetName() + '_ratio') ratioPadMaster.SetDirectory(0) ratioPadMaster.Clear() yMin, yMax = 0.0, 2.0 ratioPadMaster.SetMinimum(yMin) ratioPadMaster.SetMaximum(yMax) ratioPadMaster.SetStats(0) ratioPadMaster.Draw('axis') x_lo, x_hi = getXrange(ratioPadMaster) refLines = [referenceLine(x_lo, x_hi, y, y) for y in [0.5, 1.0, 1.5]] for l in refLines: l.Draw() err_band_r = systUtils.buildErrBandRatioGraph(totErrBand) err_band_r.Draw('E2 same') ratio.Draw('ep') xA, yA = ratioPadMaster.GetXaxis(), ratioPadMaster.GetYaxis() textScaleUp = 0.75 * 1.0 / botPad.GetHNDC() # if xaxis_title : xA.SetTitle(xaxis_title) yA.SetNdivisions(-104) yA.SetTitle('Data/Bkg') yA.CenterTitle() yA.SetTitleOffset(yA.GetTitleOffset() / textScaleUp) xA.SetTitleSize( yA.GetTitleSize()) # was set to 0 for padmaster, restore it xA.SetLabelSize(yA.GetLabelSize()) for a in [xA, yA]: a.SetLabelSize(a.GetLabelSize() * textScaleUp) a.SetTitleSize(a.GetTitleSize() * textScaleUp) botPad._graphical_objects = [ratio, ratioPadMaster, err_band_r ] + refLines # avoid garbage collection botPad.Update() can.Update() for ext in ['png', 'eps']: can.SaveAs(outdir + '/' + can.GetName() + '.' + ext)
def main() : parser = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) add_arg = parser.add_argument add_arg('-o', '--output-dir', default="./") add_arg('-p', '--process', help='one physics process, eg. ttw') add_arg('-s', '--systematic', help='one of the systematic variations') add_arg('-v', '--verbose', action='store_true') add_arg('-d', '--debug', action='store_true') args = parser.parse_args() set_log(args.verbose, args.debug) outdir = args.output_dir process = args.process systematic = args.systematic available_processes = get_input_samples().keys() if process not in available_processes: raise StandardError("invalid process %s, should be one of %s"%(process, str(available_processes))) available_systematics = get_input_samples()[process].keys() if systematic not in available_systematics: raise StandardError("invalid systematic %s, should be one of %s"%(systematic, str(available_systematics))) file_label = process+'_sys_'+systematic plot_label = process+' sys. '+systematic normalize_to_unity = False # True luminosity = 1.0 combiner = HistogramCombiner() combiner.build_samples(process=process, systematic=systematic) histogram_names = ['h_meff', 'h_jetN', # 'h_electronPt', 'h_muonPt', # 'h_bjetEmulN', 'h_bjetN', # 'h_bjetEmulN_sr3b', 'h_bjetN_sr3b', # 'h_bjetEmulN_sr1b', 'h_bjetN_sr1b', # 'h_bjetEmulN_cr2bttV', 'h_bjetN_cr2bttV', # 'h_meff_sr3b', 'h_jetN_sr3b', 'h_met_sr3b', # 'h_meff_sr1b', 'h_jetN_sr1b', 'h_met_sr1b', # 'h_meff_sr0b5j', 'h_jetN_sr0b5j', 'h_met_sr0b5j', # 'h_meff_sr0b4j', 'h_jetN_sr0b4j', 'h_met_sr0b4j', # 'h_meff_sr0b3j', 'h_jetN_sr0b3j', 'h_met_sr0b3j', # 'h_meff_cr2bttV', 'h_jetN_cr2bttV', 'h_met_cr2bttV' ] combiner.compute_normalization_factors() output_pdf_name = outdir+'/'+file_label+'.pdf' c_summary = R.TCanvas('c_summary', 'plotExplicitSamples sampes summary ') combiner.print_sample_summary_to_pdf(c_summary, label="%s: nominal vs. %s systematic"%(process, systematic)) c_summary.SaveAs(output_pdf_name+'(') for histogram_name in histogram_names: rebin = 'meff' in histogram_name # and '_sr' in histogram_name # non-inclusive histos: low stats rebin_factor = (2 if 'meff' in histogram_name else 2 if 'jetN' in histogram_name else 1) if rebin else 1 histograms = combiner.get_histograms(histogram_name=histogram_name) h_nom = histograms['nominal'] h_up = histograms['up'] h_dn = histograms['down'] if 'h_jetFlavorMultiplicity' in histogram_name: h_nom = emulate_btag_multiplicity_from_truth_flavor(h_nom, 'nom') h_up = emulate_btag_multiplicity_from_truth_flavor(h_up, 'up') h_dn = emulate_btag_multiplicity_from_truth_flavor(h_dn, 'dn') histos = [h_nom, h_up, h_dn] for h in set(histos): # set: avoid rebinning twice when up==down h.Rebin(rebin_factor) h_nom.SetLineWidth(2*h_nom.GetLineWidth()) h_nom.SetLineColor(R.kBlack) h_up.SetLineColor(R.kBlue) h_dn.SetLineColor(R.kRed) pad_master = h_nom pad_master.SetMaximum(1.1*max([h.GetMaximum() for h in histos])) pad_master.SetMinimum(1.0*min([0.0]+[h.GetMinimum() for h in histos])) pad_master.GetYaxis().SetTitle('Arbitrary Units') pad_master.SetStats(0) can = R.TCanvas('c_ttV_syst_'+histogram_name, 'ttV explicit variations '+pad_master.GetTitle(), 700, 700) botPad, topPad = ru.buildBotTopPads(can, squeezeMargins=False) # top can.cd() topPad.Draw() topPad.cd() topPad._po = [pad_master] # persistent objects pad_master.GetXaxis().SetTitleSize(0) pad_master.GetXaxis().SetLabelSize(0) pad_master.Draw('axis') # ru.topRightLabel(topPad, pad_master.GetTitle(), xpos=0.5) ru.topRightLabel(topPad, "#bf{#it{ATLAS}} Simulation Preliminary", xpos=0.85, ypos=0.9) ru.topRightLabel(topPad, "#sqrt{s} = 13 TeV", xpos=0.85, ypos=0.8) leg = ru.topRightLegend(can, legWidth=0.225, legHeight=0.300, hShift=-0.10, vShift=-0.25) leg.SetBorderSize(0) # leg.SetHeader(plot_label+ ("(norm=1)" if normalize_to_unity else "(lumi %.1f)"%luminosity)) topPad._po.append(leg) def format_legend_label(h, l): return "{0}: {1:.2E} ({2:.0f})".format(l, h.Integral(), h.GetEntries()) def pretty_scale_legend_label(h, l): return ("nominal" if l is 'nom' else "#mu = 2.0 #mu_{0}" if l is 'up' else "#mu = 0.5 #mu_{0}" if l is 'dn' else 'unknown') for h,l in [(h_nom, 'nom'), (h_up, 'up'), (h_dn, 'dn')]: h.Draw('hist same') leg.AddEntry(h, pretty_scale_legend_label(h, l), 'l') topPad._po.append(h) leg.Draw('same') def integral_and_error(h): error = R.Double(0.0) integral = h.IntegralAndError(1, h.GetNbinsX()+1, error) return integral, error def ratio_and_error(ave=(1.0, 0.01), bve=(2.0, 0.001)): a, sa = ave b, sb = bve if a and b: r = a/b e = r * sqrt((sa/a)*(sa/a)+(sb/b)*(sb/b)) return r, e else: return 0.0, 0.0 print_normalization_summary = histogram_name.startswith('h_meff') if print_normalization_summary: nom_int = h_nom.Integral() up_int = h_up.Integral() dn_int = h_dn.Integral() nom_int, nom_err = integral_and_error(h_nom) up_int, up_err = integral_and_error(h_up) dn_int, dn_err = integral_and_error(h_dn) rup, rupe = ratio_and_error((up_int, up_err), (nom_int, nom_err)) rdn, rdne = ratio_and_error((dn_int, dn_err), (nom_int, nom_err)) # print ("normalization change: " # +"{} up {:.1%} down {:.1%} (nom {:.1f}, up {:.1f}, do {:.1f})".format(h_nom.GetName(), # 1.0-up_int/nom_int if nom_int else 1.0, # 1.0-dn_int/nom_int if nom_int else 1.0, # nom_int, # up_int, # dn_int)) print ("normalization change: " +"{} up {:.1%} +/- {:.1%} down {:.1%} +/- {:.1%} ".format(h_nom.GetName(), 1.0-rup, rupe, 1.0-rdn, rdne) +"(integral: " +"nom {:.2E} +/- {:.2E}, up {:.2E} +/- {:.2E}, do {:.2E} +/- {:.2E})".format(nom_int, nom_err, up_int, up_err, dn_int, dn_err) +" (entries nom {:.2E} up {:.2E} do {:.2E}".format(h_nom.GetEntries(), h_up.GetEntries(), h_dn.GetEntries())) print ("tex normalization change: " +"{} up ${:.1%} \pm {:.1%}$ down ${:.1%} \pm {:.1%}$ ".format(h_nom.GetName(), 1.0-rup, rupe, 1.0-rdn, rdne) ) def bc(h): return [h.GetBinContent(i) for i in range(1,1+h.GetNbinsX())] def max_frac_variation(h1, h2): "maximum bin-by-bin fractional variation; h1 is denominator, empty bins skipped" bc1 = bc(h1) bc2 = bc(h2) return max([abs(b2/b1) for b1, b2 in zip(bc1, bc2) if b1 and b2]) def max_frac_variation_within10(h1, h2): """maximum bin-by-bin fractional variation; h1 is denominator. Bins with <0.1*peak are skipped""" bc1 = bc(h1) bc2 = bc(h2) m1 = max(bc1) m2 = max(bc2) return max([abs(b2/b1) for b1, b2 in zip(bc1, bc2) if b1>0.1*m1 and b2>0.1*m2]) # print ("shape change: " # +"{} up {:.1%} down {:.1%} ".format(h_nom.GetName(), # 1.0-max_frac_variation_within10(h_up, h_nom), # 1.0-max_frac_variation_within10(h_dn, h_nom))) topPad.Update() # bottom can.cd() botPad.SetTopMargin(1.25*botPad.GetTopMargin()) botPad.Draw() botPad.cd() ratio_up = ru.buildRatioHistogram(h_up, h_nom) ratio_dn = ru.buildRatioHistogram(h_dn, h_nom) yMin, yMax = 0.5, 1.5 ratioPadMaster = pad_master.Clone(pad_master.GetName()+'_ratio') ratioPadMaster.SetMinimum(yMin) ratioPadMaster.SetMaximum(yMax) ratioPadMaster.SetStats(0) ratioPadMaster.Draw('axis') x_lo, x_hi = ru.getXrange(ratioPadMaster) refLines = [ru.referenceLine(x_lo, x_hi, y, y) for y in [0.5, 1.0, 1.5] if y>yMin and y<yMax] for l in refLines : l.Draw() ratio_up.Draw('same') ratio_dn.Draw('same') xA, yA = ratioPadMaster.GetXaxis(), ratioPadMaster.GetYaxis() textScaleUp = 0.75*1.0/botPad.GetHNDC() yA.SetNdivisions(-102) yA.SetTitle('Ratio') yA.CenterTitle() yA.SetTitleOffset(yA.GetTitleOffset()/textScaleUp) xA.SetTitleSize(yA.GetTitleSize()) # x- was set to 0 for padmaster, restore it xA.SetLabelSize(yA.GetLabelSize()) xA.SetTitle(prettify_title(xA.GetTitle())) for a in [xA, yA] : a.SetLabelSize(a.GetLabelSize()*textScaleUp) a.SetTitleSize(a.GetTitleSize()*textScaleUp) botPad._graphical_objects = [ratio_up, ratio_dn, ratioPadMaster] + refLines # avoid garbage collection botPad.Update() can.Update() first_histo = histogram_name is histogram_names[0] last_histo = histogram_name is histogram_names[-1] can.SaveAs(outdir+'/'+can.GetName()+'.png') can.SaveAs(outdir+'/'+can.GetName()+'.eps') can.SaveAs(output_pdf_name+ (')' if last_histo else ''))