EMACS-DOCUMENT

=============>随便,谢谢

探索Emacs的chart库

条形图

条形图是一种通过彩色矩形对数据进行可视化分组的简单方法。在文本缓冲区和shell输出的世界中,我们很惊喜地发现Emacs居然有一个库可以用来绘制彩色矩形: chart.

(require 'chart)

(chart-bar-quickie
 'vertical "Favorite Type of Movie" ;; Type & Title
 '("Comedy" "Action" "Romance" "Drama" "Sci-Fi") "Genre" ;; Keys & Label
 '(4 5 6 1 4) "People" ;; Values & Label
 )

Rendering chart...done

exploring-emacs-chart--cover.png

正如上面的截图所示,它还不够精致,不足以进行深入的检查,但在文本缓冲区范围内仍然令人印象深刻。 这个库是内置了一些关于Emacs解释器的有趣查询,比如如 memory-usage.

exploring-emacs-chart--memory-usage.png

如果您想尝试一下,可以使用命令 load-library 来导入 chart.它定义了下面的演示命令可供你把玩:

chart-emacs-storage
这个图表显示了内存使用情况
chart-emacs-lists
chart-emacs-storage 类似,他会对Emacs会话中的 buffer /, /frames/, /processes/, /faces/和/x-display 进行计数
chart-rmail-from
rmail 连用,它通过邮件对用户发送方的事件进行分组
chart-space-usage
选择一个目录之后,它会根据该目录中的文件类型显示磁盘使用情况
chart-file-count
选择目录之后,它会显示目录中个文件类型出现的次数
chart-test-it-all
测试正负值的图表

除了这些演示命令之外,我们如何使用它来制作图表呢?

创建图表

经过一番阅读和探索,我发现理解这个库的起始命令或函数是 chart-bar-quickie,其余函数的似乎都是内部使用的。 虽然这个函数总共有只有8个参数,其中两个还是可选的,但是它封装了创建图表所需的一切,包括标签和数据,而不需要对 对象 实例化。下面用一个小代码段来演示一下:

(defun fn/chart-comparator-< (left right)
  "A sample comparator for the values.
Each parameter is a key-value cos pair and should function as a
comparator like `<', `=', or `>'."
  (pcase-let ((`(,left-key . ,left-value) left) ;; left is a cons of key and value
              (`(,right-key . ,right-value) right) ;; ditto with right
              )
    (< left-value right-value)))

(chart-bar-quickie
 'vertical ;; Chart direction, either 'vertical or 'horizontal
 "Chart Title" ;; Chart title
 '("Alpha" "Beta" "Gamma" "Delta") ;; X-Axis values or keys
 "X Line" ;; X-Axis label
 '(4 3 2 1) ;; Y-Axis values
 "Y Line" ;; Y-Axis label
 ;; Optional
 3 ;; Max value allowed, anything higher is not shown
 #'fn/chart-comparator-< ;; Sorting or ordering function
 )
Rendering chart...done

exploring-emacs-chart--sample.png

求值之后,就会根据标题创建一个新的缓冲区,并在那里呈现条形图。 不像 tabulated-list-mode 或其他特殊的缓冲区模式,显示本身是静止不动的,所以不要期望有特殊的快捷键或命令来排序、缩放、刷新图表。 除了不要报太大预期之外,所有主要(如果不是全部)功能都在该功能中。(遗憾的是,再怎么深入了解也产生不了更多的感悟)。 最后,这里唯一需要注意的是,键和值的数量应该是相同的,这很自然,因为它需要将每个键与相应的值匹配起来。

演示

作为一个例子,让我们处理一个数据集上的统计查询。我们将使用头发眼睛颜色数据集,它看起来像这样:

"","Hair","Eye","Sex","Freq"
"1","Black","Brown","Male",32
"2","Brown","Brown","Male",53
"3","Red","Brown","Male",10
"4","Blond","Brown","Male",3
"5","Black","Blue","Male",11

在查询之前,让我们先导入数据,这是一个很好的在Emacs上解析CSV的练习:

(setq csv-dataset-file (expand-file-name "HairEyeColor.csv" "~/Downloads"))

(defun read-lines (file) ;; Take your pick: custom, find-file, f-read-text
  (with-temp-buffer
    (insert-file-contents-literally file)
    (split-string
     (decode-coding-region (point-min) (point-max) 'utf-8 t)
     "\n"
     t)))

(defun read-csv-field (field) ;; Remove the pesky enclosed double quoting
  (cond
   ((zerop (length field)) "")
   ((and (string-equal (substring-no-properties field 0 1) "\"")
         (string-equal (substring-no-properties field -1) "\""))
    (substring-no-properties field 1 -1))
   (t (string-to-number field))))

(defun read-csv-line (line)
  (let ((raw-fields ;; This assumes no rogue commas need escaping
         (split-string line ",")))
    (mapcar #'read-csv-field raw-fields)))

(defun read-csv-file (file)
  (mapcar #'read-csv-line (read-lines file)))


(setq dataset (read-csv-file csv-dataset-file))

;; Generated output
(("" "Hair" "Eye" "Sex" "Freq")
 ("1" "Black" "Brown" "Male" 32)
 ("2" "Brown" "Brown" "Male" 53)
 ("3" "Red" "Brown" "Male" 10)
 ("4" "Blond" "Brown" "Male" 3))

有一些专业的库,如 csv, el-csvparse-csv,以及诸如 fparsec 这样的实用程序,但是偶尔自己动手可以复习基础只是。 数据在手后,我们能对数据提出那些问题呢? 眼睛颜色的频率是多少? 在此之前,我们需要对数据进行分组:

(setq headers (car dataset) ;; Only mentioned, not needed
      records (cdr dataset))

(defun group-by (f xs) ;; A quick write on -group-by
  (let ((groups (list)))
    (mapc
     (lambda (x)
       (let* ((key (funcall f x))
              (key-group (assoc key groups)))
         (unless key-group
           (push (cons key (list)) groups)
           (setq key-group (assoc key groups)))
         (setcdr key-group (cons x (cdr key-group)))))
     xs)
    groups))

(defun group-records-by-eye-color (records)
  (let* ((raw-eye-groups (group-by (apply-partially #'nth 2) records))
         (eye-groups (mapcar
                      (lambda (eye-group)
                        (pcase-let ((`(,eye-color . ,eye-records) eye-group))
                          (let ((eye-frequencies ;; Extract frequencies and collect it
                                 (mapcar (apply-partially #'nth 4) eye-records)))
                            (cons eye-color
                                  (apply #'+ eye-frequencies) ;; Shiv for sum
                                  ))))
                      raw-eye-groups)))
    eye-groups))


(setq eye-color-groups (group-records-by-eye-color records))

;; Generated output
(("Green" . 64)
 ("Hazel" . 93)
 ("Blue" . 215)
 ("Brown" . 220))

在提取之后,获取柱状图就很简单了。

(chart-bar-quickie
 'horizontal
 "Eye Colors"
 (mapcar #'car eye-color-groups) "Colors"
 (mapcar #'cdr eye-color-groups) "Frequency")

exploring-emacs-chart--eye-color.png

很漂亮,但再让我们按降序排列一下:

(defun on (f op) ;; Haskell's on operator
  (lexical-let ((f f) ;; Sad that parameters aren't lexically scoped here
                (op op))
    (lambda (left right)
      (funcall op (funcall f left) (funcall f right)))))

(chart-bar-quickie
 'horizontal
 "Eye Colors - Descending"
 (mapcar #'car eye-color-groups) "Colors"
 (mapcar #'cdr eye-color-groups) "Frequency"
 nil
 (on #'cdr #'>) ;; A comparator lambda also works but done for variety
 )

exploring-emacs-chart--eye-color-descending.png

看看这幅图,/棕色/ 和 蓝色 的眼睛很常见。没有什么突破性的或史诗般的东西。用同样的方法处理 发色 怎么样?我们得到如下图表:

exploring-emacs-chart--hair-color.png

头发颜色 为变量,我们发现 棕色金发 特别多。这没什么奇怪的。 我们可以把变量改成 gender,但没什么意义。 任何可视化的真正问题是 它意味着什么? 或者说,我们能从数据中解读出什么见解? 因为它只是一个样本,它可能没有任何意义,也不需要有任何意义。这是一个图书馆式的探索,而不是一个超出范围的统计学课程。

结论

奇怪的是,这就是它的全部功能: 一个整洁而简单的构建条形图的库. 因为它精确不到像素级别,因此它无法与现代可视化工具或类似 Python, R , Haskell 这样的语言相比.让然它也没那个必要。拥有它很好,知道它也很有趣。

有趣的是,如果柱状图可以用ASCII来表示,那么扇形图和气泡图呢?Emacs可以成为现代可视化工具吗?