【问题标题】:substring characters from a column in a data.table in RR中data.table中列的子字符串字符
【发布时间】:2016-01-26 00:02:57
【问题描述】:

是否有更“r”的方式从 data.table 中的列中的较长字符串中提取两个有意义的字符?

我有一个 data.table,其中有一列包含“学位字符串”...某人获得的学位和毕业年份的简写代码。

> srcDT<- data.table(
    alum=c("Paul Lennon","Stevadora Nicks","Fred Murcury"),
    degree=c("W72","WG95","W88")
    )

> srcDT
               alum degree
1:      Paul Lennon    W72
2:  Stevadora Nicks   WG95
3:     Fred Murcury    W88

我需要从学位中提取年份的数字,并将其放入一个名为“degree_year”的新列中

没问题:

> srcDT[,degree_year:=substr(degree,nchar(degree)-1,nchar(degree))]

> srcDT
                alum degree degree_year
 1:      Paul Lennon    W72          72
 2:  Stevadora Nicks   WG95          95
 3:     Fred Murcury    W88          88

要是一直这么简单就好了。 问题是,度数字符串有时看起来像上面那样。更多时候,它们看起来像这样:

srcDT<- data.table(
  alum=c("Ringo Harrison","Brian Wilson","Mike Jackson"),
  degree=c("W72 C73","WG95 L95","W88 WG90")
)

我只对我关心的字符旁边的 2 个数字感兴趣:W 和 WG(如果 W 和 WG 都在,我只关心 WG)

我是这样解决的:

x <-srcDT$degree                     ##grab just the degree column
z <-character()                       ## create an empty character vector
degree.grep.pattern <-c("WG[0-9][0-9]","W[0-9][0-9]")
                                     ## define a vector of regex's, in the order
                                     ## I want them

for(i in 1:length(x)){               ## loop thru all elements in degree column
  matched=F                          ## at the start of the loop, reset flag to F
  for(j in 1:length(degree.grep.pattern)){
                                     ## loop thru all elements of the pattern vector

    if(length(grep(degree.grep.pattern[j],x[i]))>0){
                                     ## see if you get a match

      m <- regexpr(degree.grep.pattern[j],x[i])
                                     ## if you do, great! grab the index of the match
      y<-regmatches(x[i],m)          ## then subset down.  y will equal "WG95"
      matched=T                      ## set the flag to T
      break                          ## stop looping
    }
                                     ## if no match, go on to next element in pattern vector
  }

  if(matched){                       ## after finishing the loop, check if you got a match
    yr <- substr(y,nchar(y)-1,nchar(y))
                                     ## if yes, then grab the last 2 characters of it
  }else{
    #if you run thru the whole list and don't match any pattern at all, just
    # take the last two characters from the affilitation
    yr <- substr(x[i],nchar(as.character(x[i]))-1,nchar(as.character(x[i])))
  }
  z<-c(z,yr)                         ## add this result (95) to the character vector
}
srcDT$degree_year<-z                ## set the column to the results.

> srcDT
             alum   degree degree_year
1: Ringo Harrison  W72 C73          72
2:   Brian Wilson WG95 L95          95
3:   Mike Jackson W88 WG90          90

这行得通。 100% 的时间。没有错误,没有错配。 问题是:它无法扩展。给定一个有 10k 行或 100k 行的数据表,它确实会变慢。

有没有更聪明、更好的方法来做到这一点?这个解决方案对我来说非常“C”。不是很“R”。

有改进的想法?

注意:我举了一个简化的例子。在实际数据中,大约有 30 种不同的可能的学位组合,结合不同的年份,大约有 540 种不同的学位字符串组合。 另外,我给了 degree.grep.pattern,只有 2 个模式可以匹配。在我做的实际工作中,有 7 或 8 种模式可以匹配。

【问题讨论】:

  • 我想你想要WG,因为它是最近的? 2001 年授予的学位是W01 吗?每个alum 的度数是否相同(不,我假设)?

标签: regex r data.table


【解决方案1】:

看起来(每个 OP)cmets,没有"WG W" 的情况,那么一个简单的正则表达式解决方案应该可以完成这项工作

srcDT[ , degree_year := gsub(".*WG?(\\d+).*", "\\1", degree)]
srcDT
#              alum   degree degree_year
# 1: Ringo Harrison  W72 C73          72
# 2:   Brian Wilson WG95 L95          95
# 3:   Mike Jackson W88 WG90          90

