Оптимизация цикла FOR для сравнения двух кадров данных в R

У меня есть приведенный ниже код, где я пытаюсь перейти к каждой строке определенного столбца фрейма данных и проверить это значение в другом фрейме данных, чтобы вставить значение в столбец 4 первого фрейма данных.

for (i in 1:length(DF$Date)){
  if (DF$column1[i] %in% DF_2$column_1){
    DF$column4[i] <- "YES"
  }
}

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

Было бы полезно, если бы у кого-нибудь был эффективный способ решить эту проблему в короткие сроки.

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

MrFlick 15.12.2020 05:19
Стоит ли изучать 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
1
105
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Здесь вам не нужен цикл for, так как %in% векторизован. Простой ifelse должен работать.

DF$column4 <- ifelse(DF$column1 %in% DF_2$column_1, 'YES', 'NO')

Вы также можете сделать это следующим образом:

DF$column4 <- c('NO', 'YES')[(DF$column1 %in% DF_2$column_1) + 1]

что может быть быстрее на больших наборах данных.

Большое спасибо @Ronak, как всегда помог мне :)

Vin 15.12.2020 07:14

Давайте сравним 3 подхода. Первый — это цикл for в вашем вопросе. Второй — тот, на который ответил Ронак, используя ifelse(), что ускоряет операцию. Однако сам оператор %in% несколько медленный, поэтому, если производительность действительно важна, вы можете получить еще более быстрое решение, используя индексацию с именами.

Например, используя набор данных words в пакете stringr:

library(stringr)

DF1 <- data.frame(column1 = sample(words, 700))
DF2 <- data.frame(column1 = sample(words, 700))

Мы можем сравнить эти методы:


bench::mark(for_loop = {
  res1 <- character(nrow(DF1))
  for (i in seq_len(nrow(DF1))){
    if (DF1$column1[i] %in% DF2$column1){
      res1[i] <- "YES"
    }
  }
  res1
},
ifelse = {
  res2 <- ifelse(DF1$column1 %in% DF2$column1, "YES", "")
},
by_names = {
  res3 <- setNames(rep("", nrow(DF1)),
                   DF1$column1)
  res3[intersect(DF1$column1, DF2$column1)] <- "Yes"
},check = FALSE)

# A tibble: 3 x 13
#     expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc
#     <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>
#   1 for_loop     7.94ms   8.49ms      112.     3.8MB     8.98    50     4
#   2 ifelse      200.6us  214.8us     4494.    60.7KB     4.09  2197     2
#   3 by_names     73.4us   78.2us    12342.    73.9KB    17.5   5628     8
#  ... with 5 more variables: total_time <bch:tm>, result <list>, memory <list>,
#    time <list>, gc <list>

Как видите, метод ifelse в 40 раз быстрее, чем цикл for, а индексация по имени в 3 раза быстрее, чем с ifelse.

Если метод ifelse достаточно быстрый, вы должны использовать его, так как его легче читать, но если ваш набор данных слишком велик, выбор по имени может повысить производительность.

NB: три решения дают один и тот же результат, но у третьего метода есть имена, отсюда и аргумент check=FALSE.

Большое спасибо за упрощенное объяснение @Alexlok

Vin 15.12.2020 07:13

Вы можете повысить эффективность, используя fastmatch:

library(fastmatch)
DF$column4 <- c("", "YES")[(DF$column1 %fin% DF_2$column1) + 1]

или:

DF$column4 <- ""
x[fmatch(DF_2$column1, DF$column1, 0)] <- "Yes"

Сравнительный анализ (с использованием данных @Alexlok):

library(stringr)
set.seed(42)
DF <- data.frame(column1 = sample(words, 700))
DF_2 <- data.frame(column1 = sample(words, 700))

microbenchmark::microbenchmark(control=list(order = "block")
 , Vin = {x <- character(nrow(DF))
   for (i in 1:nrow(DF)) {
     if (DF$column1[i] %in% DF_2$column1) {
       x[i] <- "YES"
     }
   }
   x}
 , RonakShah1 = ifelse(DF$column1 %in% DF_2$column1, 'YES', 'NO')
 , RonakShah2 = c('NO', 'YES')[(DF$column1 %in% DF_2$column1) + 1]
 , Alexlok = {x <- setNames(rep("", nrow(DF)), DF$column1)
   x[intersect(DF$column1, DF_2$column1)] <- "Yes"
   x}
 , fmatch = {x <- character(nrow(DF))
   x[fmatch(DF_2$column1, DF$column1, 0)] <- "Yes"
   x}
 , fin = c('NO', 'YES')[(DF$column1 %fin% DF_2$column1) + 1]
 )
#Unit: microseconds
#       expr       min         lq        mean     median         uq       max
#        Vin 13782.353 20549.5995 21467.31764 21795.3050 22845.0755 36143.621
# RonakShah1   281.118   308.1385   322.57051   316.5560   340.8620   362.097
# RonakShah2    60.906    66.5565    68.17693    67.8925    69.4110    88.363
#    Alexlok   162.817   179.6205   184.22998   182.3755   187.8450   257.323
#     fmatch    27.949    29.0590    29.90183    29.5275    30.0465    49.612
#        fin    30.446    31.0340    31.86467    31.5135    31.9540    49.817

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