Фильтрация строк в наборе данных по столбцам
У меня есть следующая таблица:
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
Я фильтрую записи, для которых LN присутствует в LN1-LN5.
Код, который я использовал:
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.
Ответы
Ответ 1
Использование:
# 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
Примечание. Рассмотрите возможность использования других решений для больших наборов данных, которые могут быть в 60 раз быстрее, чем это применимое решение.
Ответ 2
Существует альтернативный подход с использованием 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
Для 1 M строк жесткое кодированное подмножество является самым быстрым, за которым следуют 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)
![enter image description here]()
Ответ 3
не самый простой код, но
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
Ответ 4
Быстрое и очень простое решение 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
чтобы легко выбирать многие переменные на основе их имен. Или можно напрямую указать позиции столбцов.
Ответ 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
Для теста см. Ответ @Uwe.