R:根据分类变量*列表*创建虚拟变量
R: create dummy variables based on a categorical variable *of lists*
我有一个带有分类变量的数据框,其中包含 列表 个字符串,长度可变(这很重要,否则这个问题将与 this or this 重复), 例如:
df <- data.frame(x = 1:5)
df$y <- list("A", c("A", "B"), "C", c("B", "D", "C"), "E")
df
x y
1 1 A
2 2 A, B
3 3 C
4 4 B, D, C
5 5 E
并且所需的形式是在 df$y
中任何地方看到的每个唯一字符串的虚拟变量,即:
data.frame(x = 1:5, A = c(1,1,0,0,0), B = c(0,1,0,1,0), C = c(0,0,1,1,0), D = c(0,0,0,1,0), E = c(0,0,0,0,1))
x A B C D E
1 1 1 0 0 0 0
2 2 1 1 0 0 0
3 3 0 0 1 0 0
4 4 0 1 1 1 0
5 5 0 0 0 0 1
这种天真的方法有效:
> uniqueStrings <- unique(unlist(df$y))
> n <- ncol(df)
> for (i in 1:length(uniqueStrings)) {
+ df[, n + i] <- sapply(df$y, function(x) ifelse(uniqueStrings[i] %in% x, 1, 0))
+ colnames(df)[n + i] <- uniqueStrings[i]
+ }
然而,对于大数据帧来说,它非常丑陋、懒惰和缓慢。
有什么建议吗?来自 tidyverse
?
的奇思妙想
更新:我在下面得到了 3 种不同的方法。我在我的 (Windows 7, 32GB RAM) 笔记本电脑上使用 system.time
在 真实 数据集上测试了它们,该数据集包含 1M 行,每行包含一个长度列表1 到 4 个字符串(大约 350 个唯一字符串值),总共 200MB 磁盘空间。所以预期的结果是一个尺寸为 1M x 350 的数据框。tidyverse
(@Sotos) 和 base
(@joel.wilson) 方法花了很长时间,我不得不重新启动 R。qdapTools
(@akrun) 方法非常有效:
> system.time(res1 <- mtabulate(varsLists))
user system elapsed
47.05 10.27 116.82
所以这是我将标记为已接受的方法。
我们可以使用mtabulate
library(qdapTools)
cbind(df[1], mtabulate(df$y))
# x A B C D E
#1 1 1 0 0 0 0
#2 2 1 1 0 0 0
#3 3 0 0 1 0 0
#4 4 0 1 1 1 0
#5 5 0 0 0 0 1
另一个想法,
library(dplyr)
library(tidyr)
df %>%
unnest(y) %>%
mutate(new = 1) %>%
spread(y, new, fill = 0)
# x A B C D E
#1 1 1 0 0 0 0
#2 2 1 1 0 0 0
#3 3 0 0 1 0 0
#4 4 0 1 1 1 0
#5 5 0 0 0 0 1
除了您在评论中提到的情况,我们可以使用 reshape2
中的 dcast
,因为它比 spread
、
更灵活
df2 <- df %>%
unnest(y) %>%
group_by(x) %>%
filter(!duplicated(y)) %>%
ungroup()
reshape2::dcast(df2, x ~ y, value.var = 'y', length)
# x A B C D E
#1 1 1 0 0 0 0
#2 2 1 1 0 0 0
#3 3 0 0 1 0 0
#4 4 0 1 1 1 0
#5 5 0 0 0 0 1
#or with df$x <- c(1, 1, 2, 2, 3)
# x A B C D E
#1 1 1 1 0 0 0
#2 2 0 1 1 1 0
#3 3 0 0 0 0 1
#or with df$x <- rep(1,5)
# x A B C D E
#1 1 1 1 1 1 1
这不涉及外部包,
# thanks to Sotos for suggesting to use `unique(unlist(df$y))` instead of `LETTERS[1!:5]`
sapply(unique(unlist(df$y)), function(j) as.numeric(grepl(j, df$y)))
# A B C D E
#[1,] 1 0 0 0 0
#[2,] 1 1 0 0 0
#[3,] 0 0 1 0 0
#[4,] 0 1 1 1 0
#[5,] 0 0 0 0 1
我有一个带有分类变量的数据框,其中包含 列表 个字符串,长度可变(这很重要,否则这个问题将与 this or this 重复), 例如:
df <- data.frame(x = 1:5)
df$y <- list("A", c("A", "B"), "C", c("B", "D", "C"), "E")
df
x y 1 1 A 2 2 A, B 3 3 C 4 4 B, D, C 5 5 E
并且所需的形式是在 df$y
中任何地方看到的每个唯一字符串的虚拟变量,即:
data.frame(x = 1:5, A = c(1,1,0,0,0), B = c(0,1,0,1,0), C = c(0,0,1,1,0), D = c(0,0,0,1,0), E = c(0,0,0,0,1))
x A B C D E 1 1 1 0 0 0 0 2 2 1 1 0 0 0 3 3 0 0 1 0 0 4 4 0 1 1 1 0 5 5 0 0 0 0 1
这种天真的方法有效:
> uniqueStrings <- unique(unlist(df$y))
> n <- ncol(df)
> for (i in 1:length(uniqueStrings)) {
+ df[, n + i] <- sapply(df$y, function(x) ifelse(uniqueStrings[i] %in% x, 1, 0))
+ colnames(df)[n + i] <- uniqueStrings[i]
+ }
然而,对于大数据帧来说,它非常丑陋、懒惰和缓慢。
有什么建议吗?来自 tidyverse
?
更新:我在下面得到了 3 种不同的方法。我在我的 (Windows 7, 32GB RAM) 笔记本电脑上使用 system.time
在 真实 数据集上测试了它们,该数据集包含 1M 行,每行包含一个长度列表1 到 4 个字符串(大约 350 个唯一字符串值),总共 200MB 磁盘空间。所以预期的结果是一个尺寸为 1M x 350 的数据框。tidyverse
(@Sotos) 和 base
(@joel.wilson) 方法花了很长时间,我不得不重新启动 R。qdapTools
(@akrun) 方法非常有效:
> system.time(res1 <- mtabulate(varsLists))
user system elapsed
47.05 10.27 116.82
所以这是我将标记为已接受的方法。
我们可以使用mtabulate
library(qdapTools)
cbind(df[1], mtabulate(df$y))
# x A B C D E
#1 1 1 0 0 0 0
#2 2 1 1 0 0 0
#3 3 0 0 1 0 0
#4 4 0 1 1 1 0
#5 5 0 0 0 0 1
另一个想法,
library(dplyr)
library(tidyr)
df %>%
unnest(y) %>%
mutate(new = 1) %>%
spread(y, new, fill = 0)
# x A B C D E
#1 1 1 0 0 0 0
#2 2 1 1 0 0 0
#3 3 0 0 1 0 0
#4 4 0 1 1 1 0
#5 5 0 0 0 0 1
除了您在评论中提到的情况,我们可以使用 reshape2
中的 dcast
,因为它比 spread
、
df2 <- df %>%
unnest(y) %>%
group_by(x) %>%
filter(!duplicated(y)) %>%
ungroup()
reshape2::dcast(df2, x ~ y, value.var = 'y', length)
# x A B C D E
#1 1 1 0 0 0 0
#2 2 1 1 0 0 0
#3 3 0 0 1 0 0
#4 4 0 1 1 1 0
#5 5 0 0 0 0 1
#or with df$x <- c(1, 1, 2, 2, 3)
# x A B C D E
#1 1 1 1 0 0 0
#2 2 0 1 1 1 0
#3 3 0 0 0 0 1
#or with df$x <- rep(1,5)
# x A B C D E
#1 1 1 1 1 1 1
这不涉及外部包,
# thanks to Sotos for suggesting to use `unique(unlist(df$y))` instead of `LETTERS[1!:5]`
sapply(unique(unlist(df$y)), function(j) as.numeric(grepl(j, df$y)))
# A B C D E
#[1,] 1 0 0 0 0
#[2,] 1 1 0 0 0
#[3,] 0 0 1 0 0
#[4,] 0 1 1 1 0
#[5,] 0 0 0 0 1