将包含相同变量的多个列合并为一列

4

我的数据看起来像这样:

ID   Diagnosis_1   Diagnosis_2   Diagnosis_3   Diagnosis_4
A        1             0             0             0
A        1             0             0             0
A        1             0             0             0
B        0             1             0             0
C        0             0             0             1
C        0             1             0             0
D        0             0             0             1
E        0             0             1             0
E        0             1             0             0
E        0             0             1             0

Diagnosis_1:Diagnosis_4均为二进制,表示诊断的存在(1)或不存在(0)。我想要做的是创建一个数据框看起来像这样:

ID   Diagnosis
A        1
A        1
A        1
B        2
C        4
C        2
D        4
E        3
E        2
E        3

无论我读了多少遍关于reshape/reshape2/tidyr的文档,我都无法理解它们的实现方式。
使用dplyr的mutate可以解决我的问题,但这是一种耗时而绕弯的方式来实现我的目标。
编辑:数据已经更改以更真实地表示我的实际数据框架。
3个回答

6
尝试矩阵乘法:
nc <- ncol(DF)
data.frame(ID = DF$ID, Diagnosis = as.matrix(DF[-1]) %*% seq(nc-1))

提供:

  ID Diagnosis
1  A         1
2  B         2
3  C         2
4  D         4
5  E         3

注意: 我们将此作为输入:

Lines <- "ID   Diagnosis_1   Diagnosis_2   Diagnosis_3   Diagnosis_4
A        1             0             0             0
B        0             1             0             0
C        0             1             0             0
D        0             0             0             1
E        0             0             1             0"

DF <- read.table(text = Lines, header = TRUE)

3
你可以尝试使用 max.col 函数获取每行的列索引。
 data.frame(ID=df1$ID, Diagnosis=max.col(df1[-1]))
 #    ID Diagnosis
 #1  A         1
 #2  B         2
 #3  C         2
 #4  D         4
 #5  E         3

或者另一个获取索引的选项是

 unname(which(t(df1[-1])!=0, arr.ind=TRUE)[,1])
 #[1] 1 2 2 4 3

基准测试

 set.seed(25)
 m1 <- matrix(sample(0:1, 1e8*5, replace=TRUE, prob=c(0.9, 0.1)), ncol=5)
 m2 <- m1[rowSums(m1)==1,]
 dim(m2)
 #[1] 32812201        5
 set.seed(395)
 df1 <- data.frame(ID= sample(LETTERS, nrow(m2), replace=TRUE), m2,
        stringsAsFactors=FALSE) 
 colnames(df1)[-1] <- paste('X', 1:5, sep="_")

 Grothendieck <- function() {nc <- ncol(df1)
             data.frame(ID = df1$ID, Diagnosis =
                       as.matrix(df1[-1]) %*% seq(nc-1))}

akrun <- function() {data.frame(ID=df1$ID,
                   Diagnosis=max.col(df1[-1], 'first'))}
ananda <- function() {df1 %>%
                 gather(var, val, -ID) %>%
                 separate(var, into = c("var", "value")) %>%
                 filter(val == 1) %>%
                 select(ID, value)}

 system.time(akrun())
 #  user  system elapsed 
 # 3.690   0.396   4.085 
 system.time(Grothendieck())
 #  user  system elapsed 
 # 3.121   0.459   3.581 

我在 'df1' 的一个更小的子集(即 100 万行)上尝试了 dplyr 解决方案。

df1 <- df1[1:1e6,]
system.time(ananda())
#   user  system elapsed 
#  6.279   0.177   6.454 

使用microbenchmark
library(microbenchmark)
microbenchmark(akrun(), Grothendieck(), unit='relative', times=20L)
#Unit: relative
#         expr      min       lq     mean   median      uq       max neval cld
 #      akrun() 1.019108 1.252443 1.084306 1.180743 1.16463 0.6928535    20  a
#Grothendieck() 1.000000 1.000000 1.000000 1.000000 1.00000 1.0000000    20  a

数据

 df1 <- structure(list(ID = c("A", "B", "C", "D", "E"),
 Diagnosis_1 = c(1L, 
 0L, 0L, 0L, 0L), Diagnosis_2 = c(0L, 1L, 1L, 0L, 0L), 
 Diagnosis_3 = c(0L, 
 0L, 0L, 0L, 1L), Diagnosis_4 = c(0L, 0L, 0L, 1L, 0L)),
 .Names = c("ID", 
 "Diagnosis_1", "Diagnosis_2", "Diagnosis_3", "Diagnosis_4"),
  class = "data.frame", row.names = c(NA, -5L))

3

既然你提到了“reshape2”、“tidyr”和相关工具,这里有一些可供考虑的选项:

## Using "tidyr" and "dplyr"
library(dplyr)
library(tidyr)

df1 %>%
  gather(var, val, -ID) %>%
  separate(var, into = c("var", "value")) %>%
  filter(val == 1) %>%
  select(ID, value)
#   ID value
# 1  A     1
# 2  B     2
# 3  C     2
# 4  E     3
# 5  D     4

## Getting half-way there with "melt" from "reshape2"
library(reshape2)
melt(replace(df1, df1 == 0, NA), id.vars = "ID", na.rm = TRUE)
#    ID    variable value
# 1   A Diagnosis_1     1
# 7   B Diagnosis_2     1
# 8   C Diagnosis_2     1
# 15  E Diagnosis_3     1
# 19  D Diagnosis_4     1

考虑到您的更新,您只需要添加一个次要ID:
library(dplyr)
library(tidyr)

mydf %>%
  group_by(ID) %>%
  mutate(ID2 = row_number()) %>%
  gather(var, val, Diagnosis_1:Diagnosis_4) %>%
  separate(var, into = c("var", "value")) %>%
  filter(val == 1) %>%
  arrange(ID, ID2)
# Source: local data frame [10 x 5]
# 
#    ID ID2       var value val
# 1   A   1 Diagnosis     1   1
# 2   A   2 Diagnosis     1   1
# 3   A   3 Diagnosis     1   1
# 4   B   1 Diagnosis     2   1
# 5   C   1 Diagnosis     4   1
# 6   C   2 Diagnosis     2   1
# 7   D   1 Diagnosis     4   1
# 8   E   1 Diagnosis     3   1
# 9   E   2 Diagnosis     2   1
# 10  E   3 Diagnosis     3   1

'var' 和 'val' 分别指什么? - Makaira Murakami
@MakairaMurakami,当这些列被收集起来后,这就是你要创建的两列。 - A5C1D2H2I1M1N2O1R2T1
@MakairaMurakami,你的数据表中是否有比你分享的样例更多或者列名不同的列? - A5C1D2H2I1M1N2O1R2T1
是的,我有5个诊断列,分别命名为“Diagnosis___1”到“Diagnosis__4”,然后是“Diagnosis__10”,以及ID列。鉴于您提供的代码,这有什么区别吗? - Makaira Murakami
@MakairaMurakami,使用前面评论中展示的不同数量下划线? - A5C1D2H2I1M1N2O1R2T1
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接