R data.table: какой самый быстрый способ пересечения data.table по нескольким столбцам по ключам и группам - PullRequest
0 голосов
/ 30 мая 2018

ОСНОВНОЕ РЕДАКТИРОВАНИЕ, чтобы уточнить, если ответы неверны

У меня есть таблица данных с столбцами группы (split_by), ключевыми столбцами (key_by) и столбцами идентификаторов признаков (intersect_by)

Я хочу, чтобы в каждой группе split_by оставались только строки, в которых идентификаторы признаков используются всеми существующими ключами в группе.

Например:

dt <- data.table(id = 1:6, key1 = 1, key2 = c(1:2, 2), group_id1= 1, group_id2= c(1:2, 2:1, 1:2), trait_id1 = 1, trait_id2 = 2:1)
setkey(dt, group_id1, group_id2, trait_id1, trait_id2)
dt
   id key1 key2 group_id1 group_id2 trait_id1 trait_id2
1:  4    1    1         1         1         1         1
2:  1    1    1         1         1         1         2
3:  5    1    2         1         1         1         2
4:  2    1    2         1         2         1         1
5:  6    1    2         1         2         1         1
6:  3    1    2         1         2         1         2

res <- intersect_this_by(dt,
                         key_by = c("key1"),
                         split_by = c("group_id1", "group_id2"),
                         intersect_by = c("trait_id1", "trait_id2"))

Iхотим, чтобы res было таким:

> res[]
   id key1 key2 group_id1 group_id2 trait_id1 trait_id2
1:  1    1    1         1         1         1         2
2:  5    1    2         1         1         1         2
3:  2    1    2         1         2         1         1
4:  6    1    2         1         2         1         1
5:  3    1    2         1         2         1         2

Мы видим, что id 4 отброшен, как в group_id1 = 1 и group_id2 = 1 комбинированная группа (группе, которой принадлежит идентификатор 4), есть только одна комбинация клавиш (1,1), который имеет эти черты (1,1), тогда как в этой группе есть две комбинации клавиш: (1,1) и (1,2), поэтому черты (1,1) не являются общими для всех ключей в этой группе.group, поэтому мы отбрасываем эту черту из этой группы, поэтому отбрасываем id 4. Наоборот, id 1 и 5 имеют одинаковые черты, но разные ключи, и они представляют все ключи ((1,1) и (1,2)) в этомгруппа, так что черты идентификатора 1 и 5 сохраняются.

Функция для достижения этого дана там:

intersect_this_by2 <- function(dt,
                               key_by = NULL,
                               split_by = NULL,
                               intersect_by = NULL){

    dtc <- as.data.table(dt)       

    # compute number of keys in the group
    dtc[, n_keys := uniqueN(.SD), by = split_by, .SDcols = key_by]
    # compute number of keys represented by each trait in each group 
    # and keep row only if they represent all keys from the group
    dtc[, keep := n_keys == uniqueN(.SD), by = c(intersect_by, split_by), .SDcols = key_by]
    dtc <- dtc[keep == TRUE][, c("n_keys", "keep") := NULL]
    return(dtc)      
}

Но он работает довольно медленно для больших наборов данных или сложных признаков / ключей / групп ... реальная таблица данных имеет 10 миллионов строк, а характеристики имеют 30 уровней ... Есть лиспособ улучшить это?Есть ли очевидные подводные камни?Спасибо за помощь

ЗАКЛЮЧИТЕЛЬНОЕ РЕДАКТИРОВАНИЕ: Уве предложил краткое решение, которое на 40% быстрее моего исходного кода (который я удалил здесь, потому что он сбивал с толку). Последняя функция выглядит следующим образом:

