2015-06-20 2 views
3

Я ищу данные о высокопроизводительных генах и делаю тип корреляционного анализа на основе байесовской статистики. Одна из вещей, которую мне нужно сделать, - найти каждую попарную комбинацию продуктов в наборе данных и найти сумму каждой результирующей строки.Сумма всех попарных продуктов ряда как двухсторонняя матрица

Так, например, для набора данных матрицы высокой пропускной Dataset

(Dataset <- structure(list(`Condition 1` = c(1L, 3L, 2L, 2L), `Condition 2` = c(2L, 1L, 7L, 2L), `Condition 3` = c(4L, 1L, 2L, 5L)), .Names = c("Condition 1", "Condition 2", "Condition 3"), class = "data.frame", row.names = c("Gene A", "Gene B", "Gene C", "Gene D"))) 
     Condition 1 Condition 2 Condition 3 
Gene A   1   2    4 
Gene B   3   1    1 
Gene C   2   7    2 
Gene D   2   2    5 

Прежде всего я хочу, чтобы умножить каждую возможную пару строк вместе, чтобы получить следующую матрицу под названием Comb:

   Condition 1 Condition 2 Condition 3 
Gene A Gene A   1   4   9 
Gene A Gene B   3   2   4 
Gene A Gene C   2   14   8 
Gene A Gene D   2   4   20 
Gene B Gene B   9   1   1 
Gene B Gene C   6   7   2 
Gene B Gene D   6   2   5 
Gene C Gene C   4   49   4 
Gene C Gene D   4   14   10 
Gene D Gene D   4   4   25 

После I хотите найти суммы строк для каждого продукта и получить суммы в виде матрицы (которую я назову CombSums):

  Gene A  Gene B  Gene C  Gene D 
Gene A   NA   10   24   26 
Gene B   10   NA   15   13 
Gene C   24   15   NA   28 
Gene D   26   13   28   NA 

Когда я попытался сделать это, лучшее, что я мог придумать было

combs <- combn(seq_len(nrow(Dataset)), 2) 
Comb <- Dataset[combs[1,], ] * Dataset[combs[2,], ] 
rownames(Comb) <- apply(combn(rownames(Comb), 2), 2, paste, collapse = " ") 
CombSums <- rowSums(Comb) 

Какой бы дает мне суммы в виде списка, например, как показано ниже:

    [1,] 
Gene A Gene B  10 
Gene A Gene C  24 
Gene A Gene D  26 
Gene B Gene C  15 
Gene B Gene D  13 
Gene C Gene D  28 

К сожалению, я хотите, чтобы это как двухсторонняя матрица, а не список, так что это не работает, поэтому, если кто-нибудь может предложить способ получить суммы в виде матрицы, это будет большой помощью.

+1

Я считаю, что значения (1, 2) и (2, 1) должны быть 9 (3 + 2 + 4), а не 10. – josliber

+1

В матрице 'Comb' (1,3) должно быть 16 не 9 –

ответ

2

Если скорость является важным фактором (например, если вы обрабатываете огромную матрицу), вы могли бы найти реализацию Rcpp полезной. Это только заполняет верхнюю треугольную часть матрицы.

library(Rcpp) 
cppFunction(
"NumericMatrix josilberRcpp(NumericMatrix x) { 
    const int nr = x.nrow(); 
    const int nc = x.ncol(); 
    NumericMatrix y(nr, nr); 
    for (int col=0; col < nc; ++col) { 
    for (int i=0; i < nr; ++i) { 
     for (int j=i; j < nr; ++j) { 
     y(i, j) += x(i, col) * x(j, col); 
     } 
    } 
    } 
    return y; 
}") 
josilberRcpp(as.matrix(Dataset)) 
#  [,1] [,2] [,3] [,4] 
# [1,] 21 9 24 26 
# [2,] 0 11 15 13 
# [3,] 0 0 57 28 
# [4,] 0 0 0 33 

Бенчмаркинг предоставляется в моем другом ответе. Обратите внимание, что в бенчмаркинг не входит время компиляции с использованием cppFunction, что может быть весьма значительным. Поэтому эта реализация, вероятно, полезна только для очень больших входов или когда вам нужно использовать эту функцию много раз.

+0

Судя по выходу OP, вы также можете пропустить диагональ: 'for (int j = i + 1 ...' (или что-то в этом роде) – Frank

+0

Так будет ли другой ответ josilber быстрее для меньших наборов данных, но это будет это сообщение, которое быстрее для больших наборов данных? (Я не знаком с пакетом Rcpp, поэтому извиняюсь, если это кажется тривиальным вопросом). Но я буду смотреть на данные с высокой пропускной способностью, так что, вероятно, будет 10 000+ генов во многих условиях (20-30 или даже больше). Я бы предположил, что такой набор данных будет достаточно большим, чтобы метод в этом сообщении был бы быстрее? Также, как бы добавить в имена строк и столбцов (например, например, Gene A, Gene B и т. д.) до конечной матрицы? –

+1

@NelLau Для добавления имен вы можете использовать 'res <- josilberRcpp (x); dimnames (res) <- list (rownames (x), rownames (x)) 'или оберните эти два шага внутри другой функции, после чего вы можете использовать имена для подмножества, такие как' res ["Gene A", "Gene B"] '. Я подозреваю, что, работая с генетическими данными, вы захотите пойти с подходом Rcpp, да. Тем не менее, я бы предложил просто попробовать как эту, так и функцию josilber2, так как вам лучше всего найти, что лучше для вашего приложения. – Frank

