Эффективный метод преобразования множества диапазонов в кадре данных с частотой отдельных элементов?

Я работаю в R. У меня есть кадр данных, который содержит начальную и конечную позиции на хромосоме (где целое число представляет собой координату на хромосоме) Пример:

start     end
1         5
3         7
4         10
12        7            (inverted is also allowed)
8         15

Я хочу, чтобы подсчитать, сколько раз координата присутствует во всех этих диапазонах. Итак, для приведенного выше примера выход будет следующим:

position     count
1            1
2            1
3            2
4            3
5            3
6            2
7            3
8            3
9            3
10           3
11           2
12           2
13           1
14           1
15           1

У меня есть 62000+ таких диапазонов, где каждый диапазон составляет не менее 1000 позиций. Я знаю, как сделать это преобразование, но я не знаю, как это сделать эффективно, то есть через пару секунд.

Текущий (неэффективный код)

positions <- c()
for(i in seq(nrow(a))){
  positions <- c(positions, seq(a[i,3], a[i,4]))
}
table(positions)

"a" - это мой кадр данных, а начальная и конечная координаты находятся в третьем и четвертом столбцах соответственно.

Один из столбцов в кадре данных содержит символы, поэтому для использования apply мне нужно было бы создать новый кадр данных (потребляя лишнее пространство) или потребоваться преобразовать в целые числа внутри функции apply (дополнительно время). Извините, за то, что вы не сообщили об этом раньше.

Ответы

Ответ 1

Для очень быстрого кода с data.table см. ответ от docendo discimus
(+ ориентир)

Вот пример некоторых других решений:

set.seed(42)
N <- 1000
df <- data.frame(start=sample.int(10*N, N))
df$end <- df$start + sample(3:20, N, rep=TRUE) 

library("microbenchmark")
microbenchmark(unit = "relative",
ori =  { positions <- c()
  for(i in seq(nrow(df))){
    positions <- c(positions, seq(df[i,1], df[i,2]))
  }
  table(positions) },
a  = table(unlist(apply(df, 1, function(x) x[1]:x[2]))),  # my solution, similar: KenS, EricSchutte
m1 = table(unlist(mapply(seq, df$start, df$end))),        # my variant of Sotos' solution
m2 = table(unlist(mapply(':', df$start, df$end))),        # my variant of Sotos' solution
M1 = table(unlist(Map(seq, df$start, df$end))),           # my variant of Sotos' solution
M2 = table(unlist(Map(':', df$start, df$end))),           # Sotos
l  = table(unlist(lapply(seq_len(nrow(df)), function(i) seq(df$start[i], df$end[i])))),    # lmo
t  = { temp <- unlist(lapply(seq_len(nrow(df)), function(i) seq(df$start[i], df$end[i])))  # lmo tabulate()
cbind(sort(unique(temp)), tabulate(temp)) },
d  = table(do.call(c, mapply(seq, df$start, df$end))),     # @989 (comment to the answer from Sotos)
dd = table(do.call(c, mapply(seq.int, df$start, df$end))), # docendo discimus (comment to this answer)
f  = {  pos <- data.frame(x=(min(df):max(df)),n=0)         # Andrew Gustar

for(i in seq_along(df$start)){
  low=min(df$start[i])-pos$x[1]+1
  high=max(df$end[i])-pos$x[1]+1
  pos$n[low:high] <- pos$n[low:high]+1
} }
)
# Unit: relative
# expr      min       lq     mean   median       uq       max neval    cld
#  ori 7.163767 7.219099 7.573688 7.379160 7.912435  7.899586   100     e 
#    a 1.194627 1.194855 1.211432 1.209485 1.213118  1.711994   100 a     
#   m1 1.645659 1.660294 1.711141 1.686973 1.710461  2.217141   100  b    
#   m2 1.005302 1.007125 1.017115 1.009618 1.017207  1.576201   100 a     
#   M1 1.642688 1.645174 1.733173 1.673924 1.686253  2.218028   100  b    
#   M2 1.000000 1.000000 1.000000 1.000000 1.000000  1.000000   100 a     
#    l 3.487924 3.512732 3.801530 3.665725 4.188701  4.216375   100    d  
#    t 2.670636 2.711345 2.961449 2.869190 3.066150  3.745984   100   c   
#    d 1.652376 1.650798 1.721377 1.665901 1.712064  2.187129   100  b    
#   dd 1.040941 1.045652 1.060601 1.047534 1.053305  1.592163   100 a     
#    f 8.287098 8.486854 9.052884 9.046376 9.126318 25.210722   100      f

Решение с tabulate() выдает предупреждения.

Ответ 2

Одна идея,

as.data.frame(table(unlist(Map(`:`, df$start, df$end))))

#   Var1 Freq
#1     1    1
#2     2    1
#3     3    2
#4     4    3
#5     5    3
#6     6    2
#7     7    3
#8     8    3
#9     9    3
#10   10    3
#11   11    2
#12   12    2
#13   13    1
#14   14    1
#15   15    1

Ответ 3

Это примерно тот же алгоритм, который вы используете, но должен быть быстрее.