intersect_this_by_uwe <- function(dt,
                                  key_by = c("key1"),
                                  split_by = c("group_id1", "group_id2"),
                                  intersect_by = c("trait_id1", "trait_id2")){
    dti <- copy(dt)
    dti[, original_order_id__ := 1:.N]
    setkeyv(dti, c(split_by, intersect_by, key_by))
    uni <- unique(dti, by = c(split_by, intersect_by, key_by))
    unique_keys_by_group <-
        unique(uni, by = c(split_by, key_by))[, .N, by = c(split_by)]
    unique_keys_by_group_and_trait <-
        uni[, .N, by = c(split_by, intersect_by)]
    # 1st join to pick group/traits combinations with equal number of unique keys
    selected_groups_and_traits <-
        unique_keys_by_group_and_trait[unique_keys_by_group,
                                       on = c(split_by, "N"), nomatch = 0L]
    # 2nd join to pick records of valid subsets
    dti[selected_groups_and_traits, on = c(split_by, intersect_by)][
        order(original_order_id__), -c("original_order_id__","N")]
}

А для записей бенчмарки для набора данных строк 10M:

> microbenchmark::microbenchmark(old_way = {res <- intersect_this_by(dt,
+                                                                    key_by = c("key1"),
+                                                                    split_by = c("group_id1", "group_id2"),
+                                                                    intersect_by = c("trait_id1", "trait_id2"))},
+                                new_way = {res <- intersect_this_by2(dt,
+                                                                     key_by = c("key1"),
+                                                                     split_by = c("group_id1", "group_id2"),
+                                                                     intersect_by = c("trait_id1", "trait_id2"))},
+                                new_way_uwe = {res <- intersect_this_by_uwe(dt,
+                                                                            key_by = c("key1"),
+                                                                            split_by = c("group_id1", "group_id2"),
+                                                                            intersect_by = c("trait_id1", "trait_id2"))},
+                                times = 10)
Unit: seconds
        expr       min        lq      mean    median        uq       max neval cld
     old_way  3.145468  3.530898  3.514020  3.544661  3.577814  3.623707    10  b 
     new_way 15.670487 15.792249 15.948385 15.988003 16.097436 16.206044    10   c
 new_way_uwe  1.982503  2.350001  2.320591  2.394206  2.412751  2.436381    10 a  

Ответы [ 4 ]

0 голосов
/ 31 мая 2018

С помощью дополнительных объяснений ОП , я полагаю, что я получил лучшее понимание проблемы.

ОП хочет удалить неполные подмножества из своего набора данных.Каждая группа group_id1, group_id2 содержит набор уникальных значений key1.Полное подмножество содержит как минимум одну group_id1, group_id2, trait_id1, trait_id2, key1 запись для каждого из key1 значений в group_id1, group_id2group.

Это , а не , необходимо проверить key1 значения при сравнении группировки по group_id1, group_id2, trait_id1, *Уровень 1028 * с уровнем group_id1, group_id2.Достаточно проверить, равно ли число различных значений key1.

Итак, решение ниже следует общей схеме собственного ответа OP , но использует два объединения для достижения результата.:

setkey(dt, group_id1, group_id2, trait_id1, trait_id2, key1)
uni <- unique(dt, by = c("group_id1", "group_id2", "trait_id1", "trait_id2", "key1"))
unique_keys_by_group <- 
  unique(uni, by = c("group_id1", "group_id2", "key1"))[, .N, by = .(group_id1, group_id2)]
unique_keys_by_group_and_trait <- 
  uni[, .N, by = .(group_id1, group_id2, trait_id1, trait_id2)]
# 1st join to pick group/traits combinations with equal number of unique keys
selected_groups_and_traits <- 
  unique_keys_by_group_and_trait[unique_keys_by_group, 
                                 on = .(group_id1, group_id2, N), nomatch = 0L]
# 2nd join to pick records of valid subsets
res <- dt[selected_groups_and_traits, on = .(group_id1, group_id2, trait_id1, trait_id2)][
  order(id), -"N"]

Можно убедиться, что результат идентичен результату OP:

identical(
  intersect_this_by(dt,
                    key_by = c("key1"),
                    split_by = c("group_id1", "group_id2"),
                    intersect_by = c("trait_id1", "trait_id2")),
  res)
[1] TRUE

Обратите внимание, что функция uniqueN() имеет значение не используется из-за проблем с производительностью, как показано в контрольных показателях моего первого (неправильного) ответа .

Сравнение контрольных показателей

используются контрольные данные OP (10 M строк).

