1

I like the bind_rows function in dplyr but I find it annoying that when passing the .id argument it can only add a numeric index in the new column.

I'm trying write a bind_rows_named function but am getting stuck accessing the object names. This works as expected:

bind_name_to_df <- function(df){
  dfname <- deparse(substitute(df))
  df %>% mutate(label=dfname)
}

a <- data_frame(stuff=1:10)
bind_name_to_df(a)

But I can't work out how to apply this to a list of data frames, e.g. using .dots. I want this to work, but I know I have the semantics for the ... wrong somehow. Can anyone shed light?

b <- data_frame(stuff=1:10)

bind_rows_named <- function(...){
  return(
    bind_rows(lapply(..., bind_name_to_df)))
}

bind_rows_named(a, b)
bjw
  • 2,046
  • 18
  • 33
  • 2
    In other contexts, I have used the convenience function `Hmisc::llist` which "is like `list` except that it preserves the names or labels of the component variables in the variables `label` attribute." Thus, here it would be `bind_rows(Hmisc::llist(a, b), .id = "label")` – Henrik Aug 30 '17 at 13:09
  • 1
    Just found some other nice `base` alternatives here: [Can lists be created that name themselves based on input object names?](https://stackoverflow.com/questions/16951080/can-lists-be-created-that-name-themselves-based-on-input-object-names) – Henrik Aug 30 '17 at 13:20
  • 2
    The labels from `.id` are only integers when you don't give named arguments to `bind_rows`. See `bind_rows(a = a, .id = "label")`. It works well when you have a named list, which does make `Hmisc::llist` convenient for making one. – aosmith Aug 30 '17 at 14:40

1 Answers1

1

Here is an option using base R

bind_named <- function(...){
  v1 <- sapply(match.call()[-1], deparse)  
   dfs <- list(...)
   Map(cbind, dfs, label = v1)

  }

bind_named(a, b)
#[1]]
#   stuff label
#1      1     a
#2      2     a
#3      3     a
#4      4     a
#5      5     a
#6      6     a
#7      7     a
#8      8     a
#9      9     a
#10    10     a

#[[2]]
#   stuff label
#1      1     b
#2      2     b
#3      3     b
#4      4     b
#5      5     b
#6      6     b
#7      7     b
#8      8     b
#9      9     b
#10    10     b

Or using tidyverse

library(tidyverse)
bind_named <- function(...) {
 nm1 <- quos(...) %>%
             map(quo_name)
 dfs <- list(...)
 dfs %>%
   map2(nm1, ~mutate(., label = .y)) 
  }

res <- bind_named(a, b)
res %>%
     map(head, 2)
#[[1]]
#   stuff label
#1     1     a
#2     2     a

#[[2]]
#   stuff label
#1     1     b
#2     2     b

It can be also be made into a single chain

bind_named <- function(...) {
   quos(...) %>%
     map(quo_name) %>%
     map2_df(list(...), ., ~mutate(.data = .x, label = .y))
  }

bind_named(a, b)
# A tibble: 20 x 2
#   stuff label
#   <int> <chr>
# 1     1     a
# 2     2     a
# 3     3     a
# 4     4     a
# 5     5     a
# 6     6     a
# 7     7     a
# 8     8     a
# 9     9     a
#10    10     a
#11     1     b
#12     2     b
#13     3     b
#14     4     b
#15     5     b
#16     6     b
#17     7     b
#18     8     b
#19     9     b
#20    10     b

NOTE: Initially, we thought the OP wanted to create columns on separate datasets and get a list output. Upon clarification, the map2 is changed to map2_df which return a single dataset

akrun
  • 874,273
  • 37
  • 540
  • 662
  • 1
    @bjw Okay, I was confused with the `bind_rows` in your function. I thought you wanted to create a column. If you wanted a single dataset, change the `map2` to `map2_df`. (as in the last function changed) – akrun Aug 30 '17 at 12:24