minijoin provides four mutating joins – mjoin_inner_join()/_left_join()/_right_join()/_full_join() – built on base merge(). See the README for the full argument reference; this page compares all four side by side on two small toy data frames with a partial overlap, then covers the by argument in more detail.
Functions
mjoin_inner_join(x, y, by, ...) – keep only rows with a match in both x and y.
mjoin_left_join(x, y, by, ...) – keep all rows of x, filling in NA for y’s columns where there’s no match.
mjoin_right_join(x, y, by, ...) – keep all rows of y, filling in NA for x’s columns where there’s no match.
mjoin_full_join(x, y, by, ...) – keep all rows of both x and y, filling in NA on whichever side has no match.
minijoin has one internal helper, .mjoin_worker(), which all four join functions delegate to after fixing the right combination of merge()’s all.x/all.y arguments; it also restores x’s original row order, which merge() doesn’t guarantee on its own.
bands <-data.frame(band =c("Beatles", "Rolling Stones", "Kinks"),founded =c(1960, 1962, 1963))albums <-data.frame(band =c("Beatles", "Kinks", "Queen"),album =c("Abbey Road", "Arthur", "A Night at the Opera"))bands
band founded
1 Beatles 1960
2 Rolling Stones 1962
3 Kinks 1963
albums
band album
1 Beatles Abbey Road
2 Kinks Arthur
3 Queen A Night at the Opera
bands and albums share "Beatles" and "Kinks"; "Rolling Stones" exists only in bands, and "Queen" only in albums. The four join types differ only in which of those non-matching rows they keep.
Inner join: only rows matching in both
Neither "Rolling Stones" nor "Queen" appears – an inner join is the most restrictive of the four, keeping only rows with a counterpart on the other side:
mjoin_inner_join(bands, albums, by ="band")
band founded album
1 Beatles 1960 Abbey Road
2 Kinks 1963 Arthur
Left join: all rows of x (bands)
"Rolling Stones" is kept, with NA for album, since it has no match in albums. This is the join to reach for when x is your “main” table and you’re enriching it with optional data from y:
mjoin_left_join(bands, albums, by ="band")
band founded album
1 Beatles 1960 Abbey Road
2 Rolling Stones 1962 <NA>
3 Kinks 1963 Arthur
Right join: all rows of y (albums)
"Queen" is kept, with NA for founded. A right join is really just a left join with the arguments swapped – mjoin_right_join(x, y, ...) behaves like mjoin_left_join(y, x, ...), with columns in x‘s original order. Note the row order below: matched rows still come first, in bands’ original order, with the unmatched-in-x row ("Queen") appended at the end – minijoin always restores x’s original row order for matched rows, rather than y’s:
mjoin_right_join(bands, albums, by ="band")
band founded album
1 Beatles 1960 Abbey Road
2 Kinks 1963 Arthur
3 Queen NA A Night at the Opera
Full join: all rows of both
Both "Rolling Stones" and "Queen" are kept, each with an NA in the column they don’t have a match for – a full join never drops a row from either side, so it’s the join to reach for when you want to see everything and let missing matches show up as NA rather than disappearing silently:
mjoin_full_join(bands, albums, by ="band")
band founded album
1 Beatles 1960 Abbey Road
2 Rolling Stones 1962 <NA>
3 Kinks 1963 Arthur
4 Queen NA A Night at the Opera
by is always explicit
Unlike dplyr, there’s no auto-detection of shared column names – by must always be supplied. This is a deliberate simplification, not an oversight, to avoid a class of silent bugs from an unintended shared column name (two data frames that happen to both have an id column with unrelated meanings, for instance).
When the join column has different names on each side, by takes a named character vector instead of a single string, c("x_name" = "y_name") – mirroring dplyr’s own syntax for the same situation: