使用 dplyr group_by 模拟 split():返回数据帧列表

Posted

技术标签:

【中文标题】使用 dplyr group_by 模拟 split():返回数据帧列表【英文标题】:Emulate split() with dplyr group_by: return a list of data frames 【发布时间】:2016-02-19 21:42:07 【问题描述】:

我有一个大型数据集,在 R 中阻塞了 split()。我可以使用 dplyr group_by(无论如何这是一种首选方式),但我无法将生成的 grouped_df 持久化为数据框列表,我的连续处理步骤所需的格式(我需要强制转换为 SpatialDataFrames 和类似的格式)。

考虑一个示例数据集:

df = as.data.frame(cbind(c("a","a","b","b","c"),c(1,2,3,4,5), c(2,3,4,2,2)))
listDf = split(df,df$V1)

返回

$a
   V1 V2 V3
 1  a  1  2
 2  a  2  3

$b
   V1 V2 V3
 3  b  3  4
 4  b  4  2

$c
   V1 V2 V3
 5  c  5  2

我想用group_by(类似于group_by(df,V1))来模拟它,但这会返回一个grouped_df。我知道do 应该能够帮助我,但我不确定用法(另请参阅link 进行讨论。)

请注意,split 使用已用于建立此组的因素的名称来命名每个列表 - 这是一个理想的功能(最终,对于从 dfs 列表中提取这些名称的方法,这是一种额外的荣誉)。

【问题讨论】:

