【问题标题】:Creating groups of equal sum in R在R中创建相等和的组
【发布时间】:2015-04-02 23:28:03
【问题描述】:

我正在尝试将我的 data.frame/data.table 的一列分成三组,总和相等。

数据首先从小到大排序,这样第一组将由大量具有小值的行组成,而第三组将由少量具有大值的行组成。这是通过以下精神实现的:

test <- data.frame(x = as.numeric(1:100000))
store <- 0
total <- sum(test$x)

for(i in 1:100000){

  store <- store + test$x[i]

  if(store < total/3){

    test$y[i] <- 1

  } else {

      if(store < 2*total/3){

        test$y[i] <- 2

      } else { 

        test$y[i] <- 3

      }     
  }    
}

虽然成功,但我觉得必须有更好的方法(也许是我缺少的一个非常明显的解决方案)。

  • 当矢量化方法可用时,我从不喜欢使用循环,尤其是使用嵌套 if 时 - 即使有 100,000 多条记录,这段代码也会变得非常慢
  • 这种方法会变得非常复杂,无法编码到大量组(不一定是循环,而是 ifs)
  • 需要预先订购该列。可能无法解决这个问题。

作为一个细微差别(并不是说它有区别),但要求和的数据并不总是(或永远)是连续的整数。

【问题讨论】:

  • 我认为预订实际上破坏了平等分配,因为当您获得更高的价值时,您拥有的就是大尺寸的物品。
  • BondedDust - 公平点,但这就是我们想要的。我想要一个相等的总和分配,将最小值、中值和最大值组合在一起

标签: r bin-packing


【解决方案1】:

也许有cumsum:

test$z <- cumsum(test$x) %/% (ceiling(sum(test$x) / 3)) + 1

【讨论】:

    【解决方案2】:

    这或多或少是一个bin-packing 问题。

    使用BBmisc 包中的binPack 函数:

    library(BBmisc)
    test$bins <- binPack(test$x, sum(test$x)/3+1)
    

    3 个 bin 的总和几乎相同:

    tapply(test$x, test$bins, sum)
    
    
        1          2          3 
    1666683334 1666683334 1666683332
    

    【讨论】:

      【解决方案3】:

      我认为 cumsum/modulo 除法方法非常优雅,但它确实会返回一些不规则的分配:

      > tapply(test$x, test$z, sum)
               1          2          3 
      1666636245 1666684180 1666729575 
      > sum(test)/3
      [1] 1666683333
      

      所以我会先创建一个随机排列并提供类似的东西:

       test$x <- sample(test$x)
       test$z2 <- cumsum(test$x)[ findInterval(cumsum(test$x), 
                                              c(0, 1666683333*(1:2), sum(test$x)+1))]
      > tapply(test$x, test$z2, sum)
           91099     116379     129539 
      1666676164 1666686837 1666686999 
      

      这也实现了更均匀的计数分布:

      > table(test$z2)
      
       91099 116379 129539 
       33245  33235  33520 
      > table(test$z)
      
          1     2     3 
      57734 23915 18351 
      

      我必须承认对z2中条目的命名感到困惑。

      【讨论】:

        【解决方案4】:

        或者你可以在 cumsum 上 cut

        test$z <- cut(cumsum(test$x), breaks = 3, labels = 1:3) 
        

        或使用ggplot2::cut_interval 代替cut

        test$z <- cut_interval(cumsum(test$x), n = 3, labels = 1:3) 
        

        【讨论】:

          【解决方案5】:

          您可以使用 groupdata2 中的 fold() 并获得每组几乎相等数量的元素:

          # Create data frame
          test <- data.frame(x = as.numeric(1:100000)) 
          
          # Use fold() to create 3 numerically balanced groups  
          test <- groupdata2::fold(k = 3, num_col = "x")
          
          # Watch first 10 rows
          head(test, 10)
          
          ## # A tibble: 10 x 2
          ## # Groups:   .folds [3]
          ##        x .folds
          ##    <dbl> <fct> 
          ##  1     1 1     
          ##  2     2 3     
          ##  3     3 2     
          ##  4     4 1     
          ##  5     5 2     
          ##  6     6 2     
          ##  7     7 1     
          ##  8     8 3     
          ##  9     9 2     
          ## 10    10 3 
          
          # Check the sum and number of elements per group
          test %>% 
            dplyr::group_by(.folds) %>% 
            dplyr::summarize(sum_ = sum(x),
                             n_members = dplyr::n())
          
          ## # A tibble: 3 x 3
          ##   .folds       sum_ n_members
          ##   <fct>       <dbl>     <int>
          ## 1 1      1666690952     33333
          ## 2 2      1666716667     33334
          ## 3 3      1666642381     33333
          

          【讨论】:

            猜你喜欢
            • 2014-05-24
            • 2014-04-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-10-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多