-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add %fin% operator for fast row-wise existence checks (#2279) #7880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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))) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
|
@@ -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.} | ||
|
|
@@ -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}. | ||
|
|
@@ -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] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 } | ||
There was a problem hiding this comment.
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