【问题标题】:Calculate a field based on several rows selected by index value in R without looping?根据R中索引值选择的几行计算字段而不循环?
【发布时间】:2013-07-26 17:17:18
【问题描述】:

我需要根据 OBJECTID 链接的几个字段进行分类/重新编码。 如果您有兴趣:我的对象是河流,我需要总结/重新编码各种生态相关参数。

对于这个例子,我只是在做一个平均数,但实际上我需要实现更复杂的重新编码; f.e.如果由 OBJECTID 标识的河流段的 70% 为 OM

Input                              Output

OBJECTID  OM                       OBJECTID  OM     OM_mean  
1         3.1                      1         3.1    5.13                      
1         8.2                      1         8.2    5.13
1         4.1       ----->         1         4.1    5.13
2         2.3                      2         2.3    6.2
2         9.1                      2         9.1    6.2

(是的,我需要这种形式,聚合不能满足我的需要)

这使用 for 循环相对容易实现,但是,我的表非常大,并且处理我的数据的过程非常慢(在现代计算机上需要几天)

for(i in dat$OBJECTID) {
  a=dat[dat$OBJECTID == i,]
  dat$OM_mean[dat$OBJECTID == i]      = mean(a$OM)
  }

我想知道是否存在使用 apply 之类的更优雅/更快的方法,但我找不到解决方案。我希望我能够清楚地说明我的问题。

如果使用了不恰当的术语,或者如果您认为主题标题不是很清楚,请纠正我,我对 R 和一般编程比较陌生。

我用于重新编码的实际函数(而不是示例中给出的平均值)是:

for(i in ecol_risk$OBJECTID) {
  a=ecol_risk@data[ecol_risk$OBJECTID == i,] # subset one river stretch of interest

  if(min(a$OM) %in% c(1,2,3,4,5)){  # Filters out some unwanted values

    b=aggregate(a$SLengthM, by=list(a$OM), FUN=sum)
    names(b) = c("OM", "SLengthM")
    b$frac = b$SLengthM/(sum(a$SLengthM)) # Calculate the % of total river stretch length
    b$frac12 = 0
      if(1 %in% b$OM & 2 %in% b$OM) { # get % for combination of two OM values
        b$frac12 = b$frac[b$OM == 1] + b$frac[b$OM == 2] 
      }
    b$frac45 = 0
    if(4 %in% b$OM & 5 %in% b$OM) {
      b$frac12 = b$frac[b$OM == 4] + b$frac[b$OM == 5] 
    }

    b$OM_agg = 3  # do some weird recoding

    b$OM_agg[b$frac >= 0.8 & b$OM == 5] = 4
    b$OM_agg[b$frac >= 0.8 & b$OM == 4] = 4
    b$OM_agg[b$bfrac45 >= 0.7] = 4

    b$OM_agg[b$frac >= 0.5 & b$OM == 1] = 2
    b$OM_agg[b$frac >= 0.7 & b$OM == 2] = 2
    b$OM_agg[b$frac12 >= 0.7] = 2  

    b$OM_agg[b$frac >= 0.8 & b$OM == 1] = 1
    b$OM_agg[b$frac >= 0.9 & b$OM == 2] = 1
    b$OM_agg[b$bfrac12 >= 0.9] = 1

    x = min(b$OM_agg)

    ecol_risk@data$OM_agg[ecol_risk$OBJECTID == i] = x

    print(i)
  }
}

【问题讨论】:

  • 看起来您的输入是一个名为 dat 的数据框;我们能否拥有该数据框的尺寸,或者更好的str(dat)?并且未使用代码中的 a = 位。您的基本任务是获取所有具有相同 OBJECTID 的行,然后对其他列执行某些操作,对吗?
  • 我意识到我可能在问题中过度简化了我的问题。可悲的是,我的 R 现在被循环方法所占据,我将在星期一发布真实世界的数据。它也应该是平均值(a$OM)。在问题中修复它。
  • 一切都好。 @shadow 的答案很好,但它是aggregate 的一个变体(我需要研究两者之间的差异)。任何一个都允许您传递需要编写的函数,但这非常强大。
  • 我现在发布了我的重新编码功能的整个代码,我希望能够避免这种情况,因为它真的很混乱......虽然我不知道我将如何实现它作为我可以的功能传递给聚合或平均

标签: r loops indexing field apply


【解决方案1】:

为了最大限度地提高速度和语法的简单性,请使用data.table:

library(data.table)
dt = data.table(your_data_frame)

dt[, OM_MEAN := mean(OM), by = OBJECTID]

【讨论】:

    【解决方案2】:

    如果你只想计算平均值,那么?ave 函数就是你要找的

    dat[, "OM_mean"] <- ave(dat$OM, dat$OBJECTID, FUN=mean)
    

    由于您显然想用data.frame 计算许多汇总统计信息,而不仅仅是一个,我建议您改用plyr 包。如果您使用您提供的数据(连同权重),则 dput(dat) 给出:

    dat <- structure(list(OBJECTID = c(1L, 1L, 1L, 2L, 2L), 
                          OM = c(3.1, 8.2, 4.1, 2.3, 9.1), 
                          weight = c(1, 1, 2, 1, 2)), 
                     .Names = c("OBJECTID", "OM", "weight"), 
                     row.names = c(NA, -5L), class = "data.frame")
    

    然后您可以使用plyr 中的ddply 来计算您的摘要。

    # load package
    require(plyr)
    # split by OBJECTID and apply function
    ddply(dat, "OBJECTID", function(x){
      x[,"OM_mean"] <- mean(x$OM) # mean
      x[,"OM_w.mean"] <- weighted.mean(x$OM, x$weight) # weighted mean
      return(x) # return the entire data.frame
    })
    

    【讨论】:

    • 工作起来就像一个魅力,比我的 for 循环快一百万倍,并且有一个漂亮的进度条,谢谢。
    猜你喜欢
    • 1970-01-01
    • 2023-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多