【问题标题】:Summing values in R based on column value with dplyr使用 dplyr 根据列值对 R 中的值求和
【发布时间】:2019-07-03 05:09:44
【问题描述】:

我有一个包含以下信息的数据集:

Subject    Value1    Value2    Value3      UniqueNumber
001        1         0         1           3
002        0         1         1           2
003        1         1         1           1

如果 UniqueNumber 的值 > 0,我想将第 1 行到 UniqueNumber 的每个主题的值与 dplyr 相加并计算平均值。所以对于主题 001,总和 = 2,平均值 = .67。

total = 0;
average = 0;
for(i in 1:length(Data$Subject)){
   for(j in 1:ncols(Data)){
   if(Data$UniqueNumber[i] > 0){
    total[i] = sum(Data[i,1:j])
    average[i] = mean(Data[i,1:j])
   }
}

编辑:我只想对“UniqueNumber”列中列出的列数求和。所以这是循环遍历每一行并在“UniqueNumber”中列出的列处停止。 示例:主题为 002 的第 2 行应对“Value1”和“Value2”列中的值求和,而“主题”为 003 的第 3 行应仅对“Value1”列中的值求和。

【问题讨论】:

  • 你可以试试df %>% mutate(sum = ifelse(UniqueNumber > 0, rowSums(.[, 2:(length(.)-1)]), NA), mean = ifelse(UniqueNumber > 0, rowMeans(.[, 2:(length(.)-1)]), NA))
  • @tmfmnk 我认为您的代码不会遍历 UniqueNumber 的长度。看起来我的结果是对整个列求和,而不是停留在 UniqueValue 列的值。

标签: r dataframe dplyr


【解决方案1】:

我认为最简单的方法是将真正应该是NA 的零设置为NA,然后在适当的列子集上使用rowSumsrowMeans

Data[2:4][(col(dat[2:4])>dat[[5]])] <- NA
Data
#   Subject Value1 Value2 Value3 UniqueNumber
# 1       1      1      0      1            3
# 2       2      0      1     NA            2
# 3       3      1     NA     NA            1

library(dplyr)
Data%>%
  mutate(sum  =  rowSums(.[2:4], na.rm = TRUE),
         mean = rowMeans(.[2:4], na.rm = TRUE))

#   Subject Value1 Value2 Value3 UniqueNumber sum      mean
# 1       1      1      0      1            3   2 0.6666667
# 2       2      0      1     NA            2   1 0.5000000
# 3       3      1     NA     NA            1   1 1.0000000

transform(Data, sum = rowSums(Data[2:4],na.rm = TRUE), mean = rowMeans(Data[2:4],na.rm = TRUE)) 留在基地R。

数据

Data <- structure(
  list(Subject = 1:3, 
       Value1 = c(1L, 0L, 1L), 
       Value2 = c(0L, 1L, NA), 
       Value3 = c(1L, NA, NA), 
       UniqueNumber = c(3L, 2L, 1L)), 
  .Names = c("Subject","Value1", "Value2", "Value3", "UniqueNumber"),
  row.names = c(NA, 3L), class = "data.frame")

【讨论】:

    【解决方案2】:

    这是另一种方法,它使用tidyr::nestValues 列收集到一个列表中,以便我们可以使用map2 遍历表。在每一行中,我们从Values list-col 中选择正确的值并分别取总和或平均值。

    library(tidyverse)
    tbl <- read_table2(
    "Subject    Value1    Value2    Value3      UniqueNumber
    001        1         0         1           3
    002        0         1         1           2
    003        1         1         1           1"
    )
    tbl %>%
      filter(UniqueNumber > 0) %>%
      nest(starts_with("Value"), .key = "Values") %>%
      mutate(
        sum = map2_dbl(UniqueNumber, Values, ~ sum(.y[1:.x], na.rm = TRUE)),
        mean = map2_dbl(UniqueNumber, Values, ~ mean(as.numeric(.y[1:.x], na.rm = TRUE))),
      )
    #> # A tibble: 3 x 5
    #>   Subject UniqueNumber Values             sum  mean
    #>   <chr>          <dbl> <list>           <dbl> <dbl>
    #> 1 001                3 <tibble [1 × 3]>     2 0.667
    #> 2 002                2 <tibble [1 × 3]>     1 0.5  
    #> 3 003                1 <tibble [1 × 3]>     1 1
    

    reprex package (v0.2.1) 于 2019 年 2 月 14 日创建

    【讨论】:

      【解决方案3】:

      OP 可能只对dplyr 解决方案感兴趣,但出于比较目的和未来读者使用mapply 的基本R 选项

      cols <- grep("^Value", names(df))
      
      cbind(df, t(mapply(function(x, y) {
            if (y > 0) {
              vals = as.numeric(df[x, cols[1:y]])
              c(Sum = sum(vals, na.rm = TRUE), Mean = mean(vals, na.rm = TRUE))
             }
             else 
              c(0, 0)
      },1:nrow(df), df$UniqueNumber)))
      
      #  Subject Value1 Value2 Value3 UniqueNumber Sum  Mean
      #1       1      1      0      1            3   2 0.667
      #2       2      0      1      1            2   1 0.500
      #3       3      1      1      1            1   1 1.000
      

      这里我们根据其各自的UniqueNumber 对每一行进行子集化,然后如果UniqueNumber 的值大于0,则计算其为summean,否则仅返回0。

      【讨论】:

        【解决方案4】:

        使用purrr::map_df的解决方案(与dplyr出自同一作者)。

        library(dplyr)
        library(purrr)
        l_dat <- split(dat, dat$Subject) # first we need to split in a list
        
        map_df(l_dat, function(x) {
          n_cols <- x$UniqueNumber # finds the number of columns
          x <- as.numeric(x[2:(n_cols+1)]) # subsets x and converts to numeric
          mean(x, na.rm=T) # mean to be returned
        })
        # output:
        # # A tibble: 1 x 3
        #     `1`   `2`   `3`
        #   <dbl> <dbl> <dbl>
        # 1 0.667   0.5     1
        

        另一种选择(输出格式更接近dplyr 解决方案):

        map_df(l_dat, function(x) {
          n_cols <- x$UniqueNumber
          id <- x$Subject
          x <- as.numeric(x[2:(n_cols+1)])
          tibble(id=id, mean_values=mean(x, na.rm=T))
        })
        # # A tibble: 3 x 2
        # id mean_values
        # <int>       <dbl>
        # 1     1       0.667
        # 2     2       0.5  
        # 3     3       1   
        

        作为一个例子,我添加了一个sum(),然后除以length(x)-1

        map_df(l_dat, function(x) {
          n_cols <- x$UniqueNumber
          id <- x$Subject
          x <- as.numeric(x[2:(n_cols+1)])
          tibble(id=id, 
                        mean_values=sum(x, na.rm=T)/(length(x)-1)) # change here
        })
        # # A tibble: 3 x 2
        # id mean_values
        # <int>       <dbl>
        # 1     1          1.
        # 2     2          1.
        # 3     3        Inf  #beware of this case where you end up dividing by 0
        

        数据:

        tt <- "Subject    Value1    Value2    Value3      UniqueNumber
        001        1         0         1           3
        002        0         1         1           2
        003        1         1         1           1"
        
        dat <- read.table(text=tt, header=T)
        

        【讨论】:

        • 运行代码时收到以下错误:Error in 2:(n_cols + 1) : NA/NaN argument
        • 我没有这个错误,你在我的示例数据上试过了吗?如果您的“UniqueNumber”列名称不同,您需要相应地更改这部分x$UniqueNumber
        • 谢谢。我的数据缺少一列,因此代码崩溃了。回去解决了这个问题,它的工作原理!
        • 你能修改'mean'函数的分母,让它除以1吗?我需要包含第一个值(即 Value1),但这是一个起点。所以我想在每个实例中减一(同时仍然删除 NA)。
        • @statsguyz 是的,你可以,你可以在函数中做任何你想做的事情,只要改变 mean() 任何你喜欢的东西,我会用一个例子来更新。
        【解决方案5】:

        不是 tidyverse 的粉丝/专家,但我会尝试使用长格式。然后,只需按每个组的行索引进行过滤,然后在单个列上运行您想要的任何函数(这样更容易)。

        library(tidyr)
        library(dplyr)
        
        Data %>% 
          gather(variable, value, -Subject, -UniqueNumber) %>% # long format
          group_by(Subject) %>% # group by Subject in order to get row counts
          filter(row_number() <= UniqueNumber) %>% # filter by row index
          summarise(Mean = mean(value), Total = sum(value)) %>% # do the calculations
          ungroup() 
        
        ## A tibble: 3 x 3
        #  Subject  Mean Total
        #     <int> <dbl> <int>
        # 1       1 0.667     2
        # 2       2 0.5       1
        # 3       3 1         1
        

        实现这一点的一种非常相似的方法是通过列名中的整数进行过滤。过滤步骤出现在group_by 之前,因此它可能会提高性能(或不提高性能?),但它不太健壮,因为我假设感兴趣的列被称为"Value#"

        Data %>% 
          gather(variable, value, -Subject, -UniqueNumber) %>% #long format
          filter(as.numeric(gsub("Value", "", variable, fixed = TRUE)) <= UniqueNumber) %>% #filter
          group_by(Subject) %>% # group by Subject
          summarise(Mean = mean(value), Total = sum(value)) %>% # do the calculations
          ungroup()
        
        ## A tibble: 3 x 3
        #  Subject  Mean Total
        #     <int> <dbl> <int>
        # 1       1 0.667     2
        # 2       2 0.5       1
        # 3       3 1         1
        

        只是为了好玩,添加一个data.table解决方案

        library(data.table)
        
        data.table(Data) %>% 
          melt(id = c("Subject", "UniqueNumber")) %>%
          .[as.numeric(gsub("Value", "", variable, fixed = TRUE)) <= UniqueNumber,
            .(Mean = round(mean(value), 3), Total = sum(value)),
            by = Subject]
        
        #    Subject  Mean Total
        # 1:       1 0.667     2
        # 2:       2 0.500     1
        # 3:       3 1.000     1
        

        【讨论】:

        • 编辑:看起来很少有科目没有 UniqueValues。需要检查这个。一切正常!
        • 有没有办法修改它来处理缺失值?另外,是否可以在考虑缺失值的情况下用分母计算平均值?
        • 缺失值是什么意思? NAs 在Value 列中?只需将na.rm = TRUE 添加到函数中,例如summarise(Mean = mean(value, na.rm = TRUE), Total = sum(value, na.rm = TRUE))。不确定我是否理解您的第二个问题。能否请您展示一个具有所需输出的示例?
        • 哦,好吧,我就是这么想的。如果我想修改“Mean”列,将均值函数的分母修改为+1或-1,可以吗?
        • 我不确定我理解你的意思,但你可以这样做summarise(Total = sum(value, na.rm = TRUE), Mean = Total / n())
        【解决方案6】:

        检查这个解决方案:

        df %>%
          gather(key, val, Value1:Value3) %>%
          group_by(Subject) %>%
          mutate(
            Sum = sum(val[c(1:(UniqueNumber[1]))]),
            Mean = mean(val[c(1:(UniqueNumber[1]))]),
          ) %>%
          spread(key, val)
        

        输出:

         Subject UniqueNumber   Sum  Mean Value1 Value2 Value3
          <chr>          <int> <dbl> <dbl>  <dbl>  <dbl>  <dbl>
        1 001                3     2 0.667      1      0      1
        2 002                2     1 0.5        0      1      1
        3 003                1     1 1          1      1      1
        

        【讨论】:

        • 这究竟如何给出正确的结果?当我将随机 NA 插入数据时,这给了我错误的结果。例如,尝试将NA 插入Value1 的第一行。
        猜你喜欢
        • 1970-01-01
        • 2023-03-30
        • 1970-01-01
        • 2020-10-01
        • 1970-01-01
        • 1970-01-01
        • 2022-08-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多