如何拆分某一列中的所有字符串,并在所有新数据中包含前缀

6
我有一个数据集,其中的数据应该被放入三个不同的列中,但是它们都被放在了一列中。我希望通过反斜杠将其分成三个列,并且每个分割字符串必须包括一个字符前缀和一个字符后缀。字符前缀只在字符串的第一部分出现,字符后缀只在字符串结尾处出现。
例如"PC211/212.5(C)/664F"具有前缀“PC”和后缀“F”。前缀总是两个字母,后缀总是一个字母,并且它们总是字符。前缀总是跟随着数字代码,后缀总是在数字或右括号之前出现。
我的数据在一个非常大的数据框中,所以我想通过按列调用它来分开处理。这里有一个可重复使用的子集示例:
df <- data.frame("code" = c("PC211/212.5(C)/664F", "VC23152(A)/23550F", "PC459/460(B)M", "PC187(A)/664F"), stringsAsFactors = FALSE)

我希望你能够返回类似以下的内容:

df_id_like <- data.frame("code" = c("PC211/212.5(C)/664F", "VC23152(A)/23550F", "PC459/460(B)M", "PC187(A)/664F"), 
"code_1" = c("PC211F", "VC23152(A)F", "PC459M", "PC187F"), 
"code_2" = c("212.5(C)F", "VC23550F", "PC460(B)M", "PC664F"), 
"code_3" = c("PC664F", NA, NA, NA), 
stringsAsFactors = FALSE)

我认为这个问题可能需要正则表达式,但完全接受不需要正则表达式的解决方案!

4个回答

6

使用 separatetidyr 选项

library(dplyr)
library(tidyr)
df %>% separate(code, paste0("code_", 1:3), sep = "/", fill = "right", remove = F)
#                 code     code_1   code_2 code_3
#1 PC211/212.5(C)/664F      PC211 212.5(C)   664F
#2   VC23152(A)/23550F VC23152(A)   23550F   <NA>
#3       PC459/460(B)M      PC459  460(B)M   <NA>
#4       PC187(A)/664F   PC187(A)     664F   <NA>

请注意,您的预期输出似乎与输入数据不匹配。例如,对于第一行,您对code_3的预期输出为"PC664F",而相关的输入字符串是"664F"code_2对于同一行具有"212.5(C)F",而输入字符串是"212.5(C)"。我认为这些都是错误。

更新

感谢@andrew_reece的评论,我(想)现在理解您的问题了。这里是一个选项。
df %>%
    rowid_to_column("row") %>%
    separate(code, c("prefix", "main", "suffix"), sep = c(2, -1), remove = F) %>%
    separate(main, into = paste0("code_", 1:3), sep = "/", fill = "right") %>%
    gather(key, entry, starts_with("code_")) %>%
    filter(!is.na(entry)) %>%
    unite(entry, prefix, entry, suffix, sep = "") %>%
    spread(key, entry) %>%
    select(-row)

解释:我们首先从代码中分离前缀和后缀,然后从主要的代码部分分离每个组件。我们从宽形变换为长形,删除NA条目,并在从长到宽形变换之前将每个代码组件与前缀和后缀连接起来。

这样可以重现您期望的输出,但第1行中的code_2除外。


另一种方法

作为另一种方法,将预处理的代码存储在一个list列中可能更有用,而不是在宽格式中存储它们并添加code_1code_2等附加列。这样做的好处是您不必硬编码在code列中拥有的代码数量;以下方法适用于任何代码数量,并且仅假定

  1. code的前2个字符定义了prefix
  2. code的最后一个字符是suffix

df %>%
    separate(code, c("prefix", "main", "suffix"), sep = c(2, -1), remove = F) %>%
    transmute(
        code,
        codes_as_list = pmap(
            list(prefix, str_split(main, "/"), suffix),
            function(x, y, z) paste0(x, y, z)))
#                 code               codes_as_list
#1 PC211/212.5(C)/664F PC211F, PC212.5(C)F, PC664F
#2   VC23152(A)/23550F       VC23152(A)F, VC23550F
#3       PC459/460(B)M           PC459M, PC460(B)M
#4       PC187(A)/664F           PC187(A)F, PC664F

请注意,codes_as_list 现在是一个带有正确前缀/后缀代码的 list 列,可以轻松地使用 purrr::map 工具对元素进行操作。

