Bulletproof statistical functions

This commit is contained in:
Marcus Kammer 2024-08-20 18:12:03 +02:00
parent f8c0d2d0c6
commit 1a65165015
2 changed files with 31 additions and 13 deletions

View file

@ -2,7 +2,7 @@
(defsystem "dev.metalisp.survey" (defsystem "dev.metalisp.survey"
:description "A simple survey" :description "A simple survey"
:version "0.4.6" :version "0.4.7"
:author "Marcus Kammer <marcus.kammer@metalisp.dev>" :author "Marcus Kammer <marcus.kammer@metalisp.dev>"
:source-control "git@git.sr.ht:~marcuskammer/dev.metalisp.survey" :source-control "git@git.sr.ht:~marcuskammer/dev.metalisp.survey"
:licence "MIT" :licence "MIT"

View file

@ -10,6 +10,9 @@
(in-package #:ml-survey/stats) (in-package #:ml-survey/stats)
(define-condition stats-error (error)
((message :initarg :message :reader error-message)))
(defun preprocess-and-transpose (data) (defun preprocess-and-transpose (data)
(apply #'mapcar #'list (mapcar #'cdr data))) (apply #'mapcar #'list (mapcar #'cdr data)))
@ -21,19 +24,34 @@
(reduce #'max numbers))) (reduce #'max numbers)))
(defun standard-deviation (numbers) (defun standard-deviation (numbers)
(let* ((avg (mean numbers)) (handler-case
(variance (/ (reduce #'+ (mapcar (lambda (x) (expt (- x avg) 2)) numbers)) (let ((len (length numbers)))
(1- (length numbers))))) (if (< len 2)
(sqrt variance))) (error 'stats-error :message "Need at least two numbers for standard deviation")
(let ((avg (mean numbers)))
(sqrt (/ (reduce #'+ (mapcar (lambda (x) (expt (- x avg) 2)) numbers))
(1- len))))))
(type-error ()
(error 'stats-error :message "Invalid input for standard deviation calculation"))))
(defun mean (numbers) (defun mean (numbers)
(/ (reduce #'+ numbers) (length numbers))) (handler-case
(if (null numbers)
(error 'stats-error :message "Cannot calculate mean of an empty list")
(/ (reduce #'+ numbers) (length numbers)))
(division-by-zero ()
(error 'stats-error :message "Division by zero in mean calculation"))))
(defun median (numbers) (defun median (numbers)
(let ((sorted (sort (copy-seq numbers) #'<)) (handler-case
(count (length numbers))) (let ((sorted (sort (copy-seq numbers) #'<))
(if (oddp count) (len (length numbers)))
(nth (floor count 2) sorted) (if (zerop len)
(/ (+ (nth (1- (/ count 2)) sorted) (error 'stats-error :message "Cannot calculate median of an empty list")
(nth (/ count 2) sorted)) (if (oddp len)
2.0)))) (nth (floor len 2) sorted)
(/ (+ (nth (1- (/ len 2)) sorted)
(nth (/ len 2) sorted))
2))))
(type-error ()
(error 'stats-error :message "Invalid input for median calculation"))))