4

Вы можете сделать это путем вычисления попарных произведений для каждого столбца в первоначальном кадре данных с lapply и outer, а затем вы можете добавить все те попарные произведения вместе с Reduce и +.

Reduce("+", lapply(dat, function(x) outer(x, x))) 
#  [,1] [,2] [,3] [,4] 
# [1,] 21 9 24 26 
# [2,] 9 11 15 13 
# [3,] 24 15 57 28 
# [4,] 26 13 28 33 

вариация на эту тему, которая менее интенсивно использующих память (потому что не нужно хранить матрицу каждого столбца в то же время), но более типирование будет:

ret <- outer(dat[,1], dat[,1]) 
for (i in 2:ncol(dat)) ret <- ret + outer(dat[,i], dat[,i]) 
ret 
#  [,1] [,2] [,3] [,4] 
# [1,] 21 9 24 26 
# [2,] 9 11 15 13 
# [3,] 24 15 57 28 
# [4,] 26 13 28 33 

Вот тест из предложенных подходов до сих пор на раме 100 х 100 данных:

# Larger dataset 
set.seed(144) 
dat <- as.data.frame(matrix(rnorm(10000), nrow=100)) 

josilber <- function(dat) Reduce("+", lapply(dat, function(x) outer(x, x))) 
josilber2 <- function(dat) { 
    ret <- outer(dat[,1], dat[,1]) 
    for (i in 2:ncol(dat)) ret <- ret + outer(dat[,i], dat[,i]) 
    ret 
} 
frank <- function(DF) { 
    mat <- as.matrix(DF) 
    pairs <- combn(1:nrow(DF),2) 
    vals <- rowSums(mat[pairs[1,],]*mat[pairs[2,],]) 
    res <- matrix(,nrow(DF),nrow(DF),dimnames=list(rownames(DF),rownames(DF))) 
    res[lower.tri(res)] <- vals 
    res 
} 

library(microbenchmark) 
microbenchmark(josilber(dat), josilber2(dat), josilberRcpp(as.matrix(dat)), frank(dat)) 
# Unit: microseconds 
#       expr  min  lq  mean median  uq  max neval 
#     josilber(dat) 6867.499 45437.277 45506.731 46372.576 47549.834 85494.063 100 
#    josilber2(dat) 6831.692 7982.539 10245.459 9109.023 10883.965 50612.600 100 
# josilberRcpp(as.matrix(dat)) 989.592 1112.316 1290.617 1204.388 1483.638 2384.348 100 
#     frank(dat) 13043.912 53369.804 52488.997 53921.402 54855.583 62566.730 100 
+0

Интересно. Я бы догадался, что внешний будет медленнее (поскольку он должен делать более чем в два раза больше вычислений). – Frank

+1

@Frank true, но не нужно вычислять пары «пар» промежуточных объектов, 'DF [пар [1,],]' или 'DF [пар [2,],]'. – josliber

+0

Я думаю, что мой значительно меньше памяти интенсивный ...? Результат этого 'lapply' довольно огромен и заставил мой компьютер висеть, когда я проверил' n = 500; dat <- as.data.frame (матрица (rnorm (n^2), nrow = n)) '. Кстати, я просто изменил свой метод, чтобы преобразовать в матрицу, так что это не так уж плохо для вашего теста. Слишком плохо, что нет «Сокращения», который будет вычислять элементы списка последовательно (так что вам нужно только два в памяти за раз) ... Я думаю, что цикл может сделать это. – Frank

4

Использование combn, вы можете не делать лишние вычисления:

mat <- as.matrix(DF) 

pairs <- combn(1:nrow(DF),2) 

vals <- rowSums(mat[pairs[1,],]*mat[pairs[2,],]) 
res <- matrix(,nrow(DF),nrow(DF),dimnames=list(rownames(DF),rownames(DF))) 
res[lower.tri(res)] <- vals 

#  GeneA GeneB GeneC GeneD 
# GeneA NA NA NA NA 
# GeneB  9 NA NA NA 
# GeneC 24 15 NA NA 
# GeneD 26 13 28 NA 

Ваша матрица Comb является промежуточным результатом mat[pairs[1,],]*mat[pairs[2,],].


Весь расчет можно сделать внутри combn, поочередно:

vals <- combn(rownames(DF),2,FUN=function(x)sum(apply(DF[x,],2,prod))) 

Как @josilber отметил в комментарии ниже, это невероятно медленно, однако.


данные:

DF <- read.table(header=TRUE,text="Condition1 Condition2 Condition3 
GeneA   1   2    4 
GeneB   3   1    1 
GeneC   2   7    2 
GeneD   2   2    5") 
+1

Вторая (функция «combn») довольно медленная (8 секунд для моего примера 100 x 100). – josliber

+1

Я думаю, что проблема со вторым заключается в том, что он не хорошо векторизован (он выполняет вызов '*' и 'sum' для каждой пары генов). Тем временем ваш первый выполняет одно умножение и один вызов 'rowSums'. – josliber

Смежные вопросы