在 tibble 中取消嵌套多个 tibble 以拆分 Mutli-Select 调查问题
Unnesting multiple tibbles inside a tibble to split Mutli-Select survey questions
我正在尝试生成一个程序化解决方案,以将 'Multiple Response' 调查中的问题扩展到单独的列中。该设置涉及调查数据 (df1) 和将变量与有关变量的信息相关联的帮助文件。使用下面的示例数据,目标是将 DVar 和 EVar 中的响应扩展到单独的列中,例如DVar.A、DVar.b 等...,二进制 1,0 表示该 ID 是否选中了相应的框。
df1 <- tibble(ID = rep(1:8), AVar = sample(1:10, 8), BVar = rnorm(8),
CVar = c("Got", "Some", "Stuff", "In", "Here", "Got", "Others", "Too"),
DVar = c("A,B", NA , "C", "A,C", "B,D", "C", "D", "B,D"),
EVar = c("Banana,Apple", "Orange,Raspberry", "Apple", NA, "Orange", "Banana", "Banana", "Raspberry"))
Helper <- tibble(VariableName = c("ID", "AVar", "BVar", "CVar", "DVar", "EVar"),
QuestionType = c("ID", "Numeric", "Numeric", "Single Response", "Multiple Response", "Multiple Response"))
当前工作函数接受一个ID和要传播的列。就我目前的目的而言,此功能效果很好。除非列没有 NA(这是不常见的),这会在最终 select 语句的数据集中抛出关于 'None' 不在数据集中的错误。
MultiToCol <- function(ID, toSpread) {
X <- tibble(ID, toSpread)
X %>% mutate(varLong = strsplit(as.character(replace_na(toSpread, "None")),split=",")) %>%
unnest(varLong) %>% mutate(tmpValue = 1) %>% spread(varLong, tmpValue, fill = 0) %>% select(-None, -ID, -toSpread, None)
}
使用 mutate(across) 我能够取回必要的数据,然后将其连接回完整的数据集(或者可能在示例中)。
getCols <- Helper %>% filter(QuestionType == "Multiple Response") %>% select(VariableName)
spreadCols <- df1 %>% select_if(names(.) %in% c('ID', getCols$VariableName)) %>%
mutate(across(.cols = !ID, .fns = ~MultiToCol1(ID,.)))
当我查看数据时,rstudio 给了我我想要的东西!
ID DVar.A DVar.B DVar.C DVar.D DVar.None EVar.Apple EVar.Banana EVar.Orange EVar.Raspberry Evar.None
1 1 1 0 0 0 1 1 0 0 0
2 0 0 0 0 1 0 0 1 1 0
3 0 0 1 0 0 1 0 0 0 0
⋮
但是,在写入数据时,我收到有关尺寸不匹配的错误消息。这是因为生成的数据结构是一个 8x3 tibble,其列为 (Int, Tibble, Tibble)。并且内部的Tibbles似乎被转置了。
tibble [8 x 3] (S3: tbl_df/tbl/data.frame)
$ ID : int [1:8] 1 2 3 4 5 6 7 8
$ DVar: tibble [8 x 5] (S3: tbl_df/tbl/data.frame)
..$ A : num [1:8] 1 0 0 1 0 0 0 0
..$ B : num [1:8] 1 0 0 0 1 0 0 1
..$ C : num [1:8] 0 0 1 1 0 1 0 0
..$ D : num [1:8] 0 0 0 0 1 0 1 1
..$ None: num [1:8] 0 1 0 0 0 0 0 0
$ EVar: tibble [8 x 5] (S3: tbl_df/tbl/data.frame)
..$ Apple : num [1:8] 1 0 1 0 0 0 0 0
..$ Banana : num [1:8] 1 0 0 0 0 1 1 0
..$ Orange : num [1:8] 0 1 0 0 1 0 0 0
..$ Raspberry: num [1:8] 0 1 0 0 0 0 0 1
..$ None : num [1:8] 0 0 0 1 0 0 0 0
使用 unnest 函数会产生与 write_ 函数相同的关于不匹配维度的错误。
我也尝试过使用 unnest_wider,但是 运行 遇到了多个 tibble-columns 的问题,因为 unnest_wider 函数只接受一个列作为参数。
我试过使用 pivot_wider 但不知道如何从 getCols$VariableName 正确地传递列名。
我有一些失败的尝试可以添加,但我觉得这是一个简单的地图解决方案,我只是没有成功。
是否有任何简单的解决方案可以从 tibble 中取消嵌套多个 tibble。很高兴听到任何其他反馈,也可以为更大的问题创建更简洁、更优雅的解决方案。
我们可以使用 cSplit_e
library(splitstackshape)
library(dplyr)
df1 %>%
select_if(names(.) %in% c('ID', getCols$VariableName)) %>%
cSplit_e("DVar", type = "character", fill = 0, sep=",") %>%
cSplit_e("EVar", type = "character", fill = 0, sep=",")
或者如果我们想用于多列,一个选项是 map
library(purrr)
tmp <- df1 %>%
select_if(names(.) %in% c('ID', getCols$VariableName))
map_dfc(setdiff(names(tmp), "ID"), ~
tmp %>%
select(.x) %>%
cSplit_e( .x, type = "character", fill = 0, sep=",") %>%
select(-.x)) %>%
bind_cols(tmp, .)
使用OP的函数,可以很容易的用as.data.frame
压扁
out <- df1 %>%
select_if(names(.) %in% c('ID', getCols$VariableName)) %>%
mutate(across(.cols = !ID, .fns = ~MultiToCol(ID,.))) %>%
do.call(data.frame, .)
out
ID DVar.A DVar.B DVar.C DVar.D DVar.None EVar.Apple EVar.Banana EVar.Orange EVar.Raspberry EVar.None
1 1 1 1 0 0 0 1 1 0 0 0
2 2 0 0 0 0 1 0 0 1 1 0
3 3 0 0 1 0 0 1 0 0 0 0
4 4 1 0 1 0 0 0 0 0 0 1
5 5 0 1 0 1 0 0 0 1 0 0
6 6 0 0 1 0 0 0 1 0 0 0
7 7 0 0 0 1 0 0 1 0 0 0
8 8 0 1 0 1 0 0 0 0 1 0
str(out)
#'data.frame': 8 obs. of 11 variables:
# $ ID : int 1 2 3 4 5 6 7 8
# $ DVar.A : num 1 0 0 1 0 0 0 0
# $ DVar.B : num 1 0 0 0 1 0 0 1
# $ DVar.C : num 0 0 1 1 0 1 0 0
# $ DVar.D : num 0 0 0 0 1 0 1 1
# $ DVar.None : num 0 1 0 0 0 0 0 0
# $ EVar.Apple : num 1 0 1 0 0 0 0 0
# $ EVar.Banana : num 1 0 0 0 0 1 1 0
# $ EVar.Orange : num 0 1 0 0 1 0 0 0
# $ EVar.Raspberry: num 0 1 0 0 0 0 0 1
# $ EVar.None : num 0 0 0 1 0 0 0 0
或者可以使用invoke
....
%>% invoke(data.frame, .)
我正在尝试生成一个程序化解决方案,以将 'Multiple Response' 调查中的问题扩展到单独的列中。该设置涉及调查数据 (df1) 和将变量与有关变量的信息相关联的帮助文件。使用下面的示例数据,目标是将 DVar 和 EVar 中的响应扩展到单独的列中,例如DVar.A、DVar.b 等...,二进制 1,0 表示该 ID 是否选中了相应的框。
df1 <- tibble(ID = rep(1:8), AVar = sample(1:10, 8), BVar = rnorm(8),
CVar = c("Got", "Some", "Stuff", "In", "Here", "Got", "Others", "Too"),
DVar = c("A,B", NA , "C", "A,C", "B,D", "C", "D", "B,D"),
EVar = c("Banana,Apple", "Orange,Raspberry", "Apple", NA, "Orange", "Banana", "Banana", "Raspberry"))
Helper <- tibble(VariableName = c("ID", "AVar", "BVar", "CVar", "DVar", "EVar"),
QuestionType = c("ID", "Numeric", "Numeric", "Single Response", "Multiple Response", "Multiple Response"))
当前工作函数接受一个ID和要传播的列。就我目前的目的而言,此功能效果很好。除非列没有 NA(这是不常见的),这会在最终 select 语句的数据集中抛出关于 'None' 不在数据集中的错误。
MultiToCol <- function(ID, toSpread) {
X <- tibble(ID, toSpread)
X %>% mutate(varLong = strsplit(as.character(replace_na(toSpread, "None")),split=",")) %>%
unnest(varLong) %>% mutate(tmpValue = 1) %>% spread(varLong, tmpValue, fill = 0) %>% select(-None, -ID, -toSpread, None)
}
使用 mutate(across) 我能够取回必要的数据,然后将其连接回完整的数据集(或者可能在示例中)。
getCols <- Helper %>% filter(QuestionType == "Multiple Response") %>% select(VariableName)
spreadCols <- df1 %>% select_if(names(.) %in% c('ID', getCols$VariableName)) %>%
mutate(across(.cols = !ID, .fns = ~MultiToCol1(ID,.)))
当我查看数据时,rstudio 给了我我想要的东西!
ID DVar.A DVar.B DVar.C DVar.D DVar.None EVar.Apple EVar.Banana EVar.Orange EVar.Raspberry Evar.None
1 1 1 0 0 0 1 1 0 0 0
2 0 0 0 0 1 0 0 1 1 0
3 0 0 1 0 0 1 0 0 0 0
⋮
但是,在写入数据时,我收到有关尺寸不匹配的错误消息。这是因为生成的数据结构是一个 8x3 tibble,其列为 (Int, Tibble, Tibble)。并且内部的Tibbles似乎被转置了。
tibble [8 x 3] (S3: tbl_df/tbl/data.frame)
$ ID : int [1:8] 1 2 3 4 5 6 7 8
$ DVar: tibble [8 x 5] (S3: tbl_df/tbl/data.frame)
..$ A : num [1:8] 1 0 0 1 0 0 0 0
..$ B : num [1:8] 1 0 0 0 1 0 0 1
..$ C : num [1:8] 0 0 1 1 0 1 0 0
..$ D : num [1:8] 0 0 0 0 1 0 1 1
..$ None: num [1:8] 0 1 0 0 0 0 0 0
$ EVar: tibble [8 x 5] (S3: tbl_df/tbl/data.frame)
..$ Apple : num [1:8] 1 0 1 0 0 0 0 0
..$ Banana : num [1:8] 1 0 0 0 0 1 1 0
..$ Orange : num [1:8] 0 1 0 0 1 0 0 0
..$ Raspberry: num [1:8] 0 1 0 0 0 0 0 1
..$ None : num [1:8] 0 0 0 1 0 0 0 0
使用 unnest 函数会产生与 write_ 函数相同的关于不匹配维度的错误。
我也尝试过使用 unnest_wider,但是 运行 遇到了多个 tibble-columns 的问题,因为 unnest_wider 函数只接受一个列作为参数。
我试过使用 pivot_wider 但不知道如何从 getCols$VariableName 正确地传递列名。
我有一些失败的尝试可以添加,但我觉得这是一个简单的地图解决方案,我只是没有成功。
是否有任何简单的解决方案可以从 tibble 中取消嵌套多个 tibble。很高兴听到任何其他反馈,也可以为更大的问题创建更简洁、更优雅的解决方案。
我们可以使用 cSplit_e
library(splitstackshape)
library(dplyr)
df1 %>%
select_if(names(.) %in% c('ID', getCols$VariableName)) %>%
cSplit_e("DVar", type = "character", fill = 0, sep=",") %>%
cSplit_e("EVar", type = "character", fill = 0, sep=",")
或者如果我们想用于多列,一个选项是 map
library(purrr)
tmp <- df1 %>%
select_if(names(.) %in% c('ID', getCols$VariableName))
map_dfc(setdiff(names(tmp), "ID"), ~
tmp %>%
select(.x) %>%
cSplit_e( .x, type = "character", fill = 0, sep=",") %>%
select(-.x)) %>%
bind_cols(tmp, .)
使用OP的函数,可以很容易的用as.data.frame
out <- df1 %>%
select_if(names(.) %in% c('ID', getCols$VariableName)) %>%
mutate(across(.cols = !ID, .fns = ~MultiToCol(ID,.))) %>%
do.call(data.frame, .)
out
ID DVar.A DVar.B DVar.C DVar.D DVar.None EVar.Apple EVar.Banana EVar.Orange EVar.Raspberry EVar.None
1 1 1 1 0 0 0 1 1 0 0 0
2 2 0 0 0 0 1 0 0 1 1 0
3 3 0 0 1 0 0 1 0 0 0 0
4 4 1 0 1 0 0 0 0 0 0 1
5 5 0 1 0 1 0 0 0 1 0 0
6 6 0 0 1 0 0 0 1 0 0 0
7 7 0 0 0 1 0 0 1 0 0 0
8 8 0 1 0 1 0 0 0 0 1 0
str(out)
#'data.frame': 8 obs. of 11 variables:
# $ ID : int 1 2 3 4 5 6 7 8
# $ DVar.A : num 1 0 0 1 0 0 0 0
# $ DVar.B : num 1 0 0 0 1 0 0 1
# $ DVar.C : num 0 0 1 1 0 1 0 0
# $ DVar.D : num 0 0 0 0 1 0 1 1
# $ DVar.None : num 0 1 0 0 0 0 0 0
# $ EVar.Apple : num 1 0 1 0 0 0 0 0
# $ EVar.Banana : num 1 0 0 0 0 1 1 0
# $ EVar.Orange : num 0 1 0 0 1 0 0 0
# $ EVar.Raspberry: num 0 1 0 0 0 0 0 1
# $ EVar.None : num 0 0 0 1 0 0 0 0
或者可以使用invoke
....
%>% invoke(data.frame, .)