Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
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