【问题标题】:Calculate a cumulative sum columwise with a specified number of columns计算具有指定列数的累积和
【发布时间】:2018-08-16 09:38:52
【问题描述】:

您可以在下面看到我拥有的更大表的示例

library(data.table)
input  <- data.table(ID     = c("A", "B"),
                     Para   = c(2.8, 5),
                     Value1 = c(50, 80),
                     Value2 = c(80, 40),
                     Value3 = c(80, 100),
                     Value4 = c(60, 10),
                     Value5 = c(40, 80))

我想要实现的是添加一列,其中包含 Para 列中指定的下 x 列的累积总和。但如果逗号后面有数字,则列中的值应按数字调整。

所以对于第一行(Para = 2.8),结果应该是

1*50 + 1*80 + 0.8*80 = 194

第二行(Para = 5)的结果应该是

1*80 + 1*40 + 1*100 + 1*10 + 1*80 = 310

决赛桌应该是这样的

output <- cbind(input, Result = c(194, 310))

我的想法是将 Para 值 2.8 拆分为 5 个数字的百分比向量,因此是整个范围。

c(1, 1, .8, 0, 0)

将列 Value1:Value5 与该向量相乘,然后将所有 Value1:Value5 相加。但我不知道如何将 2.8 拆分为这样的向量,也许有更好的解决方案我不知道。谢谢。

【问题讨论】:

    标签: r cumsum


    【解决方案1】:

    这是一个将数据保持为宽格式并使用Reduce() 计算“加权行总和”的解决方案:

    library(data.table)
    input[, Cumul := {
      tmp <- c(rep(1, Para), Para %% 1)
      mul <- replace(rep(0, ncol(.SD)), seq_along(tmp), tmp)
      Reduce(sum, .SD * mul)
    }, .SDcols = Value1:Value5, by = ID]
    input[]
    
       ID Para Value1 Value2 Value3 Value4 Value5 Cumul
    1:  A  2.8     50     80     80     60     40   194
    2:  B  5.0     80     40    100     10     80   310
    

    这适用于.SDcols 指定的任意数量的列,或者Para 更大。

    【讨论】:

    • 也许.SDcols = names(input)[-(1:2)] 会自动考虑IDPara 之后的所有列?这假设这 2 列总是首先出现,然后是一系列 Value 列...
    • @AntoniosK 一般来说,我觉得指定我想要处理的列更安全(如果要添加另一列,你必须记住在另一个地方更新你的代码)。在这里,在给定的示例中,我们可以写更短的.SDcols = -Para,因为ID 已经通过by = ID 排除在外
    • 令人印象深刻!谢谢。
    【解决方案2】:
    # example data
    input  <- data.frame(ID     = c("A", "B"),
                         Para   = c(2.8, 5),
                         Value1 = c(50, 80),
                         Value2 = c(80, 40),
                         Value3 = c(80, 100),
                         Value4 = c(60, 10),
                         Value5 = c(40, 80))
    
    library(tidyverse)
    
    # function that creates a vector of multipliers based on Para column
    # assumes that you have ID, Para and rest columns are Value 1,2...,N
    # if Para is larger than the corresponding values it keeps first x multipliers
    f_create_vector = function(x) {
        y = if(x %% 1 > 0) c(rep(1, x), x %% 1) else rep(1, x)
        z = rep(0, ncol(input)-2)
        c(y, z[-seq_along(y)])[1:(ncol(input)-2)] 
    }
    
    
    input %>%
      group_by(ID, Para) %>%                              # for each combination
      nest() %>%                                          # nest data
      group_by(ID) %>%                                    # for each ID
      mutate(vec = list(f_create_vector(Para))) %>%       # create a column of multipliers in a list
      mutate(CumSum = map2(data, vec, ~sum(.x * .y))) %>% # get the cumsum using multipliers and the value columns
      ungroup() %>%                                       # forget the grouping
      unnest(data, CumSum) %>%                            # unnest those columns
      select(-vec)                                        # remove that column
    
    # # A tibble: 2 x 8
    #   ID     Para CumSum Value1 Value2 Value3 Value4 Value5
    #   <fct> <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
    # 1 A       2.8    194     50     80     80     60     40
    # 2 B       5      310     80     40    100     10     80
    

    【讨论】:

      【解决方案3】:

      您可以使用模除法 %/% 和其余的除法来创建乘法向量,然后使用 apply 调用中的所有内容:

      apply(input, MARGIN = 1, function(x) {
        multiplier <- as.numeric(x["Para"])
        multiplier_long <- c(rep(1, multiplier %/% 1), multiplier %% 1)[1:5]
        multiplier_long[is.na(multiplier_long)] <- 0
        sum(as.numeric(x[-c(1, 2)]) * multiplier_long)
      })
      
      # [1] 194 310
      

      【讨论】:

        【解决方案4】:

        如果您想将Para 拆分为一个 5 值向量,您可以尝试以下操作:

        input %>%
          select(ID,Para) %>%
          slice(rep(1:n(), each = 5)) %>%
          group_by(ID) %>%
          mutate(rn = 1:n()) %>%
          mutate(Para = if_else( (Para - rn)>0, 1.0, 
                                if_else(Para - rn > -1, Para - lag(rn), 0))) %>%
          select(-rn)
        

        给出:

           ID     Para
           <chr> <dbl>
         1 A     1    
         2 A     1    
         3 A     0.800
         4 A     0    
         5 A     0    
         6 B     1    
         7 B     1    
         8 B     1    
         9 B     1    
        10 B     1    
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-12-17
          • 2021-07-27
          • 2020-12-02
          • 2019-10-07
          • 1970-01-01
          • 2013-01-26
          • 2018-03-26
          相关资源
          最近更新 更多