R and MPI on Ohio Supercomputer Center’s Oakley cluster

A few years ago, I wrote a short guide to Using R and snow on the Ohio Supercomputer Center’s Glenn cluster. Several things have changed in the world of R since then (namely, the inclusion of the parallel package into the base system) and I have moved to using the Oakley cluster, so I thought it was time to write an update to that older post.

Installing Rmpi

To use parallel on the cluster, you will need to install Rmpi. First, login to the Oakley cluster with ssh and the supplied username and password:

$ ssh osuXXXX@oakley.osc.edu

Once logged in, you will need to set up R to use Rmpi. You will also need to install snow, which is used behind the scenes by parallel for MPI jobs. You will want to install Rmpi manually, as there are a few extra parameters that need to be set. The newest version of Rmpi, as of writing, is 0.6-5. You can download it using wget:

$ wget http://cran.r-project.org/src/contrib/Rmpi_0.6-5.tar.gz

With Rmpi downloaded, you can prepare to install it. Because you have to set some custom flags, the easiest way to do this is to create a short executable script

$ nano compile_Rmpi.sh

This will open nano, a text editor. Paste the following into the editor (modifying the Rmpi version for the one you are installing):


    --configure-vars="CPPFLAGS=-I${MPICH_HOME}/include LDFLAGS=-L${MPICH_HOME}/lib" \
    --configure-args="--with-Rmpi-include=${MPICH_HOME}/include \
    --with-Rmpi-libpath=${MPICH_HOME}/lib --with-Rmpi-type=MPICH2" \

Once hit ctrl-X, Y, then enter to save the file. Now make it executable:

$ chmod +x compile_Rmpi.sh

Now you will need to create a .R/Makevars file indicating which compiler to use.

$ mkdir .R
$ nano .R/Makevars

In the Makevars file, enter


Save and exit. Now create the .Renviron file that will direct R to create a local R library repository.

$ echo 'R_LIBS_USER="~/R/library"' > .Renviron
$ mkdir -p R/library

Now you are ready to load the R module and compile Rmpi.

$ module load R
$ ./compile_Rmpi.sh

Now that Rmpi is installed, you start R and install snow and rlecuyer as you would normally.

$ R
R> install.packages(c("snow", "rlecuyer"))

Making bash more pleasant

As an unrelated aside for those new to bash and the command line: If you want a better experience using bash (the shell that is used by default), you can create a file called .bashrc and enter the following (updating the username in the export PATH line to reflect your username) to get color highlighting and a meaningful prompt:

$ nano .bashrc
### ------------------------------------------------------------------------
### default environment variables
### ------------------------------------------------------------------------

export PATH=.:$PATH:/home/osuXXXX/bin

# UTF-8
export LANG="en_US.UTF-8"
export LC_CTYPE="en_US.UTF-8"
export LC_ALL="en_US.UTF-8"

### ------------------------------------------------------------------------
### aliases
### ------------------------------------------------------------------------

alias ls="ls --color=auto"
alias ll="ls -l -A --color=auto -h"

### ------------------------------------------------------------------------
### ui
### ------------------------------------------------------------------------

# username colors, green for non-root, red for root
if [ ${UID} -eq 0 ]; then

# components

# prompt

And create a .bash_profile file to make sure .bashrc is run at login:

$ nano .bash_profile
if [ -f ~/.bashrc ]; then
   . ~/.bashrc

Using MPI with the Cluster

Create a directory for the test files.

$ mkdir test

Now, change to that directory and create the batch job submission script as well as the R script.

$ cd test
$ nano test.job
#PBS -l walltime=00:01:00
#PBS -l nodes=1:ppn=12
#PBS -N test
#PBS -S /bin/bash
#PBS -j oe
#PBS -m abe
#PBS -M morgan.746@osu.edu
#PBS -A PAA0014

set echo

export TEST=${HOME}/test
pbsdcp -r ${TEST}/test.R $TMPDIR

module load R
mpiexec ~/R/library/snow/RMPISNOW < test.R

pbsdcp -g -r result.RData ${TEST}/

Now create the R script, test.R:

$ nano test.R

