Dplyr: 保留零长度组

创建于 2014-03-20  ·  44评论  ·  资料来源: tidyverse/dplyr

http://stackoverflow.com/questions/22523131

不确定它的接口应该是什么 - 可能应该默认为 drop = FALSE。

feature wip

最有用的评论

+1 - 对于许多分析来说,这是一个交易破坏者

所有44条评论

感谢您打开这个问题哈德利。

:+1: 今天遇到了同样的问题, drop = FALSE对我来说是一个很大的帮助!

关于将 .drop = FALSE 等效项放入 dplyr 的时间范围的任何想法? 我需要某些 rCharts 才能正确呈现。

与此同时,我确实在你的工作链接中得到了答案。
http://stackoverflow.com/questions/22523131

我按两个变量分组。

+1 表示不删除空组的选项

可能与 #486 和 #413 有一些重叠。

不删除空组将非常有用。 创建汇总表时经常需要。

+1 - 对于许多分析来说,这是一个交易破坏者

我同意上述所有内容-将非常有用。

@romainfrancois目前build_index_cpp()不尊重 drop 属性:

t1 <- data_frame(
  x = runif(10),
  g1 = rep(1:2, each = 5),
  g2 = factor(g1, 1:3)
)
g1 <- grouped_df(t1, list(quote(g2)), drop = FALSE)
attr(g1, "group_size")
# should be c(5L, 5L, 0L)
attr(g1, "indices")
# shoud be list(0:4, 5:9, integer(0))

drop 属性仅适用于按因子分组时,在这种情况下,我们需要每个因子级别有一个组,无论该级别是否实际适用于数据。

这也将通过以下方式影响单表动词:

  • select() : 没有效果
  • arrange() : 没有效果
  • summarise() :应用于零行组的函数应该被赋予 0 级整数。 n()应该返回 0, mean(x)应该返回 NaN
  • filter() :组的集合应该保持不变,即使某些组现在没有行
  • mutate() :不需要为空组计算表达式

最终, drop = FALSE将成为默认值,如果同时编写drop = FALSEdrop = TRUE分支很麻烦,我很乐意放弃对drop = FALSE (因为您总是可以自己重新调整因子,或者改用字符向量)。

那有意义吗? 如果工作量很大,我们可以推到 0.4

@statwonk , @wsurles , @jennybc , @slackline , @mcfrank , @eipi10如果你想提供帮助,最好的办法是

啊。 我想我只是不知道drop应该做什么。 这就清楚了。 我不认为这是很多工作。

我打开了拉取请求 #833,它测试上面的单表动词是否正确处理零长度组。 大多数测试都被注释掉了,当然,因为 dplyr 目前无法通过它们。

+1,这里有任何状态更新吗? 爱总结,需要保持空的水平!

@ebergelson ,这是我目前获得零长度组的技巧。 我经常需要这个,这样我的条形图就会堆叠起来。

这里 df 有 3 列:名称、组和指标

df2 <- expand.grid(name = unique(df$name), group = unique(df$group)) %>%
    left_join(df, by=c("name","group")) %>%
    mutate(metric = ifelse(is.na(metric),0,metric))

我做了类似的事情——检查缺少的组,然后如果有的话生成所有组合和left_join

不幸的是,这个问题似乎并没有得到很多人的喜爱……也许是因为有这个简单的解决方法。

@wsurles@bpbond谢谢,是的,我使用了与您建议的类似的解决方法! 希望看到像 .drop 这样的内置修复程序。

只是补充并同意上面的每个人 - 这是许多分析的一个非常关键的方面。 很想看到一个实现。

这里需要一些更多的细节:

如果我有这个:

> df <- data_frame( x = c(1,1,1,2,2), f = factor( c(1,2,3,1,1) ) )
> df
Source: local data frame [5 x 2]

  x f
1 1 1
2 1 2
3 1 3
4 2 1
5 2 1

我按x然后f分组,我最终得到 6 (2x3) 个组,其中(2, 2)(2,3)为空。 没关系。 我可以设法实现我认为的。