【讨论】:

  • 我认为"[^S]*W[^0-9]*(\\d+).*" 符合 OP 在我的 cmets 中描述的所有限制
  • @MichaelChirico 是的,我已经想到了类似的解决方案,但它不适用于"SW87 WG45" 之类的东西。我认为 OP 应该描述他的所有限制和可能的组合,因为我不熟悉什么可以或不可以发生。
  • 同意。 @BenAdams 您能否将您的示例 srcDT 编辑为包含所有相关案例的内容?
【解决方案2】:

这是一个基于以下假设的解决方案:想要最近的学位,其中包含W

regex <- "(?<=W|(?<=W)G)[0-9]{2}"

srcDT[ , degree_year := 
         sapply(regmatches(degree, 
                           gregexpr(regex, degree, perl = TRUE)),
                function(x) max(as.integer(x)))]

> srcDT
             alum   degree degree_year
1: Ringo Harrison  W72 C73          72
2:   Brian Wilson WG95 L95          95
3:   Mike Jackson W88 WG90          90

你说:

我给了degree.grep.pattern,只有 2 个模式可以匹配。在我做的实际工作中,有 7 或 8 种模式可以匹配。

但我不确定这意味着什么。除了WWG之外还有更多选择吗?

【讨论】:

  • 这很漂亮。非常好。但是,你是对的。除了 WWG 之外,还有其他选项,还有 GRWWAM... 最重要的是有一些 SW不想想要匹配。还有极少数,极少数,根本不匹配任何模式。您将如何更改此代码以解决这些问题?
  • 嗯,如果您已经在使用 srcDT[ , degree_year := gsub(".*((?&lt;=W|(?&lt;=W)G)[0-9]{2}).*", "\\1", degree, perl = TRUE)],为什么不这样做呢?顺便说一句,"WG88 W90" 将失败。
  • 你可以只用srcDT[ , degree_year := gsub(".*WG?(\\d+).*", "\\1", degree)](没有perl)实现同样的效果,但"WG88 W90"也会失败
  • @DavidArenburg 我正在(试图)使用一些真实世界的信息——即,“G”表示“研究生学位”,因此"WG88 W90" 不太可能存在于数据中。感谢gsub的建议,我会编辑进去。
  • @MichaelChirico 你明白了。 G 确实意味着研究生学位
【解决方案3】:

这是一个快速的技巧:

# split all words from degree and order so that WG is before W
words <- lapply(strsplit(srcDT$degree, " "), sort, decreasing=TRUE)

# obtain tags for each row (getting only first. But works since ordered)
tags <- mapply(Find, list(function(x) grepl("^WG|^W", x)), words)

# simple gsub to remove WG and W
(result <- gsub("^WG|^W", "", tags))
[1] "72" "95" "90"

100k 行速度很快。

【讨论】:

    【解决方案4】:

    一个没有正则表达式的解决方案,它创建一个稀疏表很慢......但它干净灵活,所以我把它留在这里。

    首先我按空间分割学位年,然后浏览它们并构建一个干净的结构化表格,每个学位有一列,我用年份填充它。

    degreeyear_split <- sapply(srcDT$degree,strsplit," ") 
    for(i in 1:nrow(srcDT)){
      for (degree_year in degreeyear_split[[i]]){
        n <- nchar(degree_year)
        degree <- substr(degree_year,1,n-2)
        year <- substr(degree_year,n-1,n)
        srcDT[i,degree] <- year  
      }}
    

    这里有我的结构表,我在我感兴趣的年份粘贴 W,然后在上面粘贴 WG。

    srcDT$year <- srcDT$W
    srcDT$year[srcDT$WG!=""]<-srcDT$WG[srcDT$WG!=""]
    

    那么这是你的结果:

    srcDT
                 alum   degree  W  C WG  L year
    1: Ringo Harrison  W72 C73 72 73         72
    2:   Brian Wilson WG95 L95       95 95   95
    3:   Mike Jackson W88 WG90 88    90      90
    

    【讨论】:

      猜你喜欢
      • 2021-03-26
      • 2021-12-07
      • 1970-01-01
      • 2023-03-20
      • 2020-07-01
      • 1970-01-01
      • 2021-03-24
      • 2018-05-30
      • 1970-01-01
      相关资源
      最近更新 更多