R: быстрое сопоставление записей на основе настраиваемой функции расстояния и нескольких критериев

Я сделал некоторые функции в R для сопоставления химических масс-спектров (матрица с двумя столбцами с целыми массами и интенсивностями) с библиотеками таких спектров на основе настраиваемой функции спектрального сходства и сопоставления так называемого индекса удерживания соединений (т. Е. время элюирования) (см. здесь пример, http://webbook.nist.gov/cgi/cbook.cgi?ID=C630035&Mask=200). Для этого элемент списка «RI» каждой записи должен быть сравнен с элементом списка в библиотеке, и когда абсолютное отклонение меньше заданного допуска, он должен добавить лучшее соответствие спектральной библиотеки к моим записям. Ниже приведен код, который я написал для этого, но проблема в том, что он слишком медленный для моих целей (у меня обычно около 1000 образцов спектров и 200 000 библиотечных спектров). Я попытался распараллелить его, но это тоже не помогло. Возможно, есть какие-нибудь мысли о том, как я могу сделать приведенный ниже код более эффективным, например. используя больше векторизации, или используя встроенный код C, или некоторые другие трюки R? Я знаю общий совет по этому поводу, но не совсем понимаю, как его легко реализовать в этом случае (а я, к сожалению, еще не так хорошо разбираюсь в C) ... Есть мысли или советы? Ах да, а как добавить индикатор выполнения при использовании sfLapply? Помогло бы, возможно, сначала поместить мои спектры в одну большую (разреженную, так как много нулей) матрицу, чтобы избежать шага merge в функции спектрального подобия, или использовать дополнительные критерии, такие как рассмотрение спектров только тогда, когда наибольшее / самый интенсивный пик в спектре запроса имеет ту же массу, что и спектр библиотеки (или содержится в наборе, скажем, 5 самых больших пиков в спектре библиотеки)? В любом случае, любые мысли о том, как ускорить эту задачу, были бы очень признательны!

РЕДАКТИРОВАТЬ: один остаточный запрос, который у меня все еще есть, заключается в том, как я мог бы избежать создания полной копии образцов записей recs в функции addbestlibmatches1, а вместо этого изменить только те записи, для которых есть соответствие библиотеки? Кроме того, передача выборки записей библиотеки, для которых есть соответствие индекса хранения, вероятно, неэффективна (в функции addbestlibmatch). Есть мысли, как я могу этого избежать?

# EXAMPLE DATA

rec1=list(RI=1100,spectrum=as.matrix(cbind(mz=c(57,43,71,41,85,56,55,70,42,84,98,99,39,69,58,113,156),int=c(999,684,396,281,249,173,122,107,94,73,51,48,47,47,37,33,32))))
randrec=function() list(RI=runif(1,1000,1200),spectrum=as.matrix(cbind(mz=seq(30,600,1),int=round(runif(600-30+1,0,999)))))

# spectral library
libsize=2000 # my real lib has 200 000 recs
lib=lapply(1:libsize,function(i) randrec())
lib=append(list(rec1),lib) 

# sample spectra
ssize=100 # I usually have around 1000
s=lapply(1:ssize,function(i) randrec())
s=append(s,list(rec1)) # we add the first library record as the last sample spectrum, so this should match



# SPECTRAL SIMILARITY FUNCTION

SpecSim=function (ms1,ms2,log=F) { 
  alignment = merge(ms1,ms2,by=1,all=T)
  alignment[is.na(alignment)]=0
  if (nrow(alignment)!=0) {
    alignment[,2]=100*alignment[,2]/max(alignment[,2]) # normalize base peak intensities to 100
    alignment[,3]=100*alignment[,3]/max(alignment[,3])
    if (log==T) {alignment[,2]=log2(alignment[,2]+1);alignment[,3]=log2(alignment[,3]+1)} # work on log2 intensity scale if requested
    u = alignment[,2]; v = alignment[,3]
    similarity_score = as.vector((u %*% v) / (sqrt(sum(u^2)) * sqrt(sum(v^2))))
    similarity_score[is.na(similarity_score)]=-1
    return(similarity_score)} else return(-1) }



# FUNCTION TO CALCULATE SIMILARITY VECTOR OF SPECTRUM WITH LIBRARY SPECTRA

SpecSimLib=function(spec,lib,log=F) {
  sapply(1:length(lib), function(i) SpecSim(spec,lib[[i]]$spectrum,log=log)) }



# FUNCTION TO ADD BEST MATCH OF SAMPLE RECORD rec IN SPECTRAL LIBRARY lib TO ORIGINAL RECORD
# we only compare spectra when list element RI in the sample record is within tol of that in the library
# when there is a spectral match > specsimcut within a RI devation less than tol,
# we add the record nrs in the library with the best spectral matches, the spectral similarity and the RI deviation to recs

addbestlibmatch=function(rec,lib,xvar="RI",tol=10,log=F,specsimcut=0.8) {
    nohit=list(record=-1,specmatch=NA,RIdev=NA)
    selected=abs(sapply(lib, "[[", xvar)-rec[[xvar]])<tol
    if (sum(selected)!=0) {
    specsims=SpecSimLib(rec$spectrum,lib[selected],log) # HOW CAN I AVOID PASSING THE RIGHT LIBRARY SUBSET EACH TIME?
    maxspecsim=max(specsims)
    if (maxspecsim>specsimcut) {besthsel=which(specsims==maxspecsim)[[1]] # nr of best hit among selected elements, in case of ties we just take the 1st hit
                                idbesth=which(selected)[[besthsel]] # record nr of best hit in library lib
                                return(modifyList(rec,list(record=idbesth,specsim=specsims[[besthsel]],RIdev=rec[[xvar]]-lib[[idbesth]][[xvar]])))}
                                else {return(rec)} } else {return(rec)}
}



# FUNCTION TO ADD BEST LIBRARY MATCHES TO RECORDS RECS

library(pbapply)
addbestlibmatches1=function(recs,lib,xvar="RI",tol=10,log=F,specsimcut=0.8) {
  pblapply(1:length(recs), function(i) addbestlibmatch(recs[[i]],lib,xvar,tol,log,specsimcut))
}

# PARALLELIZED VERSION
library(snowfall)
addbestlibmatches2=function(recs,lib,xvar="RI",tol=10,log=F,specsimcut=0.8,cores=4) {
  sfInit(parallel=TRUE,cpus=cores,type="SOCK")
  sfExportAll()
  sfLapply(1:length(recs), function(i) addbestlibmatch(recs[[i]],lib,xvar,tol,log,specsimcut))
  sfStop() 
}



# TEST TIMINGS

system.time(addbestlibmatches1(s,lib))
#|++++++++++++++++++++++++++++++++++++++++++++++++++| 100%
#user  system elapsed 
#83.60    0.06   83.82 

system.time(addbestlibmatches2(s,lib))
#user  system elapsed  - a bit better, but not much
#2.59    0.74   42.37 

person Tom Wenseleers    schedule 07.07.2014    source источник
comment
Простой индикатор выполнения: pb <- txtProgressBar(0, 100, style = 3); sapply(1:100, function(i) {setTxtProgressBar(pb, i); Sys.sleep(0.05); i}); close(pb)   -  person tonytonov    schedule 07.07.2014
comment
Что касается производительности, вам, вероятно, потребуется более глубокое профилирование, чтобы определить узкое место. Вы можете начать с этой главы Advanced R.   -  person tonytonov    schedule 07.07.2014
comment
Спасибо - просто попытался посмотреть на это с помощью devtools :: install_github (hadley / lineprof) library (lineprof) l = lineprof (addbestlibmatches1 (s, lib)) shine (l), но просто получил пустой экран и предупреждение Предупреждение в файле ( con, r): file () поддерживает только open = w + и open = w + b: с использованием первого. Есть мысли? Я думаю, что основным ключом к ускорению моего кода была бы векторизация и избегание копирования вещей, но не знаю, как это сделать в этом случае ...   -  person Tom Wenseleers    schedule 07.07.2014
comment
Мне тоже не удалось заставить его работать. Может быть, попробовать профилировать по базе Rprof? Например. как ответил здесь.   -  person tonytonov    schedule 07.07.2014


Ответы (1)


Не вдаваясь в подробности вашего кода, я думаю, что есть возможности для улучшения функции SpecSim, не переходя еще на C ++. Вы используете слияние, которое приводит ваши матрицы к data.frames. Это всегда плохо сказывается на производительности. Большая часть вашего кода времени, вероятно, находится в merge () и подмножестве. подмножество data.frame выполняется медленно, матрица или вектор - быстро.

SpecSim2 <- function (ms1,ms2,log=F) {
  i <- unique(c(ms1[,1], ms2[,1]))
  y <- x <- numeric(length(i))
  x[match(ms1[,1], i)] <- ms1[, 2]
  y[match(ms2[,1], i)] <- ms2[, 2]
  x <- 100 * x / max(x)
  y <- 100 * y / max(y)
  if (log){
    x <- log2(x + 1)
    y <- log2(y + 1)
  }
  similarity.score <- x %*% y / (sqrt(sum(x^2)) * sqrt(sum(y^2)))
  if (is.na(similarity.score)){
    return(-1)
  } else {
    return(similarity.score)
  }
}

Вот некоторые сроки перезаписи и оригинала соответственно:

> system.time(addbestlibmatches1(s,lib))
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100%
   user  system elapsed 
   4.16    0.00    4.17

> system.time(addbestlibmatches1(s,lib))
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100%
   user  system elapsed 
  34.25    0.02   34.34 

Может не дать вам нужной скорости, но лучше, чем 8-кратное улучшение ...

Что касается того, что делать с addbestlibmatches (), я думаю, вам нужно переосмыслить проблему как матричную проблему. Вместо того, чтобы использовать списки для хранения вашей библиотеки, используйте вектор для значений RI как для библиотеки, так и для образцов. Затем вы можете сделать свой начальный экран следующим образом:

selected <- outer(sRI, libRI, FUN = '-') < 10

Если ваша библиотека представляет собой одну большую матрицу (массы x спектры), вы можете подгруппировать библиотеку для выбранных спектров и рассчитать расстояние между образцом и всей частью библиотеки, выбранной за один проход, следующим образом:

SpecSimMat <- function(x, lib, log = F){
  stopifnot(require(matrixStats))
  x <- 100 * x / max(x)
  lib <- sweep(lib, 2, colMaxs(lib))
  x %*% lib / (sqrt(sum(x^2)) * sqrt(colSums(lib^2)))

}

где x - образец, а lib - матрица выбранных спектров (масс x спектров). Таким образом, вы разбиваете матрицу на подмножество (быстро), а затем выполняете серию матричных операций (быстро).

person oropendola    schedule 07.07.2014
comment
Ха, это здорово - огромное спасибо за это !! И хорошо знать, что большая часть времени была потрачена на вычисление оценки сходства, поэтому я могу сосредоточиться на ускорении этой части ... - person Tom Wenseleers; 07.07.2014
comment
Ха, и один остаточный вопрос, который у меня все еще есть, заключается в том, как я могу избежать создания полной копии образцов записей recs в функции addbestlibmatches1, а изменить только те записи на месте, для которых есть соответствие библиотеки? Кроме того, передача копии выбранных записей библиотеки, для которых существует соответствие индекса хранения, вероятно, неэффективна (в функции addbestlibmatch). Есть мысли, как я могу этого избежать? - person Tom Wenseleers; 08.07.2014