org-R.el 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. ;;; org-R.el --- Computing and data visualisation in Org-mode using R
  2. ;; Copyright (C) 2009
  3. ;; Free Software Foundation, Inc.
  4. ;; Author: Dan Davison <davison@stats.ox.ac.uk>
  5. ;; Keywords: org, R, ESS, tables, graphics
  6. ;; Homepage: http://www.stats.ox.ac.uk/~davison/software/org-R
  7. ;; Version: 0.06 2009-04-15
  8. ;;
  9. ;; This file is not part of GNU Emacs.
  10. ;;
  11. ;; This file is free software; you can redistribute it and/or modify
  12. ;; it under the terms of the GNU General Public License as published by
  13. ;; the Free Software Foundation; either version 3, or (at your option)
  14. ;; any later version.
  15. ;; This file is distributed in the hope that it will be useful,
  16. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;; GNU General Public License for more details.
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  21. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  22. ;;
  23. ;;; Commentary:
  24. ;; This file allows R (http://www.r-project.org) code to be applied to
  25. ;; emacs org-mode (http://orgmode.org) tables. When the result of the
  26. ;; analysis is a vector or matrix, it is output back into the org-mode
  27. ;; buffer as a new org table. Alternatively the R code may be used to
  28. ;; plot the data in the org table. It requires R to be running in an
  29. ;; inferior-ess-mode buffer (install Emacs Speaks Statistics
  30. ;; http://ess.r-project.org and issue M-x R).
  31. ;;
  32. ;;
  33. ;; The user interface is via two different options lines in the org
  34. ;; buffer. As is conventional in org-mode, these are lines starting
  35. ;; with `#+'. Lines starting with #+R: specify options in the
  36. ;; standard org style (option:value) and are used to specify certain
  37. ;; off-the-shelf transformations and plots of the table data. The
  38. ;; #+R: line is also used to specify the data to be analysed
  39. ;; (either an org table or a csv file), and to restrict the analysis
  40. ;; to certain columns etc. In lines starting #+RR: you can supply
  41. ;; literal R code, giving you full control over what you do with the
  42. ;; table. With point in the first #+R line, M-x org-R-apply
  43. ;; makes happen whatever has been specified in those lines.
  44. ;; The documentation is currently the Worg tutorial:
  45. ;;
  46. ;; http://orgmode.org/worg/org-tutorials/org-R/org-R.php
  47. ;;
  48. ;; changelog:
  49. ;; 2009-04-05 two bug fixes in org-R-eval contributed by David Moffat
  50. ;; 2009-05-15 added lwd argument to matplot because it doesn't respect global par settings
  51. ;; 2009-05-15 uncommented set-buffer to transit buffer in org-eval (why was it commented?)
  52. (defconst org-R-skeleton-funcall-1-arg
  53. "%s(x[%s]%s)"
  54. "Skeleton of a call to an R function.
  55. E.g. barplot(x[,3:5], names.arg=rownames(x))")
  56. (defconst org-R-skeleton-funcall-2-args
  57. "%s(x[,%s], x[,%s]%s)"
  58. "Skeleton of a call to an R function which can take x and y
  59. args.")
  60. (defconst org-R-write-org-table-def
  61. "write.org.table <- function (x, write.rownames = TRUE)
  62. {
  63. if(!is.null(dim(x)) && length(dim(x)) > 2)
  64. stop(\"Object must be 1- or 2-dimensional\") ;
  65. if(is.vector(x) || is.table(x) || is.factor(x) || is.array(x))
  66. x <- as.matrix(x) ;
  67. if(!(is.matrix(x) || inherits(x, c('matrix', 'data.frame')))) {
  68. invisible() ;
  69. print(x) ;
  70. stop(\"Object not recognised as 1- or 2-dimensional\") ;
  71. } ;
  72. if(is.null(colnames(x)))
  73. colnames(x) <- rep('', ncol(x)) ;
  74. if(write.rownames)
  75. x <- cbind(rownames(x), x) ;
  76. cat('|', paste(colnames(x), collapse = ' | '), '|\\n') ;
  77. cat('|', paste(rep('----', ncol(x)), collapse = '+'), '|\\n', sep = '') ;
  78. invisible(apply(x, 1, function(row) cat('|', paste(row, collapse = ' | '), '|\\n'))) ;
  79. }"
  80. "Definition of R function to write org table representation of R objects.
  81. To see a more human-readable version of this, look at the code,
  82. or type dput(write.org.table) RET at the R (inferior-ess-mode
  83. buffer) prompt.")
  84. (defun org-R-apply-maybe ()
  85. (if (save-excursion
  86. (beginning-of-line 1)
  87. (looking-at "#\\+RR?:"))
  88. (progn (call-interactively 'org-R-apply)
  89. t) ;; to signal that we took action
  90. nil)) ;; to signal that we did not
  91. (add-hook 'org-ctrl-c-ctrl-c-hook 'org-R-apply-maybe)
  92. (defun org-R-apply ()
  93. "Construct and evaluate an R function call.
  94. Construct an R function corresponding to the #+R: and #+RR:
  95. lines. R must be currently running in an inferior-ess-mode
  96. buffer. The function evaluates any user-supplied R code in the
  97. #+RR: line before the off-the-shelf actions specified in the #+R:
  98. line. The user-supplied R code can operate on a variable called x
  99. that is the org table represented as a data frame in R. Text
  100. output from the R process may be inserted into the org buffer, as
  101. an org table where appropriate."
  102. (interactive)
  103. (require 'ess)
  104. (save-excursion
  105. (beginning-of-line)
  106. (unless (looking-at "#\\+RR?:") (error "Point must be in a #+R or #+RR line"))
  107. (while (looking-at "#\\+RR?:") (forward-line -1))
  108. (forward-line)
  109. ;; For the rest of the code in this file we are based at the
  110. ;; beginning of the first #+R line
  111. ;; FIXME: if point is at the beginning of the #+RR? lines when
  112. ;; this function is called, then tabular output gets inserted,
  113. ;; leaving point up at the top of the tabular output.
  114. (let* ((options (org-R-get-options))
  115. (code (org-R-construct-code options))
  116. (infile (plist-get options :infile))
  117. (ext (if infile (file-name-extension infile)))
  118. csv-file)
  119. (if (string-equal ext "csv")
  120. (setq csv-file infile)
  121. (setq csv-file
  122. (org-R-export-to-csv
  123. (make-temp-file "org-R-tmp" nil ".csv") options)))
  124. (org-R-eval code csv-file options)
  125. (delete-other-windows) ;; FIXME
  126. (if (plist-get options :showcode) (org-R-showcode code)))))
  127. (defun org-R-apply-throughout-subtree ()
  128. "Call org-R-apply in every org-R block in current subtree."
  129. ;; This currently relies on re-search-forward leaving point after
  130. ;; the #+RR?: If point were at the beginning of the line, then
  131. ;; tabular input would get inserted leaving point above the #+RR?:,
  132. ;; and this would loop infinitely. Same for org-R-apply-to-buffer.
  133. (interactive)
  134. (save-excursion
  135. (org-back-to-heading)
  136. (while (re-search-forward
  137. "^#\\+RR?:"
  138. (save-excursion (org-end-of-subtree)) t)
  139. (org-R-apply)
  140. (forward-line)
  141. (while (looking-at "#\\+RR?")
  142. (forward-line)))))
  143. (defun org-R-apply-throughout-buffer ()
  144. "Call org-R-apply in every org-R block in the buffer."
  145. (interactive)
  146. (save-excursion
  147. (goto-char (point-min))
  148. (while (re-search-forward "^#\\+RR?:" nil t)
  149. (org-R-apply)
  150. (forward-line)
  151. (while (looking-at "#\\+RR?")
  152. (forward-line)))))
  153. (defun org-R-construct-code (options)
  154. "Construct the R function that implements the requested
  155. behaviour.
  156. The body of this function derives from two sources:
  157. 1. Explicit R code which is read from lines starting with
  158. #+RR: by org-R-get-user-code, and
  159. 2. Off-the-shelf code corresponding to options specified in the
  160. #+R: line. This code is constructed by
  161. org-R-off-the-shelf-code."
  162. (let ((user-code (org-R-get-user-code))
  163. (action (plist-get options :action)))
  164. (if (or (eq action 'tabulate) (eq action 'transpose))
  165. (setq options (plist-put options :output-to-buffer t)))
  166. (format "function(x){%sx}"
  167. (concat
  168. (when user-code (concat user-code ";"))
  169. (when action (concat (org-R-off-the-shelf-code options) ";"))))))
  170. (defun org-R-get-user-code (&optional R)
  171. "Read user-supplied R code from #+RR: lines."
  172. (let ((case-fold-search t))
  173. (save-excursion
  174. (while (looking-at "^#\\+\\(RR?:\\) *\\(.*\\)")
  175. (if (string= "RR:" (match-string 1))
  176. (setq R (concat R (when R ";") (match-string 2))))
  177. (forward-line))))
  178. R)
  179. (defun org-R-off-the-shelf-code (options)
  180. "Return R code implementing the actions requested in the
  181. #+R: lines."
  182. ;; This is a somewhat long function as it deals with several
  183. ;; different cases, corresponding to all the off-the-shelf actions
  184. ;; that have been implemented.
  185. (let* ((action (plist-get options :action))
  186. (cols (plist-get options :columns))
  187. (ncols (org-R-number-of-columns cols))
  188. (nxcols (nth 0 ncols))
  189. (nycols (nth 1 ncols))
  190. (cols-R (org-R-make-index-vectors cols))
  191. (xcols-R (nth 0 cols-R))
  192. (ycols-R (nth 1 cols-R))
  193. seq args largs extra-code title colour matrix-index)
  194. ;; I want this to affect options outside this function. Will it
  195. ;; necessarily do so? (not if plist-put adds to head of the
  196. ;; plist?)
  197. (setq options (plist-put options :nxcols nxcols))
  198. (cond ((eq action 'points)
  199. (setq action 'plot)
  200. (setq options (plist-put options :lines nil)))
  201. ((eq action 'lines)
  202. (setq action 'plot)
  203. (setq options (plist-put options :lines t))))
  204. (if (and (setq title (plist-get options :title)) (symbolp title))
  205. (setq title symbol-name title))
  206. (setq args (plist-put args :main (concat "\"" title "\"")))
  207. (if (setq colour (or (plist-get options :colour)
  208. (plist-get options :color)
  209. (plist-get options :col)))
  210. (setq args
  211. (plist-put args :col
  212. (concat "\"" (if (symbolp colour) (symbol-name colour) colour) "\""))))
  213. (setq largs
  214. (if (setq legend (plist-get options :legend))
  215. (plist-put largs :x
  216. (concat "\"" (if (symbolp legend) (symbol-name legend) legend) "\""))
  217. (plist-put largs :x "\"topright\"")))
  218. (cond
  219. ((null ycols-R)
  220. ;; single set of columns; implicit x values
  221. (if (null xcols-R)
  222. (setq xcols-R "" matrix-index "")
  223. (setq matrix-index (concat "," xcols-R)))
  224. (cond
  225. ;;----------------------------------------------------------------------
  226. ((eq action 'barplot)
  227. (if (eq nxcols 1)
  228. (progn
  229. (setq args (plist-put args :names.arg "rownames(x)"))
  230. (setq args (org-R-set-user-supplied-args args (plist-get options :args)))
  231. (format org-R-skeleton-funcall-1-arg
  232. "barplot" xcols-R
  233. (concat ", " (org-R-plist-to-R-args args))))
  234. (setq args (plist-put args :names.arg "colnames(x)"))
  235. (setq args (plist-put args :col "seq(nrow(x))"))
  236. (setq args (plist-put args :beside "TRUE"))
  237. (setq largs (plist-put largs :bty "\"n\""))
  238. ;; (setq largs (plist-put largs :lwd 10))
  239. (setq largs (plist-put largs :col "seq(nrow(x))"))
  240. (setq largs (plist-put largs :legend "rownames(x)"))
  241. (setq args (org-R-set-user-supplied-args args (plist-get options :args)))
  242. (concat (format org-R-skeleton-funcall-1-arg
  243. "barplot(as.matrix" matrix-index
  244. (concat "), " (org-R-plist-to-R-args args)))
  245. "; legend(" (org-R-plist-to-R-args largs) ")")))
  246. ;;----------------------------------------------------------------------
  247. ((eq action 'density)
  248. (if (and nxcols (> nxcols 1))
  249. (error "Multiple columns not implemented for action:%s" action))
  250. (setq args (plist-put args :xlab (concat "colnames(x)["xcols-R"]")))
  251. (setq args (org-R-set-user-supplied-args args (plist-get options :args)))
  252. (format org-R-skeleton-funcall-1-arg
  253. "plot(density" matrix-index
  254. (concat "), " (org-R-plist-to-R-args args))))
  255. ;;----------------------------------------------------------------------
  256. ((eq action 'hist)
  257. (if (and nxcols (> nxcols 1))
  258. (error "Multiple columns not implemented for action:%s" action))
  259. (setq args (plist-put args :xlab (concat "colnames(x)["xcols-R"]")))
  260. (setq args (org-R-set-user-supplied-args args (plist-get options :args)))
  261. (setq args (concat ", " (org-R-plist-to-R-args args)))
  262. (format org-R-skeleton-funcall-1-arg "hist" matrix-index args))
  263. ;;----------------------------------------------------------------------
  264. ((eq action 'image)
  265. (format org-R-skeleton-funcall-1-arg "image(as.matrix" matrix-index ")"))
  266. ;;----------------------------------------------------------------------
  267. ((eq action 'plot)
  268. (setq R-fun (if (eq nxcols 1) "plot" "matplot"))
  269. (setq seq (concat "seq_along("xcols-R")"))
  270. (setq args (plist-put args :type (if (plist-get options :lines) "\"l\"" "\"p\"")))
  271. (setq args (plist-put args :ylab (concat "colnames(x)["xcols-R"]")))
  272. (if (string-equal R-fun "matplot")
  273. (setq args (plist-put args :lwd "par(\"lwd\")")))
  274. (setq args (concat ", " (org-R-plist-to-R-args args)))
  275. (concat
  276. (format org-R-skeleton-funcall-1-arg R-fun matrix-index args)
  277. extra-code))
  278. ;;----------------------------------------------------------------------
  279. ((eq action 'tabulate)
  280. (concat
  281. (if (plist-get options :sort)
  282. (format org-R-skeleton-funcall-1-arg
  283. "x <- sort(table" xcols-R "), decreasing=TRUE")
  284. (format org-R-skeleton-funcall-1-arg "x <- table" matrix-index ""))
  285. (if (eq nxcols 1) "; x <- data.frame(value=names(x), count=x[])")))
  286. ;;----------------------------------------------------------------------
  287. ((eq action 'transpose)
  288. (format org-R-skeleton-funcall-1-arg "x <- t" matrix-index ""))
  289. ;;----------------------------------------------------------------------
  290. ;; Don't recognise action: option, try applying it as the name of an R function.
  291. (t (format org-R-skeleton-funcall-1-arg
  292. (concat "x <- " (symbol-name action)) matrix-index ""))))
  293. ;;----------------------------------------------------------------------
  294. (ycols-R
  295. ;; x and y columns specified
  296. (cond
  297. ;;----------------------------------------------------------------------
  298. ((eq action 'plot)
  299. (unless (eq nxcols 1) (error "Multiple x-columns not implemented for action:plot"))
  300. (setq R-fun (if (and (eq nxcols 1) (eq nycols 1)) "plot" "matplot"))
  301. (setq args
  302. (plist-put
  303. args :ylab
  304. (concat "if(length("ycols-R") == 1) colnames(x)["ycols-R"] else ''")))
  305. (setq args (plist-put args :xlab (concat "colnames(x)["xcols-R"]")))
  306. (if (string-equal R-fun "matplot") ;; matplot doesn't respect par()$lwd
  307. (setq args (plist-put args :lwd "par(\"lwd\")")))
  308. (setq args (plist-put args :type (if (plist-get options :lines) "\"l\"" "\"p\"")))
  309. (setq args (concat ", " (org-R-plist-to-R-args args)))
  310. (setq seq (concat "seq_along("ycols-R")"))
  311. (setq largs (plist-put largs :col seq))
  312. (setq largs (plist-put largs :lty seq))
  313. (setq largs (plist-put largs :bty "\"n\""))
  314. (setq largs (plist-put largs :legend (concat "colnames(x)["ycols-R"]")))
  315. (setq extra-code
  316. (concat "; "
  317. "if(length("ycols-R") > 1) "
  318. "legend(" (org-R-plist-to-R-args largs) ")"))
  319. (concat
  320. (format org-R-skeleton-funcall-2-args R-fun xcols-R ycols-R args)
  321. extra-code))
  322. ;;----------------------------------------------------------------------
  323. (t (error "action:%s requires a single set of columns" (symbol-name action))))))))
  324. (defun org-R-set-user-supplied-args (args user-args)
  325. "Set user-supplied values in arguments plist."
  326. (while (setq prop (pop user-args))
  327. (setq args (plist-put args prop (pop user-args))))
  328. args)
  329. (defun org-R-plist-to-R-args (plist)
  330. "Convert a plist into a string of R arguments."
  331. (let (arg-string arg)
  332. (while (setq arg (pop plist))
  333. (string-match ":\\(\.*\\)" (symbol-name arg))
  334. (setq arg (match-string 1 (symbol-name arg)))
  335. (setq arg-string
  336. (concat
  337. (if arg-string (concat arg-string ", "))
  338. (format "%s=%s" arg (pop plist)))))
  339. arg-string))
  340. (defun org-R-alist-to-R-args (alist)
  341. "Convert an alist of (argument . val) pairs into a string of R arguments.
  342. The alist is something like
  343. '((arg1 . 1)
  344. (arg2 . a))
  345. This isn't used, but it seems much nicer than
  346. my plist equivalent. Is there a better way to write the plist
  347. version?
  348. "
  349. (mapconcat
  350. 'identity
  351. (mapcar (lambda(pair) (format "%s = %s" (car pair) (cdr pair))) alist)
  352. ", "))
  353. (defun org-R-make-index-vectors (cols)
  354. "Construct R indexing vectors as strings from lisp form.
  355. COLS is the lisp form given by the `columns:' option. It may
  356. take the following forms:
  357. 1. integer atom - the number of the column
  358. 2. symbol/string atom - the name of the column
  359. 3. list of length 1 - same as 1 or 2 above
  360. 4. list of length > 1 - specification of multiple columns as 1 or 2 above, unless it is
  361. 5. list of 2 lists - each list specifies (possibly multiple) columns
  362. In cases 1-4 this function returns a list of length 1, containing
  363. the R index vector as a string. In case 5 this function returns a
  364. list of two such index vectors.
  365. In cases 1 - 4, when a bivariate plot is requested such as by
  366. `action:lines', the x values are implicit, i.e
  367. 1,2,...,number-of-rows.
  368. In case 4, an attempt is made to do something sensible with the
  369. multiple columns, e.g. for `action:lines' they will be plotted
  370. together on the same graph against the implicit x values, and for
  371. `action:barplot' the bars corresponding to a single row will be
  372. stacked on top of each other, or placed side by side, depending
  373. on the value of the `beside' option.
  374. For `action:tabulate', if 2 columns are selected, a
  375. two-dimensional table is created. If more than 2, then the
  376. appropriately dimensioned table is computed and inserted using
  377. the standard text representation of multi-dimensional arrays used
  378. by R (as org does not currently have tables of dimension > 2).
  379. The straightforward case of case 5 is that both lists are of
  380. length 1. For `action:plot' and `action:lines' these specify the
  381. y and x coordinates of the points to be plotted or joined by
  382. lines.
  383. The intention is that `org-R-apply' does something
  384. corresponding to what would happen if you did the following in R:
  385. fun(x=tab[,xcols], y=tab[,ycols])
  386. where fun is the R function implementing the desired
  387. action (plotting/computation), tab is the org table, xcols are
  388. the columns specified in cases 1-4 above, and ycols are the
  389. second set of columns which might have been specified under case
  390. 5 above. For relevant R documentation see the help page
  391. associated with the function xy.coords, e.g. by typing ?xy.coords
  392. at the R prompt.
  393. The following won't work with case 5: `tabulate'
  394. "
  395. (defun org-R-make-index-vector (cols)
  396. "Return the R indexing vector (as a string) corresponding to
  397. the lisp form COLS. In this function, COLS is a either a list of
  398. atoms, or an atom, i.e. in the form of cases 1-4"
  399. (when cols
  400. (let (to-stringf)
  401. (unless (listp cols) (setq cols (list cols)))
  402. (setq to-stringf
  403. (cond ((car (mapcar 'symbolp cols))
  404. (lambda (symbol) (concat "\"" (symbol-name symbol) "\"")))
  405. ((car (mapcar 'integerp cols))
  406. 'int-to-string)
  407. ((car (mapcar 'stringp cols))
  408. (lambda (string) (concat "\"" string "\"")))
  409. (t (error "Column selection should be symbol, integer or string: %S" cols))))
  410. (concat (when (> (length cols) 1) "c(")
  411. (mapconcat to-stringf cols ",")
  412. (when (> (length cols) 1) ")")))))
  413. (if (and (listp cols) (listp (car cols)))
  414. (mapcar 'org-R-make-index-vector cols) ;; case 5
  415. (list (org-R-make-index-vector cols)))) ;; other cases
  416. (defun org-R-number-of-columns (cols)
  417. (defun f (c) (if (listp c) (length c) 1))
  418. (if (and (listp cols) (listp (car cols)))
  419. (mapcar 'f cols)
  420. (list (f cols))))
  421. (defun org-R-eval (R-function csv-file options)
  422. "Apply an R function to tabular data and receive output as an org table.
  423. R-FUNCTION is a string; it may be simply the name of an
  424. appropriate R function (e.g. \"summary\", \"plot\"), or a
  425. user-defined anonymous function of the form
  426. \"(function(data.frame) {...})\". It will receive as its first
  427. argument the org table as an R 'data frame' -- a table-like
  428. structure which can have columns containing different types of
  429. data -- numeric, character etc.
  430. The R function may produce graphical and/or text output. If it
  431. produces text output, and the replace:t is specified, and if
  432. there is a table immediately above the #+R lines, then it is
  433. replaced by the text output. Otherwise the text output is
  434. inserted above the #+R lines.
  435. "
  436. (let ((transit-buffer "org-R-transit")
  437. (infile (plist-get options :infile))
  438. (output-file (plist-get options :outfile))
  439. (title (plist-get options :title))
  440. output-format graphics-output-file width height)
  441. (unless (not output-file)
  442. ;; We are writing output to file. Determine file format and
  443. ;; location, and open graphics device if necessary.
  444. (if (string-match
  445. "\\(.*\.\\)?\\(org\\|png\\|jpg\\|jpeg\\|pdf\\|ps\\|bmp\\|tiff\\)$"
  446. output-file)
  447. (setq output-format (match-string 2 output-file))
  448. (error "Did not recognise file name suffix %s as available output format"
  449. (match-string 2 output-file)))
  450. (unless (match-string 1 output-file)
  451. ;; only suffix provided: store in org-attach dir
  452. (require 'org-attach)
  453. (let ((temporary-file-directory (org-attach-dir t)))
  454. (setq output-file
  455. (make-temp-file
  456. "org-R-output-" nil (concat "." output-format)))))
  457. ;;; MdQ bug fix.
  458. ;;; If a filename is given, make sure it's absolute,
  459. ;;; as ess-execute needs that later.
  460. (if (match-string 1 output-file)
  461. (setq output-file (expand-file-name output-file)) )
  462. (if (eq output-format "jpg") (setq output-format "jpeg"))
  463. (setq graphics-output-file (not (string-equal output-format "org")))
  464. (if graphics-output-file ;; open the graphics device
  465. (ess-execute
  466. (concat output-format "(file=\"" output-file "\""
  467. (if (setq width (plist-get options :width))
  468. (format ", width=%d" width))
  469. (if (setq height (plist-get options :height))
  470. (format ", height=%d" height)) ")"))))
  471. ;; Apply R code to table (which is now stored as a csv file)
  472. ;; does it matter whether this uses ess-command or ess-execute?
  473. ;; First evaluate function definition for R -> org table conversion
  474. ;;; MdQ bug fix.
  475. ;;; The following save-excursion has been brought up to here
  476. ;;; so that the two ess-execute commands are now within it.
  477. ;;; This is because they have the side effect of changing current
  478. ;;; buffer to the transit-buffer, which causes error of deleting
  479. ;;; the wrong table there, instead of in the org buffer.
  480. (save-excursion
  481. (ess-execute (replace-regexp-in-string "\n" " " org-R-write-org-table-def)
  482. nil transit-buffer)
  483. ;; FIXME: why not eval the function def together with the function call
  484. ;; as in the commented out line below (it didn't work for some reason)
  485. (ess-execute
  486. (concat
  487. ;; (replace-regexp-in-string "\n" " " org-R-write-org-table-def) ";"
  488. (org-R-make-expr R-function csv-file options)) nil transit-buffer)
  489. (set-buffer (concat "*" transit-buffer "*"))
  490. (unless (or (looking-at "$")
  491. (string-equal (buffer-substring-no-properties 1 2) "|"))
  492. (error "Error in R evaluation:\n%s" (buffer-string))))
  493. (if csv-file
  494. (unless (and infile
  495. (string-equal (file-name-extension infile) "csv"))
  496. (delete-file csv-file)))
  497. (if graphics-output-file (ess-execute "dev.off()")) ;; Close graphics device
  498. (unless (or graphics-output-file
  499. (not (plist-get options :output-to-buffer)))
  500. ;; Send tabular output to a org buffer as new org
  501. ;; table. Recall that we are currently at the beginning of the
  502. ;; first #+R line
  503. (if (and output-file graphics-output-file)
  504. (error "output-to-buffer and graphics-output-file both t"))
  505. (save-excursion
  506. (if output-file
  507. (progn (set-buffer (find-file-noselect output-file))
  508. (delete-region (point-min) (point-max)))
  509. (if (plist-get options :replace)
  510. (progn ;; kill a table iff in one or one ends on the previous line
  511. (delete-region (org-table-begin) (org-table-end))
  512. (save-excursion
  513. (forward-line -1)
  514. (if (looking-at "#\\+TBLNAME")
  515. (delete-region (point) (1+ (point-at-eol))))))))
  516. (if title (insert "#+TBLNAME:" title "\n"))
  517. (insert-buffer-substring (concat "*" transit-buffer "*"))
  518. (org-table-align)
  519. (if output-file (save-buffer))))
  520. ;; We might be linking to graphical output, or to org output in
  521. ;; another file. Either way, point is still at the beginning of
  522. ;; the first #+R line.
  523. (unless (not output-file)
  524. (save-excursion
  525. (forward-line -1)
  526. (if (looking-at "\\[\\[file:")
  527. (delete-region (point) (1+ (point-at-eol)))))
  528. (insert (org-make-link-string
  529. (concat "file:" output-file)
  530. (unless (plist-get options :inline)
  531. (or title (concat output-format " output")))) "\n"))
  532. (kill-buffer (concat "*" transit-buffer "*"))))
  533. (defun org-R-export-to-csv (csv-file options)
  534. "Find and export org table to csv.
  535. If the intable: option has not been supplied, then the table must
  536. end on the line immediately above the #+R lines. Otherwise,
  537. the remote table referenced by the intable: option is found using
  538. org-R-find-table. If options:infile has been set then this is the
  539. org file containing the table. See the docstring of
  540. org-R-find-table for details."
  541. (let ((tbl-name-or-id (plist-get options :intable))
  542. (org-file (plist-get options :infile)) tbl-marker)
  543. (if (and org-file
  544. (not (string-equal (file-name-extension org-file) "org")))
  545. (error "File %s extension is not .csv so should be .org"))
  546. (save-excursion
  547. (if tbl-name-or-id
  548. ;; a remote table has been specified -- move into it
  549. (progn
  550. (if org-file (set-buffer (find-file-noselect org-file)))
  551. (setq tbl-marker (org-R-find-table tbl-name-or-id 'marker))
  552. (set-buffer (marker-buffer tbl-marker))
  553. (goto-char (marker-position tbl-marker)))
  554. (forward-line -1)) ;; move into table above
  555. (if (looking-at "[ \t]*|")
  556. (progn (org-table-export csv-file "orgtbl-to-csv") csv-file)
  557. nil))))
  558. (defun org-R-find-table (name-or-id &optional markerp)
  559. "Return location of a table.
  560. NAME-OR-ID may be the name of a
  561. table in the current file as set by a \"#+TBLNAME:\" directive.
  562. The first table following this line will then be used.
  563. Alternatively, it may be an ID referring to any entry, perhaps in
  564. a different file. In this case, the first table in that entry
  565. will be referenced. The location is returned as a marker pointing
  566. to the beginning of the first line of the table.
  567. This is taken from the first part of org-table-get-remote-range
  568. in org-table.el.
  569. "
  570. (cond
  571. ((symbolp name-or-id) (setq name-or-id (symbol-name name-or-id)))
  572. ((numberp name-or-id) (setq name-or-id (number-to-string name-or-id))))
  573. (save-match-data
  574. (let ((id-loc nil) (case-fold-search t) buffer loc)
  575. (save-excursion
  576. (save-restriction
  577. (widen)
  578. (save-excursion
  579. (goto-char (point-min))
  580. (if (re-search-forward
  581. (concat "^#\\+TBLNAME:[ \t]*" (regexp-quote name-or-id) "[ \t]*$")
  582. nil t)
  583. ;; OK, we've found a matching table name in this buffer.
  584. (setq buffer (current-buffer) loc (match-beginning 0))
  585. ;; It's not a table name in this buffer. It must be an entry id.
  586. ;; obtain a marker pointing to it.
  587. (setq id-loc (org-id-find name-or-id 'marker)
  588. buffer (marker-buffer id-loc)
  589. loc (marker-position id-loc))
  590. (move-marker id-loc nil))) ;; disable the marker
  591. ;; (switch-to-buffer buffer)
  592. (set-buffer buffer)
  593. ;; OK, so now we're in the right buffer, and loc is either
  594. ;; the beginning of the #+TBLNAME line, or the location of the entry
  595. ;; either way we need to search forward to get to the beginning of the table
  596. (save-excursion
  597. (save-restriction
  598. (widen)
  599. (goto-char loc)
  600. (forward-char 1)
  601. ;; The following regexp search finds the beginning of
  602. ;; the next table in this entry. If it gets to the next
  603. ;; entry before the next table, then it signals failure.
  604. (unless (and (re-search-forward "^\\(\\*+ \\)\\|[ \t]*|" nil t)
  605. (not (match-beginning 1)))
  606. (error "Cannot find a table at NAME or ID %s" name-or-id))
  607. (if markerp
  608. (move-marker (make-marker) (point-at-bol) (current-buffer))
  609. (error "Option to return cons cell not implemented.
  610. It should return (file-name . position) to be
  611. consistent with functions in org-id.el")))))))))
  612. (defun org-R-make-expr (R-function csv-file options)
  613. "Construct R code to read data, analyse it and write output."
  614. (let ((rownames (plist-get options :rownames))
  615. (colnames (plist-get options :colnames))
  616. (action (plist-get options :action))
  617. (replace (plist-get options :replace)))
  618. (if (and csv-file (symbolp csv-file))
  619. (setq csv-file (symbol-name csv-file)))
  620. (format "write.org.table((%s)(%s), write.rownames=%s)"
  621. R-function
  622. (if csv-file
  623. (format
  624. "read.csv(\"%s\", header=%s, row.names=%s)"
  625. csv-file
  626. ;; Do we treat first row as colnames? Yes by default
  627. ;; FIXME: should really check for hline
  628. (if colnames "TRUE" "FALSE")
  629. ;; Do we use a column as rownames? Not unless rownames: is specified
  630. (if rownames "1" "NULL"))
  631. "NULL")
  632. ;; Do we write rownames into org table?
  633. (cond ((eq action 'tabulate)
  634. (if (eq (plist-get options :nxcols) 1) "FALSE" "TRUE"))
  635. ((eq action 'transpose) (if colnames "TRUE" "FALSE"))
  636. (rownames "TRUE")
  637. (t "TRUE")))))
  638. (defun org-R-get-options ()
  639. "Parse the #+R: lines and return the options and values as a p-list."
  640. (let ((opts '(
  641. (:infile . "infile")
  642. (:intable . "intable")
  643. (:rownames . "rownames")
  644. (:colnames . "colnames")
  645. (:columns . "columns")
  646. (:action . "action")
  647. (:args . "args")
  648. (:outfile . "outfile")
  649. (:replace . "replace")
  650. (:title . "title")
  651. (:legend . "legend")
  652. (:colour . "colour")
  653. (:color . "color")
  654. (:col . "col")
  655. (:height . "height")
  656. (:width . "width")
  657. (:lines . "lines")
  658. (:sort . "sort")
  659. (:inline . "inline")
  660. (:output-to-buffer . "output-to-buffer")
  661. (:showcode . "showcode")))
  662. (regexp ":\\(\"[^\"]*\"\\|(([^)]*) *([^)]*))\\|([^)]*)\\|[^ \t\n\r;,.]*\\)")
  663. (case-fold-search t) p)
  664. ;; FIXME: set default options properly
  665. (setq p (plist-put p :output-to-buffer t)) ;; FIXME: hack: null options plist is bad news
  666. (setq p (plist-put p :replace t))
  667. (setq p (plist-put p :rownames nil))
  668. (setq p (plist-put p :colnames t))
  669. (setq p (plist-put p :inline nil))
  670. (save-excursion
  671. (while (looking-at "^#\\+\\(RR?:+\\) *\\(.*\\)")
  672. (if (string= "R:" (match-string 1))
  673. (setq p (org-R-add-options-to-plist p (match-string 2) opts regexp)))
  674. (forward-line)))
  675. p))
  676. (defun org-R-add-options-to-plist (p opt-string op regexp)
  677. "Parse a #+R: line and set values in the property list p.
  678. This function is adapted from similar functions in org-exp.el
  679. and org-plot.el. It might be a good idea to have a single
  680. function serving these three files' needs."
  681. ;; Adapted from org-exp.el and org-plot.el
  682. (let (o)
  683. (when opt-string
  684. (while (setq o (pop op))
  685. (if (string-match
  686. (concat (regexp-quote (cdr o)) regexp)
  687. opt-string)
  688. (setq p (plist-put p (car o)
  689. (car (read-from-string
  690. (match-string 1 opt-string)))))))))
  691. p)
  692. (defun org-R-sanitise-options (options)
  693. (error "not used yet")
  694. (let (should-be-strings '(title legend colour color col csv)))
  695. )
  696. (defun org-R-showcode (R)
  697. "Display R function constructed by org-R in a new R-mode
  698. buffer."
  699. (split-window-vertically)
  700. (switch-to-buffer "*org-table.R*")
  701. (kill-region (point-min) (point-max))
  702. (R-mode)
  703. (insert (replace-regexp-in-string
  704. ";" "\n" (replace-regexp-in-string "\\([{}]\\)" "\n\\1\n" R)))
  705. ;; (mark-whole-buffer)
  706. ;; (indent-region)
  707. ;; why doesn't that do what I hoped?
  708. )
  709. (defun org-R-get-remote-range (name-or-id form)
  710. "Get a field value or a list of values in a range from table at ID.
  711. This is a refactoring of Carsten's original version. I have
  712. extracted the first bit of his function and named it
  713. org-R-find-table (which would presumably be called something like
  714. org-table-find-table or org-id-find-table if this were accepted).
  715. ---
  716. Get a field value or a list of values in a range from table at ID.
  717. NAME-OR-ID may be the name of a table in the current file as set by
  718. a \"#+TBLNAME:\" directive. The first table following this line
  719. will then be used. Alternatively, it may be an ID referring to
  720. any entry, possibly in a different file. In this case, the first table
  721. in that entry will be referenced.
  722. FORM is a field or range descriptor like \"@2$3\" or or \"B3\" or
  723. \"@I$2..@II$2\". All the references must be absolute, not relative.
  724. The return value is either a single string for a single field, or a
  725. list of the fields in the rectangle."
  726. (let ((tbl-marker (org-R-find-table name-or-id 'marker))
  727. org-table-column-names org-table-column-name-regexp
  728. org-table-local-parameters org-table-named-field-locations
  729. org-table-current-line-types org-table-current-begin-line
  730. org-table-current-begin-pos org-table-dlines
  731. org-table-hlines org-table-last-alignment
  732. org-table-last-column-widths org-table-last-alignment
  733. org-table-last-column-widths tbeg)
  734. (save-excursion
  735. (set-buffer (marker-buffer tbl-marker))
  736. (goto-char (marker-position tbl-marker))
  737. (org-table-get-specials)
  738. (setq form (org-table-formula-substitute-names form))
  739. (if (and (string-match org-table-range-regexp form)
  740. (> (length (match-string 0 form)) 1))
  741. (save-match-data
  742. (org-table-get-range (match-string 0 form) (point) 1))
  743. form))))
  744. (provide 'org-R)