【问题标题】:Return most frequent string by group in base R在基本 R 中按组返回最频繁的字符串
【发布时间】:2023-03-26 03:02:01
【问题描述】:

我有一个数据集,我想为a 的每个唯一条目返回列b 中最频繁的条目,其中两列都是字符向量。如果b 中的两个条目对于a 中的唯一条目同样频繁,我想在单独的列中返回b 的两个条目(下面的所需输出)。

这类似于here 提出的问题,但是该问题的答案都使用 tidyverse。我厌恶 tidyverse,因为 tidyverse 对象会破坏我项目中的其他东西。寻找一个基本的 R 解决方案(并希望避免谈论 tidyverse 的优点)。

我的数据如下所示:

a <- as.character(c(rep(1:3,4)))
b <- c("A","A","A",
       "B","B","B",
       "A","B","A",
       "A","B","B")
df <- data.frame(a,b)

 a b
 1 A
 2 A
 3 A
 1 B
 2 B
 3 B
 1 A
 2 B
 3 A
 1 A
 2 B
 3 B

想要的输出:

 group match_1 match_2
     1       A    <NA>
     2       B    <NA>
     3       A       B

【问题讨论】:

  • “该问题的答案都使用 tidyverse”:bybase。另请参阅Find most frequent combination of values in a data.frame
  • @Henrik 我熟悉另一个问题中的by 解决方案。但是,即使在该答案中,响应者也承认要使by 解决方案的输出看起来像所需的输出一样具有挑战性。如果您可以将该输出修改为看起来像上面所需的输出,那将很有帮助。
  • b 列中是否总是只有两个不同的值?否则“b 中的两个条目同样频繁”将是模棱两可的......
  • @RYoda 道歉,你能建议重新措辞吗?我真的只想考虑a 列中的条目3b 列中具有相同数量的AB 条目的情况。

标签: r


【解决方案1】:

docendo discimus's answer继续:

library(dplyr)
# library(tidyr)
df %>%
  count(a, b) %>%
  group_by(a) %>%
  filter(n == max(n)) %>%
  mutate(r = row_number()) %>%
  tidyr::spread(r, b) %>%
  select(-n)
# # A tibble: 3 x 3
# # Groups:   a [3]
#   a     `1`   `2`  
#   <fct> <fct> <fct>
# 1 1     A     <NA> 
# 2 2     B     <NA> 
# 3 3     A     B    

然后你只需要重命名列。

基础 R 变体:

reshape(do.call(rbind.data.frame, by(df, df$a, function(x) {
  tb <- table(x$b)
  tb <- tb[ tb == max(tb) ]
  data.frame(a = x$a[1], b = names(tb), r = seq_along(tb))
})), timevar = "r", idvar = "a", direction = "wide")
#     a b.1  b.2
# 1   1   A <NA>
# 2   2   B <NA>
# 3.1 3   A    B

我会分解它,因为不是所有的都可能是直观的:

by 函数返回一个list(经过特殊格式化,但仍然只是一个列表)。如果我们查看a 的单个实例,让我们探索会发生什么。我会跳到a == "3",因为那是有重复的:

by(df, df$a, function(x) { browser(); 1; })
# Called from: FUN(data[x, , drop = FALSE], ...)
# Browse[1]> 
debug at #1: [1] 1
# Browse[2]> 
Called from: FUN(data[x, , drop = FALSE], ...)
# Browse[1]> 
debug at #1: [1] 1
# Browse[2]> 
Called from: FUN(data[x, , drop = FALSE], ...)
# Browse[1]> 
debug at #1: [1] 1
# Browse[2]> 
x
#    a b
# 3  3 A
# 6  3 B
# 9  3 A
# 12 3 B
# Browse[2]> 
( tb <- table(x$b) )
# A B 
# 2 2 

好的,所以我们现在有了每个b 的计数。意识到这里可能很容易有更多,比如说:

# A B C
# 2 2 1

所以我将把这个命名向量减少为那些具有最高值的向量:

# Browse[2]> 
( tb <- tb[ tb == max(tb) ] ) # no change here, but had there been a third value in 'b' ...
# A B 
# 2 2 

最后,我们希望by 捕获data.frame(我们稍后可以合并)。我们保证a 是一个可能重复的值,所以a[1];我们确保names(tb) 具有所有“有趣”的值,并且rreshape 的助手,稍后:

# Browse[2]> 
data.frame(a = x$a[1], b = names(tb), r = seq_along(tb))
#   a b r
# 1 3 A 1
# 2 3 B 2

