【问题标题】:Rearrange columns based on coverage of previous columns根据先前列的覆盖范围重新排列列
【发布时间】:2018-11-05 10:22:54
【问题描述】:

我正在进行测试覆盖率分析,我想重新排列一个矩阵,以便按“额外”测试失败的数量对列进行排序。

例如,我有一个 TRUE 和 FALSE 矩阵,其中 TRUE 表示失败。

df <- structure(c(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE), .Dim = c(10L, 3L), .Dimnames = list(NULL, c("t1", "t2", "t3")))

t2 的失败次数最多,应该是第一列。 t1 次之,但它的所有故障(每行)都被 t2 覆盖。然而,t3 的失败次数较少,但最后两次失败没有被 t2 覆盖,因此应该是第二列。

基于故障覆盖率的所需列顺序:

df <- structure(c(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE), .Dim = c(10L, 3L), .Dimnames = list(NULL, c("t2", "t3", "t1")))

我能够使用 for 循环和 apply 函数来计算每次测试的“额外”失败次数,但是当数据集中有很多列和行时,性能真的很差。但是,我更喜欢重新排列该列以进行进一步处理。

for (n in 2:ncol(out)) {
  idx <- which.max(apply(out, 2, sum, na.rm = T))
  col.list <- c(col.list, names(idx))
  val.list <- c(val.list, sum(out.2[ ,idx], na.rm = T))
  out[out[ ,idx] == T, ] <- F
  out <- out[ ,-idx]
}

谁能提出更好的方法来做到这一点?也许不使用 for 循环?

谢谢。

