def find_best_working_point(effs, signal_efficiencies, background_efficiencies): # Compute efficiency gradients effs_diff = np.ediff1d(effs) signal_efficiencies_diff = np.ediff1d(signal_efficiencies) background_efficiencies_diff = np.ediff1d(background_efficiencies) signal_efficiencies_diff = np.divide(signal_efficiencies_diff, effs_diff) background_efficiencies_diff = np.divide(background_efficiencies_diff, effs_diff) # Interpolate and find points where background efficiency gradient > signal efficiency gradient (with some tolerance) interp_x = np.linspace(np.amin(effs[1:]), np.amax(effs[1:]), 1000) interp_signal = np.interp(interp_x, effs[1:], signal_efficiencies_diff) interp_background = np.interp(interp_x, effs[1:], background_efficiencies_diff) optimal_points = np.argwhere( np.greater(interp_background - 0.05, interp_signal)).ravel( ) # Use a tolerance of 0.02 in case of fluctuations if len(optimal_points) == 0: print 'WARNING: no working point found where the efficiency gradient is larger for background than for signal' # Find optimal point with smallest efficiency ## Compute spacing between points, and select those with an efficiency separation > 2% optimal_discontinuities = np.argwhere( np.ediff1d(interp_x[optimal_points]) > 0.02).ravel() ## Select the point with the largest efficiency optimal_index = np.amax(optimal_discontinuities) + 1 if len( optimal_discontinuities) > 0 else 0 optimal_point = interp_x[optimal_points[optimal_index]] # Create graphs signal_efficiencies_diff_graph = Graph(len(effs) - 1) background_efficiencies_diff_graph = Graph(len(effs) - 1) optimal_points_graph = Graph(len(optimal_points)) fill_graph(signal_efficiencies_diff_graph, np.column_stack((effs[1:], signal_efficiencies_diff))) fill_graph(background_efficiencies_diff_graph, np.column_stack((effs[1:], background_efficiencies_diff))) fill_graph( optimal_points_graph, np.column_stack( (interp_x[optimal_points], interp_signal[optimal_points]))) signal_efficiencies_diff_graph.SetName('efficiencies_signal') background_efficiencies_diff_graph.SetName('efficiencies_background') optimal_points_graph.SetName('signal_background_optimal_points') return signal_efficiencies_diff_graph, background_efficiencies_diff_graph, optimal_points_graph, optimal_point
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
def find_best_working_point(effs, signal_efficiencies, background_efficiencies, signal_probabilities, background_probabilities): # Compute ratios of background and signal probabilities and truncate the array # to match the gradient array size probability_ratios = background_probabilities / signal_probabilities probability_ratios = probability_ratios[1:] # Compute efficiency gradients effs_diff = np.ediff1d(effs) signal_efficiencies_diff = np.ediff1d(signal_efficiencies) background_efficiencies_diff = np.ediff1d(background_efficiencies) signal_efficiencies_diff = np.divide(signal_efficiencies_diff, effs_diff) background_efficiencies_diff = np.divide(background_efficiencies_diff, effs_diff) # Apply averaging over 3 or 2 values in order to smooth the gradients signal_efficiencies_diff_3 = np.convolve(signal_efficiencies_diff, np.repeat(1.0, 3.) / 3., 'valid') signal_efficiencies_diff_2 = np.convolve(signal_efficiencies_diff, np.repeat(1.0, 2.) / 2., 'valid') signal_efficiencies_diff = np.append( signal_efficiencies_diff_3, [signal_efficiencies_diff_2[-1], signal_efficiencies_diff[-1]]) background_efficiencies_diff_3 = np.convolve(background_efficiencies_diff, np.repeat(1.0, 3.) / 3., 'valid') background_efficiencies_diff_2 = np.convolve(background_efficiencies_diff, np.repeat(1.0, 2.) / 2., 'valid') background_efficiencies_diff = np.append( background_efficiencies_diff_3, [background_efficiencies_diff_2[-1], background_efficiencies_diff[-1]]) # Apply the signal and background probabilities in order to weight the efficiency gradients # If it is more likely to have background than signal in this bin, then the background efficiency gradient # will be increased accordingly background_efficiencies_diff = background_efficiencies_diff * probability_ratios # Interpolate and find points where background efficiency gradient > signal efficiency gradient (with some tolerance) interp_x = np.linspace(np.amin(effs[1:]), np.amax(effs[1:]), 1000) interp_signal = np.interp(interp_x, effs[1:], signal_efficiencies_diff) interp_background = np.interp(interp_x, effs[1:], background_efficiencies_diff) optimal_points = np.argwhere( np.greater(interp_background - 0.05, interp_signal)).ravel( ) # Use a tolerance of 0.02 in case of fluctuations if len(optimal_points) == 0: print 'WARNING: no working point found where the efficiency gradient is larger for background than for signal' # Find optimal point with smallest efficiency ## Compute spacing between points, and select those with an efficiency separation > 2% optimal_discontinuities = np.argwhere( np.ediff1d(interp_x[optimal_points]) > 0.02).ravel() ## Select the point with the largest efficiency optimal_index = np.amax(optimal_discontinuities) + 1 if len( optimal_discontinuities) > 0 else 0 optimal_point = interp_x[optimal_points[optimal_index]] # Create graphs signal_efficiencies_diff_graph = Graph(len(effs) - 1) background_efficiencies_diff_graph = Graph(len(effs) - 1) optimal_points_graph = Graph(len(optimal_points)) fill_graph(signal_efficiencies_diff_graph, np.column_stack((effs[1:], signal_efficiencies_diff))) fill_graph(background_efficiencies_diff_graph, np.column_stack((effs[1:], background_efficiencies_diff))) fill_graph( optimal_points_graph, np.column_stack( (interp_x[optimal_points], interp_signal[optimal_points]))) signal_efficiencies_diff_graph.SetName('efficiencies_signal') background_efficiencies_diff_graph.SetName('efficiencies_background') optimal_points_graph.SetName('signal_background_optimal_points') return signal_efficiencies_diff_graph, background_efficiencies_diff_graph, optimal_points_graph, optimal_point