Я пытаюсь выполнить полусоединение со списком кадров данных. У меня есть около 1000 фреймов данных из 50 000 строк, в которых я хочу выбрать подмножество строк в зависимости от другого фрейма данных.
Рассмотрим этот пример:
df1 <- data.frame(id = 50:150)
set.seed(1)
l <- replicate(1000,
data.frame(id = sample(1:100, 50000, replace = TRUE),
id2 = sample(letters, 50000, replace = TRUE),
info = runif (50000, 200, 300)),
simplify = FALSE)
Я хочу подмножить каждый фрейм данных l
в зависимости от идентификаторов df1
. Я могу сделать это, выполнив полусоединение или подмножество с помощью %in%
:
library(dplyr)
semi_join(l[[1]], df1, "id")
l[[1]][l[[1]]$id %in% df1$id, ]
Я ищу быстрое решение, которое масштабируется до тысяч кадров данных. пока что я оборачиваю это в lapply
(но могут быть более быстрые векторизованные решения).
lapply(l, \(x) semi_join(x, df1, "id"))
Вот начальный тест с решениями:
bc <- bench::mark(lapply(l, \(x) semi_join(x, df1, "id")),
lapply(l, \(x) x[x$id %in% df1$id, ]), iterations = 1, check = FALSE)
> bc
# A tibble: 2 × 13
# expression min median itr/s…¹ mem_a…² gc/se…³ n_itr
# <bch:expr> <bch:tm> <bch:t> <dbl> <bch:b> <dbl> <int>
#1 lapply(l, function(x) semi_join(x, df1, "id")) 9.34s 9.34s 0.107 1.97GB 0.428 1
#2 lapply(l, function(x) x[x$id %in% df1$id, ]) 14.19s 14.19s 0.0705 2.5GB 0.282 1
Я открыт для data.table
вариантов, не знаю, как оптимизировать
Я тоже не уверен, но я думаю, преобразовать ваши кадры данных в data.tables l <- lapply(l, as.data.table)
, а затем что-то вроде lapply(l, \(i) i[df1, on = "id", nomatch = NULL])
? Может быть setkey()
здесь можно улучшить
1000 фреймов данных звучит так, как будто это должен быть один фрейм данных, вы можете bind_rows(,.id = "source")
затем полуобъединить всю таблицу, а затем разделить, если вам действительно нужно
@Maël Можете ли вы рассказать о своем стремлении к скорости? У вас есть один набор из ~ 1000 data.frame
, для которого вам нужно будет выполнить несколько подмножеств, или это одно подмножество для каждого из разных наборов из ~ 1000 data.frame
? Если вы создаете набор данных, было бы лучше создать один большой data.table
со столбцом идентификатора и сделать одно подмножество. Если вы выполняете несколько подмножеств на одном наборе data.frame
, было бы лучше сначала rbindlist
их.
У меня есть ~ 1000 кадров данных разной длины (l
), на которых мне нужно выполнить полусоединение. В результате создается еще около 1000 кадров данных, которые являются подмножеством df1
. Я понимаю, что мой пример был немного неуклюжим, поскольку он создал подмножество l
, а не df1
.
Я открою новый вопрос с более точным примером!
Спасибо за комментарий от @jblood94, у нас есть еще один более эффективный аргумент, чем использование rep
для добавления столбца grp
, и это действительно повышает производительность (см. dtsplit1
)
microbenchmark(
"dtsplit1" = split(rbindlist(l, idcol = "grp"), by = "grp", keep.by = FALSE),
"dtsplit" = split(rbindlist(l)[, grp := rep(seq_along(l), each = 50000)], by = "grp", keep.by = FALSE),
times = 10
)
дает
Unit: seconds
expr min lq mean median uq max neval
dtsplit1 1.779087 1.843032 1.989136 1.957378 2.041067 2.391234 10
dtsplit 2.059481 2.150111 2.190301 2.176397 2.215940 2.387897 10
Если вы исправили 50000
строки для всех ваших «реальных» 1000 кадров данных, надеюсь, следующий код может немного помочь.
library(data.table)
split(rbindlist(l)[, grp := rep(seq_along(l), each = 50000)], by = "grp", keep.by = FALSE)
microbenchmark(
"semijoin" = lapply(l, \(x) semi_join(x, df1, "id")),
"in" = lapply(l, \(x) x[x$id %in% df1$id, ]),
"dtsplit" = split(rbindlist(l)[, grp := rep(seq_along(l), each = 50000)], by = "grp", keep.by = FALSE),
times = 10
)
показывает
Unit: seconds
expr min lq mean median uq max neval
semijoin 2.679046 3.122411 3.589847 3.375409 3.544307 5.084460 10
in 4.357969 4.789931 6.271855 6.207127 7.651371 8.430053 10
dtsplit 1.680729 2.685881 3.377203 2.922039 3.239485 7.833068 10
Здесь полезен аргумент idcol
rbindlist
: split(rbindlist(l, idcol = "grp"), by = "grp", keep.by = FALSE)
. Это также ~ 30% быстрее.
@ jblood94 ага, какой восхитительный аргумент, что я не должен его пропустить! Большое спасибо за такое полезное напоминание!
Спасибо! отличный ответ. Я понимаю, что это немного не то, что я хотел (не то же количество строк, и подмножество должно быть выполнено на df1, но это плохо!), но это ясно отвечает на мой первоначальный вопрос, следовательно, принять.
@Maël Вы можете попробовать мое обновление, аргумент idcol
должен применяться к неидентичному количеству строк для всех фреймов данных.
Вы пробовали
data.table
подход?