我有一个数据集,我想对其进行平均值总结,但也计算其中 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(.))))
这有点复杂,需要大量输入才能添加具有不同摘要的列。
谢谢你
虽然这是一个老问题,但它仍然是一个有趣的问题,我有两个解决方案,我相信任何找到此页面的人都应该可以使用它们。
解决方案一
我自己的看法:
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
,其他列是所有其他列的平均值(按组和过滤器)。
两种解决方案都取决于三个实现:
summarise_at
接受两个列表作为参数,其中一个是 n 变量,一个是 m 函数,并将所有 m 函数应用于所有 n 变量,从而在 tibble 中生成 m X n 向量。因此,解决方案可能意味着强制该函数以某种方式在“对”之间循环,这些“对”由我们希望应用一个特定函数的所有变量和一个函数组成,然后是另一组变量和它们自己的函数,依此类推! mapply
或函数系列 map2
、pmap
及其来自 dplyr
的 tidyverse 同事 purrr
的变体等函数。两者都接受两个 l 元素列表,并对两个列表的相应元素(按位置匹配)执行给定操作。 reduce
与 inner_join
一起使用或仅使用 merge
。请注意,我获得的方法与OP的方法不同,但它们也是我通过他的可重现示例获得的方法(也许我们有两个不同版本的
iris
数据集?)。
如果您尝试使用 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)
)
如果你想做一些更复杂的事情,你可以编写你自己的
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
函数实际上只是该基本函数的便捷包装。
我正在寻找类似的东西并尝试了以下方法。它效果很好,而且比建议的解决方案更容易阅读。
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() 部分中,定义您的列名称并在您选择的函数内提供要汇总的列。
使用当前版本的 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