【问题标题】:Conditional summing across columns with dplyr使用 dplyr 跨列条件求和
【发布时间】:2018-04-13 01:41:27
【问题描述】:

我有一个数据框,其中包含八个月内对四个栖息地进行采样的数据。每个月从每个栖息地收集十个样本。计算每个样本中物种的个体数量。下面的代码生成一个类似结构的更小的数据框。

# Pseudo data
Habitat <- factor(c(rep("Dry",6), rep("Wet",6)), levels = c("Dry","Wet"))
Month <- factor(rep(c(rep("Jan",2), rep("Feb",2), rep("Mar",2)),2), levels=c("Jan","Feb","Mar"))
Sample <- rep(c(1,2),6)
Species1 <- rpois(12,6)
Species2 <- rpois(12,6)
Species3 <- rpois(12,6)

df <- data.frame(Habitat,Month, Sample, Species1, Species2, Species3)

我想按月汇总所有采样物种的个体总数。我正在使用ddply(首选),但我愿意接受其他建议。

我得到的最接近的是将每列的总和相加,如下所示。

library(plyr)
ddply(df, ~ Month, summarize, tot_by_mon = sum(Species1) + sum(Species2) + sum(Species3))

#   Month tot_by_mon
# 1   Jan         84
# 2   Feb         92
# 3   Mar         67

这行得通,但我想知道是否有一种通用方法来处理“未知”物种数量的病例。也就是说,第一个物种总是从第 4 列开始,但最后一个物种可能在第 10 列或第 42 列。我不想将实际物种名称硬编码到摘要函数中。请注意,物种名称差异很大,例如 Doryflav 和 Pheibica。

【问题讨论】:

  • 返回单列矩阵的基本 R 解决方案是 rowsum(rowSums(df[3:6]), df$Month)

标签: r dataframe dplyr plyr summarize


【解决方案1】:

假设Speciess 列都以Species 开头,您可以通过前缀选择它们并使用group_by %&gt;% do 求和:

library(tidyverse)
df %>% 
    group_by(Month) %>% 
    do(tot_by_mon = sum(select(., starts_with('Species')))) %>% 
    unnest()

# A tibble: 3 x 2
#   Month tot_by_mon
#  <fctr>      <int>
#1    Jan         63
#2    Feb         67
#3    Mar         58

如果列名不遵循模式,您可以按列位置进行选择,例如,如果 Species 列从第 4 列到数据框的末尾:

df %>% 
    group_by(Month) %>% 
    do(tot_by_mon = sum(select(., 4:ncol(.)))) %>% 
    unnest()

# A tibble: 3 x 2
#   Month tot_by_mon
#  <fctr>      <int>
#1    Jan         63
#2    Feb         67
#3    Mar         58

【讨论】:

  • 或者使用tidyr来避免do():gather(df, Species, Value, matches("Species")) %&gt;% group_by(Month) %&gt;% summarise(z = sum(Value))
  • 不幸的是,正如我在问题的最后一句中指出的那样,物种的名称不符合方便的模式。
  • @Mike See ?dplyr::select -- 有很多选项可以选择您想要的列。例如,gather(df, Species, Value, -(1:3)) %&gt;% group_by(Month) %&gt;% summarise(z = sum(Value))
  • 您也可以按列位置选择,请参阅更新。或者使用负数排除列作为@Frank 的评论。
  • @Frank 我喜欢gather 方法。你会考虑把你的评论变成答案吗?
【解决方案2】:

这是另一个使用 data.table 的解决方案,无需知道“物种”列的名称:

library(data.table)

DT = melt(setDT(df), id.vars = c("Habitat", "Month", "Sample"))    
DT[, .(tot_by_mon=sum(value)), by = "Month"]

或者如果你想要它紧凑,这里有一个单行:

melt(setDT(df), 1:3)[, .(tot_by_mon=sum(value)), by = "Month"]

