【问题标题】:Split string by position and numeric value in dplyr在dplyr中按位置和数值拆分字符串
【发布时间】:2021-05-15 04:28:47
【问题描述】:

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

  1. 字符串为 5 或 6 个字符,对应两个数字,每个数字为 2 或 3 个字符
  2. 如果你把这两个数字相加,总和大约是 150 到 250
  3. 任一侧的最大值为 150
  4. 各个值应一起最大化,例如id = 8 将是 101 和 48 而不是 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 调用(保持= TRUE),然后大概pivot_longgroup_by id 以便filter 正确的选项。有更简单的选择吗?

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

【问题讨论】:

  • 您能否解释一下 id 9 的逻辑,选择 111 + 21 而不是 11 + 121?
  • 是的,这是一个奇怪的边缘案例,老实说可能是其中之一,我无法知道,因为我正试图从数据和知识中找出这个小组围绕这些数字的规则场地。考虑到它们所代表的含义,拥有符合规则的最高个人数字“更好”,因此选择 21 比 11 更好(尽管有 11105,所以 11 必须是可能的,因为 05 很奇怪而且更糟)。如果这还不够清楚,四分之二,lmk 和我会尝试更好地表达它。

标签: r dplyr purrr stringr


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    这适用于示例数据集:

    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  
    

    【讨论】:

      猜你喜欢
      • 2018-03-27
      • 2022-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-06
      • 2015-01-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多