library(microbenchmark)
mb <- microbenchmark(
  old_way = {
    DT <- copy(dt)
    res <- intersect_this_by(DT,
                             key_by = c("key1"),
                             split_by = c("group_id1", "group_id2"),
                             intersect_by = c("trait_id1", "trait_id2"))
  },
  uwe = {
    DT <- copy(dt)
    setkey(DT, group_id1, group_id2, trait_id1, trait_id2, key1)
    uni <- 
      unique(DT, by = c("group_id1", "group_id2", "trait_id1", "trait_id2", "key1"))
    unique_keys_by_group <- 
      unique(uni, by = c("group_id1", "group_id2", "key1"))[
        , .N, by = .(group_id1, group_id2)]
    unique_keys_by_group_and_trait <- 
      uni[, .N, by = .(group_id1, group_id2, trait_id1, trait_id2)]
    selected_groups_and_traits <- 
      unique_keys_by_group_and_trait[unique_keys_by_group, 
                                     on = .(group_id1, group_id2, N), nomatch = 0L]
    res <- DT[selected_groups_and_traits, 
              on = .(group_id1, group_id2, trait_id1, trait_id2)][
      order(id), -"N"]
  },
  times = 3L)
mb

Представленное здесь решение на 40% быстрее:

Unit: seconds
    expr      min       lq     mean   median       uq      max neval cld
 old_way 7.251277 7.315796 7.350636 7.380316 7.400315 7.420315     3   b
     uwe 4.379781 4.461368 4.546267 4.542955 4.629510 4.716065     3  a

Редактировать: Дальнейшее улучшение производительности

Оператор запросил у идеи для дальнейшего повышения производительности.

Я уже пробовал разные подходы, включая группирование с двойным вложением (медленное uniqueN() только для упрощенного отображения кода):

res <- DT[, {
  nuk_g = uniqueN(key1) 
  .SD[, if(nuk_g == uniqueN(key1)) .SD, by = .(trait_id1, trait_id2)]
}, by = .(group_id1, group_id2)][order(id)]

но все они были медленнее для даютn эталонные данные .

Вполне вероятно, что производительность конкретного метода зависит не только от размера задачи , т. е. от числа строк, но также от структура проблемы например, количество различных групп, обработок и ключей, а также типы данных и т. Д.

Итак, не зная структуры ваших производственных данных и контекста вашегоПоток вычислений Я не думаю, что стоит тратить больше времени на сравнительный анализ.

В любом случае, есть одно предложение: убедитесь, что setkey() вызывается только один раз, поскольку это довольно дорого (около 2 секунд), но ускоряет все последующие операции.(Проверьте с помощью options(datatable.verbose = TRUE)).

0 голосов
/ 30 мая 2018

Другой ответ не решает проблему, но я нашел метод, вдохновленный этим.Сначала вычислите количество ключей, присутствующих в группе, и для каждой комбинации черт оставьте только ту, которая содержит полное количество ключей

 intersect_this_by2 <- function(dt,
         key_by = NULL,
         split_by = NULL,
         intersect_by = NULL){

    if (is.null(intersect_by) |
        is.null(key_by) |
        !is.data.frame(dt) |
        nrow(dt) == 0) {
        return(dt)
    }
    data_table_input <- is.data.table(dt)
    dtc <- as.data.table(dt)

    if (!is.null(split_by)) {
        # compute number of keys in the group
        dtc[, n_keys := uniqueN(.SD), by = split_by, .SDcols = key_by]
        # compute number of keys represented by each trait in each group 
        # and keep row only if they represent all keys from the group
        dtc[, keep := n_keys == uniqueN(.SD), by = c(intersect_by, split_by), .SDcols = key_by]
        dtc <- dtc[keep == TRUE][, c("n_keys", "keep") := NULL]
    } else {
        dtc[, n_keys := uniqueN(.SD), .SDcols = key_by]
        dtc[, keep := n_keys == uniqueN(.SD), by = c(intersect_by), .SDcols = key_by]
        dtc <- dtc[keep == TRUE][, c("n_keys", "keep") := NULL]
    }
    if (!data_table_input) {
        return(as.data.frame(dtc))
    } else {
        return(dtc)
    }
}

