从 R 中的整数拆分字符串
Splitting strings from integers in R
我最近在尝试时遇到了一个有趣的问题
创建自定义数据库。
我的行的格式是:
183746IGH
105928759UBS
等等(所以基本上是一个整数与一个字符串连接,两者的大小都是相对随机的。)。我想做的是以某种方式将第 1 列中的整数与第 2 列中的所有其他内容(字母)分开。如何做到这一点?我一直在尝试使用 strsplit,但它似乎没有提供这种功能。
感谢您的帮助。
你可以这样做:
df <- data.frame(V1 = c("adad131341", "adadar45365", "cavsbsb425", "daadvsv46567567"))
library(dplyr)
library(stringr)
df %>% mutate(V2 = str_extract(V1, "[0-9]+"),
V3 = str_extract(V1, "[aA-zZ]+"))
给出:
# V1 V2 V3
#1 adad131341 131341 adad
#2 adadar45365 45365 adadar
#3 cavsbsb425 425 cavsbsb
#4 daadvsv46567567 46567567 daadvsv
另一种使用 base-R 和正则表达式的方法:
all <- c(' 183746IGH','105928759UBS')
numeric <- sapply(a, function(x) sub('[[:alpha:]]+','', x))
alphabetic <- sapply(a, function(x) sub('[[:digit:]]+','', x))
> data.frame(all,alphabetic,numeric)
all alphabetic numeric
183746IGH 183746IGH IGH 183746
105928759UBS 105928759UBS UBS 105928759
或根据@rawr 的下方评论:
> read.table(text = gsub('(\d)(\D)', '\1 \2', all))
V1 V2
1 183746 IGH
2 105928759 UBS
或以上函数的矢量化版本:
get_alphanum <- function(x, type) {
type <- switch(type,
alpha = '[[:digit:]]+',
digit = '[[:alpha:]]+')
sub(type,'', x)
}
get_alphanum <- Vectorize(get_alphanum)
它给出了直接应用于向量的结果!
> get_alphanum(all, type='alpha')
183746IGH 105928759UBS
" IGH" "UBS"
> get_alphanum(all, type='digit')
183746IGH 105928759UBS
" 183746" "105928759"
也可用于创建 data.frame:
> data.frame(all,
alpha=get_alphanum(all, type='alpha') ,
numeric=get_alphanum(all, type='digit'))
all alpha numeric
183746IGH 183746IGH IGH 183746
105928759UBS 105928759UBS UBS 105928759
gsubfn 包中的 read.pattern
可以做到这一点。 pattern
参数中给出的正则表达式的每个括号部分将被读入单独的列:
x <- c("183746IGH", "105928759UBS")
library(gsubfn)
read.pattern(text = x, pattern = "(\d+)(\D+)")
给予:
V1 V2
1 183746 IGH
2 105928759 UBS
其他选项包括 tstrsplit
来自 data.table
的开发版本
library(data.table)#v1.9.5+
setDT(df)[,tstrsplit(V1,'(?<=\d)(?=\D)', perl=TRUE, type.convert=TRUE)]
# V1 V2
#1: 131341 adad
#2: 45365 adadar
#3: 425 cavsbsb
#4: 46567567 daadvsv
如果有元素 'non-numeric' 部分首先出现,'numeric' 最后出现,那么,我们可以使用更通用的选项作为正则表达式模式,
setDT(df)[,tstrsplit(V1, "(?<=\d)(?=\D)|(?<=\D)(?=\d)",
perl = TRUE)]
或使用 extract
来自 tidyr
library(tidyr)
extract(df, V1, into=c('V1', 'V2'), '(\d+)(\D+)', convert=TRUE)
# V1 V2
#1 131341 adad
#2 45365 adadar
#3 425 cavsbsb
#4 46567567 daadvsv
如果您也需要原始列,
extract(df, V1, into=c('V2', 'V3'), '(\d+)(\D+)',
convert=TRUE, remove=FALSE)
# V1 V2 V3
#1 131341adad 131341 adad
#2 45365adadar 45365 adadar
#3 425cavsbsb 425 cavsbsb
#4 46567567daadvsv 46567567 daadvsv
对于 data.table
,我们可以使用 :=
创建新列,以便现有列保留在输出中,即
setDT(df)[,paste0('V',2:3):=tstrsplit(V1,'(?<=\d)(?=\D)',
perl=TRUE, type.convert=TRUE)]
# V1 V2 V3
#1: 131341adad 131341 adad
#2: 45365adadar 45365 adadar
#3: 425cavsbsb 425 cavsbsb
#4: 46567567daadvsv 46567567 daadvsv
注意:两种解决方案都可以选择转换拆分列 (type.convert/convert
) 的 class。
数据
df <- data.frame(V1 = c("131341adad", "45365adadar", "425cavsbsb",
"46567567daadvsv"))
strsplit
如果您提供正确的正则表达式作为拆分依据,则确实有效。
在这种情况下,你会想要这样的东西:
strsplit(String, split = "(?<=[a-zA-Z])(?=[0-9])", perl = TRUE)
此处应用于@Steven 的示例数据:
strsplit(as.character(df$V1), split = "(?<=[a-zA-Z])(?=[0-9])", perl = TRUE)
# [[1]]
# [1] "adad" "131341"
#
# [[2]]
# [1] "adadar" "45365"
#
# [[3]]
# [1] "cavsbsb" "425"
#
# [[4]]
# [1] "daadvsv" "46567567"
过去有一段时间我编写了一个函数来执行此操作,因为老实说,我的大脑并不经常用正则表达式思考。该函数如下所示:
SplitMe <- function(string, alphaFirst = TRUE, bind = FALSE) {
if (!is.character(string)) string <- as.character(string)
Pattern <- ifelse(isTRUE(alphaFirst),
"(?<=[a-zA-Z])(?=[0-9])",
"(?<=[0-9])(?=[a-zA-Z])")
out <- strsplit(string, split = Pattern, perl = TRUE)
if (isTRUE(bind)) {
require(data.table)
as.data.table(do.call(rbind, out))
} else {
out
}
}
预期用途类似于:
library(data.table)
as.data.table(df)[, c("char", "num") := SplitMe(V1, bind = TRUE)][]
# V1 char num
# 1: adad131341 adad 131341
# 2: adadar45365 adadar 45365
# 3: cavsbsb425 cavsbsb 425
# 4: daadvsv46567567 daadvsv 46567567
一旦您知道该模式,您就可以在其他使用 strsplit
的地方使用它,例如 "tidyr" 中的 separate
,它可以方便地将值分隔到列中:
library(dplyr)
library(tidyr)
df %>%
separate(V1, into = c("char", "num"),
sep = "(?<=[a-zA-Z])(?=[0-9])", perl = TRUE)
# char num
# 1 adad 131341
# 2 adadar 45365
# 3 cavsbsb 425
# 4 daadvsv 46567567
我最近在尝试时遇到了一个有趣的问题 创建自定义数据库。
我的行的格式是:
183746IGH
105928759UBS
等等(所以基本上是一个整数与一个字符串连接,两者的大小都是相对随机的。)。我想做的是以某种方式将第 1 列中的整数与第 2 列中的所有其他内容(字母)分开。如何做到这一点?我一直在尝试使用 strsplit,但它似乎没有提供这种功能。
感谢您的帮助。
你可以这样做:
df <- data.frame(V1 = c("adad131341", "adadar45365", "cavsbsb425", "daadvsv46567567"))
library(dplyr)
library(stringr)
df %>% mutate(V2 = str_extract(V1, "[0-9]+"),
V3 = str_extract(V1, "[aA-zZ]+"))
给出:
# V1 V2 V3
#1 adad131341 131341 adad
#2 adadar45365 45365 adadar
#3 cavsbsb425 425 cavsbsb
#4 daadvsv46567567 46567567 daadvsv
另一种使用 base-R 和正则表达式的方法:
all <- c(' 183746IGH','105928759UBS')
numeric <- sapply(a, function(x) sub('[[:alpha:]]+','', x))
alphabetic <- sapply(a, function(x) sub('[[:digit:]]+','', x))
> data.frame(all,alphabetic,numeric)
all alphabetic numeric
183746IGH 183746IGH IGH 183746
105928759UBS 105928759UBS UBS 105928759
或根据@rawr 的下方评论:
> read.table(text = gsub('(\d)(\D)', '\1 \2', all))
V1 V2
1 183746 IGH
2 105928759 UBS
或以上函数的矢量化版本:
get_alphanum <- function(x, type) {
type <- switch(type,
alpha = '[[:digit:]]+',
digit = '[[:alpha:]]+')
sub(type,'', x)
}
get_alphanum <- Vectorize(get_alphanum)
它给出了直接应用于向量的结果!
> get_alphanum(all, type='alpha')
183746IGH 105928759UBS
" IGH" "UBS"
> get_alphanum(all, type='digit')
183746IGH 105928759UBS
" 183746" "105928759"
也可用于创建 data.frame:
> data.frame(all,
alpha=get_alphanum(all, type='alpha') ,
numeric=get_alphanum(all, type='digit'))
all alpha numeric
183746IGH 183746IGH IGH 183746
105928759UBS 105928759UBS UBS 105928759
read.pattern
可以做到这一点。 pattern
参数中给出的正则表达式的每个括号部分将被读入单独的列:
x <- c("183746IGH", "105928759UBS")
library(gsubfn)
read.pattern(text = x, pattern = "(\d+)(\D+)")
给予:
V1 V2
1 183746 IGH
2 105928759 UBS
其他选项包括 tstrsplit
来自 data.table
library(data.table)#v1.9.5+
setDT(df)[,tstrsplit(V1,'(?<=\d)(?=\D)', perl=TRUE, type.convert=TRUE)]
# V1 V2
#1: 131341 adad
#2: 45365 adadar
#3: 425 cavsbsb
#4: 46567567 daadvsv
如果有元素 'non-numeric' 部分首先出现,'numeric' 最后出现,那么,我们可以使用更通用的选项作为正则表达式模式,
setDT(df)[,tstrsplit(V1, "(?<=\d)(?=\D)|(?<=\D)(?=\d)",
perl = TRUE)]
或使用 extract
来自 tidyr
library(tidyr)
extract(df, V1, into=c('V1', 'V2'), '(\d+)(\D+)', convert=TRUE)
# V1 V2
#1 131341 adad
#2 45365 adadar
#3 425 cavsbsb
#4 46567567 daadvsv
如果您也需要原始列,
extract(df, V1, into=c('V2', 'V3'), '(\d+)(\D+)',
convert=TRUE, remove=FALSE)
# V1 V2 V3
#1 131341adad 131341 adad
#2 45365adadar 45365 adadar
#3 425cavsbsb 425 cavsbsb
#4 46567567daadvsv 46567567 daadvsv
对于 data.table
,我们可以使用 :=
创建新列,以便现有列保留在输出中,即
setDT(df)[,paste0('V',2:3):=tstrsplit(V1,'(?<=\d)(?=\D)',
perl=TRUE, type.convert=TRUE)]
# V1 V2 V3
#1: 131341adad 131341 adad
#2: 45365adadar 45365 adadar
#3: 425cavsbsb 425 cavsbsb
#4: 46567567daadvsv 46567567 daadvsv
注意:两种解决方案都可以选择转换拆分列 (type.convert/convert
) 的 class。
数据
df <- data.frame(V1 = c("131341adad", "45365adadar", "425cavsbsb",
"46567567daadvsv"))
strsplit
如果您提供正确的正则表达式作为拆分依据,则确实有效。
在这种情况下,你会想要这样的东西:
strsplit(String, split = "(?<=[a-zA-Z])(?=[0-9])", perl = TRUE)
此处应用于@Steven 的示例数据:
strsplit(as.character(df$V1), split = "(?<=[a-zA-Z])(?=[0-9])", perl = TRUE)
# [[1]]
# [1] "adad" "131341"
#
# [[2]]
# [1] "adadar" "45365"
#
# [[3]]
# [1] "cavsbsb" "425"
#
# [[4]]
# [1] "daadvsv" "46567567"
过去有一段时间我编写了一个函数来执行此操作,因为老实说,我的大脑并不经常用正则表达式思考。该函数如下所示:
SplitMe <- function(string, alphaFirst = TRUE, bind = FALSE) {
if (!is.character(string)) string <- as.character(string)
Pattern <- ifelse(isTRUE(alphaFirst),
"(?<=[a-zA-Z])(?=[0-9])",
"(?<=[0-9])(?=[a-zA-Z])")
out <- strsplit(string, split = Pattern, perl = TRUE)
if (isTRUE(bind)) {
require(data.table)
as.data.table(do.call(rbind, out))
} else {
out
}
}
预期用途类似于:
library(data.table)
as.data.table(df)[, c("char", "num") := SplitMe(V1, bind = TRUE)][]
# V1 char num
# 1: adad131341 adad 131341
# 2: adadar45365 adadar 45365
# 3: cavsbsb425 cavsbsb 425
# 4: daadvsv46567567 daadvsv 46567567
一旦您知道该模式,您就可以在其他使用 strsplit
的地方使用它,例如 "tidyr" 中的 separate
,它可以方便地将值分隔到列中:
library(dplyr)
library(tidyr)
df %>%
separate(V1, into = c("char", "num"),
sep = "(?<=[a-zA-Z])(?=[0-9])", perl = TRUE)
# char num
# 1 adad 131341
# 2 adadar 45365
# 3 cavsbsb 425
# 4 daadvsv 46567567