【问题标题】:Using functions in dplyr incorporating values in other rows在 dplyr 中使用函数合并其他行中的值
【发布时间】:2014-11-19 02:44:37
【问题描述】:

为笨拙的代码提前道歉。 我有一个类似于以下的数据框:

df <- data.frame(c(rep_len(1,5), 2, 2), c("A", "A", "B", "B", "C", "C", "C"))
names(df) <- c("id", "consequence")

  id consequence
1  1           A
2  1           A
3  1           B
4  1           B
5  1           C
6  2           C
7  2           C

我想执行以下过滤操作:

如果按 id 分组包含结果 A 或 B,则保留这些行,并删除结果为 C 的行。如果组仅包含 C 或单行,则保留那些/那行/行。

我曾尝试在 dplyr 中使用自定义函数执行此操作,但存在所有行都被过滤的问题,从而消除了所有后果 C:

# filtering function:
consequence_select <- function(x) {
  if(n_distinct(x$consequence) > 1) {
  if(any(unique(x$consequence) %in% c("A", "B"))) {
  x %>%
    filter(consequence %in% c("A", "B"))} else {return(x)}
     } else {return(x)}
}


df %>%
group_by(id) %>%
consequence_select

  id consequence
1  1           A
2  1           A
3  1           B
4  1           B

我能够用 plyr 正确地做到这一点:

ddply(df, .(id), consequence_select)

  id consequence
1  1           A
2  1           A
3  1           B
4  1           B
5  2           C
6  2           C

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    使用dplyr,您需要将函数包装在do

    df %>%
      group_by(id) %>%
      do(consequence_select(.))
    

    . 是一个“代词”,指代数据框df

    【讨论】:

    • 谢谢你,正是我需要的。
    【解决方案2】:

    您可以通过仅在 filter 参数内而不是在 do 内应用代码来优化您的代码,因为 filter 是此类任务的专用 dplyr 函数。我创建了两个函数并用现有答案对它们进行了基准测试。您要使用哪个函数取决于您的要求 - 对于示例数据,它们都产生相同的结果。我还为基准测试创建了一个稍大的样本数据,如下所示。

    # sample data
    df <- data.frame(id = sample(100, 1000, replace = T), 
                     consequence = sample(LETTERS[1:3], 1000, replace = TRUE, prob = c(0.2, 0.2, 0.6)))
    
    # the existing custom function
    consequence_select <- function(x) {
      if(n_distinct(x$consequence) > 1) {
        if(any(unique(x$consequence) %in% c("A", "B"))) {
          x %>%
            filter(consequence %in% c("A", "B"))} else {return(x)}
      } else {return(x)}
    }
    
    # eipi's answer
    f1 <- function() {
      df %>%
      group_by(id) %>%
      do(consequence_select(.)) }
    
    # jazzuro's answer
    f2 <- function() {
      df %>%
      group_by(id) %>%
      do(if(all(.$consequence == "C")) {.} else{.[-which(.$consequence == "C"), ]}) }
    
    # my answer 1
    f3a <- function() {
      df %>% 
        group_by(id) %>% 
        filter((consequence != "C" & n_distinct(consequence) > 1L) | all(consequence == "C") )
    }
    
    # my answer 2
    f3b <- function() {
      df %>% 
        group_by(id) %>% 
        filter((consequence %in% c("A", "B") & n_distinct(consequence) > 1L) | all(consequence == "C"))
    }
    
    library(microbenchmark)
    
    microbenchmark(f1(), f2(), f3a(), f3b(), unit = "relative")
    
    Unit: relative
     expr       min        lq    median        uq       max neval
    f1()  11.243524 11.092915 10.956129 10.717519  8.859949   100
    f2()   6.603549  6.663674  6.653424  6.566012 10.956784   100
    f3a()  1.279952  1.294679  1.291719  1.294606  1.165322   100
    f3b()  1.000000  1.000000  1.000000  1.000000  1.000000   100
    
    all.equal(f1(), f3a())
    #[1] TRUE
    all.equal(f1(), f3b())
    #[1] TRUE
    

    如您所见,数据大小的轻微增加已经揭示了函数之间的 10 倍以上的速度差异。

    【讨论】:

    • 很高兴看到这个。谢谢你。我预计do 会更慢。
    • 我现在才开始思考。但是,最好避免do 并像这样创建自己的函数吗?前几天我想知道这个条件过滤问题。如果你能做 filter(if(...){} else{}),那就太好了。但据我所知,这是不可能的。然后,我的回答是做。看到你的建议,写自己的函数更方便他/她的数据处理。
    • @jazzurro - 我不太了解你的 cmets。关于第一个:你不认为慢 6-10 倍已经是一个显着的差异吗?第二个:“看到你的建议,编写自己的函数更有助于他/她的数据处理。”我不跟。 “如果你可以做过滤器(if(...){} else{})”:在过滤器中你只需要 TRUE / FALSE,所以你真的只需要进行 if(.. .) 其余的都不需要。
    • 是的,6-10 次就是如此巨大的差异。至于第二条评论,我有一个正在研究的虚拟示例。你能帮我吗? do(if(length(.$value) &gt; 1 &amp; is.na(.$value[1])) {.[-1,]} else{.}) 是我拥有的。您使用group_by 并尝试从每个组中删除第一行,如果每个组有超过 2 行并且第一行有 NA。您将如何使用filter 编写此do 行?完全没有计算机科学背景,我希望我的文章对你有点意义。
    • @jazzurro,根据你的口头描述,我会使用:df %&gt;% group_by(var1) %&gt;% filter((!is.na(var2) &amp; n() != 1) | row_number() &gt; 1L) - 你可以检查这是否正确,因为这里没有可重复的例子..(顺便说一句,我没有计算机科学背景)。这将更适合作为一个单独的问题..
    【解决方案3】:

    您可以使用do 来完成您的功能。 foo 是您的数据。

    foo %>%
        group_by(id) %>%
        do(if(all(.$consequence == "C")) {.} else{.[-which(.$consequence == "C"), ]})
    
    #  id consequence
    #1  1           A
    #2  1           A
    #3  1           B
    #4  1           B
    #5  2           C
    #6  2           C
    

    【讨论】:

    • 谢谢,这也是对条件的一个很好的简化。
    • @gatsky 很高兴,伙计。 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-20
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 2018-01-27
    • 1970-01-01
    • 2020-05-10
    相关资源
    最近更新 更多