Как мне сослаться на всю строку при создании нового столбца в data.table?

У меня есть 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 столбцов.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
0
166
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Вы можете использовать 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 

Чтобы сохранить его как новый столбец: dt[, distance:=rowSums(sweep(.SD, 2, reference) != 0)]

LocoGris 24.01.2019 14:10

Это решение действительно хорошее и отлично работает в примере с игрушкой. Но почему-то при обращении к реальным данным получаю ошибку: Error in Ops.data.frame(x, aperm(array(STATS, dims[perm]), order(perm)), : list of length 3860579 not meaningful. Любая идея, почему?

Fino 24.01.2019 14:23

Без понятия. Какова структура вашей таблицы данных? (str(dt))

Sotos 24.01.2019 14:28
rowSums(dt - as.data.table(as.list(reference))[rep(1, nrow(dt))] != 0) кажется немного быстрее, если скорость является проблемой.
Henrik 24.01.2019 14:56

Все числовые. Я также пытался изменить оба на все целые, но безуспешно. Но спасибо, это уже было полезно, мне просто нужно расширить пример, чтобы он соответствовал тому, что у меня есть.

Fino 24.01.2019 14:56

@ Хенрик Я пытался добавить его в бенчмаркинг, но выдает ошибку, Error in Ops.data.frame(dt1, as.data.table(as.list(reference))[rep(1, : ‘-’ only defined for equally-sized data frames

Sotos 24.01.2019 15:07

@Сотос, хорошо. Части ошибки 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.

Henrik 24.01.2019 15:14

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

Moody_Mudskipper 24.01.2019 17:01

Растопите таблицу, затем сравните каждую группу.

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 

Это действительно интересно. Я понятия не имею, почему бенчмаркинг отличается.

Sotos 24.01.2019 18:00

Другой:

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)

который можно прочитать как

  • v = 0
  • для каждого xi в x обновить v = v + xi

См. также ?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 24.01.2019 18:16

@Fino :) Конечно, я добавил некоторые пояснения к Map и Reduce.

Frank 24.01.2019 19:03

Другие вопросы по теме