Переставьте матрицу в R, используя два фактора

Вот в чем проблема. Существует матрица с N строками и C столбцами и двумя факторами: ids и group, оба имеют длину N. Например:

m <- matrix( 1:25, nrow= 5, byrow= T )
id <- factor( c( "A", "A", "A", "B", "B" ) )
group <- factor( c( "a", "b", "c", "a", "c" ) )

Не все комбинации факторов присутствуют, но каждая комбинация факторов присутствует только один раз. Задача состоит в том, чтобы преобразовать матрицу m так, чтобы в ней было length( levels( id ) ) строк и length( levels( group ) ) * C столбцов. Другими словами, создайте матрицу, в которой каждая переменная соответствует комбинации исходного столбца и всех возможных уровней фактора group. Отсутствующие значения (для несуществующих комбинаций идентификатора и группы) заменяются NA. Вот желаемый результат приведенного выше примера:

  a.1 a.2 a.3 a.4 a.5 b.1 b.2 b.3 b.4 b.5 c.1 c.2 c.3 c.4 c.5
A   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
B  16  17  18  19  20  NA  NA  NA  NA  NA  21  22  23  24  25

Я написал свою функцию, но она ужасно неэффективна и, я уверен, дублирует функциональность чего-то очень простого.

matrixReshuffle <- function( m, ids.row, factor.group ) {

  nr <- nrow( m )
  nc <- ncol( m )
  if( is.null( colnames( m ) ) ) colnames( m ) <- 1:nc

  ret <- NULL
  for( id in levels( ids.row ) ) {

    r <- c()

    for( fg in levels( factor.group ) ) {

      d <- m[ ids.row == id & factor.group == fg,, drop= F ]
      if( nrow( d ) > 1 )
        stop( sprintf( "Too many matches for ids.row= %s and factor.group= %s", id, fg ) )
      else if( nrow( d ) < 1 ) {
        r <- c( r, rep( NA, nc ) )
      } else {
        r <- c( r, d[1,] )
      }

    }

    ret <- rbind( ret, r )

  }

  colnames( ret ) <- paste( rep( levels( factor.group ), each= nc ), rep( colnames( m ), length( levels( factor.group ) ) ), sep= "." )
  rownames( ret ) <- levels( ids.row )

  return( ret )
}

person January    schedule 21.02.2013    source источник


Ответы (3)


Следуя предложениям @Aaron:

Использование melt и acast из reshape2:

require(reshape2)
df <- as.data.frame(m)
names(df) <- seq_len(ncol(df))
df.m <- melt(df)
df.m$id <- rep(id, nrow(df.m)/length(id))
df.m$group <- rep(group, nrow(df.m)/length(group))

o <- acast(df.m, id ~ group+variable, value.var="value")
colnames(o) <- sub("_", ".", colnames(o))

#   a.1 a.2 a.3 a.4 a.5 b.1 b.2 b.3 b.4 b.5 c.1 c.2 c.3 c.4 c.5
# A   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
# B  16  17  18  19  20  NA  NA  NA  NA  NA  21  22  23  24  25

Вы можете преобразовать это обратно в матрицу.

person Arun    schedule 21.02.2013
comment
+1 Красивое решение; как я сказал в своем комментарии к собственному ответу, я бы предпочел именно это. Вы можете рассмотреть acast, чтобы дать матрицу напрямую и чтобы имена совпадали, возможно, names(df) <- 1:5 перед плавлением и colnames(out) <- sub("_", ".", colnames(out)) после литья (где out - результат преобразования). - person Aaron left Stack Overflow; 22.02.2013
comment
@ Аарон, да, мне следовало использовать acast. Спасибо за это. Я хотел дать общую идею и оставить остальную часть редактирования OP. Но теперь, после вашего комментария, я отредактировал его, чтобы он был полным. - person Arun; 22.02.2013
comment
Мне потребовалось некоторое время, чтобы разобраться со страницами руководства :-) melt () и [ad] cast - отличные функции для обнаружения. - person January; 27.02.2013

Для всех поклонников матричной индексации ...

C <- ncol(m)
to.row <- matrix(rep(as.numeric(id), C), ncol=C)
to.col <- sweep(col(m),1,(as.numeric(group)-1)*C,`+`)
out <- array(dim=c(nlevels(id), nlevels(group)*C),
             dimnames=list(levels(id), as.vector(t(outer(levels(group), 1:C, paste, sep=".")))))
out[cbind(as.vector(to.row), as.vector(to.col))] <- m
out
#   a.1 a.2 a.3 a.4 a.5 b.1 b.2 b.3 b.4 b.5 c.1 c.2 c.3 c.4 c.5
# A   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
# B  16  17  18  19  20  NA  NA  NA  NA  NA  21  22  23  24  25
person Aaron left Stack Overflow    schedule 21.02.2013
comment
Я ценю проверку, но, честно говоря, решение @Arun с использованием reshape2 - это то, что я предпочел бы; это более простое решение, и идеи легче распространить на другие потребности в изменении формы. - person Aaron left Stack Overflow; 22.02.2013

Это версия ответа @Arun, слегка измененная, чтобы ее было легче (для меня) понять. Кроме того, я всегда с осторожностью отношусь к репликации групповых факторов; Я обнаружил, что на практике это один из потенциальных источников систематической ошибки. Лучше напрямую взять на себя id и группу и позволить функции melt () воспроизвести факторы. Но это мелочи мелочи.

# add the aggregating variables to the matrix, converted to data frame
df <- data.frame( m )
df$id <- id
df$group <- group

# reshape the data frame
require( reshape2 )
df.m <- melt( df, c( "id", "group" ) )
df <- dcast( df.m, id ~ group + variable )

# df has the required shape, but convert it back to a matrix
rownames( df ) <- df$id
df$id <- NULL
m.reshaped <- as.matrix( df )
person January    schedule 27.02.2013