【问题标题】:regexp R - extract string between commasregexp R - 提取逗号之间的字符串
【发布时间】:2019-06-26 23:50:11
【问题描述】:

因为我的 csv 文件已损坏,我正在使用以下方法将其读入 R:

dataDT <- data.table::fread(".../test.csv", sep = NULL)

它给出的数据集类似于:

dataDT <- data.table("ColA,ColB,ColC,ColD" = c("1,10,some text... , some text,,20190801",
                                               "2,22,some text... , some text,,20190801",
                                               "3,30,some text... , some text,,20170601"))
dataDT
> dataDT
                       ColA,ColB,ColC,ColD
1: 1,10,some text... , some text,,20190801
2: 2,22,some text... , some text,,20190801
3: 3,30,some text... , some text,,20170601

现在,我想要将每行中的字符串拆分为 4 个新列

targetDT <- data.table(ColA = c(1,2,3), 
                       ColB = c(10,22,30), 
                       ColC = c("some text... , some text,", "some text... , some text,", "some text... , some text,"),
                       ColD = c("20190801","20190801",'20170601'))
targetDT
> targetDT
   ColA ColB                      ColC     ColD
1:    1   10 some text... , some text, 20190801
2:    2   22 some text... , some text, 20190801
3:    3   30 some text... , some text, 20170601

逻辑是:

  • ColA 在第一个逗号之前有字符串;
  • ColB 包含第一个和第二个逗号之间的所有内容;
  • ColD 在最后一个逗号后有字符串;
  • ColC 在中间部分有字符串(它可能包含额外的逗号)。

上面的逻辑如何实现?

编辑_1:

敏感数据,抱歉我无法提供确切数据。它看起来像:

        ID,Code1,Project_Name,Report_Date
1:     123123,1, A & B,20100101
2:     1413,2, C, D and E,20120101
3: 53163,333, F, G,20140303
4: 23453,44,This is a name,20160801
5: 12645,555,5th test, to continue,20190501

所以,第一个逗号之前的所有内容肯定是数字,第一个和第二个逗号之间的所有内容也是如此。最后一个逗号之后的所有内容绝对是一个类似日期的 8 位数字。中间部分可以包含多个逗号,但不能包含引号(我猜这就是 fread 将逗号视为分隔符的原因)。

【问题讨论】:

  • 请显示一些确切的数据。 some text 可能不够具体,无法有人帮助您。
  • 如果您可以在逗号处逻辑拆分数据,那么fread 会首先读取它。如果不能,则表明您在未引用的字符串中间有逗号。如果您可以定义一个规则来拆分这些字段*除了逗号(或删除那些特定的逗号),那么您将拥有它。查看数据中可用于实现这一目标的模式
  • 每一列数据都可以放入编号数组中。稍后,序列中的每个数组元素都可用于创建一行。使用全局类型正则表达式调用来获取每个列数组。 (?:(?:^|,)\s*)(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([^,]*))(?:\s*(?:(?=,)|$)) 循环执行此操作,每个匹配都是一个新元素,是第 1 组和第 2 组和第 3 组的加入。请看这里regex101.com/r/6uSkJI/1

标签: r regex data.table gsub


【解决方案1】:

这是一个regex 模式,它允许您替换与前导两个数字值和终止数字值相邻的逗号字符,并将它们彼此分隔,并用空格分隔周围的文本。使用非空格字符进行分隔可能更安全,因为我的下一步是使用read.* 函数之一或再次使用fread 读取这些“行”。也许使用“|”作为分隔符?

sub("(\\d+)[,](\\d+)[,](.+)[,](\\d+)$", 
    "\\1 \\2 '\\3' \\4", 
    dataDT$"ColA,ColB,ColC,ColD" )

[1] "1 10 'some text... , some text,' 20190801" "2 22 'some text... , some text,' 20190801"
[3] "3 30 'some text... , some text,' 20170601"

模式中的括号用于创建“捕获类”,并且在每种情况下,我都使用“\d+”模式“捕获”了任意数量的数字或小数分隔符。我还在替换模式上用单引号将文本(用“.+”捕获)括起来,因此第三列想要成为的“内部空间”不会被读取为分隔符。 "\\1""\\2" 等是对每个捕获类中捕获的字符的引用,按照它们在模式中出现的顺序排列。见?regex。但是,如果您使用不同的分隔符,则不需要单引号。

这是一个使用“|”的测试作为sep

fread(text =sub("(\\d+)[,](\\d+)[,](.+)[,](\\d+)", "\\1|\\2|'\\3'|\\4", dataDT$"ColA,ColB,ColC,ColD" ) ,sep="|")
   V1 V2                          V3       V4
1:  1 10 'some text... , some text,' 20190801
2:  2 22 'some text... , some text,' 20190801
3:  3 30 'some text... , some text,' 20170601

注意:如果您的数值有逗号或前导或尾随货币,您需要更改示例,因为使用 "\\d" 捕获数字字符组将不再成功。

【讨论】:

  • 这样的答案,以及对原因和方法的完整解释,是让我一次又一次地回到 SO 的原因。谢谢@42!
