У меня есть data.table
с более чем 200 переменными, и все они двоичные. Я хочу создать в нем новый столбец, который подсчитывает разницу между каждой строкой и опорным вектором:
#Example
dt = data.table(
"V1" = c(1,1,0,1,0,0,0,1,0,1,0,1,1,0,1,0),
"V2" = c(0,1,0,1,0,1,0,0,0,0,1,1,0,0,1,0),
"V3" = c(0,0,0,1,1,1,1,0,1,0,1,0,1,0,1,0),
"V4" = c(1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0),
"V5" = c(1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0)
)
reference = c(1,1,0,1,0)
Я могу сделать это с помощью небольшого цикла for, например
distance = NULL
for(i in 1:nrow(dt)){
distance[i] = sum(reference != dt[i,])
}
Но это довольно медленный и, конечно, не лучший способ сделать это. Я пытался:
dt[,"distance":= sum(reference != c(V1,V2,V3,V4,V5))]
dt[,"distance":= sum(reference != .SD)]
Но ни то, ни другое не работает, так как они возвращают одно и то же значение для всех строк. Кроме того, решение, в котором мне не нужно вводить все имена переменных, было бы намного лучше, поскольку реальная таблица данных имеет более 200 столбцов.
Вы можете использовать sweep()
с rowSums
, т.е.
rowSums(sweep(dt, 2, reference) != 0)
#[1] 2 2 2 2 4 4 3 2 4 3 2 1 3 4 1 3
ЭТАЛОН
HUGH <- function(dt) {
dt[, I := .I]
distance_by_I <- melt(dt, id.vars = "I")[, .(distance = sum(reference != value)), keyby = "I"]
return(dt[distance_by_I, on = "I"])
}
Sotos <- function(dt) {
return(rowSums(sweep(dt, 2, reference) != 0))
}
dt1 <- as.data.table(replicate(5, sample(c(0, 1), 100000, replace = TRUE)))
microbenchmark(HUGH(dt1), Sotos(dt1))
#Unit: milliseconds
# expr min lq mean median uq max neval cld
# HUGH(dt1) 112.71936 117.03380 124.05758 121.6537 128.09904 155.68470 100 b
# Sotos(dt1) 23.66799 31.11618 33.84753 32.8598 34.02818 68.75044 100 a
Это решение действительно хорошее и отлично работает в примере с игрушкой. Но почему-то при обращении к реальным данным получаю ошибку: Error in Ops.data.frame(x, aperm(array(STATS, dims[perm]), order(perm)), : list of length 3860579 not meaningful
. Любая идея, почему?
Без понятия. Какова структура вашей таблицы данных? (str(dt)
)
rowSums(dt - as.data.table(as.list(reference))[rep(1, nrow(dt))] != 0)
кажется немного быстрее, если скорость является проблемой.
Все числовые. Я также пытался изменить оба на все целые, но безуспешно. Но спасибо, это уже было полезно, мне просто нужно расширить пример, чтобы он соответствовал тому, что у меня есть.
@ Хенрик Я пытался добавить его в бенчмаркинг, но выдает ошибку, Error in Ops.data.frame(dt1, as.data.table(as.list(reference))[rep(1, : ‘-’ only defined for equally-sized data frames
@Сотос, хорошо. Части ошибки data.frame
кажутся подозрительными. Это работает здесь. f <- function(dt) rowSums(dt - as.data.table(as.list(reference))[rep(1, nrow(dt))] != 0)
; all.equal(Sotos(dt1), f(dt1))
, TRUE
.
интересно, я не получил такой большой разницы в моем тесте между вашими решениями.
Растопите таблицу, затем сравните каждую группу.
dt[, I := .I] # Add a dummy id if one doesn't already exist
distance_by_I <- melt(dt, id.vars = "I")[, .(distance = sum(reference != value)), keyby = "I"]
dt[distance_by_I, on = "I"]
Вот еще один способ:
mm <- function(dt){
colSums(t(dt) != reference)
}
mm(dt)
# [1] 2 2 2 2 4 4 3 2 4 3 2 1 3 4 1 3
ориентир
library(data.table)
dt1 <- as.data.table(replicate(5, sample(c(0, 1), 100000, replace = TRUE)))
identical(Sotos(dt1), mm(dt1))
# [1] TRUE
microbenchmark::microbenchmark(HUGH(dt1), Sotos(dt1), mm(dt1))
# Unit: milliseconds
# expr min lq mean median uq max neval cld
# HUGH(dt1) 85.542550 101.339416 129.71317 106.634169 112.66004 473.9380 100 b
# Sotos(dt1) 35.699128 42.677696 125.95430 180.302919 189.34098 377.9523 100 b
# mm(dt1) 4.604986 7.002416 17.57238 9.819895 12.27015 165.1440 100 a
Это действительно интересно. Я понятия не имею, почему бенчмаркинг отличается.
Другой:
ref = as.list(reference)
dt[, Reduce(`+`, Map(`!=`, .SD, ref))]
Как это работает. Итак, мы берем каждый векторный столбец в .SD
и сравниваем его с единственным соответствующим значением в ref
. Функция !=
векторизована, поэтому каждый элемент ref
перерабатывается, чтобы соответствовать длине каждого вектора.
Этот вызов Map
возвращает список векторов TRUE/FALSE, по одному для каждого столбца. Когда мы складываем значения TRUE/FALSE, они обрабатываются как 1/0, поэтому нам просто нужно сложить эти столбцы. Этого можно добиться, передав парный оператор +
между первым столбцом и вторым; а затем снова между результатом этого вычисления и третьим столбцом; и так далее. Вот как работает Reduce
. Это может быть более читабельно, как
x = dt[, Map(`!=`, .SD, ref)]
Reduce(`+`, x, init = 0L)
который можно прочитать как
См. также ?Map
и ?Reduce
.
Тайминги. Я изменяю данные теста, поскольку использование целых чисел кажется более разумным, если OP действительно имеет данные 0-1. Кроме того, добавление дополнительных столбцов, поскольку ОП говорит, что их много. Наконец, отредактировав ответ Хью, чтобы его можно было сравнить с другими:
HUGH <- function(dt, r) {
dt[, I := .I]
res <- melt(dt, id.vars = "I")[, .(distance = sum(r != value)), keyby = "I"]$distance
dt[, I := NULL]
res
}
Sotos <- function(dt, r) {
return(rowSums(sweep(dt, 2, r) != 0))
}
mm <- function(dt, r){
colSums(t(dt) != r)
}
ff <- function(DT, r){
DT[, Reduce(`+`, Map(`!=`, .SD, r))]
}
nr = 20000
nc = 500
dt1 <- as.data.table(replicate(nc, sample(0:1, nr, replace = TRUE)))
ref <- rep(as.integer(reference), length.out=nc)
lref = as.list(ref)
identical(HUGH(dt1, ref), ff(dt1, lref)) # integer output
identical(mm(dt1, ref), Sotos(dt1, ref)) # numeric output
all.equal(HUGH(dt1, ref), mm(dt1, ref)) # but they match
# all TRUE
microbenchmark::microbenchmark(times = 3,
HUGH(dt1, ref),
Sotos(dt1, ref),
mm(dt1, ref),
ff(dt1, lref)
)
Результат:
Unit: milliseconds
expr min lq mean median uq max neval
HUGH(dt1, ref) 365.0529 370.05233 378.8826 375.0517 385.79737 396.5430 3
Sotos(dt1, ref) 871.5693 926.50462 961.5527 981.4400 1006.54437 1031.6488 3
mm(dt1, ref) 104.5631 121.74086 131.7157 138.9186 145.29197 151.6653 3
ff(dt1, lref) 87.0800 87.48975 93.1361 87.8995 96.16415 104.4288 3
Мне нравится этот ответ, но я не очень понимаю, что происходит ... +`, Map(`!=
- это функция, передаваемая Reduce
? Не могли бы вы немного рассказать о том, как это работает, или дать ссылку, которую я могу прочитать?
@Fino :) Конечно, я добавил некоторые пояснения к Map и Reduce.
Чтобы сохранить его как новый столбец:
dt[, distance:=rowSums(sweep(.SD, 2, reference) != 0)]