Ramka danych d1
:
x y
4 10
6 20
7 30
Ramka danych d2
:
x z
3 100
6 200
9 300
Jak się połączyć d1
i d2
przez "x"
gdzie d1$x
powinien być dopasowany do dokładnego dopasowania lub kolejnego wyższego numeru w d2$x
. Dane wyjściowe powinny wyglądać tak:
x y z
4 10 200 # (4 is matched against next higher value that is 6)
6 20 200 # (6 is matched against 6)
7 30 300 # (7 is matched against next higher value that is 9)
Gdyby merge()
nie mogę tego zrobić, to czy jest jakiś inny sposób, aby to zrobić? Bo pętle są boleśnie powolne.
Odpowiedzi:
2 dla odpowiedzi № 1Dane wejściowe:
d1 <- data.frame(x=c(4,6,7), y=c(10,20,30))
d2 <- data.frame(x=c(3,6,9), z=c(100,200,300))
W zasadzie chcesz rozszerzyć d1
przez nową kolumnę. Skopiujmy to.
d3 <- d1
Następnie zakładam, że d2$x
jest posortowane nieskończenie i tomax(d1$x) <= max(d2$x)
.
d3$z <- sapply(d1$x, function(x) d2$z[which(x <= d2$x)[1]])
Który brzmi: dla każdego x
w d1$x
, uzyskaj najmniejszą wartość d2$x
który nie jest mniejszy niż x
.
Przy tych założeniach powyższe może być również zapisane jako (i powinno być nieco szybsze):
d3$z <- sapply(d1$x, function(x) d2$z[which.max(x <= d2$x)])
W rezultacie otrzymujemy:
d3
## x y z
## 1 4 10 200
## 2 6 20 200
## 3 7 30 300
EDIT1: Zainspirowany przez @MatthewLundberg cut
oparte na rozwiązaniu, oto kolejny z nich findInterval
:
d3$z <- d2$z[findInterval(d1$x, d2$x+1)+1]
EDIT2: (Benchmark)
Przykładowe dane:
set.seed(123)
d1 <- data.frame(x=sort(sample(1:10000, 1000)), y=sort(sample(1:10000, 1000)))
d2 <- data.frame(x=sort(c(sample(1:10000, 999), 10000)), z=sort(sample(1:10000, 1000)))
Wyniki:
microbenchmark::microbenchmark(
{d3 <- d1; d3$z <- d2$z[findInterval(d1$x, d2$x+1)+1] },
{d3 <- d1; d3$z <- sapply(d1$x, function(x) d2$z[which(x <= d2$x)[1]]) },
{d3 <- d1; d3$z <- sapply(d1$x, function(x) d2$z[which.max(x <= d2$x)]) },
{d1$x2 <- d2$x[as.numeric(cut(d1$x, c(-Inf, d2$x, Inf)))]; merge(d1, d2, by.x="x2", by.y="x")},
{d1a <- d1; setkey(setDT(d1a), x); d2a <- d2; setkey(setDT(d2a), x); d2a[d1a, roll=-Inf] }
)
## Unit: microseconds
## expr min lq median uq max neval
## findInterval 221.102 1357.558 1394.246 1429.767 17810.55 100
## which 66311.738 70619.518 85170.175 87674.762 220613.09 100
## which.max 69832.069 73225.755 83347.842 89549.326 118266.20 100
## cut 8095.411 8347.841 8498.486 8798.226 25531.58 100
## data.table 1668.998 1774.442 1878.028 1954.583 17974.10 100
4 dla odpowiedzi nr 2
To całkiem proste użycie toczące się połączenia z data.table
:
require(data.table) ## >= 1.9.2
setkey(setDT(d1), x) ## convert to data.table, set key for the column to join on
setkey(setDT(d2), x) ## same as above
d2[d1, roll=-Inf]
# x z y
# 1: 4 200 10
# 2: 6 200 20
# 3: 7 300 30
2 dla odpowiedzi nr 3
cut
można użyć do znalezienia odpowiednich dopasowań w d2$x
dla wartości w d1$x
.
Obliczenia, aby znaleźć dopasowania z cut
następująco:
as.numeric(cut(d1$x, c(-Inf, d2$x, Inf)))
## [1] 2 2 3
Są to wartości:
d2$x[as.numeric(cut(d1$x, c(-Inf, d2$x, Inf)))]
[1] 6 6 9
Można je dodać do d1
i połączenie się odbyło:
d1$x2 <- d2$x[as.numeric(cut(d1$x, c(-Inf, d2$x, Inf)))]
merge(d1, d2, by.x="x2", by.y="x")
## x2 x y z
## 1 6 4 10 200
## 2 6 6 20 200
## 3 9 7 30 300
Dodaną kolumnę można następnie usunąć, jeśli jest to pożądane.
1 dla odpowiedzi nr 4
Próbować: sapply(d1$x,function(y) d2$z[d2$x > y][which.min(abs(y - d2$x[d2$x > y]))])