22

I find myself in the position of having completed a large chunk of analysis and now need to repeat the analysis with slightly different input assumptions.

The analysis, in this case, involves cluster analysis, plotting several graphs, and exporting cluster ids and other variables of interest. The key point is that it is an extensive analysis, and needs to be repeated and compared only twice.

I considered:

  • Creating a function. This isn't ideal, because then I have to modify my code to know whether I am evaluating in the function or parent environments. This additional effort seems excessive, makes it harder to debug and may introduce side-effects.
  • Wrap it in a for-loop. Again, not ideal, because then I have to create indexing variables, which can also introduce side-effects.
  • Creating some pre-amble code, wrapping the analysis in a separate file and source it. This works, but seems very ugly and sub-optimal.

The objective of the analysis is to finish with a set of objects (in a list, or in separate output files) that I can analyse further for differences.

What is a good strategy for dealing with this type of problem?

Andrie
  • 176,377
  • 47
  • 447
  • 496

7 Answers7

17

Making code reusable takes some time, effort and holds a few extra challenges like you mention yourself.

The question whether to invest is probably the key issue in informatics (if not in a lot of other fields): do I write a script to rename 50 files in a similar fashion, or do I go ahead and rename them manually.

The answer, I believe, is highly personal and even then, different case by case. If you are easy on the programming, you may sooner decide to go the reuse route, as the effort for you will be relatively low (and even then, programmers typically like to learn new tricks, so that's a hidden, often counterproductive motivation).

That said, in your particular case: I'd go with the sourcing option: since you plan to reuse the code only 2 times more, a greater effort would probably go wasted (you indicate the analysis to be rather extensive). So what if it's not an elegant solution? Nobody is ever going to see you do it, and everybody will be happy with the swift results.

If it turns out in a year or so that the reuse is higher than expected, you can then still invest. And by that time, you will also have (at least) three cases for which you can compare the results from the rewritten and funky reusable version of your code with your current results.

If/when I do know up front that I'm going to reuse code, I try to keep that in mind while developing it. Either way I hardly ever write code that is not in a function (well, barring the two-liners for SO and other out-of-the-box analyses): I find this makes it easier for me to structure my thoughts.

Nick Sabbe
  • 11,684
  • 1
  • 43
  • 57
  • +1 because you're only going to do it twice more. The answer also depends on how much you're going to have change each time through the analysis -- just a few parameters, input data, ? I find there's often a fairly smooth transition between (1) pulling key parameters out and defining them at the top of the code [or putting them in a separate file and `source()`ing the body of the analysis] and (2) wrapping the body of the code in a function. It's not clear to me what your parent vs function environment distinctions are here. – Ben Bolker Jun 30 '11 at 16:26
  • +1 This is pretty much what I did in the end. I wrapped all of the parameter that changed for each run into a list. Then created different lists (with the same structure) containing the input values for each run. For each iteration, I copied the necessary lists and saved the resulting variables into output lists. In other words, a bit of wrapping of code into a preamble and cleanup, and job done. It works. It's OK if it's ugly... – Andrie Jul 05 '11 at 11:22
9

If at all possible, set parameters that differ between sets/runs/experiments in an external parameter file. Then, you can source the code, call a function, even utilize a package, but the operations are determined by a small set of externally defined parameters.

For instance, JSON works very well for this and the RJSONIO and rjson packages allow you to load the file into a list. Suppose you load it into a list called parametersNN.json. An example is as follows:

{
 "Version": "20110701a",
 "Initialization":
 {
   "indices": [1,2,3,4,5,6,7,8,9,10],
   "step_size": 0.05
 },
 "Stopping":
 {
   "tolerance": 0.01,
   "iterations": 100
 }
}

Save that as "parameters01.json" and load as:

library(RJSONIO)
Params <- fromJSON("parameters.json")

and you're off and running. (NB: I like to use unique version #s within my parameters files, just so that I can identify the set later, if I'm looking at the "parameters" list within R.) Just call your script and point to the parameters file, e.g.:

Rscript --vanilla MyScript.R parameters01.json

then, within the program, identify the parameters file from the commandArgs() function.

Later, you can break out code into functions and packages, but this is probably the easiest way to make a vanilla script generalizeable in the short term, and it's a good practice for the long-term, as code should be separated from the specification of run/dataset/experiment-dependent parameters.

Edit: to be more precise, I would even specify input and output directories or files (or naming patterns/prefixes) in the JSON. This makes it very clear how one set of parameters led to one particular output set. Everything in between is just code that runs with a given parametrization, but the code shouldn't really change much, should it?


Update: Three months, and many thousands of runs, wiser than my previous answer, I'd say that the external storage of parameters in JSON is useful for 1-1000 different runs. When the parameters or configurations number in the thousands and up, it's better to switch to using a database for configuration management. Each configuration may originate in a JSON (or XML), but being able to grapple with different parameter layouts requires a larger scale solution, for which a database like SQLite (via RSQLite) is a fine solution.

