17

My R workflow is usually such that I have a file open into which I type R commands, and I’d like to execute those commands in a separately opened R shell.

The easiest way of doing this is to say source('the-file.r') inside R. However, this always reloads the whole file which may take considerable time if big amounts of data are processed. It also requires me to specify the filename again.

Ideally, I’d like to source only a specific line (or lines) from the file (I’m working on a terminal where copy&paste doesn’t work).

source doesn’t seem to offer this functionality. Is there another way of achieving this?

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 2
    What terminal is that? A DEC VT100? Either get a better terminal or a better environment - emacs? - or reconsider your workflow or break your files up into other files and have a great nested mess of files that source other files or do it properly and make more functions... – Spacedman Aug 31 '12 at 12:06
  • @Spacedman tmux with split window. Copying copies across the *whole* terminal, which means that it copies garbage. But even if that weren’t the case I’m using Vim on a remote machine with cursor mode enabled, which disables terminal text selection. – i.e. you cannot use the local clipboard, and there is no remote clipboard either. – Konrad Rudolph Aug 31 '12 at 12:10
  • Why don't you just use one of the available IDEs? – Roman Luštrik Aug 31 '12 at 12:10
  • @Roman I’m working on a remote terminal. – Konrad Rudolph Aug 31 '12 at 12:11
  • 1
    So what? I use remote tmux and remote emacs all the time; still gives you buffers in emacs from which you can source parts as you desire. And Emacs ESS even lets the _local_ Emacs connect to the _remote_ R session if you're so inclined (and I don't do that). – Dirk Eddelbuettel Aug 31 '12 at 12:15
  • @Dirk I know about ESS but unfortunately nothing similar exists for vim, otherwise this whole mess would indeed be unnecessary. But I’d be interested in what you mean by these remote buffers. Since when does the shell have a built-in cross-application clipboard? Or are you using X? My vim is compiled with `-xterm_clipboard`, unfortunately. – Konrad Rudolph Aug 31 '12 at 12:23
  • @Konrad: No vim involved. For a) ESS accessing a remote R session, I believe all is done via ssh, similar to Emacs' tramp mode (which is also awesome). For b) what I do: If I must be remotely sans X11, it is just 'emacs -nw' or rather 'emacsclient -nw' as I almost always have a longer running emacs session in daemon mode. That is independent of X11 and works from other strange OS too. The key points here are i) a workflow around `source()` is **way** to limiting, ii) Emacs gives many of us a perfect environment and iii) it all works just fine inside tmux. What's not to like? – Dirk Eddelbuettel Aug 31 '12 at 12:30
  • @Dirk I don’t understand b): you simply launch emacs. But how do you copy/paste stuff? Which clipboard are you using? As I said above, I cannot use the local machine’s clipboard. – Konrad Rudolph Aug 31 '12 at 12:32
  • 1
    Maybe http://projecttemplate.net/ can be useful here with the munging tools. – daroczig Aug 31 '12 at 12:34
  • Inside Emacs, M-x R. Now you have R. From there it is ESS mode. No clipboard involved, and no animals harmed. – Dirk Eddelbuettel Aug 31 '12 at 12:35
  • @Dirk Okay, but like I said above, I’m not using Emacs, I’m using Vim. I don’t actually have no strong preference one way or another but I’ve been using Vim for years and I’m not going to switch and re-learn all this stuff simply for one tool. I appreciate that ESS is powerful, and I regret not being able to use it, but I simply don’t have the time to re-learn an editor. – Konrad Rudolph Aug 31 '12 at 12:37
  • "Those who do understand Unix are condemned to reinvent it, poorly" as Henry Spencer famously said. If Vim is a must for you, then you will forgo a very powerful aide for the very situation you are in. Your pick. I do think theree are some Vim-R bridges though, but I forgot what OS/platform constraints they have. – Dirk Eddelbuettel Aug 31 '12 at 12:39
  • @Konrad: Are you aware of this Vim plugin http://www.vim.org/scripts/script.php?script_id=2628 ? – Dirk Eddelbuettel Aug 31 '12 at 13:25
  • I agree with @daroczig -- you can also do this manually, with a bunch of `if(exists(timeConsumingResultA)) timeConsumingResultA<-makeTimeConsumingComputationsA()`-alikes. – mbq Aug 31 '12 at 13:44
  • @Dirk I’d love to try it but it requires a newer Vim version than is installed on our servers. Well, what the hell, I’ve already installed half the Unix Universe in `~` anyway. What harm can recompiling Vim possibly do? – Konrad Rudolph Aug 31 '12 at 13:46
  • @Dirk Thanks for the push. I’ve now finally succeeded in installing this plugin (after having to reinstall Vim, Hg and several Python modules from source …) and it looks like quite a versatile replacement for ESS. – Konrad Rudolph Aug 31 '12 at 18:11
  • 1
    @Konrad -- cool! Working 'by region' and from a file is the way to go. – Dirk Eddelbuettel Aug 31 '12 at 18:14

