2

I have a data.frame which specifies linear intervals (along chromosomes), where each interval is assigned to a group:

df <- data.frame(chr = c(rep("1",5),rep("2",4),rep("3",5)),
                 start = c(seq(1,50,10),seq(1,40,10),seq(1,50,10)),
                 end = c(seq(10,50,10),seq(10,40,10),seq(10,50,10)),
                 group = c(c("g1.1","g1.1","g1.2","g1.3","g1.1"),c("g2.1","g2.2","g2.3","g2.2"),c("g3.1","g3.2","g3.2","g3.2","g3.3")),
                 stringsAsFactors = F)

I'm looking for a fast way to collapse df by chr and by group such that consecutive intervals along a chr that are assigned to the same group are combined and their start and end coordinates are modified accordingly.

Here's the desired outcome for this example:

res.df <- data.frame(chr = c(rep("1",4),rep("2",4),rep("3",3)),
                     start = c(c(1,21,31,41),c(1,11,21,31),c(1,11,41)),
                     end = c(c(20,30,40,50),c(10,20,30,40),c(10,40,50)),
                     group = c("g1.1","g1.2","g1.3","g1.1","g2.1","g2.2","g2.3","g2.2","g3.1","g3.2","g3.3"),
                     stringsAsFactors = F)
dan
  • 6,048
  • 10
  • 57
  • 125

2 Answers2

2

Edit: To account for the consecutive requirement you can use the same approach as earlier but add an extra grouping variable based on consecutive values.

library(dplyr)

df  %>%
  group_by(chr, group, temp.grp = with(rle(group), rep(seq_along(lengths), lengths))) %>%
  summarise(start = min(start),
            end = max(end)) %>%
  arrange(chr, start) %>%
  select(chr, start, end, group)

# A tibble: 11 x 4
# Groups:   chr, group [9]
   chr   start   end group
   <chr> <dbl> <dbl> <chr>
 1 1         1    20 g1.1 
 2 1        21    30 g1.2 
 3 1        31    40 g1.3 
 4 1        41    50 g1.1 
 5 2         1    10 g2.1 
 6 2        11    20 g2.2 
 7 2        21    30 g2.3 
 8 2        31    40 g2.2 
 9 3         1    10 g3.1 
10 3        11    40 g3.2 
11 3        41    50 g3.3 
Ritchie Sacramento
  • 29,890
  • 4
  • 48
  • 56
  • Thanks @H 1. I think my previous example wasn't clear enough and unfortunately your answer doesn't solve the problem. – dan Mar 26 '19 at 00:55
1

A different tidyverse approach could be:

df %>%
 gather(var, val, -c(chr, group)) %>%
 group_by(chr, group) %>%
 filter(val == min(val) | val == max(val)) %>%
 spread(var, val)

  chr   group   end start
  <chr> <chr> <dbl> <dbl>
1 1     g1.1     20     1
2 1     g1.2     30    21
3 1     g1.3     50    31
4 2     g2.1     10     1
5 2     g2.2     20    11
6 2     g2.3     40    21
7 3     g3.1     10     1
8 3     g3.2     40    11
9 3     g3.3     50    41

Or:

df %>%
 group_by(chr, group) %>%
 summarise_all(funs(min, max)) %>%
 select(-end_min, -start_max)

  chr   group start_min end_max
  <chr> <chr>     <dbl>   <dbl>
1 1     g1.1          1      20
2 1     g1.2         21      30
3 1     g1.3         31      50
4 2     g2.1          1      10
5 2     g2.2         11      20
6 2     g2.3         21      40
7 3     g3.1          1      10
8 3     g3.2         11      40
9 3     g3.3         41      50

A solution, using also rleid() from data.table, to the updated post could be:

df %>%
 group_by(chr, group, group2 = rleid(group)) %>%
 summarise_all(funs(min, max)) %>%
 select(-end_min, -start_max)

   chr   group group2 start_min end_max
   <chr> <chr>  <int>     <dbl>   <dbl>
 1 1     g1.1       1         1      20
 2 1     g1.1       4        41      50
 3 1     g1.2       2        21      30
 4 1     g1.3       3        31      40
 5 2     g2.1       5         1      10
 6 2     g2.2       6        11      20
 7 2     g2.2       8        31      40
 8 2     g2.3       7        21      30
 9 3     g3.1       9         1      10
10 3     g3.2      10        11      40
11 3     g3.3      11        41      50
tmfmnk
  • 38,881
  • 4
  • 47
  • 67
  • Thanks @tmfmnk. I think my previous example wasn't clear enough and unfortunately your answer doesn't solve the problem. – dan Mar 26 '19 at 00:55