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

时间:2018-12-16 22:28:05

标签: r tidyr

这是所有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 

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

2 个答案:

答案 0 :(得分:0)

可能对于您想要的内容来说太重复了,但是当处理大量变量时,可以使用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   

答案 1 :(得分:0)

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

您可以使用一些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"))

关于保持因子水平,很遗憾,您需要在之后将其强制退回。围绕它的可能方法还有其他疑问; here's one

还值得注意的是,tidyr github在完成实现multi_gather类型的功能的工作中备有多个issues备案,很可能适用于像您这样的用例。不确定是否可以涵盖因子转换。