Работая с 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/parApply
для использования параллельной обработки. Подход apply
тоже вполне читаем.
@HieuNguyen Я не согласен. а) data.table уже распараллелен внутри. б) Если вы перебираете строки, вы обычно не используете эффективный подход.
@Roland Существует небольшой набор функций, оптимизированных для GForce, однако пользовательские функции, такие как сообщение, на которое я ссылался выше, по-прежнему выигрывают от явного распараллеливания. ОП в этом посте прокомментировал, что для них это значительно ускорилось.
@HieuNguyen Если вам действительно нужно перебирать строки (вам это не нужно), вам следует написать функцию Rcpp. Явное распараллеливание того стоит только в том случае, если ваша функция сложна и требует много времени, и в этом случае мне было бы интересно, почему вы используете data.table.
@Roland, как вы думаете, лучше изменить форму, а затем получить уникальные значения по группам? Или это лучший подход вне data.table? Я использую data.table в качестве основного синтаксиса фреймов данных, предполагая, что в большинстве случаев это самый быстрый подход. Тем не менее, я всегда ищу более эффективные подходы. Спасибо.
Это зависит от специфики, но с вашим примером я бы сначала спросил, почему вы вообще это делаете. В вашем примере я бы расплавил data.table и использовал data.table::unique. Никаких циклов по рядам не требуется.
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")]
Вы можете попробовать следующий подход:
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
и добавить его в таблицу данных. Но опять же, зачем вам это нужно?
Никогда не используйте
apply
при работе с data.table.