Мне дают большую таблицу данных с двумя индикаторами ind1
и ind2
с возможными повторениями. Например.
set.seed(1)
ind1 <- sample(1:3,1000, replace=TRUE )
ind2 <- c("a","b","c")[ind1]
dt <- data.table(ind1=ind1, ind2=ind2)
Теперь я хотел бы проверить, группируют ли эти два индикатора данные одинаково, т.е.
две строки имеют одинаковый индикатор ind1
тогда и только тогда, когда они также имеют одинаковый индикатор ind2
. В приведенном выше примере это будет иметь место по построению.
Вы можете просто сгруппировать по ind2
и подсчитать отдельные ind1
или наоборот. Если какой-либо счетчик > 1, они не группируют данные одинаково. Вот способ с базой R -
any(with(dt, ave(ind1, ind2, FUN = function(x) length(unique(x)))) > 1)
[1] FALSE # means ind1 and ind2 group the data in same way
Кроме того, вы можете проверить, все ли count == 1, используя all
, если это легче интерпретировать -
all(with(dt, ave(ind1, ind2, FUN = function(x) length(unique(x)))) == 1)
[1] TRUE # means ind1 and ind2 group the data in same way
Вы можете создать числовой индекс группы с обеими переменными и проверить, что они равны для всех строк.
Это добавит два групповых индекса в таблицу, а также проверит равенство, но вы можете удалить столбцы после, если это необходимо.
dt[, g1 := .GRP, ind1][, g2 := .GRP, ind2][, all(g1 == g2)]
#[1] TRUE
Редактировать: идея Shree с уникальным подсчетом лучше. См. ниже реализацию data.table.
Edit2: также см. комментарии для других решений.
dt[, uniqueN(ind2), ind1][, all(V1 == 1)]
#[1] TRUE
Сравнительный анализ с таблицей, содержащей 1e7 строк и 10 групп, представленных двумя эквивалентными столбцами.
set.seed(1)
ind1 <- sample(1:10,1e7, replace=TRUE )
ind2 <- c("a","b","c")[ind1]
dt <- data.table(ind1=ind1, ind2=ind2)
microbenchmark::microbenchmark(
grp = dt[, g1 := .GRP, ind1][, g2 := .GRP, ind2][, all(g1 == g2)],
uniques = dt[, uniqueN(ind2), ind1][, all(V1 == 1)]
)
# Unit: milliseconds
# expr min lq mean median uq max neval cld
# grp 727.9489 838.2190 918.280 879.1036 971.3982 1542.9655 100 b
# uniques 472.1311 502.1327 529.581 526.5357 540.5406 723.5078 100 a
К вашему сведению, я считаю, что уникальность — это очень плохо, если я масштабирую проблему: чат.stackoverflow.com/transcript/message/46781006#46781006
Для меня, если я изменяю 1e4 на 1e6 в этом связанном тесте, он меняется так, что вариант «uniques» лучше, чем все, кроме «другого». Я бы сказал, что стоит опубликовать эту информацию и другие подходы в качестве отдельного ответа.
Хорошо, да, я настраиваю параметры n и ng с момента моего предыдущего комментария и вижу, что сравнения идут во всех направлениях. Опубликую еще один ответ, спасибо
unique(length(ind2))
примерно в два раза быстрее, чем uniqueN(ind2)
.
Мой инстинкт состоял бы в том, чтобы использовать .GRP, как в ответе @IceCreamToucan, но другой способ - совместно удалить дубликаты для двух столбцов, а затем проверить наличие дубликатов в каждом столбце отдельно:
# data.table
unique(dt[, c("ind1", "ind2")])[, !(anyDuplicated(ind1) || anyDuplicated(ind2))]
# base, with df = data.frame(dt)
with(unique(df[, c("ind1", "ind2")]), !(anyDuplicated(ind1) || anyDuplicated(ind2)))
Я пробовал различные тесты, но не вижу каких-либо четких результатов, за исключением, как ни странно, того, что время между двумя вышеприведенными вариантами почти всегда сильно благоприятствует data.table.
Пример с параметрами для #rows и #groups:
library(data.table)
library(magrittr)
ng = 150
n = 1e6
set.seed(1)
ind1 <- sample(1:ng, n, replace=TRUE )
ind2 <- -ind1
dt <- data.table(ind1=ind1, ind2=ind2)
df = data.frame(dt)
microbenchmark::microbenchmark(times = 3L,
grp = dt[, g1 := .GRP, ind1][, g2 := .GRP, ind2][, all(g1 == g2)],
uniques = dt[, uniqueN(ind2), ind1][, all(V1 == 1)],
shreet = with(dt, max(tapply(ind1, ind2, function(x) length(unique(x))))) == 1L,
shreep = with(dt, tapply(ind1, ind2, . %>% unique %>% length)) %>% max %>% equals(1L),
another = unique(dt[, c("ind1", "ind2")])[, !(anyDuplicated(ind1) || anyDuplicated(ind2))],
banother = with(unique(df[, c("ind1", "ind2")]), !(anyDuplicated(ind1) || anyDuplicated(ind2)))
)
Результаты:
Unit: milliseconds
expr min lq mean median uq max neval
grp 31.89250 34.92348 46.06510 37.95446 53.15140 68.34833 3
uniques 32.82520 34.36808 36.32377 35.91097 38.07306 40.23515 3
shreet 38.26046 38.35256 44.37116 38.44467 47.42650 56.40834 3
shreep 43.37336 98.56367 145.38600 153.75399 196.39231 239.03064 3
another 14.47064 31.42879 88.20134 48.38694 125.06669 201.74643 3
banother 1338.14070 1427.35481 1658.08404 1516.56893 1818.05572 2119.54251 3
я постоянно получаю another
, чтобы быть быстрее, независимо от использования microbenchmark
или bench::mark
. Использование Win 7 R-3.5.1 x64 data.table 1.12.2, DTthreads=4, 16GB RAM
Я тоже получаю another
как самый быстрый. Если uniques
использует unique(length(ind1))
, для меня это второе место со значительным отрывом. У меня 2 ядра/4 потока.
Другой:
unique(dt[, c("ind1", "ind2")])[, !(anyDuplicated(ind1) || anyDuplicated(ind2))]
... и еще один вариант Шри, я думаю:with(dt, max(tapply(ind1, ind2, function(x) length(unique(x))))) == 1L