我有一个数据帧(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
我们可以通过拆分列来创建逻辑索引
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)
在评论中,您提到了查找表。如果是这种情况,一种方法可能是将两个集合连接在一起,然后使用 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
方法二:易极联接
一种稍微节省内存的方法可能是使用 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
来查看字符串中的出现次数是否与strsplit(str_to_find, '')
的table
匹配。最后一个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(`|`)})