在一个数据帧中,我有一个字符串列表,这些字符串彼此相似,但相差%。我想将这些通用字符串组合成一个在每个位置具有最通用字符的字符串。
数据框如下所示:
\n
我正在尝试从模式列中添加另一列中最有可能的字符串
pattern Freq score rank
DT%E 37568 1138.4242 1
%TGE 37666 1018.0000 2
D%GE 37641 1017.3243 3
DTG% 37665 965.7692 4
%VGNE 34234 684.6800 5
SVGN% 34281 634.8333 6
SV%NE 34248 634.2222 7
SVG%E 34265 623.0000 8
%LGNE 41098 595.6232 9
SL%NE 41086 595.4493 10
SLGN% 41200 564.3836 11
SPT%AYNE 35082 539.7231 12
SP%AAYNE 35094 531.7273 13
SPTA%YNE 35061 531.2273 14
SPTAA%NE 35225 518.0147 15
SPTAAYN% 35144 516.8235 16
%PTAAYNE 35111 516.3382 17
S%TAAYNE 35100 516.1765 18
SPTAAY%E 35130 509.1304 19
SLG%E 41467 450.7283 20
答案 0 :(得分:3)
这是一个棘手但有趣的问题。
这里应该给您一些想法(并复制您的预期输出);但是请注意,这在某种程度上是一种经验方法,它做出以下假设:
总是有>=2
个模式属于同一true_string
;这对于(分层)聚类方法有效(见下文)是必要的。如果您有<2
个模式定义了true_string
,那么它将不起作用,这是有道理的,因为在同一位置出现两个字符的频率相同。
所有pattern
的长度都相同;也就是说,我们只考虑单个字符的替换,而不考虑插入/删除。
我们利用库stringdist
来计算字符串相似度。 stringdistmatrix
提供了各种距离度量标准(Levenshtein,Hamming等,有关详细信息,请参见?stringdist::stringdistmatrix
)。在这种情况下,我们使用method = "qgram"
,因为它会导致与您的预期输出一致的分组(因此出现了较早的“经验”警告)。我不知道这对您的真实数据的概括程度如何,因此请务必记住,您可能需要尝试使用不同的method
来找到“适合”您的距离相似性指标期望。
计算出字符串距离矩阵后,我们然后使用分层聚类对字符串进行聚类;我们基于在grp
的垂直距离处切割树添加v = 2
标签,然后使用自定义get_consensus_string
函数来推断每个grp
的共识字符串;如开头所述,该函数假定一个grp
中的所有字符串都具有相同的长度,并针对该字符串中的每个位置选择出现频率最高的字符。
首先,自定义get_consensus_string
函数
library(tidyverse)
get_consensus_string <- function(x) {
map_dfc(x, str_split, "") %>%
rowid_to_column("pos") %>%
gather(k, v, -pos) %>%
group_by(pos, v) %>%
add_count() %>%
group_by(pos) %>%
filter(n == max(n)) %>%
arrange(pos, desc(v)) %>%
dplyr::slice(1) %>%
pull(v) %>%
paste0(collapse = "")
}
我们现在可以基于来自grp
的字符串相似性距离矩阵的分层聚类结果添加stringdist::stringdistmatrix
标签;我凭经验在v = 2
的垂直距离处切割了这棵树(这是可能需要调整的参数)。一旦有了grp
标签,我们就会添加共识字符串。
library(stringdist)
df %>%
mutate(grp = cutree(hclust(stringdistmatrix(df$pattern, method = "qgram")), h = 2)) %>%
group_by(grp) %>%
mutate(true_string = get_consensus_string(pattern)) %>%
ungroup()
## A tibble: 20 x 6
# pattern Freq score rank grp true_string
# <fct> <int> <dbl> <int> <int> <chr>
# 1 DT%E 37568 1138. 1 1 DTGE
# 2 %TGE 37666 1018 2 1 DTGE
# 3 D%GE 37641 1017. 3 1 DTGE
# 4 DTG% 37665 966. 4 1 DTGE
# 5 %VGNE 34234 685. 5 2 SVGNE
# 6 SVGN% 34281 635. 6 2 SVGNE
# 7 SV%NE 34248 634. 7 2 SVGNE
# 8 SVG%E 34265 623 8 2 SVGNE
# 9 %LGNE 41098 596. 9 3 SLGNE
#10 SL%NE 41086 595. 10 3 SLGNE
#11 SLGN% 41200 564. 11 3 SLGNE
#12 SPT%AYNE 35082 540. 12 4 SPTAAYNE
#13 SP%AAYNE 35094 532. 13 4 SPTAAYNE
#14 SPTA%YNE 35061 531. 14 4 SPTAAYNE
#15 SPTAA%NE 35225 518. 15 4 SPTAAYNE
#16 SPTAAYN% 35144 517. 16 4 SPTAAYNE
#17 %PTAAYNE 35111 516. 17 4 SPTAAYNE
#18 S%TAAYNE 35100 516. 18 4 SPTAAYNE
#19 SPTAAY%E 35130 509. 19 4 SPTAAYNE
#20 SLG%E 41467 451. 20 3 SLGNE
您可以看到最终代码非常干净,并且可以再现预期的输出。
可能需要讨论两个问题:(1)如何选择合适的距离度量标准;(2)在哪里砍树。
关于第一个问题,一种经验方法是尝试对pattern
s进行分层聚类后尝试不同的度量并可视化树状图。
例如,对于method = "qgram"
,您可以这样做
mat <- as.matrix(stringdistmatrix(df$pattern, method = "qgram"))
rownames(mat) <- df$pattern
colnames(mat) <- df$pattern
plot(hclust(as.dist(mat)))
一旦您对聚类结果感到满意,我们就可以继续。
关于砍伐树木,一种实用/务实的方法是检查树状图并找到我们砍伐树木的合适高度(本例中为v = 2
);或者,如果您知道唯一的true_string
的数量,则可以用cutree
指定k
中的组数。
用更专业的术语来说,树状图的高度与使用完全链接的组之间的距离相关联(即根据最不相似的对测量距离)。由于组之间的距离又是基于pattern
之间的q-gram距离,因此可以将高度与两个pattern
之间的q-gram-距离相关,即绝对两个pattern
的N元语法向量之间的差异。
答案 1 :(得分:2)
我检查了莫里斯的答案,但是当我添加新行时。
D%GT 12434 12421 22 DXGT
DX%T 31242 2221.2 21 DXGT
pattern Freq score rank true_string
DT%E 37568 1138.4242 1 DTGE
D%GT 12434 12421 22 DXGT
DX%T 31242 2221.2 21 DXGT
%TGE 37666 1018 2 DTGE
D%GE 37641 1017.3243 3 DTGE
DTG% 37665 965.7692 4 DTGE
%VGNE 34234 684.68 5 SVGNE
SVGN% 34281 634.8333 6 SVGNE
SV%NE 34248 634.2222 7 SVGNE
SVG%E 34265 623 8 SVGNE
%LGNE 41098 595.6232 9 SLGNE
SL%NE 41086 595.4493 10 SLGNE
SLGN% 41200 564.3836 11 SLGNE
SPT%AYNE 35082 539.7231 12 SPTAAYNE
SP%AAYNE 35094 531.7273 13 SPTAAYNE
SPTA%YNE 35061 531.2273 14 SPTAAYNE
SPTAA%NE 35225 518.0147 15 SPTAAYNE
SPTAAYN% 35144 516.8235 16 SPTAAYNE
%PTAAYNE 35111 516.3382 17 SPTAAYNE
S%TAAYNE 35100 516.1765 18 SPTAAYNE
SPTAAY%E 35130 509.1304 19 SPTAAYNE
SLG%E 41467 450.7283 20 SLGNE
df %>%
mutate(grp = cutree(hclust(stringdistmatrix(df$pattern, method = "qgram")), h = 2)) %>%
group_by(grp) %>%
mutate(true_string = get_consensus_string(pattern)) %>%
ungroup()
> Result
pattern Freq score rank grp true_string
1 DT%E 37568 1138. 1 1 DT%T
2 D%GT 12434 12421 22 1 DT%T
3 DX%T 31242 2221. 21 1 DT%T
4 %TGE 37666 1018 2 2 %TGE
5 D%GE 37641 1017. 3 2 %TGE
6 DTG% 37665 966. 4 1 DT%T
7 %VGNE 34234 685. 5 3 SVGNE
8 SVGN% 34281 635. 6 3 SVGNE
9 SV%NE 34248 634. 7 3 SVGNE
10 SVG%E 34265 623 8 3 SVGNE
11 %LGNE 41098 596. 9 4 SLGNE
12 SL%NE 41086 595. 10 4 SLGNE
13 SLGN% 41200 564. 11 4 SLGNE
14 SPT%AYNE 35082 540. 12 5 SPTAAYNE
15 SP%AAYNE 35094 532. 13 5 SPTAAYNE
16 SPTA%YNE 35061 531. 14 5 SPTAAYNE
17 SPTAA%NE 35225 518. 15 5 SPTAAYNE
18 SPTAAYN% 35144 517. 16 5 SPTAAYNE
19 %PTAAYNE 35111 516. 17 5 SPTAAYNE
20 S%TAAYNE 35100 516. 18 5 SPTAAYNE
21 SPTAAY%E 35130 509. 19 5 SPTAAYNE
22 SLG%E 41467 451. 20 4 SLGNE
从以上结果来看,它不起作用。
library(dplyr)
library(data.table)
df <- fread(data)
string_pred <- function(x){
x = x %>% mutate(CL=nchar(pattern))
x_1 = x%>% select(pattern,CL)
Chr.length = unique(x_1$CL)
final_result = NULL
for ( len in 1:length(Chr.length)){
x_1_tmp = x %>% filter(CL==Chr.length[len])
RESULT = NULL
for(i in 1:Chr.length[len]){
TMP = substr(x_1_tmp$pattern,i,i)
TMP_GUESS = unique(TMP[!grepl("%",TMP)])
if(length(TMP_GUESS)==1){
TMP[grepl("%",TMP)] <- TMP_GUESS
} else {
TMP= TMP
}
NAME = sprintf('P%s',i)
RESULT = cbind(RESULT, NAME=TMP) %>% as.data.table()
names(RESULT)[i] = eval(parse(text='NAME'))
}
material = RESULT %>% rowwise() %>% .[apply(.,1,function(x){'%' %in% x}) ,]
if (nrow(material)==0){
x_1_tmp =x_1_tmp %>% mutate( pred = apply(RESULT,1,function(x)paste(as.character(x),collapse = ''))) %>% as.data.table()
} else {
mat.loc = RESULT %>% rowwise() %>%apply(.,1,function(x){'%' %in% x}) %>% which(unlist(.)==TRUE)
for (i in 1:nrow(material)){
ori.loc = mat.loc[i]
loc = names(material[i,])[material[i,]=='%']
tmp = material[i,] %>% dplyr::select(-loc)
RESULT[ori.loc,] = RESULT %>% rowwise() %>% inner_join(., tmp) %>% .[apply(.,1,function(x){!('%' %in% x)}) ,] %>% unique()
}
x_1_tmp = x_1_tmp %>%mutate( pred = apply(RESULT,1,function(x)paste(as.character(x),collapse = ''))) %>% as.data.table()
}
final_result = rbind(final_result, x_1_tmp)
}
return(final_result)
}
> string_pred(df)
pattern Freq score rank CL pred
1: DT%E 37568 1138.4242 1 4 DTGE
2: D%GT 12434 12421.0000 22 4 DXGT
3: DX%T 31242 2221.2000 21 4 DXGT
4: %TGE 37666 1018.0000 2 4 DTGE
5: D%GE 37641 1017.3243 3 4 DTGE
6: DTG% 37665 965.7692 4 4 DTGE
7: %VGNE 34234 684.6800 5 5 SVGNE
8: SVGN% 34281 634.8333 6 5 SVGNE
9: SV%NE 34248 634.2222 7 5 SVGNE
10: SVG%E 34265 623.0000 8 5 SVGNE
11: %LGNE 41098 595.6232 9 5 SLGNE
12: SL%NE 41086 595.4493 10 5 SLGNE
13: SLGN% 41200 564.3836 11 5 SLGNE
14: SLG%E 41467 450.7283 20 5 SLGNE
15: SPT%AYNE 35082 539.7231 12 8 SPTAAYNE
16: SP%AAYNE 35094 531.7273 13 8 SPTAAYNE
17: SPTA%YNE 35061 531.2273 14 8 SPTAAYNE
18: SPTAA%NE 35225 518.0147 15 8 SPTAAYNE
19: SPTAAYN% 35144 516.8235 16 8 SPTAAYNE
20: %PTAAYNE 35111 516.3382 17 8 SPTAAYNE
21: S%TAAYNE 35100 516.1765 18 8 SPTAAYNE
22: SPTAAY%E 35130 509.1304 19 8 SPTAAYNE
pattern Freq score rank CL
1 DT%E 37568 1138.4242 1 4
2 D%GT 12434 12421.0000 22 4
3 DX%T 31242 2221.2000 21 4
4 %TGE 37666 1018.0000 2 4
5 D%GE 37641 1017.3243 3 4
6 DTG% 37665 965.7692 4 4
TMP = substr(x_1_tmp$pattern,i,i)
[1] "D" "D" "D" "%" "D" "D"
unique(pattern[i] except % ) == 1
->我们将%分配为unique(pattern[i] except % )
P1 P2 P3 P4
1: D T G E
2: D % G T
3: D X G T
4: D T G E
5: D % G E
6: D T G %
unique(pattern[i] except % ) > 1
我们检查字符长度组中的另一行。然后我们将字符(%列除外)合并为其他字符。 RESULT[ori.loc,] = RESULT %>% rowwise() %>%
inner_join(., tmp) %>%
.[apply(.,1,function(x){!('%' %in% x)}) ,] %>% unique()
>print
Joining, by = c("P1", "P3", "P4")
Source: local data frame [1 x 4]
Groups: <by row>
# A tibble: 1 x 4
P1 P2 P3 P4
<chr> <chr> <chr> <chr>
1 D X G T
%
是什么 pattern Freq score rank CL pred
1: DT%E 37568 1138.4242 1 4 DTGE
2: D%GT 12434 12421.0000 22 4 DXGT
3: DX%T 31242 2221.2000 21 4 DXGT
4: %TGE 37666 1018.0000 2 4 DTGE
5: D%GE 37641 1017.3243 3 4 DTGE
6: DTG% 37665 965.7692 4 4 DTGE
我的回答看起来并不花哨,但确实有效。
我建议您一一遵循代码