tidyr/dplyr - 为重复的 ID 传播多个变量

tidyr/dplyr - spreading multiple variables for duplicate ids

我有一些非常脏的数据,我真的很难清理。该问题的示例如下:

ID    NAME    ADDRESS               EMAIL     PHN
1   Alice 123 Street     alice@gmail.com 5555555
1   Alice 123 Street                <NA> 4444444
2     Bob   9 Circle       Bob@gmail.com 1111111
3 Charlie      4 Ave   Charlie@gmail.com 3333333
3 Charlie      4 Ave Charlie@hotmail.com 3333333
3 Charlie      4 Ave                <NA>      NA
4    Doug    1 Court                <NA> 6666666

期望的输出是这样的:

ID    NAME    ADDRESS           EMAIL_1             EMAIL_2   PHN_1   PHN_2
1   Alice 123 Street   alice@gmail.com                <NA> 5555555 4444444
2     Bob   9 Circle     bob@gmail.com                <NA> 1111111      NA
3 Charlie      4 Ave charlie@gmail.com charlie@hotmail.com 3333333      NA
4    Doug    1 Court              <NA>                <NA> 6666666      NA

理解 EMAILPHN 变量可以任意扩展(即可能有 n 重复 ID有不同的(或NA)值。)

到目前为止我的解决方案:

df.test <- df %>%
  group_by(ID) %>%
  mutate(EMAILID = paste0("EMAIL_",row_number())) %>%
  spread(EMAILID,EMAIL) %>%
  mutate(PHONEID = paste0('PHN_',row_number())) %>%
  spread(PHONEID,PHN)

但这会产生更加畸形的 data.frame:

ID    NAME    ADDRESS           EMAIL_1             EMAIL_2 EMAIL_3   PHN_1   PHN_2 PHN_3
1   Alice 123 Street   alice@gmail.com                <NA>    <NA> 5555555      NA    NA
1   Alice 123 Street              <NA>                <NA>    <NA>      NA 4444444    NA
2     Bob   9 Circle     Bob@gmail.com                <NA>    <NA> 1111111      NA    NA
3 Charlie      4 Ave Charlie@gmail.com                <NA>    <NA> 3333333      NA    NA
3 Charlie      4 Ave              <NA> Charlie@hotmail.com    <NA>      NA 3333333    NA
3 Charlie      4 Ave              <NA>                <NA>    <NA>      NA      NA    NA
4    Doug    1 Court              <NA>                <NA>    <NA> 6666666      NA    NA

有什么帮助吗?我怀疑我的问题与 spread() 命令有关,但到目前为止我的尝试都没有结果。谢谢。

您需要 summarize 而不是 mutate,然后使用 separate 拆分结果。要动态执行此操作,您可以提前确定要使用的不同电子邮件和 phone 组的数量,使用 separate_ 然后设置 fill = right 以删除警告。最后两个 mutate 语句用于清理 NA 值变成字符串。

library(dplyr)
library(tidyr)

cols <- cols <- df %>% 
  group_by(ID) %>% 
  filter(!is.na(PHN), !is.na(EMAIL)) %>% 
  group_size() %>% 
  max()

df %>%
  group_by(ID, NAME, ADDRESS) %>%
  summarize_each(funs(toString(unique(.[!is.na(.)]))), EMAIL, PHN) %>% 
  separate_("EMAIL", sprintf("EMAIL%s", 1:cols), sep = ",", fill = "right") %>% 
  separate_("PHN", sprintf("PHN%s", 1:cols), sep = ",", fill = "right") %>% 
  mutate_if(is.character, trimws) %>% 
  mutate_each(funs(replace(., grep("NA", .), NA)))

  Source: local data frame [4 x 7]
Groups: ID, NAME [4]

     ID    NAME    ADDRESS            EMAIL1              EMAIL2    PHN1    PHN2
  <int>  <fctr>     <fctr>             <chr>               <chr>   <chr>   <chr>
1     1   Alice 123 Street   alice@gmail.com                <NA> 5555555 4444444
2     2     Bob   9 Circle     Bob@gmail.com                <NA> 1111111    <NA>
3     3 Charlie      4 Ave Charlie@gmail.com Charlie@hotmail.com 3333333    <NA>
4     4    Doug    1 Court              <NA>                <NA> 6666666    <NA>

将抛出警告

1) reshape 使用 base R 这可以在 3 行中完成。第一行代码为每个 ID 添加一个序号,最后一行代码执行从长到宽的转换。第二行代码将数据框从长变为宽,最后一行代码删除了仅包含 NA 的列。 (如果 NA 列不太可能或者您不介意它们,那么可以省略第三行代码。)

df2 <- transform(df.test, seq = ave(ID, ID, FUN = seq_along))
df2 <- reshape(df2, dir = "wide", timevar = "seq", idvar = c("ID", "NAME", "ADDRESS"))
subset(df2, select = !apply(is.na(df.test2), 2, all))

给予:

  ID    NAME    ADDRESS           EMAIL.1   PHN.1             EMAIL.2   PHN.2
1  1   Alice 123 Street   alice@gmail.com 5555555                <NA> 4444444
3  2     Bob   9 Circle     Bob@gmail.com 1111111                <NA>      NA
4  3 Charlie      4 Ave Charlie@gmail.com 3333333 Charlie@hotmail.com 3333333
7  4    Doug    1 Court              <NA> 6666666                <NA>      NA

2) magrittr除了形成magrittr管道外,可以编写相同的代码:

library(magrittr)

df.test %>%
   transform(seq = ave(ID, ID, FUN = seq_along)) %>%
   reshape(dir = "wide", timevar = "seq", idvar = c("ID", "NAME", "ADDRESS")) %>%
   subset(select = !apply(is.na(.), 2, all))

注意: 可重现形式的输入 df.test 是:

Lines <- "
ID,NAME,ADDRESS,EMAIL,PHN
1,Alice,123 Street,alice@gmail.com,5555555
1,Alice,123 Street,NA,4444444
2,Bob,9 Circle,Bob@gmail.com,1111111
3,Charlie,4 Ave,Charlie@gmail.com,3333333
3,Charlie,4 Ave,Charlie@hotmail.com,3333333
3,Charlie,4 Ave,NA,
4,Doug,1 Court,NA,6666666"
df.test <- read.csv(text=Lines)

对于任何关心 summarize_each 的弃用警告的人,以下代码适用于当前支持的函数:

df.test %>% 
  group_by(ID, NAME, ADDRESS) %>%
  summarize_at(vars(EMAIL, PHN), funs(toString(unique(.[!is.na(.)])))) %>%
  separate(EMAIL, sprintf('EMAIL%s', 1:cols), sep = ",", fill = 'right') %>%
  separate(PHN, sprintf('PHN%s', 1:cols), sep = ",", fill = 'right') %>%
  mutate_if(is.character, trimws) %>%
  mutate_all(funs(replace(., grep("NA", .), NA)))