org-colview: Refactor low-high estimates
* lisp/org-colview.el (org-columns-string-to-number): (org-columns-number-to-string): Handle estimates. (org-columns-estimate-combine): Rename this to... (org-columns--estimate-combine): ... this. (org-columns-compile-map): (org-columns-compute): Apply renaming. (org-estimate-mean-and-var): (org-estimate-print): (org-string-to-estimate): Remove functions. * testing/lisp/test-org-colview.el (test-org-colview/columns-summary): Add tests.
This commit is contained in:
parent
c8e7d93bbd
commit
c158bf2f16
|
@ -770,7 +770,7 @@ When COLUMNS-FMT-STRING is non-nil, use it as the column format."
|
|||
("@min" min_age min)
|
||||
("@max" max_age max)
|
||||
("@mean" mean_age (lambda (&rest x) (/ (apply '+ x) (float (length x)))))
|
||||
("est+" estimate org-estimate-combine))
|
||||
("est+" estimate org-columns--estimate-combine))
|
||||
"Operator <-> format,function map.
|
||||
Used to compile/uncompile columns format and completing read in
|
||||
interactive function `org-columns-new'.
|
||||
|
@ -970,19 +970,21 @@ display, or in the #+COLUMNS line of the current buffer."
|
|||
level last-level)
|
||||
level (org-outline-level)
|
||||
val (org-entry-get nil property)
|
||||
valflag (and val (string-match "\\S-" val)))
|
||||
valflag (org-string-nw-p val))
|
||||
(cond
|
||||
((< level last-level)
|
||||
;; Put the sum of lower levels here as a property. If
|
||||
;; values are estimate, use an appropriate sum function.
|
||||
(setq sum (funcall
|
||||
(if (eq fun 'org-estimate-combine) #'org-estimate-combine
|
||||
#'+)
|
||||
(if (and (/= last-level inminlevel)
|
||||
(aref lvals last-level))
|
||||
(apply fun (aref lvals last-level)) 0)
|
||||
(if (aref lvals inminlevel)
|
||||
(apply fun (aref lvals inminlevel)) 0))
|
||||
;; values are estimates, use an appropriate sum function.
|
||||
(setq sum (funcall (if (eq fun 'org-columns--estimate-combine)
|
||||
#'org-columns--estimate-combine
|
||||
#'+)
|
||||
(if (and (/= last-level inminlevel)
|
||||
(aref lvals last-level))
|
||||
(apply fun (aref lvals last-level))
|
||||
0)
|
||||
(if (aref lvals inminlevel)
|
||||
(apply fun (aref lvals inminlevel))
|
||||
0))
|
||||
flag (or (aref lflag last-level) ; any valid entries from children?
|
||||
(aref lflag inminlevel)) ; or inline tasks?
|
||||
str (org-columns-number-to-string sum format printf)
|
||||
|
@ -1037,7 +1039,9 @@ display, or in the #+COLUMNS line of the current buffer."
|
|||
FMT is a symbol describing the summary type. Optional argument
|
||||
PRINTF, when non-nil, is a format string used to print N."
|
||||
(cond
|
||||
((eq fmt 'estimate) (org-estimate-print n printf))
|
||||
((eq fmt 'estimate)
|
||||
(let ((fmt (or printf "%.0f")))
|
||||
(mapconcat (lambda (n) (format fmt n)) (if (consp n) n (list n n)) "-")))
|
||||
((not (numberp n)) "")
|
||||
((memq fmt '(add_times max_times min_times mean_times))
|
||||
(org-hours-to-clocksum-string n))
|
||||
|
@ -1057,6 +1061,22 @@ PRINTF, when non-nil, is a format string used to print N."
|
|||
(format-seconds "%dd %.2hh %mm %ss" n))
|
||||
(t (number-to-string n))))
|
||||
|
||||
(defun org-columns--estimate-combine (&rest estimates)
|
||||
"Combine a list of estimates, using mean and variance.
|
||||
The mean and variance of the result will be the sum of the means
|
||||
and variances (respectively) of the individual estimates."
|
||||
(let ((mean 0)
|
||||
(var 0))
|
||||
(dolist (e estimates)
|
||||
(pcase e
|
||||
(`(,low ,high)
|
||||
(let ((m (/ (+ low high) 2.0)))
|
||||
(cl-incf mean m)
|
||||
(cl-incf var (- (/ (+ (* low low) (* high high)) 2.0) (* m m)))))
|
||||
(value (cl-incf mean value))))
|
||||
(let ((sd (sqrt var)))
|
||||
(list (- mean sd) (+ mean sd)))))
|
||||
|
||||
(defun org-columns-string-to-number (s fmt)
|
||||
"Convert a column value S to a number.
|
||||
FMT is a symbol describing the summary type."
|
||||
|
@ -1081,7 +1101,11 @@ FMT is a symbol describing the summary type."
|
|||
(setq sum (+ (string-to-number n) (/ sum 60))))))
|
||||
((memq fmt '(checkbox checkbox-n-of-m checkbox-percent))
|
||||
(if (equal s "[X]") 1. 0.000001))
|
||||
((eq fmt 'estimate) (org-string-to-estimate s))
|
||||
((eq fmt 'estimate)
|
||||
(if (not (string-match "\\(.*\\)-\\(.*\\)" s))
|
||||
(string-to-number s)
|
||||
(list (string-to-number (match-string 1 s))
|
||||
(string-to-number (match-string 2 s)))))
|
||||
((string-match-p org-columns--fractional-duration-re s)
|
||||
(let ((s (concat "0:" (org-duration-string-to-minutes s t)))
|
||||
(sum 0.0))
|
||||
|
@ -1495,47 +1519,6 @@ This will add overlays to the date lines, to show the summary for each day."
|
|||
(equal (nth 4 a) (nth 4 fm)))
|
||||
(org-columns-compute (car fm)))))))))))
|
||||
|
||||
(defun org-estimate-mean-and-var (v)
|
||||
"Return the mean and variance of an estimate."
|
||||
(let* ((v (cond ((consp v) v)
|
||||
((numberp v) (list v v))
|
||||
(t (error "Invalid estimate type"))))
|
||||
(low (float (car v)))
|
||||
(high (float (cadr v)))
|
||||
(mean (/ (+ low high) 2.0))
|
||||
(var (/ (+ (expt (- mean low) 2.0) (expt (- high mean) 2.0)) 2.0)))
|
||||
(list mean var)))
|
||||
|
||||
(defun org-estimate-combine (&rest el)
|
||||
"Combine a list of estimates, using mean and variance.
|
||||
The mean and variance of the result will be the sum of the means
|
||||
and variances (respectively) of the individual estimates."
|
||||
(let ((mean 0)
|
||||
(var 0))
|
||||
(mapc (lambda (e)
|
||||
(let ((stats (org-estimate-mean-and-var e)))
|
||||
(setq mean (+ mean (car stats)))
|
||||
(setq var (+ var (cadr stats)))))
|
||||
el)
|
||||
(let ((stdev (sqrt var)))
|
||||
(list (- mean stdev) (+ mean stdev)))))
|
||||
|
||||
(defun org-estimate-print (e &optional fmt)
|
||||
"Prepare a string representation of an estimate.
|
||||
This formats these numbers as two numbers with a \"-\" between them."
|
||||
(let ((fmt (or fmt "%.0f"))
|
||||
(e (cond ((consp e) e)
|
||||
((numberp e) (list e e))
|
||||
(t (error "Invalid estimate type")))))
|
||||
(format "%s" (mapconcat (lambda (n) (format fmt n)) e "-"))))
|
||||
|
||||
(defun org-string-to-estimate (s)
|
||||
"Convert a string to an estimate.
|
||||
The string should be two numbers joined with a \"-\"."
|
||||
(if (string-match "\\(.*\\)-\\(.*\\)" s)
|
||||
(list (string-to-number (match-string 1 s))
|
||||
(string-to-number(match-string 2 s)))
|
||||
(list (string-to-number s) (string-to-number s))))
|
||||
|
||||
(provide 'org-colview)
|
||||
|
||||
|
|
|
@ -452,6 +452,37 @@
|
|||
:A: 5d 3h
|
||||
:END:"
|
||||
(let ((org-columns-default-format "%A{@min}")) (org-columns))
|
||||
(get-char-property (point) 'org-columns-value-modified))))
|
||||
;; {est+} gives a low-high estimate using mean and standard
|
||||
;; deviation.
|
||||
(should
|
||||
(equal
|
||||
"3-17"
|
||||
(org-test-with-temp-text
|
||||
"* H
|
||||
** S1
|
||||
:PROPERTIES:
|
||||
:A: 0-10
|
||||
:END:
|
||||
** S1
|
||||
:PROPERTIES:
|
||||
:A: 0-10
|
||||
:END:"
|
||||
(let ((org-columns-default-format "%A{est+}")) (org-columns))
|
||||
(get-char-property (point) 'org-columns-value-modified))))
|
||||
;; When using {est+} summary, a single number is understood as
|
||||
;; a degenerate range.
|
||||
(should
|
||||
(equal
|
||||
"4-4"
|
||||
(org-test-with-temp-text
|
||||
"* H
|
||||
** S1
|
||||
:PROPERTIES:
|
||||
:A: 4
|
||||
:END:
|
||||
"
|
||||
(let ((org-columns-default-format "%A{est+}")) (org-columns))
|
||||
(get-char-property (point) 'org-columns-value-modified)))))
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue