如何使用 for 循环将多参数函数应用于基于分组变量的数据框?

How do I use a for loop to apply a multi-argument function to a data frame based on a grouping variable?

前言

提前致歉,我发现很难在书面上表达确切的问题,并且认为通过查看代码最清楚。另外,我对 R 比较陌生,很难使用正确的词来准确描述情况。我认为解决方案应该很容易被有更多经验的人指出,任何建议将不胜感激!

描述

我有一个专门的计算,我试图在逐个组的基础上进行,我为此编写了一个函数。该函数是用户定义的,用于执行此专门计算,需要 4 个参数(其中 2 个参数的长度 >1)并输出单个值(因此输出不等于输入的长度)。虽然这个函数确实有效,但我需要能够有效地将它应用于数据框中的每个组(对于下面的可重现示例,有 4 个组,但实际上,会有 100 或 1000 个组)。

我已经尝试使用 apply 函数,这些函数通常被推荐用于听起来与此类似的情况,但到目前为止,我在使用非 for 循环方法方面一直没有成功。我认为这是因为数据框中的每一行都没有与不同的组相关联,而是有多行与一个组相关联(对于下面的可重现示例,每个组有 21 行相关联,这与实际数据)。

无论如何,for 循环似乎是将我的函数应用于与每个组关联的行的直接方法。但是,我无法生成所需的输出。正如我在序言中提到的,我认为这只是因为我 overlooking/unaware 非常基础,例如需要在循环内执行循环或以不同方式索引我的 for 循环。

可重现的例子

功能相似的数据

interval=0.05 #used here to generate v1 and again in the function
v1 = seq(0.00000000001,1.00000000001, by=interval) 
nrows = length(v1) #determines length of other variables
g1 = c(rep(23.4, nrows), rep(19.7, nrows),rep(25.2, nrows),rep(16.4, 
nrows))           
v2 = runif(length(g1), 0,1)
dat = as.data.frame(cbind(g1,v1,v2))

其中:

函数

(这是我的第一个函数,我认为有更好的方法来编写它,但它确实有效)

MyFunction = function(v1, v2, interval, nrows) {
  sum.prod = sum(v1[2:nrows-1] * v2[2:nrows-1])
  last.val = v2[nrows]/2
  out = 2 * (sum.prod+last.val) * interval
  out
  }

函数有效的证明

我提供了第一个分组变量 (g1=23.4) 的计算结果,以防万一它有助于确认该函数是否有效以及它是如何工作的,因为没有关于此函数的文档

range1 = 1:nrows
g1.sub1 = dat$g1[range1]
v1.sub1 = dat$v1[range1]
v2.sub1 = dat$v2[range1]

g.first = 2 * ((v1.sub1[2] * v2.sub1[2])+
(v1.sub1[3] * v2.sub1[3]) + (v1.sub1[4] * v2.sub1[4]) +
(v1.sub1[5] * v2.sub1[5]) + (v1.sub1[6] * v2.sub1[6]) +
(v1.sub1[7] * v2.sub1[7]) + (v1.sub1[8] * v2.sub1[8]) +
(v1.sub1[9] * v2.sub1[9]) + (v1.sub1[10] * v2.sub1[10]) +
(v1.sub1[11] * v2.sub1[11]) + (v1.sub1[12] * v2.sub1[12]) +
(v1.sub1[13] * v2.sub1[13]) + (v1.sub1[14] * v2.sub1[14]) +
(v1.sub1[15] * v2.sub1[15]) + (v1.sub1[16] * v2.sub1[16]) +
(v1.sub1[17] * v2.sub1[17]) + (v1.sub1[18] * v2.sub1[18]) +
(v1.sub1[19] * v2.sub1[19]) + (v1.sub1[20] * v2.sub1[20]) +
v2.sub1[21] / 2) * interval

g.first

与给定的值匹配:

MyFunction(v1 = v1.sub1, v2 = v2.sub1, interval = interval, nrows=nrows)

我被困在哪里:For 循环

正如我在描述中提到的,我已经尝试了各种方法来解决这个问题,包括 apply 函数族,但没有成功。以下代码代表我最接近的代码。但是,这只为 g1 (23.4) 中的第一个元素提供了四次正确值,而不是为 g1 (23.4, 19.9.25.2,16.4) 中的四个元素中的每一个提供了一次正确值。

g=c(unique((g1)))
out=NULL
for(i in seq_along(g)){
out[i]=MyFunction( v1 = v1, v2 = v2, interval = interval, nrows = 
nrows)
}
out

