【问题标题】:How do I summarise only part of a table?如何仅汇总表格的一部分?
【发布时间】:2015-04-21 22:46:13
【问题描述】:

我有两个相关的用例,我需要总结表格的一部分,以类似于filter 的方式指定。

简而言之,我想要这样的东西:

iris %>%
    use_only(Species == 'setosa') %>%
    summarise_each(funs(sum), -Species) %>%
    mutate(Species = 'setosa_sum') %>%
    use_all()

要产生这个:

Source: local data frame [101 x 5]

   Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1         250.3       171.4         73.1        12.3 setosa_sum
2           7.0         3.2          4.7         1.4 versicolor
3           6.4         3.2          4.5         1.5 versicolor
4           6.9         3.1          4.9         1.5 versicolor
5           5.5         2.3          4.0         1.3 versicolor
…

因此,我没有按列的值进行分组,而是使用过滤条件对表的视图进行操作,而 实际上不会丢失表的其余部分(与过滤器不同)。

如何智能实现use_only/use_all 更好的是,dplyr 中是否已经包含此功能,如何使用?

当然很容易生成上面的结果,但我需要为许多不同的情况做类似的事情,过滤条件复杂多变。

【问题讨论】:

  • 为什么不把 rbind 和剩下的数据集放在最后呢?是不是太慢/效率低了?
  • @konvas 不止一次这样做很麻烦。我当前的代码需要针对八个不同的标准执行此操作。我显然已经将过滤/汇总/rbind 的逻辑包装到了一个函数中,但是它所期望的参数非常不灵活,因此绝不是一个很棒的 AP​​I。哦,这个函数是特定于我的特定 DF 布局的,由 15 行组成。
  • 返回行的顺序重要吗?如果被操纵的组在桌子中间的某个地方,它是否需要回到桌子中间?如果它穿插在各处,然后汇总成一行呢?

标签: r dplyr


【解决方案1】:

我通过让use_only 将表的其余部分保存到全局选项dplyr_use_only_rest 并让use_all 将其重新绑定在一起的方法来实现这一点。

use_only <- function(.data, ...) {
    if (!is.null(.data$.index)) {
        stop("data cannot already have .index column, would be overwritten")
    }
    filt <- .data %>%
        mutate(.index = row_number()) %>%
        filter(...)

    rest <- .data %>% slice(-filt$.index)
    options(dplyr_use_only_rest = rest)
    select(filt, -.index)
}

use_all <- function(.data, ...) {
    rest <- getOption("dplyr_use_only_rest")
    if (is.null(rest)) {
        stop("called use_all() without earlier use_only()")
    }
    options(dplyr_use_only_rest = NULL)
    bind_rows(.data, rest)
}

我认识到设置全局选项并不是函数式编程的理想设计,但我认为没有另一种方法可以确保数据帧的其余部分通过任何未触及的中间函数。向对象添加额外属性将无法在 dosummarize 等函数中保留。

此时,

iris %>%
    use_only(Species == 'setosa') %>%
    summarise_each(funs(sum), -Species) %>%
    mutate(Species = 'setosa_sum') %>%
    use_all()

根据需要返回:

   Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1         250.3       171.4         73.1        12.3 setosa_sum
2           7.0         3.2          4.7         1.4 versicolor
3           6.4         3.2          4.5         1.5 versicolor
4           6.9         3.1          4.9         1.5 versicolor
5           5.5         2.3          4.0         1.3 versicolor
...

可以使用任何中间步骤来代替 summarize_eachmutatedofilter 等),并且它们只会发生在指定的行上。您甚至可以添加或删除列(其余列将填写 NAs)。

【讨论】:

  • 谢谢。这似乎是要走的路。使用全局变量显然(……)是完全不可接受的——但我们不需要它,我们可以做group_by 所做的事情,并将相关细节存储在属性中(并在我们使用时使用适当的 S3 调度) .一旦我找到一分钟 (sigh),我就会试一试。我认为这个(“表格视图”)的通用版本可能应该在 dplyr 中找到它的方式。
  • @KonradRudolph 正如我在回答中指出的那样,我认为我们实际上 可以 将额外数据存储在属性中,因为中间 summarise_each 函数会破坏他们。例如。 attr(mtcars, "extra") &lt;- "stuff",后跟 mtcars %&gt;% group_by(am) %&gt;% summarise_each(funs(mean)) %&gt;% attr("extra") 产生 NULL。
  • @KonradRudolph 要解决这个问题,必须替换 summarise_each 函数,或者让 use_only 返回一个新的 tbl 类,其中定义了一个新的 summarise_each 方法,该方法包含 dplyr(以及dosummarise 或任何其他在不保留属性的中间步骤中想要发生的事情)。我还缺少其他解决方法吗?
  • 嗯,我完全错过了那部分。奇怪的。无论如何,我甚至会称其为错误(尽管 Hadley 可能不同意……)。覆盖summarise_ 等当然是可能的,但如果有几个函数破坏属性,或者更糟的是,如果将来添加这样的函数,这种方法就无法扩展。
  • @KonradRudolph 是的,如果有任何其他方式,我不会建议 global :) 我不认为这是一个错误。 summarise (etc) 从许多输出中构建它的输出;它不能负责组合或保留超出tbl_df 预期的属性。 (如果某些输出具有某些属性而另一些具有其他属性怎么办?它是否真的应该检查新输出是否缺少原始输入具有的任何属性?这是我从未见过的设计模式)。
