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
理解 EMAIL
和 PHN
变量可以任意扩展(即可能有 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)))
我有一些非常脏的数据,我真的很难清理。该问题的示例如下:
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
理解 EMAIL
和 PHN
变量可以任意扩展(即可能有 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)))