def test_normal_interval(): from coffea.hist.plot import normal_interval # Reference weighted efficiency and error from ROOTs TEfficiency denom = np.array([ 89.01457591590004, 2177.066076428943, 6122.5256890981855, 0.0, 100.27757990710668, ]) num = np.array([ 75.14287743709515, 2177.066076428943, 5193.454723043864, 0.0, 84.97723540536361, ]) denom_sumw2 = np.array([ 94.37919737476827, 10000.0, 6463.46795877633, 0.0, 105.90898005417333 ]) num_sumw2 = np.array( [67.2202147680005, 10000.0, 4647.983931785646, 0.0, 76.01275761253757]) ref_hi = np.array([ 0.0514643476600107, 0.0, 0.0061403263960343, np.nan, 0.0480731185500146 ]) ref_lo = np.array([ 0.0514643476600107, 0.0, 0.0061403263960343, np.nan, 0.0480731185500146 ]) interval = normal_interval(num, denom, num_sumw2, denom_sumw2) threshold = 1e-6 lo, hi = interval assert len(ref_hi) == len(hi) assert len(ref_lo) == len(lo) for i in range(len(ref_hi)): if np.isnan(ref_hi[i]): assert np.isnan(ref_hi[i]) elif ref_hi[i] == 0.0: assert hi[i] == 0.0 else: assert np.abs(hi[i] / ref_hi[i] - 1) < threshold if np.isnan(ref_lo[i]): assert np.isnan(ref_lo[i]) elif ref_lo[i] == 0.0: assert lo[i] == 0.0 else: assert np.abs(lo[i] / ref_lo[i] - 1) < threshold
def plotratio( num, denom, ax=None, clear=True, overflow=False, error_opts=None, denom_fill_opts=None, guide_opts=None, unc="clopper-pearson", label=None, ratio_yticks=[], # [start, stop, step] -> [0.5, 1.5, 0.1] ): if ax is None: fig, ax = plt.subplots(1, 1) else: if not isinstance(ax, plt.Axes): raise ValueError("ax must be a matplotlib Axes object") if clear: ax.clear() if error_opts is None and denom_fill_opts is None and guide_opts is None: error_opts = {} denom_fill_opts = {} (naxis,) = num.axes (daxis,) = denom.axes assert isinstance(naxis, (hist.axis.Regular, hist.axis.Variable)) assert isinstance(daxis, (hist.axis.Regular, hist.axis.Variable)) assert all(naxis.edges == daxis.edges) ax.set_xlabel(naxis.label) ax.set_ylabel("Ratio") edges = naxis.edges centers = naxis.centers sumw_num = num.view(flow=overflow)["value"] sumw2_num = num.view(flow=overflow)["variance"] sumw_denom = denom.view(flow=overflow)["value"] sumw2_denom = denom.view(flow=overflow)["variance"] rsumw = sumw_num / sumw_denom if unc == "clopper-pearson": rsumw_err = np.abs(clopper_pearson_interval(sumw_num, sumw_denom) - rsumw) elif unc == "poisson-ratio": # poisson ratio n/m is equivalent to binomial n/(n+m) rsumw_err = np.abs(clopper_pearson_interval(sumw_num, sumw_num + sumw_denom) - rsumw) elif unc == "num": rsumw_err = np.abs(poisson_interval(rsumw, sumw2_num / sumw_denom ** 2) - rsumw) elif unc == "normal": rsumw_err = np.abs(normal_interval(sumw_num, sumw_denom, sumw2_num, sumw2_denom)) else: raise ValueError("Unrecognized uncertainty option: %r" % unc) if error_opts is not None: opts = {"label": label, "linestyle": "none"} opts.update(error_opts) emarker = opts.pop("emarker", "") errbar = ax.errorbar(x=centers, y=rsumw, yerr=rsumw_err, **opts) plt.setp(errbar[1], "marker", emarker) if denom_fill_opts is not None: unity = np.ones_like(sumw_denom) denom_unc = poisson_interval(unity, sumw2_denom / sumw_denom ** 2) opts = {"step": "post", "facecolor": (0, 0, 0, 0.3), "linewidth": 0} opts.update(denom_fill_opts) ax.fill_between( edges, np.r_[denom_unc[0], denom_unc[0, -1]], np.r_[denom_unc[1], denom_unc[1, -1]], **opts, ) if guide_opts is not None: opts = {"linestyle": "--", "color": (0, 0, 0, 0.5), "linewidth": 1} opts.update(guide_opts) ax.axhline(1.0, **opts) if ratio_yticks: start, stop, step = ratio_yticks ax.set_yticks(np.arange(start, stop + step, step).tolist()) for label in ax.yaxis.get_ticklabels()[::2]: label.set_visible(False) ax.set_ylim(start, stop) if clear: ax.autoscale(axis="x", tight=True) ax.set_ylim(0, None) return ax