【解决方案2】:

使用 stringr 的解决方案

library(data.table)
library(stringr)
library(dplyr)

dataDT <- data.table(data = c("1,10,some text... , some text,,20190801",
                               "2,22,some text... , some text,,20190801",
                               "3,30,some text... , some text,,20170601"))

dataDT <- dataDT %>% 
    mutate(
        ColA = str_extract(data, "^[^,]*(?=,)"),
        ColB = str_extract(data, "(?<=,)[^,]*(?=,)"),
        ColD = str_extract(data, "(?<=,)[^,]*$"),
        ColC = str_sub(data, nchar(ColA)+nchar(ColB)+3, nchar(data)-nchar(ColD)-1)
    ) %>% 
    select(ColA, ColB, ColC, ColD)

dataDT
#>   ColA ColB                      ColC     ColD
#> 1    1   10 some text... , some text, 20190801
#> 2    2   22 some text... , some text, 20190801
#> 3    3   30 some text... , some text, 20170601

reprex package (v0.3.0) 于 2019 年 6 月 27 日创建

【讨论】:

  • 喜欢这个!谢谢。
【解决方案3】:

到目前为止发布的答案建议使用正则表达式的解决方案。

或者,可以考虑列的位置。正如OP指出的那样

  • ColA 在第一个逗号之前有字符串;
  • ColB 包含第一个和第二个逗号之间的所有内容;
  • ColD 在最后一个逗号后面有字符串;
  • ColC 在中间部分有字符串(它可能包含额外的逗号)。

这个想法是像往常一样使用fread()sep = "," 读取文件,这会导致数据集未对齐。在整形为长格式后,可以每行识别第一列、第二列和最后一列以及中间列。这些条目可以给出各自的列名。在最终重塑为宽格式期间,中间列被折叠成 ColC

library(data.table)
# read file
DT <- fread("
1,10,some text...  some text,,20190801
2,22,some text... , some text,,20190801
3,30,some text... ,, some text,,20170601"
            , sep = ","
            , fill = TRUE
            , header = FALSE
            , strip.white = FALSE)

请注意,示例数据集已通过插入额外的逗号进行修改,以获得更真实的测试用例。

读取操作的结果是一个未对齐且参差不齐的数据集:

DT
   V1 V2                      V3         V4         V5       V6       V7
1:  1 10 some text...  some text              20190801       NA       NA
2:  2 22           some text...   some text            20190801       NA
3:  3 30           some text...              some text       NA 20170601
cols <- c("ColA", "ColB", "ColC", "ColD")
# reshape from wide to long format
long <- melt(DT[, rn := .I], "rn", na.rm = TRUE)
# create lookup table to rename column names  
lut <- long[, .(variable, col = rep(cols, c(1L, 1L, .N - 3, 1L))), by = rn]
# rename columns by an update join
long[lut,  on = .(rn, variable), variable := col][]
# reshape and collapse 
dcast(long, rn ~ variable, paste, collapse = ",")
   ColA ColB                      ColC     ColD
1:    1   10  some text...  some text, 20190801
2:    2   22 some text... , some text, 20190801
3:    3   30 some text... ,, some text 20170601

如果我们查看中间结果,可以更好地解释该方法。

melt()之后,long

    rn variable                   value
 1:  1       V1                       1
 2:  2       V1                       2
 3:  3       V1                       3
 4:  1       V2                      10
 5:  2       V2                      22
 6:  3       V2                      30
 7:  1       V3 some text...  some text
 8:  2       V3           some text... 
 9:  3       V3           some text... 
10:  1       V4                        
11:  2       V4               some text
12:  3       V4                        
13:  1       V5                20190801
14:  2       V5                        
15:  3       V5               some text
16:  2       V6                20190801
17:  3       V7                20170601

由此创建查找表lut

    rn variable  col
 1:  1       V1 ColA
 2:  1       V2 ColB
 3:  1       V3 ColC
 4:  1       V4 ColC
 5:  1       V5 ColD
 6:  2       V1 ColA
 7:  2       V2 ColB
 8:  2       V3 ColC
 9:  2       V4 ColC
10:  2       V5 ColC
11:  2       V6 ColD
12:  3       V1 ColA
13:  3       V2 ColB
14:  3       V3 ColC
15:  3       V4 ColC
16:  3       V5 ColC
17:  3       V7 ColD

在更新加入之后和重塑回宽格式之前long 看起来像

    rn variable                   value
 1:  1     ColA                       1
 2:  2     ColA                       2
 3:  3     ColA                       3
 4:  1     ColB                      10
 5:  2     ColB                      22
 6:  3     ColB                      30
 7:  1     ColC some text...  some text
 8:  2     ColC           some text... 
 9:  3     ColC           some text... 
10:  1     ColC                        
11:  2     ColC               some text
12:  3     ColC                        
13:  1     ColD                20190801
14:  2     ColC                        
15:  3     ColC               some text
16:  2     ColD                20190801
17:  3     ColD                20170601

现在,数据项已与其各自的列名对齐。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-24
    相关资源
    最近更新 更多