CL:移動平均の計算
移動平均を計算できる汎用の処理を考えたのでメモっておく。
適当に数値データを用意する。
8,11,15,17,18,25,21,30,38,55,59,68,72,73,69,58,52,51,39,43,37,35,29,27,24,21
「汎用」的な性質として要求したい項目と、そこから導き出される動作を考えた
- 当然幅は可変で
- 別に平均計算じゃなくてもいい (移動和とか、移動部分文字列 (?) なんかも処理の差し替えで簡単に計算できる)
- この要求を満たすため、リストからデータを取り出して個包装する部分と平均計算を別腹にする
- 個包装部分:数値 (にかぎらず) データを受け取ると、一定長の部分リストを先頭から順次くくっていく
- ただし、以下のデータは意味ないからnilにする
- 幅が足りなくて結果をはじき出せない先頭部分
- 幅が足りなくて結果をはじき出せない終端部分
- 実は数値じゃない (もっと汎用的に述語pを満たさない) データが混入した場合
- ただし、以下のデータは意味ないからnilにする
- 個包装用意したら、平均計算はmapcarにお任せ
まずデータをくくりだす処理を書いた。
有効なデータを判定する処理は補助述語かいてlabelsに押し込めた。
(defun pick (p width xs) (if (evenp width) (incf width)) (let ((len (length xs)) (half (/ (1- width) 2))) (labels ((pickable (n) (and (<= half n) (< n (- len half)) (every p xs)))) (mapcar (lambda (n) (if (pickable n) (subseq xs (- n half) (+ n half 1)) nil)) (iota len)))))
(追記:everyの引数xs全部じゃなくて部分リストでした。チェックあまかった)
上で使ってるiotaは以前別記事で自作したもの。
(defun iota (count &optional (start 0) (step 1)) (do ((i 0 (1+ i)) (n start (+ n step)) (ns nil)) ((= i count) (nreverse ns)) (push n ns)))
で、ちゃんと要求どおり動くのをみとく。
> (pick #'numberp 5 (iota 11)) (NIL NIL (0 1 2 3 4) (1 2 3 4 5) (2 3 4 5 6) (3 4 5 6 7) (4 5 6 7 8) (5 6 7 8 9) (6 7 8 9 10) NIL NIL)
たぶんおk
あとはMoving Average本体と、そこから呼び出す平均を計算する関数書いて呼ぶだけ。
(defun average-list (ns) (if ns (float (/ (apply #'+ ns) (length ns))) nil)) (defun ma (width ns) (mapcar #'average-list (pick #'numberp width ns)))
できたっぽい。
> (ma 5 '(8 11 15 17 18 25 21 30 38 55 59 68 72 73 69 58 52 51 39 43 37 35 29 27 24 21)) (NIL NIL 13.8 17.2 19.2 22.2 26.4 33.8 40.6 50.0 58.4 65.4 68.2 68.0 64.8 60.6 53.8 48.6 44.4 41.0 36.6 34.2 30.4 27.2 NIL NIL)