在 dplyr 中按位置和数值拆分字符串
Split string by position and numeric value in dplyr
我正在尝试读取另一组的数据,有一列中的一对两个数字通常用逗号分隔,但在大约 10% 的情况下 (>15,000) 没有逗号。用手分开太多了,但我在设计一种有效分离字符串的有效方法时遇到了麻烦。规则如下:
- string 将是 5 或 6 个字符,对应两个数字,每个 2 或 3 个字符
- 如果将这两个数字相加,总数大约为 150 到 250
- 任何一方的最大值为 150
- 单个值应该一起最大化,例如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_long
和 group_by
id 以便 filter
正确的选项。有没有更简单的选择?
理想情况下,我想在一个 mutate 中完成这一切(可能需要 purrr::map
?)但我不确定如何按位置拆分字符串并获取它的两个部分(没有 separate
).如果可能的话,用 case_when
/ifelse
来确定哪个配对较小,然后在它们之间的正确位置添加逗号或其他分隔符应该是一件简单的事情,这样我就可以 bind_rows
回到 table 的其余部分,我会 separate
一切。但我发现分离字符串并获得两个部分的唯一方法是 separate
。来自 stringr
或 stringi
的东西会这样做吗?
这适用于示例数据集:
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
我正在尝试读取另一组的数据,有一列中的一对两个数字通常用逗号分隔,但在大约 10% 的情况下 (>15,000) 没有逗号。用手分开太多了,但我在设计一种有效分离字符串的有效方法时遇到了麻烦。规则如下:
- string 将是 5 或 6 个字符,对应两个数字,每个 2 或 3 个字符
- 如果将这两个数字相加,总数大约为 150 到 250
- 任何一方的最大值为 150
- 单个值应该一起最大化,例如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_long
和 group_by
id 以便 filter
正确的选项。有没有更简单的选择?
理想情况下,我想在一个 mutate 中完成这一切(可能需要 purrr::map
?)但我不确定如何按位置拆分字符串并获取它的两个部分(没有 separate
).如果可能的话,用 case_when
/ifelse
来确定哪个配对较小,然后在它们之间的正确位置添加逗号或其他分隔符应该是一件简单的事情,这样我就可以 bind_rows
回到 table 的其余部分,我会 separate
一切。但我发现分离字符串并获得两个部分的唯一方法是 separate
。来自 stringr
或 stringi
的东西会这样做吗?
这适用于示例数据集:
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