为什么group_bysplit 更受欢迎?因为它是哈德利写的? group_by 有它的位置,它不是为了将数据集拆分为不同的数据帧而设计的,而 split 旨在实现这一点。 不,不是因为它是由 hadley 编写的,而是因为它完成了——而且速度很快。我有一个 df 为 400mb 的数据集,拆分会导致怪物(不知道为什么它会扩大大小),并在保存时使 R 崩溃。这是一个训练数据集,真正的数据集是 8.5GB 数据集(1GB 作为 RData)。组工作,分裂失败。我尝试了 bigsplit,但也没能成功。仍然,回到问题 - 如何使用 group_by (和 dplyr)做到这一点? 再次重申,group_by 并非旨在将数据集拆分为单独的数据集。 do 可能会比 split 慢得多。 split 是完全矢量化和编译的函数,我不明白为什么它会比任何其他替代方案慢。 所以我假设你有一些函数说f(),你想应用于你的data.frames列表中的每个data.frame(由split生成)。如果是这种情况,替代的 dplyr-route(不拆分)将类似于 df %>% group_by(V1) %>% do(f(.)) 假设 f() 返回一个 data.frame。否则你可能需要df %>% group_by(V1) %>% do(data.frame(f(.))) 之类的东西。如果您真的想创建一个列表,请坚持使用 split,正如 David 所评论的那样。 你是对的。我需要应用一个生成完全不同对象的函数 - SpatialDataFrame。因此,我假设在工作流程的这个阶段,我必须“退出” dplyr 工作流程。因此,我想要一个 dfs 列表,稍后我可以遍历并做我需要的任何事情。我尝试了一个简单的 hack %>% do(as.data.frame(.)) 但这不起作用(而且我不知道如何让每个组附加到一个大的 list()。欢迎提示。我尝试了类似:xx<- group_by(df,V1) %>% do(data.frame(function(x) coordinates(x)=(~V2+V3))) 其中coordinates 来自@ 987654350@ 【参考方案1】:

dplyr 中的 group_split:

Dplyr 实现了group_split: https://dplyr.tidyverse.org/reference/group_split.html

它按组拆分数据帧,返回数据帧列表。这些数据帧中的每一个都是由拆分变量的类别定义的原始数据帧的子集。

例如。通过变量Species拆分数据集iris,并计算每个子数据集的汇总:

> iris %>% 
+     group_split(Species) %>% 
+     map(summary)
[[1]]
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.300   Min.   :2.300   Min.   :1.000   Min.   :0.100   setosa    :50  
 1st Qu.:4.800   1st Qu.:3.200   1st Qu.:1.400   1st Qu.:0.200   versicolor: 0  
 Median :5.000   Median :3.400   Median :1.500   Median :0.200   virginica : 0  
 Mean   :5.006   Mean   :3.428   Mean   :1.462   Mean   :0.246                  
 3rd Qu.:5.200   3rd Qu.:3.675   3rd Qu.:1.575   3rd Qu.:0.300                  
 Max.   :5.800   Max.   :4.400   Max.   :1.900   Max.   :0.600                  

[[2]]
  Sepal.Length    Sepal.Width     Petal.Length   Petal.Width          Species  
 Min.   :4.900   Min.   :2.000   Min.   :3.00   Min.   :1.000   setosa    : 0  
 1st Qu.:5.600   1st Qu.:2.525   1st Qu.:4.00   1st Qu.:1.200   versicolor:50  
 Median :5.900   Median :2.800   Median :4.35   Median :1.300   virginica : 0  
 Mean   :5.936   Mean   :2.770   Mean   :4.26   Mean   :1.326                  
 3rd Qu.:6.300   3rd Qu.:3.000   3rd Qu.:4.60   3rd Qu.:1.500                  
 Max.   :7.000   Max.   :3.400   Max.   :5.10   Max.   :1.800                  

[[3]]
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.900   Min.   :2.200   Min.   :4.500   Min.   :1.400   setosa    : 0  
 1st Qu.:6.225   1st Qu.:2.800   1st Qu.:5.100   1st Qu.:1.800   versicolor: 0  
 Median :6.500   Median :3.000   Median :5.550   Median :2.000   virginica :50  
 Mean   :6.588   Mean   :2.974   Mean   :5.552   Mean   :2.026                  
 3rd Qu.:6.900   3rd Qu.:3.175   3rd Qu.:5.875   3rd Qu.:2.300                  
 Max.   :7.900   Max.   :3.800   Max.   :6.900   Max.   :2.500     

它对于调试嵌套数据帧的计算也非常有帮助,因为它是一种快速“查看”嵌套数据帧计算“内部”发生情况的方法。

【讨论】:

到目前为止,这对我来说是最容易实现的答案! 另外:如果您想为列表命名。从这里使用函数named_group_split() github.com/tidyverse/dplyr/issues/4223【参考方案2】:

比较基础解决方案,plyrdplyr 解决方案,基础解决方案似乎仍然快得多!

library(plyr)
library(dplyr)   

df <- data_frame(Group1=rep(LETTERS, each=1000),
             Group2=rep(rep(1:10, each=100),26), 
             Value=rnorm(26*1000))

microbenchmark(Base=df %>%
             split(list(.$Group2, .$Group1)),
           dplyr=df %>% 
             group_by(Group1, Group2) %>% 
             do(data = (.)) %>% 
             select(data) %>% 
             lapply(function(x) (x)) %>% .[[1]],
           plyr=dlply(df, c("Group1", "Group2"), as.tbl),
           times=50) 

给予:

Unit: milliseconds
  expr      min        lq      mean    median        uq       max neval
  Base 12.82725  13.38087  16.21106  14.58810  17.14028  41.67266    50
  dplyr 25.59038 26.66425  29.40503  27.37226  28.85828  77.16062   50
  plyr 99.52911  102.76313 110.18234 106.82786 112.69298 140.97568    50

【讨论】:

您错误地使用了splitsplit 只接受一个参数作为因式分解。而不是split(.$Group2, .$Group1)split(list(.$Group2, .$Group1))。顺便说一句,这会使我的机器上 Base 的结果慢 20 倍。 感谢您指出这一点!我纠正了它,确实,base 变慢了,但仍然比其他速度快。【参考方案3】:

要“坚持”到 dplyr,您还可以使用 plyr 代替 split

library(plyr)

dlply(df, "V1", identity)
#$a
#  V1 V2 V3
#1  a  1  2
#2  a  2  3

#$b
#  V1 V2 V3
#1  b  3  4
#2  b  4  2

#$c
#  V1 V2 V3
#1  c  5  2

【讨论】:

如何使用plyrdplyr 保持一致? 非常感谢。这正是我想要的结果,并且完成得很快。我不会将其标记为正确答案,因为我仍然对如何将 group_by 的结果导出为数据框列表感兴趣,但是谢谢 - 你解决了我的问题,我学到了一些东西!有趣的是,从一个 380Mb 的数据集中,结果声称是一个 340Gb 的列表!我希望我能保存它,看起来很奇怪 - 但它完成得非常快,大约 5 分钟。 遇到了同样的结构化问题,无法通过my.data %&gt;% group_by(colA) %&gt;% do( . , function.that.returns.list) 解决,因为dplyr 期望返回data.frame 中的结果。使用您的方法效果很好results &lt;- dlply(my.data, "colA", function.that.returns.list) 为什么没有类似split_by 类似gorup_by【参考方案4】:

您可以使用dogroup_by 获取数据帧列表,只要您命名将存储数据帧的新列,然后将该列通过管道传输到lapply

listDf = df %>% group_by(V1) %>% do(vals=data.frame(.)) %>% select(vals) %>% lapply(function(x) (x))
listDf[[1]]
#[[1]]
#  V1 V2 V3
#1  a  1  2
#2  a  2  3

#[[2]]
#  V1 V2 V3
#1  b  3  4
#2  b  4  2

#[[3]]
#  V1 V2 V3
#1  c  5  2

【讨论】:

使用最新版本的tidyr (0.4.1),您可以将do(vals=data.frame(.)) 替换为nest()vals 默认命名为data 请注意,使用nest() 代替do 并不是完全一样的;结果表只有 V2 和 V3 列;分组变量丢失。 一个相同但稍短的版本是:df %&gt;% group_by(V1) %&gt;% do(data = (.)) %&gt;% select(data) %&gt;% map(identity) 使用dplyr 0.5.0.9000 或更高版本,可以进一步简化@cboettig 的解决方案:df %&gt;% group_by(V1) %&gt;% do(data=(.)) %&gt;% pull(data)【参考方案5】:

由于 dplyr 0.8 你可以使用group_split

library(dplyr)
df = as.data.frame(cbind(c("a","a","b","b","c"),c(1,2,3,4,5), c(2,3,4,2,2)))
df %>% group_by(V1) %>% group_split()
#> [[1]]
#> # A tibble: 2 x 3
#>   V1    V2    V3   
#>   <fct> <fct> <fct>
#> 1 a     1     2    
#> 2 a     2     3    
#> 
#> [[2]]
#> # A tibble: 2 x 3
#>   V1    V2    V3   
#>   <fct> <fct> <fct>
#> 1 b     3     4    
#> 2 b     4     2    
#> 
#> [[3]]
#> # A tibble: 1 x 3
#>   V1    V2    V3   
#>   <fct> <fct> <fct>
#> 1 c     5     2

【讨论】:

【参考方案6】:

由于dplyr 0.5.0.9000,使用group_by() 的最短解决方案可能是在do 后面加上pull

df %>% group_by(V1) %>% do(data=(.)) %>% pull(data)

请注意,与split 不同,这不会命名结果列表元素。如果这是需要的,那么您可能会想要类似的东西

df %>% group_by(V1) %>% do(data = (.)) %>% with( set_names(data, V1) )

稍微编辑一下,我同意人们所说的split() 是更好的选择。就个人而言,我总是觉得我必须输入两次数据框的名称(例如,split( potentiallylongname, potentiallylongname$V1 ))很烦人,但这个问题很容易通过管道回避:

df %>% split( .$V1 )

【讨论】:

以上是关于使用 dplyr group_by 模拟 split():返回数据帧列表的主要内容,如果未能解决你的问题,请参考以下文章

了解 dplyr 和 group_by

使用 group_by(多个变量)时的 dplyr 问题

R语言dplyr包使用group_by函数和summarise函数构建频率表实战

在 R 中使用 dplyr 在 group_by 之后应用自定义函数

使用 dplyr、group_by 和折叠或汇总连接字符串/行,但保持 NA 值 [重复]

在 dplyr 中使用 group_by 时在 R 中格式化数字 [重复]