Common Lispで統計関数を車輪の再発明(再訪)

だいぶ前に書いたCommon Lispのプログラムを発掘しました。

marui.hatenablog.com

まだapplymapcarを知らなかったので、再帰だけを使って計算していました。たとえばベクトル(というか数値の入ったリスト)の和を計算するのにも、以下のように再帰を使っていました。

(defun sum (x)
  (if (null x)
      0
      (+ (first x) (sum (rest x)))))

(sum '(1 2 3 4))                        ;=> 10

applyを使えば一発で書けます。

(defun sum (x)
  (apply '+ x))

(sum '(1 2 3 4))                        ;=> 10

同様に幾何平均を計算するところも、以下のようにまどろっこしいことをしていました。(とはいえ(expt (* 1 2 3 4) (/ 1 4))みたいに直接乗算を使っておらず、logexpを使ってオーバーフローが生じにくいようにしてはいますね)

(defun geomean-sub (x)
  (if (null x)
      0
      (+ (log (first x)) (geomean-sub (rest x)))))

(defun geomean (x)
  (exp (/ (geomean-sub x) (length x))))

(geomean '(1 2 3 4))                    ;=> 2.213364

mapcarを使うと次のように書き換えられます。ただ、算術平均を計算するmean関数も欲しかったので、そちらを作って下請けとして使っています。

(defun mean (x)
  (/ (sum x) (length x)))

(defun geomean (x)
  (exp (mean (mapcar 'log x))))

(geomean '(1 2 3 4))                    ;=> 2.213364

長いデータ列の処理をすることを考えると、再帰でスタックを消費するのはよくありませんから(データが多いと計算できなくなってしまう)、applymapcarなどを使うほうが安全そうです。こんなかんじで、プログラミング言語の勉強のために、ゆっくり車輪の再発明をしています。

Matlablinspaceに対応するものもCommon Lispで作ってみました。(linspace x1 x2)とすると、x1からx2の範囲を等間隔に100個作ってくれます。個数の指定をしたいときにはオプションで個数を書いてあげるとOK。

;; linspace
;; (linspace -2.0 +1.0)                 ;=> (-2.0 -1.969697 -1.939394 ... 0.969697 1.0)
;; (linspace 1 5 5)                     ;=> (1 2 3 4 5)
;;
(defun linspace (x1 x2 &optional (n 100))
  (let ((x (loop for x from 0 to (1- n)
                 collect (/ x (1- n)))))
    (mapcar #'(lambda (v) (+ v x1))
            (mapcar #'(lambda (v) (* v (- x2 x1))) x))))

追記 データはリストではなくベクトルにしたほうがいいのかも。あるいはRのようなデータフレーム型を作ったほうがいいか。……規模が大きくなってきたら考えよう。(というより、遊びで作っているだけなので規模が大きくならないようにしよう。統計処理をするツールはLisp-Statとかがあるみたいだし。)