Оптимизация dplyr summaze () с множеством sum () для большого набора данных - PullRequest
0 голосов
/ 18 июня 2019

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

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

Приведенный ниже код преобразует имя идентификатора из «pcrid» в «PCRID», чтобы сделать полученную таблицу совместимой с моим прежним кодом. Я также немного фильтрую таблицу. Это работает быстро для всего набора данных, так что это не медленная часть.

Вот как это работает с наборами данных разных размеров (с использованием head ()):

  • 1000 строк = 0,2 секунды
  • 10000 строк = 1,7 секунды
  • 100 000 строк = 15 секунд
  • 1 000 000 строк = 2,9 минуты
  • 22 000 000 строк = 42 минуты
    Start <- Sys.time()
    vitals_all <- vitals_all.df %>%
      select(PCRID = pcrid, everything()) %>%
      filter((pta == "no" | pta == "unk") & !is.na(pta)) %>%
      group_by(PCRID) %>%
      summarise(
        n_AVPU = sum(!is.na(avpu)),
        n_SBP = sum(!is.na(sbp)),
        n_DBP = sum(!is.na(dbp)),
        n_HR = sum(!is.na(pulserate)),
        n_RR = sum(!is.na(rr)),
        n_SpO2 = sum(!is.na(spo2)),
        n_EtCO2 = sum(!is.na(etco2)),
        n_CO = sum(!is.na(co)),
        n_BGL = sum(!is.na(glucose)),
        n_Temp = sum(!is.na(tempf)),
        n_Pain = sum(!is.na(painscale)),
        n_GCS = sum(!is.na(gcs))) 
    Sys.time() - Start

Ответы [ 3 ]

2 голосов
/ 18 июня 2019

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

Например, с 100 000 групп и 42 строками (т.е. всего 4 200 000 строк) я получаю 2 секунды для data.table и против 84 секунд для dplyr. Для тех же самых строк только с 100 группами я получаю 0,28 секунды для dt и 0,37 секунды для dplyr.

Я также сделал пример @Jon Springs с 2 строками на группу с 10 000 000 групп. Мое data.table решение было 339 секунд, и я остановил свою dplyr версию на 2464 секундах. Возможно, часть решения заключается в том, чтобы получить лучший процессор, такой как @ Jon's:).

РЕДАКТИРОВАТЬ: Я думаю, что если есть много групп, сначала происходит плавление / сбор данных быстрее. Пример 10 000 000 @ @ Jon занимает около 60 секунд. Примечание: чтобы вернуть его в широкоформатный формат, он добавляет еще 100 секунд, что в два раза быстрее, чем строго data.table

melt(dt, id.vars = 'ID')[!is.na(value), .N, by = .(ID, variable)]
#or to end wide
dcast(melt(dt, id.vars = 'ID')[!is.na(value), .N, by = .(ID, variable)], ID ~ variable)

Вот вызовы функций, которые я использовал. Обратите внимание, что я использовал summarized_all(), потому что у меня не было возможности написать все эти столбцы.

#Assume using all columns except the ID column

#data.table
dt[, lapply(.SD, function(x) sum(!is.na(x))), by = ID]

#dplyr
tib%>%
  group_by(ID)%>%
  summarize_all(~sum(!is.na(.)))

Данные:

n_groups <- 10
n_rows <- 42
n_cols <- 12

NA_prob <- 0.3

library(data.table)
library(dplyr)

set.seed(0)
dt <- data.table(ID = rep(seq_len(n_groups), each = n_rows)
           , matrix(sample(x = c(NA_integer_, 0L)
                           , size = n_rows * n_cols * n_groups
                           , replace = T
                           , prob = c(NA_prob, 1 - NA_prob))
                    , ncol = 12)
           )

tib <- as_tibble(dt)
2 голосов
/ 18 июня 2019

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

Я не выяснил синтаксис data.table для обеих групп по PCRID и рассчитал количество не-NA по многим столбцам.Чтобы обойти это, я попытался использовать dtplyr, основанный на dplyr интерфейс для data.table, и получил некоторые существенные улучшения производительности.

