【问题标题】:Cross-referencing data frames without using for loops在不使用 for 循环的情况下交叉引用数据帧
【发布时间】:2015-05-23 13:20:43
【问题描述】:

我对使用 for 循环交叉引用 2 个数据帧的速度有疑问。总体目标是识别数据框 2 中位于数据框 1 中指定的坐标之间的行(并满足其他标准)。例如df1:

    chr     start       stop        strand
1   chr1    179324331   179327814   +
2   chr21   45176033    45182188    +
3   chr5    126887642   126890780   +
4   chr5    148730689   148734146   +

df2:

    chr     start       strand
1   chr1    179326331   +
2   chr21   45175033    +
3   chr5    126886642   +
4   chr5    148729689   +

我目前的代码是:

for (index in 1:nrow(df1)) { 
  found_miRNAs <- ""
  curr_row = df1[index, ]; 
for (index2 in 1:nrow(df2)){
    curr_target = df2[index2, ]
    if (curr_row$chrm == curr_target$chrm & curr_row$start < curr_target$start & curr_row$stop > curr_target$start & curr_row$strand == curr_target$strand) {
      found_miRNAs <- paste(found_miRNAs, curr_target$start, sep=":")
    }
  }
  curr_row$miRNAs <- found_miRNAs
  found_log <- rbind(Mcf7_short_aUTRs2,curr_row)
}

我的实际数据帧对于 df1 是 400 行,对于 df2 是 > 100 000 行,我希望进行 500 次迭代,因此,您可以想象这非常慢。我对 R 比较陌生,所以任何可以提高效率的函数的提示都会很棒。

【问题讨论】:

标签: r for-loop dataframe


【解决方案1】:

也许不够快,但可能更快且更容易阅读:

df1 <- data.frame(foo=letters[1:5], start=c(1,3,4,6,2), end=c(4,5,5,9,4))
df2 <- data.frame(foo=letters[1:5], start=c(3,2,5,4,1))
where <- sapply(df2$start, function (x) which(x >= df1$start & x <= df1$end))

这将为您提供 df2 中每一行的 df1 中相关行的列表。我刚刚尝试了 df1 中的 500 行和 df2 中的 50000 行。它在一两秒钟内完成。

要添加条件,请更改 sapply 中的内部函数。如果您想将where 放入第二个数据框中,您可以这样做,例如

df2$matching_rows <- sapply(where, paste, collapse=":")

但您可能希望将其保留为列表,这是它的自然数据结构。

其实,你甚至可以在数据框中有一个列表列:

df2$matching_rows <- where

虽然这很不寻常。

【讨论】:

    【解决方案2】:

    您遇到了人们从另一种编程语言转向 R 时最常犯的两个错误。使用 for 循环而不是基于向量的操作并动态附加到数据对象。我建议随着您的流利程度越来越高,您可以花点时间阅读Patrick Burns' R Inferno,它提供了对这些问题和其他问题的一些有趣的见解。

    正如@David Arenburg 和@zx8754 在上面的cmets 中指出的那样,有专门的包可以解决这个问题,data.table 包和@David 的方法对于更大的数据集非常有效。但是对于您的案例库,R 也可以非常有效地完成您需要的工作。我将在此处记录一种方法,为了清晰起见,需要多几个步骤,以防您有兴趣:

    set.seed(1001)
    
    ranges <- data.frame(beg=rnorm(400))
    ranges$end <- ranges$beg + 0.005
    
    test <- data.frame(value=rnorm(100000))
    ##  Add an ID field for duplicate removal:
    test$ID <- 1:nrow(test)
    
    
    ##  This is where you'd set your criteria.  The apply() function is just 
    ##      a wrapper for a for() loop over the rows in the ranges data.frame:
    out <- apply(ranges, MAR=1, function(x) test[ (x[1] < test$value & x[2] > test$value), "ID"])
    
    selected <- unlist(out)
    selected <- unique( selected )
    
    selection <- test[ selected, ]
    

    【讨论】:

    • 我向@Jonathan Nieves 道歉,最初接受的答案颠倒了正在相互检查的 data.frame 对象。我已经颠倒了这些,但解决方案的核心保持不变,并且对于如此小的数据集仍然相对有效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-10
    • 1970-01-01
    • 2020-02-26
    • 2023-02-05
    • 1970-01-01
    • 2011-02-22
    相关资源
    最近更新 更多