Org-mode也有Excel一样的LOOKUP

原文Org tutorial on table lookup functions, 由 Jarmo Hurri 编辑,维护。本文只做学习之用。

序言

Org 提供三个不同的函数,用于在表中执行搜索和数据依赖的计算。 这些函数可以用于实现数组关联,统计匹配单元格,结果排名或分组数据。 以下示例将有助于开始使用这些功能。

具有唯一键的关联数组

查找最直接的用法是将 Org 表的一部分视为关联数组:一个键可用于查找相应的值。

假设你去斯堪的纳维亚,并且想跟踪你花了多少钱在旅途中。 你决定将所有金额转换为欧元。 在行程之前,你请记下大致汇率,如下表所示:

 #+TBLNAME: rates
| currency        | abbreviation | euros |
|-----------------+--------------+-------|
| euro            | eur          |     1 |
| Norwegian krone | nok          |  0.14 |
| Swedish krona   | sek          |  0.12 |
| US dollar       | usd          |  0.77 |

接下来将使用函数 org-lookup-first 和前面的汇率表格来自动将不同货币的金额转换成欧元。 函数 org-lookup-first 的签名如下:

(org-lookup-first VAL S-LIST R-LIST &optional PREDICATE)

假定 PREDICATEnil ,在这种情况下使用默认谓词(predicate) equal , 则该函数将在 S-LIST 中搜索 VAL 的第一个实例,并从 R-LIST 中的相应位置返回一个值。 在下表中,每笔金额分配了货币缩写; 对于相应的缩写,在汇率表格的第二列中进行查找,然后从第三列返回相应的汇率。 对于每一行只需要填充前四列; 第5列和第6列自动计算产生。 请注意,如果找不到键值,则会出现错误:在最后一行中,空键将被搜索。

|  date | expense          |  sum | currency |   rate |  euros |
|-------+------------------+------+----------+--------+--------|
|  1.3. | flights          |  324 | eur      |      1 |    324 |
|  4.6. | books and maps   |  243 | usd      |   0.77 | 187.11 |
| 30.7. | rental car       | 8300 | sek      |   0.12 |   996. |
|  2.7. | hotel            | 1150 | sek      |   0.12 |   138. |
|  2.7. | lunch            |  190 | sek      |   0.12 |   22.8 |
|  3.7. | fishing licenses | 1400 | nok      |   0.14 |   196. |
|  3.7. | gasoline         |  340 |          | #ERROR | #ERROR |
 #+TBLFM: $5='(org-lookup-first $4 '(remote(rates,@2$2..@>$2)) '(remote(rates,@2$3..@>$3)))::$6=$5*$3

多个匹配优先排序

教师的常见任务是从总分中分配考试成绩。 这种分级的起点是具有等级边界的表。 以下是一个这样的表,其中行按照特定等级所需的下限的递增排序。

 #+TBLNAME: grade-boundaries
| lower bound | grade |
|-------------+-------|
|           0 | F     |
|          10 | D     |
|          20 | C     |
|          30 | B     |
|          40 | A     |

使用函数 org-lookup-last 和根据前面的 等级边界 表来为学生分配成绩。 函数 org-lookup-last 的签名与 org-lookup-first 的完全相同:

(org-lookup-last VAL S-LIST R-LIST &optional PREDICATE)

函数 org-lookup-last 会搜索 S-LIST 中的最后一个匹配项,并从 R-LIST 中的相应位置返回一个值。 用于分配等级的查找思想如下:假定学生的考试成绩是33分。我们寻找学生的 marks 大于或等于下限的表中的最后一行; 在这种情况下,它是下边界的行30。学生的成绩是第二列的相应元素,在这种情况下是B.

因此,给定学生的标记数VAL,找到下限S满足 (>= VAL S) 的表等级边界的第一列的最后一行。 因此,我们将使用 >= 作为 PREDICATE 来执行匹配。 注意, VALS 按照它们在 org-lookup-last 的签名中的顺序被分配给谓词,其中 VALS-LIST 之前。 下表列出了从总 marks 到最终成绩的转换。 注: 文字插值 L 表示表值的字面值插入到Elisp公式中,这是必须的,因为一些值是数字,一些是符号。

