R:给定坐标的快速滑动窗口
Posted
技术标签:
【中文标题】R:给定坐标的快速滑动窗口【英文标题】:R: fast sliding window with given coordinates 【发布时间】:2012-12-20 23:40:21 【问题描述】:我有一个数据表,其中 nrow 约为 100 万或 2,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 列表中,用零填充空白,然后从 zoo 包中进行 rollmean,对每一列重复。但这是多余的,因为 rollmean 会遍历所有间隙,我最终只会使用原始坐标的值。
非常感谢您在不使用 C 的情况下使其更快的任何帮助。
【问题讨论】:
我不是zoo
的专家,但你确定使用 rollmean(data,fill=NA)
不够快吗?
如果你还是将数据存储在数据库中:使用 PostgreSQL 的数据库中的 sqldf 可以运行窗口统计。
致卡尔:rollmean 确实够快。但它不能处理任意坐标上的间隔。它只是在时间序列上使用固定的窗口大小,并且时间序列有固定的间隔。在这种情况下,间隔不是规则的,两点之间的空间可以是任意的。因此,如果我用 zoo 包的零填充所有空白 - 我将得到一个长度约为 5 亿的向量。在数据帧上使用 rollmean 是很痛苦的,尤其是当我只需要使用 rollmean 计算的 500 个中的几百万个时。
在最后一个循环中最好将行改为:wind_ints[i,] <- apply(matrix(intensities[WINDOWINDS[[i]],], ncol=ncol(intensities)), 2, mean)
。当窗口中只有一行时,您的代码会导致错误的结果。
【参考方案1】:
数据生成:
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
被称为快速子集,但this page(和其他与滑动窗口相关的)表明,情况并非如此。确实,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 开始使用 swap 之后,一切都会变慢。对于无法放入内存的非常大的数据,您应该考虑sqldf
、ff
或bigmemory
。
【讨论】:
您是否打算在第 1 节中声明data.table
在子集化方面并不快,并声明虽然 data.table
很优雅但实际上并不快?该基准似乎也使用了plyr
,并对组合进行了计时。似乎将行号向量传递给data.table
以分别获取多个副本。
这是一个更准确的链接:do rolling mean in j
not repeated i
subsets。
@Matthew Dowle,我知道data.table
在子集方面非常快,这就是我尝试的原因。但它似乎不是滚动窗口的正确工具(或者至少,我没有应付正确使用data.table
来加速计算)。
@Matthew Dowle,顺便说一句,您认为从答案中删除第 1 节更好吗?
没关系,这些cmets都覆盖了。在线使用data.table不好也是好事。【参考方案2】:
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)
Details here.
【讨论】:
以上是关于R:给定坐标的快速滑动窗口的主要内容,如果未能解决你的问题,请参考以下文章
剑指Offer(Java版)第六十七题:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。 例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口