【问题标题】:filtering or subsetting a dataframe using multiple columns matching values in a list使用与列表中的值匹配的多列过滤或子集数据框
【发布时间】:2018-07-17 16:18:39
【问题描述】:

我想使用从数据框中选择行的子集 存储在列表中的值。对数据框进行子集化是一个常见主题(例如 thisthis), 但在所有这些问题中,值在运行之前是已知的。我想 使用闪亮的应用程序中生成的命名列表来挑选行和 将它们显示在数据表对象中。我会使用dplyr::filter() 但我 认为同样的想法应该适用于subset()[]

library(dplyr) 
data("mtcars")

# get all 6 cylinder cars with 3 gears 
filter(mtcars, cyl == 6 & gear == 3)
#>    mpg cyl disp  hp drat    wt  qsec vs am gear carb
#> 1 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
#> 2 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

到目前为止一切都很好,但我在命名列表中有这些值。

pickthese <- list(cyl = 6, gear = 3)

我尝试了paste()parse()eval() 的变体,而这个 有效,但看起来很笨拙。

eval(
  parse(
    text = paste("filter(mtcars, ",
                 paste(paste0(names(pickthese)[1]," == ", pickthese[[1]]),
                       paste0(names(pickthese)[2]," == ", pickthese[[2]]), sep = ","),
                 ")")))
#>    mpg cyl disp  hp drat    wt  qsec vs am gear carb
#> 1 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
#> 2 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

我可以将内部 paste0() 变成一个函数并使用 *apply()purrr::map 简化一些。有更好的解决方案吗?

编辑:

r2evans 提出了一个很好的观点,即如果用户搞砸了会发生什么。我可以想到两种可能的方法——首先,用户可以选择数据框中没有的值,其次,用户可以选择数据框中没有的变量名。第二种情况不应该发生,因为应用程序中的变量名是我设置的,但我也不能幸免于搞砸!我认为解决方案应该返回具有相同列的零行数据框(在第一种情况下),或者导致错误。

# test case
lst <- list(cyl = 6.2, gear = 3, foo = "bar")

创建于 2018-07-17 由reprex package (v0.2.0)。

