对多组列求和
Posted
技术标签:
【中文标题】对多组列求和【英文标题】:Summing Multiple Groups of Columns 【发布时间】:2018-10-31 18:50:00 【问题描述】:我的数据框包含图像分析的结果,其中列是图像中存在的特定类的比例,因此示例数据框 class_df
看起来像:
id A B C D E F
1 0.20 0.30 0.10 0.15 0.25 0.00
2 0.05 0.10 0.05 0.30 0.10 0.40
3 0.10 0.10 0.10 0.20 0.20 0.30
这些类中的每一个都属于一个功能组,我想创建新列,其中每个功能组的比例都是从类中计算出来的。映射示例class_fg
class fg
A Z
B Z
C Z
D Y
E Y
F X
所需的结果将是(添加行以显示所需的新列):
id A B C D E F | X Y Z
1 0.20 0.30 0.10 0.15 0.25 0.00 | 0.00 0.40 0.60
2 0.05 0.10 0.05 0.30 0.10 0.40 | 0.40 0.40 0.20
3 0.10 0.10 0.10 0.20 0.20 0.30 | 0.30 0.40 0.30
我可以一次使用一个功能组来完成
first_fg <- class_fg %>%
filter(fg == "Z") %>%
select(class) %>%
unlist()
class_df <- class_df %>%
mutate(Z = rowSums(select(., one_of(first_fg))))
当然有更好的方法来做到这一点,我可以计算每个功能组的行总和,而不必为每个组重复此代码?也许使用 purrr?
【问题讨论】:
是的,这称为聚合,然后是汇总。做class_fg %>% group_by(fg) %>% summarize(...your summary code goes here...)
抱歉@Ronak,已正确更新。 “标签”应该是“类”
@smci - 我不明白这将如何让我为 class_df 创建一个我真正想要总结的摘要?
你的代码很混乱,因为你从来没有命名你的 df. (是叫class_fg
还是class_df
?什么是class_df
?)不管怎样,你想要的解决方案是whatever_your_df_is_actually_called %>% group_by(fg) %>% summarize(...your summary code goes here...)
我已更新以使示例更清晰。我正在处理一个非常大的数据集,我不确定我是否可以共享(并且在这里使用它作为示例会太大),所以希望改进的示例有所帮助。
【参考方案1】:
在列子集上使用rowSums
的另一个tidyverse
解决方案:
library(tidyverse)
class_fg %>%
group_by(fg) %>%
summarize(list(rowSums(class_df[class]))) %>%
spread(1,2) %>%
unnest() %>%
bind_cols(class_df, .)
#> id A B C D E F X Y Z
#> 1 1 0.20 0.3 0.10 0.15 0.25 0.0 0.0 0.4 0.6
#> 2 2 0.05 0.1 0.05 0.30 0.10 0.4 0.4 0.4 0.2
#> 3 3 0.10 0.1 0.10 0.20 0.20 0.3 0.3 0.4 0.3
或者为了代码高尔夫的荣耀:
x <- with(class_fg, tapply(class, fg, reformulate))
mutate(class_df, !!!map(x, ~as.list(.)[[2]]))
#> id A B C D E F X Y Z
#> 1 1 0.20 0.3 0.10 0.15 0.25 0.0 0.0 0.4 0.6
#> 2 2 0.05 0.1 0.05 0.30 0.10 0.4 0.4 0.4 0.2
#> 3 3 0.10 0.1 0.10 0.20 0.20 0.3 0.3 0.4 0.3
【讨论】:
【参考方案2】:我通常的方法是坚持base
R,只要数据集不会变得太大。在您的情况下,base
R 解决方案将是:
class_df=as.data.frame(
c(class_df,
lapply(split(class_fg,class_fg$fg),
function(x) rowSums(class_df[,x$class,drop=FALSE]))))
class_df
# id A B C D E F X Y Z
#1 1 0.20 0.3 0.10 0.15 0.25 0.0 0.0 0.4 0.6
#2 2 0.05 0.1 0.05 0.30 0.10 0.4 0.4 0.4 0.2
#3 3 0.10 0.1 0.10 0.20 0.20 0.3 0.3 0.4 0.3
如果数据集太大,我使用data.table
。 data.table
解决您的问题:
library(data.table)
class_dt=data.table(class_df)
grps=split(class_fg,class_fg$fg)
for (g in grps) class_dt[,c(g$fg[1]):=rowSums(.SD),.SDcols=g$class,]
class_dt
# id A B C D E F X Y Z
#1: 1 0.20 0.3 0.10 0.15 0.25 0.0 0.0 0.4 0.6
#2: 2 0.05 0.1 0.05 0.30 0.10 0.4 0.4 0.4 0.2
#3: 3 0.10 0.1 0.10 0.20 0.20 0.3 0.3 0.4 0.3
【讨论】:
【参考方案3】:另一个选项,连同已经贡献的工作解决方案,
将使用quasiquotation
使用 rlang
包构建表达式以计算每个中的总和
组。
library(tidyverse)
首先,定义一个辅助函数来对向量进行元素求和:
psum <- function(...) reduce(list(...), `+`)
从class_fg
中提取分组到一个列表中,然后我们可以构造
计算每组总和的表达式列表:
sum_exprs <- with(class_fg, split(class, fg)) %>%
map(~ rlang::expr(psum(!!!rlang::syms(.x))))
sum_exprs
#> $X
#> psum(F)
#>
#> $Y
#> psum(D, E)
#>
#> $Z
#> psum(A, B, C)
准备好表达式列表后,我们可以将"bang-bang-bang" (!!!
) 与mutate
一起放入数据中:
class_df %>%
mutate(!!!sum_exprs)
#> id A B C D E F X Y Z
#> 1 1 0.20 0.3 0.10 0.15 0.25 0.0 0.0 0.4 0.6
#> 2 2 0.05 0.1 0.05 0.30 0.10 0.4 0.4 0.4 0.2
#> 3 3 0.10 0.1 0.10 0.20 0.20 0.3 0.3 0.4 0.3
(我使用@MKR 在他的回答中提供的代码来创建数据)。
由reprex package (v0.2.0) 于 2018 年 5 月 22 日创建。
【讨论】:
想说声谢谢,因为将所有这些答案添加到一个小型示例库中,这样我以后可以更加灵活。【参考方案4】:处理长格式数据总是更容易。因此,使用tidyr:gather
将class_df
更改为长格式并加入class_fg
。对您的数据执行长格式分析。最后,以宽格式展开以匹配预期结果。
library(tidyverse)
class_df %>% gather(key, value, -id) %>%
inner_join(class_fg, by=c("key" = "class")) %>%
group_by(id, fg) %>%
summarise(value = sum(value)) %>%
spread(fg, value) %>%
inner_join(class_df, by="id") %>% as.data.frame()
# id X Y Z A B C D E F
# 1 1 0.0 0.4 0.6 0.20 0.3 0.10 0.15 0.25 0.0
# 2 2 0.4 0.4 0.2 0.05 0.1 0.05 0.30 0.10 0.4
# 3 3 0.3 0.4 0.3 0.10 0.1 0.10 0.20 0.20 0.3
数据:
class_fg <- read.table(text =
"class fg
A Z
B Z
C Z
D Y
E Y
F X",
header = TRUE, stringsAsFactors = FALSE)
class_df <- read.table(text =
"id A B C D E F
1 0.20 0.30 0.10 0.15 0.25 0.00
2 0.05 0.10 0.05 0.30 0.10 0.40
3 0.10 0.10 0.10 0.20 0.20 0.30",
header = TRUE, stringsAsFactors = FALSE)
【讨论】:
想说声谢谢,因为将所有这些答案添加到一个小型示例库中,这样我以后可以更加灵活。【参考方案5】:我们可以split
'class_df' by 'class',循环通过list
元素和map
,select
'class_df' 的列并得到rowSums
library(tidyverse)
class_fg %>%
split(.$fg) %>%
map_df(~ class_df %>%
select(one_of(.x$class)) %>%
rowSums) %>%
bind_cols(class_df, .)
# id A B C D E F X Y Z
#1 1 0.20 0.3 0.10 0.15 0.25 0.0 0.0 0.4 0.6
#2 2 0.05 0.1 0.05 0.30 0.10 0.4 0.4 0.4 0.2
#3 3 0.10 0.1 0.10 0.20 0.20 0.3 0.3 0.4 0.3
或者通过nest
ing 进行分组,然后通过map
ping 对list
元素进行rowSums
class_fg %>%
group_by(fg) %>%
nest %>%
mutate(out = map(data, ~ class_df %>%
select(one_of(.x$class)) %>%
rowSums)) %>%
select(-data) %>%
unnest %>%
unstack(., out ~ fg) %>%
bind_cols(class_df, .)
【讨论】:
谢谢,我有这样的感觉,用这样的单一语句是可能的。我仍在学习如何有效嵌套而不立即求助于创建长格式表。以上是关于对多组列求和的主要内容,如果未能解决你的问题,请参考以下文章