结果:

   Month tot_by_mon
1:   Jan         90
2:   Feb         81
3:   Mar         70

数据:(设置种子以使示例可重现)

set.seed(123)
Habitat <- factor(c(rep("Dry",6), rep("Wet",6)), levels = c("Dry","Wet"))
Month <- factor(rep(c(rep("Jan",2), rep("Feb",2), rep("Mar",2)),2), levels=c("Jan","Feb","Mar"))
Sample <- rep(c(1,2),6)
Species1 <- rpois(12,6)
Species2 <- rpois(12,6)
Species3 <- rpois(12,6)

df <- data.frame(Habitat,Month, Sample, Species1, Species2, Species3)

【讨论】:

    【解决方案3】:

    这是data.table 的另一个选项,无需重新整形为“长”格式

    library(data.table)
    setDT(df)[, .(tot_by_mon = Reduce(`+`, lapply(.SD, sum))), Month,
              .SDcols = Species1:Species3]
    #   Month tot_by_mon
    #1:   Jan         90
    #2:   Feb         81
    #3:   Mar         70
    

    或者使用tidyverse,我们也可以使用map函数,这样会很高效

    library(dplyr)
    library(purrr)
    df %>% 
      group_by(Month) %>%
      nest(starts_with('Species')) %>%
      mutate(tot_by_mon = map_int(data, ~sum(unlist(.x)))) %>% 
      select(-data)
    # A tibble: 3 x 2
    #    Month tot_by_mon
    #   <fctr>      <int>
    #1    Jan         90
    #2    Feb         81
    #3    Mar         70
    

    数据

    set.seed(123)
    Habitat <- factor(c(rep("Dry",6), rep("Wet",6)), levels = c("Dry","Wet"))
    Month <- factor(rep(c(rep("Jan",2), rep("Feb",2), rep("Mar",2)),2),
                            levels=c("Jan","Feb","Mar"))
    Sample <- rep(c(1,2),6)
    Species1 <- rpois(12,6)
    Species2 <- rpois(12,6)
    Species3 <- rpois(12,6)
    
    df <- data.frame(Habitat,Month, Sample, Species1, Species2, Species3)
    

    【讨论】:

      【解决方案4】:

      类似于@user 对data.table 的melt 的回答,您可以使用tidyr 与gather 一起重塑:

      library(tidyr)
      library(dplyr)
      gather(df, Species, Value, matches("Species")) %>% 
        group_by(Month) %>% summarise(z = sum(Value))
      
      # A tibble: 3 x 2
         Month     z
        <fctr> <int>
      1    Jan    90
      2    Feb    81
      3    Mar    70
      

      如果您按位置而不是“匹配”模式知道列...

      gather(df, Species, Value, -(1:3)) %>% 
        group_by(Month) %>% summarise(z = sum(Value))
      

      (使用@akrun 的set.seed(123) 示例数据显示的结果。)

      【讨论】:

      • 我尝试了 Psidom 的 gather(df, Species, Value, 4:ncol(.)) %&gt;% group_by(Month) %&gt;% summarise(z = sum(Value)) 方法,它通过安装额外的软件包“修复”了一个错误:github.com/tidyverse/tidyr/issues/350#issuecomment-326920323
      • 我在您的解决方案中遇到了一个“错误”,直到我了解到 tidyverse (inc. tidyr) 和 plyr 包之间的冲突。在没有意识到冲突的情况下,我在tidyverse 之后加载了plyrGather 返回单个总值,而不是按栖息地分组。一些阅读揭示了这个问题。我在其他地方做了一些更改,我当前的项目不再需要plyr。您的解决方案如宣传的那样工作。谢谢!
      猜你喜欢
      • 2015-05-06
      • 1970-01-01
      • 1970-01-01
      • 2019-12-11
      • 2022-12-15
      • 2021-09-20
      • 1970-01-01
      • 2021-09-28
      相关资源
      最近更新 更多