下面是一个示例数据集(A列和B列),我想创建一个新的标志列C,将第3行到第8行分组到“91345-912350”范围内(在真实数据集中,我有多个范围分组到)。我无法将 A 列转换为整数,因为它会导致前导 0 的代码消失(例如 0450 将变为 450)。有什么方法可以识别字符串数据类型的范围吗?
请随意使用以下代码生成示例数据集:
DT = data.table(
Code = c("0450", "912345", "912346","912347","912348","912349","912350","7860","X7960"),
Description = c("Desc A", "Desc B", "Desc C","Desc D","Desc E","Desc F","Desc G","Desc H","Desc I")
)
附加信息:
(1) 代码不需要连续,因为范围是预定义的。只要代码在某个范围之间,它就会进入该范围组,无论是否有任何缺失的代码。 (例如,如果缺少 912347,则 912345、912346、91238、912349、912350 仍会进入 912345-912350 范围)
(2)预定义的范围都是没有字母的数字范围,如果代码中包含字母,代码组列将与原始代码列相同,就像单元格C10和单元格A10中所示。
(3) 并非所有代码都需要在一个范围内,我有一个预定义范围列表,我需要将代码分组到。在此示例数据集中,我的预定义范围是 912345-912350。可能有 7860、7861、7862 之类的代码,但我不需要将它们分组到 7860-7862 中,因为它们不在我的预定义范围内。如果代码没有范围组,代码组将返回与代码列相同的值,就像单元格 C9 和单元格 A9 中显示的一样。
这里的要求对我来说不是很清楚,但是你可以创建一个函数来将代码映射到一个范围。例如
mapRange <- function(code, min=912345, max=912350) {
return (sapply(code, function(c) {
ic <- as.integer(c)
if (is.na(ic)) return(c)
if (ic >= min & ic <= max) return(paste(min, max, sep="-"))
return(c)
}))
}
然后
DT$`Code Range` <- mapRange(DT$Code)
或使用 tidyverse
DT <- DT %>% mutate(`Code Range` = mapRange(Code))
我只能提供一个
dplyr
解决方案:
我已经使用了第 2 行到第 7 行。我们可以根据需要进行调整。
library(dplyr)
DT %>%
filter(row_number() >= 2 & row_number() <= 7) %>%
type.convert(as.is = TRUE) %>%
mutate(Cod_Group = paste(Code, last(Code)-1 +row_number(), sep = " - ")) %>%
mutate(across(everything(), ~as.character(.))) %>%
bind_rows(DT) %>%
distinct(Code, .keep_all = TRUE) %>%
mutate(Cod_Group = coalesce(Code, Cod_Group)) %>%
arrange(Description)
Code Description Cod_Group
1: 0450 Desc A 0450
2: 912345 Desc B 912345
3: 912346 Desc C 912346
4: 912347 Desc D 912347
5: 912348 Desc E 912348
6: 912349 Desc F 912349
7: 912350 Desc G 912350
8: 7860 Desc H 7860
9: X7960 Desc I X7960
我做了更广泛的解决方案,涵盖了更多场景。请参阅我的扩展示例以及一些要测试的边缘案例。虽然我不允许范围内有间隙,但它支持具有前缀的 ID(包括填充零)。为了支持这一点,我们使用了一些辅助列并将所有结束数字视为数字(不包括填充零),并且在此之前我考虑了一个前缀(包括填充零)。这样我们就可以防止不同的 ID 前缀落在同一个组中。通过一些技巧,我们分配组 ID,然后获取每个组的第一个值和最后一个值并将它们粘贴在一起(如果组中有多个 id)。
解决方案
library(data.table)
library(stringr)
DT[, num := as.numeric(gsub("(.*)[^\\d](\\d*$)", "\\2", Code, perl = T))]
DT[, prefix := str_remove(Code, as.character(num))][prefix == "", prefix := NA_character_]
DT[, s := abs(num - shift(num, 1, "lag")), prefix][is.na(s) | s > 1, grp := .I][, s := NULL]
setnafill(DT, "locf", cols = "grp")
DT[, `Code Group` := fifelse(.SD[1] != .SD[.N], paste(.SD[1], .SD[.N], sep = " - "), unlist(.SD[1])),
by = grp, .SDcols = "Code"
]
辅助列的结果
DT
Code Description num prefix grp Code Group
1: 1 Desc A 1 <NA> 1 1 - 3
2: 2 Desc B 2 <NA> 1 1 - 3
3: 3 Desc C 3 <NA> 1 1 - 3
4: 448 Desc D 448 <NA> 2 448
5: 0449 Desc E 449 0 3 0449 - 0450
6: 0450 Desc F 450 0 3 0449 - 0450
7: 912345 Desc G 912345 <NA> 4 912345 - 912350
8: 912346 Desc H 912346 <NA> 4 912345 - 912350
9: 912347 Desc I 912347 <NA> 4 912345 - 912350
10: 912348 Desc J 912348 <NA> 4 912345 - 912350
11: 912349 Desc K 912349 <NA> 4 912345 - 912350
12: 912350 Desc L 912350 <NA> 4 912345 - 912350
13: 7860 Desc M 7860 <NA> 5 7860
14: X7960 Desc N 7960 X 6 X7960 - X7961
15: X7961 Desc O 7961 X 6 X7960 - X7961
16: Y7962 Desc P 7962 Y 7 Y7962
17: Z7963 Desc Q 7963 Z 8 Z7963
清理结果
DT[, .SD, .SDcols = c(1, 2, 6)]
Code Description Code Group
1: 1 Desc A 1 - 3
2: 2 Desc B 1 - 3
3: 3 Desc C 1 - 3
4: 448 Desc D 448
5: 0449 Desc E 0449 - 0450
6: 0450 Desc F 0449 - 0450
7: 912345 Desc G 912345 - 912350
8: 912346 Desc H 912345 - 912350
9: 912347 Desc I 912345 - 912350
10: 912348 Desc J 912345 - 912350
11: 912349 Desc K 912345 - 912350
12: 912350 Desc L 912345 - 912350
13: 7860 Desc M 7860
14: X7960 Desc N X7960 - X7961
15: X7961 Desc O X7960 - X7961
16: Y7962 Desc P Y7962
17: Z7963 Desc Q Z7963
样本数据
codes <- c(1,2,3, "448", "0449", "0450", "912345", "912346", "912347", "912348", "912349", "912350", "7860", "X7960", "X7961", "Y7962", "Z7963")
DT = data.table(Code = codes, Description = paste("Desc", LETTERS[1:length(codes)]))