尝试对 For 循环进行故障排除

我可以强制上面的 for 循环产生类似于预期结果的东西,但是必须为每个组指定范围,因为实际数据有 100 组而不是只有 4 组和组总数事先不知道这不是一个可行的解决方案。

g=c(unique((g1)))

range1 = 1:nrows
range2 = (nrows+1):(nrows*2)
range3 = (nrows*2+1):(nrows*3)
range4 = (nrows*3+1):(nrows*4)

out1=NULL
out2=NULL
out3=NULL
out4=NULL

for(i in seq_along(g)){
out1[i]=MyFunction( v1 = dat$v1[range1], v2 = dat$v2[range1], 
interval = interval, nrows = nrows)
out2[i]=MyFunction( v1 = dat$v1[range2], v2 = dat$v2[range2], 
interval = interval, nrows = nrows)
out3[i]=MyFunction( v1 = dat$v1[range3], v2 = dat$v2[range3], 
interval = interval, nrows = nrows)
out4[i]=MyFunction( v1 = dat$v1[range4], v2 = dat$v2[range4], 
interval = interval, nrows = nrows)
}

out1
out2
out3
out4

期望的输出

理想情况下,最终输出将是一个 table/matrix/list/data 帧,其中包含 g1 的每个值以及函数 "out"

输出的关联值

类似于:

g1      out
23.4    some value between 0 and 1
19.9    some value between 0 and 1
25.2    some value between 0 and 1
16.4    some value between 0 and 1

结论性思考

因为我的 "Attempt to Troubleshoot the For Loop" 最终能够提供正确的输出,尽管是以一种不受欢迎的方式(劳动密集型,不可扩展,并且它为每组输出 4 个相同的值,而不是为每组输出 1 个值) ,我认为这表明我的代码缺少一些基本的东西(例如,另一个循环、seq_along 的不同变量、不正确的索引等)。我希望这对更有经验的用户来说很容易识别和解释,因为我很困惑。

提前致谢!

这是使用 tidyverse 的方法。

首先,让我们看一下示例,将 MyFunction 替换为捕获您描述的摘要过程的几行代码:

library(tidyverse)
dat %>%
  slice(1:21) %>%  # Just the first grouping variable
  slice(2:n()) %>% # Exclude first row; has small impact since v1[1] is nearly zero already...
  mutate(prod = if_else(row_number() < n(),  # For all rows but the last one in the group,
                        v1 * v2,             # ... get the product of v1 and v2
                        v2/2)) %>%           # ... or have of v2, for the last row
  summarize(out = 2 * sum(prod) * interval)  # Sum the "prod" row, * 2 * interval

#        out
#1 0.5980449

要对 g1 的所有组执行此操作,我们首先添加 group_by,然后对每个组分别执行相同的汇总步骤:

dat %>%
  group_by(g1) %>%
  slice(1:21) %>%  # Just the first grouping variable
  slice(2:n()) %>% # Exclude first row; has small impact since v1[1] is nearly zero already...
  mutate(prod = if_else(row_number() < n(),  # For all rows but the last one in the group,
                        v1 * v2,             # ... get the product of v1 and v2
                        v2/2)) %>%           # ... or have of v2, for the last row
  summarize(out = 2 * sum(prod) * interval)  # Sum the "prod" row, * 2 * interval

## A tibble: 4 x 2
#     g1   out
#  <dbl> <dbl>
#1  16.4 0.342
#2  19.7 0.514
#3  23.4 0.598
#4  25.2 0.568

我知道您要求使用 for 循环,但正如您之前可能已经看到的那样,通常有更好的方法来实现它。我猜你还不熟悉 data.table 包,把它想象成一个增压的 data.frame.

所以您要做的是将 MyFunction 应用到您的数据,按列 g1 分组。这可以通过以下方式在 data.table 中轻松实现。

library(data.table)
DT <- as.data.table(dat)
DT[, .(out = MyFunction(v1, v2, interval, .N)), by = g1]

所以这些行所做的是首先加载库(您可能必须先使用 install.packages('data.table') 安装它。然后将您的 data.frame 转换为 data.table。最后,计算列 out 作为 MyFunction 应用于 v1, v2, interval and .N(将 .N 视为 nrows)按 g1.

分组

我认为这达到了你的目的,如果你有任何问题,请随时提问。希望这会有所帮助。