fn <- function(n) {
  sample(1:10, n, replace=TRUE)

cl <- makeCluster(type="MPI")
result <- parLapply(cl, 1:12, fn)
save(result, file="result.RData")

Submit your job using qsub:

$ qsub test.job

The prefix, 3542961, is a unique job number and will be different each time you submit a job. To check the status of the job, as well as any others you may have submitted, use qstat -u:

$ qstat -u

                                                                       Req'd  Req'd   Elap
Job ID               Username    Queue    Jobname  SessID NDS   TSK    Memory Time  S Time
-------------------- ----------- -------- -------- ------ ----- ------ ------ ----- - -----
3542961.oak-batc     osu6738     serial   test      13555     1     12   48gb 00:01 R   -- 

When the job has run, the result.RData file will be in the test directory. Open
it with R to verify that everything worked.

$ cd ~/test
$ module load R
$ R
R> load("result.RData")
R> result
[1] 2
[1]  8 10
[1]  8 10 10
[1] 1 7 3 9
[1] 10  4  8  2  1
[1] 4 2 7 3 1 5
[1] 5 5 1 4 6 1 3
[1] 10  3  7  8  3  2  7  2
[1]  9  7  6  9  1 10  9  8  8
[1]  2  1  7  8  7  1  9 10  9  9
[1]  3  4  3  1  2  2 10  6  9  9  2
[1] 10  2  5  4  1  9  7  9 10  7  5  8

If anything went wrong, details will be contained in the output file, test.o3542961. The file name will change for each job. You can view the file using less.

$ less test.o3542961

Press q to exit. Much more detail on how to create job scripts and submit them
can be found on the OSC site: OSC batch processing.

Posted in R | 1 Comment

Boolean 3 (finally) on CRAN

I have finally managed to get boolean3 accepted to CRAN. You can find it here: boolean3 on CRAN. To summarize:

boolean3 provides a means of estimating partial-observability binary 
response models following boolean logic. boolean3 was developed by 
Jason W. Morgan under the direction of Bear Braumoeller with support from 
The Ohio State University's College of Social and Behavioral Sciences. The 
package represents a significant re-write of the original boolean 
implementation developed by Bear Braumoeller, Ben Goodrich, and Jacob Kline.

For a more detailed overview of the package, see my previous post.

Posted in Data analysis, R | Leave a comment

The Latent Path Model for Social Networks: Polmeth 2013 poster

I was lucky to have the chance to attend Polmeth 2013 at the University of Virgina this July. In addition to presenting a paper with Luke Keele, I also presented a poster related to my dissertation project, titled “The Latent Path Model for Social Networks”. An earlier draft of the project was also presented as a paper at Polnet 2013 at Indiana University.

The full abstract:

This paper presents a method of estimating political actors’ positions in a latent social space from dynamic network data. The proposed latent path model is a natural extension of the latent space model for static networks (Hoff, Raftery, and Handcock 2002), allowing political actors’ locations in the social space to shift over time to reflect the changes in the structure of ties in the network. Unlike alternative models of (Purnamrita and Moore 2005; Ward, Ahlquist, and Rozenas 2013), the proposed model does not require a Markov assumption and a lag structure need not be specified. The proposed model is validated through Monte Carlo simulations and applied to Fowler’s Senate cosponsorship data (reported below) and Polish party switching (forthcoming).

The Polmeth 2013 poster: Latent Path Model for Social Networks.

In its current form, the model is estimated with maximum likelihood. However, I ran into some convergence problems with larger networks, such as the Polish party switching data mentioned in the abstract. I have begun testing the model in Stan. Preliminary results are encouraging, though I am still wrestling with issues of identification.


  • Hoff, Peter D., Adrian E. Raftery, and Mark S. Handcock. (2002). “Latent Space Approaches to Social Network Analysis”. JASA 97(460): 1090–1098.
  • Sarkar, Purnamrita and Andrew W. Moore. (2005). “Dynamic Social Network Analysis using Latent Space Models”. SIGKDD Explorations 7(2): 31–40.
  • Ward, Michael D., John S. Ahlquist, and Arturas Rozenas. (2013). “Gravity’s Rainbow: A Dynamic Latent Space Model for the World Trade Network”. Network Science 1(1): 95–118.
Posted in Network analysis, Research | Leave a comment

Pearson’s r: Not a good measure of electoral persistence

Pearson’s product-moment correlation, \(r\), is an incredibly useful tool for getting some idea about how two variables are (linearly) related. But there are times when using Pearson’s \(r\) is not appropriate and, even if linearity and all other assumptions hold, using it may lead one astray.

I recently ran into such a situation when looking at the persistence of municipal-level vote shares over the last four Polish parliamentary elections. The plot below presents an example of some of the data I was looking at. It shows the share of the vote going to Samoobrona (Self-Defense of the Republic of Poland) by municipality in 2005 (horizontal axis) and 2007 (vertical axis). The linear regression line is shown in red and the 45-degree line is the dashed grey line.

SRP vote share by municipality, 2005-2007

From the plot it is clear that support for SRP collapsed between 2005 and 2007: had SRP’s performance held steady across the two elections, we would expect the data to cluster around the 45-degree line. Instead, in all but one or two municipalities, support was lower in 2007 than 2005. Accordingly, the data fall below the 45-degree line.

However, if I had ignored the plot and used Pearson’s \(r\) as the primary measure of persistence in vote share across years — something that is not uncommon in the study party politics — I would have been lead to believe that there was, in fact, a high degree of persistence between the two elections. As reported in the plot, \(r=0.72\), which certainly seems to be good evidence of a high degree of persistence in vote shares between 2005 and 2007. So, what’s going on?

Jason Wittenberg explains in a recent paper why Pearson’s \(r\) is not an appropriate measure in this case. The problem is the fact that Pearson’s \(r\) is a measure of linearity, not persistence — it captures how far from a linear regression line the data fall (the red line in the above plot). But what we really want is a measure of how far the data are from the 45-degree line. For this, Wittenberg suggests using Lin’s concordance correlation coefficient, \(r_c\). From the Wikipedia article, Lin’s \(r_c\) is defined as follows:

r_c = \cfrac{2s_{xy}}{s_x^2 + s_y^2 + (\bar{x} – \bar{y})^2}.


The relationship between \(r\) and \(r_c\) is straightforward. From the original paper:

  1. \(-1 \leq -|r| \leq r_c \leq |r| \leq 1\);
  2. \(r_c = 0\) if and only if \(r=0\);
  3. \(r_c = r\) if and only if \(s_x = s_y\); and
  4. \(r_c = \pm 1\) if and only if \(r = \pm 1\), \(s_x = s_y\), and \(\bar{x} = \bar{y}\).

(These come from Lin’s 1989 paper, p. 258, cited below. Note I have left out a couple equivalent conditions from (iv) and have changed the notation to match the definition of \(r_c\).)

As Wittenberg notes, condition (i) is potentially quite problematic for studies of electoral persistence. It says that the magnitude of \(r_c\) is always less than or equal to \(r\). In other words, Pearson’s \(r\) (almost) always inflates the amount of persistence present in the data. Evidence of this is seen the in next plot, which compares electoral performance of each major party in the 2005 and 2007 Polish parliamentary elections.

Party vote share by municipality, 2005-2007

Clearly, Pearson’s \(r\) and Lin’s \(r_c\) point to significantly different interpretations of the data when it comes to the issue of electoral persistence. As expected the magnitude of \(r_c\) is always lower than \(r\) — sometimes dramatically lower. In the case of SRP, for instance, \(r_c = 0.06\) compared to \(r = 0.72\) reported above. Also note, as expected given the conditions above, \(r_c\) is closest to \(r\) when regression line is near a 45-degree line.

So, what’s the take-away? First, Wittenberg is right in calling into question the use of Pearson’s \(r\) as a measure of electoral persistence (anyone know a better measure?). It simply doesn’t properly capture the concept. Second, always look at your data. In this case, I had never heard of Lin’s concordance correlation coefficient until I went looking for some way to measure the discordance I was seeing in the data. Had I just decided to construct a table of correlations, I may have believed that there was a great deal more persistence in electoral votes shares than actually existed.

R Code

Lin’s \(r_c\) can be calculated with the epi.ccc function in the epiR package. For those who may care, here is the code I used to make the second plot above:

xyplot(vote_share.2007 ~ vote_share.2005 | factor(party), 
       data=PartyW[party %!in% c("Other", "RP")], as.table=TRUE, 
       between=list(x=1,y=1), aspect=1, col=grey[5], cex=0.75, 
       xlab="2005", ylab="2007", xlim=c(-0.1,1),
       ylim=c(-0.1,1), strip=strip.custom(bg=grey[3]),
       panel=function(x, y, ...)
           r <- round(cor(x, y, use="pairwise.complete.obs"), 2)
           rc <- round(epi.ccc(x, y)[["rho.c"]][1,1], 2)
           panel.xyplot(x, y, ...)
           panel.abline(a=0, b=1, lty=2, col=grey[4], lwd=2)
           panel.abline(lm(y ~ x), col=red[6], lwd=2)
           panel.text(x=0, y=0.9, labels=bquote(r == .(r)), 
                      cex=0.75, pos=4)
           panel.text(x=0, y=0.8, labels=bquote(r[c] == .(rc)), 
                      cex=0.75, pos=4)


  • Lin, Lawrence I-Kuei. (1989). “A Concordance Correlation Coefficient to Evaluate Reproducibility.” Biometrics 45: 255–268.
  • Lin, Lawrence I-Kuei. (2000). “Correction: A Note on the Concordance Correlation Coefficient.” Biometrics 56: 324–325.
  • Wittenberg, Jason. (Oct. 2011). “How Similar Are They? Rethinking Electoral Congruence.” Quality & Quantity.
Posted in Data analysis, Graphics, Poland, Political parties, R | 3 Comments

R Tip: Avoid using T and F as synonyms for TRUE and FALSE

By default when you start R, T and F are defined as TRUE and FALSE. When I review other people’s code, I often see functions defined with arguments set to these values by default. This is a very bad idea. T and F are symbols that can be redefined, so it shouldn’t be assumed that they will always evaluate to TRUE and FALSE. Making that assumption can introduce bugs to the code that are very hard to track down.

For example, imagine you have defined a function to sample from a vector after transforming the data in some way:

my_sample <- function(x, size, rep=F) {
    x <- x^2  # a simple transform
    sample(x, size, replace=rep)

When you just start R, my_sample will work as intended because F is FALSE:

> my_sample(1:10, 8)
 [1]   4   1   9 100  49  16  36  81

But if you call this function from inside another function, or after hours of hacking at the console, this may not be the case. For instance, what if at some point in your R session you redefined F <- 2:

> my_sample(1:10, 8)
 [1]   9  49 100   4  64  64  81  36

This type of bug can be very dangerous because they are very hard to replicate and, worse yet, you may never even notice you have them. Luckily, such bugs are easy to avoid: never use T and F as synonyms for TRUE and FALSE. It doesn’t take much more effort to type out TRUE and FALSE, but if you find it onerous, get an editor with tab-completion.


Via email, Ananda Mahto suggests adding the following bit of code to the top of your scripts to provide a warning when T and F have been redefined.

if (!identical(T, TRUE)) 
    stop("'T' has been remapped to '", T, "'")
if (!identical(F, FALSE)) 
    stop("'F' has been remapped to '", F, "'")

This doesn’t solve the problem, of course, but at least you get some warning.

Posted in Hacking, R, Tip | 1 Comment

Closures in R: A useful abstraction

People who have been using R for any length of time have probably become accustomed to passing functions as arguments to other functions. From my experience, however, people are much less likely to return functions from their own custom code. This is too bad because doing so can open up a whole new world of abstraction that can greatly decrease the quantity and complexity of the code necessary to complete certain types of tasks. Here I provide some brief examples of how R programmers can utilize lexical closures to encapsulate both data and methods.

To begin with a simple example, suppose you want a function that adds 2 to its argument. You would likely write something like this:

add_2 <- function(y) { 2 + y }

Which does exactly what you would expect:

> add_2(1:10)
 [1] 3 4 5 6 7 8 9 10 11 12

Now suppose you need another function that instead adds 7 to its argument. The natural thing to do would be to write another function, just like add_2, where the 2 is replaced with a 7. But this would be grossly inefficient: if in the future you discover that you made a mistake and you in fact need to multiply the values instead of add them, you would be forced to change the code in two places. In this trivial example, that may not be much trouble, but for more complicated projects, duplicating code is a recipe for disaster.

A better idea would be to write a function that takes one argument, x, that returns another function which adds its argument, y, to x. In other words, something like this:

add_x <- function(x) {
    function(y) { x + y }

Now, when you call add_x with an argument, you will get back a function that does exactly what you want:

add_2 <- add_x(2)
add_7 <- add_x(7)

> add_2(1:10)
 [1] 3 4 5 6 7 8 9 10 11 12
> add_7(1:10)
 [1] 8 9 10 11 12 13 14 15 16 17

So far, this doesn’t appear too earth-shattering. But if you look closely at the definition of add_x, you may notice something odd: how does the return function know where to find x when it’s called at a later point?

It turns out that R is lexically scoped, meaning that functions carry with them a reference to the environment within which they were defined. In this case, when you call add_x, the x argument you provide gets attached to the environment for the return function. In other words, in this simple example, you can think of R as just replacing all instances of the x variable in the function to be returned with the value you specify when you called add_x.

Ok, so this may be a neat trick, but how this can be used more productively? For a slightly more complicated example, suppose you are performing some complex bootstrapping and, for efficiency, you pre-allocate container vectors to store the results. This is straightforward when you have just a single vector of results—all you have to do is remember to iterate an index counter each time you add a result to the vector.

for (i in 1:nboot) {
  bootmeans[i] <- mean(sample(data, length(data), replace=TRUE))

> mean(data)
 [1] 0.0196
> mean(bootmeans)
 [1] 0.0188

But suppose you want to track several different statistics, each requiring you to keep track of a different index variable. If your bootstrapping routine is even a little bit complicated, this can be tedious and prone to error. By using closures, you can abstract away all of this bookkeeping. Here is a constructor function that wraps a pre-allocated container vector:

make_container <- function(n) {
    x <- numeric(n)
    i <- 1

    function(value=NULL) {
        if (is.null(value)) {
        else {
            x[i] <<- value
            i <<- i + 1

When you call make_container with an argument, it pre-allocates a numeric vector of the specified length, n, and returns a function that allows you to add data to that vector without having to worry about keeping track of an index. If you don’t the argument to that return function is NULL, the full vector is returned.

bootmeans <- make_container(nboot)

for (i in 1:nboot)
 bootmeans(mean(sample(data, length(data), replace=TRUE)))

> mean(data)
 [1] 0.0196
> mean(bootmeans())
 [1] 0.0207

Here make_container is relatively simple, but it can be as complicated as you need. For instance, you may want to have the constructor function perform some expensive calculations that you would rather not do every time the function is called. In fact, this is what I have done in the boolean3 package to minimize the number of calculations done at every iteration of the optimization routine.

Posted in R | 1 Comment

Filtering a list with the Filter higher-order function

Last week markbulling over at Drunks & Lampposts posted a method of using sapply to filter a list by a predicate. Today the @RLangTip tip of the day was to use sapply similarly. This made makes me wonder if R‘s very useful higher-order functions aren’t as well known as they should be. In this case, the Filter higher-order function would be the tool to use. Filter works more or less like the *apply family of functions, but it performs the subsetting (the filtering) of a list based on a predicate in a single step.

As an example, let’s say we have a list of 1000 vectors, each of length 2 with \(x_1,\,x_2 \in [0,\,1]\), and we want to select only those vectors where the elements of the list sum to a value greater than 1. With Filter, this is all we have to do:

mylist <- lapply(1:1000, function(i) c(runif(1), runif(1)))
method.1 <- Filter(function(x) sum(x) > 1, mylist)

Which is at least a bit more transparent than the sapply alternative:

method.2 <- mylist[sapply(mylist, function(x) sum(x) > 1)]

In some very quick tests, I found no performance difference between the two approaches.

There are other useful higher-order functions. If you are interested, check out ?Filter.

Posted in R | Leave a comment

Announcing boolean3 (beta)

After entirely too long, I am happy to announce the beta release of boolean3, an R package for modeling causal complexity. The package can be downloaded at the following links:

(Please let me know if you have any trouble installing the Windows version. I didn’t have a Windows system handy when I built the package.)

The theoretical foundation for the package was developed by Bear Braumoeller in a 2003 Political Analysis piece:

Braumoeller, Bear F. (2003) 'Causal Complexity and the Study of Politics'. 
  Political Analysis 11(3): 209-233.

Additional theoretical content can be found at the Boolean Statistics homepage. Braumoeller and Carson have a 2011 paper titled “Political Irrelevance, Democracy, and the Limits of Militarized Conflict” in the Journal of Conflict Resolution that provides a useful example of the approach.

Summarizing from the boolean3 documentation and package description:

boolean3 provides a means of estimating partial-observability binary 
response models following boolean logic. boolean3 was developed by 
Jason W. Morgan under the direction of Bear Braumoeller with support from 
The Ohio State University's College of Social and Behavioral Sciences. The 
package represents a significant re-write of the original boolean 
implementation developed by Bear Braumoeller, Ben Goodrich, and Jacob Kline.

As is typically the case with “significant re-writes”, boolean3 breaks compatibility with previous versions (which can still be downloaded and installed from CRAN). Some of the many changes and enhancements include:

  • Removed dependence on Zelig.
  • Changed the method of specifying the model to me more consistent with the style of other R packages.
  • Improved performance. Many models can be estimated in 1/10th the time, or better, when compared to the original boolean package.
  • Added support for the optimizing methods available in the optimx package. This includes the ability to apply box constraints, which can be useful in maximizing boolean likelihoods.
  • Added multiprocessor/cluster support for bootstrapping using the snow package.
  • Genetic optimization using rgenoud also provides support for clustering with snow.
  • Plotting of predicted probabilities and likelihood profiles is now done with lattice.

Some things that are still missing from the package:

  • Documentation is still thin. There is enough in the help pages to get people started using the package, but more is needed.
  • Skewed logit (scobit) is not yet implemented. Most of the code has been written, but there are a few complications yet to be worked out.

If you are a current user of boolean, we’d love for you to try out boolean3. While a lot of bug-squashing has been done, there has been fairly little “real-world” testing. Bug reports and suggestions for changes and enhancements would be quite welcome, and can be emailed to Bear Braumoeller, me, or posted below in the comments. I will continue to post updates to the package as they are available.

Posted in Code, R | 2 Comments

Welford’s method for calculating the sample variance: An implementation in Scheme

John Cook has three entries up on his blog discussing the pitfalls of calculating the sample variance using the mathematical textbook definitions. He provides a Monte Carlo comparison of methods here, and a theoretical discussion here. He also provides a C++ implementation of Welford’s method here.

I recently started hacking on Scheme, writing a basic statistics library as a way to better understand the language (which is the most fun I have ever had programming). Below is a Scheme implementation of Welford’s method. For comparison, I have included a version that uses the textbook definition

s^2 = \cfrac{1}{n-1} \sum_{i=0}^n (x_i – \bar{x})^2.


Here is the code:

;;; Textbook definition
(define (variance x)
  (let ((mu (mean x)))
    (/ (sum (f64vector-map
             (lambda (x) (expt (- x mu) 2)) x))
        (- (f64vector-length x) 1))))

;;; Welford's method
(define (variance-welford x)
  (let loop ((n (f64vector-length x))
             (k 1)
             (m (f64vector-ref x 0))
             (s 0))
    (if (= k n)
        (/ s (fx- n 1))
        (let* ((xk (f64vector-ref x k))
               (mk (+ m (/ (- xk m) (fx+ k 1))))
               (sk (+ s (* (- xk m) (- xk mk)))))
          (loop n (fx+ k 1) mk sk)))))

I implemented these in Chicken Scheme, though the code should work in any Scheme that supports SRFI-4 homogeneous numeric vectors. The code could easily be modified to support standard Scheme vectors or lists.

I make no claims that this is the most efficient code, but it seems to work.

Posted in Code, Data analysis, Scheme | Leave a comment

Three free books for better programming in R (and any other language)

Like many users and producers of R packages, I have never had any formal training in computer science. I’ve come to to the conclusion that this is a serious omission in a professional researcher’s training. Computer scientists and professional hackers have learned a lot about effective, efficient programming over the last five decades and it’s past time academic researchers begin to learn from this experience.

To that end, here are three books which I think all users of R could benefit from reading:

Yes, I realize all of these are books that use Lisp/Scheme, and I realize the structure of Lisp can be a bit off-putting ((at '(first))). But, unless you want to learn Lisp, you shouldn’t try to read these books with the goal of understanding ever nuance of the syntax; rather, you should read them with an eye toward understand how a programming language should work for and not against you. For instance, each of these books shows, in a step-by-step approach, how seemingly complex programs can be effectively broken down into simple procedures that anyone with some programming experience can understand. A great deal of emphasis in these books is the important concepts of abstraction and non-duplication. If you’re like me and spend any significant amount of time going through various R packages, you’ll know what I mean when I say that the R community could benefit from less duplication and more abstraction in contributed packages (not to mention more comments!).

Posted in Hacking, R | 4 Comments