R: быстро скользящее окно с заданными координатами

У меня есть таблица данных, в которой nrow составляет около миллиона или двух, а ncol — около 200.

Каждая запись в строке имеет связанную с ней координату.

Крошечная часть данных:

[1,] -2.80331471  -0.8874522 -2.34401863   -3.811584   -2.1292443
[2,]  0.03177716   0.2588624  0.82877467    1.955099    0.6321881
[3,] -1.32954665  -0.5433407 -2.19211837   -2.342554   -2.2142461
[4,] -0.60771429  -0.9758734  0.01558774    1.651459   -0.8137684

Координаты для первых 4 рядов:

9928202 9928251 9928288 9928319

Я хотел бы, чтобы функция, которая, учитывая данные и размер окна, возвращала бы таблицу данных того же размера со средним скользящим окном, применяемым к каждому столбцу. Или, другими словами, для каждой записи строки i будут найдены записи с координатами между coords[i]-windsize и coords[i]+windsize и заменено начальное значение средним значением значений внутри этого интервал (отдельно для каждого столбца).

Здесь главное скорость.

Вот мой первый пример такой функции.

doSlidingWindow <- function(intensities, coords, windsize) {
windHalfSize <- ceiling(windsize/2)
### whole range inds
RANGE <- integer(max(coords)+windsize)
RANGE[coords] <- c(1:length(coords)[1])

### get indeces of rows falling in each window
COORDS <- as.list(coords)
WINDOWINDS <- sapply(COORDS, function(crds){ unique(RANGE[(crds-windHalfSize):
    (crds+windHalfSize)]) })

### do windowing

wind_ints <- intensities
wind_ints[] <- 0
for(i in 1:length(coords)) {
    wind_ints[i,] <- apply(as.matrix(intensities[WINDOWINDS[[i]],]), 2, mean)
}
return(wind_ints)
}

Код перед последним циклом for довольно быстрый и дает мне список индексов, которые мне нужно использовать для каждой записи. Однако затем все разваливается, так как мне нужно перемолоть цикл for миллион раз, взять подмножества моей таблицы данных, а также убедиться, что у меня есть более одной строки, чтобы иметь возможность работать со всеми столбцами сразу внутри применения.

Мой второй подход состоит в том, чтобы просто вставить фактические значения в список RANGE, заполнить промежутки нулями и выполнить rollmean из пакета зоопарка, повторяя для каждого столбца. Но это избыточно, так как rollmean пройдет через все промежутки, и в конце я буду использовать только значения исходных координат.

Любая помощь, чтобы сделать это быстрее, не переходя на C, будет очень признательна.


person Karolis Koncevičius    schedule 07.01.2013    source источник
comment
Я не эксперт по zoo , но вы уверены, что использование rollmean(data,fill=NA) будет недостаточно быстрым?   -  person Carl Witthoft    schedule 07.01.2013
comment
Если вы все равно храните данные в базе данных: sqldf в базе данных с PostgreSQL может выполнять статистику окна.   -  person Dieter Menne    schedule 07.01.2013
comment
Карлу: Rollmean действительно достаточно быстр. Но он не может обрабатывать интервалы в произвольных координатах. Он просто использует фиксированный размер окна для временного ряда, а временной ряд имеет регулярные интервалы. В этом случае интервалы нерегулярны, а промежутки между двумя точками могут быть произвольными. Итак, если я заполню все пробелы нулями для пакета зоопарка, я получу вектор длиной около 500 миллионов. Делать это с помощью rollmean в фрейме данных очень сложно, особенно когда мне нужно всего несколько миллионов из тех 500, которые вычисляются с помощью rollmean.   -  person Karolis Koncevičius    schedule 07.01.2013
comment
В последнем цикле лучше изменить строку на: wind_ints[i,] <- apply(matrix(intensities[WINDOWINDS[[i]],], ncol=ncol(intensities)), 2, mean). Когда в окне только одна строка, ваш код приводит к неправильным результатам.   -  person redmode    schedule 20.01.2013


Ответы (2)


Генерация данных:

N <- 1e5 # rows
M <- 200 # columns
W <- 10  # window size

