对除当前行外的所有行应用一个函数(dplyr)

7

我正在学习 R 语言中的 dplyr 包,并且目前非常喜欢它。其中一个需要解决的问题是创建一个新列,其每一行的值都是将除了当前行之外的所有行(可能按组进行子集划分)应用一个函数的结果,但我无法找到一种简便的方法来实现它。

一个虚构的例子如下:

library(datasets)
library(dplyr)

data(mtcars)
x <- mtcars %>% mutate(name=rownames(mtcars)) %>% filter(cyl==4) %>% select(name,cyl,mpg)

# This is what I want to do more elegantly
x$othermpg <- NA
for (i in 1:nrow(x))
    x$othermpg[i] <- mean(x$mpg[-i])

这里,othermpg 列取得了除当前行以外所有车辆的 mpg 值的平均值。

请问有人能帮助吗?


3
请提供一个可重现的例子和期望的输出。一般情况下,您可能需要研究 mutate 函数。 - David Arenburg
谢谢。我知道“mutate”,但不知道如何获得这种特定的行为。我会尝试组合一个示例并很快修改问题。 - Jon Clayden
为什么你想要这么做? - hadley
@hadley 我目前正在研究的特定数据集由一级方程式赛车的历史结果组成。通常会将一个车手与他的队友进行比较,因此我想将同一赛季中同一辆车的所有其他车手的信息汇总到一个列中,例如每个队友的平均得分。我可以想象还有其他情况下可能需要计算某种摘要统计信息而不是每个单独行,但没有其他具体示例... - Jon Clayden
2个回答

3

对于示例案例,您可以使用以下代码避免for循环。基本上,您不需要排除“当前”行,而是只需从总和中减去它:

library(dplyr)

x %>% mutate(othermpg2 = (sum(mpg)-mpg) / (length(mpg) -1 ))

#             name cyl  mpg othermpg othermpg2
#1      Datsun 710   4 22.8    27.05     27.05
#2       Merc 240D   4 24.4    26.89     26.89
#3        Merc 230   4 22.8    27.05     27.05
#4        Fiat 128   4 32.4    26.09     26.09
#5     Honda Civic   4 30.4    26.29     26.29
#6  Toyota Corolla   4 33.9    25.94     25.94
#7   Toyota Corona   4 21.5    27.18     27.18
#8       Fiat X1-9   4 27.3    26.60     26.60
#9   Porsche 914-2   4 26.0    26.73     26.73
#10   Lotus Europa   4 30.4    26.29     26.29
#11     Volvo 142E   4 21.4    27.19     27.19

如果您觉得这个方法不适用于您的真实数据,请修改您的问题并使用更具代表性的示例。


谢谢您的建议,虽然这样运行会更快,但我不确定它是否在可读性方面向前迈进了一步。对于更复杂的函数来说,这也很可能出现错误。无论如何,我真的想知道是否有一种通用的方法来做到这一点。 - Jon Clayden
@JonClayden,akrun提供了一个不错的答案,也许这种方法可以以某种方式转移到dplyr中。但是,就代码可读性和简洁性而言,我个人认为我的方法非常简单且不容易出错——它是非常基本的“数学”方法。正如我所说,如果您想使用更复杂的函数来完成此操作,最好在问题中提供这样一个更复杂的函数,以便进行测试。 - talat
可以通过定义一个函数来使其更易读:leaveoutmean = function(df,n){(sum(df[[n]]) - df[[n]])/(nrow(df)-1)},然后变成 mutate(othermpg=leaveoutmean(x,"mpg")),这样你就得到了一个可重复使用的函数。 - Spacedman
当然,我同意对于像mean这样的简单函数,这是一个完全可行的方法,但就个人而言,我更喜欢@akrun的解决方案,因为它更具普适性。 - Jon Clayden

3
这可以在data.table中完成。
 library(data.table)
 setDT(x)[, N:= 1:.N][, othermpg2:=mean(x[N!= .BY, mpg]), by=N][,N:=NULL]
  x
  #             name cyl  mpg othermpg othermpg2
  #1:     Datsun 710   4 22.8    27.05     27.05
  #2:      Merc 240D   4 24.4    26.89     26.89
  #3:       Merc 230   4 22.8    27.05     27.05
  #4:       Fiat 128   4 32.4    26.09     26.09
  #5:    Honda Civic   4 30.4    26.29     26.29
  #6: Toyota Corolla   4 33.9    25.94     25.94
  #7:  Toyota Corona   4 21.5    27.18     27.18
  #8:      Fiat X1-9   4 27.3    26.60     26.60
  #9:  Porsche 914-2   4 26.0    26.73     26.73
 #10:   Lotus Europa   4 30.4    26.29     26.29
 #11:     Volvo 142E   4 21.4    27.19     27.19
  • 首先创建一行索引/列 N:=1:.N
  • 按照该索引进行分组 by=N]
  • x[N!= .BY, mpg] 给出了与分组变量不相等的 mpg
  • 求均值
  • N:=NULL 由于 N 不再需要,因此删除该列。

或者你可以尝试以下方法(灵感来自 @thelatemail 的答案)

 setDT(x)[, N:=1:.N]
 setkey(x, N)
 x[,othermpg2 := mean(x[!.BY, mpg]), by=N][,N:=NULL]

或者不用创建N(来自@Jon Clayden的评论)

 setDT(x)[, othermpg2:=mean(x[name!=.BY,mpg]), by=name]

使用dplyr,这似乎可以工作。
x %>% 
mutate(N=1:n()) %>% 
do( data.frame(.,othermpg2=sapply(.$N, function(i) mean(.$mpg[!.$N %in% i]))))
  #             name cyl  mpg othermpg  N othermpg2
  #1      Datsun 710   4 22.8    27.05  1     27.05
  #2       Merc 240D   4 24.4    26.89  2     26.89
  #3        Merc 230   4 22.8    27.05  3     27.05
  #4        Fiat 128   4 32.4    26.09  4     26.09
  #5     Honda Civic   4 30.4    26.29  5     26.29
  #6  Toyota Corolla   4 33.9    25.94  6     25.94
  #7   Toyota Corona   4 21.5    27.18  7     27.18
  #8       Fiat X1-9   4 27.3    26.60  8     26.60
  #9   Porsche 914-2   4 26.0    26.73  9     26.73
  #10   Lotus Europa   4 30.4    26.29 10     26.29
  #11     Volvo 142E   4 21.4    27.19 11     27.19

聪明,谢谢!这也需要一些解析,但至少它可以很容易地普遍化。学习 data.table 是我接下来要做的事情... - Jon Clayden
非常好的答案!+1。我认为这也可以通过使用键来实现,就像thelatemail在这里所示(https://dev59.com/DILba4cB1Zd3GeqPay2z#25130426)。 - Arun
жҲ‘зҢңNеҲ—дёҚйңҖиҰҒиў«еҲӣе»әпјҢеӣ дёәжҜҸиЎҢзҡ„nameеҲ—жҳҜе”ҜдёҖзҡ„гҖӮsetDT(x)[, othermpg2:=mean(x[name!=.BY,mpg]), by=name]дјјд№ҺеҸҜд»Ҙе·ҘдҪң... - Jon Clayden
@Jon Clayden; 是的,这似乎可以工作。如果您需要一个dplyr解决方案,这似乎可以工作 x %>% mutate(N=1:n()) %>% do( data.frame(.,index=sapply(.$N, function(i) mean(.$mpg[!.$N %in% i])))) - akrun

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