【问题标题】:Grouping observations conditional on discrete attribute values以离散属性值为条件对观察进行分组
【发布时间】:2018-06-04 03:53:32
【问题描述】:

每个观察值都有 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

这行得通。然而,即使是几千次观察,也需要很长时间。 我很确定有一些更有效的方法,例如一些data.table 魔法。如果问题没有明确说明,请告诉我。

【问题讨论】:

    标签: r dplyr data.table cluster-analysis


    【解决方案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
    

    【讨论】:

    • 谢谢!但我认为它并不是真的更快。也许最好先对 data.frame 进行排序并连续合并组。这种方式可以减少操作次数,因为并非所有内容都必须与所有内容进行比较。
    • 天哪,你说得对,这句话永远运行。是的,减少操作量似乎是关键。
    【解决方案2】:

    或者,这可以通过reshapingself join 来解决。这种方法不会将每一行相互比较(如嵌套的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
    

    说明

    首先,通过将所有列从宽格式重塑为长格式(在添加行 ID rn 之后)来创建临时 data.table mDT

    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
    

    最后,这个结果按照要求重新整形为矩阵状结构。

    【讨论】:

    • 感谢您的回答。如果我在我的真实数据上尝试它,我会得到mDT[mDT, on = .(variable, value), allow = FALSE] 错误:Error in vecseq(f__, len__, if (allow.cartesian || notjoin || !anyDuplicated(f__, : Join results in more than 2^31 rows (internal vecseq reached physical limit). Very likely misspecified join. Check for duplicate key values in i each of which join to the same group in x over and over again. If that's ok, try by=.EACHI to run j for each group to avoid the large allocation.
    • 当我阅读它时,结果表太大了。我尝试添加by = .EACHI,但无法正常工作。有什么想法吗?
    • 您的数据框有多大(行 x 列)?除了 3 个属性之外,还有其他列吗?如果是这样,它们需要包含在id.vars 参数中。
    • 请尝试添加nomatch = 0L。如果失败,我们可以尝试 64 位整数(相当绝望的假设)或仅填充矩阵的上三角形(应该将行数减少近 50%)。
    • 感谢您的帮助!但我会放弃这种方法,因为它不能提供我正在寻找的解决方案(参见 Anony-Mousse 的回答)。
    【解决方案3】:

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

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

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

    a和b有共同的XY,b和c有共同的Y和Z,但是a和c只有Y。

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

    【讨论】:

    • 我得出了同样的结论。这实际上不是我想要的。我得再考虑一下。
    猜你喜欢
    • 1970-01-01
    • 2021-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多