Проблема в том, что она намного медленнее в моем реальном наборе данных (5-6в разы медленнее) но я думаю, что эта функция помогает лучше понять проблему.также набор данных ближе к моему реальному определен ниже:

pacman::p_load(data.table, microbenchmark, testthat)

set.seed(0)
n <- 1e7
p <- 1e5
m <- 5
dt <- data.table(id = 1:n,
                 key1 = sample(1:m, size = n, replace = TRUE),
                 group_id1 = sample(1:2, size = n, replace = TRUE),
                 trait_id1 = sample(1:p, size = n, replace = TRUE),
                 group_id2 = sample(1:2, size = n, replace = TRUE),
                 trait_id2 = sample(1:2, size = n, replace = TRUE),
                 extra = sample(letters, n, replace = TRUE))
microbenchmark::microbenchmark(old_way = {res <- intersect_this_by(dt,
                                                                    key_by = c("key1"),
                                                                    split_by = c("group_id1", "group_id2"),
                                                                    intersect_by = c("trait_id1", "trait_id2"))},
                               new_way = {res <- intersect_this_by2(dt,
                                                                   key_by = c("key1"),
                                                                   split_by = c("group_id1", "group_id2"),
                                                                   intersect_by = c("trait_id1", "trait_id2"))},
                               times = 1)


Unit: seconds
    expr       min        lq      mean    median        uq       max neval
 old_way  5.891489  5.891489  5.891489  5.891489  5.891489  5.891489     1
 new_way 18.455860 18.455860 18.455860 18.455860 18.455860 18.455860     1

Для информации количество строк res в этом примере равно

> set.seed(0)
> n <- 1e7
> p <- 1e5
> m <- 5
> dt <- data.table(id = 1:n,
                   key1 = sample(1:m, size = n, replace = TRUE),
                   group_id1 = sample(1:2, size = n, replace = TRUE),
                   trait_id1 = sample(1:p, size = n, replace = TRUE),
                   group_id2 = sample(1:2, size = n, replace = TRUE),
                   trait_id2 = sample(1:2, size = n, replace = TRUE),
                   extra = sample(letters, n, replace = TRUE))
> res <- intersect_this_by(dt,
                            key_by = c("key1"),
                            split_by = c("group_id1", "group_id2"),
                            intersect_by = c("trait_id1", "trait_id2"))
> nrow(res)
[1] 7099860
> res <- intersect_this_by2(dt,
                            key_by = c("key1"),
                            split_by = c("group_id1", "group_id2"),
                            intersect_by = c("trait_id1", "trait_id2"))
> nrow(res)
[1] 7099860
0 голосов
/ 31 мая 2018

РЕДАКТИРОВАТЬ

Хотя ответ ниже воспроизводит ожидаемый результат для небольшого выборочного набора данных, он не может дать правильный ответ для большого набора данных из 10 М строк, предоставленного OP.

Однако я решил оставить этот неправильный ответ из-за результатов тестов, которые показывают низкую производительность функции uniqueN().Кроме того, ответ содержит тесты гораздо более быстрых альтернативных решений.



Если я правильно понимаю, ОП хочет сохранить только те строки, в которых уникальные комбинации group_id1, group_id2, trait_id1 и trait_id2 встречаются в нескольких различных key1.

Это может быть достигнуто путем подсчета уникальных значений key1 в каждой группе group_id1, group_id2, trait_id1 и trait_id2 и путем выбора только тех комбинаций group_id1, group_id2, trait_id1 и trait_id2, где число больше единицы.Наконец, соответствующие строки извлекаются путем объединения:

library(data.table)
sel <- dt[, uniqueN(key1), by = .(group_id1, group_id2, trait_id1, trait_id2)][V1 > 1]
sel
   group_id1 group_id2 trait_id1 trait_id2 V1