现在,如果我有这个怎么办:

> df <- data_frame( f = factor( c(1,1,2,2), levels = 1:3), x = c(1,2,1,4) )
> df
Source: local data frame [4 x 2]

  f x
1 1 1
2 1 2
3 2 1
4 2 4

我想按f然后x分组。 这些团体会是什么? @哈德利

在这种情况下, stats::aggregateplyr::ddply返回 4 个组(1,1; 1,2; 2,1; 和 2,4),所以我建议这是符合的行为.

它不应该与table() ,即返回 9 个组吗?

> table(df$f, df$x)
  1 2 4
1 1 1 0
2 1 0 1
3 0 0 0

我希望df %>% group_by(f, x) %>% tally基本上给出与with(df, as.data.frame(table(f, x)))ddply(df, .(f, x), nrow, .drop=FALSE)相同的结果。

我认为我们想要的行为是保留零长度组,如果它们是因子(如 .drop in plyr),所以我想我们会想要@huftis的建议。 不过,我建议默认值为 drop = TRUE,这样默认行为就不会改变,重新@bpbond的建议。

嗯,我很难完全理解行为应该是什么。 这些非常简单的思想实验看起来正确吗?

df <- data_frame(x = 1, y = factor(1, levels = 2))
df %>% group_by(x) %>% summarise(n())
#> x n
#> 1 1  

df %>% group_by(y) %>% summarise(n())
#> y n
#> 1 1
#> 2 0

df %>% group_by(x, y) %>% summarise(n()
#> x y n
#> 1 1 1
#> 1 2 0

但是如果x有多个值呢? 它应该像这样工作吗?

df <- data_frame(x = 1:2, y = factor(1, levels = 2))
df %>% group_by(x, y) %>% summarise(n()
#> x y n
#> 1 1 1
#> 2 1 1
#> 1 1 0
#> 2 2 0

也许保留空组仅在按单个变量分组时才有意义? 如果我们更现实地构建它,例如data_frame(age_group = c(40, 60), sex = factor(M, levels = c("F", "M"))你真的想要女性的计数吗? 我想有时你会,有时你不会。 扩展所有组合对我来说似乎有点不同(并且与因子的使用无关)。

也许group_by需要dropexpand参数? drop = FALSE将保留由未出现在数据中的因子水平生成的所有大小为零的组。 expand = TRUE将保留由未出现在数据中的值组合生成的所有大小为零的组。

@hadley你的例子对我来说很正确(假设你的意思是levels = 1:2 ,而不是levels = 2 )。 而且我认为即使按多个变量分组时,保留空组也是有意义的。 例如,如果变量是sexmalefemale )和answer (在问卷上,级别disagreeneutralagree ),并且您想计算每个性别的每个答案的频率(例如,对于表格,或用于以后的绘图),您不会只想删除答案类别因为没有女性回答。

我还希望因子变量在结果data_frame (未转换为字符串)中保持因子变量,并带有 _original levels_。 (因此,在绘制数据时,答案类别将按正确顺序排列,而不是按字母顺序排列的agreedisagreeneutral )。

对于您的最后一个示例,_在某些情况下_自然会删除sex变量(例如,如果_故意_没有调查女性),而_在其他情况下_ 不会(例如,在计算按以下方式分层的出生缺陷数量时)性别(可能还有年份))。 但这可以(并且应该)在聚合数据之后轻松处理。 (另一种解决方案是接受 _vector-valued_ .drop参数。这很好,但我想这可能会使事情复杂化?)

(另一种解决方案是接受向量值 .drop 参数。这很好,但我想这可能会使事情复杂化?)

是的,可能太复杂了。 否则我同意@huftis的评论。

@哈德利
我认为
YES 将所有值组合扩展到 group_by,如果它们存在于数据中。
NO 不要扩展不存在的因子水平。

我最常用的用例是为图表准备一组汇总数据(在探索期间)。 并且图表需要具有所有值的组合。 但是它们不需要所有组的因子水平都为 0.. 例如,您不能在没有所有组合的情况下堆叠条形图。 但是您不需要数据中不存在的因子值,这些值在堆叠时仅为 0,而在图例中为空值。

我相信将所有值扩展到 group_by 应该是默认值,因为如果需要,在 group by 之后过滤 0 个案例更容易(也更直观)。 我不认为 .drop 参数是必要的,因为它很容易在之后过滤 0 个案例。 我们不会对任何其他函数使用额外的参数,因此这会打破常规。 默认应该只是显示基于 group_by 的现有值的所有组合的结果。

我认为这将是正确的默认行为。 在这里,唯一值只会扩展因子中的现有值,而不是所有因子水平。 (这是我在运行 group_by 删除 0 值后运行的内容)

## Expand data so plot groups works correctly
  df2 <- expand.grid(name = unique(df$name), group = unique(df$group)) %>%
    left_join(df, by=c("name","group")) %>%
    mutate(
      measure = ifelse(is.na(measure),0,measure)
    )

即使所有组都为零,我可以看到您想要一个值的唯一情况是时间数据。 也许中间某处缺少一天的数据。 这里仍然需要在日期范围内进行扩展和连接。 因子水平的情况不适用。 我认为数据处理者自己处理丢失的日期是公平的。

感谢您在此库上所做的所有出色工作。 我 90% 的工作都在使用 dplyr。 :)

