1 ;;; org-R.el --- Computing and data visualisation in Org-mode using R
4 ;; Free Software Foundation, Inc.
6 ;; Author: Dan Davison <davison@stats.ox.ac.uk>
7 ;; Keywords: org, R, ESS, tables, graphics
8 ;; Homepage: http://www.stats.ox.ac.uk/~davison/software/org-R
9 ;; Version: 0.05 2009-02-05
11 ;; This file is not part of GNU Emacs.
13 ;; This file is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 3, or (at your option)
18 ;; This file is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
25 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
29 ;; This file allows R (http://www.r-project.org) code to be applied to
30 ;; emacs org-mode (http://orgmode.org) tables. When the result of the
31 ;; analysis is a vector or matrix, it is output back into the org-mode
32 ;; buffer as a new org table. Alternatively the R code may be used to
33 ;; plot the data in the org table. It requires R to be running in an
34 ;; inferior-ess-mode buffer (install Emacs Speaks Statistics
35 ;; http://ess.r-project.org and issue M-x R).
38 ;; The user interface is via two different options lines in the org
39 ;; buffer. As is conventional in org-mode, these are lines starting
40 ;; with `#+'. Lines starting with #+R: specify options in the
41 ;; standard org style (option:value) and are used to specify certain
42 ;; off-the-shelf transformations and plots of the table data. The
43 ;; #+R: line is also used to specify the data to be analysed
44 ;; (either an org table or a csv file), and to restrict the analysis
45 ;; to certain columns etc. In lines starting #+RR: you can supply
46 ;; literal R code, giving you full control over what you do with the
47 ;; table. With point in the first #+R line, M-x org-R-apply
48 ;; makes happen whatever has been specified in those lines.
50 ;; The best documentation is currently the Worg tutorial:
52 ;; http://orgmode.org/worg/org-tutorials/org-R/org-R.php
55 (defconst org-R-skeleton-funcall-1-arg
57 "Skeleton of a call to an R function.
58 E.g. barplot(x[,3:5], names.arg=rownames(x))")
60 (defconst org-R-skeleton-funcall-2-args
61 "%s(x[,%s], x[,%s]%s)"
62 "Skeleton of a call to an R function which can take x and y
65 (defconst org-R-write-org-table-def
66 "write.org.table <- function (x, write.rownames = TRUE)
68 if(!is.null(dim(x)) && length(dim(x)) > 2)
69 stop(\"Object must be 1- or 2-dimensional\") ;
70 if(is.vector(x) || is.table(x) || is.factor(x) || is.array(x))
72 if(!(is.matrix(x) || inherits(x, c('matrix', 'data.frame')))) {
75 stop(\"Object not recognised as 1- or 2-dimensional\") ;
77 if(is.null(colnames(x)))
78 colnames(x) <- rep('', ncol(x)) ;
80 x <- cbind(rownames(x), x) ;
81 cat('|', paste(colnames(x), collapse = ' | '), '|\\n') ;
82 cat('|', paste(rep('----', ncol(x)), collapse = '+'), '|\\n', sep = '') ;
83 invisible(apply(x, 1, function(row) cat('|', paste(row, collapse = ' | '), '|\\n'))) ;
85 "Definition of R function to write org table representation of R objects.
86 To see a more human-readable version of this, look at the code,
87 or type dput(write.org.table) RET at the R (inferior-ess-mode
90 (defun org-R-apply-maybe ()
93 (looking-at "#\\+RR?:"))
94 (progn (call-interactively 'org-R-apply
)
95 t
) ;; to signal that we took action
96 nil
)) ;; to signal that we did not
98 (add-hook 'org-ctrl-c-ctrl-c-hook
'org-R-apply-maybe
)
101 (defun org-R-apply ()
102 "Construct and evaluate an R function call.
103 Construct an R function corresponding to the #+R: and #+RR:
104 lines. R must be currently running in an inferior-ess-mode
105 buffer. The function evaluates any user-supplied R code in the
106 #+RR: line before the off-the-shelf actions specified in the #+R:
107 line. The user-supplied R code can operate on a variable called x
108 that is the org table represented as a data frame in R. Text
109 output from the R process may be inserted into the org buffer, as
110 an org table where appropriate."
115 (unless (looking-at "#\\+RR?:") (error "Point must be in a #+R or #+RR line"))
116 (while (looking-at "#\\+RR?:") (forward-line -
1))
118 ;; For the rest of the code in this file we are based at the
119 ;; beginning of the first #+R line
121 ;; FIXME: if point is at the beginning of the #+RR? lines when
122 ;; this function is called, then tabular output gets inserted,
123 ;; leaving point up at the top of the tabular output.
125 (let* ((options (org-R-get-options))
126 (code (org-R-construct-code options
))
127 (infile (plist-get options
:infile
))
128 (ext (if infile
(file-name-extension infile
)))
131 (if (string-equal ext
"csv")
132 (setq csv-file infile
)
135 (make-temp-file "org-R-tmp" nil
".csv") options
)))
137 (org-R-eval code csv-file options
)
139 (delete-other-windows) ;; FIXME
140 (if (plist-get options
:showcode
) (org-R-showcode code
)))))
142 (defun org-R-apply-throughout-subtree ()
143 "Call org-R-apply in every org-R block in current subtree."
144 ;; This currently relies on re-search-forward leaving point after
145 ;; the #+RR?: If point were at the beginning of the line, then
146 ;; tabular input would get inserted leaving point above the #+RR?:,
147 ;; and this would loop infinitely. Same for org-R-apply-to-buffer.
150 (org-back-to-heading)
151 (while (re-search-forward
153 (save-excursion (org-end-of-subtree)) t
)
156 (while (looking-at "#\\+RR?")
159 (defun org-R-apply-throughout-buffer ()
160 "Call org-R-apply in every org-R block in the buffer."
163 (goto-char (point-min))
164 (while (re-search-forward "^#\\+RR?:" nil t
)
167 (while (looking-at "#\\+RR?")
170 (defun org-R-construct-code (options)
171 "Construct the R function that implements the requested
173 The body of this function derives from two sources:
175 1. Explicit R code which is read from lines starting with
176 #+RR: by org-R-get-user-code, and
178 2. Off-the-shelf code corresponding to options specified in the
179 #+R: line. This code is constructed by
180 org-R-off-the-shelf-code."
181 (let ((user-code (org-R-get-user-code))
182 (action (plist-get options
:action
)))
184 (if (or (eq action
'tabulate
) (eq action
'transpose
))
185 (setq options
(plist-put options
:output-to-buffer t
)))
186 (format "function(x){%sx}"
188 (when user-code
(concat user-code
";"))
189 (when action
(concat (org-R-off-the-shelf-code options
) ";"))))))
191 (defun org-R-get-user-code (&optional R
)
192 "Read user-supplied R code from #+RR: lines."
193 (let ((case-fold-search t
))
195 (while (looking-at "^#\\+\\(RR?:\\) *\\(.*\\)")
196 (if (string= "RR:" (match-string 1))
197 (setq R
(concat R
(when R
";") (match-string 2))))
201 (defun org-R-off-the-shelf-code (options)
202 "Return R code implementing the actions requested in the
205 ;; This is a somewhat long function as it deals with several
206 ;; different cases, corresponding to all the off-the-shelf actions
207 ;; that have been implemented.
209 (let* ((action (plist-get options
:action
))
210 (cols (plist-get options
:columns
))
211 (ncols (org-R-number-of-columns cols
))
212 (nxcols (nth 0 ncols
))
213 (nycols (nth 1 ncols
))
214 (cols-R (org-R-make-index-vectors cols
))
215 (xcols-R (nth 0 cols-R
))
216 (ycols-R (nth 1 cols-R
))
217 seq args largs extra-code title colour matrix-index
)
219 ;; I want this to affect options outside this function. Will it
220 ;; necessarily do so? (not if plist-put adds to head of the
222 (setq options
(plist-put options
:nxcols nxcols
))
224 (cond ((eq action
'points
)
226 (setq options
(plist-put options
:lines nil
)))
229 (setq options
(plist-put options
:lines t
))))
231 (if (and (setq title
(plist-get options
:title
)) (symbolp title
))
232 (setq title symbol-name title
))
234 (setq args
(plist-put args
:main
(concat "\"" title
"\"")))
236 (if (setq colour
(or (plist-get options
:colour
)
237 (plist-get options
:color
)
238 (plist-get options
:col
)))
241 (concat "\"" (if (symbolp colour
) (symbol-name colour
) colour
) "\""))))
244 (if (setq legend
(plist-get options
:legend
))
246 (concat "\"" (if (symbolp legend
) (symbol-name legend
) legend
) "\""))
247 (plist-put largs
:x
"\"topright\"")))
251 ;; single set of columns; implicit x values
253 (setq xcols-R
"" matrix-index
"")
254 (setq matrix-index
(concat "," xcols-R
)))
257 ;;----------------------------------------------------------------------
259 ((eq action
'barplot
)
262 (setq args
(plist-put args
:names.arg
"rownames(x)"))
263 (setq args
(org-R-set-user-supplied-args args
(plist-get options
:args
)))
264 (format org-R-skeleton-funcall-1-arg
266 (concat ", " (org-R-plist-to-R-args args
))))
268 (setq args
(plist-put args
:names.arg
"colnames(x)"))
269 (setq args
(plist-put args
:col
"seq(nrow(x))"))
270 (setq args
(plist-put args
:beside
"TRUE"))
272 (setq largs
(plist-put largs
:bty
"\"n\""))
273 ;; (setq largs (plist-put largs :lwd 10))
274 (setq largs
(plist-put largs
:col
"seq(nrow(x))"))
275 (setq largs
(plist-put largs
:legend
"rownames(x)"))
277 (setq args
(org-R-set-user-supplied-args args
(plist-get options
:args
)))
279 (concat (format org-R-skeleton-funcall-1-arg
280 "barplot(as.matrix" matrix-index
281 (concat "), " (org-R-plist-to-R-args args
)))
282 "; legend(" (org-R-plist-to-R-args largs
) ")")))
284 ;;----------------------------------------------------------------------
286 ((eq action
'density
)
287 (if (and nxcols
(> nxcols
1))
288 (error "Multiple columns not implemented for action:%s" action
))
290 (setq args
(plist-put args
:xlab
(concat "colnames(x)["xcols-R
"]")))
291 (setq args
(org-R-set-user-supplied-args args
(plist-get options
:args
)))
293 (format org-R-skeleton-funcall-1-arg
294 "plot(density" matrix-index
295 (concat "), " (org-R-plist-to-R-args args
))))
297 ;;----------------------------------------------------------------------
300 (if (and nxcols
(> nxcols
1))
301 (error "Multiple columns not implemented for action:%s" action
))
302 (setq args
(plist-put args
:xlab
(concat "colnames(x)["xcols-R
"]")))
303 (setq args
(org-R-set-user-supplied-args args
(plist-get options
:args
)))
304 (setq args
(concat ", " (org-R-plist-to-R-args args
)))
305 (format org-R-skeleton-funcall-1-arg
"hist" matrix-index args
))
307 ;;----------------------------------------------------------------------
310 (format org-R-skeleton-funcall-1-arg
"image(as.matrix" matrix-index
")"))
312 ;;----------------------------------------------------------------------
315 (setq seq
(concat "seq_along("xcols-R
")"))
317 (setq args
(plist-put args
:type
(if (plist-get options
:lines
) "\"l\"" "\"p\"")))
318 (setq args
(plist-put args
:ylab
(concat "colnames(x)["xcols-R
"]")))
319 (setq args
(concat ", " (org-R-plist-to-R-args args
)))
321 (concat (format org-R-skeleton-funcall-1-arg
322 (if (eq nxcols
1) "plot" "matplot") matrix-index args
)
325 ;;----------------------------------------------------------------------
327 ((eq action
'tabulate
)
329 (if (plist-get options
:sort
)
330 (format org-R-skeleton-funcall-1-arg
331 "x <- sort(table" xcols-R
"), decreasing=TRUE")
332 (format org-R-skeleton-funcall-1-arg
"x <- table" matrix-index
""))
333 (if (eq nxcols
1) "; x <- data.frame(value=names(x), count=x[])")))
335 ;;----------------------------------------------------------------------
337 ((eq action
'transpose
)
338 (format org-R-skeleton-funcall-1-arg
"x <- t" matrix-index
""))
340 ;;----------------------------------------------------------------------
342 ;; Don't recognise action: option, try applying it as the name of an R function.
344 (t (format org-R-skeleton-funcall-1-arg
345 (concat "x <- " (symbol-name action
)) matrix-index
""))))
347 ;;----------------------------------------------------------------------
350 ;; x and y columns specified
353 ;;----------------------------------------------------------------------
356 (unless (eq nxcols
1) (error "Multiple x-columns not implemented for action:plot"))
361 (concat "if(length("ycols-R
") == 1) colnames(x)["ycols-R
"] else ''")))
362 (setq args
(plist-put args
:xlab
(concat "colnames(x)["xcols-R
"]")))
364 (setq args
(plist-put args
:type
(if (plist-get options
:lines
) "\"l\"" "\"p\"")))
366 (setq args
(concat ", " (org-R-plist-to-R-args args
)))
367 (setq seq
(concat "seq_along("ycols-R
")"))
369 (setq largs
(plist-put largs
:col seq
))
370 (setq largs
(plist-put largs
:lty seq
))
371 (setq largs
(plist-put largs
:bty
"\"n\""))
372 (setq largs
(plist-put largs
:legend
(concat "colnames(x)["ycols-R
"]")))
376 "if(length("ycols-R
") > 1) "
377 "legend(" (org-R-plist-to-R-args largs
) ")"))
379 (concat (format org-R-skeleton-funcall-2-args
380 (if (and (eq nxcols
1) (eq nycols
1)) "plot" "matplot")
381 xcols-R ycols-R args
)
384 ;;----------------------------------------------------------------------
386 (t (error "action:%s requires a single set of columns" (symbol-name action
))))))))
388 (defun org-R-set-user-supplied-args (args user-args
)
389 "Set user-supplied values in arguments plist."
390 (while (setq prop
(pop user-args
))
391 (setq args
(plist-put args prop
(pop user-args
))))
394 (defun org-R-plist-to-R-args (plist)
395 "Convert a plist into a string of R arguments."
396 (let (arg-string arg
)
397 (while (setq arg
(pop plist
))
398 (string-match ":\\(\.*\\)" (symbol-name arg
))
399 (setq arg
(match-string 1 (symbol-name arg
)))
402 (if arg-string
(concat arg-string
", "))
403 (format "%s=%s" arg
(pop plist
)))))
406 (defun org-R-alist-to-R-args (alist)
407 "Convert an alist of (argument . val) pairs into a string of R arguments.
408 The alist is something like
411 This isn't used, but it seems much nicer than
412 my plist equivalent. Is there a better way to write the plist
417 (mapcar (lambda(pair) (format "%s = %s" (car pair
) (cdr pair
))) alist
)
420 (defun org-R-make-index-vectors (cols)
421 "Construct R indexing vectors as strings from lisp form.
423 COLS is the lisp form given by the `columns:' option. It may
424 take the following forms:
426 1. integer atom - the number of the column
427 2. symbol/string atom - the name of the column
428 3. list of length 1 - same as 1 or 2 above
429 4. list of length > 1 - specification of multiple columns as 1 or 2 above, unless it is
430 5. list of 2 lists - each list specifies (possibly multiple) columns
432 In cases 1-4 this function returns a list of length 1, containing
433 the R index vector as a string. In case 5 this function returns a
434 list of two such index vectors.
436 In cases 1 - 4, when a bivariate plot is requested such as by
437 `action:lines', the x values are implicit, i.e
438 1,2,...,number-of-rows.
440 In case 4, an attempt is made to do something sensible with the
441 multiple columns, e.g. for `action:lines' they will be plotted
442 together on the same graph against the implicit x values, and for
443 `action:barplot' the bars corresponding to a single row will be
444 stacked on top of each other, or placed side by side, depending
445 on the value of the `beside' option.
447 For `action:tabulate', if 2 columns are selected, a
448 two-dimensional table is created. If more than 2, then the
449 appropriately dimensioned table is computed and inserted using
450 the standard text representation of multi-dimensional arrays used
451 by R (as org does not currently have tables of dimension > 2).
453 The straightforward case of case 5 is that both lists are of
454 length 1. For `action:plot' and `action:lines' these specify the
455 y and x coordinates of the points to be plotted or joined by
458 The intention is that `org-R-apply' does something
459 corresponding to what would happen if you did the following in R:
461 fun(x=tab[,xcols], y=tab[,ycols])
463 where fun is the R function implementing the desired
464 action (plotting/computation), tab is the org table, xcols are
465 the columns specified in cases 1-4 above, and ycols are the
466 second set of columns which might have been specified under case
467 5 above. For relevant R documentation see the help page
468 associated with the function xy.coords, e.g. by typing ?xy.coords
471 The following won't work with case 5: `tabulate'
473 (defun org-R-make-index-vector (cols)
474 "Return the R indexing vector (as a string) corresponding to
475 the lisp form COLS. In this function, COLS is a either a list of
476 atoms, or an atom, i.e. in the form of cases 1-4"
479 (unless (listp cols
) (setq cols
(list cols
)))
481 (cond ((car (mapcar 'symbolp cols
))
482 (lambda (symbol) (concat "\"" (symbol-name symbol
) "\"")))
483 ((car (mapcar 'integerp cols
))
485 ((car (mapcar 'stringp cols
))
486 (lambda (string) (concat "\"" string
"\"")))
487 (t (error "Column selection should be symbol, integer or string: %S" cols
))))
488 (concat (when (> (length cols
) 1) "c(")
489 (mapconcat to-stringf cols
",")
490 (when (> (length cols
) 1) ")")))))
492 (if (and (listp cols
) (listp (car cols
)))
493 (mapcar 'org-R-make-index-vector cols
) ;; case 5
494 (list (org-R-make-index-vector cols
)))) ;; other cases
496 (defun org-R-number-of-columns (cols)
497 (defun f (c) (if (listp c
) (length c
) 1))
498 (if (and (listp cols
) (listp (car cols
)))
502 (defun org-R-eval (R-function csv-file options
)
503 "Apply an R function to tabular data and receive output as an org table.
505 R-FUNCTION is a string; it may be simply the name of an
506 appropriate R function (e.g. \"summary\", \"plot\"), or a
507 user-defined anonymous function of the form
508 \"(function(data.frame) {...})\". It will receive as its first
509 argument the org table as an R 'data frame' -- a table-like
510 structure which can have columns containing different types of
511 data -- numeric, character etc.
513 The R function may produce graphical and/or text output. If it
514 produces text output, and the replace:t is specified, and if
515 there is a table immediately above the #+R lines, then it is
516 replaced by the text output. Otherwise the text output is
517 inserted above the #+R lines.
519 (let ((transit-buffer "org-R-transit")
520 (infile (plist-get options
:infile
))
521 (output-file (plist-get options
:outfile
))
522 (title (plist-get options
:title
))
523 output-format graphics-output-file width height
)
525 (unless (not output-file
)
526 ;; We are writing output to file. Determine file format and
527 ;; location, and open graphics device if necessary.
529 "\\(.*\.\\)?\\(org\\|png\\|jpg\\|jpeg\\|pdf\\|ps\\|bmp\\|tiff\\)$"
531 (setq output-format
(match-string 2 output-file
))
532 (error "Did not recognise file name suffix %s as available output format"
533 (match-string 2 output-file
)))
534 (unless (match-string 1 output-file
)
535 ;; only suffix provided: store in org-attach dir
536 (require 'org-attach
)
537 (let ((temporary-file-directory (org-attach-dir t
)))
540 "org-R-output-" nil
(concat "." output-format
)))))
541 (if (eq output-format
"jpg") (setq output-format
"jpeg"))
542 (setq graphics-output-file
(not (string-equal output-format
"org")))
543 (if graphics-output-file
;; open the graphics device
545 (concat output-format
"(file=\"" output-file
"\""
546 (if (setq width
(plist-get options
:width
))
547 (format ", width=%d" width
))
548 (if (setq height
(plist-get options
:height
))
549 (format ", height=%d" height
)) ")"))))
551 ;; Apply R code to table (which is now stored as a csv file)
552 ;; does it matter whether this uses ess-command or ess-execute?
554 ;; First evaluate function definition for R -> org table conversion
555 (ess-execute (replace-regexp-in-string "\n" " " org-R-write-org-table-def
)
558 ;; FIXME: why not eval the function def together with the function call
559 ;; as in the commented out line below (it didn't work for some reason)
562 ;; (replace-regexp-in-string "\n" " " org-R-write-org-table-def) ";"
563 (org-R-make-expr R-function csv-file options
)) nil transit-buffer
)
566 (set-buffer (concat "*" transit-buffer
"*"))
567 (unless (or (looking-at "$")
568 (string-equal (buffer-substring-no-properties 1 2) "|"))
569 (error "Error in R evaluation:\n%s" (buffer-string))))
574 (string-equal (file-name-extension infile
) "csv"))
575 (delete-file csv-file
)))
577 (if graphics-output-file
(ess-execute "dev.off()")) ;; Close graphics device
579 (unless (or graphics-output-file
580 (not (plist-get options
:output-to-buffer
)))
581 ;; Send tabular output to a org buffer as new org
582 ;; table. Recall that we are currently at the beginning of the
584 (if (and output-file graphics-output-file
)
585 (error "output-to-buffer and graphics-output-file both t"))
589 (progn (set-buffer (find-file-noselect output-file
))
590 (delete-region (point-min) (point-max)))
591 (if (plist-get options
:replace
)
592 (progn ;; kill a table iff in one or one ends on the previous line
593 (delete-region (org-table-begin) (org-table-end))
596 (if (looking-at "#\\+TBLNAME")
597 (delete-region (point) (1+ (point-at-eol))))))))
598 (if title
(insert "#+TBLNAME:" title
"\n"))
599 (insert-buffer-substring (concat "*" transit-buffer
"*"))
601 (if output-file
(save-buffer))))
603 ;; We might be linking to graphical output, or to org output in
604 ;; another file. Either way, point is still at the beginning of
605 ;; the first #+R line.
606 (unless (not output-file
)
609 (if (looking-at "\\[\\[file:")
610 (delete-region (point) (1+ (point-at-eol)))))
611 (insert (org-make-link-string
612 (concat "file:" output-file
)
613 (unless (plist-get options
:inline
)
614 (or title
(concat output-format
" output")))) "\n"))
616 (kill-buffer (concat "*" transit-buffer
"*"))))
619 (defun org-R-export-to-csv (csv-file options
)
620 "Find and export org table to csv.
622 If the intable: option has not been supplied, then the table must
623 end on the line immediately above the #+R lines. Otherwise,
624 the remote table referenced by the intable: option is found using
625 org-R-find-table. If options:infile has been set then this is the
626 org file containing the table. See the docstring of
627 org-R-find-table for details."
628 (let ((tbl-name-or-id (plist-get options
:intable
))
629 (org-file (plist-get options
:infile
)) tbl-marker
)
632 (not (string-equal (file-name-extension org-file
) "org")))
633 (error "File %s extension is not .csv so should be .org"))
637 ;; a remote table has been specified -- move into it
639 (if org-file
(set-buffer (find-file-noselect org-file
)))
640 (setq tbl-marker
(org-R-find-table tbl-name-or-id
'marker
))
641 (set-buffer (marker-buffer tbl-marker
))
642 (goto-char (marker-position tbl-marker
)))
643 (forward-line -
1)) ;; move into table above
644 (if (looking-at "[ \t]*|")
645 (progn (org-table-export csv-file
"orgtbl-to-csv") csv-file
)
648 (defun org-R-find-table (name-or-id &optional markerp
)
649 "Return location of a table.
651 NAME-OR-ID may be the name of a
652 table in the current file as set by a \"#+TBLNAME:\" directive.
653 The first table following this line will then be used.
654 Alternatively, it may be an ID referring to any entry, perhaps in
655 a different file. In this case, the first table in that entry
656 will be referenced. The location is returned as a marker pointing
657 to the beginning of the first line of the table.
659 This is taken from the first part of org-table-get-remote-range
663 ((symbolp name-or-id
) (setq name-or-id
(symbol-name name-or-id
)))
664 ((numberp name-or-id
) (setq name-or-id
(number-to-string name-or-id
))))
666 (let ((id-loc nil
) (case-fold-search t
) buffer loc
)
671 (goto-char (point-min))
672 (if (re-search-forward
673 (concat "^#\\+TBLNAME:[ \t]*" (regexp-quote name-or-id
) "[ \t]*$")
675 ;; OK, we've found a matching table name in this buffer.
676 (setq buffer
(current-buffer) loc
(match-beginning 0))
677 ;; It's not a table name in this buffer. It must be an entry id.
678 ;; obtain a marker pointing to it.
679 (setq id-loc
(org-id-find name-or-id
'marker
)
680 buffer
(marker-buffer id-loc
)
681 loc
(marker-position id-loc
))
682 (move-marker id-loc nil
))) ;; disable the marker
683 ;; (switch-to-buffer buffer)
685 ;; OK, so now we're in the right buffer, and loc is either
686 ;; the beginning of the #+TBLNAME line, or the location of the entry
687 ;; either way we need to search forward to get to the beginning of the table
693 ;; The following regexp search finds the beginning of
694 ;; the next table in this entry. If it gets to the next
695 ;; entry before the next table, then it signals failure.
696 (unless (and (re-search-forward "^\\(\\*+ \\)\\|[ \t]*|" nil t
)
697 (not (match-beginning 1)))
698 (error "Cannot find a table at NAME or ID %s" name-or-id
))
700 (move-marker (make-marker) (point-at-bol) (current-buffer))
701 (error "Option to return cons cell not implemented.
702 It should return (file-name . position) to be
703 consistent with functions in org-id.el")))))))))
705 (defun org-R-make-expr (R-function csv-file options
)
706 "Construct R code to read data, analyse it and write output."
708 (let ((rownames (plist-get options
:rownames
))
709 (colnames (plist-get options
:colnames
))
710 (action (plist-get options
:action
))
711 (replace (plist-get options
:replace
)))
713 (if (and csv-file
(symbolp csv-file
))
714 (setq csv-file
(symbol-name csv-file
)))
716 (format "write.org.table((%s)(%s), write.rownames=%s)"
720 "read.csv(\"%s\", header=%s, row.names=%s)"
723 ;; Do we treat first row as colnames? Yes by default
724 ;; FIXME: should really check for hline
725 (if colnames
"TRUE" "FALSE")
727 ;; Do we use a column as rownames? Not unless rownames: is specified
728 (if rownames
"1" "NULL"))
731 ;; Do we write rownames into org table?
732 (cond ((eq action
'tabulate
)
733 (if (eq (plist-get options
:nxcols
) 1) "FALSE" "TRUE"))
734 ((eq action
'transpose
) (if colnames
"TRUE" "FALSE"))
738 (defun org-R-get-options ()
739 "Parse the #+R: lines and return the options and values as a p-list."
742 (:intable .
"intable")
743 (:rownames .
"rownames")
744 (:colnames .
"colnames")
745 (:columns .
"columns")
750 (:outfile .
"outfile")
751 (:replace .
"replace")
763 (:output-to-buffer .
"output-to-buffer")
765 (:showcode .
"showcode")))
766 (regexp ":\\(\"[^\"]*\"\\|(([^)]*) *([^)]*))\\|([^)]*)\\|[^ \t\n\r;,.]*\\)")
767 (case-fold-search t
) p
)
769 ;; FIXME: set default options properly
770 (setq p
(plist-put p
:output-to-buffer t
)) ;; FIXME: hack: null options plist is bad news
771 (setq p
(plist-put p
:replace t
))
772 (setq p
(plist-put p
:rownames nil
))
773 (setq p
(plist-put p
:colnames t
))
774 (setq p
(plist-put p
:inline nil
))
777 (while (looking-at "^#\\+\\(RR?:+\\) *\\(.*\\)")
778 (if (string= "R:" (match-string 1))
779 (setq p
(org-R-add-options-to-plist p
(match-string 2) opts regexp
)))
783 (defun org-R-add-options-to-plist (p opt-string op regexp
)
784 "Parse a #+R: line and set values in the property list p.
785 This function is adapted from similar functions in org-exp.el
786 and org-plot.el. It might be a good idea to have a single
787 function serving these three files' needs."
788 ;; Adapted from org-exp.el and org-plot.el
791 (while (setq o
(pop op
))
793 (concat (regexp-quote (cdr o
)) regexp
)
795 (setq p
(plist-put p
(car o
)
796 (car (read-from-string
797 (match-string 1 opt-string
)))))))))
801 (defun org-R-sanitise-options (options)
802 (error "not used yet")
803 (let (should-be-strings '(title legend colour color col csv
)))
805 (defun org-R-showcode (R)
806 "Display R function constructed by org-R in a new R-mode
808 (split-window-vertically)
809 (switch-to-buffer "*org-table.R*")
810 (kill-region (point-min) (point-max))
812 (insert (replace-regexp-in-string
813 ";" "\n" (replace-regexp-in-string "\\([{}]\\)" "\n\\1\n" R
)))
814 ;; (mark-whole-buffer)
816 ;; why doesn't that do what I hoped?
819 (defun org-R-get-remote-range (name-or-id form
)
820 "Get a field value or a list of values in a range from table at ID.
822 This is a refactoring of Carsten's original version. I have
823 extracted the first bit of his function and named it
824 org-R-find-table (which would presumably be called something like
825 org-table-find-table or org-id-find-table if this were accepted).
829 Get a field value or a list of values in a range from table at ID.
831 NAME-OR-ID may be the name of a table in the current file as set by
832 a \"#+TBLNAME:\" directive. The first table following this line
833 will then be used. Alternatively, it may be an ID referring to
834 any entry, possibly in a different file. In this case, the first table
835 in that entry will be referenced.
836 FORM is a field or range descriptor like \"@2$3\" or or \"B3\" or
837 \"@I$2..@II$2\". All the references must be absolute, not relative.
839 The return value is either a single string for a single field, or a
840 list of the fields in the rectangle."
842 (let ((tbl-marker (org-R-find-table name-or-id
'marker
))
843 org-table-column-names org-table-column-name-regexp
844 org-table-local-parameters org-table-named-field-locations
845 org-table-current-line-types org-table-current-begin-line
846 org-table-current-begin-pos org-table-dlines
847 org-table-hlines org-table-last-alignment
848 org-table-last-column-widths org-table-last-alignment
849 org-table-last-column-widths tbeg
)
852 (set-buffer (marker-buffer tbl-marker
))
853 (goto-char (marker-position tbl-marker
))
854 (org-table-get-specials)
855 (setq form
(org-table-formula-substitute-names form
))
856 (if (and (string-match org-table-range-regexp form
)
857 (> (length (match-string 0 form
)) 1))
859 (org-table-get-range (match-string 0 form
) (point) 1))