Найти строки, содержащие последовательность символов независимо от порядка в r - PullRequest
0 голосов
/ 29 июня 2018

У меня есть датафрейм (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 ]

0 голосов
/ 29 июня 2018

Вы можете использовать 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(`|`)})
0 голосов
/ 29 июня 2018

Вот один метод, использующий 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:

  1. вектор совпадения, myVal должно быть целым числом.
  2. Вы хотите упорядочить вектор совпадения в алфавитном порядке, но вы можете сделать это заранее, вы также можете сделать это после факта с помощью order, names и [.

Кроме того, не то, чтобы я обернул вывод table в c, чтобы убрать нежелательные атрибуты при сохранении имен.

0 голосов
/ 29 июня 2018

В комментариях вы упоминаете таблицу поиска. Если это так, подход может заключаться в объединении обоих наборов, а затем использовать регулярное выражение от 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: присоединение к EACHI

Несколько более эффективным способом использования памяти может быть использование этой техники от 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
0 голосов
/ 29 июня 2018

Мы можем создать логический индекс, разделив столбец

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 ed и используйте %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)
...