将R数据框中的多行合并为一行

Uniting multiple rows in R data frame into one row

我有一个 R 数据框,里面有很多零。它基本上是这样的:

性别 宝马 大众 奔驰
2018 最大值 0 0
2019 彼得 0 0
2019 0 彼得 0
2019 0 0 彼得

此 table 中的一行表示每年一位客户。每个客户可以拥有 0、1 或多辆汽车(来自不同制造商)...

现在,我想让 table 更紧凑。事实上,我想将最后三行合并为一行。由于 Peter 拥有所有三辆车,并且他的所有汽车都在同一年注册,所以 Peter 看起来像这样的条目应该足够了:

性别 宝马 大众 奔驰
2019 彼得 彼得 彼得

最后,输出应该是这样的:

性别 宝马 大众 奔驰
2018 最大值 0 0
2019 彼得 彼得 彼得

我怎样才能做到这一点? 我想要每人一年一行!

(部分回答)

将 0 变为 NA 后(编辑:进行更改:data[data == 0] <- NA),您可以:

data %>% 
  group_by(Year, Gender) %>% 
  summarise_all(na.omit)

但这只适用于每年只有一个消费者的情况,这里就是这种情况,但可能不适用于所有数据。在按年份合并行之前,您应该考虑使用 Id 列或其他内容。

对于 YearGender 的每个值,您可以 select 每列中的第一个非 0 值。

library(dplyr)

res <- df %>%
  group_by(Year, Gender) %>%
  summarise(across(.fns = ~.[. != 0][1])) %>%
  ungroup()

res

#  Year Gender BMW   VW    Mercedes
#  <int> <chr>  <chr> <chr> <chr>   
#1  2018 Male   Max   NA    NA      
#2  2019 Male   Peter Peter Peter   

如果您想删除包含任何 NA 行的行,您可以将答案扩展为

res %>% filter(if_all(BMW:Mercedes, Negate(is.na)))

#  Year Gender BMW   VW    Mercedes
#  <int> <chr>  <chr> <chr> <chr>   
#1  2019 Male   Peter Peter Peter   

数据

df <- structure(list(Year = c(2018L, 2019L, 2019L, 2019L), Gender = c("Male", 
"Male", "Male", "Male"), BMW = c("Max", "Peter", "0", "0"), VW = c("0", 
"0", "Peter", "0"), Mercedes = c("0", "0", "0", "Peter")), 
class = "data.frame", row.names = c(NA, -4L))

由于您希望数据按性别和年份“分组”,我建议对这些变量进行整形,过滤掉您不想要的内容,然后再整形回宽。

library(dplyr)
library(tidyr) # pivot_*
dat %>%
  pivot_longer(-c(Year, Gender), values_to = "value") %>%
  filter(value != "0") %>%
  pivot_wider(c(Year, Gender), names_from = name, values_from = value)
# # A tibble: 2 x 5
#    Year Gender BMW   VW    Mercedes
#   <int> <chr>  <chr> <chr> <chr>   
# 1  2018 Male   Max   <NA>  <NA>    
# 2  2019 Male   Peter Peter Peter   

如果存在重复 year/gender/names,就会出现问题。例如,

bind_rows(dat, dat[3,])
#   Year Gender   BMW    VW Mercedes
# 1 2018   Male   Max     0        0
# 2 2019   Male Peter     0        0
# 3 2019   Male     0 Peter        0
# 4 2019   Male     0     0    Peter
# 5 2019   Male     0 Peter        0
bind_rows(dat, dat[3,]) %>%
  pivot_longer(-c(Year, Gender), values_to = "value") %>%
  filter(value != "0") %>%
  pivot_wider(c(Year, Gender), names_from = name, values_from = value)
# Warning: Values are not uniquely identified; output will contain list-cols.
# * Use `values_fn = list` to suppress this warning.
# * Use `values_fn = length` to identify where the duplicates arise
# * Use `values_fn = {summary_fun}` to summarise duplicates
# # A tibble: 2 x 5
#    Year Gender BMW       VW        Mercedes 
#   <int> <chr>  <list>    <list>    <list>   
# 1  2018 Male   <chr [1]> <NULL>    <NULL>   
# 2  2019 Male   <chr [1]> <chr [2]> <chr [1]>

在这种情况下,如果您想删除完全重复的内容,那么您可以这样做:

bind_rows(dat, dat[3,]) %>%
  pivot_longer(-c(Year, Gender), values_to = "value") %>%
  filter(value != "0", !duplicated(.)) %>%                     # updated
  pivot_wider(c(Year, Gender), names_from = name, values_from = value)
# # A tibble: 2 x 5
#    Year Gender BMW   VW    Mercedes
#   <int> <chr>  <chr> <chr> <chr>   
# 1  2018 Male   Max   <NA>  <NA>    
# 2  2019 Male   Peter Peter Peter   

尽管确实“丢失”了彼得两次列出大众汽车的事实……这可能是两个不同的大众汽车。如果您 want/need 保留此信息,则一个 hackish 修复是

bind_rows(dat, dat[3,]) %>%
  pivot_longer(-c(Year, Gender), values_to = "value") %>%
  filter(value != "0") %>%
  group_by(Year, Gender, name, value) %>%
  mutate(name = paste0(name, replace(seq_along(name), 1, ""))) %>%
  ungroup() %>%
  pivot_wider(c(Year, Gender), names_from = name, values_from = value)
# # A tibble: 2 x 6
#    Year Gender BMW   VW    Mercedes VW2  
#   <int> <chr>  <chr> <chr> <chr>    <chr>
# 1  2018 Male   Max   <NA>  <NA>     <NA> 
# 2  2019 Male   Peter Peter Peter    Peter

(或在不影响其他汽车的情况下明确传达“第二大众”的类似方式)。


数据

dat <- structure(list(Year = c(2018L, 2019L, 2019L, 2019L), Gender = c("Male", "Male", "Male", "Male"), BMW = c("Max", "Peter", "0", "0"), VW = c("0", "0", "Peter", "0"), Mercedes = c("0", "0", "0", "Peter")), class = "data.frame", row.names = c(NA, -4L))