如何将多行记录合并为一行且删除缺失值

11

假设我有以下数据框 df

name <- c("Bill", "Rob", "Joe", "Joe")
address <- c("123 Main St", "234 Broad St", NA, "456 North Ave")
favteam <- c("Dodgers", "Mets", "Pirates", NA)

df <- data.frame(name = name, 
                 address = address,
                 favteam = favteam)
df

看起来像这样:

  name       address favteam
1 Bill   123 Main St Dodgers
2  Rob  234 Broad St    Mets
3  Joe          <NA> Pirates
4  Joe 456 North Ave    <NA>

我想要做的是按名称(或一般情况下,任何数量的分组变量)折叠(合并)行,并将在最终数据中除了NA值之外的任何其他值替换为NA值,就像这样:

df_collapse <- foo(df)

  name   address        favteam
1 Bill   123 Main St    Dodgers
2  Rob   234 Broad St      Mets
3  Joe   456 North Ave  Pirates

5
乔能否对他的团队改变想法,或在第二个或随后的记录中更正他的地址? - vpipkt
乔生活在与世隔绝的地方,积极回避数据收集者。我们只成功追踪到他两次,他坚决拒绝向我们透露关于他生活的任何信息,但幸运的是,他喜欢谈论棒球,所以... - mcjudd
开玩笑的,不同的非NA响应给我的问题增加了另一个难点,但我想我会一步一步地解决它。对于基于因子变量级别顺序实现层次结构的想法有什么想法吗?这种类型的问题似乎在SO上没有得到回答... - mcjudd
“按因子变量级别实现层次结构”是什么意思?您能详细解释一下吗?在名称每个响应的非NA值不同时,您期望得到什么? - talat
1个回答

16

这里有一个使用dplyr的选项:

library(dplyr)

df %>%
  group_by(name) %>%
  summarise_each(funs(first(.[!is.na(.)]))) # or summarise_each(funs(first(na.omit(.))))

#Source: local data frame [3 x 3]
#
#  name       address favteam
#1 Bill   123 Main St Dodgers
#2  Joe 456 North Ave Pirates
#3  Rob  234 Broad St    Mets

使用 data.table:

library(data.table)
setDT(df)[, lapply(.SD, function(x) x[!is.na(x)][1L]), by = name]
#   name       address favteam
#1: Bill   123 Main St Dodgers
#2:  Rob  234 Broad St    Mets
#3:  Joe 456 North Ave Pirates

或者
setDT(df)[, lapply(.SD, function(x) head(na.omit(x), 1L)), by = name]

编辑:

你说在你的实际数据中,每个名称有不同数量的非NA响应。在这种情况下,以下方法可能会有所帮助。

考虑这个修改后的示例数据(查看最后一行):


name <- c("Bill", "Rob", "Joe", "Joe", "Joe")
address <- c("123 Main St", "234 Broad St", NA, "456 North Ave", "123 Boulevard")
favteam <- c("Dodgers", "Mets", "Pirates", NA, NA)

df <- data.frame(name = name, 
                 address = address,
                 favteam = favteam)

df
#  name       address favteam
#1 Bill   123 Main St Dodgers
#2  Rob  234 Broad St    Mets
#3  Joe          <NA> Pirates
#4  Joe 456 North Ave    <NA>
#5  Joe 123 Boulevard    <NA>

然后,您可以使用这种data.table方法按名称获取非NA响应,这些响应可能数量不同:

setDT(df)[, lapply(.SD, function(x) unique(na.omit(x))), by = name]
#   name       address favteam
#1: Bill   123 Main St Dodgers
#2:  Rob  234 Broad St    Mets
#3:  Joe 456 North Ave Pirates
#4:  Joe 123 Boulevard Pirates

非常好的答案 - 感谢您对细节的关注,@docendo。我特别喜欢dplyr的解决方案,因为它已经在我的列表上,要更熟悉该软件包的语法。对于这部分:summarise_each(funs(first(.[!is.na(.)]))),句点是否是指dfname分组?我不知道dplyr与索引的配合。如果您能向我介绍有关dplyr精细点的全面教程,我将不胜感激。 - mcjudd
@mcjudd,很高兴它能起作用 :) summarise_each 中的“.”指当前数据,它既是a)分组的,也是b)列向的。因此,first(.[!is.na(.)])的意思是:在每个列中,我们汇总并收集该列中“名称”组的每个组,对于该列中的该组,取第一个非“NA”的数据点并将其作为该组的该列的汇总值返回。不幸的是,我无法告诉你太多关于dplyr教程的事情。如果您只是搜索一下,就会找到许多教程,例如Hadley的这个教程 - talat
@mcjudd,我不确定我完全理解你的意思,但你可以尝试使用... %>% mutate_each(funs(replace(., which(. == 0), 1)))扩展dplyr管道。你也可以使用ifelse,但replace更快。从技术上讲,你也可以在summarize each中完成这个操作,但这会使它变得不太易读,更重要的是,你不需要按组执行此操作(在summarize之后,数据不再分组,因此最好在mutate each之后执行)。 - talat
抱歉,问题有点傻。我已经想通了。summarise_each(funs(first(.[!is.na(.)]), max)) :) - mcjudd
1
@mcjudd,也可以查看https://dev59.com/5F8d5IYBdhLWcg3wQwW5以获取相关问题的答案。 - talat
显示剩余2条评论

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