set.seed(1)
intensities <- matrix(rnorm(N*M), nrow=N, ncol=M)
coords <- 8000000 + sort(sample(1:(5*N), N))

Оригинальная функция с небольшими изменениями, которую я использовал для тестов:

doSlidingWindow <- function(intensities, coords, windsize) {
  windHalfSize <- ceiling(windsize/2)
  ### whole range inds
  RANGE <- integer(max(coords)+windsize)
  RANGE[coords] <- c(1:length(coords)[1])

  ### get indices of rows falling in each window
  ### NOTE: Each elements of WINDOWINDS holds zero. Not a big problem though.
  WINDOWINDS <- sapply(coords, function(crds) ret <- unique(RANGE[(crds-windHalfSize):(crds+windHalfSize)]))

  ### do windowing
  wind_ints <- intensities
  wind_ints[] <- 0
  for(i in 1:length(coords)) {
    # CORRECTION: When it's only one row in window there was a trouble
    wind_ints[i,] <- apply(matrix(intensities[WINDOWINDS[[i]],], ncol=ncol(intensities)), 2, mean)
  }
  return(wind_ints)
}

ВОЗМОЖНЫЕ РЕШЕНИЯ:


1) data.table

Известно, что data.table быстро работает с подмножеством, но эта страница (и другие, связанные со скользящим окном) предполагают, что это не так. дело. Действительно, код data.table элегантен, но, к сожалению, очень медленный:

require(data.table)
require(plyr)
dt <- data.table(coords, intensities)
setkey(dt, coords)
aaply(1:N, 1, function(i) dt[WINDOWINDS[[i]], sapply(.SD,mean), .SDcols=2:(M+1)])

2) foreach+doSNOW

Базовую процедуру легко запустить параллельно, поэтому мы можем извлечь из этого пользу:

require(doSNOW)
doSlidingWindow2 <- function(intensities, coords, windsize) {
  NC <- 2 # number of nodes in cluster
  cl <- makeCluster(rep("localhost", NC), type="SOCK")
  registerDoSNOW(cl)

  N <- ncol(intensities) # total number of columns
  chunk <- ceiling(N/NC) # number of columns send to the single node

  result <- foreach(i=1:NC, .combine=cbind, .export=c("doSlidingWindow")) %dopar% {
    start <- (i-1)*chunk+1
    end   <- ifelse(i!=NC, i*chunk, N)
    doSlidingWindow(intensities[,start:end], coords, windsize)    
  }

  stopCluster(cl)
  return (result)
}

Бенчмарк показывает заметное ускорение моего двухъядерного процессора:

system.time(res <- doSlidingWindow(intensities, coords, W))
#    user  system elapsed 
# 306.259   0.204 307.770
system.time(res2 <- doSlidingWindow2(intensities, coords, W))
#  user  system elapsed 
# 1.377   1.364 177.223
all.equal(res, res2, check.attributes=FALSE)
# [1] TRUE

3) Rcpp

Да, я знаю, что вы спросили "не переходя на C". Но, пожалуйста, взгляните. Этот код является встроенным и довольно простым:

