如何高效在R数据框列中查找语音转录近匹配词
高效匹配语音转录替换词的解决方案
问题背景
我有一个存储单词语音转录的DataFrame,需要为每条转录找到满足以下条件的词:将转录中的1.M、1.N、1.NG与1.[任意1-2字符字符串]互换后的匹配词。现有for循环实现方案在33000行数据上耗时15分钟,需要更高效的实现方式。
示例数据
Word StTrn NPhon again AH0.G.EH1.N 4 box B.AA1.K.S 4 center S.EH1.N.T.AH0.R 6 feral F.EH1.R.AH0.L 5 sector S.EH1.K.T.AH0.R 6 shadows SH.AE1.D.OW2.Z 5 stub S.T.AH1.B 4 stuck S.T.AH1.K 4 stuff S.T.AH1.F 4 stun S.T.AH1.N 4 stung S.T.AH1.NG 4 whim W.IH1.M 3 win W.IH1.N 3 wing W.IH1.NG 3 wish W.IH1.SH 3
期望输出
Word StTrn NPhon VC_VN again AH0.G.EH1.N 4 box B.AA1.K.S 4 center S.EH1.N.T.AH0.R 6 sector feral F.EH1.R.AH0.L 5 sector S.EH1.K.T.AH0.R 6 center shadows SH.AE1.D.OW2.Z 5 stub S.T.AH1.B 4 stun;stung stuck S.T.AH1.K 4 stun;stung stuff S.T.AH1.F 4 stun;stung stun S.T.AH1.N 4 stub;stuck;stuff stung S.T.AH1.NG 4 stub;stuck;stuff whim W.IH1.M 3 wish;wit;with win W.IH1.N 3 wish;wit;with wing W.IH1.NG 3 wish;wit;with wish W.IH1.SH 3 whim;win;wing wit W.IH1.T 3 whim;win;wing with W.IH1.TH 3 whim;win;wing
原低效代码
library(dplyr) library(stringr) IPhoD2_nasal <- IPhoD2 %>% filter(str_detect(StTrn, "1\\.[MN]")) IPhoD2_oral <- IPhoD2 %>% filter(str_detect(StTrn, "1\\.[^MN]")) for(i in 1:nrow(IPhoD2)) { current_word <- IPhoD2[i, "StTrn"] current_word_nasal <- str_detect(current_word, "1\\.[MN]") if(current_word_nasal == TRUE) { VC_VN <- paste(IPhoD2_oral[str_detect(IPhoD2_oral$StTrn, str_replace(current_word, "1\\.[MN]G?", "1\\..{1,3}")) & nchar(IPhoD2_oral$StTrn) == nchar(current_word), "Word"], collapse = ";") } if(current_word_nasal == FALSE) { VC_VN <- paste(IPhoD2_nasal[str_detect(IPhoD2_nasal$StTrn, str_replace(current_word, "1\\..{1,2}", "1\\.[MN]G?")) & nchar(IPhoD2_nasal$StTrn) == nchar(current_word), "Word"], collapse = ";") } if(nchar(VC_VN) != 0) {IPhoD2[i, "VC_VN"] <- VC_VN} }
数据结构
IPhoD2 <- structure(list(Word = c("again", "box", "center", "feral", "shadows", "stub", "stuck", "stuff", "stun", "stung", "whim", "win", "wing", "wish", "wit", "with"), StTrn = c("AH0.G.EH1.N", "B.AA1.K.S", "S.EH1.N.T.ER0", "F.EH1.R.AH0.L", "SH.AE1.D.OW2.Z", "S.T.AH1.B", "S.T.AH1.K", "S.T.AH1.F", "S.T.AH1.N", "S.T.AH1.NG", "HH.W.IH1.M", "W.IH1.N", "W.IH1.NG", "W.IH1.SH", "W.IH1.T", "W.IH0.DH"), NPhon = c(4L, 4L, 5L, 5L, 5L, 4L, 4L, 4L, 4L, 4L, 4L, 3L, 3L, 3L, 3L, 3L)), row.names = c(1L, 3L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 15L, 16L, 17L, 18L, 19L), class = "data.frame")
高效解决方案
核心思路是预先生成匹配键,避免逐行循环和重复正则匹配,利用向量运算和分组匹配大幅提升效率。
步骤1:标记类型并生成匹配键
为每个转录生成统一的"模板键",把需要替换的片段(鼻音或非鼻音后缀)替换为占位符,相同模板的转录会被归为一组:
library(dplyr) library(stringr) # 预处理数据:标记鼻音/非鼻音,生成匹配键 IPhoD2_processed <- IPhoD2 %>% mutate( # 标记是否为鼻音转录(包含1.M/1.N/1.NG) is_nasal = str_detect(StTrn, "1\\.(M|N|NG)"), # 生成匹配键:将目标替换段替换为统一占位符 match_key = case_when( is_nasal ~ str_replace(StTrn, "1\\.(M|N|NG)", "1.XXX"), TRUE ~ str_replace(StTrn, "1\\.[A-Z0-9]{1,2}", "1.XXX") ) ) # 分离鼻音组和非鼻音组 nasal_group <- IPhoD2_processed %>% filter(is_nasal) oral_group <- IPhoD2_processed %>% filter(!is_nasal)
步骤2:批量匹配并生成结果列
通过匹配键分组,直接合并同组内的对应类型单词,无需逐行循环:
# 为鼻音组匹配对应的非鼻音单词 nasal_matches <- nasal_group %>% left_join(oral_group %>% select(match_key, Word_oral = Word), by = "match_key") %>% group_by(Word, StTrn, NPhon) %>% summarise(VC_VN = paste(Word_oral, collapse = ";"), .groups = "drop") %>% filter(!is.na(VC_VN)) # 为非鼻音组匹配对应的鼻音单词 oral_matches <- oral_group %>% left_join(nasal_group %>% select(match_key, Word_nasal = Word), by = "match_key") %>% group_by(Word, StTrn, NPhon) %>% summarise(VC_VN = paste(Word_nasal, collapse = ";"), .groups = "drop") %>% filter(!is.na(VC_VN)) # 合并结果到原数据框 final_result <- IPhoD2 %>% left_join(bind_rows(nasal_matches, oral_matches), by = c("Word", "StTrn", "NPhon"))
步骤3:查看结果
打印final_result即可得到与期望一致的输出,处理33000行数据的时间会缩短至几秒到几十秒。
效率提升原因
- 摆脱了逐行循环的O(n²)时间复杂度,改为O(n)的向量运算和分组匹配
- 预先生成匹配键,减少重复正则替换和检测的开销
- 利用dplyr的分组聚合操作,比原生循环更高效
内容的提问来源于stack exchange,提问作者Ian Carpick
相关产品推荐
相关产品推荐

