给定一个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相同。
grp1
grp2
因此,如果我们看第3行和第4行,则基于第1列,我们知道前3行可以分组在一起,而后3行可以分组在一起。然后,由于第3行和第4行共享相同的grp2值,因此我们知道实际上所有6行都可以分组在一起。
基于相同的逻辑,我们可以看到最后六行也可以分组在一起(因为第7行和第10行共享相同的grp2)。
除了编写一组相当for()复杂的循环之外,还有其他更直接的方法吗?我还没想到一个呢。
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
感谢您对本主题的任何指导!
一种实现方法是通过矩阵,该矩阵根据组成员身份定义行之间的链接。
这种方法与 @Frank 的图答案有关,但是使用邻接矩阵而不是使用边来定义图。这种方法的优点是可以立即使用相同的代码处理多个> 2个分组列。(只要编写可灵活确定链接的函数即可。)一个缺点是您需要在行之间进行所有成对比较以构造矩阵,因此对于很长的向量,它可能很慢。 照原样 , @ Frank 的答案对于较长的数据或只有两列的情况会更好。
步骤是
您可以通过两种方法进行操作。下面我展示了一种蛮力方式,其中2a)折叠链接,直到使用矩阵乘法达到稳定的链接结构为止; 2b)使用hclust和将链接结构转换为因子cutree。您也可以igraph::clusters在根据矩阵创建的图形上使用。
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来构造一个矩阵,
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定义组(图形的连接组件)。您可以通过多种方式执行此操作。
A
一种方法是,首先定义一个距离对象,然后使用hclust和cutree。如果您考虑一下,我们想将链接(A[i,j] == 1)定义为距离0。因此,步骤如下: a) 在dist对象中将链接定义为距离0; b) 从dist对象构造一棵树; c) 将树切零。高度(即零距离):
A[i,j] == 1
df$combinedGrp <- cutree(hclust(as.dist(1 - A)), h = 0) df
在实践中 ,你可以编码步骤 1 - 2 在使用辅助功能单一lump_links和linked_rows:
lump_links
linked_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
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 的答案的连接更加清晰:
igraph
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 }