我想为每个多列值的数据表行分配唯一的ID。让我们考虑一个简单的示例:
library(data.table)
DT = data.table(a=c(4,2,NA,2,NA), b=c("a","b","c","b","c"), c=1:5)
a b c
1: 4 a 1
2: 2 b 2
3: NA c 3
4: 2 b 4
5: NA c 5
我想基于a和b列生成ID,并希望获得3个ID,其中第2行和第4行ID相同,而第3行和第5行也具有相同ID。
我已经看到了两种解决方案,但是每种解决方案都不太完善:
1)解决方案一需要排序的数据表,如果我们需要每许多列生成ID(在我的实际应用程序中,ID是基于大约十列生成的),这将非常麻烦。我们可以替换cumsum函数,所以不需要排序吗?
DT$ID1 <- cumsum(!duplicated(DT[,1:2]))
2)解决方案二忽略NA值;而我想加入NA并为其分配一个组ID
DT <- transform(DT, ID2 = as.numeric(interaction(a,b, drop=TRUE)))
对于任何有关如何修改任一解决方案以生成如下所示的Expected_ID的建议,我均表示赞赏。
a b c ID1 ID2 Expected_ID
1: 4 a 1 1 1 1
2: 2 b 2 2 2 2
3: NA c 3 3 NA 3
4: 2 b 4 3 2 2
5: NA c 5 3 NA 3
答案 0 :(得分:8)
惯用方式:
DT[, g := .GRP, by=.(a,b)]
a b c g
1: 4 a 1 1
2: 2 b 2 2
3: NA c 3 3
4: 2 b 4 2
5: NA c 5 3
有理由相信这不会很快,但是事实证明,与竞争方法相比,这还算不错:
nv = 10
nu = 3
nr = 1e6
library(data.table)
set.seed(1)
DT = do.call(CJ, rep(list(seq_len(nu)), nv))[sample(1:.N, nr, replace=TRUE)]
cols = copy(names(DT))
# "idiomatic" .GRP
system.time(DT[, g := .GRP, by=cols])
# user system elapsed
# 0.23 0.02 0.25
# sort and count runs
oi = as.call(lapply(c("order", cols), as.name))
system.time(DT[eval(oi), go := rleidv(.SD, cols)])
# user system elapsed
# 0.3 0.0 0.3
# paste 'em
system.time(DT[, gp := match(p <- do.call(paste, c(.SD, list(sep="_"))), unique(p)), .SDcols=cols])
# user system elapsed
# 5.26 0.06 5.32
# paste 'em, fact'em (@akrun's answer)
system.time(DT[, gpf := as.integer(factor(p <- do.call(paste, c(.SD, list(sep="_"))), levels = unique(p))), .SDcols=cols])
# user system elapsed
# 4.74 0.08 4.82
# check
identical(DT$g, DT$gp); identical(DT$g, DT$gpf)
uniqueN(DT, "g") == uniqueN(DT, c("g", "go"))
rleidv方法创建不同的组号,但影响相同的分组。
将问题的大小增加到nr = 5e7
会使.GRP
方法的时间增加到8s; rleidv方式为20秒;并导致R为我系统上的其他用户挂断电话。
对于任何感兴趣的人,可以在R FAQ How to create a consecutive index based on a grouping variable in a dataframe
中找到更多方法。答案 1 :(得分:0)
我们可以使用
DT[, Expected_ID := as.numeric(factor(paste(a, b), levels = unique(paste(a, b))))]