小编典典

使用两个分组名称来创建一个“组合”分组变量

algorithm

给定一个data.frame:

df <- data.frame(grp1 = c(1,1,1,2,2,2,3,3,3,4,4,4),
                 grp2 = c(1,2,3,3,4,5,6,7,8,6,9,10))

#> df
#   grp1 grp2
#1     1    1
#2     1    2
#3     1    3
#4     2    3
#5     2    4
#6     2    5
#7     3    6
#8     3    7
#9     3    8
#10    4    6
#11    4    9
#12    4   10

两个colun都是分组变量,因此grp1已知列中的所有1 都被分组在一起,依此类推,所有2都以此类推,依此类推grp2。已知所有1相同,所有2相同。

因此,如果我们看第3行和第4行,则基于第1列,我们知道前3行可以分组在一起,而后3行可以分组在一起。然后,由于第3行和第4行共享相同的grp2值,因此我们知道实际上所有6行都可以分组在一起。

基于相同的逻辑,我们可以看到最后六行也可以分组在一起(因为第7行和第10行共享相同的grp2)。

除了编写一组相当for()复杂的循环之外,还有其他更直接的方法吗?我还没想到一个呢。

我希望获得的最终输出如下所示:

# > df
#    grp1 grp2 combinedGrp
# 1     1    1           1
# 2     1    2           1
# 3     1    3           1
# 4     2    3           1
# 5     2    4           1
# 6     2    5           1
# 7     3    6           2
# 8     3    7           2
# 9     3    8           2
# 10    4    6           2
# 11    4    9           2
# 12    4   10           2

感谢您对本主题的任何指导!


阅读 433

收藏
2020-07-28

共1个答案

小编典典

一种实现方法是通过矩阵,该矩阵根据组成员身份定义行之间的链接。

这种方法与 @Frank 的图答案有关,但是使用邻接矩阵而不是使用边来定义图。这种方法的优点是可以立即使用相同的代码处理多个>
2个分组列。(只要编写可灵活确定链接的函数即可。)一个缺点是您需要在行之间进行所有成对比较以构造矩阵,因此对于很长的向量,它可能很慢。 照原样
@ Frank 的答案对于较长的数据或只有两列的情况会更好。

步骤是

  1. 根据组比较行,并将这些行定义为链接的(即,创建图形)
  2. 确定图1中的链接定义的图的连接组件。

您可以通过两种方法进行操作。下面我展示了一种蛮力方式,其中2a)折叠链接,直到使用矩阵乘法达到稳定的链接结构为止;
2b)使用hclust和将链接结构转换为因子cutree。您也可以igraph::clusters在根据矩阵创建的图形上使用。

1. 在行之间构造一个邻接矩阵(成对链接矩阵)(即,如果它们在同一组中,则矩阵入口为1,否则为0)。首先创建一个辅助函数,该函数确定是否链接了两行

linked_rows <- function(data){
  ## helper function
  ## returns a _function_ to compare two rows of data
  ##  based on group membership.

  ## Use Vectorize so it works even on vectors of indices
  Vectorize(function(i, j) {
    ## numeric: 1= i and j have overlapping group membership
    common <- vapply(names(data), function(name)
                     data[i, name] == data[j, name],
                     FUN.VALUE=FALSE)
    as.numeric(any(common))
  })
}

我用它outer来构造一个矩阵,

rows <- 1:nrow(df)
A <- outer(rows, rows, linked_rows(df))

2a。 将2度链接折叠为1度链接。也就是说,如果行是由中间节点链接而不是直接链接的,则可以通过在行之间定义链接来将它们放在同一组中。

一个迭代涉及:i)矩阵相乘以获得A的平方,以及ii)将平方矩阵中的任何非零条目设置为1(好像它是第一个度,成对链接)

## define as a function to use below
lump_links <- function(A) {
  A <- A %*% A
  A[A > 0] <- 1
  A
}

重复此操作直到链接稳定

oldA <- 0
i <- 0
while (any(oldA != A)) {
  oldA <- A
  A <- lump_links(A)
}

2b。 使用稳定链接结构A定义组(图形的连接组件)。您可以通过多种方式执行此操作。

一种方法是,首先定义一个距离对象,然后使用hclustcutree。如果您考虑一下,我们想将链接(A[i,j] == 1)定义为距离0。因此,步骤如下: a) 在dist对象中将链接定义为距离0; b) 从dist对象构造一棵树; c)
将树切零。高度(即零距离):

df$combinedGrp <- cutree(hclust(as.dist(1 - A)), h = 0)
df

在实践中 ,你可以编码步骤 1 - 2 在使用辅助功能单一lump_linkslinked_rows

lump <- function(df) {
  rows <- 1:nrow(df)
  A <- outer(rows, rows, linked_rows(df))

  oldA <- 0
  while (any(oldA != A)) {
    oldA <- A
    A <- lump_links(A)
  }
  df$combinedGrp <- cutree(hclust(as.dist(1 - A)), h = 0)
  df
}

这适用于原始版本df,也适用于 @rawr 答案中的结构

df <- data.frame(grp1 = c(1,1,1,2,2,2,3,3,3,4,4,4,5,5,6,7,8,9),
                 grp2 = c(1,2,3,3,4,5,6,7,8,6,9,10,11,3,12,3,6,12))
lump(df)

   grp1 grp2 combinedGrp
1     1    1           1
2     1    2           1
3     1    3           1
4     2    3           1
5     2    4           1
6     2    5           1
7     3    6           2
8     3    7           2
9     3    8           2
10    4    6           2
11    4    9           2
12    4   10           2
13    5   11           1
14    5    3           1
15    6   12           3
16    7    3           1
17    8    6           2
18    9   12           3

聚苯乙烯

这是using的版本igraph,它使与 @Frank 的答案的连接更加清晰:

  lump2 <- function(df) {
      rows <- 1:nrow(df)
      A <- outer(rows, rows, linked_rows(df))
      cluster_A <- igraph::clusters(igraph::graph.adjacency(A))
      df$combinedGrp <- cluster_A$membership
      df
    }
2020-07-28