有条件地将字符串分隔成列

Conditionally Separate String into Columns

在调查中提出一个问题然后告诉参与者“select 所有适用的问题”是很常见的。例如,“您喜欢吃哪些食物(请 select 所有适用项)?” a) 寿司,b) 意大利面,c) 汉堡包。

假设四 (N=4) 名参与者回答了这个问题,数据可能如下所示。

food.df <- data.frame(id = c(1,2,3,4), food.choice = c("1,2", "", "1,2,3", "3"))

我想做的是使用一种对个体数量和食物选择属性(即寿司、意大利面、汉堡包等)数量灵活的方法,有条件地将它们分成唯一的列。最终数据看起来像这样。

food.final <- data.frame(id= c(1,2,3,4), sushi = c(1,0,1,0), pasta = c(1,0,1,0), hamburger = c(0,0,1,1))

更高级的版本将允许条件分组。您可以将其视为按食物组、位置等分组。假设我们按“selected 含有蛋白质的食物”分组,这可以编码以反映总选择。这可能看起来像这样。

food.group <- data.frame(id = c(1,2,3,4), protein = c(1,0,2,1), non.protein = c(1,0,1,0))

我曾尝试使用 tidyr::separate、strsplit 和其他列拆分函数,但似乎无法获得所需的结果。感谢这方面的帮助,希望这个答案能帮助其他从事调查工作的 R 用户。

我们可以使用fastDummies

library(fastDummies)
library(dplyr)
dummy_cols(food.df, 'food.choice', split = ",", 
    remove_selected_columns = TRUE) %>%
    setNames(c("id", "sushi", "pasta", "hamburger"))

-输出

   id sushi pasta hamburger
1  1     1     1         0
2  2     0     0         0
3  3     1     1         1
4  4     0     0         1

如果重命名应该是自动的,创建一个命名向量并使用 str_replace

library(stringr)
nm1 <- setNames(c("sushi", "pasta", "hamburger"), 1:3)
 dummy_cols(food.df, 'food.choice', split = ",", 
    remove_selected_columns = TRUE) %>% 
   rename_with(~ str_replace_all(str_remove(.x, 'food.choice_'), nm1), -id)
  id sushi pasta hamburger
1  1     1     1         0
2  2     0     0         0
3  3     1     1         1
4  4     0     0         1

对于第二种情况,我们可以使用str_count

food.df %>%
   mutate(protein = str_count(food.choice, '[13]'), 
    non.protein = str_count(food.choice, '2'), .keep = 'unused')
  id protein non.protein
1  1       1           1
2  2       0           0
3  3       2           1
4  4       1           0

您可以创建或可能拥有一个分配所需信息的矩阵,如下所示 foody

(foody <- matrix(c('sushi', 'pasta', 'hamburger', 
                  'protein', 'non_protein', 'protein',
                  '1', '2', '3'), nrow=3, ncol=3, 
                dimnames=list(NULL, c('food', 'protein', 'id'))))
#       food        protein       id 
# [1,] "sushi"     "protein"     "1"
# [2,] "pasta"     "non_protein" "2"
# [3,] "hamburger" "protein"     "3"

然后您可以轻松地 strsplit 逗号和 match ID foodytabulate 创建一个长度为 nrow(foody) 的二进制匹配向量,在 sapply 中我们得到一个矩阵 mt.

(mt <- t(sapply(strsplit(food.df$food.choice, ','), \(x) {
  tabulate(match(x, foody[, 'id']), nrow(foody))
})))
#      [,1] [,2] [,3]
# [1,]    1    1    0
# [2,]    0    0    0
# [3,]    1    1    1
# [4,]    0    0    1
# [5,]    1    0    1

最后,我们需要的是创建一个 tablefactor 每一行,并将我们想要的特征作为级别。为了方便起见,我们将它包装成一个函数 f.

f <- \(v) {
  r <- apply(mt, 1, \(i) foody[as.logical(i), v])
  cbind(food.df[1], t(sapply(r, \(x) 
                             table(factor(x, levels=unique(foody[, v]))))))
}

f('food')
#   id sushi pasta hamburger
# 1  1     1     1         0
# 2  2     0     0         0
# 3  3     1     1         1
# 4  4     0     0         1
# 5  5     1     0         1

f('protein')
#   id protein non_protein
# 1  1       1           1
# 2  2       0           0
# 3  3       2           1
# 4  4       1           0
# 5  5       2           0

请注意,数字字符串应按升序排序,无论如何它们可能都是如此。


数据:

food.df <- structure(list(id = 1:5, food.choice = c("1,2", "", "1,2,3", 
           "3", "1,3")), class = "data.frame", row.names = c("1", "2", "3", 
           "4", "5"))