Назначьте уникальный идентификатор для нескольких столбцов таблицы данных - PullRequest
0 голосов
/ 04 октября 2018

Я хотел бы назначить уникальные идентификаторы для строк таблицы данных для нескольких значений столбца.Давайте рассмотрим простой пример:

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 и ожидать получить три идентификатора, где идентификаторы 2-й и 4-й строк идентичны, а 3-й и 5-й ряды одинаковыИД.

Я видел два решения, но каждое из них несколько неполное:

1) Решение 1 требует таблицы отсортированных данных, что очень громоздко, если нам нужно генерировать идентификаторы для многих столбцов (вВ моем реальном приложении идентификаторы создаются на основе примерно десяти столбцов).Можем ли мы заменить функцию cumsum, чтобы сортировка не требовалась?

DT$ID1 <- cumsum(!duplicated(DT[,1:2]))

2) Решение два игнорирует значения NA;хотя я хотел бы включить NA и назначить им идентификатор группы

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

Ответы [ 2 ]

0 голосов
/ 04 октября 2018

Идиоматический способ:

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 увеличило время до 8 с для подхода .GRP;20 для пути rleidv;и привел R повесить для других в моей системе.

Для всех, кто интересуется, в R FAQ можно найти больше подходов Как создать последовательный индекс на основе переменной группировки в кадре данных

0 голосов
/ 04 октября 2018

Мы можем использовать

DT[, Expected_ID := as.numeric(factor(paste(a, b), levels = unique(paste(a, b))))]
...