#r #dplyr #data.table #tidyverse
#r #dplyr #data.table #tidyverse
Вопрос:
У меня есть набор данных с 5 числовыми столбцами, скажем {A, B, C, D, E}, в котором значение любого столбца может варьироваться от 1 до 100. то есть,
1 <= все значения в A / B /C /D /E <= 100
и наш набор данных выглядит следующим образом :
A B C D E
1 5 7 19 2
90 12 8 45 30
30 10 20 50 40 #need this row
33 11 22 55 44
50 40 10 20 30 #and this row
40 40 10 20 30 #not this one
и я хочу фильтровать только те строки, которые содержат каждое из следующих 5 значений, например: {10,20,30,40,50}. порядок не имеет значения, но 5 столбцов должны содержать все 5 значений.
Таким образом, вывод должен быть таким :
A B C D E
30 10 20 50 40
50 40 10 20 30
Я пытался использовать множество ifelse
для фильтрации по всем условиям 5 столбцов, но дело в том, что мне нужно применить эту концепцию к более сложной задаче, где они могут не быть определенным количеством столбцов или даже определенным набором данных ‘lookup’. поэтому мы высоко ценим любое решение с использованием dplyr, data.table, tidyverse, но любое другое творческое решение, которое может предложить любой, пожалуйста, поделитесь.
Ответ №1:
tb <- data.frame(A = c(1, 90, 30 ,33,50,40),
B = c(5,12,10,11,40,40),
C = c(7,8,20,22,10,10),
D = c(19,45,50,55,20,20),
E = c(2,30,40,44,30,30))
cols <- paste0(c(10,20,30,40,50), collapse = "_")
index <- apply(tb, 1, function(x) paste0(sort(x), collapse = "_") == cols)
tb[index,]
Комментарии:
1. Спасибо! изменение строк на строки и последующее их сравнение, что на самом деле помогает мне больше.
Ответ №2:
Используя apply
, sum
, и %in%
из базы R
my_vals = c(10, 20, 30, 40, 50)
df[apply(df, 1, function(row) all(my_vals %in% row)), ]
A B C D E
3 30 10 20 50 40
5 50 40 10 20 30
Это может быть расширено до любого количества столбцов, и все, что вам нужно сделать, это обновить my_vals
.
Редактировать
Основываясь на комментарии OP относительно ситуации выбора правильных строк, когда my_vals
могут быть дублированные элементы, приведенный выше код может быть изменен примерно так
my_vals = sort(c(10, 20, 30, 40, 40))
df[apply(df, 1, function(row) all(my_vals == sort(row))), ]
A B C D E
6 40 40 10 20 30
Комментарии:
1. к вашему сведению, когда
my_vals = c(10, 20, 30, 40, 40)
это решение также будет работатьc(10, 20, 30, 40, 50)
2. Ах, да, потому что это не было перечисленным вариантом использования. Итак, в этом случае вам не нужна строка с 50 в ней? Поскольку, учитывая ваше условие в вопросе, если в строке содержится 10,20,30,40,50, в ней все еще есть все элементы, указанные в
my_vals = c(10, 20, 30, 40, 40)
плюс дополнительные 50.3. вы правы. с my_vals = c(10, 20, 30, 40, 40), он также подберет 5-ю строку в моем наборе данных. и я не хочу этого, поскольку важно, чтобы выбирались только строки, которые также имеют столько экземпляров всех значений в my_vals. Я имею в виду, что если my_vals = c(10, 20, 30, 40, 40) тогда будет выбрана только строка с двумя 40-ю строками в ней, одна 30, одна 20 и одна 10 в ней … надеюсь, это достаточно ясно.
4. Я обновил ответ, чтобы учесть ваше новое условие
5. @nsinghs Спасибо за редактирование. Теперь это работает лучше для меня.
Ответ №3:
Может быть, что-то вроде этого?
library(dplyr)
dat %>%
rowwise() %>%
filter(paste(sort(c(A, B, C, D, E)), collapse = ".") == "10.20.30.40.50") %>%
ungroup()
# A tibble: 2 x 5
A B C D E
<int> <int> <int> <int> <int>
1 30 10 20 50 40
2 50 40 10 20 30
Данные:
dat <- read.table(text = "A B C D E
1 5 7 19 2
90 12 8 45 30
30 10 20 50 40
33 11 22 55 44
50 40 10 20 30
40 40 10 20 30", header = TRUE)
Примечание: я не уверен, что это будет хорошим подходом для расширения вашей «более сложной проблемы, когда они могут не быть определенным числом столбцов или даже определенным набором данных «поиска»», поскольку это несколько расплывчато определено. Если у вас более сложная проблема, я настоятельно рекомендую вам сформулировать свой вопрос так, чтобы он отражал ее.
Ответ №4:
Вот метод преобразования в длинный формат, фильтрация и изменение формы обратно в широкий:
my_vals = c(10, 20, 30, 40, 50)
library(tidyr)
library(dplyr)
df %>% mutate(id = row_number()) %>%
gather("col", "val", -id) %>%
group_by(id) %>%
filter(all(my_vals %in% val)) %>%
spread(col, val)
# A tibble: 2 x 6
# Groups: id [2]
id A B C D E
<int> <int> <int> <int> <int> <int>
1 3 30 10 20 50 40
2 5 50 40 10 20 30
(Вы можете, конечно, удалить id
столбец, если он вам не нужен.)
Ответ №5:
Вот решение data.table:
library(data.table)
dt <- setDT(read.table(text = "A B C D E
1 5 7 19 2
90 12 8 45 30
30 10 20 50 40
33 11 22 55 44
50 40 10 20 30
40 40 10 20 30", header = TRUE))
dt = dt[, .SD[all(seq(10, 50, 10) %in% .SD)], by = 1:nrow(dt)]
Ответ №6:
Вот еще один вариант без сортировки каждой строки.
Идея состоит в том, чтобы объединить каждый столбец набора данных со значениями поиска, столбец за столбцом. Например, для столбца A используйте все 5 значений для фильтрации исходного набора данных.
Затем для столбца B объедините, используя все, что не использовалось в столбце A для каждого подмножества набора данных с предыдущего шага.
Затем для столбца C объедините, используя все, что не использовалось в столбцах A и B для каждого подмножества набора данных с предыдущего шага.
Затем для столбца D объедините, используя все, что не использовалось в столбцах A, B и C для каждого подмножества набора данных с предыдущего шага.
И так далее и тому подобное.
Вот реализация вышеупомянутой идеи в data.table
:
v <- c(10, 20, 30, 40, 40)
nm <- names(dat)
dat <- dat[.(A=unique(v)), on=.(A), nomatch=0L]
for (k in seq_along(nm)[-1L]) {
dat <- dat[, .SD[.(unique(v[-match(.BY, v)])),
on=eval(nm[k]),
nomatch=0L],
by=eval(nm[seq_len(k)[-k]])]
}
dat
вывод для v <- c(10, 20, 30, 40, 40)
:
A B C D E
1: 10 40 40 20 30
2: 40 40 10 20 30
3: 40 40 10 20 30
вывод для v <- c(10, 20, 30, 40, 50)
:
A B C D E
1: 30 10 20 50 40
2: 50 40 10 20 30
данные:
library(data.table)
dat <- fread("A B C D E
1 5 7 19 2
90 12 8 45 30
30 10 20 50 40
33 11 22 55 44
50 40 10 20 30
40 40 10 20 30
40 40 10 20 30
10 40 40 20 30") #2 dupe rows to demonstrate edge case