我有下表:
FN LN LN1 LN2 LN3 LN4 LN5
a b b x x x x
a c b d e NA NA
a d c a b x x
a e b c d x e
我正在过滤LN1到LN5中存在LN的记录。
我使用的代码:
testFilter = filter(test, LN %in% c(LN1, LN2, LN3, LN4, LN5))
结果不是我所期望的:
ï..FN LN LN1 LN2 LN3 LN4 LN5
1 a b b x x x x
2 a c b d e <NA> <NA>
3 a d c a b x x
4 a e b c d x e
我理解c(LN1, LN2, LN3, LN4, LN5)
给出:"b" "b" "c" "b" "x" "d" "a" "c" "x" "e" "b" "d" "x" NA "x" "x" "x" NA "x" "e"
并且知道这就是错误所在。
理想情况下,我想只返回第1和第4条记录。
FN LN LN1 LN2 LN3 LN4 LN5
a b b x x x x
a e b c d x e
我想仅使用列名过滤它们。这只是5.4M记录的一个子集。
答案 0 :(得分:8)
使用应用:
# data
df1 <- read.table(text = "
FN LN LN1 LN2 LN3 LN4 LN5
a b b x x x x
a c b d e NA NA
a d c a b x x
a e b c d x e", header = TRUE, stringsAsFactors = FALSE)
df1[ apply(df1, 1, function(i) i[2] %in% i[3:7]), ]
# FN LN LN1 LN2 LN3 LN4 LN5
# 1 a b b x x x x
# 4 a e b c d x e
注意:请考虑使用下面的其他解决方案来处理大数据集,这比 apply 解决方案 60 快。
答案 1 :(得分:8)
有一种使用data.table
和Reduce()
的替代方法:
library(data.table)
cols <- paste0("LN", 1:5)
setDT(test)[test[, .I[Reduce(`|`, lapply(.SD, function(x) !is.na(x) & LN == x))],
.SDcols = cols]]
FN LN LN1 LN2 LN3 LN4 LN5 1: a b b x x x x 2: a e b c d x e
library(data.table)
test <- fread(
"FN LN LN1 LN2 LN3 LN4 LN5
a b b x x x x
a c b d e NA NA
a d c a b x x
a e b c d x e")
library(data.table)
library(dplyr)
n_row <- 1e6L
set.seed(123L)
DT <- data.table(
FN = "a",
LN = sample(letters, n_row, TRUE))
cols <- paste0("LN", 1:5)
DT[, (cols) := lapply(1:5, function(x) sample(c(letters, NA), n_row, TRUE))]
DT
df1 <- as.data.frame(DT)
bm <- microbenchmark::microbenchmark(
zx8754 = {
df1[ apply(df1, 1, function(i) i[2] %in% i[3:7]), ]
},
eric = {
df1[ which(df1$LN == df1$LN1 |
df1$LN == df1$LN2 |
df1$LN == df1$LN3 |
df1$LN == df1$LN4 |
df1$LN == df1$LN5), ]
},
uwe = {
DT[DT[, .I[Reduce(`|`, lapply(.SD, function(x) !is.na(x) & LN == x))],
.SDcols = cols]]
},
axe = {
filter_at(df1, vars(num_range("LN", 1:5)), any_vars(. == LN))
},
jaap = {df1[!!rowSums(df1$LN == df1[, 3:7], na.rm = TRUE),]},
times = 50L
)
print(bm, "ms")
Unit: milliseconds expr min lq mean median uq max neval cld zx8754 3120.68925 3330.12289 3508.03001 3460.83459 3589.10255 4552.9070 50 c eric 69.74435 79.11995 101.80188 83.78996 98.24054 309.3864 50 a uwe 93.26621 115.30266 130.91483 121.64281 131.75704 292.8094 50 a axe 69.82137 79.54149 96.70102 81.98631 95.77107 315.3111 50 a jaap 362.39318 489.86989 543.39510 544.13079 570.10874 1110.1317 50 b
对于1M行,硬编码子集最快,其次是data.table
/ Reduce()
和dplyr
/ filter_at
方法。使用apply()
的速度要慢60倍。
ggplot(bm, aes(expr, time)) + geom_violin() + scale_y_log10() + stat_summary(fun.data = mean_cl_boot)
答案 2 :(得分:6)
不是最简单的代码,而是
df1[ which(df1$LN == df1$LN1 |
df1$LN == df1$LN2 |
df1$LN == df1$LN3 |
df1$LN == df1$LN4 |
df1$LN == df1$LN5), ]
#> FN LN LN1 LN2 LN3 LN4 LN5
#> 1 a b b x x x x
#> 4 a e b c d x e
答案 3 :(得分:6)
快速而简单的dplyr
解决方案:
filter_at(df1, vars(num_range("LN", 1:5)), any_vars(. == LN))
这与@EricFail的硬编码答案在性能上非常相似,因为这只是内部将调用扩展到:
filter(df1, (LN1 == LN) | (LN2 == LN) | (LN3 == LN) | (LN4 == LN) | (LN5 == LN))
num_range
可以在select
内使用vars
任何其他Advanced block settings
帮助程序,以便根据名称轻松选择多个变量。或者可以直接给列位置。
答案 4 :(得分:5)
您也可以使用rowSums
:
df1[!!rowSums(df1$LN == df1[, 3:7], na.rm = TRUE),]
给出:
FN LN LN1 LN2 LN3 LN4 LN5 1 a b b x x x x 4 a e b c d x e
有关基准,请参阅answer of @Uwe。