From d5ddad2a75755472f3e6a351bf98f3fb40e9d61a Mon Sep 17 00:00:00 2001 From: Taeknology <20297177+Taeknology@users.noreply.github.com> Date: Sat, 1 Aug 2026 23:30:25 +0900 Subject: [PATCH] gh-154942: Refresh statistics.kde cache after data changes --- Lib/statistics.py | 21 +++++++++++-------- Lib/test/test_statistics.py | 8 +++++++ ...-08-01-23-30-00.gh-issue-154942.mF8QvS.rst | 2 ++ 3 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-01-23-30-00.gh-issue-154942.mF8QvS.rst diff --git a/Lib/statistics.py b/Lib/statistics.py index 758b5b58848fb98..edcd7588565cc08 100644 --- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -1060,24 +1060,27 @@ def cdf(x): else: - sample = sorted(data) + data_snapshot = tuple(data) + sample = sorted(data_snapshot) bandwidth = h * support + def refresh(): + nonlocal n, data_snapshot, sample + updated_data = tuple(data) + if updated_data != data_snapshot: + data_snapshot = updated_data + sample = sorted(updated_data) + n = len(updated_data) + def pdf(x): - nonlocal n, sample - if len(data) != n: - sample = sorted(data) - n = len(data) + refresh() i = bisect_left(sample, x - bandwidth) j = bisect_right(sample, x + bandwidth) supported = sample[i : j] return sum(K((x - x_i) / h) for x_i in supported) / (n * h) def cdf(x): - nonlocal n, sample - if len(data) != n: - sample = sorted(data) - n = len(data) + refresh() i = bisect_left(sample, x - bandwidth) j = bisect_right(sample, x + bandwidth) supported = sample[i : j] diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index 700c5ac304f7179..d700fac42a83d88 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -2428,6 +2428,14 @@ def integrate(func, low, high, steps=10_000): data.append(100) self.assertGreater(f_hat(100), 0.0) + for cumulative in (False, True): + with self.subTest(cumulative=cumulative): + data = [1, 2, 3] + estimate = kde(data, 1.0, 'triangular', cumulative=cumulative) + data[0] = 5 + expected = kde(data, 1.0, 'triangular', cumulative=cumulative) + self.assertEqual(estimate(1.5), expected(1.5)) + def test_kde_kernel_specs(self): # White-box test for the kernel formulas in isolation from # their downstream use in kde() and kde_random() diff --git a/Misc/NEWS.d/next/Library/2026-08-01-23-30-00.gh-issue-154942.mF8QvS.rst b/Misc/NEWS.d/next/Library/2026-08-01-23-30-00.gh-issue-154942.mF8QvS.rst new file mode 100644 index 000000000000000..67e29f3adad83c1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-01-23-30-00.gh-issue-154942.mF8QvS.rst @@ -0,0 +1,2 @@ +Fix :func:`statistics.kde` with a bounded-support kernel to refresh its cached +sample when the contents of the input data change without changing its length.