按多个分隔符拆分列(保留它们)并分成不相等的列数

Spliting column by multiple delimiters (keeping them) and into unequal number of columns

提问人:Honza88 提问时间:10/31/2023 最后编辑:Darren TsaiHonza88 更新时间:11/1/2023 访问量:89

问:

假设我有一个这样的数据帧(简化的,我的问题的类似版本):

ID <- c(1,2,3)
value <- c("1+4-3", "2+7-6+4-3", "-1+3")
df <- data.frame(ID, value)

ID  value
1   1+4-3
2   2+7-6+4-3
3   -1+3

我需要通过多个分隔符( 和 )将列拆分为多列,同时将分隔符保留在单独的列中。value+-

生成的数据帧应如下所示:

ID  x1  x2  x3  x4  x5   x6   x7   x8   x9
1   1   +   4   -   3    <NA> <NA> <NA> <NA>
2   2   +   7   -   6    +    4    -    3
3   -   1   +   3   <NA> <NA> <NA> <NA> <NA>

此外,我不知道我需要多少个结果列(可能不是示例中的 9 个,而是 50 个)。

实现这一目标的最佳方法是什么?

谢谢

R 数据帧 dplyr 拆分 tidyr

评论

0赞 PGSA 10/31/2023
您确定需要它们作为多列吗?我可能会考虑将它们作为单个列表列中的字符串向量,这样您就不需要可变宽度了吗?
0赞 Darren Tsai 10/31/2023
如果我添加一个新的观测值 ID=3 和 value = “-1-2-3”(第一个数字为负数),预期输出是多少?
0赞 Honza88 10/31/2023
@PaulStaffordAllen 多列更可取。但是,对于我以后可以根据自己的目的进行调整的任何解决方案,我都会感到高兴。
0赞 Honza88 10/31/2023
@DarrenTsai完美的问题,那就是“-”、“1”、“-”等。

答:

2赞 ThomasIsCoding 10/31/2023 #1

如果您的数字仅由digits

df %>%
  mutate(value = str_extract_all(value, "\\d+|\\D")) %>%
  unnest(value) %>%
  mutate(name = seq_len(n()), .by = ID) %>%
  pivot_wider(names_prefix = "X")

这给了

# A tibble: 2 × 10
     ID X1    X2    X3    X4    X5    X6    X7    X8    X9
  <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1     1 1     +     4     -     3     NA    NA    NA    NA
2     2 2     +     7     -     6     +     4     -     3  
1赞 deschen 10/31/2023 #2

您可以执行以下操作:

library(tidyverse)

df |>
  separate_longer_delim(cols = value, delim = regex("(?=\\+|-)")) |> 
  separate_longer_position(cols = value, width = 1) |> 
  mutate(pos = row_number(), .by = ID) |> 
  pivot_wider(values_from = value,
              names_from = "pos",
              names_prefix = "X")

# A tibble: 3 × 10
     ID X1    X2    X3    X4    X5    X6    X7    X8    X9   
  <int> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1     1 1     +     4     -     3     NA    NA    NA    NA   
2     2 2     +     7     -     6     +     4     -     3    
3     3 -     1     +     3     NA    NA    NA    NA    NA    

评论

2赞 PGSA 10/31/2023
请注意,这不会处理具有多个数字的数字,这些数字可能相关,也可能不相关。
1赞 Osdorp 10/31/2023 #3

[已编辑以处理具有多个数字的数字]

仅使用 r-base:

# Example data
ID <- c(1,2)
value <- c("1+4-3","2+72-6+42-3")
df <- data.frame(ID,value)

# Function to do custom split
mysplit <- function(x){
  a <-gregexpr('[0-9]+',x)
  b <- gregexpr('[+-]{1}',x)
  res <- unlist(c(regmatches(x,a),regmatches(x,b)))
  res[order(unlist(c(a,b)))]
}

# split and fill with NAs
s <- sapply(df$value,mysplit)
mlength <- max(sapply(s,length))
s <- sapply(s, function(x) c(x,rep(NA,mlength - length(x))))

# Return dataframe
data.frame(ID = df$ID,t(s))

            ID X1 X2 X3 X4 X5   X6   X7   X8   X9
1+4-3        1  1  +  4  -  3 <NA> <NA> <NA> <NA>
2+72-6+42-3  2  2  + 72  -  6    +   42    -    3

编辑

评论

1赞 PGSA 10/31/2023
请注意,此解决方案无法处理具有多个数字的数字,如果这是相关的(OP 中不清楚)。
1赞 Osdorp 11/1/2023
我还没有意识到。现在我认为它有效
1赞 PGSA 10/31/2023 #4

我的方法:

ID <- c(1,2,3)
value <- c("1+4-3","2+7-6+4-3","25+110/2*214")
# added example 3 to show effect on numbers with >1 digit
df <- data.frame(ID,value)

df |> dplyr::mutate(
  X = lapply(value, \(x) {
    # split by word/nonword boundaries
    y <- stringr::str_split(x, pattern = "\\b", simplify = TRUE)
    # drop the empty first and last strings
    y[nzchar(y)]
  })) |> tidyr::unnest_wider(X, names_sep = "")

# A tibble: 3 × 11
     ID value        X1    X2    X3    X4    X5    X6    X7    X8    X9   
  <dbl> <chr>        <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1     1 1+4-3        1     +     4     -     3     NA    NA    NA    NA   
2     2 2+7-6+4-3    2     +     7     -     6     +     4     -     3    
3     3 25+110/2*214 25    +     110   /     2     *     214   NA    NA   

如果你把管道拆下来,你会得到这个,IMO在某些方面可能更整洁:unnest_wider

  ID        value                         X
1  1        1+4-3             1, +, 4, -, 3
2  2    2+7-6+4-3 2, +, 7, -, 6, +, 4, -, 3
3  3 25+110/2*214  25, +, 110, /, 2, *, 214
4赞 Darren Tsai 10/31/2023 #5

您可以从以下位置使用:separate_wider_delim()tidyr

library(tidyr)

df %>%
  separate_wider_delim(value,
                       delim = stringr::regex("(?<=\\d)(?=\\D)|(?<=\\D)(?=\\d)"),
                       too_few = "align_start",
                       names_sep = '',
                       names_repair = ~ sub("value", "X", .x))

# # A tibble: 3 × 10
#      ID X1    X2    X3    X4    X5    X6    X7    X8    X9   
#   <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
# 1     1 1     +     4     -     3     NA    NA    NA    NA   
# 2     2 2     +     7     -     6     +     4     -     3    
# 3     3 -     1     +     3     NA    NA    NA    NA    NA

评论

1赞 PGSA 10/31/2023
我认为这是迄今为止最干净的非基础答案。