我有一张桌子,上面有2200万行,每行包含一组生命体征,一个患者ID和一个时间。我正在尝试获取一个摘要表,其中包含每个生命体征(列)的ID和非空值的数量。
下面的代码可以执行并给出合理的答案,但需要花费很多时间。我想知道是否有更有效的方法来解决此问题。有优化策略吗?
下面的代码将ID的名称从“ pcrid”转换为“ PCRID”,以使结果表与我的旧代码兼容。我还对表格进行了过滤。这可以在整个数据集上快速运行,因此这似乎并不是很慢的部分。
以下是它对不同大小的数据集(使用head())的执行方式:
pandas.Series
答案 0 :(得分:2)
我是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.
具有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的过滤和额外操作。如果需要,只需添加它们。另外,如果您的列多于上面使用的列,则可能需要在应用我的代码之前将其删除,因为它将功能应用于“所有”列。