将二进制调查结果转换为 R 数据框中的字符向量

Convert binary survey results to character vectors in an R data frame

假设我将超市水果库存调查的结果存储在数据框中:

stock <- data.frame(
    store = c("Asda", "Booths", "Co-op"),
    apple = c(1, 0, 0),
    banana = c(1, 1, 0),
    coconut = c(0, 0, 0)
)

看起来像

   store apple banana coconut
1   Asda     1      1       0
2 Booths     0      1       0
3  Co-op     0      0       0

我的目标:

我想将上面的二进制调查结果列转换为每个超市的股票摘要的特征向量,如下所示:

   store        fruits
1   Asda apple, banana
2 Booths        banana
3  Co-op              

我的解决方案:

第 1 步: 我使用 for 循环将二进制列中的所有 1 替换为相应的列名:

for(i in names(stock)[2:4]) {
    stock[which(stock[[i]] == 1), i] <- i
}

得到了

   store apple banana coconut
1   Asda apple banana       0
2 Booths     0 banana       0
3  Co-op     0      0       0

第 2 步: 我使用 tidyr::unite() 将各个水果列连接成一个字符向量列:

library(tidyverse)
stock <- unite(stock, fruits, apple:coconut, sep = ", ")

给我

   store           fruits
1   Asda apple, banana, 0
2 Booths     0, banana, 0
3  Co-op          0, 0, 0

第 3 步: 我必须使用 stringr::str_replace_all() 删除所有不需要的 0 和逗号分隔符:

library(stringr)
stock$fruits <- str_replace_all(stock$fruits, "0, |, 0|0", "")

虽然这可以获得我想要的结果,但我发现我的解决方案相当笨拙,尤其是循环部分。请问有人可以与我分享一个更有效,更直接的解决方案吗?非常感谢!

假设商店名称唯一,只有 1 和 0,并且没有缺失值:

library(dplyr)
library(tidyr)
result <- stock %>%
  gather(fruit, binary, -store) %>%
  mutate(fruit = if_else(binary == 1, fruit, NA_character_)) %>%
  select(-binary) %>%
  filter(!is.na(fruit)) %>%
  group_by(store) %>%
  summarize(fruits = paste(fruit, collapse = ", ")) %>%
  ungroup() %>%
  right_join(stock %>% select(store)) %>%
  mutate(fruits = if_else(is.na(fruits), "", fruits))

该任务需要将输入数据从宽格式重塑为长格式。

虽然这个问题被明确地标记为 tidyverse,但我想从使用我更熟悉的 melt() 的简洁 data.table 解决方案开始:

library(data.table)
melt(setDT(stock), id.vars = "store")[
  value > 0, .(fruits = toString(variable)), keyby = store][.(stock$store)]
    store        fruits
1:   Asda apple, banana
2: Booths        banana
3:  Co-op            NA

它将 stock 强制转换为 class data.table 并将其从宽格式重塑为长格式。然后,在结果按 store 分组的后续聚合中,仅考虑具有至少一个水果的行。 toString() 用于聚合,是 paste() 的简洁替代方法。为了包括 所有 家商店,即使是那些没有任何水果的商店,也需要最终的右连接。


按照 OP 的要求,使用 tidyrdplyr 包中的函数可以实现相同的目标:

library(magrittr)
stock %>% 
  tidyr::gather(fruits, , -store) %>% 
  dplyr::filter(value > 0) %>% 
  dplyr::group_by(store) %>% 
  dplyr::summarise(toString(fruits)) %>% 
  dplyr::right_join(stock %>% dplyr::select(store))
# A tibble: 3 x 2
   store `toString(fruits)`
  <fctr>              <chr>
1   Asda      apple, banana
2 Booths             banana
3  Co-op               <NA>

两个结果是等价的。

请注意,对 tidyverse 函数的引用是明确的,以避免由于名称混乱而导致的名称冲突 space。