Support via Liberapay

Org tutorial on table lookup functions

Introduction

Org provides three different functions for performing searches and data dependent calculations in tables. These functions can, among other things, be used to implement associative arrays, count matching cells, rank results, or group data. The following examples will hopefully help you in getting started with these functions.

Associative array with unique keys

The most straightforward use of lookups is to treat part of an org table as an associative array: a key can be used to look up a corresponding value.

Say you are taking a trip to Scandinavia, and you want to keep track of how much money you have spent on the trip. You decide to convert all sums to euros. Before your trip you write down the following table of approximate currency rates.

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

In what follows we will use the function org-lookup-first and the previous table rates to automatically convert the sums in different currencies to euros. The signature of function org-lookup-first looks as follows:

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

Assuming that PREDICATE is nil, in which case the default predicate equal is used, this function does a search for the first instance of VAL in S-LIST and returns the a value from the corresponding position in R-LIST. In the table below, each sum is assigned a currency abbreviation; a lookup is done in table rates above in the second column for the corresponding abbreviation, and then the corresponding rate is returned from the third column. For each row only the first four columns need to filled; columns 5 and 6 are calculated automatically. Notice that an error results if the key is not found: in the last row, an empty key is being searched for.

|  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

Multiple matches with preferred ordering

A common task for teachers is the assignment of exam grades from total marks. The starting point for such grading is a table with grade boundaries. Below is one such table, with the rows in increasing order of the lower bound required for a particular grade.

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

We will use the function org-lookup-last and the previous table grade-boundaries to assign grades to students based on their marks. The signature of function org-lookup-last is exactly like the signature of org-lookup-first:

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

However, this function does a search for the last match in S-LIST and returns the a value from the corresponding position in R-LIST. Here the idea of the lookup used in assigning the grade is as follows. Say a student's exam result is 33 marks. We look for the last row in the table for which the student's marks are greater than or equal to the lower bound; in this case it is the row with lower bound 30. The student's grade is the corresponding element from the second column, in this case a B.

Thus, given the number of marks VAL of a student, we find the last row of the first column of table grade-boundaries for which the lower bound S fulfils (>= VAL S). Thus we will use >= as PREDICATE to perform the matching. Note that VAL and S are assigned as arguments to the predicate in the order they are in the signature of org-lookup-last, where VAL precedes S-LIST. The following table does the conversion from total marks to the final grade. Notice the literal interpolation L of table values into the Elisp formula, which is needed because some values are numbers and some are symbols.

| 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

Counting matching cells

The function org-lookup-all can not be used by itself in a table equation, because it returns a list of values. However, powerful lookup tasks can be performed by combining the function with other Elisp functions.

As a simple example consider counting the number of missing values in a table. The signature of function org-lookup-all is exactly like the signatures of the other two lookup functions:

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

However, this function does a search for the all matches in S-LIST and returns the all corresponding values from the corresponding positions in R-LIST. As is the case with org-lookup-first and org-lookup-last, if R-LIST is nil, then the corresponding matching values of S-LIST are returned directly. Notice the use of the E flag to retain empty fields in the range. Also notice that in this case we are doing the lookup in a true two-dimensional range, which is thus also possible

| 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

Ranking results

Another example application of org-lookup-all is an automatic ranking of results. In the table below, a larger total number of marks is better. Notice that the Elisp expression also automatically takes care of ties.

| 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

Frequency counts from raw data

A common situation in the analysis of data is the classification (grouping) of raw data values for, e.g., visualisation. Often this is done by counting the frequencies of observations within certain bounds. The function org-lookup-all, combined with other Elisp functions, can be used to perform this task. This example also shows how to construct more complicated lookup rules using multiple values from a table.

Consider the following table with different results from different groups 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 |

We will classify the results into different, mutually exclusive classes. For example, the observations that will belong to the first class are in the interval [1, 1.9] (endpoints included). In order to perform this classification, we define the following two-place predicate function in-interval. Notice that the first parameter of this function is a pair whose first element is the lower bound and second member the upper bound of the interval.

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

Using this predicate function, we can construct a table with class boundaries and corresponding frequencies. Note that the first argument to the function org-lookup-all, which is then passed over as the first argument to the predicate in-interval, is the pair of bounds.

| 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

Conclusion

The org lookup functions can be used for a large number of different data-dependent calculations. For example, the following spreadsheet operations familiar to libreoffice or Excel users can be implemented using them: HLOOKUP, VLOOKUP, COUNTIF, SUMIF and FREQUENCY. If you have other interesting examples of the use of these functions, feel free to send them to the org mailing list and we will be happy to add them on this page.

Documentation from the orgmode.org/worg/ website (either in its HTML format or in its Org format) is licensed under the GNU Free Documentation License version 1.3 or later. The code examples and css stylesheets are licensed under the GNU General Public License v3 or later.