【问题标题】:How do I use a for loop to apply a multi-argument function to a data frame based on a grouping variable?如何使用 for 循环将多参数函数应用于基于分组变量的数据框?
【发布时间】:2019-07-05 12:59:15
【问题描述】:

序言

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

说明

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

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

无论如何,for 循环似乎是将我的函数应用于与每个组关联的行的一种直接方法。但是,我无法生成所需的输出。正如我在序言中提到的那样,我认为这只是因为我忽略/没有意识到一些非常基本的事情,例如需要在循环内执行循环或以不同方式索引我的 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))

地点:

  • g1是分组变量
  • v1 是第一个参数,对每个分组变量重复
  • v2是第二个参数,表示与每个v1相关的概率
  • dat 是数据框

函数

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

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 循环

正如我在描述中提到的那样,我尝试了各种方法来解决这个问题,包括应用函数系列,但没有运气。下面的代码代表了我最接近的。但是,这只给了我四次 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

所需的输出

理想情况下,最终输出将是一个表格/矩阵/列表/数据框,其中包含 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

结论性思考

由于我的“For 循环故障排除尝试”最终能够提供正确的输出,尽管是以一种不合需要的方式(劳动密集型,不可扩展,并且它为每组输出 4 个相同的值,而不是为每组输出 1 个值),我认为这表明我的代码缺少一些基本的东西(例如,另一个循环、seq_along 的不同变量、不正确的索引等)。我希望这对于更有经验的用户来说很容易识别和解释,因为我很难过。

提前致谢!

【问题讨论】:

    标签: r for-loop indexing subset scalability


    【解决方案1】:

    我知道您要求使用 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应用于由g1分组的v1, v2, interval and .N(将.N视为nrows)。

    我认为这可以实现您的目标,如果您有任何问题,请随时提出。希望这会有所帮助。

    【讨论】:

      【解决方案2】:

      这是使用 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
      

      【讨论】:

        猜你喜欢
        • 2021-04-25
        • 1970-01-01
        • 2017-07-21
        • 1970-01-01
        • 2020-05-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-08-27
        相关资源
        最近更新 更多