有条件地将字符串分隔成列
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 foody
。 tabulate
创建一个长度为 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
最后,我们需要的是创建一个 table
的 factor
每一行,并将我们想要的特征作为级别。为了方便起见,我们将它包装成一个函数 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"))
在调查中提出一个问题然后告诉参与者“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 foody
。 tabulate
创建一个长度为 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
最后,我们需要的是创建一个 table
的 factor
每一行,并将我们想要的特征作为级别。为了方便起见,我们将它包装成一个函数 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"))