Turn dataframe into list of lists rowwise?

Viewed 201

Suppose I have a dataframe given by:

df <- tibble(x=c(1,2,3),
             y=c(4,5,6),
             z=c(7,8,9))
# A tibble: 3 x 3
      x     y     z
  <dbl> <dbl> <dbl>
1     1     4     7
2     2     5     8
3     3     6     9

How could I turn the data frame into a list of lists, where each list encodes the information about each row like this:

list(list(x=1, y=4, z=7),
     list(x=2, y=5, z=8),
     list(x=3, y=6, z=9))

NOTE: I benchmarked all the methods presented so far in my answer below.

5 Answers

We could just use transpose

purrr::transpose(df)

-output

[[1]]
[[1]]$x
[1] 1

[[1]]$y
[1] 4

[[1]]$z
[1] 7


[[2]]
[[2]]$x
[1] 2

[[2]]$y
[1] 5

[[2]]$z
[1] 8


[[3]]
[[3]]$x
[1] 3

[[3]]$y
[1] 6

[[3]]$z
[1] 9

Apply as.list to every row in df using apply -

apply(df, 1, as.list)

#[[1]]
#[[1]]$x
#[1] 1

#[[1]]$y
#[1] 4

#[[1]]$z
#[1] 7


#[[2]]
#[[2]]$x
#[1] 2

#[[2]]$y
#[1] 5

#[[2]]$z
#[1] 8


#[[3]]
#[[3]]$x
#[1] 3

#[[3]]$y
#[1] 6

#[[3]]$z
#[1] 9

EDIT: Updated to include the new answers.


I thought it would be helpful to provide a performance benchmark for the answers here (see below).

The method using purrr::transpose() looks to be the clear winner since it is both the fastest and it doesn't convert types.

The two methods apply_method and Map_method convert types silently, which can be problematic, so I think the other approaches should be preferred.

Benchmarking Code

library(microbenchmark)
library(tidyverse)

# Create data for benchmarking
df <- tibble(x=rep("a", 1e4),
             y=rnorm(1e4),
             z=rnorm(1e4))

apply_method <- function(df){
  apply(df, 1, as.list)
}

rowwise_method <- function(df){
  df %>%
    rowwise() %>%
    group_split() %>%
    map(~as.list(.x))
}

transpose_method <- function(df){
  purrr::transpose(df)
}

Map_method <- function(df){
  Map(as.list, data.frame(t(df)))
}

asplit_method <- function(df){
  asplit(as_tibble(Map(as.list, df)), 1)
}

m <- microbenchmark(apply_method(df),
                    rowwise_method(df),
                    transpose_method(df),
                    Map_method(df),
                    asplit_method(df))

Results

Unit: milliseconds
                 expr        min         lq      mean     median         uq       max neval   cld
     apply_method(df)  40.241112  48.098472  58.87600  55.576808  65.852798 164.43654   100   c  
   rowwise_method(df) 209.235743 248.659766 279.77535 279.719901 310.229050 366.01956   100     e
 transpose_method(df)   1.385084   1.962162   3.95705   3.013885   4.158427  41.28719   100 a    
       Map_method(df)  99.748096 124.712257 147.30464 138.841363 161.717376 283.76977   100    d 
    asplit_method(df)  22.444132  28.523968  38.90494  36.227208  44.920088 142.11396   100  b   

Example code to show silent type conversion.

> apply_method(df)[1]
[[1]]
[[1]]$x
[1] "a"

[[1]]$y
[1] " 3.470658e-01"

[[1]]$z
[1] "-0.1900941676"


> rowwise_method(df)[1]
[[1]]
[[1]]$x
[1] "a"

[[1]]$y
[1] 0.3470658

[[1]]$z
[1] -0.1900942


> transpose_method(df)[1]
[[1]]
[[1]]$x
[1] "a"

[[1]]$y
[1] 0.3470658

[[1]]$z
[1] -0.1900942


> Map_method(df)[1]
$X1
$X1[[1]]
[1] "a"

$X1[[2]]
[1] " 3.470658e-01"

$X1[[3]]
[1] "-0.1900941676"

> asplit_method(df)[1]
[[1]]
[[1]]$x
[1] "a"

[[1]]$y
[1] -0.188605

[[1]]$z
[1] 0.5599404

Another base R option

> Map(as.list, data.frame(t(df)))
$X1
$X1[[1]]
[1] 1

$X1[[2]]
[1] 4

$X1[[3]]
[1] 7


$X2
$X2[[1]]
[1] 2

$X2[[2]]
[1] 5

$X2[[3]]
[1] 8


$X3
$X3[[1]]
[1] 3

$X3[[2]]
[1] 6

$X3[[3]]
[1] 9

or

> asplit(as_tibble(Map(as.list, df)), 1)
[[1]]
[[1]]$x
[1] 1

[[1]]$y
[1] 4

[[1]]$z
[1] 7


[[2]]
[[2]]$x
[1] 2

[[2]]$y
[1] 5

[[2]]$z
[1] 8


[[3]]
[[3]]$x
[1] 3

[[3]]$y
[1] 6

[[3]]$z
[1] 9

You can do it with dplyr:

library(dplyr)
library(purrr)

df %>% rowwise() %>% group_split() %>% map(as.list)
Related