Быстрое полусоединение в списке фреймов данных

Я пытаюсь выполнить полусоединение со списком кадров данных. У меня есть около 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 подход?

Sotos 04.04.2023 17:48

Я открыт для data.table вариантов, не знаю, как оптимизировать

Maël 04.04.2023 17:50

Я тоже не уверен, но я думаю, преобразовать ваши кадры данных в data.tables l <- lapply(l, as.data.table), а затем что-то вроде lapply(l, \(i) i[df1, on = "id", nomatch = NULL])? Может быть setkey() здесь можно улучшить

Sotos 04.04.2023 17:54

1000 фреймов данных звучит так, как будто это должен быть один фрейм данных, вы можете bind_rows(,.id = "source") затем полуобъединить всю таблицу, а затем разделить, если вам действительно нужно

moodymudskipper 04.04.2023 20:27

@Maël Можете ли вы рассказать о своем стремлении к скорости? У вас есть один набор из ~ 1000 data.frame, для которого вам нужно будет выполнить несколько подмножеств, или это одно подмножество для каждого из разных наборов из ~ 1000 data.frame? Если вы создаете набор данных, было бы лучше создать один большой data.table со столбцом идентификатора и сделать одно подмножество. Если вы выполняете несколько подмножеств на одном наборе data.frame, было бы лучше сначала rbindlist их.

jblood94 05.04.2023 13:31

У меня есть ~ 1000 кадров данных разной длины (l), на которых мне нужно выполнить полусоединение. В результате создается еще около 1000 кадров данных, которые являются подмножеством df1. Я понимаю, что мой пример был немного неуклюжим, поскольку он создал подмножество l, а не df1.

Maël 05.04.2023 13:55

Я открою новый вопрос с более точным примером!

Maël 05.04.2023 13:55
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
7
87
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Обновлять

Спасибо за комментарий от @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

Здесь полезен аргумент idcolrbindlist: split(rbindlist(l, idcol = "grp"), by = "grp", keep.by = FALSE). Это также ~ 30% быстрее.

jblood94 05.04.2023 13:10

@ jblood94 ага, какой восхитительный аргумент, что я не должен его пропустить! Большое спасибо за такое полезное напоминание!

ThomasIsCoding 05.04.2023 13:49

Спасибо! отличный ответ. Я понимаю, что это немного не то, что я хотел (не то же количество строк, и подмножество должно быть выполнено на df1, но это плохо!), но это ясно отвечает на мой первоначальный вопрос, следовательно, принять.

Maël 05.04.2023 13:56

@Maël Вы можете попробовать мое обновление, аргумент idcol должен применяться к неидентичному количеству строк для всех фреймов данных.

ThomasIsCoding 05.04.2023 14:22

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