【问题标题】:find column value and name based on minimum value in other column根据其他列中的最小值查找列值和名称
【发布时间】:2020-01-09 12:05:51
【问题描述】:

我有一个如下所示的 data.table

library( data.table )

dt <- data.table( p1 = c("a", "b", "c", "d", "e", "f", "g"), 
                  p2 = c("b", "c", "d", "a", "f", "g", "h"), 
                  p3 = c("z", "x", NA, NA, "y", NA, "s"), 
                  t1 = c(1, 2, 3, NA, 5, 6, 7), 
                  t2 = c(7, 6, 5, NA, 3, 2, NA), 
                  t3 = c(8, 3, NA, NA, 2, NA, 1) )

#    p1 p2   p3 t1 t2 t3
# 1:  a  b    z  1  7  8
# 2:  b  c    x  2  6  3
# 3:  c  d <NA>  3  5 NA
# 4:  d  a <NA> NA NA NA
# 5:  e  f    y  5  3  2
# 6:  f  g <NA>  6  2 NA
# 7:  g  h    s  7 NA  1

它有代表名称的 p 列和代表值的 t 列。 t1为p1对应的值,t2为p2等。
在每一行上,p 列的值都是唯一的(或 NA)。 t 列中的值也是如此。

我想做的是创建三个新列:

  • t_min,每行所有 t 列的最小值(不包括 NA)
  • p_min,如果t_min存在(不是NA),则p列的对应值...所以如果t2-column有t-min值,则对应列p2的值。
  • p_col_min,列的名称,其值为 p_min。因此,如果 p_min 值来自列 p2,则为“p2”。

我更喜欢data.table,因为我的实际数据包含很多更多的行和列。我知道融化是一种选择,但我想用这些数据来保存我的内存,所以使用的内存越少越好(生产数据包含几百万行和 >200 列)。

到目前为止,我已经找到了一种使用以下方法创建 t_min 列的方法:

t_cols = dt[ , .SD, .SDcols = grep( "t[1-3]", names( dt ), value = TRUE ) ]
dt[ !all( is.na( t_cols ) ), 
    t_min := do.call( pmin, c( .SD, list( na.rm = TRUE ) ) ), 
    .SDcols = names( t_cols ) ]

但我无法全神贯注地创建 p_minp_col_min 列。我想which.min() 在某处发挥作用,但我无法弄清楚。可能我忽略了一些简单的事情(它似乎总是.. ;-))。

期望的输出

dt.desired <- data.table( p1 = c("a", "b", "c", "d", "e", "f", "g"), 
                          p2 = c("b", "c", "d", "a", "f", "g", "h"), 
                          p3 = c("z", "x", NA, NA, "y", NA, "s"), 
                          t1 = c(1, 2, 3, NA, 5, 6, 7), 
                          t2 = c(7, 6, 5, NA, 3, 2, NA), 
                          t3 = c(8, 3, NA, NA, 2, NA, 1),
                          t_min = c(1,2,3,NA,2,2,1),
                          p_min = c("a","b","c",NA,"y","g","s"),
                          p_col_min = c("p1","p1","p1",NA,"p3","p2","p3") )