Используя некоторые поддельные данные (см. Внизу) такого же размера, как и у вас, подсчет из вашего поста занял 197 секунд, но когда я загрузил data.table и dtplyr и перезапустил его, потребовалось 77 секунд, сбривая 61% времени, с тем же выходом.Ваши результаты могут отличаться, но я не удивлюсь, если будут еще data.table возможности, позволяющие значительно сократить это время.

library(data.table); library(dtplyr)
vitals_fake_DT <- data.table(vitals_fake)

vitals_fake_DT %>%
  arrange(PCRID) %>% # to make output order the same way between methods
  group_by(PCRID) %>%
  summarise(
    n_AVPU = sum(!is.na(avpu)),
    n_SBP = sum(!is.na(sbp)),
    # etc.

Поддельные данные с 20 миллионами строк и 10 миллионами групп:

rows = 20000000
grps = 10000000 # max, somewhat less in practice
set.seed(42)
vitals_fake <- data.frame(
  PCRID = sample(1:grps, size = rows, replace = T),
  avpu = sample(c(NA, 1:10), size = rows, replace = T),
  sbp = sample(c(NA, 1:10), size = rows, replace = T),
  dbp = sample(c(NA, 1:10), size = rows, replace = T),
  pulserate    = sample(c(NA, 1:10), size = rows, replace = T),
  rr    = sample(c(NA, 1:10), size = rows, replace = T),
  spo2  = sample(c(NA, 1:10), size = rows, replace = T),
  etco2 = sample(c(NA, 1:10), size = rows, replace = T),
  co    = sample(c(NA, 1:10), size = rows, replace = T),
  glucose   = sample(c(NA, 1:10), size = rows, replace = T),
  tempf  = sample(c(NA, 1:10), size = rows, replace = T),
  painscale  = sample(c(NA, 1:10), size = rows, replace = T),
  gcs   = sample(c(NA, 1:10), size = rows, replace = T)
)
1 голос
/ 18 июня 2019

Я попытался это сделать. Я думаю, что вы можете использовать мультидплер Hadley Wickhams, который использует преимущества нескольких ядер. Вы используете partition вместо group_by, а после summarise вы collect результат.

Я также сделал код более динамичным, используя rename_at для изменения имени столбцов и mutate_at для создания значений 1 и 0 перед суммированием данных. dummy_ создает 1, если не NA, и 0 в противном случае. Казалось, этот код работает быстро:

# devtools::install_github("hadley/multidplyr")
library(dplyr)
library(multidplyr)
library(hablar)

vitals_all <- vitals_all.df %>% 
  rename_at(vars(-PCRID), ~paste0("n_", toupper(.))) %>% 
  mutate_at(vars(-PCRID), ~dummy_(!is.na(.))) %>% 
  partition(PCRID) %>% 
  summarise_all(~sum(.)) %>% 
  collect()

Поддельные данные, позаимствованные у Джона Спринга (спасибо!):

rows = 20000000
grps = 10000000 # max, somewhat less in practice
set.seed(42)
vitals_all.df <- data.frame(
  PCRID = sample(1:grps, size = rows, replace = T),
  avpu = sample(c(NA, 1:10), size = rows, replace = T),
  sbp = sample(c(NA, 1:10), size = rows, replace = T),
  dbp = sample(c(NA, 1:10), size = rows, replace = T),
  pulserate    = sample(c(NA, 1:10), size = rows, replace = T),
  rr    = sample(c(NA, 1:10), size = rows, replace = T),
  spo2  = sample(c(NA, 1:10), size = rows, replace = T),
  etco2 = sample(c(NA, 1:10), size = rows, replace = T),
  co    = sample(c(NA, 1:10), size = rows, replace = T),
  glucose   = sample(c(NA, 1:10), size = rows, replace = T),
  tempf  = sample(c(NA, 1:10), size = rows, replace = T),
  painscale  = sample(c(NA, 1:10), size = rows, replace = T),
  gcs   = sample(c(NA, 1:10), size = rows, replace = T)
)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...