R优化双循环,矩阵操作

时间:2015-07-03 01:56:04

标签: r matrix dataframe

我试图在两列矩阵中操纵列数据并将其作为data.frame输出。

我所拥有的矩阵采用这种格式,其中起始列和结束列中的值都在增加且不重叠。此外,始终条目总是比结束条目多。

假设我从这个矩阵开始:

#       Start   End
#  [1,]     1     6
#  [2,]     2     9
#  [3,]     3    15
#  [4,]     7    NA
#  [5,]     8    NA
#  [6,]    11    NA
#  [7,]    12    NA
#  [8,]    14    NA

我希望这个double for循环输出一个data.frame,它将所有Start值分组为小于End值并将其与该End值相关联。

澄清我想输出这个:

#       Start   End
#  1    1,2,3     6
#  2      7,8     9
#  3 11,12,14    15

我尝试了一个双循环,但我需要更快的东西,因为我想在更大的矩阵~5 MB上使用此方法。

start_end <- matrix(c(1, 6, 2, 9, 3, 15, 7, NA, 8, NA, 11, NA, 12, NA, 14, NA), 
  nrow=8, 
  ncol=2)

# of non NA rows in column 2
non_nacol <- sum(is.na(start_end[,2]))

sorted.output <- data.frame(matrix(NA, nrow = nrow(start_end), ncol = 0))
sorted.output$start <- 0
sorted.output$end <- 0

#Sort and populate data frame
for (k in 1:non_nacol) {
  for (j in 1:nrow(start_end)) {
        if (start_end[j,1]<start_end[k,2]) {
        S <- (start_end[j,1])
        E <- (start_end[k,2])
        sorted.output$start[j] <- S
        sorted.output$end[j] <- E
        }
  }
}

感谢您的帮助!

4 个答案:

答案 0 :(得分:5)

以下是围绕findInterval()split()paste()构建的解决方案:

m <- matrix(c(1,2,3,7,8,11,12,14,6,9,15,NA,NA,NA,NA,NA),ncol=2,dimnames=list(NULL,c('Start','End')));
data.frame(Start=sapply(split(m[,'Start'],findInterval(m[,'Start'],na.omit(m[,'End']))),paste,collapse=','),End=na.omit(m[,'End']));
##      Start End
## 0    1,2,3   6
## 1      7,8   9
## 2 11,12,14  15

编辑:您遇到的问题是由于您的实际数据中输入End值之间的某些间隔不包含任何输入Start值。我上面的解决方案错误地忽略了输出Start向量中的那些区间,这导致长度与输出End向量不匹配。

这是一个固定的解决方案:

end <- na.omit(m[,'End']);
data.frame(Start=unname(sapply(split(m[,'Start'],findInterval(m[,'Start'],end))[as.character(0:c(length(end)-1))],paste,collapse=',')),End=end);
##      Start End
## 1    1,2,3   6
## 2      7,8   9
## 3 11,12,14  15

以下是对具有空间隔的测试矩阵的演示:

m <- matrix(c(1,2,3,11,12,14,6,9,15,NA,NA,NA),ncol=2,dimnames=list(NULL,c('Start','End')));
m;
##      Start End
## [1,]     1   6
## [2,]     2   9
## [3,]     3  15
## [4,]    11  NA
## [5,]    12  NA
## [6,]    14  NA
end <- na.omit(m[,'End']);
data.frame(Start=unname(sapply(split(m[,'Start'],findInterval(m[,'Start'],end))[as.character(0:c(length(end)-1))],paste,collapse=',')),End=end);
##      Start End
## 1    1,2,3   6
## 2            9
## 3 11,12,14  15

正如您所看到的,对于空间隔,导致输出Start向量的值是空字符串,我认为这是一个明智的结果。如果需要,您可以在以后更改结果。

最后,这是一个使用你发布到dropbox的真实数据的演示:

m <- read.table('start_end.txt',col.names=c('Start','End'));
head(m);
##   Start   End
## 1 11165 10548
## 2 12416 11799
## 3 12466 11900
## 4 12691 11976
## 5 12834 13336
## 6 13320 14028
end <- na.omit(m[,'End']);
system.time({ out <- data.frame(Start=unname(sapply(split(m[,'Start'],findInterval(m[,'Start'],end))[as.character(0:c(length(end)-1))],paste,collapse=',')),End=end); });
##    user  system elapsed
##  21.234   0.015  21.251
head(out);
##                           Start   End
## 1                               10548
## 2                         11165 11799
## 3                               11900
## 4                               11976
## 5 12416,12466,12691,12834,13320 13336
## 6       13425,13571,13703,13920 14028
nrow(out);
## [1] 131668

答案 1 :(得分:3)

你可以使用Rcpp:

start_end <- matrix(c(1, 6, 2, 9, 3, 15, 7, NA, 8, NA, 11, NA, 12, NA, 14, NA), 
                    nrow=8, 
                    ncol=2, byrow = TRUE)

library(Rcpp)
cppFunction('
            DataFrame fun(const IntegerMatrix& Mat) {
              IntegerVector start = na_omit(Mat(_, 0)); // remove NAs from starts
              std::sort(start.begin(), start.end()); // sort starts
              IntegerVector end = na_omit(Mat(_, 1)); // remove NAs from ends
              std::sort(end.begin(), end.end()); // sort ends
              IntegerVector res = clone(start); // initialize vector for matching ends
              int j = 0;
              for (int i = 0; i < start.length(); i++) { // loop over starts
                while (end(j) < start(i) && j < (end.length() - 1)) { // find corresponding end
                  j++;
                }
                if (end(j) >= start(i)) res(i) = end(j); // assign end
                else res(i) = NA_INTEGER; // assign NA if no end >= start exists
              }
              return DataFrame::create(_["start"]= start, _["end"]= res); // return a data.frame
            }
            ')

Res <- fun(start_end)

library(data.table)
setDT(Res)
Res[, .(start = paste(start, collapse = ",")), by = end]
#   end    start
#1:   6    1,2,3
#2:   9      7,8
#3:  15 11,12,14

答案 2 :(得分:2)

这是一个简单的基础R版本

with(as.data.frame(dat), {
  data.frame(
    Start=tapply(Start, cut(Start, c(0, End)), c),
    End=na.omit(End)
  )
})
#        Start End
# 1    1, 2, 3   6
# 2       7, 8   9
# 3 11, 12, 14  15

另一个

with(as.data.frame(dat), {
  group <- as.integer(cut(Start, c(0, End)))                  # assign Start values to End groups
  data.frame(
    Start=unclass(by(dat, group, function(g) g[["Start"]])),  # combine Start groups
    End=unique(na.omit(End))                                  # Remove duplicate/NA End values
  )
})

答案 3 :(得分:2)

一个丑陋的dplyr解决方案:

library(dplyr)
df <- as.data.frame(df)

df %>% mutate(End = V2[findInterval(V1, na.omit(V2)) + 1]) %>%
       group_by(End) %>%
       summarise(Start = paste(V1, collapse=", "))

编辑 - 感谢@bgoldst

使用findInterval