Skip to content

Commit f5fde45

Browse files
Merge pull request #953 from ericymiao/bugfix-rebin_data_uncertainties
Fixed incorrect error propagation in rebin_data
2 parents aed0890 + 6de1829 commit f5fde45

File tree

3 files changed

+32
-25
lines changed

3 files changed

+32
-25
lines changed

docs/changes/953.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed uncertainty propogation error in ``Lightcurve.rebin()``

stingray/tests/test_utils.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,19 @@ def test_rebin_variable_input_mean(self):
8888
assert len(xbin) == 20
8989
assert np.allclose(ybin, counts)
9090

91+
def test_rebin_error_propagation(self):
92+
x = np.arange(0, 4, 1.0) # 4 points
93+
y = np.ones(4) * 10
94+
yerr = np.ones(4) * 2 # Each point has σ=2
95+
96+
xbin, ybin, ybinerr, _ = utils.rebin_data(x, y, 4.0, yerr=yerr, method="sum")
97+
# Correct: sqrt(4*2**2) = sqrt(16) = 4
98+
assert np.allclose(ybinerr, 4.0)
99+
100+
xbin, ybin, ybinerr, _ = utils.rebin_data(x, y, 4.0, yerr=yerr, method="mean")
101+
# Correct: sqrt(4*2**2) / 4 = 4/4 = 1
102+
assert np.allclose(ybinerr, 1.0)
103+
91104

92105
class TestRebinDataLog(object):
93106
@classmethod
@@ -97,7 +110,7 @@ def setup_class(cls):
97110
cls.xmin = 1
98111
cls.x = np.arange(cls.xmin, cls.xmax, cls.dx)
99112
cls.y = np.arange(cls.xmin, cls.xmax, cls.dx)
100-
cls.y_err = np.ones_like(cls.y)
113+
cls.y_err = np.ones_like(cls.y) * 2
101114

102115
cls.true_bins = np.array([1.0, 1.0, 1.5, 1.5, 1.5, 1.5, 1.5, 2.5, 2.5, 2.5, 2.0])
103116

@@ -139,26 +152,19 @@ def test_all_outputs_have_the_same_dimension_except_binx(self):
139152
assert binyerr.shape[0] == nsamples.shape[0]
140153

141154
def test_binning_works_correctly(self):
142-
binx, _, _, _ = utils.rebin_data_log(self.x, self.y, self.f, y_err=self.y_err, dx=self.dx)
155+
binx, biny, binyerr, nsamples = utils.rebin_data_log(
156+
self.x,
157+
self.y,
158+
self.f,
159+
y_err=self.y_err,
160+
dx=self.dx,
161+
)
143162

144163
assert np.allclose(np.diff(binx), self.true_bins)
145-
146-
def test_bin_edges_are_correct(self):
147-
binx, _, _, _ = utils.rebin_data_log(self.x, self.y, self.f, y_err=self.y_err, dx=self.dx)
148-
149164
assert np.allclose(binx, self.true_bin_edges)
150-
151-
def test_bin_values_are_correct(self):
152-
_, biny, _, _ = utils.rebin_data_log(self.x, self.y, self.f, y_err=self.y_err, dx=self.dx)
153-
154165
assert np.allclose(biny, self.true_values)
155-
156-
def test_nsamples_are_correctly_calculated(self):
157-
_, _, _, nsamples = utils.rebin_data_log(
158-
self.x, self.y, self.f, y_err=self.y_err, dx=self.dx
159-
)
160-
161166
assert np.allclose(nsamples, self.true_nsamples)
167+
assert np.allclose(binyerr, 2 / np.sqrt(self.true_nsamples))
162168

163169
def test_method_works_on_complex_numbers(self):
164170
re = np.arange(self.xmin, self.xmax, self.dx)

stingray/utils.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -534,15 +534,15 @@ def rebin_data(x, y, dx_new, yerr=None, method="sum", dx=None):
534534
--------
535535
>>> x = np.arange(0, 100, 0.01)
536536
>>> y = np.ones(x.size)
537-
>>> yerr = np.ones(x.size)
537+
>>> yerr = np.ones(x.size) * 2 # Each point has σ=2
538538
>>> xbin, ybin, ybinerr, step_size = rebin_data(
539539
... x, y, 4, yerr=yerr, method='sum', dx=0.01)
540540
>>> assert np.allclose(ybin, 400)
541-
>>> assert np.allclose(ybinerr, 20)
541+
>>> assert np.allclose(ybinerr, 40) # sqrt(400 * 2**2) = 40
542542
>>> xbin, ybin, ybinerr, step_size = rebin_data(
543543
... x, y, 4, yerr=yerr, method='mean')
544544
>>> assert np.allclose(ybin, 1)
545-
>>> assert np.allclose(ybinerr, 0.05)
545+
>>> assert np.allclose(ybinerr, 0.1) # sqrt(400 * 2**2) / 400 = 0.1
546546
"""
547547

548548
y = np.asanyarray(y)
@@ -572,7 +572,7 @@ def rebin_data(x, y, dx_new, yerr=None, method="sum", dx=None):
572572
xbin = np.arange(xedges[0], xedges[-1] + dx_new, dx_new)
573573

574574
output = np.zeros(xbin.shape[0] - 1, dtype=type(y[0]))
575-
outputerr = np.zeros(xbin.shape[0] - 1, dtype=type(yerr[0]))
575+
outputvar = np.zeros(xbin.shape[0] - 1, dtype=type(yerr[0]))
576576
step_size = np.zeros(xbin.shape[0] - 1)
577577

578578
all_x = np.searchsorted(xedges, xbin)
@@ -584,29 +584,29 @@ def rebin_data(x, y, dx_new, yerr=None, method="sum", dx=None):
584584
filtered_y = y[min_ind : max_ind - 1]
585585
filtered_yerr = yerr[min_ind : max_ind - 1]
586586
output[i] = np.sum(filtered_y)
587-
outputerr[i] = np.sum(filtered_yerr)
587+
outputvar[i] = np.sum(filtered_yerr**2)
588588
step_size[i] = max_ind - 1 - min_ind
589589

590590
prev_dx = xedges[min_ind] - xedges[min_ind - 1]
591591
prev_frac = (xedges[min_ind] - xmin) / prev_dx
592592
output[i] += y[min_ind - 1] * prev_frac
593-
outputerr[i] += yerr[min_ind - 1] * prev_frac
593+
outputvar[i] += (yerr[min_ind - 1] * prev_frac) ** 2
594594
step_size[i] += prev_frac
595595

596596
if not max_ind == xedges.size:
597597
dx_post = xedges[max_ind] - xedges[max_ind - 1]
598598
post_frac = (xmax - xedges[max_ind - 1]) / dx_post
599599
output[i] += y[max_ind - 1] * post_frac
600-
outputerr[i] += yerr[max_ind - 1] * post_frac
600+
outputvar[i] += (yerr[max_ind - 1] * post_frac) ** 2
601601
step_size[i] += post_frac
602602

603603
if method in ["mean", "avg", "average", "arithmetic mean"]:
604604
ybin = output / step_size
605-
ybinerr = np.sqrt(outputerr) / step_size
605+
ybinerr = np.sqrt(outputvar) / step_size
606606

607607
elif method == "sum":
608608
ybin = output
609-
ybinerr = np.sqrt(outputerr)
609+
ybinerr = np.sqrt(outputvar)
610610

611611
else:
612612
raise ValueError(

0 commit comments

Comments
 (0)