计算分组数据框中多列因素的出现次数
Count occurrences of factors across multiple columns in grouped dataframe
我有以下数据框,想按 grp
列分组以查看每个列值在每个组中出现的数量。
> data.frame(grp = unlist(strsplit("aabbccca", "")), col1=unlist(strsplit("ABAABBAB", "")), col2=unlist(strsplit("BBCCCCDD", "")))
grp col1 col2
1 a A B
2 a B B
3 b A C
4 b A C
5 c B C
6 c B C
7 c A D
8 a B D
想要的结果:
grp col1A col1B col2B col2C col2D
1 a 1 2 2 0 1
2 b 2 0 0 2 0
3 c 1 2 0 2 1
如果我只查看 grp
和 col1
列,使用 table()
很容易解决这个问题,当只有 2 列时,我可以合并 table(df[c('grp', 'col1')])
与 table(df[c('grp', 'col2')])
。但是,随着因子列数量的增加,这会变得非常麻烦,如果 col1
和 col2
.
之间存在共享值,则会出现问题
请注意,dplyr 的计数不起作用,因为它会寻找 col1 和 col2 的独特组合。
我试过使用 tidyr 融化和传播数据框,但没有成功
> pivot_longer(df, c(col1, col2), names_to= "key", values_to = "val") %>% pivot_wider("grp", names_from = c("key", "val"), values_from = 1, values_fn = sum)
Error in `stop_subscript()`:
! Can't subset columns that don't exist.
x Column `grp` doesn't exist.
我可以找到很多适用于我有 1 个组列和 1 个值列的情况的解决方案,但我不知道如何将它们推广到更多列。
您在 melt
和 spread
方面走在了正确的轨道上。这是一个 tidyverse 解决方案。我首先使用 pivot_longer
泛化到任意数量的列,然后使用 pivot_wider
到 return 到所需的输出格式。输出数据框中列的顺序取决于数据。如果这是一个问题,只需将 select
附加到管道的末尾即可获得所需的顺序。 (或者使用@DarrenTsai 的回答中的 names_sort
。)
library(tidyverse)
d %>%
pivot_longer(
starts_with("col"),
names_to="Column",
values_to="Value"
) %>%
group_by(grp, Column, Value) %>%
summarise(N=n(), .groups="drop") %>%
group_by(grp) %>%
pivot_wider(
id_cols=grp,
values_from=N,
names_from=c(Column, Value),
names_sep="",
values_fill=0
) %>%
ungroup()
# A tibble: 3 × 6
grp col1A col1B col2B col2D col2C
<chr> <int> <int> <int> <int> <int>
1 a 1 2 2 1 0
2 b 2 0 0 0 2
3 c 1 2 0 1 2
可以把col1
和col2
叠在一起,统计每个组合的个数,然后把table变宽。
library(dplyr)
library(tidyr)
df %>%
pivot_longer(col1:col2) %>%
count(grp, name, value) %>%
pivot_wider(grp, names_from = c(name, value), names_sort = TRUE,
values_from = n, values_fill = 0)
# A tibble: 3 x 6
grp col1_A col1_B col2_B col2_C col2_D
<chr> <int> <int> <int> <int> <int>
1 a 1 2 2 0 1
2 b 2 0 0 2 0
3 c 1 2 0 2 1
一个base
解决方案(感谢@GKi提炼代码):
table(cbind(df["grp"], col=do.call(paste0, stack(df[-1])[2:1])))
col
grp col1A col1B col2B col2C col2D
a 1 2 2 0 1
b 2 0 0 2 0
c 1 2 0 2 1
使用 reshape2
包中的 recast
:
reshape2::recast(df, grp~variable+value,id.var = 'grp', fun = length)
grp col1_A col1_B col2_B col2_C col2_D
1 a 1 2 2 0 1
2 b 2 0 0 2 0
3 c 1 2 0 2 1
在基础 R 中你可以这样做:
with(df, cbind(table(grp, paste0('col1_', col1)), table(grp, paste0('col2_', col2))))
col1_A col1_B col2_B col2_C col2_D
a 1 2 2 0 1
b 2 0 0 2 0
c 1 2 0 2 1
如果您有很多列,请考虑这样做:
do.call(cbind, Map(function(x, y) table(df$grp, paste(x,y, sep = '_')),
names(df)[-1], df[,-1]))
col1_A col1_B col2_B col2_C col2_D
a 1 2 2 0 1
b 2 0 0 2 0
c 1 2 0 2 1
然后您可以将其转换为数据框
在data.table
中,我们可以像下面这样使用dcast
+ melt
dcast(
melt(setDT(df), id.vars = "grp")[
, value := paste(variable, value, sep = "_")
], grp ~ value
)
生产
grp col1_A col1_B col2_B col2_C col2_D
1: a 1 2 2 0 1
2: b 2 0 0 2 0
3: c 1 2 0 2 1
另一种可能的解决方案,基于 tidyr::pivot_longer
后跟 tidyr::pivot_wider
并使用 values_fn = length
:
library(tidyverse)
df %>%
pivot_longer(c(col1, col2)) %>%
mutate(name = str_c(name, value)) %>%
pivot_wider(grp, values_fn = length, values_fill = 0, names_sort = T)
#> # A tibble: 3 x 6
#> grp col1A col1B col2B col2C col2D
#> <chr> <int> <int> <int> <int> <int>
#> 1 a 1 2 2 0 1
#> 2 b 2 0 0 2 0
#> 3 c 1 2 0 2 1
我有以下数据框,想按 grp
列分组以查看每个列值在每个组中出现的数量。
> data.frame(grp = unlist(strsplit("aabbccca", "")), col1=unlist(strsplit("ABAABBAB", "")), col2=unlist(strsplit("BBCCCCDD", "")))
grp col1 col2
1 a A B
2 a B B
3 b A C
4 b A C
5 c B C
6 c B C
7 c A D
8 a B D
想要的结果:
grp col1A col1B col2B col2C col2D
1 a 1 2 2 0 1
2 b 2 0 0 2 0
3 c 1 2 0 2 1
如果我只查看 grp
和 col1
列,使用 table()
很容易解决这个问题,当只有 2 列时,我可以合并 table(df[c('grp', 'col1')])
与 table(df[c('grp', 'col2')])
。但是,随着因子列数量的增加,这会变得非常麻烦,如果 col1
和 col2
.
请注意,dplyr 的计数不起作用,因为它会寻找 col1 和 col2 的独特组合。
我试过使用 tidyr 融化和传播数据框,但没有成功
> pivot_longer(df, c(col1, col2), names_to= "key", values_to = "val") %>% pivot_wider("grp", names_from = c("key", "val"), values_from = 1, values_fn = sum)
Error in `stop_subscript()`:
! Can't subset columns that don't exist.
x Column `grp` doesn't exist.
我可以找到很多适用于我有 1 个组列和 1 个值列的情况的解决方案,但我不知道如何将它们推广到更多列。
您在 melt
和 spread
方面走在了正确的轨道上。这是一个 tidyverse 解决方案。我首先使用 pivot_longer
泛化到任意数量的列,然后使用 pivot_wider
到 return 到所需的输出格式。输出数据框中列的顺序取决于数据。如果这是一个问题,只需将 select
附加到管道的末尾即可获得所需的顺序。 (或者使用@DarrenTsai 的回答中的 names_sort
。)
library(tidyverse)
d %>%
pivot_longer(
starts_with("col"),
names_to="Column",
values_to="Value"
) %>%
group_by(grp, Column, Value) %>%
summarise(N=n(), .groups="drop") %>%
group_by(grp) %>%
pivot_wider(
id_cols=grp,
values_from=N,
names_from=c(Column, Value),
names_sep="",
values_fill=0
) %>%
ungroup()
# A tibble: 3 × 6
grp col1A col1B col2B col2D col2C
<chr> <int> <int> <int> <int> <int>
1 a 1 2 2 1 0
2 b 2 0 0 0 2
3 c 1 2 0 1 2
可以把col1
和col2
叠在一起,统计每个组合的个数,然后把table变宽。
library(dplyr)
library(tidyr)
df %>%
pivot_longer(col1:col2) %>%
count(grp, name, value) %>%
pivot_wider(grp, names_from = c(name, value), names_sort = TRUE,
values_from = n, values_fill = 0)
# A tibble: 3 x 6
grp col1_A col1_B col2_B col2_C col2_D
<chr> <int> <int> <int> <int> <int>
1 a 1 2 2 0 1
2 b 2 0 0 2 0
3 c 1 2 0 2 1
一个base
解决方案(感谢@GKi提炼代码):
table(cbind(df["grp"], col=do.call(paste0, stack(df[-1])[2:1])))
col
grp col1A col1B col2B col2C col2D
a 1 2 2 0 1
b 2 0 0 2 0
c 1 2 0 2 1
使用 reshape2
包中的 recast
:
reshape2::recast(df, grp~variable+value,id.var = 'grp', fun = length)
grp col1_A col1_B col2_B col2_C col2_D
1 a 1 2 2 0 1
2 b 2 0 0 2 0
3 c 1 2 0 2 1
在基础 R 中你可以这样做:
with(df, cbind(table(grp, paste0('col1_', col1)), table(grp, paste0('col2_', col2))))
col1_A col1_B col2_B col2_C col2_D
a 1 2 2 0 1
b 2 0 0 2 0
c 1 2 0 2 1
如果您有很多列,请考虑这样做:
do.call(cbind, Map(function(x, y) table(df$grp, paste(x,y, sep = '_')),
names(df)[-1], df[,-1]))
col1_A col1_B col2_B col2_C col2_D
a 1 2 2 0 1
b 2 0 0 2 0
c 1 2 0 2 1
然后您可以将其转换为数据框
在data.table
中,我们可以像下面这样使用dcast
+ melt
dcast(
melt(setDT(df), id.vars = "grp")[
, value := paste(variable, value, sep = "_")
], grp ~ value
)
生产
grp col1_A col1_B col2_B col2_C col2_D
1: a 1 2 2 0 1
2: b 2 0 0 2 0
3: c 1 2 0 2 1
另一种可能的解决方案,基于 tidyr::pivot_longer
后跟 tidyr::pivot_wider
并使用 values_fn = length
:
library(tidyverse)
df %>%
pivot_longer(c(col1, col2)) %>%
mutate(name = str_c(name, value)) %>%
pivot_wider(grp, values_fn = length, values_fill = 0, names_sort = T)
#> # A tibble: 3 x 6
#> grp col1A col1B col2B col2C col2D
#> <chr> <int> <int> <int> <int> <int>
#> 1 a 1 2 2 0 1
#> 2 b 2 0 0 2 0
#> 3 c 1 2 0 2 1