Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export(data.table, tables, setkey, setkeyv, key, haskey, CJ, SJ, copy)
export(rowwiseDT)
export(setindex, setindexv, indices)
export(as.data.table,is.data.table,test.data.table)
export(last,first,like,"%like%","%ilike%","%flike%","%plike%",between,"%between%",inrange,"%inrange%", "%notin%")
export(last,first,like,"%like%","%ilike%","%flike%","%plike%",between,"%between%",inrange,"%inrange%", "%notin%", "%fin%")
export(timetaken)
export(truelength, setalloccol, setallocrow, alloc.col, ":=", let)
export(setattr, setnames, setcolorder, set, setDT, setDF)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@

20. `print.data.table()` now correctly displays data when `col.names="none"` and `row.names=FALSE`, [#7735](https://github.com/Rdatatable/data.table/issues/7735). Thanks to @jan-swissre for the report and @venom1204 for the fix.

21. New `%fin%` operator for fast row-wise existence checks, e.g., `x %fin% y` or `x[, found := .(col1, col2) %fin% y]`. It supports multi-column matching using `data.table` join semantics, offering a concise and high-performance alternative to standard R `%in%` for membership testing, [#2279](https://github.com/Rdatatable/data.table/issues/2279). Thanks to @franknarf1 for the request and @venom1204 for teh implementation.

### Notes

1. {data.table} now depends on R 3.5.0 (2018).
Expand Down
26 changes: 26 additions & 0 deletions R/setops.R
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,29 @@ all.equal.data.table = function(target, current, trim.levels=TRUE, check.attribu
}
TRUE
}

`%fin%` = function(x, y) {
mc = match.call()
if (is.list(x) && !is.data.table(x)) {
if (is.call(mc$x) && (identical(mc$x[[1L]], quote(`list`)) || identical(mc$x[[1L]], quote(`.`)))) {
nms = names(mc$x)
arg_names = as.character(mc$x[-1L])
if (is.null(nms)) {
names(x) = arg_names
} else {
names(x) = ifelse(!nzchar(nms[-1L]), arg_names, nms[-1L])
}
}
x = as.data.table(x)
} else if (!is.data.table(x)) {
x = as.data.table(list(x))
setnames(x, names(y)[1])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using 1L here as well should be preferred

}
join_cols = names(x)
if (!all(join_cols %in% names(y))) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we stop in case there are duplicates in column names within one table?

stopf("All columns in the left-hand side (%s) must exist in the right-hand table (%s).",
paste(join_cols, collapse=","), paste(names(y), collapse=","))
}
ans = y[x, .N, on=join_cols, by=.EACHI]
ans$N > 0L
}
11 changes: 11 additions & 0 deletions inst/tests/tests.Rraw
Original file line number Diff line number Diff line change
Expand Up @@ -21985,3 +21985,14 @@ DT3 = data.table(a=c(1,NA), b=c("x",NA), c=c(3,NA))
test(2387.04, setnafill(copy(DT3), type="locf", cols=sapply(DT3, is.numeric)), data.table(a=c(1,1), b=c("x",NA), c=c(3,3)))
test(2387.05, setnafill(copy(DT3), type="locf", cols=c(TRUE,NA,FALSE)), error="'cols' contains NA at position 2")
test(2387.06, setnafill(copy(DT3), type="locf", cols=c(TRUE,FALSE)), error="'cols' is a logical vector of length 2 but there are 3 columns")

# %fin% set operator, #2279
x = data.table(name=c("A","B","C"), year=c(2020,2020,2021), grade=c(8,9,10)); y = data.table(name=c("A","B","D"), year=c(2020,2019,2021), grade=c(8,9,10))
test(2388.01, x %fin% y, c(TRUE,FALSE,FALSE))
test(2388.02, x[, .(name,year,grade) %fin% y], c(TRUE,FALSE,FALSE))
test(2388.03, data.table(name=c("A","A","B"),year=c(2020,2020,2021),grade=c(8,8,9)) %fin% y, c(TRUE,TRUE,FALSE))
test(2388.04, data.table(name=c("A","B"),year=c(2020,2021),grade=c(8,9)) %fin% y, c(TRUE,FALSE))
test(2388.05, data.table(name=c("A","B"),year=c(2020,2021),grade=c(8,9)) %fin% data.table(name=c("A","B"),year=c(2020,2021)), error="All columns in the left-hand side")
test(2388.06, data.table(name=c("A","B"),year=c(2020,2021),grade=c(8,9)) %fin% data.table(name=c("A","B"),year=c(2020,2021),grade=c(8,9),extra=c(1,2)), c(TRUE,TRUE))
test(2388.07, list(name=c("A","B"), year=c(2020,2022)) %fin% y, c(TRUE,FALSE))
test(2388.08, c("A","B") %fin% y[, .(name)], c(TRUE,TRUE))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a test where y has duplicates, and then also x and y both have duplicates on fields they need to join.

7 changes: 7 additions & 0 deletions man/setops.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
\alias{funion}
\alias{setequal}
\alias{fsetequal}
\alias{\%fin\%}
\title{ Set operations for data tables }
\description{
Similar to base R set functions, \code{union}, \code{intersect}, \code{setdiff} and \code{setequal} but for \code{data.table}s. Additional \code{all} argument controls how duplicated rows are handled. Functions \code{fintersect}, \code{setdiff} (\code{MINUS} or \code{EXCEPT} in SQL) and \code{funion} are meant to provide functionality of corresponding SQL operators. Unlike SQL, data.table functions will retain row order.
Expand All @@ -19,6 +20,7 @@ fintersect(x, y, all = FALSE)
fsetdiff(x, y, all = FALSE)
funion(x, y, all = FALSE)
fsetequal(x, y, all = TRUE)
x \%fin\% y
}
\arguments{
\item{x, y}{\code{data.table}s.}
Expand All @@ -33,6 +35,7 @@ fsetequal(x, y, all = TRUE)
}
\details{
\code{bit64::integer64} columns are supported but not \code{complex} and \code{list}, except for \code{funion}.
\code{\%fin\%} provides a fast way to check if rows of x exist in y.
}
\value{
A data.table in case of \code{fintersect}, \code{funion} and \code{fsetdiff}. Logical \code{TRUE} or \code{FALSE} for \code{fsetequal}.
Expand All @@ -54,5 +57,9 @@ funion(x, y) # union
funion(x, y, all=TRUE) # union all
fsetequal(x, x2, all=FALSE) # setequal
fsetequal(x, x2) # setequal all
# %fin% operator
x = data.table(name=c("A","B","C"), year=c(2020,2020,2021))
y = data.table(name=c("A","B"), year=c(2020,2020))
x[, found := .(name, year) \%fin\% y]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add simpler example first, basic x %in% y, without [

}
\keyword{ data }
Loading