如何用data.table高效按首个分隔符拆分字符串?
问题描述
现有如下结构的data.table数据:
require(data.table) dt = data.table(c('string1: val1', 'gnistr2: val2', 'ingstr3: :::!val3', 'gtrins4: val4'))
原始表格输出:
V1 1: string1: val1 2: gnistr2: val2 3: ingstr3: :::!val3 4: gtrins4: val4
目标是仅按首个分隔符:拆分V1列,得到N×2的表格,要求保留值部分的所有分隔符(比如第三行的:::!val3需完整保留)。
已基于stringi库的stri_split_fixed(n=2)实现可行方案,但认为dcast操作存在不必要开销,希望优化效率:
require(stringi) dt1 = dt[, .(val = unlist(stri_split_fixed(V1, ':', n=2))), .(r = seq_along(V1))] dt1[, var := paste0('COL', 1:.N), r] dt1 = dcast(dt1, r ~ var, value.var = 'val')[, .(COL1, COL2)]
期望输出:
COL1 COL2 1: string1 val1 2: gnistr2 val2 3: ingstr3 :::!val3 4: gtrins4 val4
当前有两个优化需求:
- 如何高效将以下列表列结构的
dt2转换为上述期望输出?
dt2 = dt[, .(listcol = stri_split_fixed(V1, ':', n=2)), .(r = seq_along(V1))][, .(listcol)]
dt2的内部结构:
> dt2[[1]] [[1]] [1] "string1" " val1" [[2]] [1] "gnistr2" " val2" [[3]] [1] "ingstr3" " :::!val3" [[4]] [1] "gtrins4" " val4"
- 是否存在无需列表列的更快捷方法?
解决方案
一、直接处理列表列dt2的高效方法
利用data.table的列表列特性,跳过dcast直接拆分出目标列:
# 方法1:用sapply提取列表元素 dt_result = dt2[, .(COL1 = sapply(listcol, `[`, 1), COL2 = sapply(listcol, `[`, 2))] # 方法2:用rbind直接展开列表 dt_result = dt2[, do.call(rbind, listcol)] setnames(dt_result, c("COL1", "COL2"))
两种方法都能快速将列表列转换为两列表格,避免dcast的额外开销。
二、无需列表列的更快捷方法
方法1:用data.table内置tstrsplit指定拆分次数
tstrsplit原生支持n参数限制拆分次数,直接按首个:拆分:
dt[, c("COL1", "COL2") := tstrsplit(V1, ":", fixed = TRUE, n = 2)] # 可选:去除COL2开头的空格(按需处理) dt[, COL2 := trimws(COL2)] dt[, .(COL1, COL2)]
fixed=TRUE确保按固定字符串拆分而非正则,n=2保证仅拆分一次,完美保留值部分的所有分隔符。
方法2:用stringi直接拆分并赋值
无需生成中间列表列,直接在原表上添加目标列:
require(stringi) # 简洁写法 dt[, temp := stri_split_fixed(V1, ":", n=2)][, c("COL1", "COL2") := .(sapply(temp, `[`, 1), sapply(temp, `[`, 2))][, temp := NULL]
方法3:正则表达式匹配
无需额外依赖库,用非贪婪匹配定位首个::
dt[, c("COL1", "COL2") := .(sub("^(.*?):.*", "\\1", V1), sub("^.*?:(.*)", "\\1", V1))] dt[, COL2 := trimws(COL2)] dt[, .(COL1, COL2)]
.*?的非贪婪特性确保只匹配到首个:为止,精准拆分目标内容。
内容的提问来源于stack exchange,提问作者JDG
相关产品推荐
相关产品推荐

