Объединить строки в большую матрицу по имени строки - PullRequest
7 голосов
/ 15 ноября 2011

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

> M
  a b c d
1 1 1 2 0
1 2 3 4 2
2 3 0 1 2
3 4 2 5 2
> index <- as.numeric(rownames(M))
> M <- cbind(M,index)
> Dfmat <- data.frame(M)
> Dfmat <- aggregate(. ~ index, data = Dfmat, sum)
> M <- as.matrix(Dfmat)
> rownames(M) <- M[,"index"]
> M <- subset(M, select= -index)
> M
   a b c d
 1 3 4 6 2
 2 3 0 1 2
 3 4 2 5 2

Проблема этого метода состоит в том, что мне нужно применить его к ряду очень больших матриц (до 1000 строк и 30 000 столбцов). В этих случаях время вычислений очень велико (та же проблема при использовании ddply). Есть ли более эффективный способ найти решение? Помогает ли это, чтобы исходные входные матрицы были DocumentTermMatrix из пакета tm? Насколько я знаю, они хранятся в формате разреженной матрицы.

Ответы [ 3 ]

7 голосов
/ 15 ноября 2011

Вот решение, использующее by и colSums, но требует некоторого переворота из-за вывода по умолчанию by.

M <- matrix(1:9,3)
rownames(M) <- c(1,1,2)
t(sapply(by(M,rownames(M),colSums),identity))
  V1 V2 V3
1  3  9 15
2  3  6  9
2 голосов
/ 22 января 2016

В Matrix.utils теперь есть агрегатная функция. Это позволяет достичь желаемого с помощью одной строки кода и примерно в 10 раз быстрее, чем решение combineByRow, и в 100 раз быстрее, чем решение by:

N <- 10000

m <- matrix( runif(N*100), nrow=N)
rownames(m) <- sample(1:(N/2),N,replace=T)

> microbenchmark(a<-t(sapply(by(m,rownames(m),colSums),identity)),b<-combineByRow(m),c<-aggregate.Matrix(m,row.names(m)),times = 10)
Unit: milliseconds
                                                  expr        min         lq       mean     median         uq        max neval
 a <- t(sapply(by(m, rownames(m), colSums), identity)) 6000.26552 6173.70391 6660.19820 6419.07778 7093.25002 7723.61642    10
                                  b <- combineByRow(m)  634.96542  689.54724  759.87833  732.37424  866.22673  923.15491    10
                c <- aggregate.Matrix(m, row.names(m))   42.26674   44.60195   53.62292   48.59943   67.40071   70.40842    10

> identical(as.vector(a),as.vector(c))
[1] TRUE

РЕДАКТИРОВАТЬ: Фрэнк прав, rowms несколько быстрее, чем любое из этих решений. Вы хотели бы рассмотреть возможность использования другой из этих других функций, только если вы использовали Matrix, особенно разреженную, или если вы выполняли агрегацию помимо sum.

1 голос
/ 11 марта 2013

Ответ Джеймса работает, как и ожидалось, но довольно медленно для больших матриц.Вот версия, в которой избегает создания новых объектов :

combineByRow <- function(m) {
    m <- m[ order(rownames(m)), ]

    ## keep track of previous row name
    prev <- rownames(m)[1]
    i.start <- 1
    i.end <- 1

    ## cache the rownames -- profiling shows that it takes
    ## forever to look at them
    m.rownames <- rownames(m)
    stopifnot(all(!is.na(m.rownames)))


    ## go through matrix in a loop, as we need to combine some unknown
    ## set of rows
    for (i in 2:(1+nrow(m))) {

        curr <- m.rownames[i]

        ## if we found a new row name (or are at the end of the matrix),
        ## combine all rows and mark invalid rows
        if (prev != curr || is.na(curr)) {

            if (i.start < i.end) {
                m[i.start,] <- apply(m[i.start:i.end,], 2, max)
                m.rownames[(1+i.start):i.end] <- NA
            }

            prev <- curr
            i.start <- i
        } else {
            i.end <- i
        }
    }

    m[ which(!is.na(m.rownames)),]    
}

Тестирование показывает, что примерно в 10 раз быстрее, чем ответ, используя by (2 против 20 секунд в этом примере):

N <- 10000

m <- matrix( runif(N*100), nrow=N)
rownames(m) <- sample(1:(N/2),N,replace=T)

start <- proc.time()
m1 <- combineByRow(m)
print(proc.time()-start)

start <- proc.time()
m2 <- t(sapply(by(m,rownames(m),function(x) apply(x, 2, max)),identity))
print(proc.time()-start)

all(m1 == m2)
...