R dplyr 将多个函数汇总到选定的变量

问题描述 投票:0回答:5

我有一个数据集,我想对其进行平均值总结,但也计算其中 1 个变量的最大值。

让我从一个我想要实现的目标开始:

iris %>%
  group_by(Species) %>%
  filter(Sepal.Length > 5) %>%
  summarise_at("Sepal.Length:Petal.Width",funs(mean))

这给了我以下结果

# A tibble: 3 × 5
     Species Sepal.Length Sepal.Width Petal.Length Petal.Width
      <fctr>        <dbl>       <dbl>        <dbl>       <dbl>
1     setosa          5.8         4.4          1.9         0.5
2 versicolor          7.0         3.4          5.1         1.8
3  virginica          7.9         3.8          6.9         2.5

有没有简单的方法可以添加,例如

max(Petal.Width)
来总结?

到目前为止我已经尝试过以下方法:

iris %>%
  group_by(Species) %>%
  filter(Sepal.Length > 5) %>%
  summarise_at("Sepal.Length:Petal.Width",funs(mean)) %>%
  mutate(Max.Petal.Width = max(iris$Petal.Width))

但是通过这种方法,我丢失了上面代码中的

group_by
filter
并给出了错误的结果。

我能够实现的唯一解决方案如下:

iris %>%
  group_by(Species) %>%
  filter(Sepal.Length > 5) %>%
  summarise_at("Sepal.Length:Petal.Width",funs(mean,max)) %>%
  select(Species:Petal.Width_mean,Petal.Width_max) %>% 
  rename(Max.Petal.Width = Petal.Width_max) %>%
  rename_(.dots = setNames(names(.), gsub("_.*$","",names(.))))

这有点复杂,需要大量输入才能添加具有不同摘要的列。

谢谢你

r dplyr summarize
5个回答
6
投票

虽然这是一个老问题,但它仍然是一个有趣的问题,我有两个解决方案,我相信任何找到此页面的人都应该可以使用它们。

解决方案一

我自己的看法:

mapply(summarise_at, 
       .vars = lst(names(iris)[!names(iris)%in%"Species"], "Petal.Width"), 
       .funs = lst(mean, max), 
       MoreArgs = list(.tbl = iris %>% group_by(Species) %>% filter(Sepal.Length > 5))) 
%>% reduce(merge, by = "Species")

    #         Species Sepal.Length Sepal.Width Petal.Length Petal.Width.x Petal.Width.y
    #    1     setosa        5.314       3.714        1.509        0.2773           0.5
    #    2 versicolor        5.998       2.804        4.317        1.3468           1.8
    #    3  virginica        6.622       2.984        5.573        2.0327           2.5

解决方案二

使用来自 tidyverse 本身的包

purrr
的优雅解决方案,灵感来自于 此讨论

list(.vars = lst(names(iris)[!names(iris)%in%"Species"], "Petal.Width"),
     .funs = lst("mean" = mean, "max" = max)) %>% 
      pmap(~ iris %>% group_by(Species) %>% filter(Sepal.Length > 5) %>% summarise_at(.x, .y)) 
      %>% reduce(inner_join, by = "Species")

+ + + # A tibble: 3 x 6
  Species    Sepal.Length Sepal.Width Petal.Length Petal.Width.x Petal.Width.y
  <fct>             <dbl>       <dbl>        <dbl>         <dbl>         <dbl>
1 setosa             5.31        3.71         1.51         0.277           0.5
2 versicolor         6.00        2.80         4.32         1.35            1.8
3 virginica          6.62        2.98         5.57         2.03            2.5

简短讨论

data.frame 和 tibble 是所需的结果,最后一列是

max
petal.width
,其他列是所有其他列的平均值(按组和过滤器)。

两种解决方案都取决于三个实现:

  1. summarise_at
    接受两个列表作为参数,其中一个是 n 变量,一个是 m 函数,并将所有 m 函数应用于所有 n 变量,从而在 tibble 中生成 m X n 向量。因此,解决方案可能意味着强制该函数以某种方式在“对”之间循环,这些“对”由我们希望应用一个特定函数的所有变量和一个函数组成,然后是另一组变量和它们自己的函数,依此类推!
  2. 现在,上面的内容在 R 中意味着什么?什么强制对两个列表的对应元素进行操作?诸如
    mapply
    或函数系列
    map2
    pmap
    及其来自
    dplyr
    的 tidyverse 同事
    purrr
    的变体等函数。两者都接受两个 l 元素列表,并对两个列表的相应元素(按位置匹配)执行给定操作。
  3. 因为产品不是 tibble 或 data.frame,而是一个列表,您 只需将
    reduce
    inner_join
    一起使用或仅使用
    merge

