Skip to content

Instantly share code, notes, and snippets.

@ericnormand
Last active May 6, 2020 17:23
Show Gist options
  • Save ericnormand/5a29561f7e220879210c3726ad64e2de to your computer and use it in GitHub Desktop.
Save ericnormand/5a29561f7e220879210c3726ad64e2de to your computer and use it in GitHub Desktop.

Moving average

Very often, your dataset represents real measurements over time. There's a clear trend in the data, but it's muddied by the noise in each data point. One way to focus on the trend is to perform a moving average. Each data point is the average of the n points around it (the window).

Your task is to write a function that takes a sequence of numbers and returns a sequence of weighted averages. Make n (the window size) an argument.

(defn window
"Return a window of size n, centered on i, of the values (a vector)"
[n values i]
(let [n2 (quot n 2)
n2o (- n n2)
start (max 0 (- i n2o))
end (min (count values) (+ i n2))]
(subvec values start end)))
(defn sum [numbers]
(reduce + 0 numbers))
(defn avg [numbers]
(/ (sum numbers) (count numbers)))
(defn moving-average [size numbers]
(->> numbers
vec
(map-indexed #(window size numbers %2))
(map avg)
(map double)))
(defn- slide [window x]
(-> window (subvec 1) (conj x)))
(defn- windows [n xs]
(let [first-window (vec (take n xs))
the-rest (drop n xs)]
(when (and (< 0 n) (= n (count first-window)))
(reductions slide first-window the-rest))))
(defn- avarage [xs]
(/ (apply + xs) (count xs)))
(defn moving-avarage [n xs]
(->> xs (windows n) (map avarage)))
(moving-avarage 1 []) ; => ()
(moving-avarage -1 [1 2 3 4]) ; => ()
(moving-avarage 0 [1 2 3 4]) ; => ()
(moving-avarage 1 [1 2 3 4]) ; => (1 2 3 4)
(moving-avarage 2 [1 2 3 4]) ; => (3/2 5/2 7/2)
(moving-avarage 3 [1 2 3 4]) ; => (2 3)
(moving-avarage 4 [1 2 3 4]) ; => (5/2)
(moving-avarage 5 [1 2 3 4]) ; => ()
(nth (moving-avarage 3 (range)) 1000000) ; => 1000001
@miner
Copy link

miner commented Feb 10, 2020

(moving-average 1 [10 11])
Error printing return value (IndexOutOfBoundsException) at clojure.lang.RT/subvec (RT.java:1616)

The args are swapped in the map-indexed call. You want the index (not %2) so you need to use the (fn [i _] ...) notation.

Also, window probably should never return an empty vector as that will blow up in avg with a divide by zero.
(window 1 [10 11 12] 0)
;=> []

You could change the end calculation to be (min (count values) (+ i (max 1 n2))) but maybe there's a better way.

@miner
Copy link

miner commented Feb 10, 2020

suggestion for window (revised)

(defn window
  "Return a window of size n, centered on i, of the values (a vector)"
  [n values i]
  (let [offset (quot (inc n) 2)
        end (min (count values) (+ i offset))
        start (max 0 (- i (- n offset)))]
   (subvec values start end)))

@KingCode
Copy link

KingCode commented May 6, 2020

Eric's solution documents a window to be centered around i, but it actually trails i, so I used that as a basis for my solution

@KingCode
Copy link

KingCode commented May 6, 2020

(defn give-and-take [n i]
  (let [lo (max 0 (inc (- i n))) 
        hi (-> (+ lo n) (#(if (< i %) i %)))]
    [lo, (-> (- hi lo) inc)]))
      
(defn window 
    "Return a trailing window of size n ending at position i, of the values (a vector)"
  [n values i]
  (let [[offset quantity] (give-and-take n i)]
    (take quantity (drop offset values))))

(defn avg [xs]
  (double (/ (apply + xs) (count xs))))

(defn moving-average [n xs]
  (->> (count xs) range
       (map (partial window n xs))
       (mapv avg)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment