我有两组点,分别为path
和centers
。对于path
中的每个点,我想要一种有效的方法来查找centers
中最近点的ID。我想在R中这样做。下面是一个简单的可重复的例子。
set.seed(1)
n <- 10000
x <- 100*cumprod(1 + rnorm(n, 0.0001, 0.002))
y <- 50*cumprod(1 + rnorm(n, 0.0001, 0.002))
path <- data.frame(cbind(x=x, y=y))
centers <- expand.grid(x=seq(0, 500,by=0.5) + rnorm(1001),
y=seq(0, 500, by=0.2) + rnorm(2501))
centers$id <- seq(nrow(centers))
x
和y
是坐标。我想在path
data.frame中添加一列,其中id为给定x和y坐标的最近中心。然后我想获得所有独特的ID。
我现在的解决方案确实有效,但当问题的规模增加时,我的解决方案非常缓慢。我想要更高效的东西。
path$closest.id <- sapply(seq(nrow(path)), function(z){
tmp <- ((centers$x - path[z, 'x'])^2) + ((centers$y - path[z, 'y'])^2)
as.numeric(centers[tmp == min(tmp), 'id'])
})
output <- unique(path$closest.id)
任何有关加快这项工作的帮助都将不胜感激。
我认为data.table
可能会有所帮助,但理想情况下我所寻找的是一种算法,在搜索方面可能更聪明,即不是计算到每个中心的距离,而是只选择最小值一个......获得身份......
我也很高兴使用Rcpp
/ Rcpp11
,如果这有助于提高效果。
执行此类计算的最短可接受时间为10秒,但显然更快会更好。
答案 0 :(得分:12)
您可以使用nn2
包中的RANN
执行此操作。在我的系统上,这会在2秒内计算出每个center
点的最近path
。
library(RANN)
system.time(closest <- nn2(centers[, 1:2], path, 1))
# user system elapsed
# 1.41 0.14 1.55
sapply(closest, head)
# nn.idx nn.dists
# [1,] 247451 0.20334929
# [2,] 250454 0.12326323
# [3,] 250454 0.28540127
# [4,] 253457 0.05178687
# [5,] 253457 0.13324137
# [6,] 253457 0.09009626
这是另一个包含250万个候选点的示例,所有候选点都在path
点的范围内(在您的示例中,centers
有更大的x
和y
范围比path
点范围宽。在这种情况下,它有点慢。
set.seed(1)
centers2 <- cbind(runif(2.5e6, min(x), max(x)), runif(2.5e6, min(y), max(y)))
system.time(closest2 <- nn2(centers2, path, 1))
# user system elapsed
# 2.96 0.11 3.07
sapply(closest2, head)
# nn.idx nn.dists
# [1,] 730127 0.025803703
# [2,] 375514 0.025999069
# [3,] 2443707 0.047259283
# [4,] 62780 0.022747930
# [5,] 1431847 0.002482623
# [6,] 2199405 0.028815865
这可以与使用sp::spDistsN1
的输出进行比较(这个问题慢得多):
library(sp)
apply(head(path), 1, function(x) which.min(spDistsN1(centers, x)))
# 1 2 3 4 5 6
# 730127 375514 2443707 62780 1431847 2199405
将点ID添加到path
data.frame并减少为唯一值是微不足道的:
path$closest.id <- closest$nn.idx
output <- unique(path$closest.id)
答案 1 :(得分:6)
这是一个Rcpp11
解决方案。类似的东西可能适用于Rcpp
并进行一些更改。
#define RCPP11_PARALLEL_MINIMUM_SIZE 1000
#include <Rcpp11>
inline double square(double x){
return x*x ;
}
// [[Rcpp::export]]
IntegerVector closest( DataFrame path, DataFrame centers ){
NumericVector path_x = path["x"], path_y = path["y"] ;
NumericVector centers_x = centers["x"], centers_y = centers["y"] ;
int n_paths = path_x.size(), n_centers = centers_x.size() ;
IntegerVector ids = sapply( seq_len(n_paths), [&](int i){
double px = path_x[i], py=path_y[i] ;
auto get_distance = [&](int j){
return square(px - centers_x[j]) + square(py-centers_y[j]) ;
} ;
double distance = get_distance(0) ;
int res=0;
for( int j=1; j<n_centers; j++){
double d = get_distance(j) ;
if(d < distance){
distance = d ;
res = j ;
}
}
return res + 1 ;
}) ;
return unique(ids) ;
}
我明白了:
> set.seed(1)
> n <- 10000
> x <- 100 * cumprod(1 + rnorm(n, 1e-04, 0.002))
> y <- 50 * cumprod(1 + rnorm(n, 1e-04, 0.002))
> path <- data.frame(cbind(x = x, y = y))
> centers <- expand.grid(x = seq(0, 500, by = 0.5) +
+ rnorm(1001), y = seq(0, 500, by = 0.2) + rnorm(2501))
> system.time(closest(path, centers))
user system elapsed
84.740 0.141 21.392
这利用了糖的自动并行化,即sapply
并行运行。 #define RCPP11_PARALLEL_MINIMUM_SIZE 1000
部分是强制并行,否则默认情况下仅从10000开始。但在这种情况下,因为内部计算是耗时的,所以值得。
请注意,您需要Rcpp11
的开发版本,因为unique
在已发布的版本中已被破坏。
答案 2 :(得分:1)
此解决方案将样本数据集的处理时间减少了近一半的RANN解决方案。
可以使用devtools::install_github("thell/Rcppnanoflann")
Rcppnanoflann解决方案利用了Rcpp,RcppEigen和 nanoflann EigenMatrixAdaptor与c ++ 11一起产生 原始问题的相同唯一索引。
library(Rcppnanoflann)
system.time(o.nano<-nnIndex(centers,path))
## user system elapsed
## 0.62 0.05 0.67
*使用原始问题中定义的路径和中心值
为了获得与原始样品相同的结果,RANN解决方案需要 我们在这里稍作修改......
library(RANN)
system.time(o.flann<-unique(as.numeric(nn2(centers,path,1)$nn.idx)))
## user system elapsed
## 1.24 0.07 1.30
<子> 子>
identical(o.flann,o.nano)
## [1] TRUE
Rcppnanoflann的工作功能利用了Eigen的Map
从中创建固定类型特征矩阵的输入的能力
给定的P
数据框。
使用RcppParallel包进行测试,但kd_tree对象没有 复制构造函数,因此需要为每个线程创建树 这可以消除并行查询处理中的任何收益。
RcppEigen和Rcpp11目前并没有一起玩这么想法 使用Rcpp11的并行sapply查询并不容易测试。
// [[Rcpp::export]]
std::vector<double> nnIndex(const Rcpp::DataFrame & P, const Rcpp::DataFrame & Q )
{
using namespace Eigen;
using namespace Rcpp;
using namespace nanoflann;
// Matrix of points to be queried against.
const NumericVector & Px(P[0]);
const NumericVector & Py(P[1]);
MatrixX2d M(Px.size(), 2);
M.col(0) = VectorXd::Map(&Px[0],Px.size());
M.col(1) = VectorXd::Map(&Py[0],Py.size());
// The points to query.
const NumericVector & Qx(Q[0]);
const NumericVector & Qy(Q[1]);
double query_pt[2];
size_t query_count(Qx.size());
// Populate a 2d tree.
KD_Tree kd_tree( 2, M, 10 );
kd_tree.index->buildIndex();
std::set<size_t> nn;
std::vector<double> out;
out.reserve(query_count);
size_t index(0);
double quadrance;
for( size_t i=0 ; i < query_count; ++i ) {
query_pt[0] = Qx[i];
query_pt[1] = Qy[i];
kd_tree.index->knnSearch( &query_pt[0],1, &index, &quadrance);
if( nn.emplace(index).second ) out.emplace_back(index+1);
}
return out;
}