2 Answers2

24

Here's another way with just R:

source2 <- function(file, start, end, ...) {
    file.lines <- scan(file, what=character(), skip=start-1, nlines=end-start+1, sep='\n')
    file.lines.collapsed <- paste(file.lines, collapse='\n')
    source(textConnection(file.lines.collapsed), ...)
}
Matthew Plourde
  • 43,932
  • 7
  • 96
  • 113
9

Using the right tool for the job …

As discussed in the comments, the real solution is to use an IDE that allows sourcing specific parts of a file. There are many existing solutions:

  • For Vim, there’s Nvim-R.

  • For Emacs, there’s ESS.

  • And of course there’s the excellent stand-alone RStudio IDE.

As a special point of note, all of the above solutions work both locally and on a server (accessed via an SSH connection, say). R can even be run on an HPC cluster — it can still communicate with the IDEs if set up properly.

… or … not.

If, for whatever reason, none of the solutions above work, here’s a small module[gist] that can do the job. I generally don’t recommend using it, though.1

#' (Re-)source parts of a file
#'
#' \code{rs} loads, parses and executes parts of a file as if entered into the R
#' console directly (but without implicit echoing).
#'
#' @param filename character string of the filename to read from. If missing,
#' use the last-read filename.
#' @param from first line to parse.
#' @param to last line to parse.
#' @return the value of the last evaluated expression in the source file.
#'
#' @details If both \code{from} and \code{to} are missing, the default is to
#' read the whole file.
rs = local({
    last_file = NULL

    function (filename, from, to = if (missing(from)) -1 else from) {
        if (missing(filename)) filename = last_file

        stopifnot(! is.null(filename))
        stopifnot(is.character(filename))

        force(to)
        if (missing(from)) from = 1

        source_lines = scan(filename, what = character(), sep = '\n',
                            skip = from - 1, n = to - from + 1,
                            encoding = 'UTF-8', quiet = TRUE)
        result = withVisible(eval.parent(parse(text = source_lines)))

        last_file <<- filename # Only save filename once successfully sourced.
        if (result$visible) result$value else invisible(result$value)
    }
})

Usage example:

# Source the whole file:
rs('some_file.r')
# Re-soure everything (same file):
rs()
# Re-source just the fifth line:
rs(from = 5)
# Re-source lines 5–10
rs(from = 5, to = 10)
# Re-source everything up until line 7:
rs(to = 7)

1 Funny story: I recently found myself on a cluster with a messed-up configuration that made it impossible to install the required software, but desperately needing to debug an R workflow due to a looming deadline. I literally had no choice but to copy and paste lines of R code into the console manually. This is a situation in which the above might come in handy. And yes, that actually happened.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 1
    I think that storing the last source file using `<<-` is not a good idea. (`<<-` is almost never a good idea, actually). Another issue is that `sed` might not be available on all platforms (e.g. Windows). – Gabor Csardi Aug 31 '12 at 12:42
  • I think a hidden environment placed in `.GlovalEnv` is indeed the way to go if you really want to keep the last file name. (Which is very sensible in this case.) – Gabor Csardi Aug 31 '12 at 12:50
  • As for getting rid of `sed`, one can just read the file using `readLines()`, then select the lines needed and evaluate them. – Gabor Csardi Aug 31 '12 at 12:53
  • The elegant way of doing variable arguments list in R is to define them and test if they are provided with `missing`. – mbq Aug 31 '12 at 13:48
  • 1
    @KonradRudolph, yes, you're right, it does not have to be an environment. My main point was that storing it in `.GlobalEnv` is better, and it is good if the variable name starts with a dot, because then it is not listed in `ls()`, and not deleted in `rm(list=ls())`, etc. – Gabor Csardi Aug 31 '12 at 16:35
  • @Gabor I got tired of people referring to this terrible answer so I rewrote it completely. I’m letting you know because I am cleaning up the (now obsolete) comments; maybe you want to do the same. – Konrad Rudolph Feb 24 '17 at 14:04