r data.table

Realizar una semi-unión con data.table.



(7)

¿Cómo realizo una semi-join con data.table? Una semi-unión es como una unión interna, excepto que solo devuelve las columnas de X (no también las de Y), y no repite las filas de X para que coincidan con las filas de Y. Por ejemplo, el siguiente código realiza una unirse:

x <- data.table(x = 1:2, y = c("a", "b")) setkey(x, x) y <- data.table(x = c(1, 1), z = 10:11) x[y] # x y z # 1: 1 a 10 # 2: 1 a 11

Una semi-unión devolvería solo x[1]


El paquete dplyr admite los siguientes cuatro tipos de unión:

inner_join , left_join , semi_join , anti_join

Así que para la semi-unión prueba el siguiente código

library("dplyr") table1 <- data.table(x = 1:2, y = c("a", "b")) table2 <- data.table(x = c(1, 1), z = 10:11) semi_join(table1, table2)

La salida es la esperada:

# Joining by: "x" # Source: local data table [1 x 2] # # x y # (int) (chr) # 1 1 a


Este hilo es tan viejo. Pero noté que la solución se puede derivar fácilmente de la definición de semi-join dada en la publicación original:

"Una semi-unión es como una unión interna, excepto que solo devuelve las columnas de X (no también las de Y), y no repite las filas de X para que coincidan con las filas de Y"

library(data.table) dt1 <- data.table(ProdId = 1:4, Product = c("Bread", "Cheese", "Pizza", "Butter")) dt2 <- data.table(ProdId = c(1, 1, 3, 4, 5), Company = c("A", "B", "C", "D", "E")) # semi-join unique(merge(dt1, dt2, on="ProdId")[, names(dt1), with=F]) ProdId Product 1: 1 Bread 2: 3 Pizza 3: 4 Butter

Simplemente apliqué la sintaxis de la unión interna, seguido del filtrado de columnas solo de la primera tabla, con unique() para eliminar las filas de la primera tabla que se repitieron para coincidir con las filas de la segunda tabla.

Edición: el enfoque anterior coincidirá con la dplyr::semi_join() solo si tenemos filas únicas en la primera tabla. Si necesitamos generar todas las filas, incluidos los duplicados de la primera tabla, entonces podemos usar el método fsetdiff() que se muestra a continuación.

Otra solución de una línea data.table :

fsetdiff(dt1, dt1[!dt2, on="ProdId"]) ProdId Product 1: 1 Bread 2: 3 Pizza 3: 4 Butter

Acabo de quitar de la primera mesa el anti-join de la primera y la segunda. Me parece más sencillo. Si la primera tabla tiene filas duplicadas, necesitaremos:

fsetdiff(dt1, dt1[!dt2, on="ProdId"], all=T)

El resultado de fsetdiff() con ,all=T coincide con la salida de dplyr:

dplyr::semi_join(dt1, dt2, by="ProdId") ProdId Product 1 1 Bread 2 3 Pizza 3 4 Butter

Usando otro conjunto de datos tomados de una de las publicaciones anteriores:

x <- data.table(x = c(1,1,1,2), y = c("a", "a", "a", "b")) y <- data.table(x = c(1, 1), z = 10:11)

Con dplyr:

dplyr::semi_join(x, y, by="x") x y 1 1 a 2 1 a 3 1 a

Con data.table:

fsetdiff(x, x[!y, on="x"], all=T) x y 1: 1 a 2: 1 a 3: 1 a

Sin ,all=T , se eliminan las filas duplicadas:

fsetdiff(x, x[!y, on="x"]) x y 1: 1 a


Estoy confundido con todas las no-combinaciones anteriores, no es lo que quieres simplemente:

unique(x[y, names(x), with = F]) # x y #1: 1 a

Si x puede tener claves duplicadas, entonces puedes y único en su lugar:

## Creating an example data.table ''a'' three-times-repeated first row x <- data.table(x = c(1,1,1,2), y = c("a", "a", "a", "b")) setkey(x, x) y <- data.table(x = c(1, 1), z = 10:11) setkey(y, x) x[eval(unique(y, by = key(y))), names(x), with = F] # data.table >= 1.9.8 requires by=key(y) # x y # 1: 1 a # 2: 1 a # 3: 1 a


Intenté escribir un método que no usa ningún nombre, lo cual es francamente confuso en el ejemplo del OP.

sJ <- function(x,y){ ycols <- 1:min(ncol(y),length(key(x))) yjoin <- unique(y[,ycols,with=FALSE,drop=FALSE]) yjoin } x[eval(sJ(x,y))]

Para el ejemplo más simple de Victor, esto da el resultado deseado:

x y 1: 1 a 2: 3 c 3: 5 e

