data.table 按行求和,平均值,最小值,最大值,如 dplyr?
Posted
技术标签:
【中文标题】data.table 按行求和,平均值,最小值,最大值,如 dplyr?【英文标题】:data.table row-wise sum, mean, min, max like dplyr? 【发布时间】:2015-09-24 08:23:18 【问题描述】:还有其他关于数据表上的按行运算符的帖子。他们要么是too simple,要么解决了specific scenario
我的问题更笼统。有一个使用 dplyr 的解决方案。我玩过但未能找到使用 data.table 语法的等效解决方案。您能否建议一个优雅的 data.table 解决方案,它可以重现与 dplyr 版本相同的结果?
编辑 1:在真实数据集(10MB,73000 行,24 个数字列上进行的统计)的建议解决方案的基准总结。基准测试结果是主观的。但是,经过的时间始终可以重现。
| Solution By | Speed compared to dplyr |
|-------------|-----------------------------|
| Metrics v1 | 4.3 times SLOWER (use .SD) |
| Metrics v2 | 5.6 times FASTER |
| ExperimenteR| 15 times FASTER |
| Arun v1 | 3 times FASTER (Map func)|
| Arun v2 | 3 times FASTER (foo func)|
| Ista | 4.5 times FASTER |
编辑 2:我在一天后添加了 NACount 列。这就是为什么在各种贡献者建议的解决方案中找不到此专栏的原因。
数据设置
library(data.table)
dt <- data.table(ProductName = c("Lettuce", "Beetroot", "Spinach", "Kale", "Carrot"),
Country = c("CA", "FR", "FR", "CA", "CA"),
Q1 = c(NA, 61, 40, 54, NA), Q2 = c(22, 8, NA, 5, NA),
Q3 = c(51, NA, NA, 16, NA), Q4 = c(79, 10, 49, NA, NA))
# ProductName Country Q1 Q2 Q3 Q4
# 1: Lettuce CA NA 22 51 79
# 2: Beetroot FR 61 8 NA 10
# 3: Spinach FR 40 NA NA 49
# 4: Kale CA 54 5 16 NA
# 5: Carrot CA NA NA NA NA
使用 dplyr + rowwise() 的解决方案
library(dplyr) ; library(magrittr)
dt %>% rowwise() %>%
transmute(ProductName, Country, Q1, Q2, Q3, Q4,
AVG = mean(c(Q1, Q2, Q3, Q4), na.rm=TRUE),
MIN = min (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
MAX = max (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
SUM = sum (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
NAcnt= sum(is.na(c(Q1, Q2, Q3, Q4))))
# ProductName Country Q1 Q2 Q3 Q4 AVG MIN MAX SUM NAcnt
# 1 Lettuce CA NA 22 51 79 50.66667 22 79 152 1
# 2 Beetroot FR 61 8 NA 10 26.33333 8 61 79 1
# 3 Spinach FR 40 NA NA 49 44.50000 40 49 89 2
# 4 Kale CA 54 5 16 NA 25.00000 5 54 75 1
# 5 Carrot CA NA NA NA NA NaN Inf -Inf 0 4
data.table 出现错误(计算整列而不是每行)
dt[, .(ProductName, Country, Q1, Q2, Q3, Q4,
AVG = mean(c(Q1, Q2, Q3, Q4), na.rm=TRUE),
MIN = min (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
MAX = max (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
SUM = sum (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
NAcnt= sum(is.na(c(Q1, Q2, Q3, Q4))))]
# ProductName Country Q1 Q2 Q3 Q4 AVG MIN MAX SUM NAcnt
# 1: Lettuce CA NA 22 51 79 35.90909 5 79 395 9
# 2: Beetroot FR 61 8 NA 10 35.90909 5 79 395 9
# 3: Spinach FR 40 NA NA 49 35.90909 5 79 395 9
# 4: Kale CA 54 5 16 NA 35.90909 5 79 395 9
# 5: Carrot CA NA NA NA NA 35.90909 5 79 395 9
几乎解决方案,但更复杂且缺少 Q1、Q2、Q3、Q4 输出列
dtmelt <- reshape2::melt(dt, id=c("ProductName", "Country"),
variable.name="Quarter", value.name="Qty")
dtmelt[, .(AVG = mean(Qty, na.rm=TRUE),
MIN = min (Qty, na.rm=TRUE),
MAX = max (Qty, na.rm=TRUE),
SUM = sum (Qty, na.rm=TRUE),
NAcnt= sum(is.na(Qty))), by = list(ProductName, Country)]
# ProductName Country AVG MIN MAX SUM NAcnt
# 1: Lettuce CA 50.66667 22 79 152 1
# 2: Beetroot FR 26.33333 8 61 79 1
# 3: Spinach FR 44.50000 40 49 89 2
# 4: Kale CA 25.00000 5 54 75 1
# 5: Carrot CA NaN Inf -Inf 0 4
【问题讨论】:
dt[, AVG := rowMeans(.SD, na.rm=T),.SDcols=c(Q1, Q2,Q3,Q4)]
@ExperimenteR 谢谢(SDcols 应该是一个字符向量吗?)我试过这个dt[, .(Q1, Q2, Q3, Q4, AVG = rowMeans(.SD, na.rm=T), MIN = pmin(Q1,Q2,Q3,Q4, na.rm=T), MAX = pmax(Q1,Q2,Q3,Q4, na.rm=T) ), .SDcols=c("Q1","Q2","Q3","Q4")]
但仍然错过了 SUM 并且没有 ProductName、Country 列
@Metrics 没有评估错误的输出 b/c:dt[, `:=` (AVG = rowMeans(.SD, na.rm=TRUE), MIN = min(.SD, na.rm=TRUE), MAX = max(.SD, na.rm=TRUE), SUM = sum(.SD, na.rm=TRUE)), .SDcols = c("Q1","Q2","Q3","Q4"), by=1:nrow(dt)] Warning messages: 1: In min(c(NA_real_, NA_real_, NA_real_, NA_real_), na.rm = TRUE) : no non-missing arguments to min; returning Inf 2: In max(c(NA_real_, NA_real_, NA_real_, NA_real_), na.rm = TRUE) : no non-missing arguments to max; returning -Inf
看我的回答。我已更新代码并从 cmets 中删除。 Dplyr 和 data.table 都针对 NaN 和 -Inf 发出警告。
data.table 尽可能使用基本 R 函数,以免强加“围墙花园”方法。但是基本 R 没有执行此操作的好函数 :-(。所以我们'将必须实现colwise()
和rowwise()
下提交的#1063 函数...我已将其标记为下一个版本。
【参考方案1】:
您可以使用matrixStats
包中的高效逐行函数。
library(matrixStats)
dt[, `:=`(MIN = rowMins(as.matrix(.SD), na.rm=T),
MAX = rowMaxs(as.matrix(.SD), na.rm=T),
AVG = rowMeans(.SD, na.rm=T),
SUM = rowSums(.SD, na.rm=T)), .SDcols=c(Q1, Q2,Q3,Q4)]
dt
# ProductName Country Q1 Q2 Q3 Q4 MIN MAX AVG SUM
# 1: Lettuce CA NA 22 51 79 22 79 50.66667 152
# 2: Beetroot FR 61 8 NA 10 8 61 26.33333 79
# 3: Spinach FR 40 NA 79 49 40 79 56.00000 168
# 4: Kale CA 54 5 16 NA 5 54 25.00000 75
# 5: Carrot CA NA NA NA NA Inf -Inf NaN 0
对于 500000 行的数据集(使用来自 CRAN 的 data.table
)
dt <- rbindlist(lapply(1:100000, function(i)dt))
system.time(dt[, `:=`(MIN = rowMins(as.matrix(.SD), na.rm=T),
MAX = rowMaxs(as.matrix(.SD), na.rm=T),
AVG = rowMeans(.SD, na.rm=T),
SUM = rowSums(.SD, na.rm=T)), .SDcols=c("Q1", "Q2","Q3","Q4")])
# user system elapsed
# 0.089 0.004 0.093
rowwise
(或by=1:nrow(dt)
)是for loop
的“委婉说法”,例如
library(dplyr) ; library(magrittr)
system.time(dt %>% rowwise() %>%
transmute(ProductName, Country, Q1, Q2, Q3, Q4,
MIN = min (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
MAX = max (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
AVG = mean(c(Q1, Q2, Q3, Q4), na.rm=TRUE),
SUM = sum (c(Q1, Q2, Q3, Q4), na.rm=TRUE)))
# user system elapsed
# 80.832 0.111 80.974
system.time(dt[, `:=`(AVG= mean(as.numeric(.SD),na.rm=TRUE),MIN = min(.SD, na.rm=TRUE),MAX = max(.SD, na.rm=TRUE),SUM = sum(.SD, na.rm=TRUE)),.SDcols=c("Q1", "Q2","Q3","Q4"),by=1:nrow(dt)] )
# user system elapsed
# 141.492 0.196 141.757
【讨论】:
您的解决方案是最快的! (请参阅原始问题中的基准)感谢您介绍 matrixStats 包。我想知道与 Arun 和 Metrics 的第二个解决方案相比,您的解决方案对内存资源的影响。【参考方案2】:使用by=1:nrow(dt)
,在data.table
中执行逐行操作
library(data.table)
dt[, `:=`(AVG= mean(as.numeric(.SD),na.rm=TRUE),MIN = min(.SD, na.rm=TRUE),MAX = max(.SD, na.rm=TRUE),SUM = sum(.SD, na.rm=TRUE)),.SDcols=c(Q1, Q2,Q3,Q4),by=1:nrow(dt)]
ProductName Country Q1 Q2 Q3 Q4 AVG MIN MAX SUM
1: Lettuce CA NA 22 51 79 50.66667 22 79 152
2: Beetroot FR 61 8 NA 10 26.33333 8 61 79
3: Spinach FR 40 NA 79 49 56.00000 40 79 168
4: Kale CA 54 5 16 NA 25.00000 5 54 75
5: Carrot CA NA NA NA NA NaN Inf -Inf 0
Warning messages:
1: In min(c(NA_real_, NA_real_, NA_real_, NA_real_), na.rm = TRUE) :
no non-missing arguments to min; returning Inf
2: In max(c(NA_real_, NA_real_, NA_real_, NA_real_), na.rm = TRUE) :
no non-missing arguments to max; returning -Inf
您收到警告消息,因为在第 5 行,您正在计算最大值、总和、最小值和最大值。例如,见下图:
min(c(NA,NA,NA,NA),na.rm=TRUE)
[1] Inf
Warning message:
In min(c(NA, NA, NA, NA), na.rm = TRUE) :
no non-missing arguments to min; returning Inf
【讨论】:
同样的错误,这可能是 b/c 我使用的是最新的 data.table 1.9.4(R 版本 3.2.0 (2015-04-16))吗?此外,我必须将 SDcols 放在引号.SDcols=c("Q1","Q2","Q3","Q4")
中以避免“找不到对象 'Q1'”。这是我运行您的代码时的错误:1: In min(c(NA_real_, NA_real_, NA_real_, NA_real_), na.rm = TRUE) : no non-missing arguments to min; returning Inf 2: In max(c(NA_real_, NA_real_, NA_real_, NA_real_), na.rm = TRUE) : no non-missing arguments to max; returning -Inf
那些是警告而不是错误(我也明白了)。您收到警告是因为您的输出返回无限值 -Inf
、Inf
和 NaN
(因为您正在取平均值、总和、最小值和最大值)。如果您运行自己的 dplyr 代码,它也会发出相同的警告。我正在使用开发版本 1.9.5+(您可以从 github 获取)。我不确定你为什么需要加上引号。它对我来说没有引号。在答案中查看我的更新。
哦,那是真的。我忘了打印(dt)。对不起!顺便说一句,如果我不在.SDcols=c(Q1,Q2,Q3,Q4)
(data.table 1.9.4, R v3.2.0) 中的列名周围加上引号,你知道为什么我得到object 'Q1' not found
刚刚将您的解决方案应用于 10MB 数据集,73000 行。 dplyr 版本比您建议的实现快 4 倍。会不会是计算 AVG 中的 as.numeric(.SD)?
你不能对这么小的数据集进行基准测试,这毫无意义。【参考方案3】:
只是另一种方式(虽然效率不高,因为每次都会调用na.omit()
,而且还会分配许多内存):
require(data.table)
new_cols = c("MIN", "MAX", "SUM", "AVG")
dt[, (new_cols) := Map(function(x, f) f(x),
list(na.omit(c(Q1,Q2,Q3,Q4))),
list(min, max, sum, mean)),
by = 1:nrow(dt)]
# ProductName Country Q1 Q2 Q3 Q4 MIN MAX SUM AVG
# 1: Lettuce CA NA 22 51 79 22 79 152 50.66667
# 2: Beetroot FR 61 8 NA 10 8 61 79 26.33333
# 3: Spinach FR 40 NA 79 49 40 79 168 56.00000
# 4: Kale CA 54 5 16 NA 5 54 75 25.00000
# 5: Carrot CA NA NA NA NA Inf -Inf 0 NaN
但正如我所提到的,一旦实现了colwise()
和rowwise()
,这将变得更加简单。这种情况下的语法可能类似于:
dt[, rowwise(.SD, list(MIN=min, MAX=max, SUM=sum, AVG=mean), na.rm=TRUE), by = 1:nrow(dt)]
# `by = ` is really not necessary in this case.
对于这种情况,甚至更直接:
rowwise(dt, list(...), na.rm=TRUE)
编辑:
另一种变化:
myNACount <- function(x, ...) length(attributes(x)$na.action)
foo <- function(x, ...)
funs = c(min, max, mean, sum, myNACount)
lapply(funs, function(f) f(x, ...))
dt[, (new_cols) := foo(na.omit(c(Q1, Q2, Q3, Q4)), na.rm=TRUE), by=1:nrow(dt)]
# ProductName Country Q1 Q2 Q3 Q4 MIN MAX SUM AVG NAs
# 1: Lettuce CA NA 22 51 79 22 79 50.66667 152 1
# 2: Beetroot FR 61 8 NA 10 8 61 26.33333 79 1
# 3: Spinach FR 40 NA NA 49 40 49 44.50000 89 2
# 4: Kale CA 54 5 16 NA 5 54 25.00000 75 1
# 5: Carrot CA NA NA NA NA Inf -Inf NaN 0 4
【讨论】:
是的,您为什么在rowwise
潜在解决方案中添加by
?
可能会有像dt[, if (TRUE) do_bla else rowwise(...), by=some_cols]
这样的复杂场景(就像我说的,在这种情况下,没有必要)。
@Arun myNACount <- function(x) length(attributes(x)$na.action)
功能非常出色。谢谢。我希望我能理解优化的机制。您建议的第二个变体非常快。
@Arun Ahem ...对不起,我在基准测试中犯了一个错误。您制作的第二个变体比第一个版本稍快。最快的执行时间来自 ExperimenteR 的解决方案。
@Polymerase,不用担心。我想我们都在这里学到了很多:-)。大问。【参考方案4】:
apply
函数可用于执行逐行计算。单独定义函数使事情更整洁:
dstats <- function(x)
c(mean(x,na.rm=TRUE),
min(x, na.rm=TRUE),
max(x, na.rm=TRUE),
sum(x, na.rm=TRUE))
该函数现在可以应用于 data.table 的行。
(dt[,
c("AVG", "MIN", "MAX", "SUM") := data.frame(t(apply(.SD, 1, dstats))),
.SDcols=c("Q1", "Q2","Q3","Q4"),
])
请注意,使用 [.data.table
执行此操作的唯一优点是它允许使用 :=
通过引用快速添加。
这比matrixStats
解决方案更慢但更灵活,并且比@ExperimenteR 的dplyr
解决方案更快,计时为36 秒(我对其他方法的计时与@ExperimenteR 的答案中的类似)。
【讨论】:
1.apply()
将 .SD
转换为矩阵 = mem alloc。 2. t()
转置结果 = 另一个副本。 3. data.frame()
= 另一个内存分配。不确定这里是否需要with = FALSE
。通过避免所有这些副本,我们当然可以做得更好。
@Arun 也许,但它已经相当快了,如果我们需要更快的速度,我们可以使用matrixStats
。我有with = FALSE
,因为help(":=")
暗示当RHS 返回一个列表时需要这样做。
相当快是不够的,真的,特别是当提高效率是微不足道的时候。我已经在 github 项目页面上回复了您的回复,详细说明了原因。在with=FALSE
上,这不是它的意思,但我理解这种困惑。会修复的。
@Ista 您的解决方案是第二快的,请参阅原始问题中的基准测试结果。【参考方案5】:
我希望其他人在遇到同样的问题时,他们可能会有所帮助。
第一种方法:结合基础R
dt[,`:=`(MIN = apply(dt[, Q1:Q4], 1, FUN = min, na.rm=TRUE),
MAX = apply(dt[, Q1:Q4], 1, FUN = max, na.rm = TRUE),
AVG = rowMeans(dt[, Q1:Q4], na.rm = TRUE),
SUM = rowSums(dt[, Q1:Q4], na.rm = TRUE))][]
# ProductName Country Q1 Q2 Q3 Q4 MIN MAX AVG SUM
# 1: Lettuce CA NA 22 51 79 22 79 50.66667 152
# 2: Beetroot FR 61 8 NA 10 8 61 26.33333 79
# 3: Spinach FR 40 NA NA 49 40 49 44.50000 89
# 4: Kale CA 54 5 16 NA 5 54 25.00000 75
# 5: Carrot CA NA NA NA NA Inf -Inf NaN 0
第二种方法:基于@ExperimenteR的想法,使用matrixStats包
dt1 <- dt[,`:=`(MIN = rowMins(as.matrix(dt[, Q1:Q4]), na.rm=TRUE),
MAX = rowMaxs(as.matrix(dt[, Q1:Q4]), na.rm = TRUE),
AVG = rowMeans(dt[, Q1:Q4], na.rm = TRUE),
SUM = rowSums(dt[, Q1:Q4], na.rm = TRUE))][]
# ProductName Country Q1 Q2 Q3 Q4 MIN MAX AVG SUM
# 1: Lettuce CA NA 22 51 79 22 79 50.66667 152
# 2: Beetroot FR 61 8 NA 10 8 61 26.33333 79
# 3: Spinach FR 40 NA NA 49 40 49 44.50000 89
# 4: Kale CA 54 5 16 NA 5 54 25.00000 75
# 5: Carrot CA NA NA NA NA Inf -Inf NaN 0
【讨论】:
以上是关于data.table 按行求和,平均值,最小值,最大值,如 dplyr?的主要内容,如果未能解决你的问题,请参考以下文章