既然我们在内部进行了探索,让我们总结一下。

by(df, df$a, function(x) {
   tb <- table(x$b)
   tb <- tb[ tb == max(tb) ]
   data.frame(a = x$a[1], b = names(tb), r = seq_along(tb))
})
# df$a: 1
#   a b r
# 1 1 A 1
# ------------------------------------------------------------ 
# df$a: 2
#   a b r
# 1 2 B 1
# ------------------------------------------------------------ 
# df$a: 3
#   a b r
# 1 3 A 1
# 2 3 B 2

这看起来很别扭,但如果您深入了解(使用dput),您会发现它只是重新分类的list。我们现在可以将它们组合成一个框架:

do.call(rbind.data.frame, by(df, df$a, function(x) {
  tb <- table(x$b)
  tb <- tb[ tb == max(tb) ]
  data.frame(a = x$a[1], b = names(tb), r = seq_along(tb))
}))
#     a b r
# 1   1 A 1
# 2   2 B 1
# 3.1 3 A 1
# 3.2 3 B 2

顺便说一句:对于data.framerbind.data.frame,这些默认情况下都会为您提供factors。如果你不想要它们,那么:

do.call(rbind.data.frame, c(by(df, df$a, function(x) {
  tb <- table(x$b)
  tb <- tb[ tb == max(tb) ]
  data.frame(a = x$a[1], b = names(tb), r = seq_along(tb),
             stringsAsFactors = FALSE)
}), stringsAsFactors=FALSE))
#     a b r
# 1   1 A 1
# 2   2 B 1
# 3.1 3 A 1
# 3.2 3 B 2

然后是重塑。我承认这是其中最脆弱的部分(至少对我而言)。我不是reshape 用户,我倾向于tidyr::spreaddata.table::dcast,但这是base-R 并且现在可以使用。 reshape 的使用本身就是一个教程,所以我不会在这里详细介绍。有许多尝试提供对用户更友好的重塑工具(reshape2tidyrdata.table 都在脑海中浮现,但不太可能是唯一的)。

【讨论】:

  • 感谢您,但我特别希望避免使用 tidyverse 解决方案。
  • 另外,当我运行上面的代码时,它会抛出这个错误:Error in UseMethod("groups") : no applicable method for 'groups' applied to an object of class "character"
  • as.data.framedata.frametibble 之间通常有两个区别。 (1) tibbles 不保留行名,如 mtcarstbl_df(mtcars) 所示。 (2) class(mtcars)"data.frame",而 class(tbl_df(mtcars))c("tbl_df","tbl","data.frame")所有其他差异在打印到控制台时处理。所以如果你的数据没有行名,唯一的区别就是在类中增加了tbl_dftbl
  • 如果将tableseq_along 移到by 之外,则不必为每个组运行它们。 d &lt;- as.data.frame(table(df)); d2 &lt;- do.call(rbind, by(d, d$a, function(x){ data.frame(a = x$a[1], nm = x$b[x$Freq == max(x$Freq)]) })); d2$time &lt;- ave(d2$a, d2$a, FUN = seq_along); reshape(d2, idvar = "a", direction = "wide")
  • 如果速度是一个问题,我们可能会这样做是data.table(它不会“破坏”东西,这是OP反对tidyverse的论点);)+1很好,彻底的回答。
【解决方案2】:

另一个base 替代方案。

创建列联表并转换为数据框:as.data.frame(table(df))

使用ave 按组选择具有最大值的行。

使用ave 创建一个“时间”变量以“区分来自同一组的多个记录”(请参阅​​?reshape)。

reshape相关变量要宽。

d <- as.data.frame(table(df))
d2 <- d[d$Freq == ave(d$Freq, d$a, FUN = max), ]
d2$time <- ave(d2$a, d2$a, FUN = seq_along)
reshape(d2[ , c("a", "b", "time")], idvar = "a", direction = "wide")

#   a b.1  b.2
# 1 1   A <NA>
# 3 3   A    B
# 5 2   B <NA>

如果需要,请按“a”排序。

【讨论】:

    【解决方案3】:

    我们可以在base R这样做

    tbl <- table(df)
    ifelse(tbl[,1] == tbl[,2], toString(colnames(tbl)), colnames(tbl)[max.col(tbl)])
    

    【讨论】:

      猜你喜欢
      • 2015-07-07
      • 1970-01-01
      • 1970-01-01
      • 2021-03-12
      • 2010-11-27
      • 2015-07-19
      • 2013-10-28
      • 2018-11-18
      • 2021-01-30
      相关资源
      最近更新 更多