【解决方案2】:

我认为您搜索函数以满足特定语法的方法过于严格。这就是我使用data.table 会做的事情(我不确定dplyr 是否允许像这样的可变行,我知道它已经有一段时间了):

library(data.table)
dt = as.data.table(iris)

dt[, if (Species == 'setosa') lapply(.SD, sum) else .SD, by = Species]
#        Species Sepal.Length Sepal.Width Petal.Length Petal.Width
#  1:     setosa        250.3       171.4         73.1        12.3
#  2: versicolor          7.0         3.2          4.7         1.4
#  3: versicolor          6.4         3.2          4.5         1.5
#  4: versicolor          6.9         3.1          4.9         1.5
#  5: versicolor          5.5         2.3          4.0         1.3
# ---                                                             

您也可以在末尾添加[Species == 'setosa', Species := 'setosa_sum']来修改名称。它应该很容易扩展到多个标准/任何功能。

【讨论】:

  • 我不太确定您所说的承诺是什么意思 - (a) dplyr 与 data.tables 一起使用,并且 (b) data.table 具有 setDTsetDF 函数在data.framedata.table 之间来回切换。
  • “提交”是指提交给一个 API。我不想混合几个本质上做同样事情的 API……
  • meh,通过扩展该论点,您应该留在基地。 imo R 的方式是使用任何和所有包你可以以最简单和最快的形式实现你想要的
  • 听起来你想为了流水线而流水线——这显然是你的选择,但我认为这不是一个明智的方法。而且这个选项与手动拆分和缝合在一起根本不同。无论如何,你有选择,可以选择做任何你想做的事,我想说的是,你认为你拥有的任何承诺都是完全想象的。
  • @BrodieG “可以”与“应该”不同。这是一个非常难以理解的混乱。事实上,我一开始并不喜欢 data.table 语法,但是以这种方式将它与 dplyr 管道相结合……不。我的首要目标是编写(尽可能)明显没有错误的可理解、可维护的代码。使用这样的卷积会适得其反。正如我之前提到的,通过临时变量有很多可行的解决方案。制作一个可读解决方案是我所追求的。
【解决方案3】:

您可以创建一个新列进行分组:

iris %>%
  mutate( group1 = ifelse(Species == "setosa", "", row_number()))  %>%
  group_by( group1, Species ) %>%
  summarise_each(funs(sum), -Species, -group1) %>%
  ungroup() %>%
  select(-group1)

更新 - 作为更通用的解决方案

library(lazyeval)

use_only_ <- function(x, condition, ...) {
  condition <- as.lazy(condition, parent.frame())
  mutate_(x, .group = condition) %>% 
    group_by_(".group", ...)
}

use_only <- function(x, condition, ...) {
  use_only_(x, lazy(condition), ...)
}

use_all <- function(x) {
  ungroup(x) %>%
    select(- .group)
}

在数据框和调用环境的上下文中使用use_only 与任何条件。在这种情况下:

iris %>%
  use_only( ifelse(Species == "setosa", "", row_number()), "Species") %>%
  summarise_each(funs(sum), -Species, -.group) %>%
  use_all()

use_only_ 可以与公式或字符串一起使用。例如:

condition <- ~ifelse(Species == "setosa", "", row_number())

condition <- "ifelse(Species == 'setosa' , "", row_number())"

然后调用:

iris %>%
  use_only_(condition, "Species") %>%
  summarise_each(funs(sum), -Species, -.group) %>%
  use_all()

当在 use_onlyuse_all 调用之间发生变异时,您必须注意仅更改标记组内的值。

【讨论】:

  • 您可能需要在select(-group1) 之前添加ungroup(),因为select 无法删除分组列。
  • 请注意,这不适用于非汇总操作,例如 OP 示例中的 mutate,或者对于在一行执行时不保留同一行的汇总操作(尽管也许这些可以解决,对于 OP 的情况来说不是必需的!)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-01
相关资源
最近更新 更多