-
Notifications
You must be signed in to change notification settings - Fork 991
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
Both columns for rolling and non-equi joins #3093
base: master
Are you sure you want to change the base?
Conversation
New SQL-like column return now works when j is provided and there are only non-equi / roll key columns in 'on'.
…n(s) 129: added additional V1 column from J() to expected output. 130: added additional V1 column from J() to expected output. 131: added additional V1 column from J() to expected output. 148: added additional V1 column from CJ() to expected output. 149: added additional V1 column from J() to expected output. 917: updated expected output. added i.ts and updated ts (from x) to contain matched values from x$ts instead of matched values from i$ts. 1317.1: updated expected output. added i.y and updated y (from x) to contain matched values from x$y instead of matched values from i$y. 1317.2: updated expected output. added i.y and updated y (from x) to contain matched values from x$y instead of matched values from i$y. 1470.1: rolling join on J(), renamed x column from output as V1 and put in correct position, then added x as DT$x. 1470.2: rolling join on J(), renamed x column from output as V1 and put in correct position, then added x as DT$x. 1470.3: rolling join on SJ(), renamed x column from output as V1 and put in correct position, then added x as DT$x. 1614.1: updated expected output. renamed x column from output as V1 and put in correct position, then added dt$x column to expected output. 1614.2: updated expected output. renamed x column from output as V1 and put in correct position, then added dt$x column to expected output. 1614.3: updated expected output. renamed x column from output as V1 and put in correct position, then added dt$x column to expected output. 1614.4: updated expected output. renamed x column from output as V1 and put in correct position, then added dt$x column to expected output. 1660.1: updated expected return columns. 1660.2: updated expected return columns. 1682.1: Updated `by` statement in chained operator to use column names in dt2 to match columns used by `by=.EACHI` 1682.2: Updated `by` statement in chained operator to use column names in dt2 to match columns used by `by=.EACHI` 1682.3: Updated `by` statement in chained operator to use column names in dt2 to match columns used by `by=.EACHI` 1682.4: Updated `by` statement in chained operator to use column names in dt2 to match columns used by `by=.EACHI` 1682.5: Updated `by` statement in chained operator to use column names in dt2 to match columns used by `by=.EACHI` 1682.6: Updated `by` statement in chained operator to use column names in dt2 to match columns used by `by=.EACHI` 1682.7: Updated `by` statement in chained operator to use column names in dt2 to match columns used by `by=.EACHI` New bugs (test ouput different from expected new behaviour): 1660.1: "End" column missing from output, and Location column has contents of dt1$end even though it is involved in a non-equi join. 1660.2: "Location" column missing from output. 1682.1: Column V2 is missing from non-equi join result 1682.2: Column V2 is missing from non-equi join result 1682.3: Column V2 is missing from non-equi join result 1682.4: Column V2 is missing from non-equi join result 1682.5: Column V2 is missing from non-equi join result 1682.6: Column V2 is missing from non-equi join result 1682.7: Column V2 is missing from non-equi join result Bugs that have regressed with this PR and need to be patched again: 1469.2: after roll DT now has a key when it shouldnt Tests that need following up, but may not be due to introduced bugs 1540.15: unexpected difference in column order (but not contents) after rolling join. 1614.4: values in dt$x do not match those expected based on values in dt$y, but this might be due to rollends=TRUE. 1846.1: columns do not match - check that handling of 0-row dt in i is correct based on linked issue. 1846.2 columns do not match - check that handling of 0-row dt in i is correct based on linked issue.
Expected output still had value from `J(4)` in `a` column instead of `TESTDT$a`.
Expected output still had values from `CJ(2,3)` in `a` column instead of `dt$a`.
missing c() inside of as.integer64
Added columns from empty d1 to expected output and and added column names prefixed with i. for the columns from d2.
Tests 1660.1, 1660.2 and 1846.1 now pass
To get the equivalent grouping of `by = .EACHI` when chaining operators you now have to specify `by = .(key.x = key.i)` to get the same answer and column names
This test checks whether a rolling join can be perform when a key has been set in the left data.table (DT1.copy), but not in the the right data.table (DT3). DT3 has to be reordered in this case so that the column to join on is the first column in DT3. Now that the rolling join column y is returned from both DT1 and DT3, the test needed to be updated to reorder the columns in the expected output to match the ordering in the tested join.
Updated columns extracted in j to `V1` to reflect `x` is now from `DT` rather than `J(c(2,0))`. When extracting `x` instead of `V1`, `irows` is sorted so the result has `key() == "x"`
Functionality tested is captured by earlier unit tests that have been updated.
When TRUE (default) non-equi and roll columns are returned from both x and i.
options(datatable.bothkeycols=FALSE) is set at the top of the file so these tests can remain unmodified
incremented test number in tests.Rraw to solve merge conflict. Merge branch 'master' into non-equi-key # Conflicts: # inst/tests/tests.Rraw
@sritchie73 could you please merge to recent master and work out code coverage? |
hi any update/plan to merge this soon? I would love to use non-equi join more often in research and teaching and this PR would make it so much easier to use/explain. |
I'm not sure how easy this will be to get work after all this time, but I think it is an important feature, so I tried to resolve the conflicts with master. |
on master we have in data.table.R ans = bmerge(i, x, leftcols, rightcols, roll, rollends, nomatch, mult, ops, verbose=verbose) whereas that line in this branch is now ans = bmerge(i, x, leftcols, rightcols, xo, roll, rollends, nomatch, mult, ops, nqgrp, nqmaxgrp, verbose=verbose) https://github.com/Rdatatable/data.table/blame/c6e5a0384ce2d6fe5f45406ffc07baea9ab2d460/R/data.table.R#L513 shows this line was modified in this pr, but why is the definition of bmerge not consistently updated in this pr? currently bmerge.R is defined as below so I guess the solution is to remove the extra arguments. bmerge = function(i, x, icols, xcols, roll, rollends, nomatch, mult, ops, verbose) |
Generated via commit 7889c86 Download link for the artifact containing the test results: ↓ atime-results.zip
|
we are getting non_equi not found error now. diff -u "c:/Users/hoct2726/R/data.table/R/data.table-master.R" "c:/Users/hoct2726/R/data.table/R/data.table-before-merge.R"
--- c:/Users/hoct2726/R/data.table/R/data.table-master.R 2024-11-28 14:37:50.101759300 -0500
+++ c:/Users/hoct2726/R/data.table/R/data.table-before-merge.R 2024-11-28 14:37:47.261831400 -0500
@@ -1,43 +1,78 @@
- if (is.data.frame(i)) {
- if (missing(on)) {
- if (!haskey(x)) {
- stopf("When i is a data.table (or character vector), the columns to join by must be specified using 'on=' argument (see ?data.table), by keying x (i.e. sorted, and, marked as sorted, see ?setkey), or by sharing column names between x and i (i.e., a natural join). Keyed joins might have further speed benefits on very large data due to x being sorted in RAM.")
- }
- } else if (identical(substitute(on), as.name(".NATURAL"))) {
- naturaljoin = TRUE
- }
- if (naturaljoin) { # natural join #629
- common_names = intersect(names_x, names(i))
- len_common_names = length(common_names)
- if (!len_common_names) stopf("Attempting to do natural join but no common columns in provided tables")
- if (verbose) {
- which_cols_msg = if (len_common_names == length(x)) {
- catf("Joining but 'x' has no key, natural join using all 'x' columns")
- } else {
- catf("Joining but 'x' has no key, natural join using: %s", brackify(common_names))
- }
- }
- on = common_names
+ if (is.data.table(i)) {
+ if (!haskey(x) && missing(on) && is.null(xo)) {
+ stop("When i is a data.table (or character vector), the columns to join by must be specified either using 'on=' argument (see ?data.table) or by keying x (i.e. sorted, and, marked as sorted, see ?setkey). Keyed joins might have further speed benefits on very large data due to x being sorted in RAM.")
}
if (!missing(on)) {
# on = .() is now possible, #1257
on_ops = .parse_on(substitute(on), isnull_inames)
on = on_ops[[1L]]
ops = on_ops[[2L]]
- if (any(ops > 1L)) { ## fix for #4489; ops = c("==", "<=", "<", ">=", ">", "!=")
- allow.cartesian = TRUE
- }
# TODO: collect all '==' ops first to speeden up Cnestedid
- rightcols = colnamesInt(x, names(on), check_dups=FALSE)
- leftcols = colnamesInt(i, unname(on), check_dups=FALSE)
- } else {
- ## missing on
- rightcols = chmatch(key(x), names_x) # NAs here (i.e. invalid data.table) checked in bmerge()
+ rightcols = chmatch(names(on), names(x))
+ if (length(nacols <- which(is.na(rightcols))))
+ stop("Column(s) [", paste(names(on)[nacols], collapse=","), "] not found in x")
+ leftcols = chmatch(unname(on), names(i))
+ if (length(nacols <- which(is.na(leftcols))))
+ stop("Column(s) [", paste(unname(on)[nacols], collapse=","), "] not found in i")
+ # figure out the columns on which to compute groups on
+ non_equi = which.first(ops != 1L) # 1 is "==" operator
+ if (!is.na(non_equi)) {
+ # non-equi operators present.. investigate groups..
+ if (verbose) cat("Non-equi join operators detected ... \n")
+ if (!missingroll) stop("roll is not implemented for non-equi joins yet.")
+ if (verbose) {last.started.at=proc.time();cat(" forder took ... ");flush.console()}
+ # TODO: could check/reuse secondary indices, but we need 'starts' attribute as well!
+ xo = forderv(x, rightcols, retGrp=TRUE)
+ if (verbose) {cat(timetaken(last.started.at),"\n"); flush.console()}
+ xg = attr(xo, 'starts')
+ resetcols = head(rightcols, non_equi-1L)
+ if (length(resetcols)) {
+ # TODO: can we get around having to reorder twice here?
+ # or at least reuse previous order?
+ if (verbose) {last.started.at=proc.time();cat(" Generating group lengths ... ");flush.console()}
+ resetlen = attr(forderv(x, resetcols, retGrp=TRUE), 'starts')
+ resetlen = .Call(Cuniqlengths, resetlen, nrow(x))
+ if (verbose) {cat("done in",timetaken(last.started.at),"\n"); flush.console()}
+ } else resetlen = integer(0L)
+ if (verbose) {last.started.at=proc.time();cat(" Generating non-equi group ids ... ");flush.console()}
+ nqgrp = .Call(Cnestedid, x, rightcols[non_equi:length(rightcols)], xo, xg, resetlen, mult)
+ if (verbose) {cat("done in",timetaken(last.started.at),"\n"); flush.console()}
+ if (length(nqgrp)) nqmaxgrp = max(nqgrp) # fix for #1986, when 'x' is 0-row table max(.) returns -Inf.
+ if (nqmaxgrp > 1L) { # got some non-equi join work to do
+ if ("_nqgrp_" %in% names(x)) stop("Column name '_nqgrp_' is reserved for non-equi joins.")
+ if (verbose) {last.started.at=proc.time();cat(" Recomputing forder with non-equi ids ... ");flush.console()}
+ set(nqx<-shallow(x), j="_nqgrp_", value=nqgrp)
+ xo = forderv(nqx, c(ncol(nqx), rightcols))
+ if (verbose) {cat("done in",timetaken(last.started.at),"\n"); flush.console()}
+ } else nqgrp = integer(0L)
+ if (verbose) cat(" Found", nqmaxgrp, "non-equi group(s) ...\n")
+ }
+ if (is.na(non_equi)) {
+ # equi join. use existing key (#1825) or existing secondary index (#1439)
+ if ( identical(head(key(x), length(on)), names(on)) ) {
+ xo = integer(0L)
+ if (verbose) cat("on= matches existing key, using key\n")
+ } else {
+ if (isTRUE(getOption("datatable.use.index"))) {
+ xo = getindex(x, names(on))
+ if (verbose && !is.null(xo)) cat("on= matches existing index, using index\n")
+ }
+ if (is.null(xo)) {
+ if (verbose) {last.started.at=proc.time(); flush.console()}
+ xo = forderv(x, by = rightcols)
+ if (verbose) {cat("Calculated ad hoc index in",timetaken(last.started.at),"\n"); flush.console()}
+ # TODO: use setindex() instead, so it's cached for future reuse
+ }
+ }
+ }
+ } else if (is.null(xo)) {
+ rightcols = chmatch(key(x),names(x)) # NAs here (i.e. invalid data.table) checked in bmerge()
leftcols = if (haskey(i))
- chmatch(head(key(i), length(rightcols)), names(i))
+ chmatch(head(key(i),length(rightcols)),names(i))
else
seq_len(min(length(i),length(rightcols)))
rightcols = head(rightcols,length(leftcols))
+ xo = integer() ## signifies 1:.N
ops = rep(1L, length(leftcols))
}
# Implementation for not-join along with by=.EACHI, #604 I could use some help from anyone who has hacked data.table.R (this is my first time). |
I would rather look at the diff of this PR vs master at the time of fork, then understand the logic, then apply same logic in a fresh branch forked from current master. |
👍 to @jangorecki 's suggestion - from recollection at the time it was also one of my first attempts at modifying the data.table package, so the diff between the original upstream commit and the changes made should be relatively small. You may then want to create a new clean branch from the current master and incorporate that same logic as needed, rather than modifying my very old branch. |
I can't push to remote branch
sritchie73:non-equi-key
so copied it into main from PR #2706.Looks like commit history from Scott is retained which is good, and retained the branch name.
The extended discussion history will just be in the previous PR #2706 though. Can't think of a way round this. Scott is member of project so can create branches in main going forward.
Closes #1615
Closes #1700
Closes #2006
Closes #2569
Other related issues already closed (e.g. dups) : #1469, #1807, #2307, #2595, #2602
datatable.bothkeycols
is set to TRUE (caused by typodatatable.bothkeycol
vsdatatable.bothkeycols
)bothkeycols=
renamed toold.nonequi=
(explanation in comments below)roll=
column just after the first one, not at the end of the result.