Skip to content

Commit be4dd7f

Browse files
authored
bpo-44150: Support optional weights parameter for fmean() (GH-26175)
1 parent 18f41c0 commit be4dd7f

File tree

4 files changed

+59
-9
lines changed

4 files changed

+59
-9
lines changed

Doc/library/statistics.rst

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ or sample.
4343

4444
======================= ===============================================================
4545
:func:`mean` Arithmetic mean ("average") of data.
46-
:func:`fmean` Fast, floating point arithmetic mean.
46+
:func:`fmean` Fast, floating point arithmetic mean, with optional weighting.
4747
:func:`geometric_mean` Geometric mean of data.
4848
:func:`harmonic_mean` Harmonic mean of data.
4949
:func:`median` Median (middle value) of data.
@@ -128,7 +128,7 @@ However, for reading convenience, most of the examples show sorted sequences.
128128
``mean(data)`` is equivalent to calculating the true population mean μ.
129129

130130

131-
.. function:: fmean(data)
131+
.. function:: fmean(data, weights=None)
132132

133133
Convert *data* to floats and compute the arithmetic mean.
134134

@@ -141,8 +141,25 @@ However, for reading convenience, most of the examples show sorted sequences.
141141
>>> fmean([3.5, 4.0, 5.25])
142142
4.25
143143

144+
Optional weighting is supported. For example, a professor assigns a
145+
grade for a course by weighting quizzes at 20%, homework at 20%, a
146+
midterm exam at 30%, and a final exam at 30%:
147+
148+
.. doctest::
149+
150+
>>> grades = [85, 92, 83, 91]
151+
>>> weights = [0.20, 0.20, 0.30, 0.30]
152+
>>> fmean(grades, weights)
153+
87.6
154+
155+
If *weights* is supplied, it must be the same length as the *data* or
156+
a :exc:`ValueError` will be raised.
157+
144158
.. versionadded:: 3.8
145159

160+
.. versionchanged:: 3.11
161+
Added support for *weights*.
162+
146163

147164
.. function:: geometric_mean(data)
148165

Lib/statistics.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
from itertools import groupby, repeat
137137
from bisect import bisect_left, bisect_right
138138
from math import hypot, sqrt, fabs, exp, erf, tau, log, fsum
139-
from operator import itemgetter
139+
from operator import itemgetter, mul
140140
from collections import Counter, namedtuple
141141

142142
# === Exceptions ===
@@ -345,7 +345,7 @@ def mean(data):
345345
return _convert(total / n, T)
346346

347347

348-
def fmean(data):
348+
def fmean(data, weights=None):
349349
"""Convert data to floats and compute the arithmetic mean.
350350
351351
This runs faster than the mean() function and it always returns a float.
@@ -363,13 +363,24 @@ def count(iterable):
363363
nonlocal n
364364
for n, x in enumerate(iterable, start=1):
365365
yield x
366-
total = fsum(count(data))
367-
else:
366+
data = count(data)
367+
if weights is None:
368368
total = fsum(data)
369-
try:
369+
if not n:
370+
raise StatisticsError('fmean requires at least one data point')
370371
return total / n
371-
except ZeroDivisionError:
372-
raise StatisticsError('fmean requires at least one data point') from None
372+
try:
373+
num_weights = len(weights)
374+
except TypeError:
375+
weights = list(weights)
376+
num_weights = len(weights)
377+
num = fsum(map(mul, data, weights))
378+
if n != num_weights:
379+
raise StatisticsError('data and weights must be the same length')
380+
den = fsum(weights)
381+
if not den:
382+
raise StatisticsError('sum of weights must be non-zero')
383+
return num / den
373384

374385

375386
def geometric_mean(data):

Lib/test/test_statistics.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1972,6 +1972,27 @@ def test_special_values(self):
19721972
with self.assertRaises(ValueError):
19731973
fmean([Inf, -Inf])
19741974

1975+
def test_weights(self):
1976+
fmean = statistics.fmean
1977+
StatisticsError = statistics.StatisticsError
1978+
self.assertEqual(
1979+
fmean([10, 10, 10, 50], [0.25] * 4),
1980+
fmean([10, 10, 10, 50]))
1981+
self.assertEqual(
1982+
fmean([10, 10, 20], [0.25, 0.25, 0.50]),
1983+
fmean([10, 10, 20, 20]))
1984+
self.assertEqual( # inputs are iterators
1985+
fmean(iter([10, 10, 20]), iter([0.25, 0.25, 0.50])),
1986+
fmean([10, 10, 20, 20]))
1987+
with self.assertRaises(StatisticsError):
1988+
fmean([10, 20, 30], [1, 2]) # unequal lengths
1989+
with self.assertRaises(StatisticsError):
1990+
fmean(iter([10, 20, 30]), iter([1, 2])) # unequal lengths
1991+
with self.assertRaises(StatisticsError):
1992+
fmean([10, 20], [-1, 1]) # sum of weights is zero
1993+
with self.assertRaises(StatisticsError):
1994+
fmean(iter([10, 20]), iter([-1, 1])) # sum of weights is zero
1995+
19751996

19761997
# === Tests for variances and standard deviations ===
19771998

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add optional *weights* argument to statistics.fmean().

0 commit comments

Comments
 (0)