【问题标题】:Convert binary survey results to character vectors in an R data frame将二进制调查结果转换为 R 数据框中的字符向量
【发布时间】:2017-06-22 15:23:11
【问题描述】:

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

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", "")

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

【问题讨论】:

    标签: r dataframe character binary-data tidyverse


    【解决方案1】:

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

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

    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 强制转换为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 函数的引用是明确的,以避免由于名称空间混乱而导致名称冲突。

    【讨论】:

    • 非常感谢您为我的问题提供了两个解决方案,同时体贴地关注我的问题标签,并感谢您额外的编码 cmets 清楚地解释了事情!虽然我首选的tidyverse 解决方案非常简洁,但我也对data.table 替代方案的简洁性感到惊讶。
    • @elarry 说实话,我更喜欢data.table 不仅是因为它简洁的代码,还因为性能原因可以解决更大的问题。例如,见my benchmark of a different question
    【解决方案2】:

    假设商店名称唯一,只有 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))
    

    【讨论】:

    • 非常感谢您分享paste() 将二进制数据转换为字符向量的替代方法——它对于像我这样不知道toString() 函数的人特别有用。 :)
    猜你喜欢
    • 2021-12-29
    • 1970-01-01
    • 2018-04-08
    • 2013-09-27
    • 1970-01-01
    • 1970-01-01
    • 2017-04-02
    • 1970-01-01
    • 2020-01-19
    相关资源
    最近更新 更多