【问题标题】:Using tidyverse gather() to output multiple value vectors with a single key in a data frame使用 tidyverse gather() 在数据帧中输出具有单个键的多个值向量
【发布时间】:2017-06-22 19:53:52
【问题描述】:

尽管有 R 的约定,但对我来说,数据收集和输入最容易在垂直列中完成。因此,我有一个关于使用 tidyverse 库中的 gather() 函数有效转换为水平行的问题。我发现自己一遍又一遍地使用 collect() 似乎效率低下。有没有更有效的方法?现有的向量可以作为关键吗?这是一个例子:

假设我们有以下关于幼鸟的健康指标。

    bird day_1_mass day_2_mass day_1_heart_rate day_3_heart_rate
1    1          5          6               60               55
2    2          6          8               62               57
3    3          3          3               45               45

使用收集功能,我可以将海量数据重新组织成行。

horizontal.data <- gather(vertical.data,
                      key = age, 
                      value = mass, 
                      day_1_mass:day_2_mass, 
                      factor_key=TRUE)

给我们

    bird day_1_heart_rate day_3_heart_rate        age mass
1    1               60               55 day_1_mass    5
2    2               62               57 day_1_mass    6
3    3               45               45 day_1_mass    3
4    1               60               55 day_2_mass    6
5    2               62               57 day_2_mass    8
6    3               45               45 day_2_mass    3

并再次使用相同的函数来类似地重组心率数据。

horizontal.data.2 <- gather(horizontal.data, 
                            key = age2, 
                            value = heart_rate, 
                            day_1_heart_rate:day_3_heart_rate, 
                            factor_key=TRUE)

产生一个新的数据帧

     bird        age mass             age2 heart_rate
1     1 day_1_mass    5 day_1_heart_rate         60
2     2 day_1_mass    6 day_1_heart_rate         62
3     3 day_1_mass    3 day_1_heart_rate         45
4     1 day_2_mass    6 day_1_heart_rate         60
5     2 day_2_mass    8 day_1_heart_rate         62
6     3 day_2_mass    3 day_1_heart_rate         45
7     1 day_1_mass    5 day_3_heart_rate         55
8     2 day_1_mass    6 day_3_heart_rate         57
9     3 day_1_mass    3 day_3_heart_rate         45
10    1 day_2_mass    6 day_3_heart_rate         55
11    2 day_2_mass    8 day_3_heart_rate         57
12    3 day_2_mass    3 day_3_heart_rate         45

所以它采取了两个步骤,但它奏效了。问题是 1) 有没有办法一步完成? 2) 是否可以用一个键(“年龄”向量)来完成,然后我可以简单地将其替换为数字数据?

【问题讨论】:

  • 你为什么不直接使用管道函数来结合这两个步骤。我认为没有办法将您想要的操作包含在一个 gather() 函数中 >> gather(vertical.data, key = age, value = mass, day_1_mass:day_2_mass, factor_key=TRUE) %&gt;% gather(key = age2, value = heart_rate, day_1_heart_rate:day_3_heart_rate, factor_key=TRUE)

标签: r tidyverse


【解决方案1】:

如果我的问题是正确的,您可以先将所有内容收集在一起,然后“传播”质量和心率:

library(forcats)
library(dplyr)

mass_levs       <- names(vertical.data)[grep("mass", names(vertical.data))]
hearth_levs     <- names(vertical.data)[grep("heart", names(vertical.data))]
horizontal.data <- vertical.data %>% 
                     gather(variable, value, -bird,  factor_key = TRUE) %>% 
                     mutate(day = stringr::str_sub(variable, 5,5)) %>% 
                     mutate(variable = fct_collapse(variable,  
                                                    "mass" = mass_levs, 
                                                    "hearth_rate" = hearth_levs)) %>% 
                     spread(variable, value)

,给予:

  bird day mass hearth_rate
1    1   1    5          60
2    1   2    6          NA
3    1   3   NA          55
4    2   1    6          62
5    2   2    8          NA
6    2   3   NA          57
7    3   1    3          45
8    3   2    3          NA
9    3   3   NA          45

我们可以通过一次通过管道来了解它是如何工作的。 首先,我们以长格式收集所有内容:

horizontal.data <- vertical.data %>% 
                     gather(variable, value, -bird,  factor_key = TRUE)

  bird         variable value
1     1       day_1_mass     5
2     2       day_1_mass     6
3     3       day_1_mass     3
4     1       day_2_mass     6
5     2       day_2_mass     8
6     3       day_2_mass     3
7     1 day_1_heart_rate    60
8     2 day_1_heart_rate    62
9     3 day_1_heart_rate    45
10    1 day_3_heart_rate    55
11    2 day_3_heart_rate    57
12    3 day_3_heart_rate    45

然后,如果我们想保留一个“适当的”长表,正如 OP 建议的那样,我们必须创建一个 key 变量。在这种情况下,使用日期(= 年龄)是有意义的。要创建day变量,我们可以从variable中的字符串中提取它:

%>% mutate(day = stringr::str_sub(variable, 5,5))

在这里,str_sub 获取位置 5 中的子字符串,即日期(请注意,如果在完整数据集中您有多位数的天数,则必须稍微调整一下,可能通过拆分 _) :

   bird         variable value day
