dplyr::bind_cols
bind_cols(..., .name_repair = "unique") data.frame · Updated April 18, 2026 · Tidyverse bind_cols() adds columns from multiple data frames side by side. It matches rows by their position in each data frame, not by any key column. This makes it fast and simple for data that is already aligned, but dangerous for data that might be in different orders.
Where possible, prefer a join by key over bind_cols(). Joins match rows explicitly, so you catch misalignments instead of silently producing garbage.
Syntax
bind_cols(..., .name_repair = "unique")
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
... | data frames | required | Data frames to combine. Each can be a data frame, a list of data frames, or a list that could be a data frame. Shorter inputs are recycled to match the longest. |
.name_repair | string | "unique" | How to handle duplicate column names. One of "unique" (make unique), "universal" (R-compatible unique), "check_unique" (error on conflict), or "minimal" (no repair). |
Basic Usage
Join two data frames column-wise:
library(dplyr)
df1 <- tibble(name = c("Alice", "Bob", "Charlie"))
df2 <- tibble(score = c(85, 92, 78), grade = c("B", "A", "C"))
bind_cols(df1, df2)
# # A tibble: 3 × 3
# name score grade
# <chr> <dbl> <chr>
# 1 Alice 85 B
# 2 Bob 92 A
# 3 Charlie 78 C
Rows are matched purely by position. The first row of df1 combines with the first row of df2, and so on.
The Positional Matching Gotcha
This is the most important thing to understand about bind_cols(). It does not check that your rows align by any key. If df1 and df2 are in different orders, you’ll get wrong results with no warning:
df1 <- tibble(name = c("Alice", "Bob", "Charlie"))
df2 <- tibble(score = c(92, 85, 78)) # Bob and Alice swapped!
bind_cols(df1, df2)
# Alice gets Bob's score. No error. No warning.
Always sort your data before binding, or use a join instead:
# Safe: join by name instead
df1 <- tibble(name = c("Alice", "Bob"), score = c(85, 92))
df2 <- tibble(name = c("Bob", "Alice"), grade = c("A", "B"))
df1 |> left_join(df2, by = "name")
Supplying Multiple Data Frames
You can bind any number of data frames in one call:
df1 <- tibble(x = 1:3)
df2 <- tibble(y = 4:6)
df3 <- tibble(z = 7:9)
bind_cols(df1, df2, df3)
# # A tibble: 3 × 3
# x y z
# <int> <int> <int>
# 1 1 4 7
# 2 2 5 8
# 3 3 6 9
Combining a List of Data Frames
Pass a list of data frames with !!! (splice) or directly:
df_list <- list(
tibble(a = 1:3),
tibble(b = 4:6),
tibble(c = 7:9)
)
bind_cols(!!!df_list)
# # A tibble: 3 × 3
# a b c
# <int> <int> <int>
# 1 1 4 7
# 2 2 5 8
# 3 3 6 9
Row Size Mismatch
If the data frames have incompatible row counts, bind_cols() throws an error:
df1 <- tibble(x = 1:3)
df2 <- tibble(y = 1:2)
bind_cols(df1, df2)
# Error: Can't recycle `..1` (size 3) to match `..2` (size 2).
Recycling handles simple cases where one data frame is shorter:
df1 <- tibble(x = 1:3)
df2 <- tibble(y = c(1, 2)) # recycled to c(1, 2, 1)
bind_cols(df1, df2)
# # A tibble: 3 × 2
# x y
# <int> <dbl>
# 1 1 1
# 2 2 2
# 3 3 1
This recycling is convenient but risky. A length-1 vector is safe for constants; otherwise, watch out.
Column Name Repair
The .name_repair parameter controls what happens when column names collide:
df1 <- tibble(x = 1:3)
df2 <- tibble(x = 4:6)
bind_cols(df1, df2, .name_repair = "unique")
# # A tibble: 3 × 2
# x x...1 x...2
# <int> <int> <int>
bind_cols(df1, df2, .name_repair = "check_unique")
# Error: Names must be unique.
Common Patterns
Adding metadata columns
df <- tibble(name = c("Alice", "Bob"), score = c(85, 92))
bind_cols(df, tibble(
date = Sys.Date(),
source = "exam"
))
# # A tibble: 2 × 4
# name score date source
# <chr> <dbl> <date> <chr>
# 1 Alice 85 2026-04-18 exam
# 2 Bob 92 2026-04-18 exam
Building a wide dataset incrementally
part1 <- tibble(id = 1:3, x = c("a", "b", "c"))
part2 <- tibble(y = c(1, 2, 3))
part3 <- tibble(z = c(TRUE, FALSE, TRUE))
bind_cols(part1, part2, part3)
See Also
- /reference/tidyverse/dplyr-bind/ — combines both
bind_rows()andbind_cols() - /reference/tidyverse/dplyr-join/ — safer alternative: join by key instead of by position
- /reference/tidyverse/dplyr-mutate/ — add computed columns within a pipeline