Найти строки, содержащие последовательность символов независимо от порядка в r
У меня есть датафрейм (df)
V1 V2
1 "BCC" Yes
2 "ABB" Yes
Я хочу найти все строки, которые содержат определенную последовательность символов, независимо от порядка. Например, если у меня есть строка "CBC" или "CCB", я хотел бы получить
V1 V2
1 "BCC" Yes
Я пробовал с grep, но он находит только подходящие шаблоны
>df[grep("CBC", df$V1),]
1 V1 V2
<0 rows> (or 0-length row.names)
>df[grep("BCC", df$V1),]
V1 V2
1 "BCC" Yes
4 ответа
Мы можем создать логический индекс, разделив столбец
i1 <- sapply(strsplit(df$V1, ""), function(x) all(c("B", "C") %in% x))
df[i1, , drop = FALSE]
# V1 V2
#1 BCC Yes
если у нас есть два набора данных, и один из них является справочной таблицей ('df2'), то разбейте столбец на символы,paste
sort
элементы и использование %in%
создать логическое vector
для фильтрации строк
v1n <- sapply(strsplit(df1$v1, ""), function(x) paste(sort(x), collapse=""))
v1l <- sapply(strsplit(df2$v1, ""), function(x) paste(sort(x), collapse=""))
df1[v1n %in% v1l, , drop = FALSE]
данные
df1 <- data.frame(v1 = c("BCC", "CAB" , "ABB", "CBC", "CCB", "BAB", "CDB"),
stringsAsFactors = FALSE)
df2 <- data.frame(v1 = c("CBC", "ABB"), stringsAsFactors = FALSE)
В комментариях вы упоминаете таблицу поиска. Если это так, то подход может заключаться в объединении обоих наборов, а затем использовать регулярное выражение Wiktor Stribiżew, чтобы указать, какие из них действительны
Когда я присоединяюсь к наборам данных, я буду использовать data.table
Способ 1: присоединиться ко всему
library(data.table)
## dummy data, and a lookup table
dt <- data.frame(V1 = c("BCC", "ABB"))
dt_lookup <- data.frame(V1 = c("CBC","BAB", "CCB"))
## convert to data.table
setDT(dt); setDT(dt_lookup)
## add some indexes to keep track of rows from each dt
dt[, idx := .I]
dt_lookup[, l_idx := .I]
## create a column to join on
dt[, key := 1L]
dt_lookup[, key := 1L]
## join EVERYTHING
dt <- dt[
dt_lookup
, on = "key"
, allow.cartesian = T
]
#regex
dt[
, valid := grepl(paste0("^[",i.V1,"]+$"), V1)
, by = 1:nrow(dt)
]
# V1 idx key i.V1 l_idx valid
# 1: BCC 1 1 CBC 1 TRUE
# 2: ABB 2 1 CBC 1 FALSE
# 3: BCC 1 1 BAB 2 FALSE
# 4: ABB 2 1 BAB 2 TRUE
# 5: BCC 1 1 CCB 3 TRUE
# 6: ABB 2 1 CCB 3 FALSE
Способ 2: каждое присоединение
Несколько более эффективным способом использования памяти может быть использование этой техники Jaap, поскольку она избегает шага "объединить все" и вместо этого соединяет его "каждым i" (строкой) за раз.
dt_lookup[
dt,
{
valid = grepl(paste0("^[",i.V1,"]+$"), V1)
.(
V1 = V1[valid]
, idx = i.idx
, match = i.V1
, l_idx = l_idx[valid]
)
}
, on = "key"
, by = .EACHI
]
# key V1 idx match l_idx
# 1: 1 CBC 1 BCC 1
# 2: 1 CCB 1 BCC 3
# 3: 1 BAB 2 ABB 2
Вот один метод, использующий sapply
, table
, а также identical
,
# construct a named vector of integers with names in
# alphabetical order: your match
myVal <- c("B"=1L, "C"=2L)
# run through character variable, perform check
sapply(strsplit(dat$V1, ""), function(x) identical(c(table(x)), myVal))
[1] TRUE FALSE
Два ключевых момента, связанных с использованием identical
и выход table
:
- вектор совпадения myVal должен быть целым числом.
- Вы хотите упорядочить вектор соответствия в алфавитном порядке, но вы можете сделать это заранее, вы также можете сделать это после факта с
order
,names
, а также[
,
Кроме того, не то, чтобы я завернул вывод table
в c
удалить нежелательные атрибуты, сохраняя имена.
Ты можешь использовать stringi::stri_count_regex
чтобы увидеть, соответствует ли количество вхождений в вашей строке table
из strsplit(str_to_find, '')
, Последний reduce("|")
означает, что он проверяет, есть ли совпадения, поэтому измените |
в &
если вы хотите проверить, соответствует ли он всем строкам в to.find
,
set.seed(0)
df <- data.frame(a = replicate(20, paste0(sample(LETTERS[1:3], 3, T), collapse = ''))
, stringsAsFactors = F)
to.find <- c("CBB", "CCB")
to.find <- strsplit(to.find, '')
library(tidyverse)
library(stringi)
df$b <-
sapply(df$a, function(x){
lapply(to.find, function(y){
imap(table(y), ~ .x == stri_count_regex(x, .y)) %>%
reduce(`&`)}) %>%
reduce(`|`)})
df
# a b
# 1 CAB FALSE
# 2 BCA FALSE
# 3 CCB TRUE
# 4 BAA FALSE
# 5 ACB FALSE
# 6 CBC TRUE
# 7 CBC TRUE
# 8 CAB FALSE
# 9 AAB FALSE
# 10 ABC FALSE
# 11 BBB FALSE
# 12 BAC FALSE
# 13 CCA FALSE
# 14 CBC TRUE
# 15 BCB TRUE
# 16 BCA FALSE
# 17 BCC TRUE
# 18 BCB TRUE
# 19 AAA FALSE
# 20 ABB FALSE
# 19 AAA FALSE
# 20 ABB FALSE
Вы также можете сделать все это с map
, но это сложнее читать
df$b <-
df$a %>%
map(~{x <- .x
map(to.find,
~imap(table(.x), ~ .x == stri_count_regex(x, .y)) %>%
reduce(`&`)) %>%
reduce(`|`)})