繁体   English   中英

使用Tidyverse同时收集不同类别的多个变量

[英]Gather multiple variables of different classes at the same time using Tidyverse

这是所有Tidyverse专家都面临的一个问题。 我有一个包含许多不同类(日期时间,整数,因子等)的数据集,并希望使用tidyr同时收集多个变量 在下面的可复制示例中,我想一次收集time_,factor_和integer_,而id和gender保持不变。

我正在寻找使用任何Tidyverse函数的当前最佳实践解决方案。

(我希望解决方案不是太“ hacky”,因为我有一个包含数十个不同关键变量和大约五十万行的数据集)。

示例数据:

library("tidyverse")
data <- tibble(
  id = c(1, 2, 3),
  gender = factor(c("Male", "Female", "Female")),
  time1 = as.POSIXct(c("2014-03-03 20:19:42", "2014-03-03 21:53:17", "2014-02-21 12:13:06")),
  time2 = as.POSIXct(c("2014-05-28 15:26:49 UTC", NA, "2014-05-24 10:53:01 UTC")),
  time3 = as.POSIXct(c(NA, "2014-09-26 00:52:40 UTC", "2014-09-27 07:08:47 UTC")),
  factor1 = factor(c("A", "B", "C")),
  factor2 = factor(c("B", NA, "C")),
  factor3 = factor(c(NA, "A", "B")),
  integer1 = c(1, 3, 2),
  integer2 = c(1, NA, 4),
  integer3 = c(NA, 5, 2)
)

期望的结果:

# A tibble: 9 x 5
     id gender Time                Integer Factor
  <dbl> <fct>  <dttm>                <dbl> <fct> 
1     1 Male   2014-03-03 20:19:42       1 A     
2     2 Female 2014-03-03 21:53:17       3 B     
3     3 Female 2014-02-21 12:13:06       2 C     
4     1 Male   2014-05-28 15:26:49       1 B     
5     2 Female NA                       NA NA    
6     3 Female 2014-05-24 10:53:01       4 C     
7     1 Male   NA                       NA NA    
8     2 Female 2014-09-26 00:52:40       5 A     
9     3 Female 2014-09-27 07:08:47       2 B 

PS我确实找到了几个线程,这些线程从头开始收集多个变量,但是没有一个线程处理收集不同类的问题并描述了Tidyverse解决方案的当前状态。

可能对于您想要的内容来说太重复了,但是当处理大量变量时,可以使用mutate_at在最后重新编码多个变量

一开始将它们全部更改为字符会保留time数据,然后需要在结束时将其转换回日期时间

 data %>% 
  mutate_all(funs(as.character)) %>%
  gather(key = variable, value = value, -id, -gender, convert = T) %>%
  mutate(wave = readr::parse_number(variable),
         variable = gsub("\\d","", x = variable)) %>% 
  spread(variable, value, convert = T) %>%
  mutate(time = as.POSIXct(time),
         factor = factor(factor),
         gender = factor(gender)) %>%
  select(1, 2, 6, 5, 4)

 # A tibble: 9 x 5
  id    gender time                integer factor
  <chr> <fct>  <dttm>                <int> <fct> 
1 1     Male   2014-03-03 20:19:42       1 A     
2 1     Male   2014-05-28 15:26:49       1 B     
3 1     Male   NA                       NA NA    
4 2     Female 2014-03-03 21:53:17       3 B     
5 2     Female NA                       NA NA    
6 2     Female 2014-09-26 00:52:40       5 A     
7 3     Female 2014-02-21 12:13:06       2 C     
8 3     Female 2014-05-24 10:53:01       4 C     
9 3     Female 2014-09-27 07:08:47       2 B   

(我基本上重写了我以前的所有答案,但保留了此帖子以保留评论。)

您可以使用一些tidyselect辅助函数,即starts_with ,选择要收集的一批列,然后删除多余的列。 这可以通过收集来处理(某些)数据类型的问题,因为您是一起收集同一类型的列集,但是由于收集时存在不同的因子级别,因此仍然需要将Factor重新强制为一个因子(请参见警告消息)。

我很难理解的是,在保持ID和性别列的某种模式的同时,收集的列将如何“移动”。 进行一系列的gather调用不会保持您想要的模式,但是您可以执行每个 gather调用并将它们重新组合在一起。

这是一个:

library(tidyverse)

data %>%
  select(id, gender, starts_with("time")) %>%
  gather(key = key_time, value = Time, starts_with("time"))
#> # A tibble: 9 x 4
#>      id gender key_time Time               
#>   <dbl> <fct>  <chr>    <dttm>             
#> 1     1 Male   time1    2014-03-03 20:19:42
#> 2     2 Female time1    2014-03-03 21:53:17
#> 3     3 Female time1    2014-02-21 12:13:06
#> 4     1 Male   time2    2014-05-28 15:26:49
#> 5     2 Female time2    NA                 
#> 6     3 Female time2    2014-05-24 10:53:01
#> 7     1 Male   time3    NA                 
#> 8     2 Female time3    2014-09-26 00:52:40
#> 9     3 Female time3    2014-09-27 07:08:47

要完成所有这些操作,您可以映射前缀-“时间”,“因数”和“整数”,并将它们归约连接在一起。 诀窍是您需要为每行添加一些唯一的标识符,以便正确连接。 为此,我添加了具有row_number的列,将其用作连接列,然后将其删除。

map(c("time", "factor", "integer"), function(p) {
  val_name <- str_to_title(p)
  data %>%
    select(id, gender, starts_with(p)) %>%
    gather(key = key, value = !!val_name, starts_with(p)) %>%
    select(-key) %>%
    mutate(row = row_number())
}) %>%
  reduce(left_join) %>%
  select(-row)
#> Warning: attributes are not identical across measure variables;
#> they will be dropped
#> Joining, by = c("id", "gender", "row")
#> Joining, by = c("id", "gender", "row")
#> # A tibble: 9 x 5
#>      id gender Time                Factor Integer
#>   <dbl> <fct>  <dttm>              <chr>    <dbl>
#> 1     1 Male   2014-03-03 20:19:42 A            1
#> 2     2 Female 2014-03-03 21:53:17 B            3
#> 3     3 Female 2014-02-21 12:13:06 C            2
#> 4     1 Male   2014-05-28 15:26:49 B            1
#> 5     2 Female NA                  <NA>        NA
#> 6     3 Female 2014-05-24 10:53:01 C            4
#> 7     1 Male   NA                  <NA>        NA
#> 8     2 Female 2014-09-26 00:52:40 A            5
#> 9     3 Female 2014-09-27 07:08:47 B            2

这有点丑陋,无法很好地适应正在进行的管道工作流程,但是您可以轻松地将其包装到函数中:

gather_by_prefix <- function(.data, prefix) {
  map(prefix, function(p) {
    val_name <- str_to_title(p)
    data %>%
      select(id, gender, starts_with(p)) %>%
      gather(key = key, value = !!val_name, starts_with(p)) %>%
      select(-key) %>%
      mutate(row = row_number())
  }) %>%
    reduce(left_join) %>%
    select(-row)
}

这样调用即可获得与上述相同的输出:

data %>%
  gather_by_prefix(c("time", "factor", "integer"))

至于保持因子水平,我认为很遗憾,之后需要将其强制退回。 围绕它的可能方法还有其他疑问; 这是一个

值得注意的是, tidyr github在实现multi_gather函数类型的工作方面存在一些问题 ,很可能适用于像您这样的用例。 不知道这些内容是否涵盖了因子转换。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM