【问题标题】:Efficient way to conditionally categorize across columns in R?在 R 中跨列有条件地分类的有效方法?
【发布时间】:2020-04-17 22:39:28
【问题描述】:

我正在尝试将数据框分类。类似于您需要的维恩图,但我想以某种方式对列标题进行“grepl”采样。意思是,只要一个模式是共享的并且它满足数字阈值,它就可以被分类到所述类别中。

示例数据:

df <- data.frame(a_b_c = c(1,3,5,0,0), a_b=c(0,0,4,0,0), a_b_c_d=c(1,2,2,3,0),
                 b_d=c(0,0,3,2,3), a_c = c(1,5,1,0,0))
df

>  a_b_c a_b a_b_c_d b_d a_c    
>     1   0       1   0   1     
>     3   0       2   0   5     
>     5   4       2   3   1   
>     0   0       3   2   0   
>     0   0       0   3   0 

期望的输出

df_final <- data.frame(df, category = c("Other", "Shared c", "Shared all", "Shared d", "Appears once"))

df_final

>  a_b_c a_b a_b_c_d b_d a_c     category
>     1   0       1   0   1        Other
>     3   0       2   0   5     Shared c
>     5   4       2   3   1   Shared all
>     0   0       3   2   0     Shared d
>     0   0       0   3   0 Appears once

我认为它涉及带有 case_when() 和/或 ifelse() 语句的整洁的 verse mutate 语句,但我无法正确理解逻辑。这是一个示例测试数据集,我的实际数据有 >20 列。所以这就是为什么我想用通配符对列标题进行分类。

接受任何和所有建议。

【问题讨论】:

  • 这似乎是在 names 列上,而不是它们的值,对吗?

标签: r dplyr plyr


【解决方案1】:

我建议使用“pivot”case_when,然后“join”可以得到结果。

library(dplyr)
# library(tidyr) # pivot_longer

df <- data.frame(a_b_c = c(1,3,5,0,0), a_b=c(0,0,4,0,0), a_b_c_d=c(1,2,2,3,0),
                 b_d=c(0,0,3,2,3), a_c = c(1,5,1,0,0)) %>%
  mutate(row = row_number())

df %>%
  tidyr::pivot_longer(-row) %>%
  group_by(row) %>%
  summarize(
    category = case_when(
      all(value > 0)                     ~ "Shared all",
      sum(value > 0) == 1L               ~ "Appears once",
      all(value == 0 | grepl("c", name)) ~ "Shared c",
      all(value == 0 | grepl("d", name)) ~ "Shared d",
      TRUE                               ~ "Other"
    )
  ) %>%
  left_join(df, ., by = "row")
#   a_b_c a_b a_b_c_d b_d a_c row     category
# 1     1   0       1   0   1   1     Shared c
# 2     3   0       2   0   5   2     Shared c
# 3     5   4       2   3   1   3   Shared all
# 4     0   0       3   2   0   4     Shared d
# 5     0   0       0   3   0   5 Appears Once

我必须添加一个row 列,以便确信该类别将与原始行配对,因为否则这些行不是唯一标识的(除了完全唯一性,我不指望这一点)。我pivoted 以便我们不依赖特定的列名或恰好存在五列(这同样适用于 3 和 300,给出或采用你的逻辑规则。最后,!grepl(...) | value&gt;0 的使用是特定的反转以确保所有c-包括(和d)名称的值都大于0;它很容易扩展,但根据您的实际用例,您可能需要更强大的正则表达式(例如,单词边界)。

【讨论】:

  • 如果我需要添加一个包含“a”和排除“c”的 grepl 语句怎么办?按照您的建议扩展 grepl 语句 - 但需要使用另一个 grepl() 指定排除项(或值 == 0)。
  • 不确定您的意思,但可能类似于all( (grepl("a", name) &amp; !grepl("c", name)) | value == 0)
  • 是的,这行得通。但是,我并不完全了解如何修改反转 (!grel()) 以执行我正在处理的其他复杂“排序”。我正在继续玩这个并阅读正则表达式。我需要分配其他类别,例如“仅与 a_ 一起出现”和“仅与 a_b 出现”。
  • 想知道是否以长轴开始并有其他列作为因素会有所帮助。我确信有几种方法可以解决这个问题,但我的数据在实现这一目标的过程中提出了额外的挑战。
  • 长和宽都肯定有好处,但我认为在这种情况下,预先旋转(就像我在这里所做的那样)启用不同的看待它的方式.对于试图找出正则表达式的问题,请这样想:如果您以 all(value == 0 | ...) 开始一个正则表达式,那么您实际上是在说“只考虑那些具有非零值的”,这似乎有点不直观,直到您意识到这些值零永远不会计入反对您的其他条件。我编辑了答案以使两行适应这种方法/模式。我希望它能解决问题。
【解决方案2】:

base R中,我们可以根据逻辑创建一个数字索引,然后将该索引作为位置索引来改变值

i1 <- apply(df > 0, 1, function(x) {
      x1 <- x[x]
       c1 <- all(grepl('c', names(x1)))
       d1 <- all(grepl('d', names(x1)))
       all(x) + 2 * c1 + 4 * d1 +  8 * (length(x1) == 1)})
df$category <- c('Shared all', 'Shared c', 'Shared d',
            'Appears once', 'other')[as.integer(factor(i1))]
df$category
#[1] "Shared c"     "Shared c"     "Shared all"   "Shared d"     "Appears once"

【讨论】:

  • 不知道为什么基础 R 解决方案被低估了
猜你喜欢
  • 2017-12-18
  • 1970-01-01
  • 1970-01-01
  • 2014-06-23
  • 1970-01-01
  • 2022-11-29
  • 2018-08-10
  • 1970-01-01
  • 2015-09-13
相关资源
最近更新 更多