Несогласованный вывод data.table с использованием apply(.SD, 1, FUN)

Работая с data.table в R, я пытаюсь объединить два столбца и создать новый столбец, в котором у меня есть уникальные значения предыдущего шага. В примере ниже видно, что с фреймом z1 код работает нормально, но с фреймом z2 у меня возникла ошибка. Однако обе таблицы были созданы одинаково. В столбцах разная информация, но это не должно быть причиной того, что один и тот же код не работает на z2.

Большое спасибо за вашу помощь, и, пожалуйста, дайте мне знать, если я не понимаю.

Лучший,

library(data.table)


z1 <- data.table(a = c("ARE_2014_HIES_D1_INC_GROUP", "ARE_2014_HIES_D1_INC_GROUP"), 
                 b = c("ARE_2014_HIES_D1_INC_GROUP", "ARE_2015_HIES_D1_INC_GROUP"))


z2 <- data.table(a = c("ARG_1980_EPH_D2_INC_GROUP", "ARG_1980_EPH_D2_INC_GROUP"), 
                 b = c("ARG_1986_EPH_D2_INC_HIST", "ARG_1986_EPH_D2_INC_HIST"))


z1[,
   cache_id := as.list(apply(.SD, 1, unique)),
   .SDcols = c("a", "b")
]

z1[]
#>                             a                          b
#> 1: ARE_2014_HIES_D1_INC_GROUP ARE_2014_HIES_D1_INC_GROUP
#> 2: ARE_2014_HIES_D1_INC_GROUP ARE_2015_HIES_D1_INC_GROUP
#>                                                 cache_id
#> 1:                            ARE_2014_HIES_D1_INC_GROUP
#> 2: ARE_2014_HIES_D1_INC_GROUP,ARE_2015_HIES_D1_INC_GROUP


z2[,
   cache_id := as.list(apply(.SD, 1, unique)),
   .SDcols = c("a", "b")
]
#> Error in `[.data.table`(z2, , `:=`(cache_id, as.list(apply(.SD, 1, unique))), : Supplied 4 items to be assigned to 2 items of column 'cache_id'. If you wish to 'recycle' the RHS please use rep() to make this intent clear to readers of your code.
z2[]
#>                            a                        b
#> 1: ARG_1980_EPH_D2_INC_GROUP ARG_1986_EPH_D2_INC_HIST
#> 2: ARG_1980_EPH_D2_INC_GROUP ARG_1986_EPH_D2_INC_HIST

Created on 2023-06-12 with reprex v2.0.2

Никогда не используйте apply при работе с data.table.

Roland 13.06.2023 10:41

@ Роланд, я не думаю, что это хороший совет. См. еще один ответ, в котором использовалась функция apply/parApply для использования параллельной обработки. Подход apply тоже вполне читаем.

Hieu Nguyen 13.06.2023 19:42

@HieuNguyen Я не согласен. а) data.table уже распараллелен внутри. б) Если вы перебираете строки, вы обычно не используете эффективный подход.

Roland 14.06.2023 07:11

@Roland Существует небольшой набор функций, оптимизированных для GForce, однако пользовательские функции, такие как сообщение, на которое я ссылался выше, по-прежнему выигрывают от явного распараллеливания. ОП в этом посте прокомментировал, что для них это значительно ускорилось.

Hieu Nguyen 14.06.2023 08:17

@HieuNguyen Если вам действительно нужно перебирать строки (вам это не нужно), вам следует написать функцию Rcpp. Явное распараллеливание того стоит только в том случае, если ваша функция сложна и требует много времени, и в этом случае мне было бы интересно, почему вы используете data.table.

Roland 14.06.2023 08:25

@Roland, как вы думаете, лучше изменить форму, а затем получить уникальные значения по группам? Или это лучший подход вне data.table? Я использую data.table в качестве основного синтаксиса фреймов данных, предполагая, что в большинстве случаев это самый быстрый подход. Тем не менее, я всегда ищу более эффективные подходы. Спасибо.

R.Andres Castaneda 14.06.2023 17:51

Это зависит от специфики, но с вашим примером я бы сначала спросил, почему вы вообще это делаете. В вашем примере я бы расплавил data.table и использовал data.table::unique. Никаких циклов по рядам не требуется.

Roland 14.06.2023 19:24
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
61
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

apply возвращает матрицу, если каждый результат имеет одинаковую длину, в противном случае список:

apply(z1[,.(a ,b)], 1, unique)

[[1]]
[1] "ARE_2014_HIES_D1_INC_GROUP"

[[2]]
[1] "ARE_2014_HIES_D1_INC_GROUP" "ARE_2015_HIES_D1_INC_GROUP"

