tags:

views:

156

answers:

3

I'm looking for an elegant way to generate a sequence of the rolling average of a sequence of numbers. Hopefully something more elegant than using lazy-seq

+3  A: 

There's a very similar question on SO: Calculating the Moving Average of a List. It's more general -- a number of FP-friendly languages are represented, with the accepted answer using Scala -- but there are a few nice Clojure solutions.

I've posted my own solution over there. Note that it does use lazy-seq, but that's because I wanted it to perform well for large periods (which means adjusting the average at each step rather than calculating a separate average for each window of size = period into the input list). Look around that Q for nice solutions which made the other tradeoff, resulting in shorter code with a somewhat more declarative feel, which actually performs better for very short periods (although suffers significant slowdowns for longer periods, as is to be expected).

Michał Marczyk
Good idea with the link, thanks!
Michał Marczyk
+4  A: 

Without any consideration of efficiency:

(defn average [lst] (/ (reduce + lst) (count lst)))

(defn moving-average [window lst] (map average (partition window 1 lst)))


user> (moving-average 5 '(1 2 3 4 5 6 7 8))
(3 4 5 6)

If you need it to be fast, there are some fairly obvious improvements to be made! But it will get less elegant.

John Lawrence Aspden
I appreciate the brevity and clarity and in this case efficiency is simply not a concern as this only needs to run once.
Arthur Ulfeldt
This is actually just about the most efficient lazy version for shorter periods, so if you don't expect to exceed a period length of somewhere around 8, you should use it as a matter of optimisation. If you're going to do periods of length 20, 50 or more, then this will become abysmal performance-wise and you should go with my version. ;-) Also, check out the leading Clojure answer (2nd overall as of now; by James Cunningham) in that old Q I linked to for a very elegant one line version of this. :-)
Michał Marczyk
+1  A: 

This version is a bit faster, especially for long windows, since it keeps a rolling sum and avoids repeatedly adding the same things.

Because of the lazy-seq, it's also perfectly general and won't blow stack

(defn partialsums [start lst]
  (lazy-seq
    (if-let [lst (seq lst)] 
          (cons start (partialsums (+ start (first lst)) (rest lst)))
          (list start))))

(defn sliding-window-moving-average [window lst]
  (map #(/ % window)
       (let [start   (apply + (take window lst))
             diffseq (map - (drop window lst) lst)]
         (partialsums start diffseq))))

;; To help see what it's doing:

(sliding-window-moving-average 5 '(1 2 3 4 5 6 7 8 9 10 11))

start = (+ 1 2 3 4 5) = 15

diffseq = - (6 7 8 9 10 11)
            (1 2 3 4  5  6 7 8 9 10 11)

        =   (5 5 5 5  5  5)

(partialsums 15 '(5 5 5 5 5 5) ) = (15 20 25 30 35 40 45)

(map #(/ % 5) (20 25 30 35 40 45)) = (3 4 5 6 7 8 9)

;; Example

(take 20 (sliding-window-moving-average 5 (iterate inc 0)))
John Lawrence Aspden
Oh, that's a very elegant idea... Performs just about the same as my sliding window version, but is definately more aesthetically pleasing. Would you consider posting it as an answer to that other SO question (see the link in my answer here)? +1 anyway!
Michał Marczyk
Thank you for your kind words. I've posted it in the other thread.
John Lawrence Aspden