Polymorphic input: arrays, lists, scalars, and generators#

screamer functions accept multiple input forms and return a result in the same shape. A rolling mean, for instance, produces at each step the average of the recent values, turning a noisy series into a smoother one.

Pass a NumPy array and get back an array. Pass a Python list and get back a list. Feed values in one at a time or wrap the operator around a generator for lazy evaluation. Every example below uses one function, a five-sample rolling mean. The polymorphic API reference gives the precise rule for each input type.

import numpy as np
from screamer import RollingMean

data = [3.2, 1.8, 3.4, 2.9, 3.6, 3.1, 4.0, 3.3, 2.7, 3.5]   # ten readings
mean5 = RollingMean(5)   # a five-sample rolling mean

An array#

Pass a one-dimensional array and get back an array of the same length. The first four entries are NaN, because the window needs five samples before it can report a mean.

mean5(np.array(data)).round(3)
array([ nan,  nan,  nan,  nan, 2.98, 2.96, 3.4 , 3.38, 3.34, 3.32])

Several series at once#

The first axis is time. Any further axes are separate series carried alongside each other. A (10, 3) array is three rolling means worked out together, and you write no loop of your own.

series = np.random.default_rng(0).standard_normal((10, 3))
mean5(series).shape
(10, 3)

A Python list#

Give it a list and a list comes back, which is convenient when your data is not already in NumPy.

out = mean5(data)
print(type(out).__name__)          # list
print([round(v, 3) for v in out])
list
[nan, nan, nan, nan, 2.98, 2.96, 3.4, 3.38, 3.34, 3.32]

One value at a time#

When values arrive one by one, call the operator on each as it shows up. Every call returns the rolling mean up to that point, and the operator remembers what it has seen so the next call continues the same window. A fresh operator starts a fresh stream.

live = RollingMean(5)
[round(live(x), 3) for x in data]   # one value in, one value out
[nan, nan, nan, nan, 2.98, 2.96, 3.4, 3.38, 3.34, 3.32]

A lazy iterator#

Hand the operator a generator and it hands back a lazy iterator. Nothing is computed until you pull a value, and each value you pull draws exactly one input through. This is the form to reach for inside an event loop, or for a stream with no fixed length.

stream = RollingMean(5)(x for x in data)
print(type(stream).__name__)                 # a lazy iterator; nothing has run yet
[round(next(stream), 3) for _ in range(6)]   # pull six values on demand
LazyEvalIterator
[nan, nan, nan, nan, 2.98, 2.96]

Building a pipeline#

One more input type changes what a call does. Apply an operator to an Input placeholder instead of to data, and it records a step in a pipeline rather than computing right away. This is how operators compose into a reusable pipeline. The pipelines notebook covers it.