def __init__(self, width=None, height=None, xtitle=None, ytitle=None, tick_length=15, logy=False): super(SimplePlot, self).__init__(width=width, height=height) left, right, bottom, top = self.margin self.SetMargin(0, 0, 0, 0) # top pad for histograms with self: main = Pad(0., 0., 1., 1.) if logy: main.SetLogy() main.margin = (left, right, bottom, top) main.Draw() # draw axes with main: main_hist = Hist(1, 0, 1) main_hist.Draw('AXIS') if xtitle is not None: main_hist.xaxis.title = xtitle if ytitle is not None: main_hist.yaxis.title = ytitle # set the tick lengths tick_length_pixels(main, main_hist.xaxis, main_hist.yaxis, tick_length) self.main = main self.main_hist = main_hist self.logy = logy
def create_canvas(self, ratio=True): if ratio: self.canvas = Canvas(width=700, height=700) self.canvas.Draw() self.canvas.cd() self.main_pad = Pad(0., 0.25, 1., 1.) self.main_pad.Draw() self.canvas.cd() self.ratio_pad = Pad(0., 0., 1., 0.25) self.ratio_pad.Draw() self.main_pad.SetTicks(True) self.main_pad.SetBottomMargin(0.) self.main_pad.SetLeftMargin(0.15) self.main_pad.SetRightMargin(0.15) self.ratio_pad.SetLeftMargin(0.15) self.ratio_pad.SetRightMargin(0.15) self.ratio_pad.SetTopMargin(0.) self.ratio_pad.SetGridy() self.ratio_pad.SetBottomMargin(0.3) else: self.canvas = Canvas(width=700, height=700) self.canvas.Draw() self.canvas.cd() self.main_pad = Pad(0., 0., 1., 1.) self.main_pad.Draw() self.canvas.cd() self.ratio_pad = Pad(-1., -1., -.9, -.9) self.ratio_pad.Draw() # put it outside the canvas self.main_pad.SetTicks(True) self.main_pad.SetTopMargin(0.15) self.main_pad.SetBottomMargin(0.15) self.main_pad.SetLeftMargin(0.15) self.main_pad.SetRightMargin(0.15)
#bg_scaled_cut = Cut("1")#Cut("pileupWeight*normweight*mcEventWeight*"+str(lumipb)) * cut #top_cutfix = bg_scaled_cut #WW_cutfix = Cut("mcChannelNumber!=361068") * bg_scaled_cut #==================== BEGINS ================================# for (variable, xmin, xmax, nbins, xtitle, ytitle) in zip(variable_list, xmin_list, xmax_list, nbins_list, xtitle_list, ytitle_list): # Canvas Variables and declaration canvas = Canvas(width=700, height=500) bottom_padmargin = 0.0 left_padmargin = 0.0 right_padmargin = 0.0 pad = Pad(left_padmargin, 0.15 - bottom_padmargin, 1 - right_padmargin, 0.95) # pad.SetTopMargin(0.2) pad.Draw() pad.cd() # if variable != 'RISR': #pad.SetLogy() pad.SetLogy() pad.SetGrid() # pad = Canvas(0,0.3,1,1) ##### KEEP THIS # canvas.Divide(1, 2) # pad = canvas.cd(1)
except: print "stack has no integral!" continue if plotWithMPL: gs = mpl.gridspec.GridSpec(2,1,height_ratios=[4,1]) gs.update(wspace=0.00, hspace=0.00) axes = plt.subplot(gs[0]) axes_ratio = plt.subplot(gs[1], sharex=axes) plt.setp(axes.get_xticklabels(), visible=False) if plotWithROOT: c = Canvas(700,700) c.cd() pad1 = Pad( 0, 0.3, 1, 1.0) pad1.SetBottomMargin(0); # Upper and lower plot are joined pad1.SetGrid(); # Vertical grid pad1.Draw(); # Draw the upper pad: pad1 c.cd() pad2 = Pad( 0, 0.05, 1, 0.3); pad2.SetTopMargin(0); # Upper and lower plot are joined pad2.SetBottomMargin(0.3); # Upper and lower plot are joined pad2.SetGrid(); # Vertical grid pad2.Draw(); # Draw the upper pad: pad1 pad1.cd(); # pad1 becomes the current pad rootstack = ROOT.THStack(stack) rootstack.Draw('HIST') rootstack.GetYaxis().SetTitle("Entries"); rootstack.GetYaxis().SetTitleSize(20);
class Plotter(object): def __init__(self, channel, year, plot_dir, base_dir, post_fix, selection_data, selection_mc, selection_tight, pandas_selection, lumi, model, transformation, features, process_signals, plot_signals, blinded, datacards=[], mini_signals=False, do_ratio=True, mc_subtraction=True, dir_suffix='', relaxed_mc_scaling=1., data_driven=True): self.channel = channel.split('_')[0] self.year = year self.full_channel = channel self.plt_dir = '/'.join( [plot_dir, channel, '_'.join([dir_suffix, get_time_str()])]) self.base_dir = base_dir self.post_fix = post_fix self.selection_data = ' & '.join(selection_data) self.selection_mc = ' & '.join(selection_mc) self.selection_tight = selection_tight self.pandas_selection = pandas_selection self.lumi = lumi self.model = model self.transformation = transformation self.features = features self.process_signals = process_signals self.plot_signals = plot_signals if self.process_signals else [] self.blinded = blinded self.selection_lnt = 'not (%s)' % self.selection_tight self.do_ratio = do_ratio self.mini_signals = mini_signals self.datacards = datacards self.mc_subtraction = mc_subtraction self.relaxed_mc_scaling = relaxed_mc_scaling self.data_driven = data_driven if self.year == 2018: from plotter.samples.samples_2018 import get_data_samples, get_mc_samples, get_signal_samples if self.year == 2017: from plotter.samples.samples_2017 import get_data_samples, get_mc_samples, get_signal_samples if self.year == 2016: from plotter.samples.samples_2016 import get_data_samples, get_mc_samples, get_signal_samples self.get_data_samples = get_data_samples self.get_mc_samples = get_mc_samples self.get_signal_samples = get_signal_samples def total_weight_calculator(self, df, weight_list, scalar_weights=[]): total_weight = df[weight_list[0]].to_numpy().astype(np.float) for iw in weight_list[1:]: total_weight *= df[iw].to_numpy().astype(np.float) for iw in scalar_weights: total_weight *= iw return total_weight def create_canvas(self, ratio=True): if ratio: self.canvas = Canvas(width=700, height=700) self.canvas.Draw() self.canvas.cd() self.main_pad = Pad(0., 0.25, 1., 1.) self.main_pad.Draw() self.canvas.cd() self.ratio_pad = Pad(0., 0., 1., 0.25) self.ratio_pad.Draw() self.main_pad.SetTicks(True) self.main_pad.SetBottomMargin(0.) self.main_pad.SetLeftMargin(0.15) self.main_pad.SetRightMargin(0.15) self.ratio_pad.SetLeftMargin(0.15) self.ratio_pad.SetRightMargin(0.15) self.ratio_pad.SetTopMargin(0.) self.ratio_pad.SetGridy() self.ratio_pad.SetBottomMargin(0.3) else: self.canvas = Canvas(width=700, height=700) self.canvas.Draw() self.canvas.cd() self.main_pad = Pad(0., 0., 1., 1.) self.main_pad.Draw() self.canvas.cd() self.ratio_pad = Pad(-1., -1., -.9, -.9) self.ratio_pad.Draw() # put it outside the canvas self.main_pad.SetTicks(True) self.main_pad.SetTopMargin(0.15) self.main_pad.SetBottomMargin(0.15) self.main_pad.SetLeftMargin(0.15) self.main_pad.SetRightMargin(0.15) def create_datacards(self, data, bkgs, signals, label, protect_empty_bins=['nonprompt']): ''' FIXME! For now this is specific to the data-driven case ''' # save a ROOT file with histograms, aka datacard datacard_dir = '/'.join([self.plt_dir, 'datacards']) makedirs(datacard_dir, exist_ok=True) outfile = ROOT.TFile.Open( '/'.join([datacard_dir, 'datacard_%s.root' % label]), 'recreate') outfile.cd() # data in tight data.name = 'data_obs' data.Write() # reads off a dictionary for bkg_name, bkg in bkgs.items(): bkg.name = bkg_name.split('#')[0] bkg.drawstyle = 'HIST E' bkg.color = 'black' bkg.linewidth = 2 # manual protection against empty bins, that would make combine crash if bkg_name in protect_empty_bins: for ibin in bkg.bins_range(): if bkg.GetBinContent(ibin) <= 0.: bkg.SetBinContent(ibin, 1e-2) bkg.SetBinError(ibin, np.sqrt(1e-2)) bkg.Write() # signals for isig in signals: isig.name = isig.name.split('#')[0] isig.drawstyle = 'HIST E' isig.color = 'black' isig.Write() # print out the txt datacard with open( '/'.join([ datacard_dir, 'datacard_%s_%s.txt' % (label, isig.name) ]), 'w') as card: card.write(''' imax 1 number of bins jmax * number of processes minus 1 kmax * number of nuisance parameters -------------------------------------------------------------------------------------------------------------------------------------------- shapes * {cat} datacard_{cat}.root $PROCESS $PROCESS_$SYSTEMATIC -------------------------------------------------------------------------------------------------------------------------------------------- bin {cat} observation {obs:d} -------------------------------------------------------------------------------------------------------------------------------------------- bin {cat} {cat} {cat} process {signal_name} nonprompt prompt process 0 1 2 rate {signal:.4f} {nonprompt:.4f} {prompt:.4f} -------------------------------------------------------------------------------------------------------------------------------------------- lumi lnN 1.025 - - norm_prompt_{ch}_{y}_{cat} lnN - - 1.15 norm_nonprompt_{ch}_{y}_{cat} lnN - 1.20 - norm_sig_{ch}_{y}_{cat} lnN 1.2 - - -------------------------------------------------------------------------------------------------------------------------------------------- {cat} autoMCStats 0 0 1 '''.format( cat=label, obs=int(data.integral()) if self.blinded == False else -1, signal_name=isig.name, signal=isig.integral(), ch=self.full_channel, y=self.year, prompt=bkgs['prompt'].integral(), nonprompt=bkgs['nonprompt'].integral(), )) outfile.Close() def plot(self): evaluator = Evaluator(self.model, self.transformation, self.features) makedirs(self.plt_dir, exist_ok=True) makedirs('/'.join([self.plt_dir, 'lin']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'log']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'lin', 'png']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'lin', 'root']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'log', 'png']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'log', 'root']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'lnt_region', 'lin']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'lnt_region', 'log']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'lnt_region', 'lin', 'png']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'lnt_region', 'lin', 'root']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'lnt_region', 'log', 'png']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'lnt_region', 'log', 'root']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'shapes', 'lin']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'shapes', 'log']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'shapes', 'lin', 'png']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'shapes', 'lin', 'root']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'shapes', 'log', 'png']), exist_ok=True) makedirs('/'.join([self.plt_dir, 'shapes', 'log', 'root']), exist_ok=True) # NN evaluator print('============> starting reading the trees') print('Plots will be stored in: ', self.plt_dir) now = time() signal = [] if self.process_signals: signal = self.get_signal_samples(self.channel, self.base_dir, self.post_fix, self.selection_data, mini=self.mini_signals) else: signal = [] data = self.get_data_samples(self.channel, self.base_dir, self.post_fix, self.selection_data) mc = self.get_mc_samples(self.channel, self.base_dir, self.post_fix, self.selection_mc) print('============> it took %.2f seconds' % (time() - now)) # evaluate FR for isample in (mc + data): #+signal): isample.df['fr'] = evaluator.evaluate(isample.df) # already corrected, ready to be applied in lnt-not-tight isample.df['fr_corr'] = isample.df['fr'] / (1. - isample.df['fr']) # apply an extra selection to the pandas dataframes if len(self.pandas_selection): for isample in (mc + data + signal): isample.df = isample.df.query(self.pandas_selection) # split the dataframe in tight and lnt-not-tight (called simply lnt for short) print('============> splitting dataframe in tight and loose not tight') for isample in (mc + data + signal): isample.df_tight = isample.df.query(self.selection_tight) if isample not in signal: isample.df_lnt = isample.df.query(self.selection_lnt) # free some mem del isample.df gc.collect() print('============> ... done') # sort depending on their position in the stack mc.sort(key=lambda x: x.position_in_stack) # now we plot self.create_canvas(self.do_ratio) for ivar in variables: variable, bins, label, xlabel, ylabel, extra_sel = ivar.var, ivar.bins, ivar.label, ivar.xlabel, ivar.ylabel, ivar.extra_selection print('plotting', label) ###################################################################################### # plot MC stacks, in tight and lnt ###################################################################################### stack_prompt = [] stack_nonprompt = [] stack_nonprompt_control = [] for imc in mc: if extra_sel: mc_df_tight = imc.df_tight.query(extra_sel) mc_df_lnt = imc.df_lnt.query(extra_sel) else: mc_df_tight = imc.df_tight mc_df_lnt = imc.df_lnt histo_tight = Hist(bins, title=imc.label, markersize=0, legendstyle='F', name=imc.datacard_name + '#' + label + '#t') weights = self.total_weight_calculator( mc_df_tight, ['weight'] + imc.extra_signal_weights, [self.lumi, imc.lumi_scaling]) histo_tight.fill_array(mc_df_tight[variable], weights=weights * self.relaxed_mc_scaling) histo_tight.fillstyle = 'solid' histo_tight.fillcolor = 'steelblue' if self.data_driven else imc.colour histo_tight.linewidth = 0 stack_prompt.append(histo_tight) # optionally remove the MC subtraction in loose-not-tight # may help if MC stats is terrible (and it often is) if self.data_driven: if self.mc_subtraction: histo_lnt = Hist(bins, title=imc.label, markersize=0, legendstyle='F', name=imc.datacard_name + '#' + label + '#lnt') weights = self.total_weight_calculator( mc_df_lnt, ['weight', 'fr_corr'] + imc.extra_signal_weights, [-1., self.lumi, imc.lumi_scaling]) histo_lnt.fill_array(mc_df_lnt[variable], weights=weights * self.relaxed_mc_scaling) histo_lnt.fillstyle = 'solid' histo_lnt.fillcolor = 'skyblue' if self.data_driven else imc.colour histo_lnt.linewidth = 0 stack_nonprompt.append(histo_lnt) histo_lnt_control = Hist(bins, title=imc.label, markersize=0, legendstyle='F', name=imc.datacard_name + '#' + label + '#lntcontrol') weights_control = self.total_weight_calculator( mc_df_lnt, ['weight'] + imc.extra_signal_weights, [self.lumi, imc.lumi_scaling]) histo_lnt_control.fill_array(mc_df_lnt[variable], weights=weights_control * self.relaxed_mc_scaling) histo_lnt_control.fillstyle = 'solid' histo_lnt_control.fillcolor = imc.colour histo_lnt_control.linewidth = 0 # print(histo_lnt_control) # print(histo_lnt_control.fillcolor) # print(imc.name, imc.colour) # print(histo_lnt_control.integral()) stack_nonprompt_control.append(histo_lnt_control) # merge different samples together (add the histograms) # prepare two temporary containers for the post-grouping histograms stack_prompt_tmp = [] stack_nonprompt_tmp = [] stack_nonprompt_control_tmp = [] for ini, fin in [(stack_prompt, stack_prompt_tmp), (stack_nonprompt, stack_nonprompt_tmp), (stack_nonprompt_control, stack_nonprompt_control_tmp)]: for k, v in groups.items(): grouped = [] for ihist in ini: if ihist.name.split('#')[0] in v: grouped.append(ihist) elif ihist.name.split('#')[0] not in togroup: fin.append(ihist) if len(grouped): group = sum(grouped) group.title = k group.name = '#'.join([k] + ihist.name.split('#')[1:]) group.fillstyle = grouped[0].fillstyle group.fillcolor = grouped[0].fillcolor group.linewidth = grouped[0].linewidth fin.append(group) stack_prompt = stack_prompt_tmp stack_nonprompt = stack_nonprompt_tmp stack_nonprompt_control = stack_nonprompt_control_tmp ###################################################################################### # plot the signals ###################################################################################### all_signals = [] signals_to_plot = [] for isig in signal: if variable not in self.datacards: if not isig.toplot: continue if variable == 'fr' or variable == 'fr_corr': continue if extra_sel: isig_df_tight = isig.df_tight.query(extra_sel) else: isig_df_tight = isig.df_tight histo_tight = Hist( bins, title=isig.label, markersize=0, legendstyle='L', name=isig.datacard_name + '#' + label ) # the "#" thing is a trick to give hists unique name, else ROOT complains weights = self.total_weight_calculator( isig_df_tight, ['weight'] + isig.extra_signal_weights, [self.lumi, isig.lumi_scaling]) histo_tight.fill_array(isig_df_tight[variable], weights=weights) histo_tight.color = isig.colour histo_tight.fillstyle = 'hollow' histo_tight.linewidth = 2 histo_tight.linestyle = 'dashed' histo_tight.drawstyle = 'HIST' all_signals.append(histo_tight) if isig.toplot: signals_to_plot.append(histo_tight) ###################################################################################### # plot the data ###################################################################################### data_prompt = [] data_nonprompt = [] data_nonprompt_control = [] for idata in data: if extra_sel: idata_df_tight = idata.df_tight.query(extra_sel) idata_df_lnt = idata.df_lnt.query(extra_sel) else: idata_df_tight = idata.df_tight idata_df_lnt = idata.df_lnt histo_tight = Hist(bins, title=idata.label, markersize=1, legendstyle='LEP') histo_tight.fill_array(idata_df_tight[variable]) data_prompt.append(histo_tight) if self.data_driven: histo_lnt = Hist(bins, title=idata.label, markersize=0, legendstyle='F') histo_lnt.fill_array(idata_df_lnt[variable], weights=idata_df_lnt.fr_corr) histo_lnt.fillstyle = 'solid' histo_lnt.fillcolor = 'skyblue' histo_lnt.linewidth = 0 histo_lnt_control = Hist(bins, title=idata.label, markersize=1, legendstyle='LEP') histo_lnt_control.fill_array(idata_df_lnt[variable]) data_nonprompt.append(histo_lnt) data_nonprompt_control.append(histo_lnt_control) if self.data_driven: # put the prompt backgrounds together all_exp_prompt = sum(stack_prompt) all_exp_prompt.title = 'prompt' # put the nonprompt backgrounds together all_exp_nonprompt = sum(stack_nonprompt + data_nonprompt) all_exp_nonprompt.fillstyle = 'solid' all_exp_nonprompt.fillcolor = 'skyblue' all_exp_nonprompt.linewidth = 0 all_exp_nonprompt.title = 'nonprompt' # create the stacks stack = HistStack([all_exp_prompt, all_exp_nonprompt], drawstyle='HIST', title='') stack_control = HistStack(stack_nonprompt_control, drawstyle='HIST', title='') else: stack = HistStack(stack_prompt, drawstyle='HIST', title='') # stat uncertainty hist_error = stack.sum #sum([all_exp_prompt, all_exp_nonprompt]) hist_error.drawstyle = 'E2' hist_error.fillstyle = '/' hist_error.color = 'gray' hist_error.title = 'stat. unc.' hist_error.legendstyle = 'F' if self.data_driven: hist_error_control = stack_control.sum hist_error_control.drawstyle = 'E2' hist_error_control.fillstyle = '/' hist_error_control.color = 'gray' hist_error_control.title = 'stat. unc.' hist_error_control.legendstyle = 'F' # put the data together all_obs_prompt = sum(data_prompt) all_obs_prompt.title = 'observed' if self.data_driven: all_obs_nonprompt_control = sum(data_nonprompt_control) all_obs_nonprompt_control.title = 'observed' all_obs_nonprompt_control.drawstyle = 'EP' # prepare the legend print(signals_to_plot) for jj in signals_to_plot: print(jj.name, jj.integral()) if len(signals_to_plot): legend = Legend([all_obs_prompt, stack, hist_error], pad=self.main_pad, leftmargin=0., rightmargin=0., topmargin=0., textfont=42, textsize=0.025, entrysep=0.01, entryheight=0.04) legend_signals = Legend(signals_to_plot, pad=self.main_pad, leftmargin=0., rightmargin=0., topmargin=0., textfont=42, textsize=0.025, entrysep=0.01, entryheight=0.04) legend_signals.SetBorderSize(0) legend_signals.x1 = 0.42 legend_signals.y1 = 0.74 legend_signals.x2 = 0.88 legend_signals.y2 = 0.90 legend_signals.SetFillColor(0) legend.SetBorderSize(0) legend.x1 = 0.2 legend.y1 = 0.74 legend.x2 = 0.45 legend.y2 = 0.90 legend.SetFillColor(0) else: legend = Legend([all_obs_prompt, stack, hist_error], pad=self.main_pad, leftmargin=0., rightmargin=0., topmargin=0., textfont=42, textsize=0.03, entrysep=0.01, entryheight=0.04) legend.SetBorderSize(0) legend.x1 = 0.55 legend.y1 = 0.74 legend.x2 = 0.88 legend.y2 = 0.90 legend.SetFillColor(0) # plot with ROOT, linear and log scale for islogy in [False, True]: things_to_plot = [stack, hist_error] if not self.blinded: things_to_plot.append(all_obs_prompt) # plot signals, as an option if self.plot_signals: things_to_plot += signals_to_plot # set the y axis range # FIXME! setting it by hand to each object as it doesn't work if passed to draw if islogy: yaxis_max = 40. * max( [ithing.max() for ithing in things_to_plot]) else: yaxis_max = 1.65 * max( [ithing.max() for ithing in things_to_plot]) if islogy: yaxis_min = 0.01 else: yaxis_min = 0. for ithing in things_to_plot: ithing.SetMaximum(yaxis_max) draw(things_to_plot, xtitle=xlabel, ytitle=ylabel, pad=self.main_pad, logy=islogy) # expectation uncertainty in the ratio pad ratio_exp_error = Hist(bins) ratio_data = Hist(bins) for ibin in hist_error.bins_range(): ratio_exp_error.set_bin_content(ibin, 1.) ratio_exp_error.set_bin_error( ibin, hist_error.get_bin_error(ibin) / hist_error.get_bin_content(ibin) if hist_error.get_bin_content(ibin) != 0. else 0.) ratio_data.set_bin_content( ibin, all_obs_prompt.get_bin_content(ibin) / hist_error.get_bin_content(ibin) if hist_error.get_bin_content(ibin) != 0. else 0.) ratio_data.set_bin_error( ibin, all_obs_prompt.get_bin_error(ibin) / hist_error.get_bin_content(ibin) if hist_error.get_bin_content(ibin) != 0. else 0.) ratio_data.drawstyle = 'EP' ratio_data.title = '' ratio_exp_error.drawstyle = 'E2' ratio_exp_error.markersize = 0 ratio_exp_error.title = '' ratio_exp_error.fillstyle = '/' ratio_exp_error.color = 'gray' for ithing in [ratio_data, ratio_exp_error]: ithing.xaxis.set_label_size( ithing.xaxis.get_label_size() * 3. ) # the scale should match that of the main/ratio pad size ratio ithing.yaxis.set_label_size( ithing.yaxis.get_label_size() * 3. ) # the scale should match that of the main/ratio pad size ratio ithing.xaxis.set_title_size( ithing.xaxis.get_title_size() * 3. ) # the scale should match that of the main/ratio pad size ratio ithing.yaxis.set_title_size( ithing.yaxis.get_title_size() * 3. ) # the scale should match that of the main/ratio pad size ratio ithing.yaxis.set_ndivisions(405) ithing.yaxis.set_title_offset(0.4) things_to_plot = [ratio_exp_error] if not self.blinded: things_to_plot.append(ratio_data) draw(things_to_plot, xtitle=xlabel, ytitle='obs/exp', pad=self.ratio_pad, logy=False, ylimits=(0.5, 1.5)) line = ROOT.TLine(min(bins), 1., max(bins), 1.) line.SetLineColor(ROOT.kBlack) line.SetLineWidth(1) self.ratio_pad.cd() line.Draw('same') # chi2_score_text = '\chi^{2}/NDF = %.1f' %(all_obs_prompt.Chi2Test(hist_error, 'UW CHI2/NDF')) chi2_score_text = 'p-value = %.2f' % (all_obs_prompt.Chi2Test( hist_error, 'UW')) chi2_score = ROOT.TLatex(0.7, 0.81, chi2_score_text) chi2_score.SetTextFont(43) chi2_score.SetTextSize(15) chi2_score.SetNDC() chi2_score.Draw('same') self.canvas.cd() # FIXME! add SS and OS channels if self.full_channel == 'mmm': channel = '\mu\mu\mu' elif self.full_channel == 'eee': channel = 'eee' elif self.full_channel == 'mem_os': channel = '\mu^{\pm}\mu^{\mp}e' elif self.full_channel == 'mem_ss': channel = '\mu^{\pm}\mu^{\pm}e' elif self.full_channel == 'eem_os': channel = 'e^{\pm}e^{\mp}\mu' elif self.full_channel == 'eem_ss': channel = 'e^{\pm}e^{\pm}\mu' else: assert False, 'ERROR: Channel not valid.' finalstate = ROOT.TLatex(0.68, 0.68, channel) finalstate.SetTextFont(43) finalstate.SetTextSize(25) finalstate.SetNDC() finalstate.Draw('same') self.canvas.cd() # remove old legend for iprim in self.canvas.primitives: if isinstance(iprim, Legend): self.canvas.primitives.remove(iprim) legend.Draw('same') if self.plot_signals: legend_signals.Draw('same') CMS_lumi(self.main_pad, 4, 0, lumi_13TeV="%d, L = %.1f fb^{-1}" % (self.year, self.lumi / 1000.)) self.canvas.Modified() self.canvas.Update() for iformat in ['pdf', 'png', 'root']: self.canvas.SaveAs('/'.join([ self.plt_dir, 'log' if islogy else 'lin', iformat if iformat != 'pdf' else '', '%s%s.%s' % (label, '_log' if islogy else '_lin', iformat) ])) # plot distributions in loose not tight # check MC contamination there if self.data_driven and variable not in ['fr', 'fr_corr']: things_to_plot = [ stack_control, hist_error_control, all_obs_nonprompt_control ] # set the y axis range # FIXME! setting it by hand to each object as it doesn't work if passed to draw if islogy: yaxis_max = 40. * max( [ithing.max() for ithing in things_to_plot]) else: yaxis_max = 1.65 * max( [ithing.max() for ithing in things_to_plot]) if islogy: yaxis_min = 0.01 else: yaxis_min = 0. for ithing in things_to_plot: ithing.SetMaximum(yaxis_max) ithing.SetMinimum(yaxis_min) draw(things_to_plot, xtitle=xlabel, ytitle=ylabel, pad=self.main_pad, logy=islogy, ylimits=(yaxis_min, yaxis_max)) new_legend = Legend( stack_control.hists + [hist_error_control, all_obs_nonprompt_control], pad=self.main_pad, leftmargin=0., rightmargin=0., topmargin=0., textfont=42, textsize=0.03, entrysep=0.01, entryheight=0.04) new_legend.SetBorderSize(0) new_legend.x1 = 0.55 new_legend.y1 = 0.71 new_legend.x2 = 0.88 new_legend.y2 = 0.90 new_legend.SetFillColor(0) # divide MC to subtract by data stack_nonprompt_control_scaled_list = [] for ihist in stack_control.hists: new_hist = copy(ihist) for ibin in new_hist.bins_range(): new_hist.SetBinContent( ibin, np.nan_to_num( np.divide( new_hist.GetBinContent(ibin), all_obs_nonprompt_control. GetBinContent(ibin)))) new_hist.SetBinError( ibin, np.nan_to_num( np.divide( new_hist.GetBinError(ibin), all_obs_nonprompt_control. GetBinContent(ibin)))) stack_nonprompt_control_scaled_list.append(new_hist) stack_control_scaled = HistStack( stack_nonprompt_control_scaled_list, drawstyle='HIST', title='') stack_control_scaled_err = stack_control_scaled.sum stack_control_scaled_err.drawstyle = 'E2' stack_control_scaled_err.fillstyle = '/' stack_control_scaled_err.color = 'gray' stack_control_scaled_err.title = 'stat. unc.' stack_control_scaled_err.legendstyle = 'F' draw([stack_control_scaled, stack_control_scaled_err], xtitle=xlabel, ytitle='MC/data', pad=self.ratio_pad, logy=False) stack_control_scaled.xaxis.set_label_size( stack_control_scaled.xaxis.get_label_size() * 3. ) # the scale should match that of the main/ratio pad size ratio stack_control_scaled.yaxis.set_label_size( stack_control_scaled.yaxis.get_label_size() * 3. ) # the scale should match that of the main/ratio pad size ratio stack_control_scaled.xaxis.set_title_size( stack_control_scaled.xaxis.get_title_size() * 3. ) # the scale should match that of the main/ratio pad size ratio stack_control_scaled.yaxis.set_title_size( stack_control_scaled.yaxis.get_title_size() * 3. ) # the scale should match that of the main/ratio pad size ratio stack_control_scaled.yaxis.set_ndivisions(405) stack_control_scaled.yaxis.set_title_offset(0.4) stack_control_scaled.SetMinimum(0.) stack_control_scaled.SetMaximum(1.5) CMS_lumi(self.main_pad, 4, 0, lumi_13TeV="%d, L = %.1f fb^{-1}" % (self.year, self.lumi / 1000.)) self.canvas.cd() # remove old legend for iprim in self.canvas.primitives: if isinstance(iprim, Legend): self.canvas.primitives.remove(iprim) # draw new legend new_legend.Draw('same') self.canvas.Modified() self.canvas.Update() for iformat in ['pdf', 'png', 'root']: self.canvas.SaveAs('/'.join([ self.plt_dir, 'lnt_region', 'log' if islogy else 'lin', iformat if iformat != 'pdf' else '', '%s%s.%s' % (label, '_log' if islogy else '_lin', iformat) ])) # compare shapes in tight and loose not tight # data in tight all_obs_prompt_norm = copy(all_obs_prompt) if all_obs_prompt_norm.integral() != 0: all_obs_prompt_norm.Scale( np.nan_to_num( np.divide(1., all_obs_prompt_norm.integral()))) #import pdb; pdb.set_trace() all_obs_prompt_norm.drawstyle = 'hist e' all_obs_prompt_norm.linecolor = 'black' all_obs_prompt_norm.markersize = 0 all_obs_prompt_norm.legendstyle = 'LE' all_obs_prompt_norm.title = '' all_obs_prompt_norm.label = 'data - tight' # data MC subtracted in loose all_obs_prompt_mc_sub_norm = copy(all_obs_prompt) all_obs_prompt_mc_sub_norm.add(all_exp_prompt, -1) all_obs_prompt_mc_sub_norm.Scale( np.nan_to_num( np.divide(1., all_obs_prompt_mc_sub_norm.integral()))) all_obs_prompt_mc_sub_norm.drawstyle = 'hist e' all_obs_prompt_mc_sub_norm.linecolor = 'green' all_obs_prompt_mc_sub_norm.markersize = 0 all_obs_prompt_mc_sub_norm.legendstyle = 'LE' all_obs_prompt_mc_sub_norm.title = '' all_obs_prompt_mc_sub_norm.label = '(data-MC) - tight' # data in loose all_obs_nonprompt_control_norm = copy( all_obs_nonprompt_control) all_obs_nonprompt_control_norm.Scale( np.nan_to_num( np.divide( 1., all_obs_nonprompt_control_norm.integral()))) all_obs_nonprompt_control_norm.drawstyle = 'hist e' all_obs_nonprompt_control_norm.linecolor = 'red' all_obs_nonprompt_control_norm.markersize = 0 all_obs_nonprompt_control_norm.legendstyle = 'LE' all_obs_nonprompt_control_norm.title = '' all_obs_nonprompt_control_norm.label = 'data - l-n-t' # data MC subtracted in loose all_obs_nonprompt_control_mc_sub_norm = copy( all_obs_nonprompt_control) all_obs_nonprompt_control_mc_sub_norm.add( stack_control.sum, -1) all_obs_nonprompt_control_mc_sub_norm.Scale( np.nan_to_num( np.divide( 1., all_obs_nonprompt_control_mc_sub_norm.integral( )))) all_obs_nonprompt_control_mc_sub_norm.drawstyle = 'hist e' all_obs_nonprompt_control_mc_sub_norm.linecolor = 'blue' all_obs_nonprompt_control_mc_sub_norm.markersize = 0 all_obs_nonprompt_control_mc_sub_norm.legendstyle = 'LE' all_obs_nonprompt_control_mc_sub_norm.title = '' all_obs_nonprompt_control_mc_sub_norm.label = '(data-MC) - l-n-t' things_to_plot = [ all_obs_prompt_norm, all_obs_prompt_mc_sub_norm, all_obs_nonprompt_control_norm, all_obs_nonprompt_control_mc_sub_norm, ] yaxis_max = max([ii.GetMaximum() for ii in things_to_plot]) draw(things_to_plot, xtitle=xlabel, ytitle=ylabel, pad=self.main_pad, logy=islogy, ylimits=(yaxis_min, 1.55 * yaxis_max)) self.canvas.cd() # remove old legend for iprim in self.canvas.primitives: if isinstance(iprim, Legend): self.canvas.primitives.remove(iprim) shape_legend = Legend([], pad=self.main_pad, leftmargin=0., rightmargin=0., topmargin=0., textfont=42, textsize=0.03, entrysep=0.01, entryheight=0.04) shape_legend.AddEntry(all_obs_prompt_norm, all_obs_prompt_norm.label, all_obs_prompt_norm.legendstyle) shape_legend.AddEntry( all_obs_prompt_mc_sub_norm, all_obs_prompt_mc_sub_norm.label, all_obs_prompt_mc_sub_norm.legendstyle) shape_legend.AddEntry( all_obs_nonprompt_control_norm, all_obs_nonprompt_control_norm.label, all_obs_nonprompt_control_norm.legendstyle) shape_legend.AddEntry( all_obs_nonprompt_control_mc_sub_norm, all_obs_nonprompt_control_mc_sub_norm.label, all_obs_nonprompt_control_mc_sub_norm.legendstyle) shape_legend.SetBorderSize(0) shape_legend.x1 = 0.50 shape_legend.y1 = 0.71 shape_legend.x2 = 0.88 shape_legend.y2 = 0.90 shape_legend.SetFillColor(0) shape_legend.Draw('same') # plot ratios all_obs_prompt_norm_ratio = copy(all_obs_prompt_norm) all_obs_prompt_mc_sub_norm_ratio = copy( all_obs_prompt_mc_sub_norm) all_obs_nonprompt_control_norm_ratio = copy( all_obs_nonprompt_control_norm) all_obs_nonprompt_control_mc_sub_norm_ratio = copy( all_obs_nonprompt_control_mc_sub_norm) all_obs_prompt_norm_ratio.Divide( all_obs_prompt_mc_sub_norm_ratio) all_obs_nonprompt_control_norm_ratio.Divide( all_obs_prompt_mc_sub_norm_ratio) all_obs_nonprompt_control_mc_sub_norm_ratio.Divide( all_obs_prompt_mc_sub_norm_ratio) things_to_plot_ratio = [ all_obs_prompt_norm_ratio, all_obs_nonprompt_control_norm_ratio, all_obs_nonprompt_control_mc_sub_norm_ratio, ] for ithing in things_to_plot_ratio: ithing.xaxis.set_label_size( ithing.xaxis.get_label_size() * 3. ) # the scale should match that of the main/ratio pad size ratio ithing.yaxis.set_label_size( ithing.yaxis.get_label_size() * 3. ) # the scale should match that of the main/ratio pad size ratio ithing.xaxis.set_title_size( ithing.xaxis.get_title_size() * 3. ) # the scale should match that of the main/ratio pad size ratio ithing.yaxis.set_title_size( ithing.yaxis.get_title_size() * 3. ) # the scale should match that of the main/ratio pad size ratio ithing.yaxis.set_ndivisions(405) ithing.yaxis.set_title_offset(0.4) ithing.SetMinimum(0.) ithing.SetMaximum(2.) draw(things_to_plot_ratio, xtitle=xlabel, ytitle='1/(data-MC)_{tight}', pad=self.ratio_pad, logy=False, ylimits=(0., 2.)) self.ratio_pad.cd() line.Draw('same') CMS_lumi(self.main_pad, 4, 0, lumi_13TeV="%d, L = %.1f fb^{-1}" % (self.year, self.lumi / 1000.)) self.canvas.Modified() self.canvas.Update() for iformat in ['pdf', 'png', 'root']: self.canvas.SaveAs('/'.join([ self.plt_dir, 'shapes', 'log' if islogy else 'lin', iformat if iformat != 'pdf' else '', '%s%s.%s' % (label, '_log' if islogy else '_lin', iformat) ])) # save only the datacards you want, don't flood everything if len(self.datacards) and label not in self.datacards: continue # FIXME! allow it to save datacards even for non data driven bkgs if self.data_driven: self.create_datacards(data=all_obs_prompt, bkgs={ 'prompt': all_exp_prompt, 'nonprompt': all_exp_nonprompt }, signals=all_signals, label=label)
canvas = Canvas(width=canvaswidth,height=int((1-(1-doRatio)*0.2)*canvasheight)) canvas.SetFrameBorderMode(0) topmargins = (1.0 , 1.0 ) bottommargins = (0.0 , 0.4 ) leftmargins = (0.0 , 0.0 ) rightmargins = (0.0 , 0.0 ) top = topmargins[doRatio] bottom = bottommargins[doRatio] left = leftmargins[doRatio] right = 1 - rightmargins[doRatio] canvas.cd() histpad = Pad(left,bottom,right, top,color="white",bordersize =5) if not doRatio: histpad.SetBottomMargin(0.15) histpad.SetFrameBorderMode(0) histpad.Draw() histpad.SetLogy() histpad.cd() histpad.SetFrameBorderSize(2) histpad.SetFrameLineWidth(2); canvas.cd() #ratiopad = Pad(leftmargins[1],0.00,1 - rightmargins[1],bottommargins[1]-0.02) ratiopad = Pad(leftmargins[1],0.00,1 - rightmargins[1],bottommargins[1]-0.02) ratiopad.SetBottomMargin(0.33)
def draw_to_canvas(self): """ Draw this figure to a canvas, which is then returned. """ if len(self._plottables) == 0: raise IndexError("No plottables defined") c = Canvas(width=self.style.canvasWidth, height=self.style.canvasHeight, size_includes_decorations=True) if self.legend.position == 'seperate': legend_width = .2 pad_legend = Pad(1 - legend_width, 0, 1., 1., name="legend") pad_legend.SetLeftMargin(0.0) pad_legend.SetFillStyle(0) # make this pad transparent pad_legend.Draw() else: legend_width = 0 pad_plot = Pad(0., 0., 1 - legend_width, 1., name="plot", ) pad_plot.SetMargin(*self.style.plot_margins) pad_plot.Draw() pad_plot.cd() # awkward hack around a bug in get limits where everything fails if one plottable is shitty... xmin, xmax, ymin, ymax = None, None, None, None for pdic in self._plottables: try: limits = get_limits(pdic['p'], logx=self.plot.logx, logy=self.plot.logy) # Beware: Python 2 evaluates min/max of None in an undefined way with no error! Wow... xmin = min([xmin, limits[0]]) if xmin is not None else limits[0] xmax = max([xmax, limits[1]]) if xmax is not None else limits[1] ymin = min([ymin, limits[2]]) if ymin is not None else limits[2] ymax = max([ymax, limits[3]]) if ymax is not None else limits[3] except (TypeError, ValueError): # some plottables do not work with this rootpy function (eg. graph without points, tf1) # TODO: should be fixed upstream pass # overwrite these ranges if defaults are given if self.plot.xmin is not None: xmin = self.plot.xmin if self.plot.xmax is not None: xmax = self.plot.xmax if self.plot.ymax is not None: ymax = self.plot.ymax if self.plot.ymin is not None: ymin = self.plot.ymin if not all([val is not None for val in [xmin, xmax, ymin, ymax]]): raise TypeError("unable to determine plot axes ranges from the given plottables") colors = get_color_generator(self.plot.palette, self.plot.palette_ncolors) # draw an empty frame within the given ranges; frame_from_plottable = [p for p in self._plottables if p.get('use_as_frame')] if len(frame_from_plottable) > 0: frame = frame_from_plottable[0]['p'].Clone('__frame') frame.Reset() frame.SetStats(0) frame.xaxis.SetRangeUser(xmin, xmax) frame.yaxis.SetRangeUser(ymin, ymax) frame.GetXaxis().SetTitle(self.xtitle) frame.GetYaxis().SetTitle(self.ytitle) self._theme_plottable(frame) frame.Draw() else: frame = Graph() frame.SetName("__frame") # add a silly point in order to have root draw this frame... frame.SetPoint(0, 0, 0) frame.GetXaxis().SetLimits(xmin, xmax) frame.GetYaxis().SetLimits(ymin, ymax) frame.SetMinimum(ymin) frame.SetMaximum(ymax) frame.GetXaxis().SetTitle(self.xtitle) frame.GetYaxis().SetTitle(self.ytitle) self._theme_plottable(frame) # Draw this frame: 'A' should draw the axis, but does not work if nothing else is drawn. # L would draw a line between the points but is seems to do nothing if only one point is present # P would also draw that silly point but we don't want that! frame.Draw("AL") xtick_length = frame.GetXaxis().GetTickLength() ytick_length = frame.GetYaxis().GetTickLength() for i, pdic in enumerate(self._plottables): obj = pdic['p'] if isinstance(obj, ROOT.TLegendEntry): _root_color = Color(pdic['color']) _root_markerstyle = MarkerStyle(pdic['markerstyle']) obj.SetMarkerStyle(_root_markerstyle('root')) obj.SetMarkerColor(_root_color('root')) elif isinstance(obj, (ROOT.TH1, ROOT.TGraph, ROOT.TF1)): self._theme_plottable(obj) obj.SetMarkerStyle(pdic.get('markerstyle', 'circle')) if pdic.get('color', None): obj.color = pdic['color'] else: try: color = next(colors) except StopIteration: log.warning("Ran out of colors; defaulting to black") color = 1 obj.color = color xaxis = obj.GetXaxis() yaxis = obj.GetYaxis() # Set the title to the given title: obj.title = self.title # the xaxis depends on the type of the plottable :P if isinstance(obj, ROOT.TGraph): # SetLimit on a TH1 is simply messing up the # lables of the axis to screw over the user, presumably... xaxis.SetLimits(xmin, xmax) yaxis.SetLimits(ymin, ymax) # for unbinned data # 'P' plots the current marker, 'L' would connect the dots with a simple line # see: https://root.cern.ch/doc/master/classTGraphPainter.html for more draw options drawoption = 'Psame' elif isinstance(obj, ROOT.TH1): obj.SetStats(0) xaxis.SetRangeUser(xmin, xmax) yaxis.SetRangeUser(ymin, ymax) drawoption = 'same' elif isinstance(obj, ROOT.TF1): # xaxis.SetLimits(xmin, xmax) # yaxis.SetLimits(ymin, ymax) # for unbinned data drawoption = 'same' obj.Draw(drawoption) # Its ok if obj is non; then we just add it to the legend. else: raise TypeError("Un-plottable type given.") pad_plot.SetTicks() pad_plot.SetLogx(self.plot.logx) pad_plot.SetLogy(self.plot.logy) pad_plot.SetGridx(self.plot.gridx) pad_plot.SetGridy(self.plot.gridy) # do we have legend titles? if any([pdic.get('legend_title') for pdic in self._plottables]): leg = self._create_legend() longest_label = 0 for pdic in self._plottables: if not pdic.get('legend_title', False): continue leg.AddEntry(pdic['p'], pdic['legend_title']) if len(pdic['legend_title']) > longest_label: longest_label = len(pdic['legend_title']) # Set the legend position # vertical: if self.legend.position.startswith('t'): leg_hight = leg.y2 - leg.y1 leg.y2 = 1 - pad_plot.GetTopMargin() - ytick_length leg.y1 = leg.y2 - leg_hight elif self.legend.position.startswith('b'): leg_hight = leg.y2 - leg.y1 leg.y1 = pad_plot.GetBottomMargin() + ytick_length leg.y2 = leg.y1 + leg_hight # horizontal: if self.legend.position[1:].startswith('l'): leg_width = 0.3 leg.x1 = pad_plot.GetLeftMargin() + xtick_length leg.x2 = leg.x1 + leg_width elif self.legend.position[1:].startswith('r'): leg_width = 0.3 leg.x2 = 1 - pad_plot.GetRightMargin() - xtick_length leg.x1 = leg.x2 - leg_width if self.legend.position == 'seperate': with pad_legend: leg.Draw() else: leg.Draw() if self.plot.logx: pad_plot.SetLogx(True) if self.plot.logy: pad_plot.SetLogy(True) pad_plot.Update() # needed sometimes with import of canvas. maybe because other "plot" pads exist... return c
} hists = { } maxY = 0 minY = 99999999 for f in files: normalisations[f] = read_tuple_from_file( files[f] )['QCD'] hists[f] = value_error_tuplelist_to_hist( normalisations[f], reco_bin_edges_vis[variable] ).Rebin(2) maxY = max([maxY]+list(hists[f].y() ) ) minY = min([minY]+list(hists[f].y() ) ) if minY <= 0 : minY = 0.1 can = Canvas() pad1 = Pad( 0, 0.3, 1, 1) pad2 = Pad( 0, 0, 1, 0.3) pad1.Draw() pad2.Draw() pad1.cd() # print normalisations hists['central'].SetLineColor(2) hists['central'].SetLineWidth(3) hists['central'].SetLineStyle(3) hists['central'].GetYaxis().SetRangeUser(minY*0.9,maxY*1.2) hists['central'].Draw('HIST E') hists['QCD_signal_MC'].SetLineColor(4) hists['QCD_signal_MC'].SetLineWidth(3)
def __init__(self, width=None, height=None, offset=0, ratio_height=None, ratio_margin=26, ratio_limits=(0, 2), ratio_divisions=4, prune_ratio_ticks=False, ratio_line_values=(1, ), ratio_line_width=2, ratio_line_style='dashed', xtitle=None, ytitle=None, ratio_title=None, tick_length=15, logy=False): # first init as normal canvas super(RatioPlot, self).__init__(width=width, height=height) # get margins in pixels left, right, bottom, top = self.margin_pixels default_height = self.height default_frame_height = default_height - bottom - top if ratio_height is None: ratio_height = default_height / 4. self.height += int(ratio_height) + ratio_margin + offset self.margin = (0, 0, 0, 0) main_height = default_frame_height + top + ratio_margin / 2. + offset ratio_height += ratio_margin / 2. + bottom # top pad for histograms with self: main = Pad(0., ratio_height / self.height, 1., 1.) if logy: main.SetLogy() main.margin_pixels = (left, right, ratio_margin / 2., top) main.Draw() # bottom pad for ratio plot with self: ratio = Pad(0, 0, 1, ratio_height / self.height) ratio.margin_pixels = (left, right, bottom, ratio_margin / 2.) ratio.Draw() # draw main axes with main: main_hist = Hist(1, 0, 1) main_hist.Draw('AXIS') # hide x-axis labels and title on main pad xaxis, yaxis = main_hist.xaxis, main_hist.yaxis xaxis.SetLabelOffset(1000) xaxis.SetTitleOffset(1000) # adjust y-axis title spacing yaxis.SetTitleOffset(yaxis.GetTitleOffset() * self.height / default_height) # draw ratio axes with ratio: ratio_hist = Hist(1, 0, 1) ratio_hist.Draw('AXIS') # adjust x-axis label and title spacing xaxis, yaxis = ratio_hist.xaxis, ratio_hist.yaxis xaxis.SetLabelOffset(xaxis.GetLabelOffset() * self.height / ratio_height) xaxis.SetTitleOffset(xaxis.GetTitleOffset() * self.height / ratio_height) # adjust y-axis title spacing yaxis.SetTitleOffset(yaxis.GetTitleOffset() * self.height / default_height) if ratio_limits is not None: low, high = ratio_limits if prune_ratio_ticks: delta = 0.01 * (high - low) / float(ratio_divisions % 100) low += delta high -= delta yaxis.SetLimits(low, high) yaxis.SetRangeUser(low, high) yaxis.SetNdivisions(ratio_divisions) if xtitle is not None: ratio_hist.xaxis.title = xtitle if ytitle is not None: main_hist.yaxis.title = ytitle if ratio_title is not None: ratio_hist.yaxis.title = ratio_title # set the tick lengths tick_length_pixels(main, main_hist.xaxis, main_hist.yaxis, tick_length) tick_length_pixels(ratio, ratio_hist.xaxis, ratio_hist.yaxis, tick_length) # draw ratio lines lines = [] if ratio_line_values: with ratio: for value in ratio_line_values: line = Line(0, value, 1, value) line.linestyle = ratio_line_style line.linewidth = ratio_line_width line.Draw() lines.append(line) self.lines = lines self.main = main self.main_hist = main_hist self.ratio = ratio self.ratio_hist = ratio_hist self.ratio_limits = ratio_limits self.logy = logy
# draw the text we need textConfigs = plots.get('config', {}).get('texts', []) textLocals = plots_path.get('texts', []) for text in chain(textConfigs, textLocals): # attach the label label = ROOT.TLatex(text['x'], text['y'], text['label']) label.SetTextFont(text['font']) label.SetTextSize(text['size']) label.SetNDC() label.Draw() # draw the ratio if plots_path.get('ratio', plots_config.get('ratio', False)): canvas.get_pad(0).set_bottom_margin(0.3) p = Pad(0,0,1,1) # create new pad, fullsize to have equal font-sizes in both plots #p.set_top_margin(1-canvas.get_bottom_margin()) # top-boundary (should be 1-thePad->GetBottomMargin()) p.set_top_margin(1-canvas.get_pad(0).get_bottom_margin()) # top-boundary (should be 1-thePad->GetBottomMargin()) p.set_right_margin(canvas.get_right_margin()) p.set_left_margin(canvas.get_left_margin()) p.set_fill_style(0) # needs to be transparent p.set_gridy(True) p.Draw() p.cd() # do ratio for each histogram in solo hist for i,hist in enumerate(soloHists): # if (i>0): continue ratio = Hist.divide(hist, sum(hstack)) ratio.draw("same")
stack.sum.Integral() except: print "stack has no integral!" continue if plotWithMPL: gs = mpl.gridspec.GridSpec(2, 1, height_ratios=[4, 1]) gs.update(wspace=0.00, hspace=0.00) axes = plt.subplot(gs[0]) axes_ratio = plt.subplot(gs[1], sharex=axes) plt.setp(axes.get_xticklabels(), visible=False) if plotWithROOT: c = Canvas(700, 700) c.cd() pad1 = Pad(0, 0.3, 1, 1.0) pad1.SetBottomMargin(0) # Upper and lower plot are joined pad1.SetGrid() # Vertical grid pad1.Draw() # Draw the upper pad: pad1 c.cd() pad2 = Pad(0, 0.05, 1, 0.3) pad2.SetTopMargin(0) # Upper and lower plot are joined pad2.SetBottomMargin(0.3) # Upper and lower plot are joined pad2.SetGrid() # Vertical grid pad2.Draw()
def draw_to_canvas(self): """ Draw this figure to a canvas, which is then returned. """ if len(self._plottables) == 0: raise IndexError("No plottables defined") c = Canvas(width=self.style.canvasWidth, height=self.style.canvasHeight, size_includes_decorations=True) if self.legend.position == 'seperate': legend_width = .2 pad_legend = Pad(1 - legend_width, 0, 1., 1., name="legend") pad_legend.SetLeftMargin(0.0) pad_legend.SetFillStyle(0) # make this pad transparent pad_legend.Draw() else: legend_width = 0 pad_plot = Pad( 0., 0., 1 - legend_width, 1., name="plot", ) pad_plot.SetMargin(*self.style.plot_margins) pad_plot.Draw() pad_plot.cd() # awkward hack around a bug in get limits where everything fails if one plottable is shitty... xmin, xmax, ymin, ymax = None, None, None, None for pdic in self._plottables: try: limits = get_limits(pdic['p'], logx=self.plot.logx, logy=self.plot.logy) # Beware: Python 2 evaluates min/max of None in an undefined way with no error! Wow... xmin = min([xmin, limits[0] ]) if xmin is not None else limits[0] xmax = max([xmax, limits[1] ]) if xmax is not None else limits[1] ymin = min([ymin, limits[2] ]) if ymin is not None else limits[2] ymax = max([ymax, limits[3] ]) if ymax is not None else limits[3] except (TypeError, ValueError): # some plottables do not work with this rootpy function (eg. graph without points, tf1) # TODO: should be fixed upstream pass # overwrite these ranges if defaults are given if self.plot.xmin is not None: xmin = self.plot.xmin if self.plot.xmax is not None: xmax = self.plot.xmax if self.plot.ymax is not None: ymax = self.plot.ymax if self.plot.ymin is not None: ymin = self.plot.ymin if not all([val is not None for val in [xmin, xmax, ymin, ymax]]): raise TypeError( "unable to determine plot axes ranges from the given plottables" ) colors = get_color_generator(self.plot.palette, self.plot.palette_ncolors) # draw an empty frame within the given ranges; frame_from_plottable = [ p for p in self._plottables if p.get('use_as_frame') ] if len(frame_from_plottable) > 0: frame = frame_from_plottable[0]['p'].Clone('__frame') frame.Reset() frame.SetStats(0) frame.xaxis.SetRangeUser(xmin, xmax) frame.yaxis.SetRangeUser(ymin, ymax) frame.GetXaxis().SetTitle(self.xtitle) frame.GetYaxis().SetTitle(self.ytitle) self._theme_plottable(frame) frame.Draw() else: frame = Graph() frame.SetName("__frame") # add a silly point in order to have root draw this frame... frame.SetPoint(0, 0, 0) frame.GetXaxis().SetLimits(xmin, xmax) frame.GetYaxis().SetLimits(ymin, ymax) frame.SetMinimum(ymin) frame.SetMaximum(ymax) frame.GetXaxis().SetTitle(self.xtitle) frame.GetYaxis().SetTitle(self.ytitle) self._theme_plottable(frame) # Draw this frame: 'A' should draw the axis, but does not work if nothing else is drawn. # L would draw a line between the points but is seems to do nothing if only one point is present # P would also draw that silly point but we don't want that! frame.Draw("AL") xtick_length = frame.GetXaxis().GetTickLength() ytick_length = frame.GetYaxis().GetTickLength() for i, pdic in enumerate(self._plottables): obj = pdic['p'] if isinstance(obj, ROOT.TLegendEntry): _root_color = Color(pdic['color']) _root_markerstyle = MarkerStyle(pdic['markerstyle']) obj.SetMarkerStyle(_root_markerstyle('root')) obj.SetMarkerColor(_root_color('root')) elif isinstance(obj, (ROOT.TH1, ROOT.TGraph, ROOT.TF1)): self._theme_plottable(obj) obj.SetMarkerStyle(pdic.get('markerstyle', 'circle')) if pdic.get('color', None): obj.color = pdic['color'] else: try: color = next(colors) except StopIteration: log.warning("Ran out of colors; defaulting to black") color = 1 obj.color = color xaxis = obj.GetXaxis() yaxis = obj.GetYaxis() # Set the title to the given title: obj.title = self.title # the xaxis depends on the type of the plottable :P if isinstance(obj, ROOT.TGraph): # SetLimit on a TH1 is simply messing up the # lables of the axis to screw over the user, presumably... xaxis.SetLimits(xmin, xmax) yaxis.SetLimits(ymin, ymax) # for unbinned data # 'P' plots the current marker, 'L' would connect the dots with a simple line # see: https://root.cern.ch/doc/master/classTGraphPainter.html for more draw options drawoption = 'Psame' elif isinstance(obj, ROOT.TH1): obj.SetStats(0) xaxis.SetRangeUser(xmin, xmax) yaxis.SetRangeUser(ymin, ymax) drawoption = 'same' elif isinstance(obj, ROOT.TF1): # xaxis.SetLimits(xmin, xmax) # yaxis.SetLimits(ymin, ymax) # for unbinned data drawoption = 'same' obj.Draw(drawoption) # Its ok if obj is non; then we just add it to the legend. else: raise TypeError("Un-plottable type given.") pad_plot.SetTicks() pad_plot.SetLogx(self.plot.logx) pad_plot.SetLogy(self.plot.logy) pad_plot.SetGridx(self.plot.gridx) pad_plot.SetGridy(self.plot.gridy) # do we have legend titles? if any([pdic.get('legend_title') for pdic in self._plottables]): leg = self._create_legend() longest_label = 0 for pdic in self._plottables: if not pdic.get('legend_title', False): continue leg.AddEntry(pdic['p'], pdic['legend_title']) if len(pdic['legend_title']) > longest_label: longest_label = len(pdic['legend_title']) # Set the legend position # vertical: if self.legend.position.startswith('t'): leg_hight = leg.y2 - leg.y1 leg.y2 = 1 - pad_plot.GetTopMargin() - ytick_length leg.y1 = leg.y2 - leg_hight elif self.legend.position.startswith('b'): leg_hight = leg.y2 - leg.y1 leg.y1 = pad_plot.GetBottomMargin() + ytick_length leg.y2 = leg.y1 + leg_hight # horizontal: if self.legend.position[1:].startswith('l'): leg_width = 0.3 leg.x1 = pad_plot.GetLeftMargin() + xtick_length leg.x2 = leg.x1 + leg_width elif self.legend.position[1:].startswith('r'): leg_width = 0.3 leg.x2 = 1 - pad_plot.GetRightMargin() - xtick_length leg.x1 = leg.x2 - leg_width if self.legend.position == 'seperate': with pad_legend: leg.Draw() else: leg.Draw() if self.plot.logx: pad_plot.SetLogx(True) if self.plot.logy: pad_plot.SetLogy(True) pad_plot.Update( ) # needed sometimes with import of canvas. maybe because other "plot" pads exist... return c
hist.markercolor = color hist.fillcolor = color hist.title += ' [%s, %s]' % (low_edge, up_edge) low_edge = up_edge hist.drawstyle = 'P' hist.inlegend = True hist.legendstyle = 'p' hist.xaxis.title = 'Optimization iteration (Bin)' hist.yaxis.title = 'Relative uncertainty' for idx, json in enumerate(jsons): for hist, fit_bin in zip(hists, json): hist[idx+1].value = fit_bin['one_sigma']['relative'] canvas = Canvas(800, 800) pad = Pad(0, 0., 1., 0.33) pad.SetPad(0, 0.33, 1., 1.) pad.Draw() lower_pad = Pad(0, 0., 1., 0.33) lower_pad.Draw() nhists = len(hists) nlegends = int(ceil(float(nhists) / 5)) nhist_for_leg = int(ceil(float(nhists)/nlegends)) legends = [] #left_margin = 0.1 #right_margin = 0.77 left_margin = 0.05 right_margin = 0.82 for _ in range(nlegends-1): legends.append(
def __init__(self, width=None, height=None, offset=0, ratio_height=None, ratio_margin=26, ratio_limits=(0, 2), ratio_divisions=4, prune_ratio_ticks=False, ratio_line_values=(1,), ratio_line_width=2, ratio_line_style='dashed', xtitle=None, ytitle=None, ratio_title=None, tick_length=15, logy=False): # first init as normal canvas super(RatioPlot, self).__init__(width=width, height=height) # get margins in pixels left, right, bottom, top = self.margin_pixels default_height = self.height default_frame_height = default_height - bottom - top if ratio_height is None: ratio_height = default_height / 4. self.height += int(ratio_height) + ratio_margin + offset self.margin = (0, 0, 0, 0) main_height = default_frame_height + top + ratio_margin / 2. + offset ratio_height += ratio_margin / 2. + bottom # top pad for histograms with self: main = Pad(0., ratio_height / self.height, 1., 1.) if logy: main.SetLogy() main.margin_pixels = (left, right, ratio_margin / 2., top) main.Draw() # bottom pad for ratio plot with self: ratio = Pad(0, 0, 1, ratio_height / self.height) ratio.margin_pixels = (left, right, bottom, ratio_margin / 2.) ratio.Draw() # draw main axes with main: main_hist = Hist(1, 0, 1) main_hist.Draw('AXIS') # hide x-axis labels and title on main pad xaxis, yaxis = main_hist.xaxis, main_hist.yaxis xaxis.SetLabelOffset(1000) xaxis.SetTitleOffset(1000) # adjust y-axis title spacing yaxis.SetTitleOffset( yaxis.GetTitleOffset() * self.height / default_height) # draw ratio axes with ratio: ratio_hist = Hist(1, 0, 1) ratio_hist.Draw('AXIS') # adjust x-axis label and title spacing xaxis, yaxis = ratio_hist.xaxis, ratio_hist.yaxis xaxis.SetLabelOffset( xaxis.GetLabelOffset() * self.height / ratio_height) xaxis.SetTitleOffset( xaxis.GetTitleOffset() * self.height / ratio_height) # adjust y-axis title spacing yaxis.SetTitleOffset( yaxis.GetTitleOffset() * self.height / default_height) if ratio_limits is not None: low, high = ratio_limits if prune_ratio_ticks: delta = 0.01 * (high - low) / float(ratio_divisions % 100) low += delta high -= delta yaxis.SetLimits(low, high) yaxis.SetRangeUser(low, high) yaxis.SetNdivisions(ratio_divisions) if xtitle is not None: ratio_hist.xaxis.title = xtitle if ytitle is not None: main_hist.yaxis.title = ytitle if ratio_title is not None: ratio_hist.yaxis.title = ratio_title # set the tick lengths tick_length_pixels(main, main_hist.xaxis, main_hist.yaxis, tick_length) tick_length_pixels(ratio, ratio_hist.xaxis, ratio_hist.yaxis, tick_length) # draw ratio lines lines = [] if ratio_line_values: with ratio: for value in ratio_line_values: line = Line(0, value, 1, value) line.linestyle = ratio_line_style line.linewidth = ratio_line_width line.Draw() lines.append(line) self.lines = lines self.main = main self.main_hist = main_hist self.ratio = ratio self.ratio_hist = ratio_hist self.ratio_limits = ratio_limits self.logy = logy
normalisations = {} hists = {} maxY = 0 minY = 99999999 for f in files: normalisations[f] = read_tuple_from_file(files[f])['QCD'] hists[f] = value_error_tuplelist_to_hist( normalisations[f], reco_bin_edges_vis[variable]).Rebin(2) maxY = max([maxY] + list(hists[f].y())) minY = min([minY] + list(hists[f].y())) if minY <= 0: minY = 0.1 can = Canvas() pad1 = Pad(0, 0.3, 1, 1) pad2 = Pad(0, 0, 1, 0.3) pad1.Draw() pad2.Draw() pad1.cd() # print normalisations hists['central'].SetLineColor(2) hists['central'].SetLineWidth(3) hists['central'].SetLineStyle(3) hists['central'].GetYaxis().SetRangeUser(minY * 0.9, maxY * 1.2) hists['central'].Draw('HIST E') hists['QCD_signal_MC'].SetLineColor(4) hists['QCD_signal_MC'].SetLineWidth(3) hists['QCD_signal_MC'].SetLineStyle(1)
if args.underflow: h.SetBinContent(1, h.GetBinContent(1) + h.GetBinContent(0)) # # h = h.merge_bins([(0, 1),]) h.drawstyle = 'hist' h.color = sigCOLORS[i] h.legendstyle = 'L' h.linewidth = 2 hs.append(h) legItems.append(h) if args.dataset == 'all': # divide canvas to draw ratio mainPad = Pad(0, 0.25, 1, 1.) mainPad.SetBottomMargin(0.0) mainPad.Draw() subPad = Pad(0, 0.05, 1, 0.24) subPad.SetTopMargin(0.02) subPad.SetBottomMargin(0.25) subPad.Draw() else: mainPad = ROOT.gPad mainPad.cd() legend = Legend(legItems, pad=mainPad, margin=0.25, leftmargin=0.45, topmargin=0.02,
# draw the text we need textConfigs = plots.get('config', {}).get('texts', []) textLocals = plots_path.get('texts', []) for text in chain(textConfigs, textLocals): # attach the label label = ROOT.TLatex(text['x'], text['y'], text['label']) label.SetTextFont(text['font']) label.SetTextSize(text['size']) label.SetNDC() label.Draw() # draw the ratio if plots_path.get('ratio', False): canvas.get_pad(0).set_bottom_margin(0.3) p = Pad(0,0,1,1) # create new pad, fullsize to have equal font-sizes in both plots #p.set_top_margin(1-canvas.get_bottom_margin()) # top-boundary (should be 1-thePad->GetBottomMargin()) p.set_top_margin(1-canvas.get_pad(0).get_bottom_margin()) # top-boundary (should be 1-thePad->GetBottomMargin()) p.set_right_margin(canvas.get_right_margin()) p.set_left_margin(canvas.get_left_margin()) p.set_fill_style(0) # needs to be transparent p.set_gridy(True) p.Draw() p.cd() # do ratio for each histogram in solo hist for hist in soloHists: ratio = Hist.divide(hist, sum(hstack)) ratio.draw() #set_minmax(ratio, plots_path) #get_axis(ratio, 'x').SetNdivisions(canvasConfigs.get('ndivisions', 5))