【问题标题】:Arrange data frame in a specific way以特定方式排列数据框
【发布时间】:2017-12-29 20:34:06
【问题描述】:

抱歉标题不好,但我真的不知道如何简洁地用词。

我有一个我正在使用的数据框,其中一个项目可以属于 4 个类别中的任何一个,不限于 1 个。这是我正在使用的虚拟矩阵的一个示例:

ID <- 1:7
A <- c(1,0,0,1,1,0,0)
B <- c(0,1,0,0,1,0,1)
C <- c(0,0,0,0,0,1,1)
D <- c(1,0,1,1,0,0,0)
A_B <- (A+B > 0)*1
C_D <- (C+D > 0)*1
Cost <- c(25, 52, 11, 75, 45, 5, 34)

df <- data.frame(ID, A, B, C, D, A_B, C_D, A_B_C_D = 1, Cost)
df

ID A B C D A_B C_D A_B_C_D Cost
1  1 0 0 1  1   1     1     25
2  0 1 0 0  1   0     1     52
3  0 0 0 1  0   1     1     11
4  1 0 0 1  1   1     1     75
5  1 1 0 0  1   0     1     45
6  0 0 1 0  0   1     1     5
7  0 1 1 0  1   1     1     34

我需要组织这个数据框,使第 1 行包含 A,第 2 行包含 B,第 3 行包含 C,第 4 行包含 D,第 5 行包含 A 或 B,第 6 行包含 C 或 D,以及第7行剩下的。我不能使用arrange,因为以desc(A) 开头会自动给出1、4、5。这个问题的一个可接受的解决方案是:

Order <- c(4, 2, 7, 1, 5, 3, 6)
df[Order,]
df

ID A B C D A_B C_D A_B_C_D Cost
4  1 0 0 1   1   1       1   75
2  0 1 0 0   1   0       1   52
7  0 1 1 0   1   1       1   34
1  1 0 0 1   1   1       1   25
5  1 1 0 0   1   0       1   45
3  0 0 0 1   0   1       1   11
6  0 0 1 0   0   1       1    5

基本上,对角线需要是 7 条直线,但无论数据集如何,我都想不出如何对其进行编程以正确排序。我觉得这应该很容易,但我只是没有看到它。转置会更容易吗?

提前致谢。

【问题讨论】:

  • 更多行数的逻辑是什么?
  • @akrun 永远只有 7 行,只是多次迭代:它们是 lpsolve 优化的结果。优化工作使我始终能够做出“正确”的订单,我只需要简化订单。最终输出需要正确的顺序才能工作。谢谢
  • 只有一种解决方案还是可以有多种解决方案?如果是后者,你在乎你得到哪一个吗?您可能必须实现一种搜索算法,该算法遍历可能的安排树,直到找到一个可行的安排。
  • @ClausWilke 排序可以有多种解决方案,只要对角线上有一个 1,顺序无关紧要。另一种算法是我害怕的哈哈。希望比我更聪明的人有一个简单的解决方案,但听起来我会开始研究算法。感谢您的帮助

标签: r dplyr


【解决方案1】:

一种方法是使用蛮力,通过获取行排列的所有排列并检查是否满足对角线期望:

z <- apply(permute::allPerms(1:7), 1, function(x){
  mat <- as.matrix(df[,2:8])
  if(all(diag(mat[x,]) == rep(1,7))){
    return(df[x,])
  }
  })

那么您可以删除 NULL 值:

z <- Filter(Negate(is.null), z)

并获得所有 88 个解决方案

length(z) #88

z[[5]] #random solution
#output

  ID A B C D A_B C_D A_B_C_D Cost
1  1 1 0 0 1   1   1       1   25
2  2 0 1 0 0   1   0       1   52
6  6 0 0 1 0   0   1       1    5
4  4 1 0 0 1   1   1       1   75
5  5 1 1 0 0   1   0       1   45
3  3 0 0 0 1   0   1       1   11
7  7 0 1 1 0   1   1       1   34

要获得第一个匹配的排列,可以使用 while 循环:

perms <- permute::allPerms(1:7)
mat <- as.matrix(df[,2:8])
i <- 1
while (!all(diag(mat[perms[i,],])  == rep(1,7))) {
  i = i+1
}

df[perms[i,],]

#  ID A B C D A_B C_D A_B_C_D Cost
1  1 1 0 0 1   1   1       1   25
2  2 0 1 0 0   1   0       1   52
6  6 0 0 1 0   0   1       1    5
3  3 0 0 0 1   0   1       1   11
4  4 1 0 0 1   1   1       1   75
7  7 0 1 1 0   1   1       1   34
5  5 1 1 0 0   1   0       1   45

