以离散属性值为条件对观察进行分组

时间:2017-12-21 12:55:17

标签: r dplyr data.table cluster-analysis

每个观察我有3个离散属性。如果对于2次观察,这些属性中至少有2个采用相同的值,那么我想将它们组合在一起(实际上总是最多2个属性是相同的)。

我的想法是建立一个分组矩阵。每行和每列代表一次观察。行和列的交叉表示两个观察值的“相似性”,如果至少两个属性值相同,则应采用值TRUE,否则FALSE

以下是我所做的可重现的示例(abc是要比较的属性:

df <- data.frame(a = c(1, 1, 2, 2, 3), 
                 b = c(1, 2, 3, 2, 4), 
                 c =c("a", "a", "d", "a", "c"))

grouping_matrix <- matrix(nrow = nrow(df), ncol = nrow(df))

for (i in 1:nrow(df)){
  for (j in 1:nrow(df)){
    if(sum(df[i, ] == df[j, ]) >= 2) {
      grouping_matrix [i, j] <- TRUE
    } else {
      grouping_matrix [i, j]  <- FALSE  
    }
  }
}

> df
  a b c
1 1 1 a
2 1 2 a
3 2 3 d
4 2 2 a
5 3 4 c
> grouping_matrix 
      [,1]  [,2]  [,3]  [,4]  [,5]
[1,]  TRUE  TRUE FALSE FALSE FALSE
[2,]  TRUE  TRUE FALSE  TRUE FALSE
[3,] FALSE FALSE  TRUE FALSE FALSE
[4,] FALSE  TRUE FALSE  TRUE FALSE
[5,] FALSE FALSE FALSE FALSE  TRUE

这很有效。然而,即使对于几个thousend观察,它也需要永远。 我很确定有一些更有效的方式,例如一些data.table魔法。如果问题没有明确说明,请告诉我。

3 个答案:

答案 0 :(得分:1)

如果没有双for循环,这样做会相同。我会做一个快速的性能测试来检查它是否相当快。

grouping_matrix <- do.call(rbind, lapply(1:nrow(df), function(x) rowSums(df[rep(x, nrow(df)),] == df) >= 2))

         1   1.1   1.2   1.3   1.4
[1,]  TRUE  TRUE FALSE FALSE FALSE
[2,]  TRUE  TRUE FALSE  TRUE FALSE
[3,] FALSE FALSE  TRUE FALSE FALSE
[4,] FALSE  TRUE FALSE  TRUE FALSE
[5,] FALSE FALSE FALSE FALSE  TRUE

答案 1 :(得分:1)

或者,这可以通过重塑自联接来解决。此方法不会将每一行相互比较(如嵌套的for循环或嵌套的lapply()方法),而只是查找列名和值中的匹配项。

library(data.table)
mDT <- setDT(df)[, rn := .I][, melt(.SD, id.vars = "rn")]
mDT[mDT, on = .(variable, value), allow = TRUE, nomatch = 0L][
  , .N >= 2L, by = .(rn, i.rn)][
    , dcast(.SD, rn ~ i.rn, fill = FALSE)]
   rn     1     2     3     4     5
1:  1  TRUE  TRUE FALSE FALSE FALSE
2:  2  TRUE  TRUE FALSE  TRUE FALSE
3:  3 FALSE FALSE  TRUE FALSE FALSE
4:  4 FALSE  TRUE FALSE  TRUE FALSE
5:  5 FALSE FALSE FALSE FALSE  TRUE

解释

首先,通过将所有列从宽格式转换为长格式(在添加了行标识mDT之后)创建临时data.table rn

mDT
    rn variable value
 1:  1        a     1
 2:  2        a     1
 3:  3        a     2
 4:  4        a     2
 5:  5        a     3
 6:  1        b     1
 7:  2        b     2
 8:  3        b     3
 9:  4        b     2
10:  5        b     4
11:  1        c     a
12:  2        c     a
13:  3        c     d
14:  4        c     a
15:  5        c     c

然后,mDT将自己与列名和值相连接:

mDT[mDT, on = .(variable, value), allow = TRUE, nomatch = 0L]
    rn variable value i.rn
 1:  1        a     1    1
 2:  2        a     1    1
 3:  1        a     1    2
 4:  2        a     1    2
 5:  3        a     2    3
 6:  4        a     2    3
 7:  3        a     2    4
 8:  4        a     2    4
 9:  5        a     3    5
10:  1        b     1    1
11:  2        b     2    2
12:  4        b     2    2
13:  3        b     3    3
14:  2        b     2    4
15:  4        b     2    4
16:  5        b     4    5
17:  1        c     a    1
18:  2        c     a    1
19:  4        c     a    1
20:  1        c     a    2
21:  2        c     a    2
22:  4        c     a    2
23:  3        c     d    3
24:  1        c     a    4
25:  2        c     a    4
26:  4        c     a    4
27:  5        c     c    5
    rn variable value i.rn

参数nomatch = 0L是在OP报告其生产数据集出现问题时尝试减少生成的行数。

现在,对rni.rn的每个组合计算匹配数,并检查数字是否大于1:

mDT[mDT, on = .(variable, value), allow = TRUE][
  , .N >= 2L, by = .(rn, i.rn)]
    rn i.rn    V1
 1:  1    1  TRUE
 2:  2    1  TRUE
 3:  1    2  TRUE
 4:  2    2  TRUE
 5:  3    3  TRUE
 6:  4    3 FALSE
 7:  3    4 FALSE
 8:  4    4  TRUE
 9:  5    5  TRUE
10:  4    2  TRUE
11:  2    4  TRUE
12:  4    1 FALSE
13:  1    4 FALSE

最后,根据要求将此结果重新整形为类似矩阵的结构。

答案 2 :(得分:1)

如果您遇到性能问题避免使用矩阵。任何需要距离矩阵的东西都需要至少O(n²)的时间,并且需要永远。在R中,因为R解释器真的慢,所以避免任何for循环。像所有快速R模块一样,用C或Fortran重写代码。纯R是sloooow。

但是你的方法有一个更普遍的问题。如果a和b应该分组,b和c应该分组,但a和c太不一样了怎么办?例如

a X Y U
b X Y Z
c V Y Z

a和b共有X Y,b和c共有Y和Z,但a和c只有Y.

如果可以对a和c进行分组,那么您正在寻找传递闭包不相交的集合结构可以帮助加速计算。