我非常同意@huftis。

我认为降低级别或级别组合与数据无关。 您可能正在使用小样本对函数或图形进行原型设计。 或者进行拆分-应用-组合操作,在这种情况下,您需要保证每个组的输出与所有其他组一致。

软化我的立场:我认为值得考虑当分组变量已经是一个适当的因素时默认行为是否应该不同,而当它被强制为因素时。 我可以看到在强制情况下保留未使用级别的义务可能较少。 但是,如果我不厌其烦地将某些东西设置为一个因素并控制水平......通常有一个很好的理由,我不想一直为保持这一点而奋斗。

仅供参考,我也想看到这个功能。 我有一个与@huftis描述的类似的场景,并且必须跳过箍以获得我需要的结果。

从 SO 过来。 这不是“tidyr”中的complete应该提供帮助的吗?

是的,它确实。 实际上,我最近刚刚了解了“完整”,它似乎以一种深思熟虑的方式完成了这项工作。

为 SQL 后端实现它看起来很困难,因为它们默认会删除所有组。 我们是否应该保留它并为 SQL 实现 tidyr::complete() ?

我创建了问题 #3033 并没有意识到这个问题已经存在 - 为重复道歉。 为了添加我自己的谦虚建议,我目前使用pull()forcats::fct_count()作为解决此问题的方法。

我不喜欢这种方法,因为fct_count()违背了 tidyverse 原则,即输出总是与输入相同的类型(即这个函数从向量中创建一个小标题),我有重命名输出中的列。 当dplyr::count()旨在涵盖一个步骤时,这将创建 3 个步骤 ( pull() %>% fct_count() %>% rename() )。 如果forcats::fct_count()dplyr::count()可以以某种方式合并,并弃用forcats::fct_count() ,那就太棒

tidyr::complete()对因子有效吗?

默认情况下,必须保留所有因子级别和因子级别的组合。 这种行为可以通过dropexpand等参数来控制。因此dplyr::count()的默认行为应该是这样的:

df <- data.frame(x = 1:2, y = factor(c(1, 1), levels = 1:2))
df %>% dplyr::count(x, y)
#>  # A tibble: 4 x 3
#>       x        y       n
#>     <int>   <fct>    <int>
#> 1     1        1       1
#> 2     2        1       1
#> 3     1        2       0
#> 4     2        2       0

零长度组(组的组合)可以稍后过滤。 但是对于探索性分析,我们必须看到全貌。

  1. 此问题的解决方案是否有任何状态更新?
  2. 有没有计划彻底解决这个问题?