| student | marks | grade |
|---------+-------+-------|
| X       |    30 | B     |
| Y       |    29 | C     |
| Z       |     5 | F     |
| W       |    55 | A     |
 #+TBLFM: $3='(org-lookup-last $2 '(remote(grade-boundaries,@2$1..@>$1)) '(remote(grade-boundaries,@2$2..@>$2)) '>=);L

统计匹配单元格

函数 org-lookup-all 不能在表等式中使用自己,因为它返回值列表。 但是,通过将函数与其他 elisp 函数相结合,可执行强大的查找任务。

作为一个简单的例子,计算表中缺少值的数量。 函数 org-lookup-all 的签名与其他两个查找函数的签名完全相同:

(org-lookup-all VAL S-LIST R-LIST &optional PREDICATE)

数搜索 S-LIST 中的所有匹配项,并从 R-LIST 中的相应位置返回所有相应的值。 与org-lookup-first和org-lookup-last的情况一样,如果 R-LIST 为nil,则直接返回 S-LIST 相应匹配值。 注意使用 E 标志来保留范围内的空字段。 还要注意,在这种情况下,以真正的二维范围来进行查找,这也是可能的

| group | round 1 | round 2 |
|-------+---------+---------|
| A     |         |     2.4 |
| B     |     4.7 |      11 |
| C     |         |         |
| D     |       5 |         |
| E     |         |     7.2 |
| F     |     3.2 |     4.3 |
| G     |         |     4.4 |
| H     |         |       8 |
|-------+---------+---------|
| total | missing |       7 |
 #+TBLFM: @>$3='(length(org-lookup-all "" '(@2$2..@-1$3) nil));E

排序结果

org-lookup-all 的另一个示例应用是结果的自动排序。 在下表中,总数越大越好。 请注意,Elisp表达式还自动处理关联关系。

| group | marks | rank |
|-------+-------+------|
| A     |    22 |    2 |
| B     |    22 |    2 |
| C     |    14 |    4 |
| D     |    28 |    1 |
| E     |     9 |    5 |
 #+TBLFM: $3='(+ 1 (length (org-lookup-all $2 '(@2$2..@>$2) nil '<)));N

统计原始数据的频率

数据分析中的常见情况是对可视化的原始数据值进行分类(分组)。 通常是通过统计在特定范围内的出现频率来完成的。 可使用函数 org-lookup-all ,结合其他 elisp 函数来执行此任务。 此示例还显示了如何使用表中的多个值构建更复杂的查找规则。

考虑下表,不同组A-I的不同结果。

 #+TBLNAME: raw-data
| group | result |
|-------+--------|
| A     |    2.3 |
| B     |    4.2 |
| C     |    1.1 |
| D     |    3.6 |
| E     |    4.5 |
| F     |    2.4 |
| G     |    1.0 |
| H     |    2.3 |
| I     |    2.8 |

将结果分为不同的,并且相斥的类。 例如,属于第一类的值在区间 [1,1.9] (包括端点)中。 为了执行这样的分类,我们定义了以下两参数谓词函数 in-interval 。 请注意,此函数的第一个参数是一对,其第一个元素是下限,第二个成员是该间隔的上限。

#+BEGIN_SRC emacs-lisp
  (defun in-interval (bounds el)
    (and (>= el (car bounds)) (<= el (cadr bounds))))
#+END_SRC

#+RESULTS:
: in-interval

使用这个谓词函数,我们可以构造一个具有分类边界和相应频率的表。 请注意,函数 org-lookup-all 的第一个参数是作为第一个参数传递给谓词 in-interval 中的第一个参数,是一对边界。

| lower bound | upper bound | frequency |
|-------------+-------------+-----------|
|           1 |         1.9 |         2 |
|           2 |         2.9 |         4 |
|           3 |         3.9 |         1 |
|           4 |         4.9 |         2 |
 #+TBLFM: $3='(length (org-lookup-all '($1 $2) '(remote(raw-data,@2$2..@>$2)) nil 'in-interval));N

结论

Org 的 lookup 函数可用于大量不同的数据相关计算。 例如,libreoffice或Excel用户熟悉的以下电子表格操作都可以使用它们来实现: HLOOKUPVLOOKUPCOUNTIFSUMIFFREQUENCY

推荐阅读更多精彩内容