使用dplyr根据列值对R中的数值进行求和

10

我有一个数据集,其中包含以下信息:

Subject    Value1    Value2    Value3      UniqueNumber
001        1         0         1           3
002        0         1         1           2
003        1         1         1           1
如果UniqueNumber的值大于0,我想使用dplyr为每个受试者从第1行到UniqueNumber的值求和并计算平均值。因此,对于Subject 001来说,sum = 2,mean = .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'中的值。


2
你可以尝试使用 df %>% mutate(sum = ifelse(UniqueNumber > 0, rowSums(.[, 2:(length(.)-1)]), NA), mean = ifelse(UniqueNumber > 0, rowMeans(.[, 2:(length(.)-1)]), NA)) - tmfmnk
@tmfmnk 我认为你的代码不会按照UniqueNumber的长度进行迭代。看起来我的结果是在整个列上求和,而不是停在UniqueValue列的值处。 - statsguyz
6个回答

11

虽然我不是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。需要检查一下。一切都运行良好! - statsguyz
有没有办法修改这个程序来处理缺失值?另外,是否可以计算平均值,并考虑到缺失值的分母? - statsguyz
什么是缺失值?“Value”列中的“NA”吗?只需在函数中添加na.rm = TRUE,例如summarise(Mean = mean(value, na.rm = TRUE), Total = sum(value, na.rm = TRUE))。我不确定我是否理解您的第二个问题。您能否举一个带有期望输出的示例? - David Arenburg
哦,好的,这正是我想到的。如果我想修改“Mean”列以通过+1或-1修改平均函数的分母,这是否可能? - statsguyz
我不确定我理解你的意思,但你可以使用 summarise(Total = sum(value, na.rm = TRUE), Mean = Total / n()) - David Arenburg

3

这里有另一种方法,它使用tidyr::nestValues列收集到一个列表中,以便我们可以使用map2迭代整个表格。在每一行中,我们从Values列表列中选择正确的值,并分别取和或平均值。

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-02-14 创建的。


2
检查这个解决方案:

最初的回答


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

3
这个方法如何确保正确的结果?当我在数据中随意插入NA时,它给我错误的结果。例如,在第一行中的Value1插入NA - David Arenburg

2

如果只考虑使用 dplyr 解决方案,OP 可能会对以下这个基于 mapply 的 R 原始答案不感兴趣。但是为了比较和未来读者的参考,我们提供此解决方案:

Original Answer(最初的回答):

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子集,然后计算它的summean,如果UniqueNumber值大于0,则返回其值,否则仅返回0。


1

一种使用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)

当我运行你的代码时,收到以下错误:2:(n_cols + 1) 中的错误:NA/NaN 参数 - statsguyz
我没有这个错误,你是否尝试在我的示例数据上运行?如果你的“UniqueNumber”列命名不同,你需要相应地更改这部分 x$UniqueNumber - RLave
1
谢谢。我的数据缺少一列,所以代码崩溃了。回去修复了这个问题,现在它可以工作了! - statsguyz
你能否修改“平均”函数的分母,使其除以1减少?我需要包括第一个值(即Value1),但它是一个起点。因此,我希望在每个实例中都减少一个(同时仍然删除NAs)。 - statsguyz
@statsguyz 是的,你可以,在函数内部做任何你想做的事情,只需用你喜欢的任何东西替换mean(),我会提供一个示例更新。 - RLave

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")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接