Tidyverse:按组减少变量

5

我有一个数据框,看起来像这样:

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函数无法使其正常工作。我已经开始使用这个库,但在网上找不到解决我的问题的方法。


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

5
你可以使用data.table从@akrun的回答中进行长表格转宽表格的操作。语法更加简洁。
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包(v2.0.1)于2021-11-29创建


非常优雅!只需确保使用 paste0( 'date' , rowid(ID)),以匹配 OP 所需的输出列名。 - Greg
1
谢谢@Greg,我已经编辑了答案。 - IceCreamToucan

4

一种选项是使用 rowwise - 按行分组,使用 na.last 为 TRUE 进行 sort,将排序后的输出保留在 list 中,对其进行 unnest 操作以展开为多列,并选择仅具有至少一个非-NA元素的列进行 select

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

或者使用将数据转换为“长”格式,删除值后再将其转换为“宽”格式。
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
@diggi2395 显然有更简单的方法。由于您指定了tidyverse,我更倾向于使用tidyverse函数,在某些情况下需要更多步骤。 - akrun
1
非常感谢您的编辑,第二个选项对我来说更容易理解 :) - diggi2395

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

1
非常感谢您提供这么多的解决方案!非常简单易懂! - diggi2395

2
“base R” 是指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

创建于2021年11月29日,使用reprex包(v2.0.1)


1
谢谢tjebo,这就是我在没有dplyr的情况下会做的方式。然而,我越来越多地使用这个库,这就是为什么我想提高我的这个库的技能水平。 - diggi2395

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接