将DataFrame按行转换为列表的列表?

4

假设我有一个给定的数据框:

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

我如何将数据框转换为列表的列表,其中每个列表都编码了每行的信息,如下所示:
list(list(x=1, y=4, z=7),
     list(x=2, y=5, z=8),
     list(x=3, y=6, z=9))

注意:我在下面的回答中对所有介绍过的方法进行了基准测试。

5个回答

4

我们可以直接使用transpose函数

purrr::transpose(df)

-输出

[[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

1
我认为这个方法是最好的 - 请查看我的更新答案,比较所有描述的方法。 - max

3

使用applyas.list应用于df中的每一行 -

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

3

编辑:已更新以包括新的答案。


我认为为这里的答案提供一个性能基准会很有帮助(见下文)。

使用 purrr::transpose() 方法似乎是明显的赢家,因为它既最快,又不会转换类型。

apply_methodMap_method 这两种方法会默默地转换类型,这可能会产生问题,因此我认为应优先考虑其他方法。

基准测试代码

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))

结果

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   

演示静默类型转换的示例代码。

> 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

1
这是可以预料的。dplyr方法由于在行分组时的逐行循环%>%分割%>%循环而不够高效,但它保持了类型并且安全地适用于任何数据框。apply会强制转换为矩阵并进行单次逐行调用。 你在基准测试方面做得很好。 - GuedesBF
1
很棒的基准测试!我用一种新方法更新了我的答案。你可以将它加入到你的基准测试中,看看它的性能如何。 - ThomasIsCoding
谢谢!我刚刚添加了你的新方法——它很快,而且不会转换类型,但是purrr::transpose仍然更快。 - max

1
你可以使用dplyr来完成它:
library(dplyr)
library(purrr)

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

我认为你忘了加上 .x,正确的写法应该是 df %>% rowwise() %>% group_split() %>% map(~as.list(.x)) - max
也可以是 ... %>% map(as.list) - Phil

1

另一个基本的R选项

> 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

或者

> 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

使用t()的好技巧。 - GuedesBF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接