'Combine (rbind) data frames and create column with name of original data frames

I have several data frames that I want to combine by row. In the resulting single data frame, I want to create a new variable identifying which data set the observation came from.

# original data frames
df1 <- data.frame(x = c(1, 3), y = c(2, 4))
df2 <- data.frame(x = c(5, 7), y = c(6, 8))

# desired, combined data frame
df3  <- data.frame(x = c(1, 3, 5, 7), y = c(2, 4, 6, 8),
                   source = c("df1", "df1", "df2", "df2")
# x y source
# 1 2    df1
# 3 4    df1
# 5 6    df2
# 7 8    df2

How can I achieve this? Thanks in advance!

r


Solution 1:[1]

Another approach using dplyr:

df1 <- data.frame(x = c(1,3), y = c(2,4))
df2 <- data.frame(x = c(5,7), y = c(6,8))

df3 <- dplyr::bind_rows(list(df1=df1, df2=df2), .id = 'source')

df3
Source: local data frame [4 x 3]

  source     x     y
   (chr) (dbl) (dbl)
1    df1     1     2
2    df1     3     4
3    df2     5     6
4    df2     7     8

Solution 2:[2]

I'm not sure if such a function already exists, but this seems to do the trick:

bindAndSource <-  function(df1, df2) { 
  df1$source <- as.character(match.call())[[2]]
  df2$source <- as.character(match.call())[[3]]
  rbind(df1, df2)
}

results:

bindAndSource(df1, df2)

1 1 2    df1
2 3 4    df1
3 5 6    df2
4 7 8    df2


Caveat: This will not work in *aply-like calls

Solution 3:[3]

A blend of the other two answers:

df1 <- data.frame(x = 1:3,y = 1:3)
df2 <- data.frame(x = 4:6,y = 4:6)

> foo <- function(...){
    args <- list(...)
    result <- do.call(rbind,args)
    result$source <- rep(as.character(match.call()[-1]),times = sapply(args,nrow))
    result
 }

> foo(df1,df2,df1)
  x y source
1 1 1    df1
2 2 2    df1
3 3 3    df1
4 4 4    df2
5 5 5    df2
6 6 6    df2
7 1 1    df1
8 2 2    df1
9 3 3    df1

If you want to avoid the match.call business, you can always limit yourself to naming the function arguments (i.e. df1 = df1, df2 = df2) and using names(args) to access the names.

Solution 4:[4]

Another workaround for this one is using ldply in the plyr package...

df1 <- data.frame(x = c(1,3), y = c(2,4))
df2 <- data.frame(x = c(5,7), y = c(6,8))
list = list(df1 = df1, df2 = df2)
df3 <- ldply(list)

df3
  .id x y
  df1 1 2
  df1 3 4
  df2 5 6
  df2 7 8

Solution 5:[5]

Even though there are already some great answers here, I just wanted to add the one I have been using. It is base R so it might be be less limiting if you want to use it in a package, and it is a little faster than some of the other base R solutions.

dfs <- list(df1 = data.frame("x"=c(1,2), "y"=2),
            df2 = data.frame("x"=c(2,4), "y"=4),
            df3 = data.frame("x"=2, "y"=c(4,5,7)))

> microbenchmark(cbind(do.call(rbind,dfs), 
                       rep(names(dfs), vapply(dfs, nrow, numeric(1)))), times = 1001)
Unit: microseconds
     min      lq     mean  median      uq      max neval
 393.541 409.083 454.9913 433.422 453.657 6157.649  1001

The first part, do.call(rbind, dfs) binds the rows of data frames into a single data frame. The vapply(dfs, nrow, numeric(1)) finds how many rows each data frame has which is passed to rep in rep(names(dfs), vapply(dfs, nrow, numeric(1))) to repeat the name of the data frame once for each row of the data frame. cbind puts them all together.

This is similar to a previously posted solution, but about 2x faster.

> microbenchmark(do.call(rbind, 
                         lapply(names(dfs), function(x) cbind(dfs[[x]], source = x))), 
                 times = 1001)
Unit: microseconds
      min      lq     mean  median       uq      max neval
  844.558 870.071 1034.182 896.464 1210.533 8867.858  1001

I am not 100% certain, but I believe the speed up is due to making a single call to cbind rather than one per data frame.

Solution 6:[6]

Here is one option using Map. First, I create a named list of dataframes. Then, I can cbind the names to each dataframe. Then, use unname to remove the row names. Finally, rbind all the dataframes together.

# original data frames
df1 <- data.frame(x = c(1, 3), y = c(2, 4))
df2 <- data.frame(x = c(5, 7), y = c(6, 8))

df.list <- Hmisc::llist(df1, df2)

do.call(rbind, unname(Map(cbind, source = names(df.list), df.list)))

Output

  source x y
1    df1 1 2
2    df1 3 4
3    df2 5 6
4    df2 7 8

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Ricardo Saporta
Solution 3
Solution 4 maloneypatr
Solution 5 Barker
Solution 6 AndrewGB