使用 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_by
比split
更受欢迎?因为它是哈德利写的? 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】:
比较基础解决方案,plyr
和 dplyr
解决方案,基础解决方案似乎仍然快得多!
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
【讨论】:
您错误地使用了split
。 split
只接受一个参数作为因式分解。而不是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
【讨论】:
如何使用plyr
与dplyr
保持一致?
非常感谢。这正是我想要的结果,并且完成得很快。我不会将其标记为正确答案,因为我仍然对如何将 group_by 的结果导出为数据框列表感兴趣,但是谢谢 - 你解决了我的问题,我学到了一些东西!有趣的是,从一个 380Mb 的数据集中,结果声称是一个 340Gb 的列表!我希望我能保存它,看起来很奇怪 - 但它完成得非常快,大约 5 分钟。
遇到了同样的结构化问题,无法通过my.data %>% group_by(colA) %>% do( . , function.that.returns.list)
解决,因为dplyr
期望返回data.frame
中的结果。使用您的方法效果很好results <- dlply(my.data, "colA", function.that.returns.list)
为什么没有类似split_by
类似gorup_by
【参考方案4】:
您可以使用do
从group_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 %>% group_by(V1) %>% do(data = (.)) %>% select(data) %>% map(identity)
使用dplyr 0.5.0.9000
或更高版本,可以进一步简化@cboettig 的解决方案:df %>% group_by(V1) %>% do(data=(.)) %>% 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():返回数据帧列表的主要内容,如果未能解决你的问题,请参考以下文章
R语言dplyr包使用group_by函数和summarise函数构建频率表实战
在 R 中使用 dplyr 在 group_by 之后应用自定义函数