请注意,我获得的方法与OP的方法不同,但它们也是我通过他的可重现示例获得的方法(也许我们有两个不同版本的

iris
数据集?)。


2
投票

如果您尝试使用 dplyr 完成所有操作(这可能更容易记住),那么您可以利用新的

across
函数,该函数将从 dplyr 1.0.0 开始提供。

iris %>%
  group_by(Species) %>%
  filter(Sepal.Length > 5) %>% 
  summarize(across(Sepal.Length:Petal.Width, mean)) %>% 
  cbind(iris %>% 
          group_by(Species) %>% 
          summarize(across(Petal.Width, max)) %>% 
          select(-Species)
  )

它表明唯一的困难是在分组变量的同一列

Petal.Width
上组合两个计算 - 您必须再次进行分组,但可以将其嵌套到
cbind
中。 这会正确返回结果:

     Species Sepal.Length Sepal.Width Petal.Length Petal.Width Petal.Width
1     setosa     5.313636    3.713636     1.509091   0.2772727         0.6
2 versicolor     5.997872    2.804255     4.317021   1.3468085         1.8
3  virginica     6.622449    2.983673     5.573469   2.0326531         2.5

如果任务不指定两个计算,而只在同一列上指定一个计算

Petal.Width
,那么这可以优雅地写为:

iris %>%
  group_by(Species) %>%
  filter(Sepal.Length > 5) %>% 
  summarize(
    across(Sepal.Length:Petal.Length, mean),
    across(Petal.Width, max)
  )

1
投票

如果你想做一些更复杂的事情,你可以编写你自己的

summarize_at
版本。在此版本中,您可以提供列名、函数和命名规则的三元组。例如

这是一个艰难的开始

my_summarise_at<-function (.tbl, ...) 
{
    dots <- list(...)
    stopifnot(length(dots)%%3==0)
    vars <- do.call("append", Map(function(.cols, .funs, .name) {
        cols <- select_colwise_names(.tbl, .cols)
        funs <- as.fun_list(.funs, .env = parent.frame())
        val<-colwise_(.tbl, funs, cols)
        names <- sapply(names(val), function(x) gsub("%", x, .name))
        setNames(val, names)
    }, dots[seq_along(dots)%%3==1], dots[seq_along(dots)%%3==2], dots[seq_along(dots)%%3==0]))
    summarise_(.tbl, .dots = vars)
}
environment(my_summarise_at)<-getNamespace("dplyr")

你可以用

来调用它
iris %>%
  group_by(Species) %>%
  filter(Sepal.Length > 5) %>%
  my_summarise_at("Sepal.Length:Petal.Width", mean, "%_mean", 
      "Petal.Width", max, "%_max")

对于名称,我们只需将“%”替换为默认名称。这个想法只是动态构建

summarize_
表达式。
summarize_at
函数实际上只是该基本函数的便捷包装。


0
投票

我正在寻找类似的东西并尝试了以下方法。它效果很好,而且比建议的解决方案更容易阅读。

iris %>% 
group_by(Species) %>%
filter(Sepal.Length > 5) %>% 
summarise(MeanSepalLength=mean(Sepal.Length), 
MeanSepalWidth = mean(Sepal.Width),
MeanPetalLength=mean(Petal.Length),
MeanPetalWidth=mean(Petal.Width), 
MaxPetalWidth=max(Petal.Width))

# A tibble: 3 x 6
Species    MeanSepalLength MeanSepalWidth MeanPetalLength MeanPetalWidth MaxPetalWidth
<fct>                <dbl>          <dbl>           <dbl>          <dbl>         <dbl>
1 setosa                5.01           3.43            1.46          0.246           0.6
2 versicolor            5.94           2.77            4.26          1.33            1.8
3 virginica             6.59           2.97            5.55          2.03            2.5

在 summarise() 部分中,定义您的列名称并在您选择的函数内提供要汇总的列。


0
投票

使用当前版本的 across(tidyverse 1.3.2),您可以通过提供要分配的 cross 名称来轻松完成此操作。 它将返回给定的名称,后跟“$”符号,然后是原始列名称。 当然,您可以使用“重命名”轻松重命名。

iris %>% 
  group_by(Species) %>% 
  filter(Sepal.Length > 5) %>% 
  summarise(mean = across(Sepal.Length:Petal.Length, mean), 
            max = across(Petal.Width, max))

# A tibble: 3 × 3
  Species    mean$Sepal.Length $Sepal.Width $Petal.Length max$Petal.Width
  <fct>                  <dbl>        <dbl>         <dbl>           <dbl>
1 setosa                  5.01         3.43          1.46             0.6
2 versicolor              5.94         2.77          4.26             1.8
3 virginica               6.59         2.97          5.55             2.5
© www.soinside.com 2019 - 2024. All rights reserved.