require(Rcpp)
require(inline)
doSlidingWindow3 <- cxxfunction(signature(intens="matrix", crds="numeric", wsize="numeric"), plugin="Rcpp", body='
  #include <vector>
  Rcpp::NumericMatrix intensities(intens);
  const int N = intensities.nrow();
  const int M = intensities.ncol();
  Rcpp::NumericMatrix wind_ints(N, M);

  std::vector<int> coords = as< std::vector<int> >(crds);
  int windsize = ceil(as<double>(wsize)/2);  

  for(int i=0; i<N; i++){
    // Simple search for window range (begin:end in coords)
    // Assumed that coords are non-decreasing
    int begin = (i-windsize)<0?0:(i-windsize);
    while(coords[begin]<(coords[i]-windsize)) ++begin;
    int end = (i+windsize)>(N-1)?(N-1):(i+windsize);
    while(coords[end]>(coords[i]+windsize)) --end;

    for(int j=0; j<M; j++){
      double result = 0.0;
      for(int k=begin; k<=end; k++){
        result += intensities(k,j);
      }
      wind_ints(i,j) = result/(end-begin+1);
    }
  }

  return wind_ints;
')

Ориентир:

system.time(res <- doSlidingWindow(intensities, coords, W))
#    user  system elapsed 
# 306.259   0.204 307.770
system.time(res3 <- doSlidingWindow3(intensities, coords, W))
#  user  system elapsed 
# 0.328   0.020   0.351
all.equal(res, res3, check.attributes=FALSE)
# [1] TRUE

Я надеюсь, что результаты весьма мотивируют. Пока данные умещаются в памяти, Rcpp версия работает довольно быстро. Скажем, с N <- 1e6 и M <-100 у меня получилось:

   user  system elapsed 
  2.873   0.076   2.951

Естественно, после того, как R начинает использовать своп, все замедляется. С действительно большими данными, которые не помещаются в памяти, вам следует рассмотреть sqldf, ff или bigmemory.

person redmode    schedule 20.01.2013
comment
Вы намеревались в разделе 1 указать, что data.table не быстр в подмножестве, и заявить, что, хотя data.table элегантен, на самом деле он не быстр? Похоже, что этот тест также использует plyr и умножает комбинацию. Кажется, он передает векторы номеров строк в data.table, чтобы сделать много копий по отдельности. - person Matt Dowle; 20.01.2013
comment
Это более точная ссылка: сделать скользящее среднее в j не повторяющихся i подмножествах. - person Matt Dowle; 20.01.2013
comment
@ Мэтью Доул, я знаю, что data.table довольно быстро справляется с подмножеством, поэтому я попробовал. Но, похоже, это не тот инструмент для прокрутки окна (по крайней мере, я не справился с тем, чтобы правильно использовать data.table для ускорения вычислений). - person redmode; 20.01.2013
comment
@ Мэтью Доул, кстати, как вы думаете, лучше ли удалить раздел 1 из ответа? - person redmode; 20.01.2013
comment
Все в порядке, эти комментарии покрывают это. Хорошо также плохо использовать data.table онлайн. - person Matt Dowle; 21.01.2013
comment
@redmode Сколько настроек требуется здесь, чтобы обработать coords, которые начинаются близко к 0? То есть код выдает ошибки, если я делаю coords <- sort(sample(1:(5*N), N)). - person Jota; 15.07.2014
comment
@ Фрэнк, это странно. Я только что запустил coords <- sort(sample(1:(5*N), N)), а затем doSlidingWindow3(intensities, coords, W) - все работает как есть. Какую версию функции вы использовали? - person redmode; 15.07.2014
comment
@redmode, изначально я использовал doSlidingWindow. Теперь я попробовал doSlidingWindow3, и это работает, хотя стоит отметить, что мне нужно было ввести 1/2 желаемого размера окна вместо самого размера окна, и я получил предупреждающее сообщение cygwin после определения функции. - person Jota; 15.07.2014
comment
@ Фрэнк, что за сообщение? Я не могу воспроизвести это, хотя. - person redmode; 16.07.2014
comment
@Frank, это просто предупреждение CYGWIN, вы можете узнать, как его отключить здесь: cygwin.com/cygwin-ug-net/setup-env.html. Этот ответ также может быть полезен: stackoverflow.com/questions/9764495/ - person redmode; 16.07.2014

Rollapply отлично работает с небольшим набором данных. Однако, если вы работаете с несколькими миллионами строк (геномика), это довольно медленно.

Следующая функция очень быстрая:

data <- c(runif(100000, min=0, max=.1),runif(100000, min=.05, max=.1),runif(10000, min=.05, max=1), runif(100000, min=0, max=.2))
slideFunct <- function(data, window, step){
  total <- length(data)
  spots <- seq(from=1, to=(total-window), by=step)
  result <- vector(length = length(spots))
  for(i in 1:length(spots)){
    result[i] <- mean(data[spots[i]:(spots[i]+window)])
  }
  return(result)
}

Подробности здесь.

person Jacky Smith    schedule 06.09.2017