1:         1         2         3         1  2
2:         2         2         2         1  2
3:         2         1         1         2  2
4:         1         1         1         1  2
5:         1         1         2         2  2
6:         2         2         2         2  2
7:         1         1         1         2  2
8:         1         1         3         2  2
res <- dt[sel, on = .(group_id1, group_id2, trait_id1, trait_id2)][order(id), -"V1"]
res
    id key1 group_id1 trait_id1 group_id2 trait_id2 extra
 1:  1    2         1         3         2         1     u
 2:  2    1         2         2         2         1     g
 3:  5    2         2         1         1         2     g
 4:  8    2         1         3         2         1     o
 5:  9    2         1         1         1         1     d
 6: 10    2         2         1         1         2     g
 7: 13    1         2         1         1         2     c
 8: 14    2         1         2         1         2     t
 9: 15    1         1         3         2         1     y
10: 16    2         1         3         2         1     v
11: 19    2         2         2         2         2     y
12: 22    2         2         2         2         1     g
13: 24    2         1         1         1         2     i
14: 25    1         1         3         1         2     n
15: 26    1         2         2         2         2     y
16: 27    1         1         1         1         1     n
17: 28    1         1         1         1         2     h
18: 29    1         2         2         2         2     b
19: 30    2         1         3         1         2     k
20: 31    1         2         2         2         2     w
21: 35    1         1         2         1         2     q
22: 37    2         2         1         1         2     r
23: 39    1         1         1         1         2     o
    id key1 group_id1 trait_id1 group_id2 trait_id2 extra

Это воспроизводит ожидаемый результат OP, но также самый быстрый способ в соответствии с запросом OP?


Сравнительный анализ, часть 1

Код OP для создания контрольных данных (но с 1 M строкамивместо 10 M строк) здесь используется:

set.seed(0)
n <- 1e6
p <- 1e5
m <- 5
dt <- data.table(id = 1:n,
                 key1 = sample(1:m, size = n, replace = TRUE),
                 group_id1 = sample(1:2, size = n, replace = TRUE),
                 trait_id1 = sample(1:p, size = n, replace = TRUE),
                 group_id2 = sample(1:2, size = n, replace = TRUE),
                 trait_id2 = sample(1:2, size = n, replace = TRUE),
                 extra = sample(letters, n, replace = TRUE))

Я был весьма удивлен, обнаружив, что решение с использованием uniqueN() не является самым быстрым:

Unit: milliseconds
    expr       min        lq      mean    median        uq       max neval cld
 old_way  489.4606  496.3801  523.3361  503.2997  540.2739  577.2482     3 a  
 new_way 9356.4131 9444.5698 9567.4035 9532.7265 9672.8987 9813.0710     3   c
    uwe1 5946.4533 5996.7388 6016.8266 6047.0243 6052.0133 6057.0023     3  b

Код теста:

microbenchmark::microbenchmark(
  old_way = {
    DT <- copy(dt)
    res <- intersect_this_by(DT,
                             key_by = c("key1"),
                             split_by = c("group_id1", "group_id2"),
                             intersect_by = c("trait_id1", "trait_id2"))
  },
  new_way = {
    DT <- copy(dt)
    res <- intersect_this_by2(DT,
                              key_by = c("key1"),
                              split_by = c("group_id1", "group_id2"),
                              intersect_by = c("trait_id1", "trait_id2"))
  },
  uwe1 = {
    DT <- copy(dt)
    sel <- DT[, uniqueN(key1), by = .(group_id1, group_id2, trait_id1, trait_id2)][V1 > 1]
    res <- DT[sel, on = .(group_id1, group_id2, trait_id1, trait_id2)][
      order(id)]
  },
  times = 3L)

Обратите внимание, что для каждого прогона используется свежая копия данных теста, чтобы избежать каких-либо побочных эффектов от предыдущих прогонов, например, индексов, установленных data.table.

Включение подробного режима

options(datatable.verbose = TRUE)

показывает, что большую часть времени тратится на вычисления uniqueN() для всех групп:

sel <- DT[, uniqueN(key1), by = .(group_id1, group_id2, trait_id1, trait_id2)][V1 > 1]

Detected that j uses these columns: key1 
Finding groups using forderv ... 0.060sec 
Finding group sizes from the positions (can be avoided to save RAM) ... 0.000sec 
Getting back original order ... 0.050sec 
lapply optimization is on, j unchanged as 'uniqueN(key1)'
GForce is on, left j unchanged
Old mean optimization is on, left j unchanged.
Making each group and running j (GForce FALSE) ... 
  collecting discontiguous groups took 0.084s for 570942 groups
  eval(j) took 5.505s for 570942 calls
