【问题标题】:Tidyverse: Reduce variables by groupTidyverse:按组减少变量
【发布时间】:2022-01-06 12:50:06
【问题描述】:

我有一个如下所示的数据框:

ID  pick1      pick2     pick3
1   NA         21/11/29  21/11/30
2   21/11/28   21/11/29  NA
3   21/11/28   NA        21/11/30   
4   NA         21/11/29  21/11/30

每个参与者 (ID) 可以从 3 个选项中选择 2 个日期。现在我想总结选定的日期以获得这样的小标题:

ID  date1      date2
1   21/11/29   21/11/30
2   21/11/28   21/11/29
3   21/11/28   21/11/30   
4   21/11/29   21/11/30

但是,我无法仅使用 tidyverse 函数使其工作。我已经开始使用这个库,但在网上找不到我的问题的解决方案

【问题讨论】:

  • 如果您绝对确定只需将 3 个选项减少到 2 个,那么只需使用 dplyr 并执行 my_data %>% mutate(date1 = coalesce(pick2, pick1), date2 = coalesce(pick3, pick2)) %>% select(!starts_with("pick"))
  • 实际上,30 天中有 10 天。我使用了一个简化版本以使其尽可能简单。
  • “这是 30 天中的 10 天” 在这种情况下,您可能应该选择以下一种旋转解决方案。

标签: r tidyverse


【解决方案1】:

一个选项是rowwise - 按行分组,将sortna.last 设为TRUE,将排序后的输出保存在listunnest 到多个列中,并且select 仅列至少有一个非NA元素

library(dplyr)
library(tidyr)
library(stringr)
 df1 %>% 
   rowwise %>% 
   transmute(ID, date = list(sort(c_across(starts_with('pick')), 
       na.last = TRUE))) %>% 
   ungroup %>%
   unnest_wider(date) %>%
   rename_with(~ str_c('date', seq_along(.)), -ID) %>%
   select(where(~ any(!is.na(.))))

-输出

# A tibble: 4 × 3
     ID date1    date2   
  <int> <chr>    <chr>   
1     1 21/11/29 21/11/30
2     2 21/11/28 21/11/29
3     3 21/11/28 21/11/30
4     4 21/11/29 21/11/30

或使用pivot_longer 重新整形为“长”格式删除NAs 并将其重新整形为“宽”格式

library(stringr)
df1 %>% 
   pivot_longer(cols = -ID, values_drop_na = TRUE) %>%
   group_by(ID) %>% 
   mutate(name = str_c('date', row_number())) %>%
   ungroup %>% 
   pivot_wider(names_from = name, values_from = value)

-输出

# A tibble: 4 × 3
     ID date1    date2   
  <int> <chr>    <chr>   
1     1 21/11/29 21/11/30
2     2 21/11/28 21/11/29
3     3 21/11/28 21/11/30
4     4 21/11/29 21/11/30

数据

df1 <- structure(list(ID = 1:4, pick1 = c(NA, "21/11/28", "21/11/28", 
NA), pick2 = c("21/11/29", "21/11/29", NA, "21/11/29"), pick3 = c("21/11/30", 
NA, "21/11/30", "21/11/30")), class = "data.frame",
 row.names = c(NA, 
-4L))

【讨论】:

  • 感谢您的回复!即使它似乎有效(明天会检查),我想等待是否有人提出简化的解决方案。我希望有一种更直观的方法,将来更容易复制。
  • @diggi2395 显然还有更简单的方法。当您指定 tidyverse 时,我更多地考虑使用 tidyverse 函数,在某些情况下会采取更多步骤
  • 非常感谢您的编辑,第二个选项对我来说更容易理解 :)
【解决方案2】:

基于tidyr::unitetidyr::separate的解决方案:

library(tidyverse)

df <- data.frame(
  stringsAsFactors = FALSE,
  ID = c(1L, 2L, 3L, 4L),
  pick1 = c(NA, "21/11/28", "21/11/28", NA),
  pick2 = c("21/11/29", "21/11/29", NA, "21/11/29"),
  pick3 = c("21/11/30", NA, "21/11/30", "21/11/30")
)

