exupero's blog
RSSApps

Exponential smoothing transducer

In the previous post I highlighted a few transducers I've implemented that were tailored to specific purposes, but my favorite is the one that taught me how to write transducers, one for exponential smoothing.

To demonstrate, we need a noisy signal. Here's a sum of 20 random sine waves:

(def signal
  (let [rng (java.util.Random. 0)
        signals (repeatedly 20 (fn []
                                 {:amplitude (+ 1 (.nextInt rng 10))
                                  :frequency (.nextDouble rng 100)}))]
    (for [t (range 0 (* Math/PI 2) 0.01)]
      [t (reduce +
           (for [{:keys [amplitude frequency]} signals]
             (* amplitude (Math/sin (* frequency t)))))])))

Exponential smoothing is a weighted average of the signal's current value and the previous weighted average. It's not quite a map operation, because it depends on the previously emitted value, yet it can't be implemented via, say, iterate because it also depends on values coming in from another sequence. Both of those facets suggest a transducer.

(defn exponential-smoothing [alpha]
  (let [prev (volatile! nil)]
    (fn [rf]
      (fn
        ([] (rf))
        ([result] (rf result))
        ([result [x y]]
         (if-let [p @prev]
           (let [s (+ (* alpha y) (* (- 1 alpha) p))]
             (vreset! prev s)
             (rf result [x s]))
           (do
             (vreset! prev y)
             (rf result [x y]))))))))

Weighting is determined by the parameter alpha. With a value of 0.5, y and p are averaged and exponential smoothing dulls the spikes in our signal but leaves the signal mostly intact:

(sequence
  (exponential-smoothing 0.5)
  signal)

We can smooth further by decreasing alpha and reducing how much each incoming value is weighed:

(sequence
  (exponential-smoothing 0.1)
  signal)

The choice of alpha depends on your use case and the characteristics of your data, but a little trial and error should help you home in on an appropriate value.

In the next post we'll look at an experimental transducer for transducing nested values.