df <- data.frame(X = LETTERS[1:20],
Y = paste0("abc_", 1:20))
# X Y
#1 A abc_1
#2 B abc_2
#3 C abc_3
#4 D abc_4
#5 E abc_5
#6 F abc_6
# ...
我需要基于两个整数向量分配组ID。一个指示组从哪里开始,另一个指示组在哪里结束:
start_ix <- c(2, 5, 8, 10, 15, 18)
end_ix <- c(4, 7, 9, 13, 17, 19)
I.E。第一组是第2至4行,第二组是第5至7行,依此类推。这些索引中未包含的任何行(或开始和停止值之间的跨度)应为
NA
。
期望的结果是:
df_want <- structure(list(X = c("A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T"),
Y = c("abc_1", "abc_2", "abc_3", "abc_4", "abc_5", "abc_6",
"abc_7", "abc_8", "abc_9", "abc_10", "abc_11", "abc_12",
"abc_13", "abc_14", "abc_15", "abc_16", "abc_17", "abc_18",
"abc_19", "abc_20"), grp = c(NA, 1, 1, 1, 2, 2, 2, 3, 3,
4, 4, 4, 4, NA, 5, 5, 5, 6, 6, NA)), row.names = c(NA, -20L
), class = "data.frame")
# X Y grp
# 1 A abc_1 NA
# 2 B abc_2 1
# 3 C abc_3 1
# 4 D abc_4 1
# 5 E abc_5 2
# 6 F abc_6 2
# 7 G abc_7 2
# 8 H abc_8 3
# 9 I abc_9 3
# 10 J abc_10 4
# 11 K abc_11 4
# 12 L abc_12 4
# 13 M abc_13 4
# 14 N abc_14 NA
# 15 O abc_15 5
# 16 P abc_16 5
# 17 Q abc_17 5
# 18 R abc_18 6
# 19 S abc_19 6
# 20 T abc_20 NA
在我的具体情况下,解决方案需要在基本r中完成,但是为了其他可能存在相同问题的人,可以随意从外部包装发布解决方案。
我尝试了索引,分类和seq
的组合,但似乎无法提出解决方案。
abase使用
。
. <- 1 + end_ix - start_ix
df[sequence(., start_ix), "grp"] <- rep(seq_along(.), .)
用另一个使用
Map
basemeth。
. <- Map(seq, start_ix, end_ix)
df[unlist(.), "grp"] <- rep(seq_along(.), lengths(.))
基准测试
bench::mark(
jpsmith = local({seqV <- Vectorize(seq.default, vectorize.args = c("to", "from"))
ix <- unlist(seqV(start_ix, end_ix))
df[ix, "grp"] <- rep(1:length(start_ix), (end_ix - start_ix) + 1)
df}),
zx8754 = local({for(i in seq_along(start_ix)){
df[ start_ix[ i ]:end_ix[ i ], "grp"] <- i}
df}),
GKiSeq = local({. <- 1 + end_ix - start_ix
df[sequence(., start_ix), "grp"] <- rep(seq_along(.), .)
df}),
GKiMap = local({. <- Map(seq, start_ix, end_ix)
df[unlist(.), "grp"] <- rep(seq_along(.), lengths(.))
df})
)
# expression min median itr/s…¹ mem_a…² gc/se…³ n_itr n_gc total…⁴ result
# <bch:expr> <bch:tm> <bch:t> <dbl> <bch:b> <dbl> <int> <dbl> <bch:t> <list>
#1 jpsmith 186.1µs 198.4µs 4944. 280B 12.3 2408 6 487ms <df>
#2 zx8754 212.4µs 226.6µs 4339. 280B 12.4 2095 6 483ms <df>
#3 GKiSeq 60.6µs 67.1µs 14679. 280B 12.3 7160 6 488ms <df>
#4 GKiMap 103µs 111.6µs 8748. 280B 12.3 4265 6 488ms <df>
虽然可能有更好的解决方案,这是一个潜在的解决方案,该解决方案是Vectorize
函数索引行,然后使用
seq
中的启动和终端位置差的向量来识别组:
使用
for loop:
#Index
seqV <- Vectorize(seq.default, vectorize.args = c("to", "from"))
ix <- unlist(seqV(start_ix, end_ix))
#Assign groups
df[ix, "grp"] <- rep(1:length(start_ix), (end_ix - start_ix) + 1)
# Validate
all.equal(df_want, df)
# [1] TRUE
其他选项,使用data.table::foverlaps::
for(i in seq_along(start_ix)){
df[ start_ix[ i ]:end_ix[ i ], "grp"] <- i
}
a
library(data.table)
df1 <- cbind(data.table(start = seq(nrow(df)), end = seq(nrow(df))), df)
df2 <- data.table(start = start_ix, end = end_ix, grp = seq_along(start_ix))
setkey(df1, start, end)
setkey(df2, start, end)
foverlaps(df1, df2)[, .(X, Y, group)]
# X Y grp
# 1: A abc_1 NA
# 2: B abc_2 1
# 3: C abc_3 1
# 4: D abc_4 1
# 5: E abc_5 2
# etc...
选项可能是:使用索引,ids_lst <- map2(start_ix, end_ix, seq)
df %>%
mutate(grp = map_int(row_number(),
function(rowid) match(TRUE, map2_lgl(rowid, ids_lst, function(rowid, ids) rowid %in% ids), nomatch = NA)))
X Y grp
1 A abc_1 NA
2 B abc_2 1
3 C abc_3 1
4 D abc_4 1
5 E abc_5 2
6 F abc_6 2
7 G abc_7 2
8 H abc_8 3
9 I abc_9 3
10 J abc_10 4
11 K abc_11 4
12 L abc_12 4
13 M abc_13 4
14 N abc_14 NA
15 O abc_15 5
16 P abc_16 5
17 Q abc_17 5
18 R abc_18 6
19 S abc_19 6
20 T abc_20 NA
和:
rbind
从@gki添加基准:
df$grp <- rbind(NA, 1:nrow(df))[
findInterval(
1:nrow(df),
c(
rbind(
start_ix,
end_ix + 0.1
)
)
) + 1L
]