Skip to content

Commit b168865

Browse files
authored
Additional itertool recipes for running statistics (gh-148879)
1 parent 04fd103 commit b168865

1 file changed

Lines changed: 66 additions & 9 deletions

File tree

Doc/library/itertools.rst

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,7 @@ and :term:`generators <generator>` which incur interpreter overhead.
833833
from collections import Counter, deque
834834
from contextlib import suppress
835835
from functools import reduce
836+
from heapq import heappush, heappushpop, heappush_max, heappushpop_max
836837
from math import comb, isqrt, prod, sumprod
837838
from operator import getitem, is_not, itemgetter, mul, neg, truediv
838839

@@ -848,11 +849,6 @@ and :term:`generators <generator>` which incur interpreter overhead.
848849
# prepend(1, [2, 3, 4]) → 1 2 3 4
849850
return chain([value], iterable)
850851

851-
def running_mean(iterable):
852-
"Yield the average of all values seen so far."
853-
# running_mean([8.5, 9.5, 7.5, 6.5]) → 8.5 9.0 8.5 8.0
854-
return map(truediv, accumulate(iterable), count(1))
855-
856852
def repeatfunc(function, times=None, *args):
857853
"Repeat calls to a function with specified arguments."
858854
if times is None:
@@ -1150,6 +1146,49 @@ and :term:`generators <generator>` which incur interpreter overhead.
11501146
return n
11511147

11521148

1149+
# ==== Running statistics ====
1150+
1151+
def running_mean(iterable):
1152+
"Average of values seen so far."
1153+
# running_mean([37, 33, 38, 28]) → 37 35 36 34
1154+
return map(truediv, accumulate(iterable), count(1))
1155+
1156+
def running_min(iterable):
1157+
"Smallest of values seen so far."
1158+
# running_min([37, 33, 38, 28]) → 37 33 33 28
1159+
return accumulate(iterable, func=min)
1160+
1161+
def running_max(iterable):
1162+
"Largest of values seen so far."
1163+
# running_max([37, 33, 38, 28]) → 37 37 38 38
1164+
return accumulate(iterable, func=max)
1165+
1166+
def running_median(iterable):
1167+
"Median of values seen so far."
1168+
# running_median([37, 33, 38, 28]) → 37 35 37 35
1169+
read = iter(iterable).__next__
1170+
lo = [] # max-heap
1171+
hi = [] # min-heap the same size as or one smaller than lo
1172+
with suppress(StopIteration):
1173+
while True:
1174+
heappush_max(lo, heappushpop(hi, read()))
1175+
yield lo[0]
1176+
heappush(hi, heappushpop_max(lo, read()))
1177+
yield (lo[0] + hi[0]) / 2
1178+
1179+
def running_statistics(iterable):
1180+
"Aggregate statistics for values seen so far."
1181+
# Generate tuples: (size, minimum, median, maximum, mean)
1182+
t0, t1, t2, t3 = tee(iterable, 4)
1183+
return zip(
1184+
count(1),
1185+
running_min(t0),
1186+
running_median(t1),
1187+
running_max(t2),
1188+
running_mean(t3),
1189+
)
1190+
1191+
11531192
.. doctest::
11541193
:hide:
11551194

@@ -1226,10 +1265,6 @@ and :term:`generators <generator>` which incur interpreter overhead.
12261265
[(0, 'a'), (1, 'b'), (2, 'c')]
12271266

12281267

1229-
>>> list(running_mean([8.5, 9.5, 7.5, 6.5]))
1230-
[8.5, 9.0, 8.5, 8.0]
1231-
1232-
12331268
>>> for _ in loops(5):
12341269
... print('hi')
12351270
...
@@ -1789,6 +1824,28 @@ and :term:`generators <generator>` which incur interpreter overhead.
17891824
True
17901825

17911826

1827+
>>> list(running_mean([8.5, 9.5, 7.5, 6.5]))
1828+
[8.5, 9.0, 8.5, 8.0]
1829+
>>> list(running_mean([37, 33, 38, 28]))
1830+
[37.0, 35.0, 36.0, 34.0]
1831+
1832+
1833+
>>> list(running_min([37, 33, 38, 28]))
1834+
[37, 33, 33, 28]
1835+
1836+
1837+
>>> list(running_max([37, 33, 38, 28]))
1838+
[37, 37, 38, 38]
1839+
1840+
1841+
>>> list(running_median([37, 33, 38, 28]))
1842+
[37, 35.0, 37, 35.0]
1843+
1844+
1845+
>>> list(running_statistics([37, 33, 38, 28]))
1846+
[(1, 37, 37, 37, 37.0), (2, 33, 35.0, 37, 35.0), (3, 33, 37, 38, 36.0), (4, 28, 35.0, 38, 34.0)]
1847+
1848+
17921849
.. testcode::
17931850
:hide:
17941851

0 commit comments

Comments
 (0)