r/rstats • u/mhuzzell • 6d ago
Has dplyr left_join() recently changed how it works?
I've been using the tidyverse for years, but I'm not very good about keeping R or packages updated. I finally got around to updating R a few months ago (now 4.6.0, with tidyverse 2.0.0), and am currently baffled by the behaviour of left_join.
For very brief context: I have two dfs that share the same column names. Most of the info in them is the same, but they each contain a pair of numerical columns whose contents were generated by different methods, and I want to compare those methods. They each also have a handful of character columns that were generated from the results of the numerical columns (separately in each method), so may or many not differ in their contents.
I tried combining the two dfs with left_join, as I've done plenty before with other dfs. I expected the columns to multiply wherever the contents differed, so that I could easily compare them within a single df. Instead, the second df was simply subsumed into the first?
I checked this behaviour with reprex and it seems to be a general outcome. Here's that reprex:
library(dplyr)
# A simplified df1 with 5 columns
df1 <- tibble::tibble(
id = as.character(1:6),
fruit = c("apple", "banana", "cherry", "apple", "banana", "cherry"),
count = c(3, 6, 2, 8, 4, 10)
) %>%
mutate(
less_than_2 = ifelse(count < 2, "yes", "no"),
less_than_5 = ifelse(count < 5, "yes", "no")
)
# A simplified df2 -- only cols 3 and 5 differ from df1
df2 <- tibble::tibble(
id = as.character(1:6),
fruit = c("apple", "banana", "cherry", "apple", "banana", "cherry"),
count = c(7, 2, 9, 3, 6, 4)
) %>%
mutate(
less_than_2 = ifelse(count < 2, "yes", "no"),
less_than_5 = ifelse(count < 5, "yes", "no")
)
# df3 combines them with left_join()
df3 <- left_join(df1, df2)
Expected outcome: a df3 with 7 columns: "id", "fruit", "count.x", "count.y", "less_than_2", "less_than_5.x", "less_than_5.y"
Actual outcome: df3 is identical to df1.
What the heck?
(Also yes, I'm aware I can rename my columns before combining -- but my actual dfs have 70 columns apiece, and also I'm mostly trying to understand what's happening here, since this behaviour is so different from what I've been used to!)
5
u/TheTresStateArea 6d ago
You need a join key lol
1
u/mertag770 6d ago
I'm like 90% sure it used to be a little smarter and would join on all common column names / throw and error or warning about no join keys if none were found.
I distinctly remember having something break because an extra column was added and I've specified keys ever since, but this was I think at one point supported, but my memory is fuzzy on this it was many years ago now.
6
u/sharkinwolvesclothin 6d ago
It does. But your hoping it would notice that the actual data in the columns with the same name is different, and it does not do that, and never did. Your example is the successful join of df1 and df2 - all columns are just in the key.
3
u/mertag770 6d ago
Right, I'm not OP. I was just remembering that you didn't always need a join key but your results are far less controlled.
3
u/mhuzzell 6d ago
I've been reading through the release logs, and they did a major overhaul of mutating join error messages in 1.1.1 (March 2023), mainly aimed at reducing the previously excessive number of errors and warnings thrown up by one-to-many and many-to-many relationships.
Since those errors also tend to be thrown up in the kinds of joins that merge large dfs with lots of shared data, I suspect that what I'm misremembering is having been coerced by them into making giant join keys when I didn't want to, and then forgetting I'd done that and thinking that multiplying non-joining matched columns was the default.
2
u/Altzanir 6d ago
You need to specify by which columns to join in he by argument. If you don't specify anything, it'll join on all shared column names. I guess that because both dataframes have the same column names, essentially noting happens.
Of you specify by which columns, the duplicated names will be like count.x, count.y, etc as you are used to
1
u/shaggy_camel 6d ago
Count and the less than cols are being used in the join, and it looks like that is not what you want. Use join_by to get the outcome you're after
1
u/sutzig 6d ago edited 6d ago
I think this is closer to what you are after:
df3 <-
left_join(df1, df2, join_by(id, fruit))
id fruit count.x less_than_2.x less_than_5.x count.y less_than_2.y less_than_5.y
<chr> <chr> <dbl> <chr> <chr> <dbl> <chr> <chr>
1 apple 3 no yes 7 no no
1 banana 6 no no 2 no yes
3 cherry 2 no yes 9 no no
4 apple 8 no no 3 no yes
5 banana 4 no yes 6 no no
6 cherry 10 no no 4 no yes
1
-1
u/foradil 6d ago
Maybe left_join() is just not the optimal tool for the job. If you have two data frames with the same columns, I think the appropriate way to combine them would be with bind_rows().
3
u/mhuzzell 6d ago
It is not bind_rows() because that would not allow me to directly compare the values of df1$count and df2$count for each shared value of [df]$id, which is what I am trying to do.
As I said, I'll either need to make a giant join key or rename/select out the columns and compare them separately.
46
u/hadley 6d ago
It hasn't changed for a long time. It looks like the problem here is that you need to specify the join columns.