2:是的
1:关于这个问题有一些技术实现上的困难,但我会在接下来的几周内研究它。

我们可以通过事后扩展数据来解决这个问题,如下所示:

library(tidyverse)

truly_group_by <- function(data, ...){
  dots <- quos(...)
  data <- group_by( data, !!!dots )

  labels <- attr( data, "labels" )
  labnames <- names(labels)
  labels <- mutate( labels, ..index.. =  attr(data, "indices") )

  expanded <- labels %>%
    tidyr::expand( !!!dots ) %>%
    left_join( labels, by = labnames ) %>%
    mutate( ..index.. = map(..index.., ~if(is.null(.x)) integer() else .x ) )

  indices <- pull( expanded, ..index..)
  group_sizes <- map_int( indices, length)
  labels <- select( expanded, -..index..)

  attr(data, "labels")  <- labels
  attr(data, "indices") <- indices
  attr(data, "group_sizes") <- group_sizes

  data
}

df  <- data_frame(
  x = 1:2,
  y = factor(c(1, 1), levels = 1:2)
)
tally( truly_group_by(df, x, y) )
#> # A tibble: 4 x 3
#> # Groups:   x [?]
#>       x y         n
#>   <int> <fct> <int>
#> 1     1 1         1
#> 2     1 2         0
#> 3     2 1         1
#> 4     2 2         0
tally( truly_group_by(df, y, x) )
#> # A tibble: 4 x 3
#> # Groups:   y [?]
#>   y         x     n
#>   <fct> <int> <int>
#> 1 1         1     1
#> 2 1         2     1
#> 3 2         1     0
#> 4 2         2     0

显然,这将在内部处理,没有使用 tidyr 或 purrr。

这似乎解决了原来的问题:

> df = data.frame(a=rep(1:3,4), b=rep(1:2,6))
> df$b = factor(df$b, levels=1:3)
> df %>%
+   group_by(b) %>%
+   summarise(count_a=length(a), .drop=FALSE)
# A tibble: 2 x 3
  b     count_a .drop
  <fct>   <int> <lgl>
1 1           6 FALSE
2 2           6 FALSE
> df %>%
+   truly_group_by(b) %>%
+   summarise(count_a=length(a), .drop=FALSE)
# A tibble: 3 x 3
  b     count_a .drop
  <fct>   <int> <lgl>
1 1           6 FALSE
2 2           6 FALSE
3 3           0 FALSE

这里的关键是这个

 tidyr::expand( !!!dots ) %>%

这意味着无论变量是否为因素,都要扩展所有可能性。

我会说我们要么:

  • 展开所有drop=FALSE ,可能有很多 0 长度组
  • 如果drop=TRUE做我们现在做的事情

也许有一个功能来切换下降。

我会说这是一个相对便宜的操作,因为它只涉及操作元数据,所以也许首先在 R 中执行此操作风险较小?

你的意思是crossing()而不是expand()吗?

查看内部结构,您是否同意我们“仅”需要更改build_index_cpp() ,特别是labels数据框的生成,才能实现这一点?

我们可以从仅用drop = FALSE扩展因子开始吗? 我考虑了一种“自然”语法,但这最终可能太令人困惑(甚至可能不够强大):

group_by(data, crossing(col1, col2), col3)

语义:使用col1col2所有组合,并且存在与col3

是的,我想说这只会影响build_index_cpp和属性labelsindicesgroup_sizes ,我想在一个整洁的结构作为#3489 的一部分

这次讨论的“唯一扩展因素”部分是花了这么长时间的。

这些结果会是什么:

library(dplyr)

d <- data_frame(
  f1 = factor( rep( c("a", "b"), each = 4 ), levels = c("a", "b", "c") ),
  f2 = factor( rep( c("d", "e", "f", "g"), each = 2 ), levels = c("d", "e", "f", "g", "h") ),
  x  = 1:8,
  y  = rep( 1:4, each = 2)
)

