【问题标题】:tidyr/dplyr - spreading multiple variables for duplicate idstidyr/dplyr - 为重复的 id 传播多个变量
【发布时间】:2024-11-18 07:15:01
【问题描述】:

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

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 个具有不同(或NA)值的ID 重复.)

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

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() 命令有关,但到目前为止我的尝试都证明是徒劳的。谢谢。

【问题讨论】:

  • 如果你去掉变异会发生什么?
  • @RyanMorton:如果我将代码替换为:df.test &lt;- df %&gt;% group_by(ID) %&gt;% spread(ID,EMAIL) %&gt;% spread(ID,PHN) 我得到错误:行标识符重复 (4,5)
  • 嗯,你能把 NAME 添加到 group_by 吗?
  • 不幸的是,导致同样的错误。如果我尝试使用 ADDRESS,也是一样。
  • 在原始文件末尾添加一个summarise_each(funs(first) 怎么样?

标签: r dplyr tidyr


【解决方案1】:

您需要summarize 而不是mutate,然后使用separate 来拆分结果。要动态执行此操作,您可以提前确定要使用的不同电子邮件和电话组的数量,使用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>

警告将被抛出

【讨论】:

  • 这会给您带来很多错误吗?输出是正确的,但是我在运行时收到了这个错误列表:Warning messages: 1: Too many values at 1 locations: 3 2: Too few values at 2 locations: 2, 4 3: Too many values at 1 locations: 3 4: Too few values at 2 locations: 2, 4
  • 您会收到警告,而不是错误。警告是指某些组合产生的额外项目大于拆分列的数量。请参阅 ?separate 并查看我关于 separate_ 的注释
【解决方案2】:

1) 重塑 使用基数 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)

【讨论】:

    【解决方案3】:

    对于关注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)))
    

    【讨论】: