【问题标题】:How to Run a Loop through multiple groups in R如何在 R 中通过多个组运行循环
【发布时间】:2019-04-16 20:20:36
【问题描述】:

我有一个包含多个变量的数据集,我想将两列的值逐行求和。如果总和低于设定的阈值,我想用总和值替换第二列(正在求和)的值。但是,我想按组执行此操作。

我的数据框设置有 18 个不同的列,包括“Closed_Grid”、“Closed_Sets”、“BestAvail”、“Best_Sets”和“Best_Distance”。 “BestAvail”、“Best_Sets”和“Best_Distance”重复用于第二最佳、第三、第四和第五。我正在使用此信息来确定最终目标位置(第 18 列“Dest_Grid”),该位置将根据“Closed_Sets”和目标集的条件求和填充来自“BestAvail”、“2nd_Best”等的网格索引(最佳、第二等)。最后,如果两列的总和

因此,为了帮助简化这一点,我的数据集的样本(和子集)如下所示:

Closed_Grid Closed_Sets BestAvail Best_Sets
GY38         72.875     GX38       91.75
GY37         87.125     GX38       91.75
GY36         39.875     GX38       91.75
GZ38         29         GX38       91.75
GZ37         80         GX38       91.75
GY35         2.375      GX38       91.75
GZ36         125.25     GX38       91.75
GZ35         29.875     GX38       91.75
GY39         17.5       GX39       54.125
HA35         34.375     GZ33       30.5
GZ41         109.625    GZ42       76.76
GY41         82.28571   GZ42       76.75
HA41         87.5       GZ42       76.75
GZ40         104.75     GZ42       76.75
GY40         60.625     GZ42       76.75
HA40         79.875     GZ42       76.75
GZ39         51.57143   GZ42       76.75
HA39         71         GZ42       76.75

我首先使用“BestAvail”和“Distance”(从小到大)排列我的数据:

Destination <- Destination %>% arrange(BestAvail, BestDistance)

这是一个重要的顺序,因为距离 BestAvail 距离最近的 Closed_Grid 优先进入该网格。

所以现在我想在一个组中按行对“Closed_Sets”和“Best_Sets”求和(即“BestAvail”相同)。每当行的总和小于阈值 (150) 时,“Best_Sets”值将替换为先前的总和。所以,我想要的输出是这样的:

Closed_Grid Closed_Sets BestAvail Best_Sets BestSum
GY38         72.875     GX38       91.75    164.6250
GY37         87.125     GX38       91.75    178.8750
GY36         39.875     GX38       91.75    131.625
GZ38         29         GX38       131.625  160.625  
GZ37         80         GX38       131.625  211.625
GY35         2.375      GX38       131.625  134.00
GZ36         125.25     GX38       134.00   259.250
GZ35         29.875     GX38       134.00   163.8750
GY39         17.5       GX39       54.125   71.625
HA35         34.375     GZ33       30.5     64.875
GZ41         109.625    GZ42       76.75    186.375
GY41         82.28571   GZ42       76.75    159.03571
HA41         87.5       GZ42       76.75    164.25
GZ40         104.75     GZ42       76.75    181.5
GY40         60.625     GZ42       76.75    137.375
HA40         79.875     GZ42       137.375  217.25
GZ39         51.57143   GZ42       137.375  188.94643
HA39         71         GZ42       137.375  208.375

我可以通过使用这个循环部分实现这一点:

for (i in 1:nrow(Destination)){
    Destination$BestSum[i] <- sum(Destination$Closed_Sets[i], Destination$Best_Sets[i])
    if (Destination$BestSum[i] <= 150){
      Destination [i:length(Destination),"Best_Sets"] <- Destination$BestSum[i]
    }
  }

但是,此代码将所有“Best_Sets”的值设为 134,并且在“BestAvail”值更改时不会重新启动,这反过来又会混淆以下所有总和。最终,我试图对一个保持在 150 以下的组中的每个“Closed_Set”进行条件累积总和。

这是我正在研究的模型的一部分,它将有超过 150 个单独的数据集贯穿其中,所有数据集的长度和值都不同。这段特殊的代码还需要遍历第二、第三等集合,因此它需要是可以重复的,并且变量很容易改变。

我尝试在循环中使用 unique() 函数,尝试让我自己的函数在 dplyr 中使用(这将是理想的!),尝试使用重置函数进行不同的累积和,并在此搜索了数百个线程观点。

我对 R 和编程比较陌生,很难弄清楚如何做到这一点。我已经查看了与此相关的每个可能问题的多次讨论,但似乎无法让它在我的数据上发挥作用。

我希望我想要实现的目标是有意义的。

提前致谢!

【问题讨论】:

    标签: r for-loop dplyr


    【解决方案1】:

    注意:以下 R 代码不是很惯用,可能会很慢。我不建议您将这种样式用于常见任务。

    # build the data frame
    Closed_Grid = c(
      "GY38",
      "GY37",
      "GY36",
      "GZ38",
      "GZ37",
      "GY35",
      "GZ36",
      "GZ35",
      "GY39",
      "HA35",
      "GZ41",
      "GY41",
      "HA41",
      "GZ40",
      "GY40",
      "HA40",
      "GZ39",
      "HA39"
    )
    
    Closed_Sets = c(
      72.875,
      87.125,
      39.875,
      29,
      80,
      2.375,
      125.25,
      29.875,
      17.5,
      34.375,
      109.625,
      82.28571,
      87.5,
      104.75,
      60.625,
      79.875,
      51.57143,
      71
    )
    
    BestAvail = c(
      "GX38",
      "GX38",
      "GX38",
      "GX38",
      "GX38",
      "GX38",
      "GX38",
      "GX38",
      "GX39",
      "GZ33",
      "GZ42",
      "GZ42",
      "GZ42",
      "GZ42",
      "GZ42",
      "GZ42",
      "GZ42",
      "GZ42"
    )
    
    Best_Sets = c(
      91.75,
      91.75,
      91.75,
      91.75,
      91.75,
      91.75,
      91.75,
      91.75,
      54.125,
      30.5,
      76.76,
      76.75,
      76.75,
      76.75,
      76.75,
      76.75,
      76.75,
      76.75
    )
    
    dat <- data.frame(
      Closed_Grid, Closed_Sets, BestAvail, Best_Sets,
      stringsAsFactors = FALSE
    )
    
    # allocate a vector; this makes the for() loop use significantly
    # less memory, see https://adv-r.hadley.nz/perf-improve.html#avoid-copies
    
    dat$BestSum <- NA_real_
    
    # split the data frame to work on one group of BestAvail at a time
    Destination <- split(dat, factor(dat[["BestAvail"]]))
    
    Destination <- lapply(Destination, function(dat) {
      for (i in seq_len(nrow(dat))) {
    
        BestSum <- rowSums(dat[i, c("Closed_Sets", "Best_Sets")])
        dat[i, "BestSum"] <- BestSum
    
        if (as.integer(i) > 1L) {
          if (BestSum < 150.0) {
            dat[i+1:(nrow(dat) - i), "Best_Sets"] <- dat[i, "BestSum"]
          }
        }
    
      }
    
      dat
    
    })
    
    # recombine
    Destination <- do.call(rbind, Destination)
    
    Destination
    

    这段代码可能会很慢。如果您在大型数据集上运行此程序,则可能值得用 C++ 编写。

    【讨论】:

    • 感谢您的回复!我想我应该提到我还根据距离(我的示例中没有包括的另一列)和 BestAvail 对行进行了排序。我还有更多的列(2ndBest、3rd Best 等),并且我计划对每个块运行相同的条件总和。它还将自动运行 150 多个数据帧,所以我不确定这是否适用于我正在尝试做的事情。我可以试一试。再次感谢!
    • 理想情况下,是的,您应该预先指定。就目前而言,这回答了您的问题,您应该能够调整您的代码以在其他数据集/变量名称上运行。
    【解决方案2】:

    目标是您的数据框

    试试这个:

      library(tidyverse)
      Destination %>%
       arrange(BestAvail, BestDistance) %>%
       mutate(BestSum = Closed_Sets + Best_Sets) %>%
       group_by(BestAvail) %>%
       mutate(Best_Sets2 = case_when(BestSum < 150 ~ lag(BestSum),
                                     TRUE ~ Best_Sets))
    

    【讨论】:

    • 您好,感谢您的建议 - 不幸的是,它对我不起作用。它在 Best_Sets2 中给了我错误的总和和一些 NA,但不确定哪里出错了。
    【解决方案3】:

    所以我尝试了提供的几种解决方案,但不幸的是我无法让它们真正为我工作。但是,我确实想出了一个解决方案(在我的主管的帮助下),它超级丑陋而且很长,但它确实给了我想要的结果。

    ##Rearrange data from ascending order of Best Avail grids and Distance
    Destination <- Destination %>% arrange(BestAvail, BestDistance)
    
    ####Set Levels and change to a factor in order to iterate through the different groups
    Destination$Closed_Grid <- droplevels(Destination$Closed_Grid)
    Destination$BestAvail <- as.factor(Destination$BestAvail)
    
    ###create a working file
    WorkingDest = Destination[FALSE,]
    
    ##Loop that conditionally sums row by group, gives final dest grid, and pastes into working file
    for (f in 1: nlevels (Destination$BestAvail)) {
      work <- subset(Destination, BestAvail == levels(Destination$BestAvail)[f])
      for (i in 1:nrow(work)){
        for (j in 1:length(levels(work$BestAvail))){
          if (as.character(work$BestAvail)[i] == as.character(levels(work$BestAvail)[j])){
            work$BestSum[i] <- sum(work$Closed_Sets[i], work$Best_Sets[i])
            if (work$BestSum[i] <= 150){
              work [i:nrow(work),"Best_Sets"] <- work$BestSum[i]
              work$Dest_Grid [i] <- as.character(work$BestAvail)[i]
            }
          }
        }
      }
      WorkingDest <- rbind(WorkingDest, work)
    }
    ###Create Results DataFrame for Closed Sets that have Moved
    FinalDestination <- WorkingDest[WorkingDest$Dest_Grid != 0,]
    
    ##Create a working df that only have the new base sets for matching purposes
    MaxSetsBest <- WorkingDest %>%
      group_by(BestAvail) %>% top_n(1, Best_Sets)
    MaxSetsBest <- MaxSetsBest[!duplicated(MaxSetsBest$BestAvail), ]
    
    ####change basesets for SecondBest based on previous iterations
    for(id in 1:nrow(MaxSetsBest)){
      WorkingDest$Second_Sets[WorkingDest$SecondBest %in% MaxSetsBest$BestAvail[id]] <- MaxSetsBest$Best_Sets[id]
    }
    rm(id)
    
    ##Reset Destination with new basesets
    Destination <- WorkingDest
    
    ##Remove Closed Grids that have moved from working file
    Destination <- WorkingDest[!WorkingDest$Dest_Grid != 0,]
    
    

    然后我为 SecondBest 选项重新运行相同的代码,依此类推。我知道这不是很好的编码,但它可以工作,而且我有非常小的数据帧(最多 50 行)正在运行,所以速度不是一个重要因素。如果有人知道如何让这变得更好,但如果没有,它对我有用!

    【讨论】:

      猜你喜欢
      • 2015-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多