apply(z2[, .(a, b), 1, unique)

     [,1]                        [,2]                       
[1,] "ARG_1980_EPH_D2_INC_GROUP" "ARG_1980_EPH_D2_INC_GROUP"
[2,] "ARG_1986_EPH_D2_INC_HIST"  "ARG_1986_EPH_D2_INC_HIST" 

Кроме того, as.list в матрице не дает вам список по столбцам, но вы получаете каждый элемент как элемент списка:

as.list(apply(z2[, .(a, b)], 1, unique))

[[1]]
[1] "ARG_1980_EPH_D2_INC_GROUP"

[[2]]
[1] "ARG_1986_EPH_D2_INC_HIST"

[[3]]
[1] "ARG_1980_EPH_D2_INC_GROUP"

[[4]]
[1] "ARG_1986_EPH_D2_INC_HIST"

отсюда и предупреждение о длине.

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

Использование simplify = FALSE в apply может привести к тому, что результатом apply будет постоянный список. Тогда ответ для OP просто z2[, cache_id := apply(.SD, 1, unique, simplify = FALSE), .SDcols = c("a", "b")]

Hieu Nguyen 13.06.2023 19:26
Ответ принят как подходящий

Вы можете попробовать следующий подход:

z1[, cache_id:=list(.(unique(c(a,b)))), 1:nrow(z1)]

и аналогично для z2

Выход:

                            a                          b                                              cache_id
                       <char>                     <char>                                                <list>
1: ARE_2014_HIES_D1_INC_GROUP ARE_2014_HIES_D1_INC_GROUP                            ARE_2014_HIES_D1_INC_GROUP
2: ARE_2014_HIES_D1_INC_GROUP ARE_2015_HIES_D1_INC_GROUP ARE_2014_HIES_D1_INC_GROUP,ARE_2015_HIES_D1_INC_GROUP

Другой подход, который не требует повторения строк, таких как ответ @langtang:

z1[, cache_id := lapply(.mapply(c, .SD, NULL), unique), .SDcols = c("a", "b")
   ][, cache_id := sapply(cache_id, paste, collapse = ", ")]

Спасибо всем за ваши ответы. Они действительно помогли мне, и я узнал больше о apply и data.table. Я выбираю ответ @langtang, потому что он самый быстрый. Тем не менее, большое спасибо @hieu-nguyen за оба решения. Я думаю, что simply = FALSE был ключом к проблеме, но вы указали на это в комментарии, который я не могу выбрать в качестве ответа. Пожалуйста, найдите ниже эталон

library(data.table)


n <- 1e4
x <- sapply(1:n, \(x) sample(letters, 10) |> paste(collapse = ""))
y <- sapply(1:n, \(x) sample(letters, 10) |> paste(collapse = ""))

ni <- sample(1:n, floor(n/10), replace = FALSE)

x[ni] <- y[ni]



z1 <- data.table(a = x, 
                 b = y)

bench <- microbenchmark::microbenchmark(
  times = 30,
  simplify = z1[,
                cache_id := as.list(apply(.SD, 1, unique, simplify = FALSE)),
                .SDcols = c("a", "b")],
  loop_rows = z1[, cache_id:=list(.(unique(c(a,b)))), 1:nrow(z1)], 
  mapply    = z1[, cache_id := lapply(.mapply(c, .SD, NULL), unique), .SDcols = c("a", "b")]
)

bench
#> Unit: milliseconds
#>       expr       min        lq     mean   median       uq      max neval cld
#>   simplify 145.03549 171.67624 209.7165 214.4948 244.1717 268.3255    30 a  
#>  loop_rows  80.62317  98.74864 110.2403 106.6774 122.0016 148.4702    30  b 
#>     mapply 337.39212 409.21162 482.0041 478.9344 544.5397 765.9302    30   c

Created on 2023-06-13 with reprex v2.0.2

Непонятно, почему вы это делаете, и я подозреваю, что мы имеем дело с xy проблемой. В любом случае вам почти никогда не придется перебирать строки data.table. Обычно это проблема дизайна. Если вам действительно нужно это сделать, обратитесь к Rcpp, если это не одноразовое действие или если ваша таблица данных действительно велика.

В любом случае, в конкретном примере вы можете использовать data.table::unique:

library(data.table)
z1 <- data.table(a = c("ARE_2014_HIES_D1_INC_GROUP", "ARE_2014_HIES_D1_INC_GROUP"), 
                b = c("ARE_2014_HIES_D1_INC_GROUP", "ARE_2015_HIES_D1_INC_GROUP"))
z1[, rn := .I]
unique(melt(z1, "rn"), by = c("rn", "value"))
#   rn variable                      value
#1:  1        a ARE_2014_HIES_D1_INC_GROUP
#2:  2        a ARE_2014_HIES_D1_INC_GROUP
#3:  2        b ARE_2015_HIES_D1_INC_GROUP

При необходимости вы можете разделить столбец value на rn и добавить его в таблицу данных. Но опять же, зачем вам это нужно?

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

Фильтрация или выбор в data.table приводит к тому, что новая таблица data.table оказывается пустой с правильными столбцами, но без строк
Как преобразовать данные из длинного в широкий формат в r?
Как я могу условно присвоить значения столбцу в data.table, используя функцию, содержащую операторы «если» и функцию сдвига?
Как рассчитать среднее значение столбцов, если не все столбцы присутствуют во всех файлах?
Сравнить две ячейки data.table за ячейкой в ​​​​R
Как отфильтровать строки, в которых все определенные столбцы являются NA с data.table?
Нужно пропустить разное количество строк в R
Как устранить ошибку «Атрибут класса в столбце 1 разных элементов не совпадает» в R
Включить уровни фактора нулевого счета при подсчете количества строк в data.table
Как я могу прочитать все файлы CSV из заархивированной папки, используя функцию R data.table fread(), выполнить предварительную обработку и добавить в результат столбец с исходным именем файла?