1     1       day_1_mass     5   1
2     2       day_1_mass     6   1
3     3       day_1_mass     3   1
4     1       day_2_mass     6   2
5     2       day_2_mass     8   2
6     3       day_2_mass     3   2
7     1 day_1_heart_rate    60   1
8     2 day_1_heart_rate    62   1
9     3 day_1_heart_rate    45   1
10    1 day_3_heart_rate    55   3
11    2 day_3_heart_rate    57   3
12    3 day_3_heart_rate    45   3

现在,为了完成,我们必须“展开”表格以拥有 massheart rate 列。

这里我们遇到了一个问题,因为目前variable 列中有 2 个级别,每个级别对应于质量和炉膛速率。因此,在variable 上应用spread 将再次给我们四列。

为了防止这种情况,我们需要将variable 中的四个级别聚合为两个级别。我们可以通过使用forcats::fc_collapse 来做到这一点,方法是提供新关卡名称和“旧”关卡名称之间的关联。在管道之外,这将对应于:

horizontal.data$variable <- fct_collapse(horizontal.data$variable, 
                                mass = c("day_1_mass", "day_2_mass",
                                heart = c("day_1_hearth_rate", "day_3_heart_rate")

但是,如果你有很多关卡,把它们都写出来很麻烦。因此,我事先使用

找到了对应于两个“类别”的级别名称
mass_levs       <- names(vertical.data)[grep("mass", names(vertical.data))]
hearth_levs     <- names(vertical.data)[grep("heart", names(vertical.data))]

mass_levs
[1] "day_1_mass" "day_2_mass"
壁炉架
[1] "day_1_heart_rate" "day_3_heart_rate"

因此,管道的第三行可以缩短为:

%>% mutate(variable = fct_collapse(variable,  
                              "mass" = mass_levs, 
                              "hearth_rate" = hearth_levs))

,之后我们有:

  bird    variable value day
1     1        mass     5   1
2     2        mass     6   1
3     3        mass     3   1
4     1        mass     6   2
5     2        mass     8   2
6     3        mass     3   2
7     1 hearth_rate    60   1
8     2 hearth_rate    62   1
9     3 hearth_rate    45   1
10    1 hearth_rate    55   3
11    2 hearth_rate    57   3
12    3 hearth_rate    45   3

,所以我们现在处于根据variable再次“传播”表格的条件,使用:

%>% spread(variable, value)

  bird day mass hearth_rate
1    1   1    5          60
2    1   2    6          NA
3    1   3   NA          55
4    2   1    6          62
5    2   2    8          NA
6    2   3   NA          57
7    3   1    3          45
8    3   2    3          NA
9    3   3   NA          45

HTH

【讨论】:

  • 是的,它工作得很好,而且是一个优雅的脚本。如果您可以稍微注释一下,特别是您在 mutate 中使用 stringr(?) 和 fct_collapse 所做的事情,这将有助于更好地“教人钓鱼”。
  • 好的。不,应该更清楚。感谢您的建议
  • 很好的解释!
【解决方案2】:

如果你坚持一个命令,我可以给你一个

设置data.frame

c1<-c(1,2,3)
c2<-c(5,6,3)
c3<-c(6,8,3)
c4<-c(60,62,45)
c5<-c(55,57,45)
dt<-as.data.table(cbind(c1,c2,c3,c4,c5))
colnames(dt)<-c("bird","day_1_mass","day_2_mass","day_1_heart_rate","day_3_heart_rate")

现在使用这个命令来获得最终结果

merge(melt(dt[,c("bird","day_1_mass","day_2_mass")],id.vars = c("bird"),variable.name = "age",value.name="mass"),melt(dt[,c("bird","day_1_heart_rate","day_3_heart_rate")],id.vars = c("bird"),variable.name = "age2",value.name="heart_rate"),by = "bird")

最终结果是

     bird        age     mass             age2 heart_rate
 1:    1 day_1_mass         5 day_1_heart_rate         60
 2:    1 day_1_mass         5 day_3_heart_rate         55
 3:    1 day_2_mass         6 day_1_heart_rate         60
 4:    1 day_2_mass         6 day_3_heart_rate         55
 5:    2 day_1_mass         6 day_1_heart_rate         62
 6:    2 day_1_mass         6 day_3_heart_rate         57
 7:    2 day_2_mass         8 day_1_heart_rate         62
 8:    2 day_2_mass         8 day_3_heart_rate         57
 9:    3 day_1_mass         3 day_1_heart_rate         45
10:    3 day_1_mass         3 day_3_heart_rate         45
11:    3 day_2_mass         3 day_1_heart_rate         45
12:    3 day_2_mass         3 day_3_heart_rate         45

【讨论】:

    【解决方案3】:

    虽然已经回答了,但我有一个不同的解决方案,您可以保存要运行的收集参数列表,然后为列表中的每组参数运行gather_() 命令。

    # Create a list of gather parameters
    # Format is key, value, columns_to_gather
    gather.list <- list(c("age", "mass", "day_1_mass", "day_2_mass"),
                        c("age2", "heart_rate", "day_1_heart_rate", "day_3_heart_rate"))
    
    # Run gather command for each list item
    for(i in gather.list){
      df <- gather_(df, key_col = i[1], value_col = i[2], gather_cols = c(i[3:length(i)]), factor_key = TRUE)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-18
      • 1970-01-01
      • 2018-06-24
      • 2018-10-17
      • 2020-11-12
      • 2018-04-08
      • 2022-09-29
      • 1970-01-01
      相关资源
      最近更新 更多