f <- function(data, ...){
  group_by(data, !!!quos(...))  %>%
    tally()
}
f(d, f1, f2, x)
f(d, x, f1, f2)

f(d, f1, f2, x, y)
f(d, x, f1, f2, y)

如果忽略行顺序,我认为f(d, f1, f2, x)应该给出与f(d, x, f1, f2)相同的结果。 其他两个也一样。

也很有趣:

f(d, f2, x, f1, y)
d %>% sample_frac(0.3) %>% f(...)

我喜欢只为因子实现完全扩展的想法。 对于非字符数据(包括逻辑数据),我们可以定义/使用继承各自数据类型的类因子类。 也许由forcats提供? 这使得用脚射击自己变得更加困难。

#3492 正在进行中

library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
df <- data_frame( f = factor( c(1,1,2,2), levels = 1:3), x = c(1,2,1,4) )

( res1 <- tally(group_by(df,f,x, drop = FALSE)) )
#> # A tibble: 9 x 3
#> # Groups:   f [?]
#>   f         x     n
#>   <fct> <dbl> <int>
#> 1 1        1.     1
#> 2 1        2.     1
#> 3 1        4.     0
#> 4 2        1.     1
#> 5 2        2.     0
#> 6 2        4.     1
#> 7 3        1.     0
#> 8 3        2.     0
#> 9 3        4.     0
( res2 <- tally(group_by(df,x,f, drop = FALSE)) )
#> # A tibble: 9 x 3
#> # Groups:   x [?]
#>       x f         n
#>   <dbl> <fct> <int>
#> 1    1. 1         1
#> 2    1. 2         1
#> 3    1. 3         0
#> 4    2. 1         1
#> 5    2. 2         0
#> 6    2. 3         0
#> 7    4. 1         0
#> 8    4. 2         1
#> 9    4. 3         0

all.equal( res1, arrange(res2, f, x) )
#> [1] TRUE

all.equal( filter(res1, n>0), tally(group_by(df, f, x)) )
#> [1] TRUE
all.equal( filter(res2, n>0), tally(group_by(df, x, f)) )
#> [1] TRUE

reprex 包(v0.2.0) 于

至于complete()解决了这个问题——不,不是真的。 无论正在计算摘要,它们在空向量上的行为都需要保留,而不是事后修补。 例如:

data.frame(x=factor(1, levels=1:2), y=4:5) %>%
     group_by(x) %>%
     summarize(min=min(y), sum=sum(y), prod=prod(y))
# Should be:
#> x       min   sum  prod
#> 1         4     9    20
#> 2       Inf     0     1

sumprod (以及在较小程度上, min )(以及其他各种函数)在空向量上具有非常明确的语义,并且不必之后使用complete()并重新定义这些行为。

@kenahoo我不确定我是否理解。 这是您使用当前开发版本获得的结果。 所以你唯一没有得到的是来自min()的警告

library(dplyr)

data.frame(x=factor(1, levels=1:2), y=4:5) %>%
  group_by(x) %>%
  summarize(min=min(y), sum=sum(y), prod=prod(y))
#> # A tibble: 2 x 4
#>   x       min   sum  prod
#>   <fct> <dbl> <int> <dbl>
#> 1 1         4     9    20
#> 2 2       Inf     0     1

min(integer())
#> Warning in min(integer()): no non-missing arguments to min; returning Inf
#> [1] Inf
sum(integer())
#> [1] 0
prod(integer())
#> [1] 1

reprex 包(v0.2.0) 于 2018 年 5 月 15 日创建。

@romainfrancois哦,

这个老问题已被自动锁定。 如果您认为您发现了相关问题,请提交一个新问题(使用 reprex)并链接到此问题。 https://reprex.tidyverse.org/

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

Prometheus77 picture Prometheus77  ·  4评论

profdave picture profdave  ·  3评论

steromano picture steromano  ·  4评论

JohnMount picture JohnMount  ·  3评论

JohnMount picture JohnMount  ·  4评论