【问题讨论】:

    标签: r dataframe


    【解决方案1】:

    更多选项,无需parseing。

    lst <- list(cyl=6, gear=3)
    
    df1 <- as.data.frame(lst)
    mtcars %>% inner_join(df1)
    # Joining, by = c("cyl", "gear")
    #    mpg cyl disp  hp drat    wt  qsec vs am gear carb
    # 1 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
    # 2 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
    
    nlst <- paste(names(lst), lst, sep="==")
    mtcars %>% filter_(.dots=nlst)
    #    mpg cyl disp  hp drat    wt  qsec vs am gear carb
    # 1 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
    # 2 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
    

    【讨论】:

    • 非常感谢!我从你的回答中学到了很多。我非常喜欢nlst 的构造,但不接受这个答案 b/c filter_() is deprecated.
    • 您的来电!我发现在几乎所有情况下使用parse 用户生成的代码都是缺少功能的一个脆弱的侧步。很高兴我能帮上忙。
    • inner_join@atiretoo 出了什么问题?它更干净,如果你有一些边缘情况,也许你可以分享它?
    • 我实际上忘记了inner_join()——我正要返回并针对可能的不匹配问题测试所有答案,看看这是否能完全区分它们。
    【解决方案2】:

    最简单的是baseR

    l <- list(cyl = 6, gear = 3)
    merge(mtcars,l)
    #   cyl gear  mpg disp  hp drat    wt  qsec vs am carb
    # 1   6    3 18.1  225 105 2.76 3.460 20.22  1  0    1
    # 2   6    3 21.4  258 110 3.08 3.215 19.44  1  0    1
    

    使用dplyr,我们首先需要将您的列表转换为tibble/data.frame,然后使用right_joininner_join 将其加入表格,我们最终得到@r2evans 的解决方案。

    如果我们真的想过滤,我们可以使用 filter_atreduce

    library(tidyverse) # for dplyr and purrr
    reduce(imap(l,~setNames(.x,.y)),
      ~filter_at(.x, names(.y),all_vars(.== .y)),
      .init=mtcars)
    #    mpg cyl disp  hp drat    wt  qsec vs am gear carb
    # 1 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
    # 2 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
    

    【讨论】:

    • 是的,我来到了 inner_join() / merge() 位置。 merge() 从列表上的 as.data.frame() 开始,所以差别不大!
    • 嗯,只是缩短了 3 倍,而且没有额外的包装;)
    【解决方案3】:

    一种选择是创建一个可以解析的表达式

    expr <- do.call(paste, c(stack(pickthese)[2:1], sep="==", collapse=";"))
    

    或使用tidyverse创建

    expr <- enframe(pickthese) %>% 
                    unnest %>%
                    reduce(paste, sep="==", collapse=";")
    
    
    mtcars %>% 
        filter(!!! rlang::parse_exprs(expr))
    #    mpg cyl disp  hp drat    wt  qsec vs am gear carb
    #1 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
    #2 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
    

    【讨论】:

      【解决方案4】:

      我希望我能接受 3 个答案!我在这里学到了很多东西。我将结合上述每个答案的部分,以非常清晰的方式得到我想要的。

      nlst <- paste(names(pickthese), pickthese, sep="==") # from r2evans
      
      mtcars %>% filter(!!! rlang::parse_exprs(nlst)) # from akrun
      
      #>    mpg cyl disp  hp drat    wt  qsec vs am gear carb
      #> 1 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
      #> 2 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
      

      尽管担心解析,并且因为merge()inner_join() 方法存在问题,我仍将坚持这一点。如果在数据中未找到该值,则所有解决方案都按预期工作,返回零行数据帧。

      如果变量名不是数据框的一部分,它们在第二个问题上的表现确实不同。使用merge()inner_join() 将错误的变量名称添加为新列,即使在零行数据框中也是如此。我刚刚注意到merge() 的另一件事——没有保留结果中的行或列顺序。有时这无关紧要,但在这种情况下确实如此,因为如果我选择不同的变量,结果会显示不同,这至少会让人讨厌。

      lst <- list(cyl = 6, gear = 3, foo = "bar") # 2nd problem
      
      merge(mtcars, lst) # two rows, but silently adds column foo
      #>   cyl gear  mpg disp  hp drat    wt  qsec vs am carb foo
      #> 1   6    3 18.1  225 105 2.76 3.460 20.22  1  0    1 bar
      #> 2   6    3 21.4  258 110 3.08 3.215 19.44  1  0    1 bar
      
      inner_join(mtcars, as.data.frame(lst)) # two rows and silently adds column foo
      #> Joining, by = c("cyl", "gear")
      #>    mpg cyl disp  hp drat    wt  qsec vs am gear carb foo
      #> 1 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1 bar
      #> 2 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1 bar
      
      nlst <- paste(names(lst), lst, sep="==")
      mtcars %>% filter(!!! rlang::parse_exprs(nlst)) # error -- tells me I screwed up the programming.
      #> Error in filter_impl(.data, quo): Evaluation error: object 'foo' not found.
      

      Annnd,又多了一个。 semi_join() 如下所示是我实际用例中的赢家,因为有关实际 data.frame 的某些内容破坏了此处有效的解析解决方案-我认为它是字符变量。

      【讨论】:

      • 我理解您的意思,仅供参考,您可以使用semi_join(mtcars, as.data.frame(lst)) ,附加变量不会出现在结果中,但不会触发错误。一种可能是添加stopifnot(all(names(lst) %in% names(mtcars)),请注意,如果 foo 实际存在,则错误变为 Error: x` 必须是字符串或 R 连接`,这不太可读
      猜你喜欢
      • 2019-04-04
      • 1970-01-01
      • 1970-01-01
      • 2021-05-31
      • 1970-01-01
      • 2022-11-22
      • 1970-01-01
      • 1970-01-01
      • 2020-08-03
      相关资源
      最近更新 更多