Skip to content

Commit 97cb79b

Browse files
authored
Merge pull request #182 from paulgessinger/normal-interval-eff-error
Add normal interval to plotratio
2 parents 6fc3ef6 + 74e1d7d commit 97cb79b

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

coffea/hist/plot.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,24 @@ def clopper_pearson_interval(num, denom, coverage=_coverage1sd):
7878
return interval
7979

8080

81+
def normal_interval(pw, tw, pw2, tw2, coverage=_coverage1sd):
82+
# see https://root.cern.ch/doc/master/TEfficiency_8cxx_source.html#l02515
83+
84+
eff = pw / tw
85+
86+
variance = (pw2 * (1 - 2 * eff) + tw2 * eff**2) / (tw**2)
87+
sigma = np.sqrt(variance)
88+
89+
prob = 0.5 * (1 - coverage)
90+
delta = np.zeros_like(sigma)
91+
delta[sigma != 0] = scipy.stats.norm.ppf(prob, scale=sigma[sigma != 0])
92+
93+
lo = eff - np.minimum(eff + delta, np.ones_like(eff))
94+
hi = np.maximum(eff - delta, np.zeros_like(eff)) - eff
95+
96+
return np.array([lo, hi])
97+
98+
8199
def plot1d(hist, ax=None, clear=True, overlay=None, stack=False, overflow='none', line_opts=None,
82100
fill_opts=None, error_opts=None, legend_opts={}, overlay_overflow='none', density=False, binwnorm=None):
83101
"""Create a 1D plot from a 1D or 2D `Hist` object
@@ -352,6 +370,8 @@ def plotratio(num, denom, ax=None, clear=True, overflow='none', error_opts=None,
352370
rsumw_err = np.abs(clopper_pearson_interval(sumw_num, sumw_num + sumw_denom) - rsumw)
353371
elif unc == 'num':
354372
rsumw_err = np.abs(poisson_interval(rsumw, sumw2_num / sumw_denom**2) - rsumw)
373+
elif unc == "normal":
374+
rsumw_err = np.abs(normal_interval(sumw_num, sumw_denom, sumw2_num, sumw2_denom))
355375
else:
356376
raise ValueError("Unrecognized uncertainty option: %r" % unc)
357377

tests/test_hist_plot.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,47 @@ def test_clopper_pearson_interval():
241241
threshold = 1e-6
242242
assert(all((interval[1, :] / ref_hi) - 1 < threshold))
243243
assert(all((interval[0, :] / ref_lo) - 1 < threshold))
244+
245+
def test_normal_interval():
246+
from coffea.hist.plot import normal_interval
247+
248+
# Reference weighted efficiency and error from ROOTs TEfficiency
249+
250+
denom = np.array([ 89.01457591590004, 2177.066076428943 , 6122.5256890981855 ,
251+
0. , 100.27757990710668])
252+
num = np.array([ 75.14287743709515, 2177.066076428943 , 5193.454723043864 ,
253+
0. , 84.97723540536361])
254+
denom_sumw2 = np.array([ 94.37919737476827, 10000. , 6463.46795877633 ,
255+
0. , 105.90898005417333])
256+
num_sumw2 = np.array([ 67.2202147680005 , 10000. , 4647.983931785646 ,
257+
0. , 76.01275761253757])
258+
ref_hi = np.array([0.0514643476600107, 0. , 0.0061403263960343,
259+
np.nan, 0.0480731185500146])
260+
ref_lo = np.array([0.0514643476600107, 0. , 0.0061403263960343,
261+
np.nan, 0.0480731185500146])
262+
263+
interval = normal_interval(num, denom, num_sumw2, denom_sumw2)
264+
threshold = 1e-6
265+
266+
lo, hi = interval
267+
268+
assert len(ref_hi) == len(hi)
269+
assert len(ref_lo) == len(lo)
270+
271+
for i in range(len(ref_hi)):
272+
if np.isnan(ref_hi[i]):
273+
assert np.isnan(ref_hi[i])
274+
elif ref_hi[i] == 0.0:
275+
assert hi[i] == 0.0
276+
else:
277+
assert np.abs(hi[i] / ref_hi[i] - 1) < threshold
278+
279+
if np.isnan(ref_lo[i]):
280+
assert np.isnan(ref_lo[i])
281+
elif ref_lo[i] == 0.0:
282+
assert lo[i] == 0.0
283+
else:
284+
assert np.abs(lo[i] / ref_lo[i] - 1) < threshold
285+
286+
287+

0 commit comments

Comments
 (0)