Thursday, July 02, 2009

R String processing

Note: Nowadays, stringr's str_match solves this problem, nicely. Another option is gsubfn's very R-ish strapply.

Here's a little vignette of data munging using the regular expression facilities of R (aka the R-project for statistical computing). Let's say I have a vector of strings that looks like this:

> coords
[1] "chromosome+:157470-158370" "chromosome+:158370-158450" "chromosome+:158450-158510"
[4] "chromosome+:158510-159330" "chromosome-:157460-158560" "chromosome-:158560-158920"

What I'd like to do is parse these out into a data.frame with a column for each of sequence, strand, start, end. A regex that would do that kind of thing looks like this: (.*)([+-]):(\d+)-(\d+). R does regular expressions, but it's missing a few pieces. For example, in python you might say:

import re

coords = """
chromosome+:157470-158370
chromosome+:158370-158450
chromosome+:158450-158510
chromosome+:158510-159330
chromosome-:157460-158560
chromosome-:158560-158920
"""

regex = re.compile("(.*)([+-]):(\\d+)-(\\d+)")

for line in coords.split("\n"):
 line = line.strip()
 if (len(line)==0): continue
 m = regex.match(line)
 if (m):
  seq = m.group(1)
  strand = m.group(2)
  start = int(m.group(3))
  end = int(m.group(4))
  print "%s\t%s\t%d\t%d" % (seq, strand, start, end)

As far as I've found, there doesn't seem to be an equivalent in R to regex.match, which is a shame. The gsub function supports capturing groups in regular expressions, but isn't very flexible about what you do with them. One way to solve this problem is to use gsub to pull out each individual column. Not efficient, but it works:

> coords.df = data.frame(
 seq=gsub("(.*)([+-]):(\\d+)-(\\d+)", "\\1", row.names(m), perl=T),
 strand=gsub("(.*)([+-]):(\\d+)-(\\d+)", "\\2", row.names(m), perl=T),
 start=as.integer(gsub("(.*)([+-]):(\\d+)-(\\d+)", "\\3", row.names(m), perl=T)),
 end=as.integer(gsub("(.*)([+-]):(\\d+)-(\\d+)", "\\4", row.names(m), perl=T)))
> coords.df
         seq strand  start    end
1 chromosome      + 157470 158370
2 chromosome      + 158370 158450
3 chromosome      + 158450 158510
4 chromosome      + 158510 159330
5 chromosome      - 157460 158560
6 chromosome      - 158560 158920

It seems strange that R doesn't have a more direct way of accomplishing this. I'm not an R expert, so maybe it's there and I'm missing it. I guess it's not called the R project for string processing, but still... By the way, if you're ever tempted to name a project with a single letter, consider the poor schmucks trying to google for help.

6 comments:

  1. What I would do is use one of the apply family functions. Function to be used by apply would extract each component (using regular expressions) and stick it into a [1, 3] data frame. At the end you can arrange your result into a data frame (for instance, if you get a list of [1, 3] data frames, you can stick them together with do.call("rbind", my.list). But, there's more than a 100 ways to skin a cat...

    ReplyDelete
  2. Thanks for the tip, Roman.

    My issue was that I didn't know how to extract all the captured pieces of my original strings. gsub gives you a way to grab one field at a time, whereas most languages give you the ability to grab several captured blocks out of a single match (as in the python example). If that's possible, it would be good to know.

    ReplyDelete
  3. Richard Hertz8/25/2011 3:58 PM

    Same topic addressed on stack overflow w/o any resolution:

    http://stackoverflow.com/questions/952275/regex-group-capture-in-r

    ReplyDelete
  4. Looks like this is finally coming in R 2.14!

    See: Toby Hocking's Fast, named capture regular expressions in R 2.14

    ReplyDelete
  5. What about this solution :
    coords <- c("chromosome+:157470-158370", "chromosome+:158370-158450", "chromosome+:158510-159330", "chromosome-:157460-158560")

    ligne <- strsplit(coords, "\n")
    regex <- "(.*)([+-]):(\\d+)-(\\d+)"

    results <- as.data.frame(sapply(1:4, function(x) sub(pattern = regex, replacement= paste("\\", x, sep = ""), x = coords)), stringsAsFactors = FALSE)

    results

    V1 V2 V3 V4
    1 chromosome + 157470 158370
    2 chromosome + 158370 158450
    3 chromosome + 158510 159330
    4 chromosome - 157460 158560


    I'm pretty sure that we can almost do everything python do in R may be it's not just as straightforward as the python solution sometime. But this is R

    ReplyDelete
  6. @Dickoa - Nice one! That looks like a very R-ish solution. I had to try it out to figure out what your code is doing - pulling one group out of the original string for each pass through the sapply. Being a stickler, I have to point out that pulling out four fields requires four applications of the regex, instead of one, but that probably won't matter in practice.

    BTW, I recently put together a quick crib-sheet on R string functions.

    When R 2.14 comes out at the end of October, R's string manipulation capability is slated to get a big boost.

    ReplyDelete