diff options
Diffstat (limited to 'lisp/org/ob-R.el')
| -rw-r--r-- | lisp/org/ob-R.el | 218 |
1 files changed, 120 insertions, 98 deletions
diff --git a/lisp/org/ob-R.el b/lisp/org/ob-R.el index d990d69b357..2be79926a44 100644 --- a/lisp/org/ob-R.el +++ b/lisp/org/ob-R.el | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | ;; Author: Eric Schulte, Dan Davison | 5 | ;; Author: Eric Schulte, Dan Davison |
| 6 | ;; Keywords: literate programming, reproducible research, R, statistics | 6 | ;; Keywords: literate programming, reproducible research, R, statistics |
| 7 | ;; Homepage: http://orgmode.org | 7 | ;; Homepage: http://orgmode.org |
| 8 | ;; Version: 7.01 | 8 | ;; Version: 7.3 |
| 9 | 9 | ||
| 10 | ;; This file is part of GNU Emacs. | 10 | ;; This file is part of GNU Emacs. |
| 11 | 11 | ||
| @@ -36,6 +36,9 @@ | |||
| 36 | (declare-function orgtbl-to-tsv "org-table" (table params)) | 36 | (declare-function orgtbl-to-tsv "org-table" (table params)) |
| 37 | (declare-function R "ext:essd-r" (&optional start-args)) | 37 | (declare-function R "ext:essd-r" (&optional start-args)) |
| 38 | (declare-function inferior-ess-send-input "ext:ess-inf" ()) | 38 | (declare-function inferior-ess-send-input "ext:ess-inf" ()) |
| 39 | (declare-function ess-make-buffer-current "ext:ess-inf" ()) | ||
| 40 | (declare-function ess-eval-buffer "ext:ess-inf" (vis)) | ||
| 41 | (declare-function org-number-sequence "org-compat" (from &optional to inc)) | ||
| 39 | 42 | ||
| 40 | (defconst org-babel-header-arg-names:R | 43 | (defconst org-babel-header-arg-names:R |
| 41 | '(width height bg units pointsize antialias quality compression | 44 | '(width height bg units pointsize antialias quality compression |
| @@ -48,21 +51,11 @@ | |||
| 48 | (defvar org-babel-R-command "R --slave --no-save" | 51 | (defvar org-babel-R-command "R --slave --no-save" |
| 49 | "Name of command to use for executing R code.") | 52 | "Name of command to use for executing R code.") |
| 50 | 53 | ||
| 51 | (defun org-babel-expand-body:R (body params &optional processed-params) | 54 | (defun org-babel-expand-body:R (body params) |
| 52 | "Expand BODY according to PARAMS, return the expanded body." | 55 | "Expand BODY according to PARAMS, return the expanded body." |
| 53 | (let* ((processed-params (or processed-params | 56 | (let ((out-file (cdr (assoc :file params)))) |
| 54 | (org-babel-process-params params))) | 57 | (mapconcat |
| 55 | (vars (mapcar | 58 | #'identity |
| 56 | (lambda (i) | ||
| 57 | (cons (car (nth i (nth 1 processed-params))) | ||
| 58 | (org-babel-reassemble-table | ||
| 59 | (cdr (nth i (nth 1 processed-params))) | ||
| 60 | (cdr (nth i (nth 4 processed-params))) | ||
| 61 | (cdr (nth i (nth 5 processed-params)))))) | ||
| 62 | (number-sequence 0 (1- (length (nth 1 processed-params)))))) | ||
| 63 | (out-file (cdr (assoc :file params)))) | ||
| 64 | (mapconcat ;; define any variables | ||
| 65 | #'org-babel-trim | ||
| 66 | ((lambda (inside) | 59 | ((lambda (inside) |
| 67 | (if out-file | 60 | (if out-file |
| 68 | (append | 61 | (append |
| @@ -70,49 +63,36 @@ | |||
| 70 | inside | 63 | inside |
| 71 | (list "dev.off()")) | 64 | (list "dev.off()")) |
| 72 | inside)) | 65 | inside)) |
| 73 | (append | 66 | (append (org-babel-variable-assignments:R params) |
| 74 | (mapcar | 67 | (list body))) "\n"))) |
| 75 | (lambda (pair) | ||
| 76 | (org-babel-R-assign-elisp | ||
| 77 | (car pair) (cdr pair) | ||
| 78 | (equal "yes" (cdr (assoc :colnames params))) | ||
| 79 | (equal "yes" (cdr (assoc :rownames params))))) | ||
| 80 | vars) | ||
| 81 | (list body))) "\n"))) | ||
| 82 | 68 | ||
| 83 | (defun org-babel-execute:R (body params) | 69 | (defun org-babel-execute:R (body params) |
| 84 | "Execute a block of R code. | 70 | "Execute a block of R code. |
| 85 | This function is called by `org-babel-execute-src-block'." | 71 | This function is called by `org-babel-execute-src-block'." |
| 86 | (save-excursion | 72 | (save-excursion |
| 87 | (let* ((processed-params (org-babel-process-params params)) | 73 | (let* ((result-type (cdr (assoc :result-type params))) |
| 88 | (result-type (nth 3 processed-params)) | ||
| 89 | (session (org-babel-R-initiate-session | 74 | (session (org-babel-R-initiate-session |
| 90 | (first processed-params) params)) | 75 | (cdr (assoc :session params)) params)) |
| 91 | (colnames-p (cdr (assoc :colnames params))) | 76 | (colnames-p (cdr (assoc :colnames params))) |
| 92 | (rownames-p (cdr (assoc :rownames params))) | 77 | (rownames-p (cdr (assoc :rownames params))) |
| 93 | (out-file (cdr (assoc :file params))) | 78 | (out-file (cdr (assoc :file params))) |
| 94 | (full-body (org-babel-expand-body:R body params processed-params)) | 79 | (full-body (org-babel-expand-body:R body params)) |
| 95 | (result | 80 | (result |
| 96 | (org-babel-R-evaluate | 81 | (org-babel-R-evaluate |
| 97 | session full-body result-type | 82 | session full-body result-type |
| 98 | (or (equal "yes" colnames-p) | 83 | (or (equal "yes" colnames-p) |
| 99 | (org-babel-pick-name (nth 4 processed-params) colnames-p)) | 84 | (org-babel-pick-name |
| 85 | (cdr (assoc :colname-names params)) colnames-p)) | ||
| 100 | (or (equal "yes" rownames-p) | 86 | (or (equal "yes" rownames-p) |
| 101 | (org-babel-pick-name (nth 5 processed-params) rownames-p))))) | 87 | (org-babel-pick-name |
| 88 | (cdr (assoc :rowname-names params)) rownames-p))))) | ||
| 102 | (message "result is %S" result) | 89 | (message "result is %S" result) |
| 103 | (or out-file result)))) | 90 | (or out-file result)))) |
| 104 | 91 | ||
| 105 | (defun org-babel-prep-session:R (session params) | 92 | (defun org-babel-prep-session:R (session params) |
| 106 | "Prepare SESSION according to the header arguments specified in PARAMS." | 93 | "Prepare SESSION according to the header arguments specified in PARAMS." |
| 107 | (let* ((session (org-babel-R-initiate-session session params)) | 94 | (let* ((session (org-babel-R-initiate-session session params)) |
| 108 | (vars (org-babel-ref-variables params)) | 95 | (var-lines (org-babel-variable-assignments:R params))) |
| 109 | (var-lines | ||
| 110 | (mapcar | ||
| 111 | (lambda (pair) (org-babel-R-assign-elisp | ||
| 112 | (car pair) (cdr pair) | ||
| 113 | (equal (cdr (assoc :colnames params)) "yes") | ||
| 114 | (equal (cdr (assoc :rownames params)) "yes"))) | ||
| 115 | vars))) | ||
| 116 | (org-babel-comint-in-buffer session | 96 | (org-babel-comint-in-buffer session |
| 117 | (mapc (lambda (var) | 97 | (mapc (lambda (var) |
| 118 | (end-of-line 1) (insert var) (comint-send-input nil t) | 98 | (end-of-line 1) (insert var) (comint-send-input nil t) |
| @@ -130,6 +110,24 @@ This function is called by `org-babel-execute-src-block'." | |||
| 130 | 110 | ||
| 131 | ;; helper functions | 111 | ;; helper functions |
| 132 | 112 | ||
| 113 | (defun org-babel-variable-assignments:R (params) | ||
| 114 | "Return list of R statements assigning the block's variables" | ||
| 115 | (let ((vars (mapcar #'cdr (org-babel-get-header params :var)))) | ||
| 116 | (mapcar | ||
| 117 | (lambda (pair) | ||
| 118 | (org-babel-R-assign-elisp | ||
| 119 | (car pair) (cdr pair) | ||
| 120 | (equal "yes" (cdr (assoc :colnames params))) | ||
| 121 | (equal "yes" (cdr (assoc :rownames params))))) | ||
| 122 | (mapcar | ||
| 123 | (lambda (i) | ||
| 124 | (cons (car (nth i vars)) | ||
| 125 | (org-babel-reassemble-table | ||
| 126 | (cdr (nth i vars)) | ||
| 127 | (cdr (nth i (cdr (assoc :colname-names params)))) | ||
| 128 | (cdr (nth i (cdr (assoc :rowname-names params))))))) | ||
| 129 | (org-number-sequence 0 (1- (length vars))))))) | ||
| 130 | |||
| 133 | (defun org-babel-R-quote-tsv-field (s) | 131 | (defun org-babel-R-quote-tsv-field (s) |
| 134 | "Quote field S for export to R." | 132 | "Quote field S for export to R." |
| 135 | (if (stringp s) | 133 | (if (stringp s) |
| @@ -139,23 +137,25 @@ This function is called by `org-babel-execute-src-block'." | |||
| 139 | (defun org-babel-R-assign-elisp (name value colnames-p rownames-p) | 137 | (defun org-babel-R-assign-elisp (name value colnames-p rownames-p) |
| 140 | "Construct R code assigning the elisp VALUE to a variable named NAME." | 138 | "Construct R code assigning the elisp VALUE to a variable named NAME." |
| 141 | (if (listp value) | 139 | (if (listp value) |
| 142 | (let ((transition-file (make-temp-file "org-babel-R-import"))) | 140 | (let ((transition-file (org-babel-temp-file "R-import-"))) |
| 143 | ;; ensure VALUE has an orgtbl structure (depth of at least 2) | 141 | ;; ensure VALUE has an orgtbl structure (depth of at least 2) |
| 144 | (unless (listp (car value)) (setq value (list value))) | 142 | (unless (listp (car value)) (setq value (list value))) |
| 145 | (with-temp-file (org-babel-maybe-remote-file transition-file) | 143 | (with-temp-file transition-file |
| 146 | (insert (orgtbl-to-tsv value '(:fmt org-babel-R-quote-tsv-field))) | 144 | (insert (orgtbl-to-tsv value '(:fmt org-babel-R-quote-tsv-field))) |
| 147 | (insert "\n")) | 145 | (insert "\n")) |
| 148 | (format "%s <- read.table(\"%s\", header=%s, row.names=%s, sep=\"\\t\", as.is=TRUE)" | 146 | (format "%s <- read.table(\"%s\", header=%s, row.names=%s, sep=\"\\t\", as.is=TRUE)" |
| 149 | name transition-file | 147 | name (org-babel-process-file-name transition-file 'noquote) |
| 150 | (if (or (eq (nth 1 value) 'hline) colnames-p) "TRUE" "FALSE") | 148 | (if (or (eq (nth 1 value) 'hline) colnames-p) "TRUE" "FALSE") |
| 151 | (if rownames-p "1" "NULL"))) | 149 | (if rownames-p "1" "NULL"))) |
| 152 | (format "%s <- %s" name (org-babel-R-quote-tsv-field value)))) | 150 | (format "%s <- %s" name (org-babel-R-quote-tsv-field value)))) |
| 153 | 151 | ||
| 152 | (defvar ess-ask-for-ess-directory nil) | ||
| 154 | (defun org-babel-R-initiate-session (session params) | 153 | (defun org-babel-R-initiate-session (session params) |
| 155 | "If there is not a current R process then create one." | 154 | "If there is not a current R process then create one." |
| 156 | (unless (string= session "none") | 155 | (unless (string= session "none") |
| 157 | (let ((session (or session "*R*")) | 156 | (let ((session (or session "*R*")) |
| 158 | (ess-ask-for-ess-directory (not (cdr (assoc :dir params))))) | 157 | (ess-ask-for-ess-directory |
| 158 | (and ess-ask-for-ess-directory (not (cdr (assoc :dir params)))))) | ||
| 159 | (if (org-babel-comint-buffer-livep session) | 159 | (if (org-babel-comint-buffer-livep session) |
| 160 | session | 160 | session |
| 161 | (save-window-excursion | 161 | (save-window-excursion |
| @@ -168,6 +168,15 @@ This function is called by `org-babel-execute-src-block'." | |||
| 168 | (buffer-name)))) | 168 | (buffer-name)))) |
| 169 | (current-buffer)))))) | 169 | (current-buffer)))))) |
| 170 | 170 | ||
| 171 | (defvar ess-local-process-name nil) | ||
| 172 | (defun org-babel-R-associate-session (session) | ||
| 173 | "Associate R code buffer with an R session. | ||
| 174 | Make SESSION be the inferior ESS process associated with the | ||
| 175 | current code buffer." | ||
| 176 | (setq ess-local-process-name | ||
| 177 | (process-name (get-buffer-process session))) | ||
| 178 | (ess-make-buffer-current)) | ||
| 179 | |||
| 171 | (defun org-babel-R-construct-graphics-device-call (out-file params) | 180 | (defun org-babel-R-construct-graphics-device-call (out-file params) |
| 172 | "Construct the call to the graphics device." | 181 | "Construct the call to the graphics device." |
| 173 | (let ((devices | 182 | (let ((devices |
| @@ -205,65 +214,78 @@ This function is called by `org-babel-execute-src-block'." | |||
| 205 | 214 | ||
| 206 | (defvar org-babel-R-eoe-indicator "'org_babel_R_eoe'") | 215 | (defvar org-babel-R-eoe-indicator "'org_babel_R_eoe'") |
| 207 | (defvar org-babel-R-eoe-output "[1] \"org_babel_R_eoe\"") | 216 | (defvar org-babel-R-eoe-output "[1] \"org_babel_R_eoe\"") |
| 208 | (defvar org-babel-R-wrapper-method "main <- function ()\n{\n%s\n} | 217 | (defvar org-babel-R-write-object-command "{function(object, transfer.file) {invisible(if(inherits(try(write.table(object, file=transfer.file, sep=\"\\t\", na=\"nil\",row.names=%s, col.names=%s, quote=FALSE), silent=TRUE),\"try-error\")) {if(!file.exists(transfer.file)) file.create(transfer.file)})}}(object=%s, transfer.file=\"%s\")") |
| 209 | write.table(main(), file=\"%s\", sep=\"\\t\", na=\"nil\",row.names=%s, col.names=%s, quote=FALSE)") | ||
| 210 | (defvar org-babel-R-wrapper-lastvar "write.table(.Last.value, file=\"%s\", sep=\"\\t\", na=\"nil\",row.names=%s, col.names=%s, quote=FALSE)") | ||
| 211 | 218 | ||
| 212 | (defun org-babel-R-evaluate | 219 | (defun org-babel-R-evaluate |
| 213 | (session body result-type column-names-p row-names-p) | 220 | (session body result-type column-names-p row-names-p) |
| 214 | "Pass BODY to the R process in SESSION. | 221 | "Evaluate R code in BODY." |
| 215 | If RESULT-TYPE equals 'output then return a list of the outputs | 222 | (if session |
| 216 | of the statements in BODY, if RESULT-TYPE equals 'value then | 223 | (org-babel-R-evaluate-session |
| 217 | return the value of the last statement in BODY, as elisp." | 224 | session body result-type column-names-p row-names-p) |
| 218 | (if (not session) | 225 | (org-babel-R-evaluate-external-process |
| 219 | ;; external process evaluation | 226 | body result-type column-names-p row-names-p))) |
| 220 | (case result-type | 227 | |
| 221 | (output (org-babel-eval org-babel-R-command body)) | 228 | (defun org-babel-R-evaluate-external-process |
| 222 | (value | 229 | (body result-type column-names-p row-names-p) |
| 223 | (let ((tmp-file (make-temp-file "org-babel-R-results-"))) | 230 | "Evaluate BODY in external R process. |
| 224 | (org-babel-eval org-babel-R-command | 231 | If RESULT-TYPE equals 'output then return standard output as a |
| 225 | (format org-babel-R-wrapper-method | 232 | string. If RESULT-TYPE equals 'value then return the value of the |
| 226 | body tmp-file | 233 | last statement in BODY, as elisp." |
| 227 | (if row-names-p "TRUE" "FALSE") | 234 | (case result-type |
| 228 | (if column-names-p | 235 | (value |
| 229 | (if row-names-p "NA" "TRUE") | 236 | (let ((tmp-file (org-babel-temp-file "R-"))) |
| 230 | "FALSE"))) | 237 | (org-babel-eval org-babel-R-command |
| 231 | (org-babel-R-process-value-result | 238 | (format org-babel-R-write-object-command |
| 232 | (org-babel-import-elisp-from-file | 239 | (if row-names-p "TRUE" "FALSE") |
| 233 | (org-babel-maybe-remote-file tmp-file)) column-names-p)))) | 240 | (if column-names-p |
| 234 | ;; comint session evaluation | 241 | (if row-names-p "NA" "TRUE") |
| 235 | (case result-type | 242 | "FALSE") |
| 236 | (value | 243 | (format "{function ()\n{\n%s\n}}()" body) |
| 237 | (let ((tmp-file (make-temp-file "org-babel-R")) | 244 | (org-babel-process-file-name tmp-file 'noquote))) |
| 238 | broke) | 245 | (org-babel-R-process-value-result |
| 239 | (org-babel-comint-with-output (session org-babel-R-eoe-output) | 246 | (org-babel-import-elisp-from-file tmp-file '(16)) column-names-p))) |
| 240 | (insert (mapconcat | 247 | (output (org-babel-eval org-babel-R-command body)))) |
| 241 | #'org-babel-chomp | 248 | |
| 242 | (list | 249 | (defun org-babel-R-evaluate-session |
| 243 | body | 250 | (session body result-type column-names-p row-names-p) |
| 244 | (format org-babel-R-wrapper-lastvar | 251 | "Evaluate BODY in SESSION. |
| 245 | tmp-file | 252 | If RESULT-TYPE equals 'output then return standard output as a |
| 246 | (if row-names-p "TRUE" "FALSE") | 253 | string. If RESULT-TYPE equals 'value then return the value of the |
| 247 | (if column-names-p | 254 | last statement in BODY, as elisp." |
| 248 | (if row-names-p "NA" "TRUE") | 255 | (case result-type |
| 249 | "FALSE")) | 256 | (value |
| 250 | org-babel-R-eoe-indicator) "\n")) | 257 | (with-temp-buffer |
| 251 | (inferior-ess-send-input)) | 258 | (insert (org-babel-chomp body)) |
| 252 | (org-babel-R-process-value-result | 259 | (let ((ess-local-process-name |
| 253 | (org-babel-import-elisp-from-file | 260 | (process-name (get-buffer-process session)))) |
| 254 | (org-babel-maybe-remote-file tmp-file)) column-names-p))) | 261 | (ess-eval-buffer nil))) |
| 255 | (output | 262 | (let ((tmp-file (org-babel-temp-file "R-"))) |
| 256 | (mapconcat | 263 | (org-babel-comint-eval-invisibly-and-wait-for-file |
| 257 | #'org-babel-chomp | 264 | session tmp-file |
| 258 | (butlast | 265 | (format org-babel-R-write-object-command |
| 259 | (delq nil | 266 | (if row-names-p "TRUE" "FALSE") |
| 260 | (mapcar | 267 | (if column-names-p |
| 261 | #'identity | 268 | (if row-names-p "NA" "TRUE") |
| 262 | (org-babel-comint-with-output (session org-babel-R-eoe-output) | 269 | "FALSE") |
| 263 | (insert (mapconcat #'org-babel-chomp | 270 | ".Last.value" (org-babel-process-file-name tmp-file 'noquote))) |
| 264 | (list body org-babel-R-eoe-indicator) | 271 | (org-babel-R-process-value-result |
| 265 | "\n")) | 272 | (org-babel-import-elisp-from-file tmp-file '(16)) column-names-p))) |
| 266 | (inferior-ess-send-input)))) 2) "\n"))))) | 273 | (output |
| 274 | (mapconcat | ||
| 275 | #'org-babel-chomp | ||
| 276 | (butlast | ||
| 277 | (delq nil | ||
| 278 | (mapcar | ||
| 279 | (lambda (line) ;; cleanup extra prompts left in output | ||
| 280 | (if (string-match | ||
| 281 | "^\\([ ]*[>+][ ]?\\)+\\([[0-9]+\\|[ ]\\)" line) | ||
| 282 | (substring line (match-end 1)) | ||
| 283 | line)) | ||
| 284 | (org-babel-comint-with-output (session org-babel-R-eoe-output) | ||
| 285 | (insert (mapconcat #'org-babel-chomp | ||
| 286 | (list body org-babel-R-eoe-indicator) | ||
| 287 | "\n")) | ||
| 288 | (inferior-ess-send-input)))) 2) "\n")))) | ||
| 267 | 289 | ||
| 268 | (defun org-babel-R-process-value-result (result column-names-p) | 290 | (defun org-babel-R-process-value-result (result column-names-p) |
| 269 | "R-specific processing of return value. | 291 | "R-specific processing of return value. |