5.940sec

Это известная проблема .Однако альтернатива lenght(unique()) (для которой uniqueN() является аббревиатурой) приносит лишь умеренное ускорение 2.

Так что я начал искать способы избежать uniqueN() или lenght(unique()).


Бенчмаркинг, часть 2

Я нашел две альтернативы, которые достаточно быстрые.Оба создают таблицу данных уникальных комбинаций group_id1, group_id2, trait_id1, trait_id2, и key1 на первом шаге, подсчитывают количество различных значений key1для каждой группы group_id1, group_id2, trait_id1, trait_id2 и фильтр для счетчиков с большим значением:

sel <- DT[, .N, by = .(group_id1, group_id2, trait_id1, trait_id2, key1)][
  , .N, by = .(group_id1, group_id2, trait_id1, trait_id2)][N > 1]

и

sel <- unique(DT, by = c("group_id1", "group_id2", "trait_id1", "trait_id2", "key1"))[
  , .N, by = .(group_id1, group_id2, trait_id1, trait_id2)][N > 1]

Подробный вывод показываетчто время вычислений для этих вариантов значительно лучше.

Для бенчмаркинга используются только самые быстрые методы, но теперь с 10 М строками.Кроме того, каждый вариант пробуется с setkey() и setorder(), соответственно, примененными заранее:

microbenchmark::microbenchmark(
  old_way = {
    DT <- copy(dt)
    res <- intersect_this_by(DT,
                             key_by = c("key1"),
                             split_by = c("group_id1", "group_id2"),
                             intersect_by = c("trait_id1", "trait_id2"))
  },
  uwe3 = {
    DT <- copy(dt)
    sel <- DT[, .N, by = .(group_id1, group_id2, trait_id1, trait_id2, key1)][
      , .N, by = .(group_id1, group_id2, trait_id1, trait_id2)][N > 1]
    res <- DT[sel, on = .(group_id1, group_id2, trait_id1, trait_id2)][
      order(id)]
  },
  uwe3k = {
    DT <- copy(dt)
    setkey(DT, group_id1, group_id2, trait_id1, trait_id2, key1)
    sel <- DT[, .N, by = .(group_id1, group_id2, trait_id1, trait_id2, key1)][
      , .N, by = .(group_id1, group_id2, trait_id1, trait_id2)][N > 1]
    res <- DT[sel, on = .(group_id1, group_id2, trait_id1, trait_id2)][
      order(id)]
  },
  uwe3o = {
    DT <- copy(dt)
    setorder(DT, group_id1, group_id2, trait_id1, trait_id2, key1)
    sel <- DT[, .N, by = .(group_id1, group_id2, trait_id1, trait_id2, key1)][
      , .N, by = .(group_id1, group_id2, trait_id1, trait_id2)][N > 1]
    res <- DT[sel, on = .(group_id1, group_id2, trait_id1, trait_id2)][
      order(id)]
  },
  uwe4 = {
    DT <- copy(dt)
    sel <- unique(DT, by = c("group_id1", "group_id2", "trait_id1", "trait_id2", "key1"))[
      , .N, by = .(group_id1, group_id2, trait_id1, trait_id2)][N > 1]
    res <- DT[sel, on = .(group_id1, group_id2, trait_id1, trait_id2)][
      order(id)]
  },
  uwe4k = {
    DT <- copy(dt)
    setkey(DT, group_id1, group_id2, trait_id1, trait_id2, key1)
    sel <- unique(DT, by = c("group_id1", "group_id2", "trait_id1", "trait_id2", "key1"))[
      , .N, by = .(group_id1, group_id2, trait_id1, trait_id2)][N > 1]
    res <- DT[sel, on = .(group_id1, group_id2, trait_id1, trait_id2)][
      order(id)]
  },
  uwe4o = {
    DT <- copy(dt)
    setorder(DT, group_id1, group_id2, trait_id1, trait_id2, key1)
    sel <- unique(DT, by = c("group_id1", "group_id2", "trait_id1", "trait_id2", "key1"))[
      , .N, by = .(group_id1, group_id2, trait_id1, trait_id2)][N > 1]
    res <- DT[sel, on = .(group_id1, group_id2, trait_id1, trait_id2)][
      order(id)]
  },
  times = 3L)

