Отфильтруйте каждый столбец data.frame на основе определенного значения
Рассмотрим следующий фрейм данных:
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))
Примечание. Скажем, я хотел отфильтровать только первые четыре столбца, я бы сделал:
df %>% filter(X1 >= 2, X2 >= 2, X3 >= 2, X4 >= 2)
или
df %>% filter(!rowSums(.[-5] < 2))
Будет ли более эффективная альтернатива?
Изменить: дополнительный вопрос
Как указать имя столбца и подражать гипотетическому filter_each(funs(. >= 2), -X5)
?
Подкатегория Benchmark
Поскольку я должен запускать это на большом наборе данных, я сравнивал эти предложения.
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
![enter image description here]()
Ответы
Ответ 1
Вот еще один вариант с slice
, который можно использовать аналогично filter
в этом случае. Основное различие заключается в том, что вы отправляете целочисленный вектор в slice
, тогда как filter
принимает логический вектор.
df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L)))
Что мне нравится в этом подходе, так это то, что, поскольку мы используем select
внутри rowSums
, вы можете использовать все специальные функции, которые 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
![pic]()
Изменить примечание: обновлено с более надежным эталоном с 50 повторениями (times = 50L).
После комментария, что база R будет иметь ту же скорость, что и подход slice
(без указания того, какой подход базовой R подразумевается точно), я решил обновить свой ответ сравнением с базой R, используя почти тот же подход как в моем ответе. Для базы R я использовал:
base = df[!rowSums(df[-5L] < 2L), ],
base_which = df[which(!rowSums(df[-5L] < 2L)), ]
Benchmark:
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
![pic2]()
Не похоже на лучшую или сопоставимую производительность с этими двумя базовыми подходами R.
Изменить примечание # 2: добавлен тест с базовыми параметрами R.
Ответ 2
Вот идея, которая позволяет довольно просто выбирать имена. Вы можете настроить список вызовов для отправки в аргумент .dots
filter_()
. Сначала функция, которая создает неоценимый вызов.
Call <- function(x, value, fun = ">=") call(fun, as.name(x), value)
Теперь мы используем filter_()
, передавая список вызовов в аргумент .dots
, используя lapply()
, выбирая любое имя и значение, которое вы хотите.
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
Итак, если вы отредактируете names()
в аргументе X
lapply()
, вы должны быть в порядке.
Ответ 3
Как указать имя столбца и имитировать гипотетический 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))