如何从数据框的多个列中找到最常见的值

4
我有一个以下格式的数据框:
```html

```
S A B C D E 
1 N N N N N
2 N Y Y N N
3 Y N Y N N
4 Y N Y Y Y

我需要在哪里创建一个新的列F,该列包含多个列A、B、C、D和E中出现最多的字符?

输出应该如下所示:

 S A B C D E F
 1 N N N N N N
 2 N Y Y N N N
 3 Y N Y N N N
 4 Y N Y Y Y Y
4个回答

7
我们可以创建一个“Mode”函数,并将其应用于行。
df1$F <- apply(df1[-1], 1, Mode)
df1
#  S A B C D E F
#1 1 N N N N N N
#2 2 N Y Y N N N
#3 3 Y N Y N N N
#4 4 Y N Y Y Y Y

或者另一个选择是:
df1$F <- c('N', 'Y')[max.col(table(c(row(df1[-1])), unlist(df1[-1])), 'first')]

where

Mode <- function(x) {
 ux <- unique(x)
 ux[which.max(tabulate(match(x, ux)))]
}

或者使用tidyverse

library(tidyverse)
df1 %>% 
    mutate(F = pmap_chr(.[-1], ~ Mode(c(...))))

另一个选择是:
gather(df1, key, F, - S) %>% 
     group_by(S, F) %>% 
     summarise(n = n()) %>% 
     slice(which.max(n)) %>% 
     ungroup %>% 
     dplyr::select(F) %>% 
     bind_cols(df1, .)

或者我们可以转置数据集,对每列应用Mode函数,然后将输出作为新列绑定到原始数据集中。

t(df1[-1]) %>%
   as.data.frame %>% 
   summarise_all(Mode) %>% 
   unlist %>%
   bind_cols(df1, F = .)

或者使用 data.table 的选项

library(data.table)
setDT(df1)[,  F := names(which.max(table(unlist(.SD)))), S][]

注意:这些是通用方法,而不仅仅是针对某个特定情况的检查。


如果我们需要一种高效的方法,而不使用任何ifelse语句,我们也可以通过以下方式来实现:

df1$F <- c("Y", "N")[(rowSums(df1[-1] == "N") > 2) + 1]
df1$F
#[1] "N" "N" "N" "Y"

或者使用 Reduce
c("Y", "N")[(Reduce(`+`, lapply(df1[-1], `==`, "N")) > 2) + 1]

另一种方法是

c("Y", "N")[(str_count(do.call(paste0, df1[-1]), "N") > 2) + 1]

数据

df1 <- structure(list(S = 1:4, A = c("N", "N", "Y", "Y"), B = c("N", 
"Y", "N", "N"), C = c("N", "Y", "Y", "Y"), D = c("N", "N", "N", 
"Y"), E = c("N", "N", "N", "Y")), class = "data.frame", row.names = c(NA, 
-4L))

3

一个 dplyr 的可能性是:

df %>%
 mutate(F = ifelse(rowSums(.[2:length(.)] == "N") > 2, "N", "Y"))

  S A B C D E F
1 1 N N N N N N
2 2 N Y Y N N N
3 3 Y N Y N N N
4 4 Y N Y Y Y Y

它假设只有NY值,并且列数为5。

正如@Sotos所指出的,它可以轻松地改写成base R形式:

df$F <- ifelse(rowSums(df[2:length(df)] == "N") > 2, "N", "Y")

或者不基于列数的假设(基于 @TinglTanglBob 的方法):

df %>%
 mutate(F = ifelse(rowMeans(.[2:length(.)] == "N") > 0.5, "N", "Y"))

同基础R一样:
df$F <- ifelse(rowMeans(df[2:length(df)] == "N") > 0.5, "N", "Y")

1
+1 好主意,避免使用 apply() 函数并指定边缘为 1。如果您不仅仅是为了使用管道和 mutate 而加载 dplyr,那么它可能会更好。只需将其保留在基本的 R 中即可。此外,您还可以使 2 动态化,例如 ceiling(ncol(df) / 2) - Sotos
2
你可以使用rowMeans(...) >0.5来避免对列数做出假设。 - TinglTanglBob

2
一个稍微不同的替代方案:

最初的回答

x$F <- unlist(do.call(Map, c(function(...) names(sort(-table(c(...)), partial=1)[1]), x[,-1])))
x
#   S A B C D E F
# 1 1 N N N N N N
# 2 2 N Y Y N N N
# 3 3 Y N Y N N N
# 4 4 Y N Y Y Y Y

也许我现在只是试图编写晦涩的代码...
我意识到这可能比绝对必要的更为一般化。它可以找到最常见的“事物”,而不管行中存在多少不同的“事物”。 sort(..., partial=1) 在第一次排序后停止排序。
最初的回答:也许我正在尝试编写晦涩难懂的代码...
我发现这样做可能过于笼统了。无论行中存在多少不同的“事物”,它都能找到最常见的“事物”。 sort(..., partial=1) 在第一次排序后就停止了排序。

1

或者:

d <- read.table(text ="S A B C D E 
1 N N N N N
2 N Y Y N N
3 Y N Y N N
4 Y N Y Y Y", header = TRUE, row.names = 1, stringsAsFactors = FALSE)

d$F <- with(
  stack(data.frame(t(as.matrix(d)), stringsAsFactors = FALSE)),
  tapply(values, ind, function(x) names(sort(table(x), decreasing = TRUE)[1])))
d

#A B C D E F
#1 N N N N N N
#2 N Y Y N N N
#3 Y N Y N N N
#4 Y N Y Y Y Y

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接