Home > Uncategorized > My no loops in R hair shirt

My no loops in R hair shirt

Being professional involved with analyzing source code I get to work with a much larger number of programming languages than most people. There is a huge difference between knowing the intricate details of the semantics of a language and being able to fluently program in a language like a native developer. There are languages whose semantics I probably know better than nearly all its users and yet can only code in like a novice, and there are languages whose reference manual I might have read once and yet can write fluently.

I try not to learn new languages in which to write programs, they just clutter my brain. It can be very embarrassing having somebody sitting next to me while I write an example and not be able to remember whether the language I am using requires a then in its if-statement or getting the details of a print statement wrong; I am supposed to be a computer language expert.

Having decided to migrate from being a casual R user to being a native user (my current status is somebody who owns more than 10 books that make extensive use of R) I resolved to invest the extra time needed to learn how to write code the ‘R-way’ (eighteen months later I’m not sure that there is an ‘R way’ in the sense that could be said to exist in other languages, or if there is it is rather diffuse). One of my self-appointed R-way rules is that any operation involving every element of a vector should be performed using whole vector operations (i.e., no looping constructs).

Today I was analyzing the release history of the Linux kernel and wanted to get the list of release dates for the current version of the major branch; I had a list of dates for every release. The problem is that when a major release branch is started previous branches, now in support only mode, may continue to be maintained for some time, for instance after the version 2.3 branch was created the version 2.2 branch continued to have releases made for it for another five years.

The obvious solution to removing non-applicable versions from the release list is to sort on release date and then loop through the elements removing those whose version number was less than the version appearing before them in the list. In the following excerpt the release of 2.3.0 causes the following 2.2.9 release to be removed from the list, also versions 2.0.37 and 2.2.10 should be removed.

Version Release_date
2.2.8   1999-05-11
2.3.0   1999-05-11
2.2.9   1999-05-13
2.3.1   1999-05-14
2.3.2   1999-05-15
2.3.3   1999-05-17
2.3.4   1999-06-01
2.3.5   1999-06-02
2.3.6   1999-06-10
2.0.37   1999-06-14
2.2.10   1999-06-14
2.3.7   1999-06-21
2.3.8   1999-06-22

While this is an easy problem to solve using a loop, what is the R-way of solving it (use the xyz package would be the answer half said in jest)? My R-way rule did not allow loops, so a-head scratching I did go. On the assumption that the current branch version dates would be intermingled with releases of previous branches I decided to use simple pair-wise comparison (which could be coded up as a whole vector operation); if an element contained a version number that was less than the element before it, then it was removed.

Here is the code (treat step parameter was introduced later as part of the second phase tidy up; data here):

ld$Release_date=as.POSIXct(ld$Release_date, format="%d-%b-%Y")
ld.ordered=ld[order(ld$Release_date), ]
strip.support.v=function(version.date, step)
# Strip off the least significant value of the Version id
v = substr(version.date$Version, 1, 3)
# Build a vector of TRUE/FALSE indicating ordering of element pairs
q = c(rep(TRUE, step), v[1:(length(v)-step)] <= v[(1+step):length(v)])
# Only return TRUE entries
return (version.date[q, ])
h1=strip.support.v(ld.ordered, 1)

This pair-wise approach only partially handles the following sequence (2.2.10 is greater than 2.0.37 and so would not be removed).

2.3.6   1999-06-10
2.0.37   1999-06-14
2.2.10   1999-06-14
2.3.7   1999-06-21

The no loops rule prevented me iterating over calls to strip.support.v until there were no more changes.

Would a native R speaker assume there would not be many extraneous Version/Release_date pairs and be willing to regard their presence as a minor data pollution problem? If so I have some way to go before I might be able to behave as a native.

My next line of reasoning was that any contiguous sequence of non-applicable version numbers would probably be a remote island in a sea of applicable values. Instead of comparing an element against its immediate predecessor it should be compared against an element step back (I chose a value of 5).

h2=strip.support.v(h1, 5)

The original vector contained 832 rows, which was reduced to 745 and then down to 734 on the second step.

Are there any non-loop solutions that are capable of handling a higher density of non-applicable values? Do tell if you can think of one.

Update (a couple of days later)

Thanks to Charles Lowe, Wojtek and kaz_yos for their solutions using cummax, a function that I was previously unaware of. This was a useful reminder that what other languages do in the syntax/semantics R surprisingly often does via a function call (I’m still getting my head around the fact that a switch-statement is implemented via a function in R); as a wannabe native R speaker I need to remove my overly blinked language approach to problems and learn a lot more about the functions that come as part of the base system

  1. Charles Lowe
    July 28th, 2012 at 05:31 | #1

    Here’s an attempt to use `numeric_version` to handle version parsing & ordering, and `subset` with `cummax` selecting monotonically increasing versions after sorting by date:

    ld = read.csv(‘http://www.coding-guidelines.com/R_code/Linux-days.csv’)
    ld = subset(ld, !Version %in% c(‘2.4.11-dontuse’, ‘3.2-rc1′))
    ld = transform(ld, VerId=as.integer(factor(Version, format(sort(numeric_version(Version))))))
    subset(ld[order(as.Date(ld$Release_date, ‘%d-%b-%Y’)), ], cummax(VerId) == VerId)

  2. Wojtek
    July 28th, 2012 at 06:21 | #2

    Here you can convert version number to numeric (in function strip.support.v).

    v = as.numeric(substr(version.date$Version, 1, 3))
    vc = cummax(v)
    q = v >= vc

    Second argument step is not necessary.

  3. July 28th, 2012 at 09:14 | #3

    ## I am a public health student. I only code in R and some bash.
    ## I wonder if it does what you want.

    linux.days <- read.csv("http://www.coding-guidelines.com/R_code/Linux-days.csv&quot😉

    linux.days <- within(linux.days, {
    Release_date <- as.Date(Release_date, "%d-%b-%Y")
    version.simple <- as.numeric(substr(Version, 1, 3)) # Convert it to numerics

    linux.days <- linux.days[order(linux.days$Release_date),] # Order by release date

    linux.days <- within(linux.days, {
    version.cummax <- cummax(version.simple) # Cumulative max vector
    version.reversed <- version.simple < version.cummax # version.simple smaller than cummax?

    linux.days.stripped <- linux.days[linux.days$version.reversed == FALSE,] # Delete if smaller than cummax


  4. July 28th, 2012 at 12:15 | #4

    Hi. I remember looking at a similar problem on stackoverflow: http://stackoverflow.com/questions/11423846/smoothing-a-sequence-without-using-a-loop-in-r/11424478#11424478 where after I scratched my head like you for a while, I concluded there was no other way than use a loop. At best, you will be able to hide the loop under a function (people have already mentioned cummax for your particular problem), like pushing it under the rug, but no purely vectorized solution I can think of.

  5. James M. Ward
    July 30th, 2012 at 05:08 | #5

    My first suggestion is to use mixedsort() and mixedorder() from the gtools package. It provides the version/semantic sorting found in newer unix sort functions (using the -V switch.) Don’t try to parse the version into numeric values. Then in my hands, I just needed to test: does there exist any higher-version entry with an earlier date than this version entry — if that makes any sense. That logic is vectorized. Fancy plotting tricks using ordered factors helped me produce a reasonable looking plot, though I’m not sure exactly what your target is, how general you want the solution to be for future linux numbering schemes.

  1. No trackbacks yet.

A question to answer *