【问题标题】:Filter rows in dplyr chain if a set of rows doesn't contain a specific word如果一组行不包含特定单词,则过滤 dplyr 链中的行
【发布时间】:2019-09-03 08:38:50
【问题描述】:

假设我有以下数据集:

df <- read.table(header=TRUE, text="name value
stranger_things_mc Stranger_Land
stranger_things_confidence 100
stranger_things_importance 1
stranger_things_answer Stranger_Things
immigrant_crime_number 140
immigrant_crime_confidence 100
immigrant_crime_importance 3
immigrant_crime_answer 50
dog_things_mc Stranger_Land
dog_things_confidence 100
dog_things_importance 1
dog_things_answer Stranger_Things
fighting_stats_number 140
fighting_stats_confidence 100
fighting_stats_answer 50")

每第四行应该包含三个后缀(_confidence、_importance、_answer),但有时不包含(如上面的“fighting_stats”)。一行有时有 (_mc) 后缀,有时它会显示 (_number 或 _slider)。

我想过滤掉任何包含(_number 或 _slider)以及与该 _number 或 _slider 列关联的三行的行。因此,在上面的示例中,结果输出将是:

df <- read.table(header=TRUE, text="name value
stranger_things_mc Stranger_Land
stranger_things_confidence 100
stranger_things_importance 1
stranger_things_answer Stranger_Things
dog_things_mc Stranger_Land
dog_things_confidence 100
dog_things_importance 1
dog_things_answer Stranger_Things")

我可以像这样过滤掉特定的列:

final_results <- df %>% 
  filter(!str_detect(name, "_number") & !str_detect(name, "_slider"))

但无法弄清楚如何删除所有关联的列。一般算法应该是:

首先,在名称列中找到带有“_number”或“_slider”的行,然后抓取它之前的文本。在上面的示例中,这将是“fighting_stats”和“immigrant_crime”。然后,删除任何包含该文本的行。

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    我们根据每第四行是一个新块 (gl) 的条件创建一个分组列,然后将 filter 排除在 'name' 的 first 元素不是 _number_slider,然后是 ungroup 并删除创建的临时“grp”列

    library(dplyr)
    df %>% 
        group_by(grp = as.integer(gl(n(), 4, n()))) %>% 
        filter(!str_detect(first(name), "_(number|slider)")) %>%
        ungroup %>%
        select(-grp)
    

    更新

    基于来自 OP 的 cmets,即块由它们的公共前缀确定,然后提取第一个 word,将其用作分组变量并像以前一样执行 filter

    library(stringr)
    df %>%
      group_by(grp = word(name, 1, sep="_")) %>% 
      filter(!str_detect(first(name), "_(number|slider)"))
    

    ungroup 部分与之前相同

    如果有重复的前缀,即不相邻的前缀并且需要被视为单独的块,则使用 data.table 中的 rleid 创建分组变量

    df %>%
      group_by(grp = rleid(word(name, 1, sep="_"))) %>%
      filter(!str_detect(first(name), "_(number|slider)"))
    

    【讨论】:

    • 非常聪明。谢谢!一个问题是,不能保证每四行都是“链接的”。例如,有时一个分组会丢失其关联的“_importance”行。在不假设所有四行始终存在的情况下,是否有另一种解决方法?
    • @Parseltongue 你如何确定一个区块?
    • 首先,在名称列中找到带有“_number”或“_slider”的行,然后抓取它前面的文本。在上面的示例中,这将是“fighting_stats”和“immigrant_crime”。然后,删除任何包含该文本的行。
    • @Parseltongue 在示例中,给出的方式不同。 _number 是 'name' 的第 5 行,第 4、3 和 2 行,它不是 fighting_stats
    • 令人惊讶的是,第二个工作......虽然我不知道如何。
    【解决方案2】:

    我将如何解决这个问题:

    groups <- df %>% 
      mutate(grp = str_extract(name, '.*(?=_confidence|_importance|_answer|_mc|_number|_slider)'),
             sfx = str_extract(name, '(_confidence|_importance|_answer|_mc|_number|_slider)')) %>% 
      group_by(grp) %>% 
      summarize(confidence = '_confidence' %in% sfx,
                importance = '_importance' %in% sfx,
                answer = '_answer' %in% sfx,
                mc = '_mc' %in% sfx,
                number = '_number' %in% sfx,
                slider = '_slider' %in% sfx) %>% 
      ungroup() %>% 
      gather(sfx, contains, -grp) %>% 
      filter(contains == TRUE) %>% 
      select(-contains)
    
    
    df %>% 
      mutate(grp = str_extract(name, '.*(?=_confidence|_importance|_answer|_mc|_number|_slider)')) %>% 
      anti_join(groups %>% 
                   filter(sfx == 'number') %>%
                   select(grp))
    

    在高层次上,我正在创建一个包含字符串 name 的词干和后缀的中间数据框,并使用词干创建一个组,并确定每个组中包含哪些后缀集列表的茎。然后,我们不是在原始数据帧上使用filter,而是在中间数据帧上使用filter,然后在原始数据帧上使用anti_join

    让我们更详细地介绍一下:

     mutate(grp = str_extract(name, '.*(?=_confidence|_importance|_answer|_mc|_number|_slider)'),
             sfx = str_extract(name, '(_confidence|_importance|_answer|_mc|_number|_slider)'))
    

    这部分是我们如何使用正则表达式将name 列拆分为其组成部分。

    group_by(grp) %>% 
      summarize(confidence = '_confidence' %in% sfx,
                importance = '_importance' %in% sfx,
                answer = '_answer' %in% sfx,
                mc = '_mc' %in% sfx,
                number = '_number' %in% sfx,
                slider = '_slider' %in% sfx) %>% 
      ungroup()
    

    在这里,我们按“词干”(我将其命名为 grp)进行分组,然后查找每个后缀。这部分有点不稳定,如果您的数据中有更多组,则需要扩展。

    gather(sfx, contains, -grp) %>% 
      filter(contains == TRUE) %>% 
      select(-contains)
    

    在这里,我们将数据转换为“长”样式的数据框,并且只保留每个组中实际包含的后缀。

    这样就完成了中间数据帧。

    df %>% 
      mutate(grp = str_extract(name, '.*(?=_confidence|_importance|_answer|_mc|_number|_slider)'))
    

    我们首先必须在原始数据框上创建grp 列,以使anti_join 工作。

    anti_join(groups %>% 
                   filter(sfx == 'number') %>%
                   select(grp))
    

    最后,我们anti_join将中间数据帧的过滤版本转换为原始数据帧。我相信这会达到你想要的效果。

    希望有帮助!

    【讨论】:

    • 我想接受这个,因为它很聪明,显然需要做很多工作,但 akrun 的解决方案要高效得多。谢谢你写这篇文章——我学到了很多。
    • 在手动完成这个过程中,这是一个超级智能的策略,它足够通用,可以在很多情况下使用。再次感谢您写下来
    猜你喜欢
    • 2017-11-13
    • 1970-01-01
    • 1970-01-01
    • 2021-01-10
    • 2022-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多