rguides

dplyr::*_join()

left_join(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...)

The dplyr join functions combine two data frames by matching rows based on one or more key columns. Each join type produces different results depending on which rows you want to keep from each table.

Syntax

left_join(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...)
right_join(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...)
inner_join(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...)
full_join(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...)

Parameters

ParameterTypeDefaultDescription
xtibble / data.frame,The primary table (left table for left_join)
ytibble / data.frame,The table to join (right table for left_join)
bycharacter / NULLNULLColumn name(s) to join on. Use a character vector for multiple keys, or c("key1" = "key2") for different column names
copylogicalFALSEIf TRUE, copy y to the same source as x for database-like operations
suffixcharacterc(“.x”, “.y”)Suffix to add to overlapping column names
...arguments,Additional arguments passed to underlying methods

Examples

Basic usage

left_join() preserves every row from the first (left) table and pulls in matching columns from the second (right) table using the specified key. When a row in the left table has no match in the right table, the columns coming from the right side are filled with NA. The example below joins employee records to their department names:

library(dplyr)

# Create two sample data frames
employees <- data.frame(
  id = c(1, 2, 3, 4),
  name = c("Alice", "Bob", "Charlie", "Diana"),
  department_id = c(10, 20, 10, 30)
)

departments <- data.frame(
  department_id = c(10, 20, 30),
  department_name = c("Engineering", "Marketing", "Sales")
)

# Left join: keep all rows from employees
left_join(employees, departments, by = "department_id")
#   id    name department_id department_name
# 1  1    Alice             10     Engineering
# 2  2      Bob             20      Marketing
# 3  3  Charlie             10     Engineering
# 4  4    Diana             30          Sales

Right join

right_join() is the mirror of left_join() — it preserves every row from the right table and brings in matching data from the left. Rows in the right table with no match in the left get NA for the left-side columns. Use this when the right table is your reference and you want to see which left-side records attach to each reference entry:

# Right join: keep all rows from departments
right_join(employees, departments, by = "department_id")
#   id    name department_id department_name
# 1  1    Alice             10     Engineering
# 2  3  Charlie             10     Engineering
# 3  2      Bob             20      Marketing
# 4  4    Diana             30          Sales

Inner join

inner_join() keeps only the rows where the key value exists in both tables. Rows from either table that lack a match in the other are silently dropped. This is the safest join when you need complete data for every output row and want to exclude partial records automatically:

# Inner join: keep only matching rows
inner_join(employees, departments, by = "department_id")
#   id    name department_id department_name
# 1  1    Alice             10     Engineering
# 2  3  Charlie             10     Engineering
# 3  2      Bob             20      Marketing
# 4  4    Diana             30          Sales

Full join

full_join() keeps every row from both tables, matching where possible and filling unmatched positions with NA. This is the most inclusive join type, useful when you need a complete audit of which keys appear in either table and where the gaps are:

# Full join: keep all rows from both tables
full_join(employees, departments, by = "department_id")
#   id    name department_id department_name
# 1  1    Alice             10     Engineering
# 2  3  Charlie             10     Engineering
# 3  2      Bob             20      Marketing
# 4  4    Diana             30          Sales

Multiple key columns

When a single column is not enough to identify a row uniquely, you can join on multiple columns by passing a character vector to by. The join matches rows where all specified columns have identical values across both tables, effectively treating the combination as a composite key:

# Join on multiple columns
df1 <- data.frame(
  year = c(2023, 2023, 2024),
  quarter = c(1, 2, 1),
  sales = c(100, 150, 200)
)

df2 <- data.frame(
  year = c(2023, 2023, 2024),
  quarter = c(1, 2, 1),
  profit = c(20, 35, 45)
)

inner_join(df1, df2, by = c("year", "quarter"))
#   year quarter sales profit
# 1 2023       1   100     20
# 2 2023       2   150     35
# 3 2024       1   200     45

Different column names

When the key columns have different names in the two tables, use the by = c("left_col" = "right_col") syntax to define the mapping. This is common when joining across data sources that use inconsistent naming conventions — the left side of each pair names the column in x and the right side names the corresponding column in y:

# Join when column names differ
products <- data.frame(
  product_id = c(101, 102, 103),
  price = c(19.99, 29.99, 39.99)
)

orders <- data.frame(
  id = c(1, 2, 3),
  product_id = c(101, 102, 104),
  quantity = c(2, 1, 3)
)

left_join(orders, products, by = c("product_id" = "product_id"))
#   id product_id quantity  price
# 1  1        101        2  19.99
# 2  2        102        1  29.99
# 3  3        104        3     NA

Common patterns

Filter with joins: Use semi_join() to keep rows in x that have a match in y, or anti_join() to remove them. These filtering joins do not add columns from y; they only determine which rows of x to keep based on the existence of matching keys. semi_join() is the set-intersection of row keys, while anti_join() is the set-difference:

# Find employees in the Engineering department
dept_eng <- departments %>% filter(department_name == "Engineering")
semi_join(employees, dept_eng, by = "department_id")

# Find employees NOT in Sales
dept_sales <- departments %>% filter(department_name == "Sales")
anti_join(employees, dept_sales, by = "department_id")

Multiple joins in sequence: Chain multiple joins for complex data transformations. Each join resolution happens before the next one begins, so later joins can reference columns brought in by earlier ones. This pattern is the relational equivalent of building a wide denormalized table from several normalized source tables:

# Join multiple tables
result <- orders %>%
  left_join(customers, by = "customer_id") %>%
  left_join(products, by = "product_id") %>%
  left_join(shipping, by = "order_id")

dplyr join functions in practice

left_join(x, y, by = "key") keeps all rows from x and adds matching columns from y. Rows in x without a match in y get NA for y’s columns. inner_join() keeps only rows with matches in both tables. full_join() keeps all rows from both. right_join() mirrors left_join() but keeps all rows from y.

anti_join(x, y) returns rows from x with no match in y, it is the complement of semi_join(), which returns rows from x that do have a match. These filtering joins do not add new columns; they just select rows based on existence of matches.

For joins on columns with different names, use by = c("x_col" = "y_col"). For joins on multiple columns, pass a named character vector: by = c("id", "date") joins on both columns.

Watch for row multiplication from one-to-many relationships: left_join(x, y) when y has multiple rows per key produces multiple output rows per input row. After any join, check nrow() to verify the output size matches expectations. x |> left_join(y) |> nrow() == nrow(x) should hold for one-to-one relationships.

suffix = c(".x", ".y") controls how conflicting non-join column names are disambiguated in the output. By default they get .x and .y suffixes; customize to more meaningful names.

left_join() keeps all rows from x and matching rows from y, filling unmatched columns with NA. inner_join() keeps only rows with matches in both. full_join() keeps all rows from both, filling both sides. anti_join() keeps rows from x with no match in y — useful for finding records absent from a reference table. Specify the join key with by = "col" or by = c("x_col" = "y_col") when column names differ.

See also