使用正则表达式删除 R 数据帧列中的重复元素
Using regex to drop duplicated elements in columns of an R dataframe
我有一个虚拟数据框 df
,其尺寸为 6 X 4。
df <- data.frame(
Hits = c("Hit1", "Hit2", "Hit3", "Hit4", "Hit5", "Hit6"),
GO = c("GO:0005634~nucleus,", "", "GO:0005737~cytoplasm,", "GO:0005634~nucleus,GO:0005737~cytoplasm,", "",
"GO:0005634~nucleus,GO:0005654~nucleoplasm,"),
KEGG = c("", "", "", "", "", ""),
SMART = c("SM00394:RIIa,", "SM00394:RIIa,", "", "SM00054:EFh,",
"", "SM00394:RIIa,SM00239:C2,"))
df
看起来像这样
列中的元素由两部分组成:
- 一个
identifier
(例如GO:0005634~
、SM00394:
等)
- a
term
(例如nucleus
、EFh
等)
对于每一列,我想保留一行,如果它至少包含一个 term
,而该行不存在于它上面的任何行中。例如在 GO
列中,第 1 行和第 3 行包含唯一术语,因此应保留这些术语。第 4 行包含已存在于第 1 行和第 3 行中的术语,因此应将其删除。第 6 行有一项在它上面的任何一行中都不存在,因此它也应该被保留。
我已经能够想出正则表达式来从 GO
和 SMART
列中提取术语
Regex for GO: (?<=~).*?(?=,(?:GO:\d+~|$))
Regex for SMART: (?<=:).*?(?=,(?:\w+\d+:|$))
但我无法找到一种方法将正则表达式和上述条件集成到解决方案中。输出应该是这样的
关于如何解决这个问题有什么建议吗?
这是一个通用方法,可以处理 GO
、SMART
和潜在的 KEGG
,但如果没有关于 KEGG
的任何信息就不可能说。
下面的函数f
接受参数
x
,一个字符向量
split
,列表中分隔项目的分隔符
sep
,项目中分隔标识符和术语的分隔符
和 returns 一个逻辑向量,用至少一个 non-duplicated 项索引 x
的元素。
f <- function(x, split, sep) {
l1 <- strsplit(x, split)
tt <- sub(paste0("^[^", sep, "]*", sep), "", unlist(l1))
l2 <- relist(duplicated(tt), l1)
!vapply(l2, all, NA)
}
将f
应用到GO
和SMART
:
nms <- c("GO", "SMART")
l <- Map(f, x = df[nms], split = ",", sep = c("~", ":"))
l
## $GO
## [1] TRUE FALSE TRUE FALSE FALSE TRUE
##
## $SMART
## [1] TRUE FALSE FALSE TRUE FALSE TRUE
将GO
和SMART
的""
个元素设置为零non-duplicated项,然后过滤掉空行,我们得到想要的结果:
df2 <- df
df2[nms] <- Map(replace, df2[nms], lapply(l, `!`), "")
df2[Reduce(`|`, l), ]
## Hits GO KEGG SMART
## 1 Hit1 GO:0005634~nucleus, SM00394:RIIa,
## 3 Hit3 GO:0005737~cytoplasm,
## 4 Hit4 SM00054:EFh,
## 6 Hit6 GO:0005634~nucleus,GO:0005654~nucleoplasm, SM00394:RIIa,SM00239:C2,
以下算法应用于每个术语(GO、SMART、KEGG):
- 将标识符+术语列表提取为comma-separated。参见
stringr::str_split
等
- 将术语提取为正则表达式
- 在出现时沿数据框累积所有项
- 提取每行与前一行之间的差异
- 如果没有引入新术语,则将字符串替换为
""
- 过滤并非所有术语都是
""
的行
library(dplyr)
library(stringr)
library(purrr)
termred <- function(terms, rx) {
terms |>
stringr::str_split(",") |>
purrr::map(stringr::str_trim) |>
purrr::map(~{.x[.x != ""]}) |>
purrr::map(~stringr::str_extract(.x, rx)) |>
purrr::accumulate(union) %>%
{mapply(setdiff, ., lag(., 1), SIMPLIFY = TRUE)} %>%
{ifelse(sapply(., length) > 0, terms, "")}
}
df |>
transform(GO = termred(GO, "~.*$")) |>
transform(SMART = termred(SMART, ":.*$")) |>
filter(GO != "" | SMART != ""| KEGG != "")
##> Hits GO KEGG SMART
##>1 Hit1 GO:0005634~nucleus, SM00394:RIIa,
##>2 Hit3 GO:0005737~cytoplasm,
##>3 Hit4 SM00054:EFh,
##>4 Hit6 GO:0005634~nucleus,GO:0005654~nucleoplasm, SM00394:RIIa,SM00239:C2,
我有一个虚拟数据框 df
,其尺寸为 6 X 4。
df <- data.frame(
Hits = c("Hit1", "Hit2", "Hit3", "Hit4", "Hit5", "Hit6"),
GO = c("GO:0005634~nucleus,", "", "GO:0005737~cytoplasm,", "GO:0005634~nucleus,GO:0005737~cytoplasm,", "",
"GO:0005634~nucleus,GO:0005654~nucleoplasm,"),
KEGG = c("", "", "", "", "", ""),
SMART = c("SM00394:RIIa,", "SM00394:RIIa,", "", "SM00054:EFh,",
"", "SM00394:RIIa,SM00239:C2,"))
df
看起来像这样
列中的元素由两部分组成:
- 一个
identifier
(例如GO:0005634~
、SM00394:
等) - a
term
(例如nucleus
、EFh
等)
对于每一列,我想保留一行,如果它至少包含一个 term
,而该行不存在于它上面的任何行中。例如在 GO
列中,第 1 行和第 3 行包含唯一术语,因此应保留这些术语。第 4 行包含已存在于第 1 行和第 3 行中的术语,因此应将其删除。第 6 行有一项在它上面的任何一行中都不存在,因此它也应该被保留。
我已经能够想出正则表达式来从 GO
和 SMART
Regex for GO: (?<=~).*?(?=,(?:GO:\d+~|$))
Regex for SMART: (?<=:).*?(?=,(?:\w+\d+:|$))
但我无法找到一种方法将正则表达式和上述条件集成到解决方案中。输出应该是这样的
关于如何解决这个问题有什么建议吗?
这是一个通用方法,可以处理 GO
、SMART
和潜在的 KEGG
,但如果没有关于 KEGG
的任何信息就不可能说。
下面的函数f
接受参数
x
,一个字符向量split
,列表中分隔项目的分隔符sep
,项目中分隔标识符和术语的分隔符
和 returns 一个逻辑向量,用至少一个 non-duplicated 项索引 x
的元素。
f <- function(x, split, sep) {
l1 <- strsplit(x, split)
tt <- sub(paste0("^[^", sep, "]*", sep), "", unlist(l1))
l2 <- relist(duplicated(tt), l1)
!vapply(l2, all, NA)
}
将f
应用到GO
和SMART
:
nms <- c("GO", "SMART")
l <- Map(f, x = df[nms], split = ",", sep = c("~", ":"))
l
## $GO
## [1] TRUE FALSE TRUE FALSE FALSE TRUE
##
## $SMART
## [1] TRUE FALSE FALSE TRUE FALSE TRUE
将GO
和SMART
的""
个元素设置为零non-duplicated项,然后过滤掉空行,我们得到想要的结果:
df2 <- df
df2[nms] <- Map(replace, df2[nms], lapply(l, `!`), "")
df2[Reduce(`|`, l), ]
## Hits GO KEGG SMART
## 1 Hit1 GO:0005634~nucleus, SM00394:RIIa,
## 3 Hit3 GO:0005737~cytoplasm,
## 4 Hit4 SM00054:EFh,
## 6 Hit6 GO:0005634~nucleus,GO:0005654~nucleoplasm, SM00394:RIIa,SM00239:C2,
以下算法应用于每个术语(GO、SMART、KEGG):
- 将标识符+术语列表提取为comma-separated。参见
stringr::str_split
等 - 将术语提取为正则表达式
- 在出现时沿数据框累积所有项
- 提取每行与前一行之间的差异
- 如果没有引入新术语,则将字符串替换为
""
- 过滤并非所有术语都是
""
的行
library(dplyr)
library(stringr)
library(purrr)
termred <- function(terms, rx) {
terms |>
stringr::str_split(",") |>
purrr::map(stringr::str_trim) |>
purrr::map(~{.x[.x != ""]}) |>
purrr::map(~stringr::str_extract(.x, rx)) |>
purrr::accumulate(union) %>%
{mapply(setdiff, ., lag(., 1), SIMPLIFY = TRUE)} %>%
{ifelse(sapply(., length) > 0, terms, "")}
}
df |>
transform(GO = termred(GO, "~.*$")) |>
transform(SMART = termred(SMART, ":.*$")) |>
filter(GO != "" | SMART != ""| KEGG != "")
##> Hits GO KEGG SMART
##>1 Hit1 GO:0005634~nucleus, SM00394:RIIa,
##>2 Hit3 GO:0005737~cytoplasm,
##>3 Hit4 SM00054:EFh,
##>4 Hit6 GO:0005634~nucleus,GO:0005654~nucleoplasm, SM00394:RIIa,SM00239:C2,