27

Using the tidyverse a lot i often face the challenge of turning named vectors into a data.frame/tibble with the columns being the names of the vector.
What is the prefered/tidyversey way of doing this?
EDIT: This is related to: this and this github-issue

So i want:

require(tidyverse)
vec <- c("a" = 1, "b" = 2)

to become this:

# A tibble: 1 × 2
      a     b
  <dbl> <dbl>
1     1     2

I can do this via e.g.:

vec %>% enframe %>% spread(name, value)
vec %>% t %>% as_tibble

Usecase example:

require(tidyverse)
require(rvest)
txt <- c('<node a="1" b="2"></node>',
         '<node a="1" c="3"></node>')

txt %>% map(read_xml) %>% map(xml_attrs) %>% map_df(~t(.) %>% as_tibble)

Which gives

# A tibble: 2 × 3
      a     b     c
  <chr> <chr> <chr>
1     1     2  <NA>
2     1  <NA>     3
Rentrop
  • 20,979
  • 10
  • 72
  • 100
  • what exactly, if anything, do you think is lacking about what you are doing(?) – shayaa Oct 14 '16 at 06:30
  • 2
    I asked myself the same, because `bind_rows` does not work instead of `map_df(~t(.) %>% as_tibble)`. So, until now, I transpose, convert to a data frame with strings staying characters (not factors), and then bind the results together. However, a shortcut for this common task could be nice. – lukeA Oct 14 '16 at 08:07
  • 1
    @lukeA, I think `bind_rows` has been updated to now work in the way you want – markdly Sep 18 '17 at 23:25

5 Answers5

30

This is now directly supported using bind_rows (introduced in dplyr 0.7.0):

library(tidyverse)) 
vec <- c("a" = 1, "b" = 2)

bind_rows(vec)
#> # A tibble: 1 x 2
#>       a     b
#>   <dbl> <dbl>
#> 1     1     2

This quote from https://cran.r-project.org/web/packages/dplyr/news.html explains the change:

bind_rows() and bind_cols() now accept vectors. They are treated as rows by the former and columns by the latter. Rows require inner names like c(col1 = 1, col2 = 2), while columns require outer names: col1 = c(1, 2). Lists are still treated as data frames but can be spliced explicitly with !!!, e.g. bind_rows(!!! x) (#1676).

With this change, it means that the following line in the use case example:

txt %>% map(read_xml) %>% map(xml_attrs) %>% map_df(~t(.) %>% as_tibble)

can be rewritten as

txt %>% map(read_xml) %>% map(xml_attrs) %>% map_df(bind_rows)

which is also equivalent to

txt %>% map(read_xml) %>% map(xml_attrs) %>% { bind_rows(!!! .) }

The equivalence of the different approaches is demonstrated in the following example:

library(tidyverse)
library(rvest)

txt <- c('<node a="1" b="2"></node>',
         '<node a="1" c="3"></node>')

temp <- txt %>% map(read_xml) %>% map(xml_attrs)

# x, y, and z are identical
x <- temp %>% map_df(~t(.) %>% as_tibble)
y <- temp %>% map_df(bind_rows)
z <- bind_rows(!!! temp)

identical(x, y)
#> [1] TRUE
identical(y, z)
#> [1] TRUE

z
#> # A tibble: 2 x 3
#>       a     b     c
#>   <chr> <chr> <chr>
#> 1     1     2  <NA>
#> 2     1  <NA>     3
markdly
  • 4,394
  • 2
  • 19
  • 27
  • I am a bit confused by the distinction between setting *inner names* and *outer names* that you quote. See my question here: https://stackoverflow.com/q/67010860/5535152 – Emy Apr 09 '21 at 12:28
7

The idiomatic way would be to splice the vector with !!! within a tibble() call so the named vector elements become column definitions :

library(tibble)
vec <- c("a" = 1, "b" = 2)
tibble(!!!vec)
#> # A tibble: 1 x 2
#>       a     b
#>   <dbl> <dbl>
#> 1     1     2

Created on 2019-09-14 by the reprex package (v0.3.0)

moodymudskipper
  • 46,417
  • 11
  • 121
  • 167
1

This works for me: c("a" = 1, "b" = 2) %>% t() %>% tbl_df()

Gabi
  • 1,303
  • 16
  • 20
1

Interestingly you can use the as_tibble() method for lists to do this in one call. Note that this isn't best practice since this isn't an exported method.

tibble:::as_tibble.list(vec)
Davis Vaughan
  • 2,780
  • 9
  • 19
1
as_tibble(as.list(c(a=1, b=2)))
Sergey Skripko
  • 336
  • 1
  • 8