我想使用以下方式从数据框中选择行的子集
值存储在列表中。子集数据框是一个常见的主题(例如
this或this),
但是在所有这些问题中,值都是在运行前知道的。我想要
使用在闪亮的应用程序中生成的命名列表来挑选行并
将它们显示在数据表对象中。我将使用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)。
答案 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_join
或inner_join
将其加入表中,然后结束@ r2evans的解决方案。
或者,如果我们确实要过滤,可以将filter_at
与reduce
结合使用:
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的问题打破了在这里起作用的解析解决方案-我认为这是字符变量。