#    p1 p2   p3 t1 t2 t3 t_min p_min p_col_min
# 1:  a  b    z  1  7  8     1     a        p1
# 2:  b  c    x  2  6  3     2     b        p1
# 3:  c  d <NA>  3  5 NA     3     c        p1
# 4:  d  a <NA> NA NA NA    NA  <NA>      <NA>
# 5:  e  f    y  5  3  2     2     y        p3
# 6:  f  g <NA>  6  2 NA     2     g        p2
# 7:  g  h    s  7 NA  1     1     s        p3

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    我不能保证这对于您的工作数据是否足够有效,但这是我首先要尝试的:

    m1 <- as.matrix(dt[, grep('^t', names(dt)), with = FALSE])
    m2 <- as.matrix(dt[, grep('^p', names(dt)), with = FALSE])
    
    t_min <- apply(m1, 1, min, na.rm = TRUE)
    t_min[is.infinite(t_min)] <- NA_real_
    p_min_index <- rep(NA_integer_, length(t_min))
    p_min_index[!is.na(t_min)] <- apply(m1[!is.na(t_min), ], 1, which.min)
    
    dt[, t_min  := t_min]
    dt[, p_min := m2[cbind(seq_len(nrow(m2)), p_min_index)] ]
    dt[, p_min_col := grep('^p', names(dt), value = TRUE)[p_min_index] ]
    
    
    #    p1 p2   p3 t1 t2 t3 t_min p_min p_min_col
    # 1:  a  b    z  1  7  8     1     a        p1
    # 2:  b  c    x  2  6  3     2     b        p1
    # 3:  c  d <NA>  3  5 NA     3     c        p1
    # 4:  d  a <NA> NA NA NA    NA  <NA>      <NA>
    # 5:  e  f    y  5  3  2     2     y        p3
    # 6:  f  g <NA>  6  2 NA     2     g        p2
    # 7:  g  h    s  7 NA  1     1     s        p3
    

    另外,您想要的输出中的第 2 行似乎不正确?

    【讨论】:

      【解决方案2】:

      一种简单而有效的方法是循环遍历“t*”列并在一次遍历中跟踪所有相应的值。

      首先初始化适当的向量:

      p.columns = which(startsWith(names(dt), "p"))
      t.columns = which(startsWith(names(dt), "t"))
      
      p_col_min = integer(nrow(dt))
      p_min = character(nrow(dt))
      t_min = rep_len(Inf, nrow(dt))
      

      并在更新时迭代:

      for(i in seq_along(p.columns)) {
          cur.min = which(dt[[t.columns[i]]] < t_min)
      
          p_col_min[cur.min] = p.columns[i]
      
          t_min[cur.min] = dt[[t.columns[i]]][cur.min]
          p_min[cur.min] = dt[[p.columns[i]]][cur.min]
      }
      

      最后在需要的地方填写NAs:

      whichNA = is.infinite(t_min)
      is.na(t_min) = is.na(p_min) = is.na(p_col_min) = whichNA
      
      t_min
      #[1]  1  2  3 NA  2  2  1
      p_min
      #[1] "a" "b" "c" NA  "y" "g" "s"
      p_col_min
      #[1]  1  1  1 NA  3  2  3
      

      【讨论】:

      • 个人不是for-loops 的忠实粉丝。但感谢您的方法.. 非常感谢!
      • @Wimpel :您关心效率吗?如果是这样,我鼓励......“给for循环一个机会”。当前的 for 循环,此处 1) 是简单有效的操作 ("length(<columns starting with "t">) 重复,避免使用任何额外的内存任何(非“类列表”(data.table))大型临时对象。
      • 我猜 p_min 可以在循环外填充最终值(而不是在循环中覆盖),因为它不会影响 cur.min,例如 is.na(p_col_min) &lt;- p_col_min == 0; dt[, pm := if (is.na(p_col_min)) NA_character_ else .SD[[p_col_min]], by=p_col_min]$pm
      • @Frank2 :是的,我认为这是在检索最小值(及其 p 索引)后对 p 列进行子集化的好方法。同样,“按”评估(在“[.data.table”中)似乎不会成为瓶颈,请记住 p 列的数量(以及“p_col_min”的不同值) 只有几百个。
      【解决方案3】:

      这是另一条路线:

      dt[, t_min := do.call(pmin, c(.SD, na.rm = TRUE)), .SDcols = patterns('t[[:digit:]]')]
      
      dt[!is.na(t_min),
         c('p_min', 'p_min_col') := {
      
           arr_ind = .SD[, which(t_min == .SD, arr.ind = TRUE), .SDcols = patterns('t[[:digit:]]')]
           arr_ind = arr_ind[order(arr_ind[, 1]), ]
      
           p_m = .SD[, as.matrix(.SD)[arr_ind], .SDcols = patterns('p')]
           p_m_c = grep('^p', names(.SD), value = TRUE)[arr_ind[, 2]]
      
           list(p_m, p_m_c)
           } 
         ]
      

      【讨论】:

      • 谢谢.. 还提醒我.SDcols 可以使用patterns(差不多一年了,但不知何故我忘了)...
      【解决方案4】:

      这是另一种选择:

      ri <- dt[, .I[rowSums(is.na(.SD))==ncol(.SD)], .SDcols=t1:t3]
      
      dt[-ri, c("t_min", "p_min", "p_col_min") := {
              pmat <- .SD[, .SD, .SDcols=p1:p3]
              tmat <- as.matrix(.SD[, .SD, .SDcols=t1:t3])
      
              i <- max.col(-replace(tmat, is.na(tmat), Inf), "first")
      
              y <- cbind(seq_len(.N), i)
      
              .(t_min = tmat[y],
                  p_min = as.matrix(pmat)[y],
                  p_col_min = names(pmat)[i])
          }]
      dt
      

      输出:

         p1 p2   p3 t1 t2 t3 t_min p_min p_col_min
      1:  a  b    z  1  7  8     1     a        p1
      2:  b  c    x  2  6  3     2     b        p1
      3:  c  d <NA>  3  5 NA     3     c        p1
      4:  d  a <NA> NA NA NA    NA  <NA>      <NA>
      5:  e  f    y  5  3  2     2     y        p3
      6:  f  g <NA>  6  2 NA     2     g        p2
      7:  g  h    s  7 NA  1     1     s        p3
      

      【讨论】:

      • +1,我正准备将我的数组索引更改为与您的 iy 完全相同的内容。虽然有data.table 解决方案,但如果data.table 可以采用矩阵进行子集化,那就容易多了。
      • @Cole 我认为 S. Ritchie 正在 github/rdatatable 上做一些事情
      猜你喜欢
      • 1970-01-01
      • 2018-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-21
      • 2021-09-09
      相关资源
      最近更新 更多