I realize this answer is overkill for the original question - how to repeat work only a couple of times, with a few parameter changes, but when scaling up to hundreds or thousands of parameter changes in ongoing research, more extensive tools are necessary. :)

Iterator
  • 20,250
  • 12
  • 75
  • 111
  • This was a hugely useful answer for me. I didn't even go all the way to `JSON` but just used a small `.R` script in a config directory. The grid engine sets an environmental variable with the job name in it, so I can read that and have it pull the configuration file of the same name for that run. Works well for my needs; it sounds like yours are much more substantial! – Ari B. Friedman May 29 '14 at 11:04
  • 1
    @AriB.Friedman glad to help! The DB approach seems to work well for the thousands and up. If nothing else, you can think of parameter settings as analogous to experimental design - storing the parameters of the design is scalable in a DB, and ultimately we do want to know why different runs had different outcomes. – Iterator Jun 05 '14 at 10:41
3

I like to work with combination of a little shell script, a pdf cropping program and Sweave in those cases. That gives you back nice reports and encourages you to source. Typically I work with several files, almost like creating a package (at least I think it feels like that :) . I have a separate file for the data juggling and separate files for different types of analysis, such as descriptiveStats.R, regressions.R for example.

btw here's my little shell script,

 #!/bin/sh
 R CMD Sweave docSweave.Rnw
 for file in `ls pdfs`;
 do pdfcrop  pdfs/"$file" pdfs/"$file"
 done
 pdflatex docSweave.tex
 open docSweave.pdf 

The Sweave file typically sources the R files mentioned above when needed. I am not sure whether that's what you looking for, but that's my strategy so far. I at least I believe creating transparent, reproducible reports is what helps to follow at least A strategy.

Matt Bannert
  • 27,631
  • 38
  • 141
  • 207
  • +1 I'll have to explore shell scripts more - I tend not to use them with R. This seems equivalent, or at least analogous, to using `source`. – Andrie Jul 05 '11 at 11:18
  • It all depends a little on the OS. I am not sure yet whether python, shell scripting, or apple script fits my needs best, but as far as I can judge it shell scripting with R seems really worth a look, in particular for automated reports. – Matt Bannert Jul 05 '11 at 13:45
2

Your third option is not so bad. I do this in many cases. You can build a bit more structure by putting the results of your pre-ample code in environments and attach the one you want to use for further analysis. An example:

    setup1 <- local({
          x <- rnorm(50, mean=2.0)
          y <- rnorm(50, mean=1.0)
          environment()
          # ...
        })

    setup2 <- local({
          x <- rnorm(50, mean=1.8)
          y <- rnorm(50, mean=1.5)
          environment()
          # ...
        })

attach(setup1) and run/source your analysis code

plot(x, y)
t.test(x, y, paired = T, var.equal = T)
...

When finished, detach(setup1) and attach the second one.

Now, at least you can easily switch between setups. Helped me a few times.

  • +1 This approach is really interesting. I generally try to avoid using `attach` but I like the idea of creating environments to work around this problem. I shall explore this further. – Andrie Jul 05 '11 at 11:14
  • I like this one, too, with the same caution about using 'attach' as Andrie has mentioned. What's neat about this is that it's more general than just parameter lists, and can include computations. – Iterator Jul 12 '11 at 15:57
1

I tend to push such results into a global list. I use Common Lisp but then R isn't so different.

whoplisp
  • 2,508
  • 16
  • 19
  • 1
    +1 Yes, this is what I did in the end. Create a list of variables that need to change and another list with results. Then switch local variables in and out of the list. – Andrie Jul 05 '11 at 11:16
1

Too late for you here, but I use Sweave a lot, and most probably I'd have used a Sweave file from the beginning (e.g. if I know that the final product needs to be some kind of report).

For repeating parts of the analysis a second and third time, there are then two options:

  • if the results are rather "independent" (i.e. should produce 3 reports, comparison means the reports are inspected side by side), and the changed input comes in the form of new data files, that goes into its own directory together with a copy of the Sweave file, and I create separate reports (similar to source, but feels more natural for Sweave than for plain source).

  • if I rather need to do the exactly same thing once or twice again inside one Sweave file I'd consider reusing code chunks. This is similar to the ugly for-loop.

    The reason is that then of course the results are together for the comparison, which would then be the last part of the report.

  • If it is clear from the beginning that there will be some parameter sets and a comparison, I write the code in a way that as soon as I'm fine with each part of the analysis it is wrapped into a function (i.e. I'm acutally writing the function in the editor window, but evaluate the lines directly in the workspace while writing the function).

Given that you are in the described situation, I agree with Nick - nothing wrong with source and everything else means much more effort now that you have it already as script.

cbeleites unhappy with SX
  • 13,717
  • 5
  • 45
  • 57
0

I can't make a comment on Iterator's answer so I have to post it here. I really like his answer so I made a short script for creating the parameters and exporting them to external JSON files. And I hope someone finds this useful: https://github.com/kiribatu/Kiribatu-R-Toolkit/blob/master/docs/parameter_configuration.md