我可以使用 tidyr 中的 separate() 或 extract() 将可变长度的数值拆分为其组成数字吗?

Can I use separate() or extract() from tidyr to split a numeric value of variable length into its component digits?

我有一个包含约 300 个观测值的数据框,每个观测值都与一个数字代码相关联,我想将该数字代码拆分成其组成部分。代码变量是 3 位或 4 位整数,按最后一位对齐,所以我想要的输出看起来像这样:

code    d4 d3 d2 d1
 403  <NA>  4  0  3 
5123     5  1  2  3
 105  <NA>  1  0  5    

虽然我可以看到很多使用 strsplit(base R)或 stringr::str_split 划分代码的方法,但我很难将这些操作应用于我的数据框。

library(stringr)
as.integer(unlist(str_split(5123, ""))[1]) # returns 5, the first digit - correct
as.integer(rev(unlist(str_split(5123, "")))[1]) # returns 3, the last digit - correct

但是(对我来说)合理的操作

libray(dplyr)
df <- data.frame(code = c(403, 5123, 105))
df <- df %>% 
  mutate(
    last = as.integer(rev(unlist(str_split(df$code,"")))[4])
  )

returns

> df
  code last
1  403    3
2 5123    3
3  105    3

很明显,我对如何在数据帧中处理列表和原子向量的操作缺乏理解...

然后我确信 tidyr 包中的 separate()extract() 函数会有所帮助。当然,如果代码以字符串的形式提供,每个数字前都有一个前导 space,tidyr::separate() 会产生所需的结果:

library(tidyr)
dfsep <- data.frame(code = c(" 4 0 3", "5 1 2 3", " 1 0 5"))
dfsep <- dfsep %>% 
  separate(
    code, c("d4", "d3", "d2", "d1"), fill =  "right", remove = FALSE
    )

dfsep
     code d4 d3 d2 d1
1   4 0 3     4  0  3
2 5 1 2 3  5  1  2  3
3   1 0 5     1  0  5

但是连续的一串数字不能用这种方式分割; tidyr::separate()

不支持空搜索模式
df <- data.frame(code = c(403, 5123, 105))
df <- df %>% 
  separate(
    code, c("d4", "d3", "d2", "d1"), fill =  "right", remove = FALSE
  )

df
  code   d4   d3   d2   d1
1  403  403 <NA> <NA> <NA>
2 5123 5123 <NA> <NA> <NA>
3  105  105 <NA> <NA> <NA>

虽然 tidyr::extract() 的问题在于,虽然它可以很好地提取数字,但我还没有找到一组可以处理 3 位和 4 位整数的参数:

dfext <- data.frame(code = c(403, 5123, 105))
dfext <- dfext %>% 
  extract(
    code, c("d4", "d3", "d2", "d1"), "(.)(.)(.)(.)", remove = FALSE
    ) 

dfext
  code   d4   d3   d2   d1
1  403 <NA> <NA> <NA> <NA>
2 5123    5    1    2    3
3  105 <NA> <NA> <NA> <NA>

也许我还没有理解如何为我的目的构建正确的正则表达式代码...

我查看了 Whosebug 上的相关问题,包括这个关于 separate() and this one about , but I could not see how to apply the answers to my own problem. The question 的问题给出了一个解决方案,该变量的值是固定长度的,而不是可变的。

如有任何帮助、提示或意见,我们将不胜感激!

P.S。为了提供上下文,这是跳水比赛中跳水的数据框架。每行代表一次潜水,一次观察具有多个分组变量:姓名、年龄、性别、潜水编号(例如 5 次中的 1 次)、冲浪板高度、潜水代码、潜水位置、关税、J1 奖、J2 奖、... J5奖励、总奖励(放弃最高和最低奖励)和分数(总奖励乘以关税)。代码由 FINA

决定

我们可以在 strsplit

拆分后使用 stringi 中的 stri_list2matrix
n <- max(nchar(df$code)) #get the maximum number of characters
fmt <- paste0('%', n, 'd') #create a format for the `sprintf`
library(stringi)
#the list output from `strsplit` can be coerced to `matrix` using
#stri_list2matrix.
d1 <- stri_list2matrix(strsplit(sprintf( fmt, df$code), ''), byrow=TRUE)
#But, the output is character class, which we can convert to 'numeric' 
m1 <- matrix(as.numeric(d1), ncol=ncol(d1), nrow=nrow(d1))
m1
#     [,1] [,2] [,3] [,4]
#[1,]   NA    4    0    3
#[2,]    5    1    2    3
#[3,]   NA    1    0    5

对于 'dfsep' 数据集

v1 <- gsub('\s+', '', dfsep$code)
n <- max(nchar(v1))
fmt <- paste0('%', n, 's')
d1  <- stri_list2matrix(strsplit(sprintf(fmt, v1), ''), byrow=TRUE)
m1 <- matrix(as.numeric(d1), ncol=ncol(d1), nrow=nrow(d1))
m1
#     [,1] [,2] [,3] [,4]
#[1,]   NA    4    0    3
#[2,]    5    1    2    3
#[3,]   NA    1    0    5

我们可以cbind使用原始数据集

cbind(dfsep, m1)

这可以做成一个函数,适用于不同的数据集。

只测试了几个案例,但这应该也适用于不同类型的输入

f <- function(df) {
  f <- tempfile()
  df$ccode <- gsub('\s+', '', df$code)
  cat(file = f, sprintf('%4s', gsub('\s+', '', df$ccode)), sep = "\n")
  cbind(code = df$code, read.fwf(f, widths = rep(1, max(nchar(df$ccode)))))
}

df <- data.frame(code = c(403, 5123, 105))
f(df)
#   code V1 V2 V3 V4
# 1  403 NA  4  0  3
# 2 5123  5  1  2  3
# 3  105 NA  1  0  5

dfsep <- data.frame(code = c(" 4 0 3", "5 1 2 3", " 1 0 5"))
f(dfsep)
#      code V1 V2 V3 V4
# 1   4 0 3 NA  4  0  3
# 2 5 1 2 3  5  1  2  3
# 3   1 0 5 NA  1  0  5

一个简单的基础 R 解决方案

codes = c(403, 5123, 105)

# make all codes the same length
l = sapply(codes, nchar)
s = strrep(' ', max(l) - l)
new_codes = paste0(s, codes)

# split and combine into matrix
res = do.call(rbind, strsplit(new_codes, ''))

根据需要重新格式化:

res = data.frame(code=codes, res)
colnames(res) = c('code', 'd4', 'd3', 'd2', 'd1')

输出:

  code d4 d3 d2 d1
1  403     4  0  3
2 5123  5  1  2  3
3  105     1  0  5

正则表达式应为“(.)?(.)(.)(.)”

?用于项目出现零次或一次

dfext %>% extract(code, c('d1','d2','d3','d4'), "(.)?(.)(.)(.)")
d1 d2 d3 d4
1 <NA>  4  0  3
2    5  1  2  3
3 <NA>  1  0  5