在大型数据集上使用多个sum()优化dplyr summarise()

时间:2019-06-17 23:01:42

标签: r optimization dplyr

我有一张桌子,上面有2200万行,每行包含一组生命体征,一个患者ID和一个时间。我正在尝试获取一个摘要表,其中包含每个生命体征(列)的ID和非空值的数量。

下面的代码可以执行并给出合理的答案,但需要花费很多时间。我想知道是否有更有效的方法来解决此问题。有优化策略吗?

下面的代码将ID的名称从“ pcrid”转换为“ PCRID”,以使结果表与我的旧代码兼容。我还对表格进行了过滤。这可以在整个数据集上快速运行,因此这似乎并不是很慢的部分。

以下是它对不同大小的数据集(使用head())的执行方式:

  • 1,000行= 0.2秒
  • 10,000行= 1.7秒
  • 100,000行= 15秒
  • 1,000,000行= 2.9分钟
  • 22,000,000行= 42分钟
pandas.Series

3 个答案:

答案 0 :(得分:2)

我是data.table的初学者,但是我知道如果要计算的组数量很多,与dplyr相比,它可以显着提高性能。

我还没有想出data.table语法来按PCRID分组并计算跨许多列的非NA的计数。为了解决这个问题,我尝试使用dtplyr(一种基于dplyr的基于data.table的前端),并获得了一些实质性的性能改进。

使用一些与您的大小相似的伪造数据(请参阅底部),计算您帖子的时间需要197秒,但是当我加载data.tabledtplyr并重新运行时,它花费了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.

具有2000万行和1000万组的伪数据:

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 :(得分:2)

答案很大程度上取决于数据的外观,尤其是每组中有多少行。

例如,对于100,000个组和42行(即总共4,200,000行),data.table获得2秒,而dplyr获得84秒。对于只有100个组的相同总行,我得到dt的0.28秒和dplyr的0.37秒。

我也做了@Jon Springs的示例,每组2行,每行10,000,000个组。我的data.table解决方案是339秒,而我的dplyr版本在2464秒时停止了。也许解决方案的一部分是获得更好的处理器,例如@ Jon's:)。

编辑:我认为,如果有很多组,则首先融化/收集数据会更快。 @Jon的10,000,000组示例大约需要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 :(得分:1)

我对此进行了尝试。我认为您可以使用利用多核的Hadley Wickhams multidplyr。您使用partition而不是group_by,然后在summarise之后collect使用结果。

我还通过在汇总数据之前使用rename_at进行列名更改和使用mutate_at创建值1和0来使代码更具动态性。 dummy_如果不为NA,则创建1,否则为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()

从Jon Spring借来的虚假数据(谢谢!):

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的过滤和额外操作。如果需要,只需添加它们。另外,如果您的列多于上面使用的列,则可能需要在应用我的代码之前将其删除,因为它将功能应用于“所有”列。