如何根据其他列的排列组合在数据框中创建新列?

4
假设我有一个数据框,看起来像这样:
    var1   var2   var3   var4  
a   TRUE   FALSE  TRUE   FALSE
b   TRUE   TRUE   TRUE   FALSE
c   FALSE  TRUE   FALSE  TRUE
d   TRUE   FALSE  FALSE  FALSE
e   TRUE   FALSE  TRUE   FALSE
f   FALSE  TRUE   FALSE  TRUE

我希望创建一个新列,根据每个变量在顶部的TRUEFALSE的排列方式,将af分配给类别。
在这个简化的例子中,结果应如下所示:
    var1   var2   var3   var4    category
a   TRUE   FALSE  TRUE   FALSE      A
b   TRUE   TRUE   TRUE   FALSE      B
c   FALSE  TRUE   FALSE  TRUE       C
d   TRUE   FALSE  FALSE  FALSE      D
e   TRUE   FALSE  TRUE   FALSE      A
f   FALSE  TRUE   FALSE  TRUE       C

请注意,每个唯一的TRUEFALSE排列组合都成为不同的分类,由于ae具有相同的排列组合,它们最终属于同一类别(A)。
是否有一种简单的方法可以解决这个问题,在顶部有大量变量的情况下也能工作,并且潜在地不仅限于TRUEFALSE ,而且如果数据框中填充了类别/数字呢?
3个回答

7
您可以尝试像这样做:

## paste the rows together, creating a character vector
x <- do.call(paste, df)
## match it against itself and apply to 'LETTERS', and assign as new column
df$category <- LETTERS[match(x, x)]
df
#    var1  var2  var3  var4 category
# a  TRUE FALSE  TRUE FALSE        A
# b  TRUE  TRUE  TRUE FALSE        B
# c FALSE  TRUE FALSE  TRUE        C
# d  TRUE FALSE FALSE FALSE        D
# e  TRUE FALSE  TRUE FALSE        A
# f FALSE  TRUE FALSE  TRUE        C

如果我们使用命名列表作为环境,上述代码可以写成一行。这样可以避免对全局环境进行任何新的赋值。
df$category <- LETTERS[with(list(x = do.call(paste, df)), match(x, x))]

数据:

df <- structure(list(var1 = c(TRUE, TRUE, FALSE, TRUE, TRUE, FALSE), 
    var2 = c(FALSE, TRUE, TRUE, FALSE, FALSE, TRUE), var3 = c(TRUE, 
    TRUE, FALSE, FALSE, TRUE, FALSE), var4 = c(FALSE, FALSE, 
    TRUE, FALSE, FALSE, TRUE)), .Names = c("var1", "var2", "var3", 
"var4"), row.names = c("a", "b", "c", "d", "e", "f"), class = "data.frame")

已接受。一个漂亮而优雅的答案,我已经能够根据自己的需求进行修改。 - Yang Li
2
LETTERS[with(list(x = interaction(df)), match(x, x))] 稍微简化了一下。 - thelatemail

2
#Example DATA
mydata = structure(list(V1 = c(TRUE, TRUE, FALSE, TRUE, TRUE, FALSE), 
V2 = c(FALSE, TRUE, TRUE, FALSE, FALSE, TRUE), V3 = c(TRUE, 
TRUE, FALSE, FALSE, TRUE, FALSE), V4 = c(FALSE, FALSE, TRUE, 
FALSE, FALSE, TRUE)), .Names = c("V1", "V2", "V3", "V4"),
class = "data.frame", row.names = c(NA,-6L))

#RUN THE ONE LINER (Incorporating David Arenburg's advice in comment)
mydata$category = toupper(letters[as.numeric(as.factor(do.call(paste, mydata)))])

1
as.numeric(as.factor(do.call(paste, df))) 更加通用一些。 - David Arenburg
你的结果不符合 OP 所期望的顺序。 - Rich Scriven
1
LETTERS[interaction(df, drop=TRUE)]如果字母的顺序无关紧要。 - thelatemail

1
以下是另一个想法,可能有助于避免强制转换为“字符”。(“data.frame”df来自RichScriven的答案。)
排序数据:
o = do.call(order, df)

对于每个有序列,找出下一个元素是否与其前一个元素不同:

starts_new_elt = lapply(df, function(x) { 
                                 xo = x[o]
                                 c(TRUE, xo[-1] != xo[-length(x)]) 
                             })

这将返回一个向量,其中每个位置都指定了其是否与前一个元素相同的TRUE/FALSE。有了这个,我们可以获得类似的向量,用于确定在有序的"data.frame"中,一行是否与其前一行相同:

starts_new_row = Reduce("|", starts_new_elt)

将这个向量与有序的"data.frame"比较,我们注意到每当一行与其前一行不同时,就会出现一个TRUE,否则就是FALSE
starts_new_row
#[1]  TRUE FALSE  TRUE  TRUE FALSE  TRUE

df[o, ]
#   var1  var2  var3  var4
#c FALSE  TRUE FALSE  TRUE
#f FALSE  TRUE FALSE  TRUE
#d  TRUE FALSE FALSE FALSE
#a  TRUE FALSE  TRUE FALSE
#e  TRUE FALSE  TRUE FALSE
#b  TRUE  TRUE  TRUE FALSE

最后,在这个向量上使用 cumsum 可以为 有序的 "data.frame" 中的每一组行返回一个 ID,可以通过 order(o) 重新排序:

gr = cumsum(starts_new_row)[order(o)]
gr
#[1] 3 4 1 2 3 1

"

对于精确的输出,我们可以使用:

"
LETTERS[match(gr, unique(gr))]
#[1] "A" "B" "C" "D" "A" "C"

以下是一种方便的替代方法,基于较新版本的R中添加到"data.table"包中的函数groupinggrouping返回与以前相同的排序,但还有一些方便的属性:
o2 = do.call(grouping, df)
ends = attr(o2, "ends")
gr2 = rep(seq_along(ends), c(ends[1], diff(ends)))[order(o2)]

gr2
#[1] 3 4 1 2 3 1
LETTERS[match(gr2, unique(gr2))]
#[1] "A" "B" "C" "D" "A" "C"

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