From d43f0e9041138dea167783a253b77ff7ae3a4f81 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Tue, 19 May 2026 18:02:29 +0100 Subject: [PATCH 1/4] Add a python equivalent to MATLABs smoothdata and use in Bayes plots --- ratapi/utils/plotting.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/ratapi/utils/plotting.py b/ratapi/utils/plotting.py index 6f9debb..07c1f80 100644 --- a/ratapi/utils/plotting.py +++ b/ratapi/utils/plotting.py @@ -826,9 +826,7 @@ def plot_one_hist( sd_y = np.std(parameter_chain) if smooth: - if sigma is None: - sigma = sd_y / 2 - counts = gaussian_filter1d(counts, sigma) + counts = moving_avg(counts) axes.hist( bins[:-1], bins, @@ -1233,3 +1231,30 @@ def plot_bayes(project: ratapi.Project, results: ratapi.outputs.BayesResults): plot_corner(results) else: raise ValueError("Bayes plots are only available for the results of Bayesian analysis (NS or DREAM)") + + +def moving_avg(data: np.ndarray, window_size: int = 8) -> list[float]: + """Calculate the moving average of an array with a given window size. + + This is a python equivalent to MATLABs smoothdata(A, 'movmean') + + Parameters + ---------- + data : np.ndarray + The input array to smooth + window_size : int + The window slides down the length of the vector, + computing an average over the elements within each window. + + """ + i = 0 + moving_averages = [] + + while i < len(data): + start_window_ind = floor(float(i - window_size / 2)) if i - window_size / 2 > 0 else 0 + end_window_ind = floor(float(i + window_size / 2)) if i + window_size / 2 < len(data) else len(data) + window_average = np.sum(data[start_window_ind:end_window_ind]) / (end_window_ind + 0 - start_window_ind) + moving_averages.append(window_average) + i += 1 + + return moving_averages From f1a07bc2323695e2b739edfb87c565eede27efac Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 22 May 2026 17:25:45 +0100 Subject: [PATCH 2/4] suggested changes to moving_average --- ratapi/utils/plotting.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ratapi/utils/plotting.py b/ratapi/utils/plotting.py index 07c1f80..98221f4 100644 --- a/ratapi/utils/plotting.py +++ b/ratapi/utils/plotting.py @@ -763,7 +763,7 @@ def plot_one_hist( results: ratapi.outputs.BayesResults, param: int | str, smooth: bool = True, - sigma: float | None = None, + window_size: int = 8, estimated_density: Literal["normal", "lognor", "kernel", None] = None, axes: Axes | None = None, block: bool = False, @@ -783,9 +783,9 @@ def plot_one_hist( smooth : bool, default True Whether to apply Gaussian smoothing to the histogram. Defaults to True. - sigma: float or None, default None - If given, is used as the sigma-parameter for the Gaussian smoothing. - If None, the default (1/3rd of parameter chain standard deviation) is used. + window_size : int, default 8 + The width of the smoothing window centered around the element being averaged. + The window moves down the length of the data, computing an average over the elements within each window. estimated_density : 'normal', 'lognor', 'kernel' or None, default None If None (default), ignore. Else, add an estimated density of the given form on top of the histogram by the following estimations: @@ -826,7 +826,7 @@ def plot_one_hist( sd_y = np.std(parameter_chain) if smooth: - counts = moving_avg(counts) + counts = moving_average(counts, window_size=window_size) axes.hist( bins[:-1], bins, @@ -1233,7 +1233,7 @@ def plot_bayes(project: ratapi.Project, results: ratapi.outputs.BayesResults): raise ValueError("Bayes plots are only available for the results of Bayesian analysis (NS or DREAM)") -def moving_avg(data: np.ndarray, window_size: int = 8) -> list[float]: +def moving_average(data: np.ndarray, window_size: int = 8) -> list[float]: """Calculate the moving average of an array with a given window size. This is a python equivalent to MATLABs smoothdata(A, 'movmean') @@ -1247,10 +1247,10 @@ def moving_avg(data: np.ndarray, window_size: int = 8) -> list[float]: computing an average over the elements within each window. """ - i = 0 + assert 0 <= window_size <= len(data) moving_averages = [] - while i < len(data): + for i in range(len(data)): start_window_ind = floor(float(i - window_size / 2)) if i - window_size / 2 > 0 else 0 end_window_ind = floor(float(i + window_size / 2)) if i + window_size / 2 < len(data) else len(data) window_average = np.sum(data[start_window_ind:end_window_ind]) / (end_window_ind + 0 - start_window_ind) From e379583627c7efb2a81cc80a70fabac1c35c3b4f Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Mon, 1 Jun 2026 15:18:33 +0100 Subject: [PATCH 3/4] unit test test_moving_average --- tests/test_plotting.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 31049a2..a5802ae 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -495,3 +495,21 @@ def test_extract_plot_data(data) -> None: with pytest.raises(ValueError, match=r"Parameter `shift_value` must be between 0 and 100"): RATplot._extract_plot_data(data, False, True, 100.5) + + +def test_moving_average() -> None: + """Test the moving_average function.""" + data_to_average = np.arange(0, 20) + mov_avg = RATplot.moving_average(data_to_average) + assert mov_avg == [1.5, 2.0, 2.5, 3.0, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.0, + 16.5, 17.0] + + mov_avg = RATplot.moving_average(data_to_average, window_size=2) + assert mov_avg == [0.0, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, + 17.5, 18.5] + + with pytest.raises(AssertionError): + RATplot.moving_average(data_to_average, window_size=-1) + + with pytest.raises(AssertionError): + RATplot.moving_average(data_to_average, window_size=len(data_to_average)+1) From 46388d71e99a406ed890276068ea542c13cd53c5 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Mon, 1 Jun 2026 15:45:31 +0100 Subject: [PATCH 4/4] ruff fix --- tests/test_plotting.py | 50 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index a5802ae..f1721ec 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -501,15 +501,55 @@ def test_moving_average() -> None: """Test the moving_average function.""" data_to_average = np.arange(0, 20) mov_avg = RATplot.moving_average(data_to_average) - assert mov_avg == [1.5, 2.0, 2.5, 3.0, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.0, - 16.5, 17.0] + assert mov_avg == [ + 1.5, + 2.0, + 2.5, + 3.0, + 3.5, + 4.5, + 5.5, + 6.5, + 7.5, + 8.5, + 9.5, + 10.5, + 11.5, + 12.5, + 13.5, + 14.5, + 15.5, + 16.0, + 16.5, + 17.0, + ] mov_avg = RATplot.moving_average(data_to_average, window_size=2) - assert mov_avg == [0.0, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, - 17.5, 18.5] + assert mov_avg == [ + 0.0, + 0.5, + 1.5, + 2.5, + 3.5, + 4.5, + 5.5, + 6.5, + 7.5, + 8.5, + 9.5, + 10.5, + 11.5, + 12.5, + 13.5, + 14.5, + 15.5, + 16.5, + 17.5, + 18.5, + ] with pytest.raises(AssertionError): RATplot.moving_average(data_to_average, window_size=-1) with pytest.raises(AssertionError): - RATplot.moving_average(data_to_average, window_size=len(data_to_average)+1) + RATplot.moving_average(data_to_average, window_size=len(data_to_average) + 1)