#r #dplyr #euclidean-distance
Вопрос:
У меня есть список точек a1, a2, b1 и b2 и набор опорных точек. Я хотел бы вычислить расстояние между четырьмя точками и всеми контрольными точками. Я могу сделать это без проблем, используя следующий пример
library(dplyr)
x <- rep(1:5, 5)
y <- rep(1:5, each = 5)
a1.x <- c(4.5)
a1.y <- c(6)
a2.x <- c(0.8)
a2.y <- c(3.2)
b1.x <- c(2.5)
b1.y <- c(5)
b2.x <- c(3.8)
b2.y <- c(1.5)
time <- 1
time1 <- as.data.frame(cbind(time,x,y,a1.x,a1.y,a2.x,a2.y,b1.x,b1.y,b2.x,b2.y))
x <- rep(1:5, 5)
y <- rep(1:5, each = 5)
a1.x <- c(4)
a1.y <- c(5)
a2.x <- c(1.5)
a2.y <- c(3.9)
b1.x <- c(1.4)
b1.y <- c(4.6)
b2.x <- c(6)
b2.y <- c(5.2)
time <- 2
time2 <- as.data.frame(cbind(time,x,y,a1.x,a1.y,a2.x,a2.y,b1.x,b1.y,b2.x,b2.y))
df <- rbind(time1,time2)
df <- df %>%
mutate(dista1 = sqrt((x-a1.x)^2 (y-a1.y)^2)) %>%
mutate(dista2 = sqrt((x-a2.x)^2 (y-a2.y)^2)) %>%
mutate(distb1 = sqrt((x-b1.x)^2 (y-b1.y)^2)) %>%
mutate(distb2 = sqrt((x-b2.x)^2 (y-b2.y)^2))
Это работает без проблем и довольно быстро. Однако в большем наборе данных это замедляется за счет ввода всех столбцов или необходимости полагаться на циклы. Каков наиболее эффективный способ выполнения вышеперечисленных действий?
Редактировать — кроме того, у меня также есть фактор времени
Ответ №1:
Использование rdist
функции в пакете fields
проще:
library(fields) #use install.packages("fields") first
pts <- cbind(x= c(a1.x, a2.x, b1.x, b2.x), y=c(a1.y, a2.y, b1.y, b2.y))
ref <- cbind(x, y)
distances <- rdist(ref, pts)
colnames(distances) <- c("dista1", "dista2", "distb1", "distb2")
head(distances)
# dista1 dista2 distb1 distb2
# [1,] 6.103278 2.209072 4.272002 2.8442925
# [2,] 5.590170 2.505993 4.031129 1.8681542
# [3,] 5.220153 3.111270 4.031129 0.9433981
# [4,] 5.024938 3.883298 4.272002 0.5385165
# [5,] 5.024938 4.741308 4.716991 1.3000000
# [6,] 5.315073 1.216553 3.354102 2.8442925
Если вы хотите сопоставить df
в своем примере:
df <- cbind(ref, a1.x, a1.y, a2.x, a2.y, b1.x, b1.y, b2.x, b2.y, distances)
head(df)
# x y a1.x a1.y a2.x a2.y b1.x b1.y b2.x b2.y dista1 dista2 distb1 distb2
# [1,] 1 1 4.5 6 0.8 3.2 2.5 5 3.8 1.5 6.103278 2.209072 4.272002 2.8442925
# [2,] 2 1 4.5 6 0.8 3.2 2.5 5 3.8 1.5 5.590170 2.505993 4.031129 1.8681542
# [3,] 3 1 4.5 6 0.8 3.2 2.5 5 3.8 1.5 5.220153 3.111270 4.031129 0.9433981
# [4,] 4 1 4.5 6 0.8 3.2 2.5 5 3.8 1.5 5.024938 3.883298 4.272002 0.5385165
# [5,] 5 1 4.5 6 0.8 3.2 2.5 5 3.8 1.5 5.024938 4.741308 4.716991 1.3000000
# [6,] 1 2 4.5 6 0.8 3.2 2.5 5 3.8 1.5 5.315073 1.216553 3.354102 2.8442925
Если есть несколько раз, этот подход может быть расширен. Сначала извлеките из ваших time1
time2
объектов и, чтобы создать несколько точек и опорных матриц:
time1.pts <- matrix(unlist(time1[1, 4:11]), 4, 2, byrow=TRUE)
time2.pts <- matrix(unlist(time2[1, 4:11]), 4, 2, byrow=TRUE)
ref1 <- matrix(unlist(time1[1, 2:3]), 4, 2, byrow=TRUE)
ref2 <- matrix(unlist(time2[1, 2:3]), 4, 2, byrow=TRUE)
ref <- list(ref1=ref1, ref2=ref2)
pts <- list(time1.pts=time1.pts, time2.pts=time2.pts)
Матрицы обрабатываются быстрее, чем фреймы данных, поэтому это должно быть быстрее, чем работа с фреймами данных. Теперь анализ:
results <- lapply(seq(ntimes), function(i) rdist(ref[[i]], pts[[i]]))
distances <- do.call(rbind, results)
colnames(distances) <- c("dista1", "dista2", "distb1", "distb2")
distances
Матрица содержит все расстояния. Теперь мы просто объединяем их с вашими df
:
df <- data.frame(df, distances)
options(digits=4)
head(df, 5); cat(". . . . .n"); tail(df, 5)
# time x y a1.x a1.y a2.x a2.y b1.x b1.y b2.x b2.y dista1 dista2 distb1 distb2
# 1 1 1 1 4.5 6 0.8 3.2 2.5 5 3.8 1.5 6.103 2.209 4.272 2.8443
# 2 1 2 1 4.5 6 0.8 3.2 2.5 5 3.8 1.5 5.590 2.506 4.031 1.8682
# 3 1 3 1 4.5 6 0.8 3.2 2.5 5 3.8 1.5 5.220 3.111 4.031 0.9434
# 4 1 4 1 4.5 6 0.8 3.2 2.5 5 3.8 1.5 5.025 3.883 4.272 0.5385
# 5 1 5 1 4.5 6 0.8 3.2 2.5 5 3.8 1.5 5.025 4.741 4.717 1.3000
# . . . . .
# time x y a1.x a1.y a2.x a2.y b1.x b1.y b2.x b2.y dista1 dista2 distb1 distb2
# 46 2 1 5 4 5 1.5 3.9 1.4 4.6 6 5.2 3 1.208 0.5657 5.004
# 47 2 2 5 4 5 1.5 3.9 1.4 4.6 6 5.2 2 1.208 0.7211 4.005
# 48 2 3 5 4 5 1.5 3.9 1.4 4.6 6 5.2 1 1.860 1.6492 3.007
# 49 2 4 5 4 5 1.5 3.9 1.4 4.6 6 5.2 0 2.731 2.6306 2.010
# 50 2 5 5 4 5 1.5 3.9 1.4 4.6 6 5.2 1 3.669 3.6222 1.020
Комментарии:
1. Это, безусловно, самый быстрый. Я собирался опубликовать решение в 2,5 раза быстрее, чем у Джонаса, но ваше в 14 раз быстрее моего.
2. Спасибо, что проверили Руи.
3. Мне очень нравится это решение, раньше я не сталкивался с пакетом fields. Но у меня также есть фактор времени. Есть ли способ сказать ей, чтобы она сложила расстояния друг на друга. таким образом, у вас будет df всех расстояний между точкой отсчета и позициями для времени 1, затем ниже этого времени 2 и так далее. отредактировали код вверху
Ответ №2:
Я бы предложил base-R
использовать
referencePointList <- list(a1 = c(4.5,6), a2 = c(0.8,3.2), b1 = c(2.5,5),b2 = c(3.8,1.5))
distanceDfToReferencePoints <- function(x,y,referencePointList) {
distDf <- setNames(data.frame(do.call("cbind", lapply(referencePointList, function(rp) {
sqrt((x-rp[1])^2 (y-rp[2])^2)
}))), paste0("dist_",names(referencePointList)))
cbind(data.frame(x=x,y=y),distDf)
}
Давайте поместим ваш метод в функцию, скажем
f0 <- function() {
df <- as.data.frame(cbind(x,y,a1.x,a1.y,a2.x,a2.y,b1.x,b1.y,b2.x,b2.y))
df %>%
mutate(dista1 = sqrt((x-a1.x)^2 (y-a1.y)^2)) %>%
mutate(dista2 = sqrt((x-a2.x)^2 (y-a2.y)^2)) %>%
mutate(distb1 = sqrt((x-b1.x)^2 (y-b1.y)^2)) %>%
mutate(distb2 = sqrt((x-b2.x)^2 (y-b2.y)^2))
}
и сделайте контрольный показатель:
microbenchmark::microbenchmark(distanceDfToReferencePoints(x,y,referencePointList),f0())
даю на своем автоответчике
Unit: microseconds
expr min lq mean median uq max neval
distanceDfToReferencePoints(x, y, referencePointList) 302.3 340.90 543.882 411.15 479.8 11869.5 100
f0() 6074.3 6557.85 7497.018 6994.55 7591.5 28291.7 100
Следовательно base-R
, решение, по-видимому, намного быстрее. Может быть, это уже помогает вам.
Ответ №3:
подход data.table
library(data.table)
setDT(df)
#get names of points
refpoints <- unique( sub( "(^.*)\.[xy]", "\1", names( df[, -c(1,2)] ) ) )
#melt
DT <- melt(df, id.vars = c("x","y"), measure.vars = patterns(x2 = ".*\.x", y2 = ".*\.y"))
#set points' names
setattr(DT$variable, "levels", refpoints )
#calculate distance
DT[, distance := sqrt((x-x2)^2 (y-y2)^2)]
#cast to wide again
dcast(DT, x y ~ paste0("dist_", variable), value.var = "distance")
# x y dist_a1 dist_a2 dist_b1 dist_b2
# 1: 1 1 6.103278 2.2090722 4.272002 2.8442925
# 2: 1 2 5.315073 1.2165525 3.354102 2.8442925
# 3: 1 3 4.609772 0.2828427 2.500000 3.1764760
# 4: 1 4 4.031129 0.8246211 1.802776 3.7536649
# 5: 1 5 3.640055 1.8110770 1.500000 4.4821870
# 6: 2 1 5.590170 2.5059928 4.031129 1.8681542
# 7: 2 2 4.716991 1.6970563 3.041381 1.8681542
# 8: 2 3 3.905125 1.2165525 2.061553 2.3430749
# 9: 2 4 3.201562 1.4422205 1.118034 3.0805844
#10: 2 5 2.692582 2.1633308 0.500000 3.9357337
#11: 3 1 5.220153 3.1112698 4.031129 0.9433981
#12: 3 2 4.272002 2.5059928 3.041381 0.9433981
#13: 3 3 3.354102 2.2090722 2.061553 1.7000000
#14: 3 4 2.500000 2.3409400 1.118034 2.6248809
#15: 3 5 1.802776 2.8425341 0.500000 3.5902646
#16: 4 1 5.024938 3.8832976 4.272002 0.5385165
#17: 4 2 4.031129 3.4176015 3.354102 0.5385165
#18: 4 3 3.041381 3.2062439 2.500000 1.5132746
#19: 4 4 2.061553 3.2984845 1.802776 2.5079872
#20: 4 5 1.118034 3.6715120 1.500000 3.5057096
#21: 5 1 5.024938 4.7413078 4.716991 1.3000000
#22: 5 2 4.031129 4.3680659 3.905125 1.3000000
#23: 5 3 3.041381 4.2047592 3.201562 1.9209373
#24: 5 4 2.061553 4.2755117 2.692582 2.7730849
#25: 5 5 1.118034 4.5694639 2.500000 3.7000000
# x y dist_a1 dist_a2 dist_b1 dist_b2
Ответ №4:
Мое предложение состоит в том, чтобы использовать Rfast::dista() и посмотреть время самостоятельно.
Ответ №5:
использование tidyverse:
df %>%
rename(x_new = x, y_new = y)%>%
pivot_longer(3:ncol(df), names_pattern ="(\w )\.(\w )",
names_to = c('var', '.value')) %>%
mutate(value = sqrt((x-x_new)^2 (y-y_new)^2)) %>%
pivot_wider(c(x_new,y_new),var, values_from = value)
x_new y_new a1 a2 b1 b2
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 1 6.10 2.21 4.27 2.84
2 2 1 5.59 2.51 4.03 1.87
3 3 1 5.22 3.11 4.03 0.943
4 4 1 5.02 3.88 4.27 0.539
5 5 1 5.02 4.74 4.72 1.3
6 1 2 5.32 1.22 3.35 2.84
7 2 2 4.72 1.70 3.04 1.87
8 3 2 4.27 2.51 3.04 0.943
9 4 2 4.03 3.42 3.35 0.539
10 5 2 4.03 4.37 3.91 1.3
# ... with 15 more rows
Если бы вы вообще могли извлекать столбцы вручную, вы могли бы даже сделать это быстрее:
pts <- cbind(x= c(a1.x, a2.x, b1.x, b2.x), y=c(a1.y, a2.y, b1.y, b2.y))
ref <- cbind(x, y)
sqrt(laGP::distance(ref, pts))
[,1] [,2] [,3] [,4]
[1,] 6.103278 2.2090722 4.272002 2.8442925
[2,] 5.590170 2.5059928 4.031129 1.8681542
[3,] 5.220153 3.1112698 4.031129 0.9433981
[4,] 5.024938 3.8832976 4.272002 0.5385165
[5,] 5.024938 4.7413078 4.716991 1.3000000
[6,] 5.315073 1.2165525 3.354102 2.8442925
[7,] 4.716991 1.6970563 3.041381 1.8681542
[8,] 4.272002 2.5059928 3.041381 0.9433981
или даже:
apply(pts, 1, function(x)sqrt(rowSums((x-ref)^2)))
[,1] [,2] [,3] [,4]
[1,] 6.103278 2.2090722 4.272002 2.8442925
[2,] 5.315073 1.2165525 3.354102 2.8442925
[3,] 5.220153 3.1112698 4.031129 0.9433981
[4,] 4.031129 0.8246211 1.802776 3.7536649
[5,] 5.024938 4.7413078 4.716991 1.3000000
[6,] 5.590170 2.5059928 4.031129 1.8681542
[7,] 4.716991 1.6970563 3.041381 1.8681542
[8,] 3.905125 1.2165525 2.061553 2.3430749
Если вы используете R >= 4.1
df |>
reshape(matrix(3:ncol(df), 2), dir='long') |>
transform(new_pt = sqrt((x-a1.x)^2 (y - a1.y)^2)) |>
reshape(v.names = c('a1.x','a1.y','new_pt'),dir='wide')
Если нет, замените |>
трубу на %>%