myNums <- unlist(lapply(seq_len(nrow(df)), function(i) seq(df$start[i], df$end[i])))

table(myNums)
myNums
 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 
 1  1  2  3  3  2  3  3  3  3  2  2  1  1  1

Еще более быстрый метод заключается в использовании tabulate, а не table. Например,

temp <- unlist(lapply(seq_len(nrow(df)), function(i) seq(df$start[i], df$end[i])))
cbind(sort(unique(temp)), tabulate(temp))

который возвращает матрицу

      [,1] [,2]
 [1,]    1    1
 [2,]    2    1
 [3,]    3    2
 [4,]    4    3
 [5,]    5    3
 [6,]    6    2
 [7,]    7    3
 [8,]    8    3
 [9,]    9    3
[10,]   10    3
[11,]   11    2
[12,]   12    2
[13,]   13    1
[14,]   14    1
[15,]   15    1

работает на 50% быстрее для данного набора данных.

Unit: microseconds
     expr     min       lq     mean   median       uq     max neval cld
    table 223.233 237.6305 250.0329 245.8985 253.4545 423.944   100   b
 tabulate 142.835 159.0860 166.9775 167.3540 175.7650 195.009   100  a

Ответ 4

Я предлагаю решение data.table, так как мы заинтересованы в производительности. Подход выглядит следующим образом:

library(data.table)
setDT(df)
df[, list(seq.int(start, end)), by = 1:nrow(df)][, .N, by = V1]

И он работает очень хорошо по сравнению с другими решениями, несмотря на операцию по строке.

Здесь находится эталон для строк 1e4:

set.seed(42)
N <- 1e4
vals = 1:100
df <- data.frame(start=sample(vals, N, replace = TRUE), end = sample(vals, N, replace = TRUE))
library(data.table)
library("microbenchmark")
dt <- copy(df)
setDT(dt)

microbenchmark(unit = "relative", times = 10,
               jogo = table(unlist(Map(seq, df$start, df$end))),           # jogo
               sotos = table(unlist(Map(':', df$start, df$end))),           # Sotos
               lmo  = table(unlist(lapply(seq_len(nrow(df)), function(i) seq(df$start[i], df$end[i])))),    # lmo
               orig_989  = table(do.call(c, mapply(seq, df$start, df$end))),     # @989 (comment to the answer from Sotos)
               mod_989  = table(do.call(c, mapply(seq.int, df$start, df$end))), # docendo discimus (comment to this answer)
               dd = dt[, list(seq.int(start, end)), by = 1:nrow(dt)][, .N, by = V1]
)

Unit: relative
     expr       min        lq      mean    median        uq       max neval cld
     jogo  8.794179  8.735461 19.226146  8.584978  8.637774 52.782168    10  ab
    sotos 10.669810 10.623685  8.984351 10.437937 10.164045  4.846189    10  ab
      lmo 21.319154 21.117393 27.452902 22.558436 22.913901 43.403024    10   b
 orig_989  9.190209  8.725191  7.532509  8.730023  8.516305  3.948500    10  ab
  mod_989  5.372087  5.097636  5.067462  5.305532  6.214493  3.188091    10  ab
       dd  1.000000  1.000000  1.000000  1.000000  1.000000  1.000000    10  a 

Ответ 5

Я создаю последовательность для каждой строки в df, например. c(1,2,3,4,5) для первой строки. используя:

all.pos <- apply(df, 1, function(x){x[1]:x[2]})
all.pos <- unlist(all.pos)
#  1  2  3  4  5  3  4  5  6  7  4  5  6  7  8  9 10 12 11 10  9  8  7  8  9 
#  10 11 12 13 14 15

Таблица подсчитывает, как часто каждая позиция возникает в all.pos.

table(all.pos)
# 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 
# 1  1  2  3  3  2  3  3  3  3  2  2  1  1  1 

Ответ 6

Другой подход, избегая table, перебирая диапазоны и добавляя 1 к соответствующим значениям каждый раз...

ranges <- data.frame(start=c(1,3,4,12,8), end=c(5,7,10,7,15) )

pos <- data.frame(x=(min(ranges):max(ranges)),n=0)

for(i in seq_along(ranges$start)){
  low=min(ranges$start[i])-pos$x[1]+1
  high=max(ranges$end[i])-pos$x[1]+1
  pos$n[low:high] <- pos$n[low:high]+1
}

pos
    x n
1   1 1
2   2 1
3   3 2
4   4 3
5   5 3
6   6 2
7   7 3
8   8 3
9   9 3
10 10 3
11 11 2
12 12 2
13 13 1
14 14 1
15 15 1

Ответ 7

Первое, что придумало, может быть не самым лучшим, но при использовании применения вещи должны значительно ускориться.

df <- data.frame(start=c(1,3,4,12,8), end=c(5,7,10,7,15) )

positions <- apply(df, 1, function (x) {
  seq(x[1], x[2])
})

table(unlist(positions))

дает..

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 
 1  1  2  3  3  2  3  3  3  3  2  2  1  1  1