Esto es un ~ 30% más lento que el de Victor.

EDITAR: Y el enfoque de Víctor, que es único antes de unirse, es un poco más rápido:

N <- 1e5*26 x <- data.table(x = 1:N, y = letters, z = rnorm(N)) setkey(x, x) y <- data.table(x = sample(N, N/10, replace = TRUE), z = sample(letters, N/10, replace = TRUE)) setkey(y, x) require(microbenchmark) microbenchmark( sJ=x[eval(sJ(x,y))], dolla=unique(x[eval(y$x)]), brack=x[eval(unique(y[[''x'']]))] ) Unit: milliseconds expr min lq median uq max neval # sJ 120.22700 125.04900 126.50704 132.35326 217.6566 100 # dolla 105.05373 108.33804 109.16249 118.17613 285.9814 100 # brack 53.95656 61.32669 61.88227 65.21571 235.8048 100

Supongo que el [[ vs $ no ayuda a la velocidad, pero no lo comprobé.


Más posibilidades:

w = unique(x[y,which=TRUE]) # the row numbers in x which have a match from y x[w]

Si hay valores de clave duplicados en x, entonces eso necesita:

w = unique(x[y,which=TRUE,allow.cartesian=TRUE]) x[w]

O al revés :

setkey(y,x) w = !is.na(y[x,which=TRUE,mult="first"]) x[w]

Si nrow (x) << nrow (y) entonces el enfoque y [x] debería ser más rápido.
Si nrow (x) >> nrow (y), entonces el enfoque x [y] debería ser más rápido.

Pero los llamamientos anti anti unirse también :-)


Una solución que se me ocurre es:

tmp <- x[!y] x[!tmp]

En data.table , puede tener otra tabla de datos como una expresión i (es decir, la primera expresión en data.table.[ Call), y eso realizará una unión, por ejemplo:

x <- data.table(x = 1:10, y = letters[1:10]) setkey(x, x) y <- data.table(x = c(1,3,5,1), z = 1:4) > x[y] x y z 1: 1 a 1 2: 3 c 2 3: 5 e 3 4: 1 a 4

El antes de que la expresión i sea ​​una extensión de la sintaxis anterior que realiza una ''no unión'', como se describe en la p. 11 de la documentation data.table. Así que las primeras asignaciones se evalúan como un subconjunto de x que no tiene ninguna fila donde la clave (columna x ) está presente en y :

> x[!y] x y 1: 2 b 2: 4 d 3: 6 f 4: 7 g 5: 8 h 6: 9 i 7: 10 j

Es similar a setdiff en este sentido. Y, por lo tanto, la segunda instrucción devuelve todas las filas en x donde la clave está presente en y .

El La característica se agregó en data.table 1.8.4 con la siguiente nota en NEWS :

o A new "!" prefix on i signals ''not-join'' (a.k.a. ''not-where''), #1384i. DT[-DT["a", which=TRUE, nomatch=0]] # old not-join idiom, still works DT[!"a"] # same result, now preferred. DT[!J(6),...] # !J == not-join DT[!2:3,...] # ! on all types of i DT[colA!=6L | colB!=23L,...] # multiple vector scanning approach (slow) DT[!J(6L,23L)] # same result, faster binary search ''!'' has been used rather than ''-'' : * to match the ''not-join''/''not-where'' nomenclature * with ''-'', DT[-0] would return DT rather than DT[0] and not be backwards compatible. With ''!'', DT[!0] returns DT both before (since !0 is TRUE in base R) and after this new feature. * to leave DT[+J...] and DT[-J...] available for future use

Por alguna razón, lo siguiente no funciona x[!(x[!y])] - probablemente data.table es demasiado inteligente para analizar el argumento.

PS Como Josh O''Brien señaló en otra respuesta, una línea sería x[!eval(x[!y])] .


Actualizar Basado en toda la discusión aquí, haría algo como esto, que debería ser rápido y funcionar en el caso más general:

x[eval(unique(y[, key(x), with = FALSE]))]

Aquí hay otra solución más directa:

unique(x[eval(y$x)])

Es más directo y se ejecuta más rápido. Aquí está la comparación en tiempos de ejecución con mi solución anterior:

# Generate some large data N <- 1000000 * 26 x <- data.table(x = 1:N, y = letters, z = rnorm(N)) setkey(x, x) y <- data.table(x = sample(N, N/10, replace = TRUE), z = sample(letters, N/10, replace = TRUE)) setkey(y, x) system.time(r1 <- x[!eval(x[!y])]) user system elapsed 7.772 1.217 11.998 system.time(r2 <- unique(x[eval(y$x)])) user system elapsed 0.540 0.142 0.723

En un caso más general, puedes hacer algo como

x[eval(y[, key(x), with = FALSE])]