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])
}
join_cols = names(x)
if (!all(join_cols %in% names(y))) {
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))
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]
}
\keyword{ data }
Loading