Результаты тестов для случая 10 M показывают, что оба варианта быстрее, чем OP intersect_this_by()функция и что при наборе и вводе в порядок ускоряется (с минимальным преимуществом для заказа).

Unit: seconds
    expr      min       lq     mean   median       uq      max neval  cld
 old_way 7.173517 7.198064 7.256211 7.222612 7.297559 7.372506     3    d
    uwe3 6.820324 6.833151 6.878777 6.845978 6.908003 6.970029     3   c 
   uwe3k 5.349949 5.412018 5.436806 5.474086 5.480234 5.486381     3 a   
   uwe3o 5.423440 5.432562 5.467376 5.441683 5.489344 5.537006     3 a   
    uwe4 6.270724 6.276757 6.301774 6.282790 6.317299 6.351807     3  b  
   uwe4k 5.280763 5.295251 5.418803 5.309739 5.487823 5.665906     3 a   
   uwe4o 4.921627 5.095762 5.157010 5.269898 5.274702 5.279506     3 a
0 голосов
/ 30 мая 2018

Я начну с подхода tidyverse и покажу эквивалент в data.table.

Дайте мне знать, если этот результат не тот, который предназначен, потому что он отличается от вашего требуемого результата - но егочто вы описали в тексте.

1.Опрятный подход

Просто создайте один столбец из признаков, а затем группируйте по столбцам группировки и новым комбинированным признакам.Фильтр для групповой частоты больше 1.

dt %>%
  mutate(comb = paste0(trait_id1, trait_id2)) %>%
  group_by(group_id1, group_id2, comb) %>%
  filter(n() > 1)

2.подход data.table

Практически такая же методология, как и в предыдущем подходе аккуратности, только что написанном в data.table.

Использование ответа из здесь для поиска методов быстрой вставки.

dt[, comb := do.call(paste, c(.SD, sep = "")), .SDcols = c("trait_id1", "trait_id2")][, freq := .N, by = .(group_id1, group_id2, comb)][freq > 1]

Сравнение

Сравнивая два метода, Chinsoons комментируют скорости:

microbenchmark::microbenchmark(zac_tidy = {
  dt %>%
    mutate(comb = paste0(trait_id1, trait_id2)) %>%
    group_by(group_id1, group_id2, comb) %>%
    filter(n() > 1)
},
zac_dt = {
  dt[, comb := do.call(paste, c(.SD, sep = "")), .SDcols = c("trait_id1", "trait_id2")][, freq := .N, by = .(group_id1, group_id2, comb)][freq > 1]
},
chin_dt = {
  dt[id %in% dt[, .SD[, if (.N > 1) id, by=.(trait_id1, trait_id2)], by=.(group_id1, group_id2)]$V1]
}, times = 100)

Unit: milliseconds
     expr      min       lq     mean   median       uq       max neval
 zac_tidy 4.151115 4.677328 6.150869 5.552710 7.765968  8.886388   100
   zac_dt 1.965013 2.201499 2.829999 2.640686 3.507516  3.831240   100
  chin_dt 4.567210 5.217439 6.972013 7.330628 8.233379 12.807005   100

> identical(zac_dt, chin_dt)
[1] TRUE

Сравнение на 10 миллионов

10 повторений:

Unit: milliseconds
     expr       min        lq      mean    median       uq       max neval
 zac_tidy 12.492261 14.169898 15.658218 14.680287 16.31024 22.062874    10
   zac_dt 10.169312 10.967292 12.425121 11.402416 12.23311 21.036535    10
  chin_dt  6.381693  6.793939  8.449424  8.033886  9.78187 12.005604    10
 chin_dt2  5.536246  6.888020  7.914103  8.310142  8.74655  9.600121    10

Поэтому я бы порекомендовал метод Чинсуна.Либо работает.

...