1
我认为OP的意思是前缀(例如PC,VC)和后缀(例如F,M)应保留在每个分开的部分中。这与OP预期的输出一致,但第1行中的code_2除外。 - andrew_reece
@andrew_reece 啊,现在我明白了!这将比单个separate调用更复杂。我正在研究它... - Maurits Evers

3

如果我理解正确,这将为您提供每个分隔列的前缀和后缀:

library(tidyverse)

df %>%
  mutate(prefix = str_extract(code, "^[A-Z]+"),
         suffix = str_extract(code, "[A-Z]$")) %>%
  separate(code, into = c("code_1", "code_2", "code_3"), 
           sep = "/", fill = "right", remove = F) %>%
  mutate_at(vars(matches("_1$")), 
            list(~paste0(., suffix))) %>%
  mutate_at(vars(matches("_2$")), 
            list(~if_else(str_sub(., -1) == suffix, 
                          paste0(prefix, .),
                          paste0(paste0(prefix, .), suffix)))) %>%
  mutate_at(vars(matches("_3$")), 
            list(~if_else(is.na(.), 
                          NA_character_, 
                          paste0(prefix, .)))) %>%
  select(-prefix, -suffix)

                 code      code_1      code_2 code_3
1 PC211/212.5(C)/664F      PC211F PC212.5(C)F PC664F
2   VC23152(A)/23550F VC23152(A)F    VC23550F   <NA>
3       PC459/460(B)M      PC459M   PC460(B)M   <NA>
4       PC187(A)/664F   PC187(A)F      PC664F   <NA>

刚刚注意到这不太对。在没有第三个“code”的情况下,后缀会重复。现在正在查看更新。 - andrew_reece

2
这里有另一种使用separatestr_extract_all的选项。我们创建一个模式('pat'),使用正则表达式环视来匹配斜杠后面跟着数字([0-9])的位置,以及第二个模式来匹配斜杠前面的字符位置。使用str_replace_all,将由'pat'匹配的位置插入字符串的前两个字符(substr),并将斜杠前面的位置插入字符串的最后一个字符,然后使用separate在分隔符/处将列分成三个部分。"最初的回答"
library(tidyverse)
#pat <- "(?<=\\/)(?=[0-9]+\\(?[A-Z])"
pat <- "(?<=\\/)(?=[0-9])"
pat2 <- "(?=\\/)"
df %>% 
  mutate(code1 = str_replace_all(code, pat, substr(code, 1, 2)) %>% 
  str_replace_all(pat2, substring(code, nchar(code))))%>%
  separate(code1, into = paste0("code_", 1:3), sep="[/]")
#                 code      code_1      code_2 code_3
#1 PC211/212.5(C)/664F      PC211F PC212.5(C)F PC664F
#2   VC23152(A)/23550F VC23152(A)F    VC23550F   <NA>
#3       PC459/460(B)M      PC459M   PC460(B)M   <NA>
#4       PC187(A)/664F   PC187(A)F      PC664F   <NA>

1

一种非常冗长的基础R解决方案,不使用正则表达式

pre <- substr(df$code, 1, 2)
post <- substring(df$code, nchar(df$code))
split_string <- strsplit(df$code, "/")
max_len <- max(lengths(split_string))

df[paste0("code", seq_len(max_len))] <- t(mapply(function(x, y, z) {
    if (length(x) >  2)
     c(paste0(x[1], z), paste0(y, x[-c(1, length(x))], z), paste0(y, x[length(x)]), 
        rep(NA, max_len - length(x)))
    else
     c(paste0(x[1], z), paste0(y, x[length(x)]), rep(NA, max_len - length(x))) 
}, split_string, pre, post))


df
#                 code       code1       code2  code3
#1 PC211/212.5(C)/664F      PC211F PC212.5(C)F PC664F
#2   VC23152(A)/23550F VC23152(A)F    VC23550F   <NA>
#3       PC459/460(B)M      PC459M   PC460(B)M   <NA>
#4       PC187(A)/664F   PC187(A)F      PC664F   <NA>

首先找到我们想要添加到字符串每个部分的code的前缀和后缀,将字符串拆分为"/"并计算要添加的列数(max_len)。使用mapplyprepost分别粘贴到字符串的每个部分,并用NA填充空格。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接