【问题讨论】:

    标签: r apply


    【解决方案1】:

    这是一种与 OP 有点相似的方法,但我希望它的性能会稍微好一些(虽然未经测试):

    select_cols <- names(tail(sort(colSums(df)), 1)) # first col
    for(i in seq_len(ncol(df)-1)) {
      remaining_cols <- setdiff(colnames(df), select_cols)
      idx <- rowSums(df[, select_cols, drop=FALSE]) > 0
      select_cols <- c(select_cols, 
                       names(tail(sort(colSums(df[!idx, remaining_cols, drop=FALSE])), 1)))
    }
    df <- df[, select_cols]
    df
    
    #        t2    t3    t1
    # [1,]  TRUE FALSE  TRUE
    # [2,]  TRUE FALSE  TRUE
    # [3,]  TRUE FALSE  TRUE
    # [4,]  TRUE FALSE  TRUE
    # [5,]  TRUE FALSE  TRUE
    # [6,]  TRUE FALSE  TRUE
    # [7,]  TRUE FALSE FALSE
    # [8,]  TRUE  TRUE FALSE
    # [9,] FALSE  TRUE FALSE
    # [10,] FALSE  TRUE FALSE
    

    更新:试试这个稍微修改过的版本 - 它更快,我认为它会产生正确的结果:

      select_cols <- names(tail(sort(colSums(m)), 1)) # first col
      idx <- rowSums(m[, select_cols, drop = FALSE]) > 0
      for(i in seq_len(ncol(m)-1)) {
        remaining_cols <- setdiff(colnames(m), select_cols)
        idx[!idx] <- rowSums(m[!idx, select_cols, drop=FALSE]) > 0
        select_cols <- c(select_cols, 
                         names(tail(sort(colSums(m[!idx, remaining_cols, drop=FALSE])), 1)))
      }
      m <- m[, select_cols]
      m
    

    两者的主要区别在于这一行:

    idx[!idx] <- rowSums(m[!idx, select_cols, drop=FALSE]) > 0
    

    这意味着我们不需要为任何先前选择的列已经为真的行计算 rowSums。

    【讨论】:

    • 看来 rowSums 很慢。我有一个包含 12k 观察值和 1400 个变量的矩阵。处理需要很长时间。你知道替代方案吗?谢谢。
    • @docendo_discimus 感谢您的 cmets。我也更新了我的版本,我真的很好奇是否可以提供帮助!
    【解决方案2】:

    这是我基于快捷方式的解决方案。

    df <- as.data.frame(df)
    df_new <- df
    index <- NULL
    for (i in 1:dim(df)[2]) {
      var <- names(sort(apply(X=df, MARGIN=2, sum), decreasing = T))[1]
      index = c(index, var)
      df<-df[df[,var]==F,]
    }
    df_new[,c(index)]
    

    如果只有新的失败计数,我们可以通过以下方式迭代循环:

    1. 取失败次数较多的变量
    2. 删除先前变量失败的数据
    3. 重新获取另一个失败次数较多的变量。

    步骤 2 允许使循环更快,步骤 1 和 3 基于应用。

    希望对你有帮助!

    【讨论】:

    • 我不认为排名和会导致随机结果。
    • 你是对的,排序而不是排名解决了这个问题。我解决了这个解决方案,因为它是基于问题所有者要求的应用。
    • 我从您的代码中了解到,您正在按每列的失败次数对列进行排序。但是我真正需要的是按额外失败的数量对列进行排序,这意味着如果每行有两列匹配失败,那么这两列中的一列可能会排在最后,因为它不会在总数中添加新的失败。
    【解决方案3】:

    这里有另一种处理长格式数据的方法。我使用data.table 函数,但如果需要,它可以适应base。我希望我正确理解了您的逻辑;)至少我尝试在注释代码中解释我的理解。

    # convert matrix to data.table
    dt <- as.data.table(df)
    
    # add row index, 'ci'
    dt[ , ri := 1:.N]
    
    # melt to long format
    d <- melt(dt, id.vars = "ri", variable.factor = FALSE, variable.name = "ci")
    
    # determine first column
    # for each 'ci' (columns in 'df'), count number of TRUE
    # select 'ci' with max count
    first_col <- d[ , sum(value), by = ci][which.max(V1), ci]
    
    # for each 'ri' (rows in 'df'),
    # check if number of unique 'ci' is one (i.e. "additional" test failures)    
    d[(value), new := uniqueN(ci) == 1, by = ri]
    
    # select rows where 'new' is TRUE
    # for each 'ci', count the number of rows, i.e the number of 'new'
    # -> number of rows in 'df' where this column is the only TRUE
    d_new <- d[(new), .(n_new = .N), ci]
    
    # set order to descending 'n_new'
    setorder(d_new, -n_new)
    
    # combine first column and columns which contribute with additional TRUE
    cols <- c(first_col, setdiff(d_new[ , ci], first_col)) 
    
    # set column order. 
    # First 'cols', then any columns which haven't contributed with new values
    # (none in the test data, but needed for more general cases)  
    setcolorder(dt, c(cols, setdiff(names(dt), cols)))
    

    dt
    #        t2    t3    t1 ri
    #  1:  TRUE FALSE  TRUE  1
    #  2:  TRUE FALSE  TRUE  2
    #  3:  TRUE FALSE  TRUE  3
    #  4:  TRUE FALSE  TRUE  4
    #  5:  TRUE FALSE  TRUE  5
    #  6:  TRUE FALSE  TRUE  6
    #  7:  TRUE FALSE FALSE  7
    #  8:  TRUE  TRUE FALSE  8
    #  9: FALSE  TRUE FALSE  9
    # 10: FALSE  TRUE FALSE 10
    

    在大小为mentioned in comment的矩阵上进行了尝试:

    set.seed(1)
    nr <- 14000
    nc <- 1400
    df <- matrix(sample(c(TRUE, FALSE), nr*nc, replace = TRUE), nr, nc,
                 dimnames = list(NULL, paste0("t", 1:nc)))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-24
      • 1970-01-01
      • 1970-01-01
      • 2014-08-05
      • 1970-01-01
      • 2021-12-26
      • 2021-09-30
      • 1970-01-01
      相关资源
      最近更新 更多