kiyo_hikoのブログ

メモ+日記?

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を満たさない) データが混入した場合
    • 個包装用意したら、平均計算は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)

f:id:kiyo_hiko:20131111020532p:plain