在 dplyr 中按位置和数值拆分字符串

Split string by position and numeric value in dplyr

我正在尝试读取另一组的数据,有一列中的一对两个数字通常用逗号分隔,但在大约 10% 的情况下 (>15,000) 没有逗号。用手分开太多了,但我在设计一种有效分离字符串的有效方法时遇到了麻烦。规则如下:

  1. string 将是 5 或 6 个字符,对应两个数字,每个 2 或 3 个字符
  2. 如果将这两个数字相加,总数大约为 150 到 250
  3. 任何一方的最大值为 150
  4. 单个值应该一起最大化,例如id = 8 将是 101 和 48 NOT 10 和 148,如果有平局,选择具有最大小值的对,例如11121 应该是 111 和 21 而不是 11 和 121

这是一个示例:

tribble(
  ~id, ~to_split,
  1,   33118,
  2,   37118,
  3,   30121,
  4,   41110,
  5,   98121,
  6,   101102,
  7,   101110,
  8,   10148,
  9,   11121
) %>% 
  mutate_at("to_split", as.character)

这是我想得出的结果:

tribble(
  ~id, ~left, ~right,
  1,   33, 118,
  2,   37, 118,
  3,   30, 121,
  4,   41, 110,
  5,   98, 121,
  6,   101, 102,
  7,   101, 110,
  8,   101, 48,
  9,   111, 21
)

我考虑过将它们分成两个不同的配对,方法是取前两个字符和其余字符以及前三个字符和其余字符,然后将这两个配对相加,看看哪个总数最小。虽然我认为这种启发式方法适用于所有情况,但实施起来有点痛苦。我正在考虑进行两次 separate 调用(使用 keep = TRUE),然后大概 pivot_longgroup_by id 以便 filter 正确的选项。有没有更简单的选择?

理想情况下,我想在一个 mutate 中完成这一切(可能需要 purrr::map?)但我不确定如何按位置拆分字符串并获取它的两个部分(没有 separate).如果可能的话,用 case_when/ifelse 来确定哪个配对较小,然后在它们之间的正确位置添加逗号或其他分隔符应该是一件简单的事情,这样我就可以 bind_rows 回到 table 的其余部分,我会 separate 一切。但我发现分离字符串并获得两个部分的唯一方法是 separate。来自 stringrstringi 的东西会这样做吗?

这适用于示例数据集:

library(dplyr)

df %>% mutate(cut = if_else(as.numeric(substr(to_split,1,3))<=150,3L,2L),
              length = nchar(to_split)) %>%
       mutate(left  = substr(to_split,1,cut),
              right = substr(to_split,cut+1,length)) %>%
       select(-cut,-length)

# A tibble: 8 x 4
     id to_split left  right
  <dbl> <chr>    <chr> <chr>
1     1 33118    33    118  
2     2 37118    37    118  
3     3 30121    30    121  
4     4 41110    41    110  
5     5 98121    98    121  
6     6 101102   101   102  
7     7 101110   101   110  
8     8 10148    101   48  

这里有一个详细的方法,在您优化边缘情况时可能会有所帮助。

library(tidyverse)

# combine splits @ 2 and @ 3
bind_rows(df1 %>% mutate(split_pos = 2),
          df1 %>% mutate(split_pos = 3)) %>%
  
# calc features
  mutate(num1 = str_sub(to_split,   end = split_pos)     %>% parse_number(),
         num2 = str_sub(to_split, start = split_pos + 1) %>% parse_number(),
         total = num1 + num2,
         max = pmax(num1, num2)) %>%
  
# filter and pick best fit
  filter(total %>% between(100, 250), max <= 150) %>%
  arrange(id) %>%
  group_by(id) %>%
  slice_min(max) %>%
  ungroup()



    id to_split split_pos  num1  num2 total   max
  <dbl> <chr>        <dbl> <dbl> <dbl> <dbl> <dbl>
1     1 33118            2    33   118   151   118
2     2 37118            2    37   118   155   118
3     3 30121            2    30   121   151   121
4     4 41110            2    41   110   151   110
5     5 98121            2    98   121   219   121
6     6 101102           3   101   102   203   102
7     7 101110           3   101   110   211   110
8     8 10148            3   101    48   149   101
9     9 11121            3   111    21   132   111