根据特定值过滤 data.frame 的每一列
Filter each column of a data.frame based on a specific value
考虑以下数据框:
df <- data.frame(replicate(5,sample(1:10,10,rep=TRUE)))
# X1 X2 X3 X4 X5
#1 7 9 8 4 10
#2 2 4 9 4 9
#3 2 7 8 8 6
#4 8 9 6 6 4
#5 5 2 1 4 6
#6 8 2 2 1 7
#7 3 8 6 1 6
#8 3 8 5 9 8
#9 6 2 3 10 7
#10 2 7 4 2 9
使用 dplyr
,我如何过滤每列(没有隐式命名)所有大于 2 的值。
模仿假设的东西 filter_each(funs(. >= 2))
现在我在做:
df %>% filter(X1 >= 2, X2 >= 2, X3 >= 2, X4 >= 2, X5 >= 2)
相当于:
df %>% filter(!rowSums(. < 2))
注意:假设我只想过滤前 4 列,我会这样做:
df %>% filter(X1 >= 2, X2 >= 2, X3 >= 2, X4 >= 2)
或
df %>% filter(!rowSums(.[-5] < 2))
会有更有效的替代方案吗?
编辑:子问题
如何指定列名并模拟假设的 filter_each(funs(. >= 2), -X5)
?
基准子题
由于我必须 运行 在大型数据集上进行此操作,因此我对这些建议进行了基准测试。
df <- data.frame(replicate(5,sample(1:10,10e6,rep=TRUE)))
mbm <- microbenchmark(
Marat = df %>% filter(!rowSums(.[,!colnames(.) %in% "X5", drop = FALSE] < 2)),
Richard = filter_(df, .dots = lapply(names(df)[names(df) != "X5"], function(x, y) { call(">=", as.name(x), y) }, 2)),
Docendo = df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L))),
times = 50
)
结果如下:
#Unit: milliseconds
# expr min lq mean median uq max neval
# Marat 1209.1235 1320.3233 1358.7994 1362.0590 1390.342 1448.458 50
# Richard 1151.7691 1196.3060 1222.9900 1216.3936 1256.191 1266.669 50
# Docendo 874.0247 933.1399 983.5435 985.3697 1026.901 1053.407 50
这里有一个想法可以让选择名称变得相当简单。您可以设置调用列表以发送到 filter_()
的 .dots
参数。首先是创建未评估调用的函数。
Call <- function(x, value, fun = ">=") call(fun, as.name(x), value)
现在我们使用 filter_()
,使用 lapply()
将调用列表传递到 .dots
参数,选择您想要的任何名称和值。
nm <- names(df) != "X5"
filter_(df, .dots = lapply(names(df)[nm], Call, 2L))
# X1 X2 X3 X4 X5
# 1 6 5 7 3 1
# 2 8 10 3 6 5
# 3 5 7 10 2 5
# 4 3 4 2 9 9
# 5 8 3 5 6 2
# 6 9 3 4 10 9
# 7 2 9 7 9 8
您可以查看 Call()
创建的未评估调用,例如 X4
和 X5
,以及
lapply(names(df)[4:5], Call, 2L)
# [[1]]
# X4 >= 2L
#
# [[2]]
# X5 >= 2L
所以如果你在lapply()
的X
参数中调整names()
,你应该没问题。
How to specify a column name and mimic an hypothethical filter_each(funs(. >= 2), -X5) ?
这可能不是最优雅的解决方案,但它完成了工作:
df %>% filter(!rowSums(.[,!colnames(.)%in%'X5',drop=F] < 2))
如果有多个排除列(例如 X3、X5),可以使用:
df %>% filter(!rowSums(.[,!colnames(.)%in%c('X3','X5'),drop=F] < 2))
这是另一个带有 slice
的选项,在这种情况下可以类似于 filter
使用。主要区别在于您向 slice
提供了一个整数向量,而 filter
接受了一个逻辑向量。
df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L)))
我喜欢这种方法的地方在于,因为我们在 rowSums
中使用了 select
,所以您可以使用 select
提供的所有特殊功能,例如 matches
例如。
让我们看看它与其他答案的比较:
df <- data.frame(replicate(5,sample(1:10,10e6,rep=TRUE)))
mbm <- microbenchmark(
Marat = df %>% filter(!rowSums(.[,!colnames(.) %in% "X5", drop = FALSE] < 2)),
Richard = filter_(df, .dots = lapply(names(df)[names(df) != "X5"], function(x, y) { call(">=", as.name(x), y) }, 2)),
dd_slice = df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L))),
times = 50L,
unit = "relative"
)
#Unit: relative
# expr min lq median uq max neval
# Marat 1.304216 1.290695 1.290127 1.288473 1.290609 50
# Richard 1.139796 1.146942 1.124295 1.159715 1.160689 50
# dd_slice 1.000000 1.000000 1.000000 1.000000 1.000000 50
编辑说明: 更新了更可靠的基准,重复 50 次(次数 = 50L)。
根据评论说 base R 将具有与 slice
方法相同的速度(没有具体说明 base R 方法的确切含义),我决定通过与 base R 的比较来更新我的答案使用几乎与我的回答中的方法相同。对于我使用的基础 R:
base = df[!rowSums(df[-5L] < 2L), ],
base_which = df[which(!rowSums(df[-5L] < 2L)), ]
基准:
df <- data.frame(replicate(5,sample(1:10,10e6,rep=TRUE)))
mbm <- microbenchmark(
Marat = df %>% filter(!rowSums(.[,!colnames(.) %in% "X5", drop = FALSE] < 2)),
Richard = filter_(df, .dots = lapply(names(df)[names(df) != "X5"], function(x, y) { call(">=", as.name(x), y) }, 2)),
dd_slice = df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L))),
base = df[!rowSums(df[-5L] < 2L), ],
base_which = df[which(!rowSums(df[-5L] < 2L)), ],
times = 50L,
unit = "relative"
)
#Unit: relative
# expr min lq median uq max neval
# Marat 1.265692 1.279057 1.298513 1.279167 1.203794 50
# Richard 1.124045 1.160075 1.163240 1.169573 1.076267 50
# dd_slice 1.000000 1.000000 1.000000 1.000000 1.000000 50
# base 2.784058 2.769062 2.710305 2.669699 2.576825 50
# base_which 1.458339 1.477679 1.451617 1.419686 1.412090 50
这两种基本 R 方法并没有更好或可比的性能。
编辑注释 #2: 添加了带有基本 R 选项的基准。
如果您只想过滤前四列,如:
df %>% filter(X1 >= 2, X2 >= 2, X3 >= 2, X4 >= 2)
...试试这个:
df %>%
filter_at(vars(X1:X4), #<Select columns to filter
all_vars(.>=2) ) #<Scope with all_vars (or any_vars)
另一种方法是排除您要过滤的列,如:
df %>%
filter_at(vars(-X5)), #<Exclude column X5
all_vars(.>=2) )
考虑以下数据框:
df <- data.frame(replicate(5,sample(1:10,10,rep=TRUE)))
# X1 X2 X3 X4 X5
#1 7 9 8 4 10
#2 2 4 9 4 9
#3 2 7 8 8 6
#4 8 9 6 6 4
#5 5 2 1 4 6
#6 8 2 2 1 7
#7 3 8 6 1 6
#8 3 8 5 9 8
#9 6 2 3 10 7
#10 2 7 4 2 9
使用 dplyr
,我如何过滤每列(没有隐式命名)所有大于 2 的值。
模仿假设的东西 filter_each(funs(. >= 2))
现在我在做:
df %>% filter(X1 >= 2, X2 >= 2, X3 >= 2, X4 >= 2, X5 >= 2)
相当于:
df %>% filter(!rowSums(. < 2))
注意:假设我只想过滤前 4 列,我会这样做:
df %>% filter(X1 >= 2, X2 >= 2, X3 >= 2, X4 >= 2)
或
df %>% filter(!rowSums(.[-5] < 2))
会有更有效的替代方案吗?
编辑:子问题
如何指定列名并模拟假设的 filter_each(funs(. >= 2), -X5)
?
基准子题
由于我必须 运行 在大型数据集上进行此操作,因此我对这些建议进行了基准测试。
df <- data.frame(replicate(5,sample(1:10,10e6,rep=TRUE)))
mbm <- microbenchmark(
Marat = df %>% filter(!rowSums(.[,!colnames(.) %in% "X5", drop = FALSE] < 2)),
Richard = filter_(df, .dots = lapply(names(df)[names(df) != "X5"], function(x, y) { call(">=", as.name(x), y) }, 2)),
Docendo = df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L))),
times = 50
)
结果如下:
#Unit: milliseconds
# expr min lq mean median uq max neval
# Marat 1209.1235 1320.3233 1358.7994 1362.0590 1390.342 1448.458 50
# Richard 1151.7691 1196.3060 1222.9900 1216.3936 1256.191 1266.669 50
# Docendo 874.0247 933.1399 983.5435 985.3697 1026.901 1053.407 50
这里有一个想法可以让选择名称变得相当简单。您可以设置调用列表以发送到 filter_()
的 .dots
参数。首先是创建未评估调用的函数。
Call <- function(x, value, fun = ">=") call(fun, as.name(x), value)
现在我们使用 filter_()
,使用 lapply()
将调用列表传递到 .dots
参数,选择您想要的任何名称和值。
nm <- names(df) != "X5"
filter_(df, .dots = lapply(names(df)[nm], Call, 2L))
# X1 X2 X3 X4 X5
# 1 6 5 7 3 1
# 2 8 10 3 6 5
# 3 5 7 10 2 5
# 4 3 4 2 9 9
# 5 8 3 5 6 2
# 6 9 3 4 10 9
# 7 2 9 7 9 8
您可以查看 Call()
创建的未评估调用,例如 X4
和 X5
,以及
lapply(names(df)[4:5], Call, 2L)
# [[1]]
# X4 >= 2L
#
# [[2]]
# X5 >= 2L
所以如果你在lapply()
的X
参数中调整names()
,你应该没问题。
How to specify a column name and mimic an hypothethical filter_each(funs(. >= 2), -X5) ?
这可能不是最优雅的解决方案,但它完成了工作:
df %>% filter(!rowSums(.[,!colnames(.)%in%'X5',drop=F] < 2))
如果有多个排除列(例如 X3、X5),可以使用:
df %>% filter(!rowSums(.[,!colnames(.)%in%c('X3','X5'),drop=F] < 2))
这是另一个带有 slice
的选项,在这种情况下可以类似于 filter
使用。主要区别在于您向 slice
提供了一个整数向量,而 filter
接受了一个逻辑向量。
df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L)))
我喜欢这种方法的地方在于,因为我们在 rowSums
中使用了 select
,所以您可以使用 select
提供的所有特殊功能,例如 matches
例如。
让我们看看它与其他答案的比较:
df <- data.frame(replicate(5,sample(1:10,10e6,rep=TRUE)))
mbm <- microbenchmark(
Marat = df %>% filter(!rowSums(.[,!colnames(.) %in% "X5", drop = FALSE] < 2)),
Richard = filter_(df, .dots = lapply(names(df)[names(df) != "X5"], function(x, y) { call(">=", as.name(x), y) }, 2)),
dd_slice = df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L))),
times = 50L,
unit = "relative"
)
#Unit: relative
# expr min lq median uq max neval
# Marat 1.304216 1.290695 1.290127 1.288473 1.290609 50
# Richard 1.139796 1.146942 1.124295 1.159715 1.160689 50
# dd_slice 1.000000 1.000000 1.000000 1.000000 1.000000 50
编辑说明: 更新了更可靠的基准,重复 50 次(次数 = 50L)。
根据评论说 base R 将具有与 slice
方法相同的速度(没有具体说明 base R 方法的确切含义),我决定通过与 base R 的比较来更新我的答案使用几乎与我的回答中的方法相同。对于我使用的基础 R:
base = df[!rowSums(df[-5L] < 2L), ],
base_which = df[which(!rowSums(df[-5L] < 2L)), ]
基准:
df <- data.frame(replicate(5,sample(1:10,10e6,rep=TRUE)))
mbm <- microbenchmark(
Marat = df %>% filter(!rowSums(.[,!colnames(.) %in% "X5", drop = FALSE] < 2)),
Richard = filter_(df, .dots = lapply(names(df)[names(df) != "X5"], function(x, y) { call(">=", as.name(x), y) }, 2)),
dd_slice = df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L))),
base = df[!rowSums(df[-5L] < 2L), ],
base_which = df[which(!rowSums(df[-5L] < 2L)), ],
times = 50L,
unit = "relative"
)
#Unit: relative
# expr min lq median uq max neval
# Marat 1.265692 1.279057 1.298513 1.279167 1.203794 50
# Richard 1.124045 1.160075 1.163240 1.169573 1.076267 50
# dd_slice 1.000000 1.000000 1.000000 1.000000 1.000000 50
# base 2.784058 2.769062 2.710305 2.669699 2.576825 50
# base_which 1.458339 1.477679 1.451617 1.419686 1.412090 50
这两种基本 R 方法并没有更好或可比的性能。
编辑注释 #2: 添加了带有基本 R 选项的基准。
如果您只想过滤前四列,如:
df %>% filter(X1 >= 2, X2 >= 2, X3 >= 2, X4 >= 2)
...试试这个:
df %>%
filter_at(vars(X1:X4), #<Select columns to filter
all_vars(.>=2) ) #<Scope with all_vars (or any_vars)
另一种方法是排除您要过滤的列,如:
df %>%
filter_at(vars(-X5)), #<Exclude column X5
all_vars(.>=2) )