【问题标题】:For each row, find column which is closest to a specified value对于每一行,找到最接近指定值的列
【发布时间】:2020-06-15 18:20:28
【问题描述】:

我有一个数据集,其中包含一个 ID 变量和数千列平均值。下面是一个可重现的示例。对于每个 ID,我想选择包含最接近 0.50 的值的列名。如果有平局,请选择最低值。有没有一种有效的方法来做到这一点(最好使用 dplyr 或 data.table)?

df = data.frame(ID = paste("ID", 1:1000, sep = ""),
                matrix(rnorm(20000), nrow=10))

> df[1:5, 1:5]

   ID         X1          X2          X3          X4
1 ID1 -0.5532944 -1.20671805  0.75142048  0.56022595
2 ID2 -1.0083010 -0.01534611  1.53546691 -0.08762588
3 ID3 -0.1606776 -0.96947669 -0.38631278 -1.15647134
4 ID4 -0.5957471 -0.20918120 -0.05246698 -0.84235789
5 ID5  0.1569595 -0.62460245 -0.39454014  0.91089249

我的目标是拥有一个包含 ID 变量和列名的数据框,其中包含最接近 0.5 的值以及该值。

   ID    T      P
1 ID1  X10 0.5671
2 ID2 X100 0.4999
3 ID3  X34 0.5877
4 ID4  X21 0.5055
5 ID5  X15 0.4987

【问题讨论】:

  • 相关:For each row return the column name of the largest value。首先抓取与 0.5 偏差最小的列,ci = max.col(-abs(d[ , -1] - 0.5))。然后data.frame(id = d[ , 1], nm = names(d[ , -1])[ci], val = d[ , -1][cbind(seq_len(nrow(d)), ci)])
  • 请使用set.seed(<fixed_value>) 使随机数可重现。谢谢。

标签: r dplyr data.table


【解决方案1】:

这是一种不同的方法,它使用 melt() 将数据集从宽格式重新调整为长格式。

# create sample data: ID has constant length, values are rounded to 3 digits
set.seed(2020)
df = data.frame(ID = sprintf("ID%04i", 1:1000),
                matrix(round(rnorm(20000), 3), nrow=10))
target <- 0.5

library(data.table)
long <- melt(setDT(df), "ID")
long[, .SD[which.min(abs(value - target))], by = ID]
          ID variable value
   1: ID0001    X1924 0.501
   2: ID0002    X1440 0.499
   3: ID0003     X906 0.500
   4: ID0004     X180 0.503
   5: ID0005    X1757 0.498
  ---                      
 996: ID0996    X1568 0.500
 997: ID0997     X565 0.501
 998: ID0998     X613 0.502
 999: ID0999    X1344 0.500
1000: ID1000    X1018 0.501

现在,OP has requested 选择较低的值以防出现平局。这可以通过订购来实现:

long[order(ID, value), .SD[which.min(abs(value - target))], by = ID]
          ID variable value
   1: ID0001    X1924 0.501
   2: ID0002    X1440 0.499
   3: ID0003     X906 0.500
   4: ID0004     X180 0.503
   5: ID0005    X1757 0.498
  ---                      
 996: ID0996    X1568 0.500
 997: ID0997     X565 0.501
 998: ID0998     X613 0.502
 999: ID0999    X1344 0.500
1000: ID1000    X1971 0.499

注意第 1000 行的差异。

通过链接 data.table 表达式,该语句可以写成“单行”:

melt(setDT(df), "ID")[order(ID, value), .SD[which.min(abs(value - target))], by = ID]

还要注意示例数据集已经修改

  1. set.seed() 用于确保生成的随机数是可重现的。
  2. 通过使用sprintf("ID%04i", 1:1000) 代替paste()ID 具有固定长度。这有助于保持一致的排序顺序。
  3. 随机数四舍五入为 3 位数,以使其更有可能遇到平局。

【讨论】:

  • 不同的链接,可以说更具可读性:setDF(df);df[, melt(.SD, "ID")][order(ID, value), .SD[which.min(abs(value - target))], by = ID]
  • @sindri_baldur,您可以从链中删除;dfsetDT(df)[, melt(.SD, "ID")][order(ID, value), .SD[which.min(abs(value - target))], by = ID],但这比melt(setDT(df), "ID")[order(ID, value), .SD[which.min(abs(value - target))], by = ID] 稍慢。
【解决方案2】:

在出现平局时应始终选择较低值的基本 R 解决方案:

num_cols_idx <- which(sapply(df, is.numeric))
min_vec <- sapply(split(df, rownames(df)), function(x) {
  sorted_named_vec <- sort(unlist(x[num_cols_idx]))
  names(sorted_named_vec)[which.min(abs(sorted_named_vec - 0.5))]
  }, 
simplify = TRUE)

【讨论】:

    【解决方案3】:

    我已将示例代码简化为较小的子集,以节省我的处理器工作时间:

    data.frame(
        ID = df[1:5,1],
        T = apply(df[1:5, 2:5],1, function(x) colnames(df)[which.min(abs(x - 0.5))]),
        P = apply(df[1:5, 2:5],1, function(x) x[which.min(abs(x - 0.5))])
    )
    

    【讨论】:

    • 如果列值之间存在关联,这将选择较低的值吗?
    • min.which 盲目地选择它看到的第一个 min,因此无论哪个 min 最靠近向量的头部。欺骗它的唯一方法是首先将列从最低到最高排序
    【解决方案4】:

    我想这就是你想要的。因为您的播放数据会重复,所以我会检查其他一些更随机的数据。是的,它使用它找到的第一个“X”变量,这就是我假设你所说的“最低”的意思。

    library(dplyr)
    
    set.seed(2020)
    
    df <- data.frame(ID = paste("ID", 1:1000, sep = ""),
                    matrix(rnorm(20000), nrow=10))
    
    
    
    
    results <- df %>%
      rowwise %>%
      summarise(ID = ID, 
                col_index = which.min(abs(c_across(X1:X2000) - 0.5)) + 1,
                whichcolumn = colnames(.[col_index]),
                value = nth(c_across(X1:X2000), which.min(abs(c_across(X1:X2000) - .5))),
                .groups = "rowwise")
    
    results
    #> # A tibble: 1,000 x 4
    #> # Rowwise: 
    #>    ID    col_index whichcolumn value
    #>    <chr>     <dbl> <chr>       <dbl>
    #>  1 ID1        1925 X1924       0.501
    #>  2 ID2        1441 X1440       0.499
    #>  3 ID3         907 X906        0.500
    #>  4 ID4         181 X180        0.503
    #>  5 ID5        1758 X1757       0.498
    #>  6 ID6        1569 X1568       0.500
    #>  7 ID7         566 X565        0.501
    #>  8 ID8        1448 X1447       0.502
    #>  9 ID9        1345 X1344       0.500
    #> 10 ID10       1019 X1018       0.501
    #> # … with 990 more rows
    

    【讨论】:

    • 我在使用 c_across 函数时遇到了问题。我不知道为什么,但 R 找不到它。它是 dplyr 的一部分吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多