在 R 中使用 data.table 的虚拟代码折叠列

dummy code collapsed column using data.table in R

使用 tidyverse 对折叠的列进行虚拟编码非常容易。这是一个简单的例子,说明我过去是如何做到的。首先,我将加载 iris 数据并创建随机抽样字母的自定义折叠列:

library(tidyverse)

# load practice data
data(iris)
iris <- as_tibble(iris)

# create column of collapsed values
lst <- list()
for(i in 1:150) {
  value <- as.list(paste0(sample(letters[1:2], 1), ", ", sample(letters[3:4], 1)))
  lst[i] <- value
}

# append custom columns to the iris dataset
iris$Samples <- unlist(lst)
iris$Subject <- c(1:150)
iris <- iris %>% select(Subject, everything())

# preview custom dataset
iris

# A tibble: 150 x 7
   Subject Sepal.Length Sepal.Width Petal.Length Petal.Width Species Samples
     <int>        <dbl>       <dbl>        <dbl>       <dbl> <fct>   <chr>  
 1       1          5.1         3.5          1.4         0.2 setosa  a, d   
 2       2          4.9         3            1.4         0.2 setosa  a, c   
 3       3          4.7         3.2          1.3         0.2 setosa  a, c   
 4       4          4.6         3.1          1.5         0.2 setosa  b, c   
 5       5          5           3.6          1.4         0.2 setosa  a, c   
 6       6          5.4         3.9          1.7         0.4 setosa  a, d   
 7       7          4.6         3.4          1.4         0.3 setosa  b, c   
 8       8          5           3.4          1.5         0.2 setosa  b, c   
 9       9          4.4         2.9          1.4         0.2 setosa  b, d   
10      10          4.9         3.1          1.5         0.1 setosa  a, c
# ... with 140 more rows

所以,假设每个字母都代表一个唯一的兴趣值,我想将这些数据整理成每个字母的一系列虚拟编码变量。以下是我将如何使用 tidyverse 函数执行此操作:

iris %>%
  separate_rows(Samples, sep = ', ') %>%
  mutate(Values = 1) %>%
  pivot_wider(names_from = "Samples", values_from = "Values") %>%
  mutate_if(is.double, ~replace_na(., 0))

 # A tibble: 150 x 10
   Subject Sepal.Length Sepal.Width Petal.Length Petal.Width Species     a     d     c     b
     <int>        <dbl>       <dbl>        <dbl>       <dbl> <fct>   <dbl> <dbl> <dbl> <dbl>
 1       1          5.1         3.5          1.4         0.2 setosa      1     1     0     0
 2       2          4.9         3            1.4         0.2 setosa      1     0     1     0
 3       3          4.7         3.2          1.3         0.2 setosa      1     0     1     0
 4       4          4.6         3.1          1.5         0.2 setosa      0     0     1     1
 5       5          5           3.6          1.4         0.2 setosa      1     0     1     0
 6       6          5.4         3.9          1.7         0.4 setosa      1     1     0     0
 7       7          4.6         3.4          1.4         0.3 setosa      0     0     1     1
 8       8          5           3.4          1.5         0.2 setosa      0     0     1     1
 9       9          4.4         2.9          1.4         0.2 setosa      0     1     0     1
10      10          4.9         3.1          1.5         0.1 setosa      1     0     1     0
# ... with 140 more rows

这对于小型数据集来说既快速又高效。但是,我正在快速进入具有数百万行的数据集。输入 data.table.

如何使用 data.table 完成相同的过程?这是我的尝试:

library(data.table)

# convert my tibble into a data.table
iris.dt <- as.data.table(iris)

# perform the separate_rows functionality on my data
result <- iris.dt[, list(Samples = unlist(strsplit(Samples, ", "))), by = Subject
                  ][, Values := 1]

print(result)

     Subject Samples Values
  1:       1       a      1
  2:       1       d      1
  3:       2       a      1
  4:       2       c      1
  5:       3       a      1
---                       
296:     148       d      1
297:     149       a      1
298:     149       d      1
299:     150       b      1
300:     150       c      1

问题是我不知道如何 (1) 保留所有其他列和 (2) 以类似于 dplyr::pivot_wider 的方式展开此信息。

如有任何帮助,我们将不胜感激!

一种方法是先tstrsplit,然后melt+dcast。似乎有点低效但不确定另一种方式

示例数据:

library(magrittr)
library(data.table)

set.seed(2020)
iris.dt <- as.data.table(iris)
iris.dt[, samples := paste0(sample(letters[1:2], .N, T), ', ', sample(letters[3:4], .N, T))]

创建虚拟列

new_cols <- 
  iris.dt[, tstrsplit(samples, ', ')][, I := .I] %>% 
    melt('I') %>% 
    dcast(I ~ value, fun.agg = length) %>% 
    .[, I := NULL]


iris.dt[, names(new_cols) := new_cols][]
#      Sepal.Length Sepal.Width Petal.Length Petal.Width   Species samples a b c d
#   1:          5.1         3.5          1.4         0.2    setosa    b, c 0 1 1 0
#   2:          4.9         3.0          1.4         0.2    setosa    a, d 1 0 0 1
#   3:          4.7         3.2          1.3         0.2    setosa    b, c 0 1 1 0
#   4:          4.6         3.1          1.5         0.2    setosa    a, d 1 0 0 1
#   5:          5.0         3.6          1.4         0.2    setosa    a, c 1 0 1 0
#  ---                                                                            
# 146:          6.7         3.0          5.2         2.3 virginica    b, d 0 1 0 1
# 147:          6.3         2.5          5.0         1.9 virginica    a, d 1 0 0 1
# 148:          6.5         3.0          5.2         2.0 virginica    b, c 0 1 1 0
# 149:          6.2         3.4          5.4         2.3 virginica    a, c 1 0 1 0
# 150:          5.9         3.0          5.1         1.8 virginica    a, d 1 0 0 1

这是另一个使用矩阵数字索引的选项:

l <- strsplit(DT[["Samples"]], ",")
nl <- lengths(l)
ul <- unlist(l)
cols <- sort(unique(ul))

DT[, (cols) := {
        m <- matrix(0L, nrow=.N, ncol=length(cols))
        m[cbind(rep(1L:.N, nl), match(ul, cols))] <- 1L
        as.data.table(m)
    }]

输出:

    Subject Samples a b c d
 1:       1     a,d 1 0 0 1
 2:       2     a,c 1 0 1 0
 3:       3     a,c 1 0 1 0
 4:       4     b,c 0 1 1 0
 5:       5     a,c 1 0 1 0
 6:       6     a,d 1 0 0 1
 7:       7     b,c 0 1 1 0
 8:       8     b,c 0 1 1 0
 9:       9     b,d 0 1 0 1
10:      10     a,c 1 0 1 0

数据:

DT <- fread("Subject Samples
1         a,d   
2         a,c   
3         a,c   
4         b,c   
5         a,c   
6         a,d   
7         b,c   
8         b,c   
9         b,d   
10        a,c", sep=" ")