1 #+TITLE: Org as a spreadsheet system: using Emacs lisp as formulas
3 #+EMAIL: bzg AT altern DOT org
4 #+OPTIONS: H:3 num:nil toc:t \n:nil ::t |:t ^:t -:t f:t *:t tex:t d:(HIDE) tags:not-in-toc
5 #+STARTUP: align fold nodlcheck hidestars oddeven lognotestate
6 #+SEQ_TODO: TODO(t) INPROGRESS(i) WAITING(w@) | DONE(d) CANCELED(c@)
7 #+TAGS: Write(w) Update(u) Fix(f) Check(c)
12 [[file:../index.org][{Back to Worg's index}]]
16 This tutorial explains how to use Emacs Lisp as formulas in Org tables. If
17 you want a general tutorial on how to use Org as a spreadsheet system, read
18 [[file:org-spreadsheet-intro.org][this tutorial]]. You can also check the complete [[https://orgmode.org/manual/The-spreadsheet.html#The-spreadsheet][Org documentation]] on this
21 * Example 1: manipulate cells
23 Here is a simple table:
25 | First name | Last Name | Email |
26 |------------+-----------+----------------------|
27 | John | Doe | john.doe@emacs.edu |
28 | Jennie | Duh | jennie.duh@emacs.edu |
30 You easily notice the pattern =[firstname].[lastname]@emacs.edu= for the
31 third column. Given a first name and a last name, it should be easy to
32 compute the result for the "Email" column.
34 Okay, let's do it then. First put your cursor in the third column:
36 # | First name | Last Name | Email |
37 # |------------+-----------+----------------------|
38 # | John | Doe | john.doe@emacs.edu |
39 # | Jennie | Duh | jennie.duh@emacs.edu |
40 # | Jack | Goody | <= [cursor is here] |
42 #+ATTR_HTML: width="400px"
43 [[file:../images/bzg/org-spreadsheet-table1.jpg]]
45 Now type =C-c }= to display the table's coordinates.
47 For each row, you want to concatenate the content of the first column
48 (accessed with =$1=) to a string with a dot (".") to the cell of the second
49 column (accessed with =$2=) and to the string "@emacs.edu". In Emacs Lisp,
52 #+BEGIN_SRC emacs-lisp
53 '(concat $1 "." $2 "@emacs.edu")
56 Now copy this formula, type =C-c == in the bottom right field to insert a
57 /column formula/[fn:2] and copy the formula here. Hitting =RET= will
58 immediately insert the result in this field (=Jack.Goody@emacs.edu=) and
59 add the =#+TBLFM= line at the bottom of the table.
61 *Caveat*: beware of the initial quote: the formula is the /expression
62 itself/, not its value. This expression will only have a meaning when the
63 =$1= and =$2= references will be replaced by the right strings, an the
64 expression will then by applied as a formula by pressing =C-c C-c= on
67 So you now have this table:
69 # | First name | Last Name | Email |
70 # |------------+-----------+----------------------|
71 # | John | Doe | John.Doe@emacs.edu |
72 # | Jennie | Duh | Jennie.Duh@emacs.edu |
73 # | Jack | Goody | Jack.Goody@emacs.edu |
74 # #+TBLFM: $3='(concat $1 "." $2 "@emacs.edu")
76 #+ATTR_HTML: width="400px"
77 [[file:../images/bzg/org-spreadsheet-table2.jpg]]
79 At the time the formula is evaluated, the references =$1= and =$2= are
80 interpreted and replaced by the values of these cells /as strings/: you
81 don't need to enclose =$1= with quotes ("$1").
83 If you want to force the references (=$1= and =$2=) to be interpreted as
84 numbers, add the flag =;N= at the end of the Emacs lisp expression. See
85 for example this table
87 | First name | Last Name | Maths | French | Mean |
88 |------------+-----------+-------+--------+------|
89 | John | Doe | 12 | 16 | 14 |
90 | Jennie | Duh | 15 | 9 | 12 |
91 #+TBLFM: $5='(/ (+ $3 $4) 2);N
93 where we used this formula for the fifth column:
96 ,#+TBLFM: $5='(/ (+ $3 $4) 2);N
99 As an exercise, try to find Emacs lisp formula for the fifth column in this
102 | First name | Last Name | Maths | French | Mean |
103 |------------+-----------+-------+--------+------------|
104 | John | Doe | 12 | 16 | John: 14 |
105 | Jennie | Duh | 15 | 9 | Jennie: 12 |
106 #+TBLFM: $5='(concat "$1" ": " (number-to-string (/ (+ $3 $4) 2)));L
107 #+TBLFM: $5='(concat "$1" ": " (number-to-string (/ (+ $3 $4) 2)));N
108 #+TBLFM: $5='(concat $1 ": " (number-to-string (/ (+ (string-to-number $3) (string-to-number $4)) 2)))
110 All you have is the values of the first four columns. (Hint: check the
111 Emacs lisp functions =string-to-number= and =number-to-string=.)
113 *Solution*: we cannot use the =;N= flag because it will force
114 interpretation of the cells as numbers, and if we do so, we will not be
115 able to access the value of cells in the first row. So one idea is to
116 use =string-to-number= and =number-to-string= this way:
119 ,#+TBLFM: $5='(concat $1 ": " (number-to-string (/ (+ (string-to-number $3) (string-to-number $4)) 2)))
122 Another idea is to use the =;L= flag: instead of using interpreting cells
123 as strings or numbers, it inserts their content literally in the Emacs lisp
124 expression. So the formula above can safely be replaced by this more
128 ,#+TBLFM: $5='(concat "$1" ": " (number-to-string (/ (+ $3 $4) 2)));L
131 Note the double-quotes around "$1": that's because inserting a first name
132 literally would mean "it is an Emacs lisp symbol". So, when using the =;L=
133 flag, adding double-quotes makes sure the reference is interpreted as a
138 * Example 2: manipulating ranges
140 Suppose now we have this table
142 | Col1 | Col2 | Col3 | Col4 | Col5 |
143 |------+------+----------------------------------+--------------+--------------|
144 | ? | ? | in Col1 and Col2 (no duplicates) | only in Col1 | only in Col2 |
145 | ? | ? | ... | ... | ... |
146 | ? | ? | ... | ... | ... |
148 =Col1= and =Col2= contain strings.
150 We want the first cell of the third column to contain a string with all the
151 strings that are in both =Col1= and =Col2=, with no duplicates. =Col4=
152 contains strings that are only in =Col1= (and not in =Col2=) whereas =Col5=
153 contains strings that are only in =Col2= (and not in =Col1=).
155 How to automate this with an Emacs lisp formula?
157 Let's first figure out the result we want for a simple table:
159 | Col1 | Col2 | Col3 | Col4 | Col5 |
160 |------+------+---------+------+------|
161 | a | a | a b c d | c | d |
166 Now let's get the values of the first column starting from the second row.
168 The "a" on the top left cell can be accessed through the reference
169 =@2$1=. The "c" on the bottom left cell can be accessed through the
170 reference =@5$1=. The range of cells can then be accessed with
173 Let's add this range in the first cell of =Col3=:
175 | Col1 | Col2 | Col3 | Col4 | Col5 |
176 |------+------+---------+------+------|
177 | a | a | a a b c | c | d |
181 #+TBLFM: @2$3='(mapconcat 'identity (list @2$1..@5$1) " ")
186 ,#+TBLFM: @2$3='(mapconcat 'identity (list @2$1..@5$1) " ")
191 When interpreted, the range =@2$1..@5$1= is replaced by the values of the
192 cells, separated with a space. So =(list @2$1..@5$1)= becomes =(list "a"
193 "a" "b" "c")= and the whole formula becomes
195 #+BEGIN_SRC emacs-lisp
196 '(mapconcat 'identity (list "a" "a" "b" "c") " ")
199 which basically means "Concatenate elements of ("a" "a" "b" "c") and add a
200 single space between each of them".
202 Let's generalize and say that we don't know how many rows our table
203 contains. The range =@2$1..@5$1= becomes =@2$1..@>$1= where =@>= means
204 "the last row" and =@>$1= "the last row in the first column".
206 Remember: we want the third column to contain a string with all the strings
207 that are in both =Col1= and =Col2=, with no duplicates. Let's first list
208 all values from =Col1= and =Col2= with =(list @2$1..@>$1 @2$2..@>$2)=, then
209 delete duplicates like this =(delete-dups (list @2$1..@>$1 @2$2..@>$2))=
210 then put this expression in the one we already have above.
213 ,#+TBLFM: @2$3='(mapconcat 'identity (delete-dups (list @2$1..@>$1 @2$2..@>$2)) " ")
216 | Col1 | Col2 | Col3 | Col4 | Col5 |
217 |------+------+---------+------+------|
218 | a | a | a b c d | ? | ? |
222 #+TBLFM: @2$3='(mapconcat 'identity (delete-dups (list @2$1..@>$1 @2$2..@>$2)) " ")
224 Okay. Now that you know how to manipulate ranges, you can replace the "?"
225 with the right formulas... remember: =Col4= contains strings that are only
226 in =Col1= and not in =Col2=, whereas =Col5= contains strings that are only
227 in =Col2= and not in =Col1=. (Hint: you can write your own functions and
228 use them in an Emacs lisp formula.)
230 Don't forget that you can edit a table's formulas with by hitting C-c '
231 anywhere on the table: it will open the formulas editor, which highlights
232 references that the cursor is on (both in the formulas editor and in the
233 table). The formulas editor is really handy when you need to check that
234 your references are correct. Also, hitting =TAB= on a formulas in this
235 editor will pretty-print the formula, which helps editing a lot !
239 Please check the Org manual for (condensed but accurate and up to date)
240 information on using Lisp as formulas: see the [[https://orgmode.org/manual/Formula-syntax-for-Lisp.html][manual online]] or as an [[info:org#Formula%20syntax%20for%20Lisp][Info
245 [fn:1] If you are reading this tutorial from within Emacs, go to the
246 spreadsheet section of the manual by clicking on this link:
247 [[info:org#The spreadsheet][the spreadsheet]].
249 [fn:2] Column formulas apply to the whole column, whereas Field formulas
250 only apply to the current field. Field formulas can be inserted by
251 pressing =C-u C-c == in a field.