使用多个与列表中的值匹配的列来过滤或设置数据框

时间:2018-07-17 16:18:39

标签: r dataframe

我想使用以下方式从数据框中选择行的子集 值存储在列表中。子集数据框是一个常见的主题(例如 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")

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

4 个答案:

答案 0 :(得分:2)

更多选项,无需parse

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

答案 1 :(得分:2)

最简单的是基础R

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

我希望我可以接受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.

接着,再增加一个。以下建议的semi_join()在我的实际用例中是赢家,因为有关实际data.frame的问题打破了在这里起作用的解析解决方案-我认为这是字符变量。