通过 gsub / regex 从混乱的向量中选择数字

Selecting digits from messy vector via gsub / regex

我正在使用一个向量 vecA 对应于下面生成的向量:

vecA <- c("[  0, 10)", "[ 10, 20)", "[ 20, 30)", "[ 50, 60)", "[ 90,100]")

我想达到 vecB 删除特殊字符并插入连字符的目的,如以下生成的示例所示:

vecB <- c("0 - 10", "10 - 20", "20 - 30", "50 - 60", "90 - 100")

问题

我有 gsub 语法,几乎 有效:

vecB <- gsub(pattern =
                 "^(\[{1})([[:blank:]]*)(\d{1,2})([,])(.*)(\d{2,3})([[:punct:]])$",
                          x = vecA, replacement = "\3 - \6")

唯一的问题是值 [ 90,100] 被错误地转换为 90 - 00 而不是 90 - 100 应该是 (regex101).

我们可以使用捕获组,即在 (..) 中获取数字部分并删除所有其他部分,即非数字 (\D+)。

在下面的模式中,我们匹配一个或多个非数字元素(\D+- 它包括 [ 和它后面的白色 space),然后捕获一个或更多数字((\d+)),然后匹配一个或多个非数字(\D+-匹配,和白色space),第二个捕获数字组(\d+) 后跟 .* 即它匹配字符串的其余部分直到它的末尾。在替换中,我们指定反向引用 (\1) 后跟 space 然后是 - 和第二个反向引用 ('\2`).

sub('\D+(\d+)\D+(\d+).*', '\1 - \2', vecA)
#[1] "0 - 10"   "10 - 20"  "20 - 30"  "50 - 60"  "90 - 100"

编辑:基于@Wiktor Stribiżew 的评论


或者我们可以使用 library(stringr) 中的 str_extract 来提取数字,然后 paste 一起

library(stringr)
sapply(str_extract_all(vecA, '[0-9]+'), paste, collapse=' - ')
#[1] "0 - 10"   "10 - 20"  "20 - 30"  "50 - 60"  "90 - 100"

1) sub/gsub 这可以分成两个简单的 sub/gsub 调用。内部 gsub 将任何不是数字或逗号的内容替换为空字符串,外部 sub 将逗号转换为 space-minus-space.

sub(",", " - ", gsub("[^0-9,]", "", vecA))
## [1] "0 - 10"   "10 - 20"  "20 - 30"  "50 - 60"  "90 - 100"

2) 一个子 用一个sub:

sub("^\D*(\d+)\D*(\d+)\D*$", "\1 - \2", vecA)
## "0 - 10"   "10 - 20"  "20 - 30"  "50 - 60"  "90 - 100"

3) substring/read.table 这个不使用 sub 或 gsub 或任何正则表达式:

with(read.table(text = substring(vecA, 2, nchar(vecA)-1), sep = ","), paste(V1, "-", V2))
## [1] "0 - 10"   "10 - 20"  "20 - 30"  "50 - 60"  "90 - 100"

3a) (3) 的变体略短:

with(read.table(text = gsub("\D", " ", vecA)), paste(V1, "-", V2))
## [1] "0 - 10"   "10 - 20"  "20 - 30"  "50 - 60"  "90 - 100"

4) gsubfn 这会提取捕获组并执行指定的 paste:

library(gsubfn)
strapply(vecA, "(\d+)\D*(\d+)", ~ paste(x, "-", y), simplify = c)
## [1] "0 - 10"   "10 - 20"  "20 - 30"  "50 - 60"  "90 - 100"

4a) (4) 的变体,使用 stapplyc 而不是 strapply:

library(gsubfn)
sapply(strapplyc(vecA, "\d+"), paste, collapse = " - ")
## [1] "0 - 10"   "10 - 20"  "20 - 30"  "50 - 60"  "90 - 100"

4b) (4) 的变体,使用 gsubfn 而不是 strapply:

library(gsubfn)
gsubfn("\D+", ~ if (grepl(",", x)) " - " else "", vecA)
## [1] "0 - 10"   "10 - 20"  "20 - 30"  "50 - 60"  "90 - 100"

5) strsplit 这是另一种不使用sub或gsub的解决方案:

f <- function(x) {
  paste0(ifelse(x == ",", " - ", ifelse(x %in% 0:9, x, "")), collapse = "")
}
sapply(strsplit(vecA, ""), f)
## [1] "0 - 10"   "10 - 20"  "20 - 30"  "50 - 60"  "90 - 100"

提醒我,我正在很好地解决标记间隔的相同问题。这是我的结果,忽略其中没有正则表达式:

library(dplyr)

# 1-9 by one, up to 75 by 5, up to 300 by 50, rest by 100
c(0:9, 
    seq(14, 50, by=5), 
    seq(59, 100, by=10), 
    seq(149, 300, by=50), 
    seq(400, 1000, by=100)) ->
  breaks

# create nice labels for the intervals
# assuming integral numbers will be cut by the breaks (hence the `l + 1`)
data.frame(l = breaks[1:length(breaks) - 1],
           r = breaks[2:length(breaks)]) %>%
               mutate(diff = r - l,
                      lab = ifelse(diff > 1, 
                                   paste0(l + 1, " - ", r), 
                                   as.character(r))) ->
  labs

# and cut() the data in `pos` colum getting directly the factors with
# nice names
d %>% mutate(bin=cut(pos, breaks, labels=labs$lab))