rawmeat.org

Code, craft, creating

Exponential smoothing in Python

2014-02-12 3 min read coding

The problem

While measuring temperature using my Arduino and a TMP36, I found that the temperature reading fluctuated wildly. After reading up a little, it became clear that since the Arduino’s analog pin measures discrete values of 0-1023, and the voltage varies between 0-5V, the smallest difference that can be detected is ca. 4.9mV. For the TMP36, a temperature change of 1°C triggers a voltage change of 10mV, so the smallest temperature increment that can be registered is ±0.5°C. For additional fun, the sensor is only accurate to ±2°C, so plenty of opportunities for sensor fluctuations.

The solution

I figured I could get a better result by sampling more values and averaging them. Since I only needed actual readings every few seconds, I started by just sampling the previous few hundred values and taking a simple mean. The temperature, at least, was a lot more stable now. Then I tried a different method, comparing each temperature reading to the previous and adding 1% of the difference to the current value. This produced a relatively nice smoothing.

At this point, I realized this must be something which had a very common solution, and a brief bit of googling led me to the exponential moving average which seemed to be a good fit. I also quickly found a stackoverflow answer that had some sample Python code. It looked like a good start, but since I wanted to measure continuously, I wanted to refactor it a bit so that I could continuously smooth my readings without saving up a lot of previous values in memory. The below is what I ended up with.

The code

def exponential_moving_average(period=1000):
    """ Exponential moving average. Smooths the values over the period.  Send
    in values - at first it'll return a simple average, but as soon as it's
    gathered 'period' values, it'll start to use the Exponential Moving
    Averge to smooth the values.

    period: int - how many values to smooth over (default=1000). """
    multiplier = 2 / float(1 + period)
    cumulative_temp = yield None  # We are being primed

    # Start by just returning the simple average until we have enough data.
    for i in xrange(1, period + 1):
        cumulative_temp += yield cumulative_temp / float(i)

    # Grab the simple average,
    ema = cumulative_temp / period

    # and start calculating the exponentially smoothed average we want.
    while True:
        ema = (((yield ema) - ema) * multiplier) + ema


def temp_monitor(pin):
    """ Read from the temperature sensor - and smooth the value out. The
    sensor is noisy, so we use exponential smoothing. """
    ema = exponential_moving_average()
    next(ema)  # Prime the generator

    while True:
        yield ema.send(val_to_temp(pin.read()))  # pin.read() is sensor read.


def val_to_temp(v):
    """ Convert the sensor reading to a temperature in C. """
    # 10mV/C - and a correction factor of 0.5V.
    return (v * 5.0 - 0.5) * 100

Final words

As you can see, the exponential smoothing introduces some lag in the responsiveness, but I’m not going to measure anything that reacts that quickly. It’s the noise that’s the problem. By picking a smaller window to average over you can increase the responsiveness, but at the price of more jitter.

graph

By making the exponential_moving_average into a generator, you only need to keep a few values in memory, and you can feed it one value at a time getting a continuous stream of results. This is very convenient, and the only change needed is using .send() on the generator over just calling the function.