让我们检查速度:

test <- function(df){
  z <- apply(permute::allPerms(1:7), 1, function(x){
    mat <- as.matrix(df[,2:8])
    if(all(diag(mat[x,]) == rep(1,7))){
      return(df[x,])
    }
  })
  z <- Filter(Negate(is.null), z)
  return(z)
}

test2 <- function(df){
  perms <- permute::allPerms(1:7)
  mat <- as.matrix(df[,2:8])
  i <- 1
  while (!all(diag(mat[perms[i,],])  == rep(1,7))) {
    i = i+1
  }
  df[perms[i,],]
}
microbenchmark::microbenchmark(b <- test(df), 
                           c <- test2(df), times = 10L)

    Unit: milliseconds
           expr       min        lq      mean   median        uq       max neval cld
  b <- test(df) 392.68257 396.81450 412.41600 401.0613 408.15582 509.77693    10   b
 c <- test2(df)  46.11754  46.92276  47.80778  47.3977  48.82543  50.05795    10  a 

没那么糟糕

【讨论】:

  • 这很好,感谢您的帮助。就这样我在更高层次上理解test2:基本上你告诉R 做的是尽可能多的行组合(具体的排序存储在perms 中)。然后你告诉 R 根据perms 中的规范重新组织df。一旦所有对角线都等于 1,停止循环并按该顺序返回 df
  • 拍摄,我的实际dfs 多一列,所以它是一个 8:8 虚拟矩阵。 permute::allPerms 无法处理 1:8(“可能的排列数量太大”)。 allPerms(1:5) 有 119 行,allPerms(1:6) 有 719 行,allPerms(1:7) 有 5039 行,所以我怀疑allPerms(1:8) 大约有 38k 行。 38000x8 矩阵通常适用于 R,所以我不确定它为什么会卡住。你知道是否有解决方法吗?现在在看 CRAN...@missuse
  • 很抱歉给您发送垃圾邮件,但想让您知道,在您搜索之前我已经弄清楚了。添加ctrl &lt;- permute::how(maxperm = 50000 然后permute::allPerms(1:8, control = ctrl) 就可以了。再次感谢!
  • @CoolGuyHasChillDay test 和 test2 的区别在于,在第一个函数中,R 正在测试所有排列,而在第二个函数中,它正在测试直到找到第一个匹配项,为了您的目的,它会更好,因为速度,因为看起来测试部分比进行排列要多得多。我很高兴你解决了 8 个问题!问题,9!应该也可以,但是10!恐怕就无从谈起了。我还在考虑其他解决方案,如果我发现有什么值得称赞的,我会更新帖子。
【解决方案2】:

根据您发布的数据,不可能有唯一的解决方案,因为第 1 行和第 4 行具有相同的 A 到 D 列序列。否则,使用四位布尔模式似乎是一个简单的练习。我不明白为什么您会重复位模式 1001,除非这是您在设置示例数据时犯的错误。

为了解释我为什么感到困惑,如果第 1 行和第 4 行在您建议的顺序中颠倒了,它不会使您的对角线全为 1 的要求无效,但它显然与以前的顺序不同:

Order2 <- c(1, 2, 7, 4, 5, 3, 6)
df[Order2,]


   ID A B C D A_B C_D A_B_C_D Cost
    1 1 0 0 1   1   1       1   25
    2 0 1 0 0   1   0       1   52
    7 0 1 1 0   1   1       1   34
    4 1 0 0 1   1   1       1   75
    5 1 1 0 0   1   0       1   45
    3 0 0 0 1   0   1       1   11
    6 0 0 1 0   0   1       1    5

如果您不关心排序本身,则可以使用 AND 和 OR 组合确定非唯一解决方案 - 这是使用真值表(或组合逻辑的应用,例如使用De Morgan's Theorem)。

【讨论】:

  • 感谢您帮助我!我说“一个可以接受的答案是:”但我应该更清楚的是,没有唯一的解决方案,只要对角线用 1 填充,它就可以工作。话虽如此,我会看看德摩根定理,谢谢你的资源。
猜你喜欢
  • 2020-07-16
  • 1970-01-01
  • 1970-01-01
  • 2019-07-19
  • 1970-01-01
  • 1970-01-01
  • 2015-06-12
  • 2022-06-28
相关资源
最近更新 更多