df %>% 
  unite(date, sep=",", na.rm=T) %>% 
  separate(date, into = c("ID", str_c("date", 1:2)), sep = ",", convert = T)

#>   ID    date1    date2
#> 1  1 21/11/29 21/11/30
#> 2  2 21/11/28 21/11/29
#> 3  3 21/11/28 21/11/30
#> 4  4 21/11/29 21/11/30

另一个解决方案:

library(tidyverse)

df %>% 
  pivot_longer(cols = str_c("pick",1:3), values_drop_na = T) %>% 
  mutate(name = rep(c("date1","date2"), n()/2)) %>% 
  pivot_wider(ID)

#> # A tibble: 4 × 3
#>      ID date1    date2   
#>   <int> <chr>    <chr>   
#> 1     1 21/11/29 21/11/30
#> 2     2 21/11/28 21/11/29
#> 3     3 21/11/28 21/11/30
#> 4     4 21/11/29 21/11/30

还有tidyr::unnest_wider:

library(tidyverse)

df %>% 
  pivot_longer(cols = str_c("pick",1:3),values_drop_na = T) %>% 
  mutate(name = "date") %>% 
  pivot_wider(ID, values_fn = list) %>% 
  unnest_wider(col="date", names_sep = "")

#> # A tibble: 4 × 3
#>      ID date1    date2   
#>   <int> <chr>    <chr>   
#> 1     1 21/11/29 21/11/30
#> 2     2 21/11/28 21/11/29
#> 3     3 21/11/28 21/11/30
#> 4     4 21/11/29 21/11/30

【讨论】:

  • 非常感谢您提供各种解决方案!非常简单!
  • 不客气,@diggi2395!
【解决方案3】:

您可以从@akrun 对data.table 的回答中执行长轴然后回到宽方法。语法更简洁一些

df1 <- structure(list(ID = 1:4, pick1 = c(NA, "21/11/28", "21/11/28", 
NA), pick2 = c("21/11/29", "21/11/29", NA, "21/11/29"), pick3 = c("21/11/30", 
NA, "21/11/30", "21/11/30")), class = "data.frame",
 row.names = c(NA, 
-4L))

library(data.table)
setDT(df1)

dcast(
  melt(df1, 'ID', na.rm = TRUE),
  ID ~ paste0('date', rowid(ID)))
#>    ID    date1    date2
#> 1:  1 21/11/29 21/11/30
#> 2:  2 21/11/28 21/11/29
#> 3:  3 21/11/28 21/11/30
#> 4:  4 21/11/29 21/11/30

reprex package (v2.0.1) 于 2021 年 11 月 29 日创建

【讨论】:

  • 非常优雅!请务必使用paste0( 'date' , rowid(ID)),以匹配 OP 所需输出中的列名。
  • 谢谢@Greg,我已经编辑了答案
【解决方案4】:

基础 R 呢?

df <- read.table(text = "ID  pick1      pick2     pick3
1   NA         21/11/29  21/11/30
2   21/11/28   21/11/29  NA
3   21/11/28   NA        21/11/30   
4   NA         21/11/29  21/11/30", header = TRUE)

data.frame(t(apply(df, 1, function(x) x[!is.na(x)])))
#>   X1       X2       X3
#> 1  1 21/11/29 21/11/30
#> 2  2 21/11/28 21/11/29
#> 3  3 21/11/28 21/11/30
#> 4  4 21/11/29 21/11/30

reprex package (v2.0.1) 于 2021 年 11 月 29 日创建

【讨论】:

  • 感谢 tjebo,如果没有 dplyr,我会这样做。然而,我越来越多地使用这个库,这就是为什么我想提高我对这个库的技能。
猜你喜欢
  • 2021-03-19
  • 2021-05-15
  • 1970-01-01
  • 2023-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-18
  • 1970-01-01
相关资源
最近更新 更多