我想在R中创建一个类似于 dplyr
的 group_by
函数的函数,当与 summarise
结合使用时,可以为组成员资格不相互排斥的数据集提供汇总统计信息 . 即,观察可以属于多个组 . 考虑它的一种方法可能是考虑标签;观察可能属于可能重叠的一个或多个标签 .
例如,采用R的 esoph
数据集(https://stat.ethz.ch/R-manual/R-devel/library/datasets/html/esoph.html)记录食管癌的病例对照研究 . 假设我将数据集转换为长格式(每行一个参与者),然后将这些标记(逻辑列)添加到数据集中:http:// 117196977_ tag ', where the tags are: 65+ years old; 80+ gm/day alcohol; 20+ gm/day tobacco; and a '
library('dplyr')
data(esoph)
esophlong = bind_rows(esoph %>% .[rep(seq_len(nrow(.)), .$ncases), 1:3] %>% mutate(case=1),
esoph %>% .[rep(seq_len(nrow(.)), .$ncontrols), 1:3] %>% mutate(case=0)
) %>%
mutate(highage=(agegp %in% c('65-74','75+')),
highalc=(alcgp %in% c('80-119','120+')),
hightob=(tobgp %in% c('20-29','30+')),
highrisk=(highage & highalc & hightob)
)
我通常的方法是创建一个数据集,其中每个观察对于它所属的每个标记都是重复的,然后 summarise
这个数据集:
esophdup = bind_rows(esophlong %>% filter(highage) %>% mutate(tag='age>=65'),
esophlong %>% filter(highalc) %>% mutate(tag='alc>=80'),
esophlong %>% filter(hightob) %>% mutate(tag='tob>=20'),
esophlong %>% filter(highrisk) %>% mutate(tag='high risk'),
esophlong %>% filter() %>% mutate(tag='all')
) %>%
mutate(tag=factor(tag, levels = unique(.$tag)))
summary = esophdup %>%
group_by(tag) %>%
summarise(n=n(), ncases=sum(case), case.rate=mean(case))
对于大型数据集或大量标签,这种方法效率低下,而且我经常会耗尽内存来存储它 .
另一种方法是单独使用 summarise
每个标记,然后将这些摘要数据集绑定,如下所示:
summary.age = esophlong %>%
filter(highage) %>%
summarise(n=n(), ncases=sum(case), case.rate=mean(case)) %>%
mutate(tag='age>=65')
summary.alc = esophlong %>%
filter(highalc) %>%
summarise(n=n(), ncases=sum(case), case.rate=mean(case)) %>%
mutate(tag='alc>=80')
summary.tob = esophlong %>%
filter(hightob) %>%
summarise(n=n(), ncases=sum(case), case.rate=mean(case)) %>%
mutate(tag='tob>=20')
summary.highrisk = esophlong %>%
filter(highrisk) %>%
summarise(n=n(), ncases=sum(case), case.rate=mean(case)) %>%
mutate(tag='high risk')
summary.all = esophlong %>%
summarise(n=n(), ncases=sum(case), case.rate=mean(case)) %>%
mutate(tag='all')
summary=bind_rows(summary.age,summary.alc,summary.tob,summary.highrisk,summary.all)
当我有大量标签或者我想在整个项目中经常重复使用标签以进行不同的汇总测量时,这种方法既费时又乏味 .
我想到的函数,例如 group_by_tags(data, key, ...)
,它包含一个指定分组列名称的参数,应该是这样的:
summary = esophlong %>%
group_by_tags(key='tags',
'age>=65'=highage,
'alc>=80'=highalc,
'tob>=20'=hightob,
'high risk'=highrisk,
'all ages'=1
) %>%
summarise(n=n(), ncases=sum(case), case.rate=mean(case))
使用如下所示的摘要数据集:
> summary
tags n ncases case.rate
1 age>=65 273 68 0.2490842
2 alc>=80 301 96 0.3189369
3 tob>=20 278 64 0.2302158
4 high risk 11 5 0.4545455
5 all 1175 200 0.1702128
更好的是,它可以采用类型“因素”和“逻辑”类型的变量,以便它可以总结,例如,每个年龄组,65岁的人和每个人:
summaryage = esophlong %>%
group_by_tags(key='Age.group',
agegp,
'65+'=(agegp %in% c('65-74','75+')),
'all'=1
) %>%
summarise(n=n(), ncases=sum(case), case.rate=mean(case))
>summaryage
Age.group n ncases case.rate
1 25-34 117 1 0.0085470
2 35-44 208 9 0.0432692
3 45-54 259 46 0.1776062
4 55-64 318 76 0.2389937
5 65-74 216 55 0.2546296
6 75+ 57 13 0.2280702
7 65+ 273 68 0.2490842
8 all 1175 200 0.1702128
也许用 ...
是不可能的,相反,您可能需要传递标签的列名称的向量/列表 .
有任何想法吗?
编辑:要清楚,解决方案应该将标记/组定义和所需的摘要统计信息作为参数,而不是内置到函数本身 . 作为两步 data %>% group_by_tags(tags) %>% summarise_tags(stats)
或一步 data %>% summary_tags(tags,stats)
过程 .
5 回答
这是@ eddi答案的变体 . 我将
highage
等的定义作为函数工作的一部分:以及一些示例用法:
在
DT[...]
中使用eval
的技术解释in the data.table FAQ .Not a completely functional answer ,更多"WIP"或开始讨论 . 这应该最终进入一个回购和一个额外的包或dplyr PR .
一种方法是从“正常”分组变量模拟属性的结构:
我们可以人工重现这个,看它是否/如何工作:
哪个“看起来”像普通的分组data.frame:
不幸:
我的评论
summarize
假设全面报道;你必须修改dplyr_summarise_impl(在C中),可能是summarise_grouped和summarise_not_grouped的第三个选项 .当然,这可以更加可定制,这留给读者作为练习 .
在没有任何tidyverse内部知识的情况下,我避免尝试创建
group_by()
-type函数,其输出应传递给summarise()
,而是组合一个结合两者的函数(类似于其他答案,但我希望,更加用户友好和普遍意义) .由于
group_by() %>% summarise()
返回每个嵌套的分组变量组合的联合摘要信息,因此我选择名称summarise_marginal()
,因为它将独立返回每个分组变量的边际摘要信息 .不适用于groups_df对象的解决方案
首先,解决方案不适用于
grouped_df
类,但扩展如下:使用
vars()
捕获组(与summarise_at()
或mutate_at()
一样)可以巧妙地将组与摘要函数分开,并允许在运行中创建新组:我们可以使用
.removeF
参数来删除FALSE
逻辑值 . 如果您想要汇总某些行而不是它们的赞美,则很有用:请注意,即使没有明确命名
cyl == 6
组,我们仍然会得到一个有用的名称 .与groups_df对象一起使用的解决方案
summarise_marginal0()
可以扩展为与group_by()
返回的grouped_df
对象一起使用:事实上,
summarise_marginal()
将适用于分组和未分组的data.frame
s,因此仅此功能是合适的 .这是一个有用的解决方案,但是鉴于
group_by()
的使用超出summarise()
,例如nest()
或do()
,我认为group_by_marginal()
(或group_by_tag()
或其他任何名称最好)的想法值得追求 .一些剩余的问题:
该函数需要将整数,因子和逻辑列转换为字符,以便它们的值可以很好地放在同一个
values
列中 . 这略微违反了整洁的数据原则,但与gather()
的行为方式没有什么不同 .假设
group_by_marginal()
函数是可能的,它的输出不能传递给mutate()
而不解决从每个组放置值的位置的模糊性 . 从上面的例子中,应该将meanmpg
的哪一个值赋予cyl==4
和am==0
?26.66364
(来自cyl==4
)和17.14737
(来自am==0
)都是相关的 . (注意group_by() %>% mutate()
没有歧义,因为它将返回cyl==4 & am==0
的联合汇总函数) .group_by_marginal() %>% mutate()
有三种可能的选择:应该禁止 .
它应该创建多个列,例如
meanmpg_cyl
和meanmpg_am
.它应该为每个组复制行 .
速度 . 我确信我对这个概念的实现是低效的,可以改进 .
最后,要演示原始示例问题:
这是(大多数)
dplyr
版本:给定OP创建的列,标签可以是:
但最好是像@Frank那样从原始数据创建过滤表达式:
然后创建一个函数,使用
lapply
在tags1
的每一行上运行dplyr
摘要:我希望创建一种更简单的方法来生成过滤表达式,但是我仍然对如何在
dplyr
函数的标准评估版本中创建复杂表达式感到有些困惑 .例如,我对如何使用类似下面的方法感兴趣 .
filt
函数用于创建过滤表达式,但返回的表达式需要不加引号,并且前面有一个~
,以便filter_
正确解释它 . 或者可能有interp
对于如何使这项工作感兴趣(或建议更好的方法)以及如何通过组合各个过滤器来创建具有多个条件的过滤器(如在'high risk'过滤器中):