/ / Porównanie numeryczne podczas scalania w R - r, scalanie, ramka danych

Porównanie liczbowe podczas scalania w R - r, scalanie, ramka danych

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 № 1

Dane 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 cutoparte 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]))])