使用列作为参数,将R data.table的apply函数应用于行

35

我有以下的data.table

x = structure(list(f1 = 1:3, f2 = 3:5), .Names = c("f1", "f2"), row.names = c(NA, -3L), class = c("data.table", "data.frame"))

我希望对data.table的每一行应用一个函数。该函数func.test使用参数f1f2,并对它们进行操作,并返回计算出来的值。 假设(作为示例)

func.text <- function(arg1,arg2){ return(arg1 + exp(arg2))}

但我的真实功能更为复杂,包括循环等操作,但最终返回计算出来的值。如何最好地完成这个任务?

4个回答

54

最好的方法是编写一个向量化的函数,但如果您不行,那么也许这样做:

x[, func.text(f1, f2), by = seq_len(nrow(x))]

1
啊,没想到可以用<code>by = 1:nrow(x)</code>的技巧。不错。 - broccoli
不确定为什么不直接使用.I,例如像这样x[, func.text(f1, f2), by = .I] - David Arenburg
1
@DavidArenburg 我不知道 by=.I 是在做什么。它与 by=1:nrow(x) 不完全相同,您可以通过比较例如 x[, 1, by = .I]x[, 1, by = 1:nrow(x)] 来检查。 - eddi
如果它按照你期望的方式工作,那就太好了(还有 by=1:.N)。 - eddi
2
是的,你可能是正确的,但在这种情况下,似乎OP不需要在此处使用by语句,因为他的函数已经按行操作整个数据集,因此即使x[, func.text(f1, f2)]也可以得到所需的结果。问题在于它将失去data.table类并变成一个数字向量。添加by = .I将保留该类,但我不确定为什么或如何保留(很快我可能会从@Arun那里收到一些愤怒的评论,指出我的对data.table的理解不足)。 - David Arenburg
显示剩余6条评论

30

我发现最优雅的方式是使用mapply

x[, value := mapply(func.text, f1, f2)]
x
#    f1 f2    value
# 1:  1  3 21.08554
# 2:  2  4 56.59815
# 3:  3  5 151.4132

或者使用purrr包:

x[, value := purrr::pmap_dbl(.(f1, f2), func.text)]
如果您的情况允许,另一种方法是将参数名称与列名匹配以使用:
library("purrr")

# arguments match the names of the columns, dots collect other 
# columns existing in the data.table
func.text <- function(f1, f2, ...) { return(f1 + exp(f2)) }

# use `set` to modify the data.table by reference
purrr::pmap_dbl(x, func.text) %>%
  data.table::set(x, i = NULL, j = "value", value = .)

print(x)
##    f1 f2     value
## 1:  1  3  21.08554
## 2:  2  4  56.59815
## 3:  3  5 151.41316

9
我们可以使用.I函数来定义行。
dt_iris <- data.table(iris)
dt_iris[, ..I := .I]

## Let's define some function
some_fun <- function(dtX) {
    print('hello')
    return(dtX[, Sepal.Length / Sepal.Width])
}

## by row
dt_iris[, some_fun(.SD), by = ..I] # or simply: dt_iris[, some_fun(.SD), by = .I]

## vectorized calculation
some_fun(dt_iris) 

我有这样的印象,曾经有一个时代可以直接在第三个组件中使用 by=.I。不是吗? - Stéphane Laurent
@StéphaneLaurent 当然,这只是为了表明用户看到的数据,他对其应用“by”。我已经更新了帖子,以消除任何疑虑 ;) - Cron Merdek
@StéphaneLaurent 我想你是指 .I,所以你可以使用点号 dt_iris[, some_fun(.SD), by = .I] - Cron Merdek
是的,抱歉,我指的是.I。但是昨天我尝试过了,它没有起作用...嗯,我现在刚刚尝试了一下,它可以工作了。抱歉,我肯定太累了 :) - Stéphane Laurent
1
请注意,.I 应该作为 data.table 中的 j 参数使用,而不是在 by 子句中使用。在 DT >1.12.4 中似乎也无法正常工作。@CronMerdek,请重新评估您的答案。 - Davor Josipovic
显示剩余3条评论

0

这是一种相当紧凑的语法

x[, c := .(Map(func.text, f1, f2))]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接