Skip to content

join column naming with on syntax #1700

Description

@eantonya

I remember seeing a similar issue to this, but am failing to find it at the moment.

I find current naming of columns with on syntax to be confusing and counterintuitive. This becomes especially apparent when using non-equi joins. Right now the column name is the left side in an on = .(LHS = RHS) expression and imo it would make a lot more sense for it to be the right side.

X = data.table(pos = 1:3)
#   pos
#1:   1
#2:   2
#3:   3

Y = data.table(start = c(1, 3), end = c(2, 4))
#   start end
#1:     1   2
#2:     3   4

Y[X, on = .(start <= pos, end >= pos)]
#   start end
#1:     1   1
#2:     2   2
#3:     3   3

X[Y, on = .(pos >= start, pos <= end)]
#   pos pos.1
#1:   1     2
#2:   1     2
#3:   3     4

In both cases above it would make a lot more sense imo to have the column name taken from RHS.

Activity

  1. jangorecki commented on May 11, 2016

    @jangorecki
    Member

    Agree it makes more sense, but I'm not sure if we should change that default.
    This issue overlaps with #1615.
    Current behavior, the default when j is not provided, is consistent to base R merge (+ making name unique).

    base::merge(X, Y, by.x=c("pos","pos"), by.y=c("start","end"))
    #Empty data.table (0 rows) of 2 cols: pos,pos

    The good thing is that it is just a matter of using j argument, and since it now handles both x. and i. prefixes, it should handle all cases.

  2. eantonya commented on May 11, 2016

    @eantonya
    ContributorAuthor

    Sticking to a poor decision because base does it that way has never been the philosophy of data.table.

    I agree that using x. and i. gives a workaround, but also quickly becomes a huge pain if you have many (non-join) columns (that you'll now have to either type out, or will have to change the names of the join columns later).

  3. jangorecki commented on May 11, 2016

    @jangorecki
    Member

    This is what the linked issue is about, to drop consistency to base R when joining with [.
    I usually produce j as quoted expression in such cases. Not really user friendly but its quite straightforward and works like a charm :)

  4. eantonya commented on May 11, 2016

    @eantonya
    ContributorAuthor

    Thanks - that was the issue I was looking for earlier.

  5. tdhock commented on Mar 24, 2017

    @tdhock
    Member

    I completely agree with @eantonya -- I just ran into the same issue, and I dealt with it by using setnames after the join:

    X = data.table(pos = 1:3)
    Y = data.table(start = c(1, 3), end = c(2, 4))
    joined.badColNames <- X[Y, on = .(pos >= start, pos <= end)]
    joined.goodColNames <- data.table(joined.badColNames)
    setnames(joined.goodColNames, c("start", "end"))
    > joined.badColNames
       pos pos.1
    1:   1     2
    2:   1     2
    3:   3     4
    > joined.goodColNames
       start end
    1:     1   2
    2:     1   2
    3:     3   4
    

    it would be more user-friendly if data.table would rename the columns (and it would be backwards-compatible with the setnames code that I have already written)

  6. eantonya commented on Dec 8, 2017

    @eantonya
    ContributorAuthor
  7. ethanbsmith commented on Mar 5, 2018

    @ethanbsmith
    Contributor

    simple example of non-equi join column name masking

    
    a <- data.table(foo = c(1:5))
    b <- data.table(bar = c(1:5))
    
    > a
       foo
    1:   1
    2:   2
    3:   3
    4:   4
    5:   5
    > b
       bar
    1:   1
    2:   2
    3:   3
    4:   4
    5:   5
    

    It's very odd that these outputs below are not identical. if you read all the related issues, many people expect them to be and keep getting surprised that they are not

    identical(a[b, on = .(foo >= bar), .(foo, bar)], a[b, on = .(foo >= bar), .(x.foo, i.bar)])

  8. sritchie73 commented on Mar 8, 2018

    @sritchie73
    Contributor

    Just wanted to weigh in on this topic since it keeps cropping up for me when teaching non-equi and rolling joins (Have I found the root issue? I followed a chain of issues closed as duplicates – it looks like this is the original).

    Currently the way I teach both non-equi joins and rolling joins is to recommend students to create duplicates of the join columns, e.g.:

    dt.x[, by.x.copy := by.x]
    dt.y[, by.y.copy := by.y]
    joined <- dt.x[dt.y, on = .(by.x.copy <= by.y.copy)]
    joined[, by.x.copy := NULL] # remove meaningless and misleading column
    

    That way the information in the non-equi/rolling join key columns from both data.tables are preserved, and the duplicate column actually used in the on argument can be deleted since it is redundant (and at worst, misleading).

    As others have proposed, I think there is a strong rationale for treating join key columns differently in the case of non-equi and rolling joins – that is both columns should be kept in the result of the join. There is precedence for this already in the foverlaps() function, which will return both sets of interval columns in the join, while additional keys are treated the same way as in merge() (i.e. only by.x is kept).

    My view is that breaking backwards compatibility with merge() in these cases is fine, since it is not possible to perform non-equi or rolling joins with merge().

    This would also simplify learning of these joins, since the current behaviour is unintuitive and the source of many conceptual and runtime errors.

  9. added this to the milestone on Apr 10, 2018
  10. modified the milestones: v1.11.0, on Apr 29, 2018
  11. removed this from the 1.12.0 milestone on Jun 6, 2018
  12. 8 remaining items

  13. modified the milestones: 1.12.4, 1.13.0 on Sep 19, 2019
  14. modified the milestones: 1.12.7, 1.12.9 on Dec 8, 2019
  15. tdhock commented on Apr 3, 2020

    @tdhock
    Member

    hi all just wondering if you can prioritize this issue /plan to include in an upcoming release? I also would like to include this in my teaching but it is needlessly complicated with current version (the column names returned are confusing)

  16. jangorecki commented on Apr 3, 2020

    @jangorecki
    Member

    I think the solution might be already waiting in PRs, if so we should put it into next release.

  17. MichaelChirico commented on Apr 3, 2020

    @MichaelChirico
    Member

    There is, but no commits since Oct 2018 -- may take some time to de-conflict & rebase to current master

    https://github.com/Rdatatable/data.table/pull/3093/commits

  18. sritchie73 commented on Apr 3, 2020

    @sritchie73
    Contributor
  19. jangorecki commented on Apr 3, 2020

    @jangorecki
    Member

    I think the important thing is to give option for backward compatibility.

  20. added
    joinsUse label:"non-equi joins" for rolling, overlapping, and non-equi joins
    on Apr 10, 2020
  21. added
    top requestOne of our most-requested issues
    and removed on Oct 15, 2020
  22. modified the milestones: 1.13.1, 1.13.3 on Oct 17, 2020
  23. removed this from the 1.14.1 milestone on Aug 28, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementfeature requestjoinsUse label:"non-equi joins" for rolling, overlapping, and non-equi joinstop requestOne of our most-requested issues

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions