Skip to content

Commit f3bff4e

Browse files
authored
gh-112540: Support zero inputs in geometric_mean() (gh-112880)
1 parent 76929fd commit f3bff4e

File tree

3 files changed

+35
-9
lines changed

3 files changed

+35
-9
lines changed

Lib/statistics.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -527,20 +527,36 @@ def count(iterable):
527527
def geometric_mean(data):
528528
"""Convert data to floats and compute the geometric mean.
529529
530-
Raises a StatisticsError if the input dataset is empty,
531-
if it contains a zero, or if it contains a negative value.
530+
Raises a StatisticsError if the input dataset is empty
531+
or if it contains a negative value.
532+
533+
Returns zero if the product of inputs is zero.
532534
533535
No special efforts are made to achieve exact results.
534536
(However, this may change in the future.)
535537
536538
>>> round(geometric_mean([54, 24, 36]), 9)
537539
36.0
538540
"""
539-
try:
540-
return exp(fmean(map(log, data)))
541-
except ValueError:
542-
raise StatisticsError('geometric mean requires a non-empty dataset '
543-
'containing positive numbers') from None
541+
n = 0
542+
found_zero = False
543+
def count_positive(iterable):
544+
nonlocal n, found_zero
545+
for n, x in enumerate(iterable, start=1):
546+
if x > 0.0 or math.isnan(x):
547+
yield x
548+
elif x == 0.0:
549+
found_zero = True
550+
else:
551+
raise StatisticsError('No negative inputs allowed', x)
552+
total = fsum(map(log, count_positive(data)))
553+
if not n:
554+
raise StatisticsError('Must have a non-empty dataset')
555+
if math.isnan(total):
556+
return math.nan
557+
if found_zero:
558+
return math.nan if total == math.inf else 0.0
559+
return exp(total / n)
544560

545561

546562
def harmonic_mean(data, weights=None):

Lib/test/test_statistics.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,10 +2302,12 @@ def test_error_cases(self):
23022302
StatisticsError = statistics.StatisticsError
23032303
with self.assertRaises(StatisticsError):
23042304
geometric_mean([]) # empty input
2305-
with self.assertRaises(StatisticsError):
2306-
geometric_mean([3.5, 0.0, 5.25]) # zero input
23072305
with self.assertRaises(StatisticsError):
23082306
geometric_mean([3.5, -4.0, 5.25]) # negative input
2307+
with self.assertRaises(StatisticsError):
2308+
geometric_mean([0.0, -4.0, 5.25]) # negative input with zero
2309+
with self.assertRaises(StatisticsError):
2310+
geometric_mean([3.5, -math.inf, 5.25]) # negative infinity
23092311
with self.assertRaises(StatisticsError):
23102312
geometric_mean(iter([])) # empty iterator
23112313
with self.assertRaises(TypeError):
@@ -2328,6 +2330,12 @@ def test_special_values(self):
23282330
with self.assertRaises(ValueError):
23292331
geometric_mean([Inf, -Inf])
23302332

2333+
# Cases with zero
2334+
self.assertEqual(geometric_mean([3, 0.0, 5]), 0.0) # Any zero gives a zero
2335+
self.assertEqual(geometric_mean([3, -0.0, 5]), 0.0) # Negative zero allowed
2336+
self.assertTrue(math.isnan(geometric_mean([0, NaN]))) # NaN beats zero
2337+
self.assertTrue(math.isnan(geometric_mean([0, Inf]))) # Because 0.0 * Inf -> NaN
2338+
23312339
def test_mixed_int_and_float(self):
23322340
# Regression test for b.p.o. issue #28327
23332341
geometric_mean = statistics.geometric_mean
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The statistics.geometric_mean() function now returns zero for datasets
2+
containing a zero. Formerly, it would raise an exception.

0 commit comments

Comments
 (0)