подмножество бавно в голяма матрица

Имам числов вектор с дължина 5 000 000

>head(coordvec)
[1] 47286545 47286546 47286547 47286548 47286549 472865

и 3 х 1 400 000 цифрова матрица

>head(subscores)
        V1       V2     V3
1 47286730 47286725  0.830
2 47286740 47286791  0.065
3 47286750 47286806 -0.165
4 47288371 47288427  0.760
5 47288841 47288890  0.285
6 47288896 47288945  0.225

Това, което се опитвам да постигна, е за всяко число в coordvec да намеря средната стойност на V3 за редове в подрезултати, в които V1 и V2 обхващат числото в coordvec. За да направя това, използвам следния подход:

results<-numeric(length(coordvec))
for(i in 1:length(coordvec)){
    select_rows <- subscores[, 1] < coordvec[i] & subscores[, 2] > coordvec[i]
scores_subset <- subscores[select_rows, 3]
results[m]<-mean(scores_subset)
}

Това е много бавно и ще отнеме няколко дни, за да завърши. Има ли по-бърз начин?

Благодаря,

Дан


person dlv    schedule 19.01.2013    source източник


Отговори (3)


Мисля, че има две предизвикателни части на този въпрос. Първият е намирането на припокриванията. Бих използвал пакета IRanges от Bioconductor (?findInterval в основния пакет може също да е полезно)

library(IRanges)

създаване на диапазони с ширина 1, представляващи координатния вектор, и набор от диапазони, представящи резултатите; Сортирам координатните вектори за удобство, като приемам, че дублиращите се координати могат да бъдат третирани еднакво

coord <- sort(sample(.Machine$integer.max, 5000000))
starts <- sample(.Machine$integer.max, 1200000)
scores <- runif(length(starts))

q <- IRanges(coord, width=1)
s <- IRanges(starts, starts + 100L)

Тук намираме кое query припокрива кое subject

system.time({
    olaps <- findOverlaps(q, s)
})

Това отнема около 7s на моя лаптоп. Има различни видове припокривания (вижте ?findOverlaps), така че може би тази стъпка изисква малко усъвършенстване. Резултатът е двойка вектори, индексиращи заявката и припокриващия се обект.

> olaps
Hits of length 281909
queryLength: 5000000
subjectLength: 1200000
       queryHits subjectHits 
        <integer>   <integer> 
 1             19      685913 
 2             35      929424 
 3             46     1130191 
 4             52       37417 

Мисля, че това е краят на първата сложна част, намирането на 281909 припокривания. (Не мисля, че отговорът data.table, предложен другаде, се отнася за това, въпреки че може да греша...)

Следващата предизвикателна част е изчисляването на голям брой средни стойности. Вграденият начин би бил нещо подобно

olaps0 <- head(olaps, 10000)
system.time({
    res0 <- tapply(scores[subjectHits(olaps0)], queryHits(olaps0), mean)
})

което отнема около 3,25 секунди на моя компютър и изглежда се мащабира линейно, така че може би 90 секунди за 280k се припокриват. Но мисля, че можем да изпълним тази таблица ефективно с data.table. Оригиналните координати са start(v)[queryHits(olaps)], така че

require(data.table)
dt <- data.table(coord=start(q)[queryHits(olaps)],
                 score=scores[subjectHits(olaps)])
res1 <- dt[,mean(score), by=coord]$V1

което отнема около 2,5 s за всички 280k припокривания.

Може да се постигне малко повече скорост, като се разпознае, че попаденията на заявката са подредени. Искаме да изчислим средна стойност за всяко изпълнение на попадения на заявка. Започваме със създаване на променлива, за да посочим края на всяко изпълнение на заявка

idx <- c(queryHits(olaps)[-1] != queryHits(olaps)[-length(olaps)], TRUE)

и след това изчислете кумулативните резултати в края на всяко бягане, дължината на всяко бягане и разликата между кумулативния резултат в края и в началото на бягането

scoreHits <- cumsum(scores[subjectHits(olaps)])[idx]
n <- diff(c(0L, seq_along(idx)[idx]))
xt <- diff(c(0L, scoreHits))

И накрая, средната стойност е

res2 <- xt / n

Това отнема около 0,6s за всички данни и е идентично с (макар и по-загадъчно от?) резултата data.table

> identical(res1, res2)
[1] TRUE

Оригиналните координати, съответстващи на средствата, са

start(q)[ queryHits(olaps)[idx] ]
person Martin Morgan    schedule 20.01.2013
comment
Благодаря. scoreHits ми дава вектор с по-къса дължина от olaps. как мога да свържа res2 с координатите, с които са свързани средните стойности? - person dlv; 20.01.2013
comment
Оригиналните координати са start(q)[queryHits(olaps)][idx]; Промених въпроса, за да включа това (малко по-ефективно) както в data.table, така и в по-сложен пример. - person Martin Morgan; 20.01.2013
comment
Здравей Мартин. Съгласен съм, припокриващите се диапазони са предизвикателство, не мога да се сетя за по-хубав начин от твоя. Може да откриете, че ключът е по-бърз от този без ключ. Първият setkey ще има цена, разбира се, но ако знаете със сигурност, че данните вече са сортирани (както тук iiuc), тогава можете да setattr(DT,"sorted",keycols) вместо това без разходи. - person Matt Dowle; 20.01.2013

Нещо като това може да е по-бързо:

require(data.table)
subscores <- as.data.table(subscores)

subscores[, cond := V1 < coordvec & V2 > coordvec]
subscores[list(cond)[[1]], mean(V3)] 

list(cond)[[1]] защото: "Когато i е едно име на променлива, то не се счита за израз на имена на колони и вместо това се оценява в обхвата на извикване." източник: ?data.table

person Michael    schedule 20.01.2013
comment
Можете ли да предоставите малко симулирани данни, за да покажете как работи това? Не мисля, че създаването на cond работи, когато coordvec не е скалар? - person Martin Morgan; 20.01.2013
comment
Прав си - прочетох погрешно структурата, предложена във въпроса. Това ще изисква по-сложен алгоритъм. Ще го публикувам, ако разбера. - person Michael; 20.01.2013
comment
@Michael Малко по-лесно i за придвижване е subscores[cond==TRUE, mean(V3)] или subscores[(cond), mean(V3)]. - person Matt Dowle; 20.01.2013
comment
Майкъл, сигурен съм, че съм правил нещо като тези припокриващи се групи и преди, но все още не съм го намерил. roll присъединяване при започване, преобръщане при краища, след това vecseq между може би. Или направете coorddev само ключ data.table и се присъединете по обратния начин. - person Matt Dowle; 20.01.2013

Тъй като вашият отговор не е лесно възпроизводим и дори да беше, никой от вашите subscores не отговаря на вашето булево условие, не съм сигурен дали това прави точно това, което търсите, но можете да използвате едно от семейството apply и функция .

myfun <- function(x) {
  y <- subscores[, 1] < x & subscores[, 2] > x
  mean(subscores[y, 3])
}

sapply(coordvec, myfun)

Можете също така да разгледате mclapply. Ако имате достатъчно памет, това вероятно ще ускори значително нещата. Можете обаче да разгледате и пакета foreach с подобни резултати. Получихте своя for loop "правилен", като присвоявате в results, вместо да го увеличавате, но наистина правите много сравнения. Ще бъде трудно да се ускори много това.

person Justin    schedule 19.01.2013