Изменить кадр данных с тремя столбцами на матрицу ( "длинный" до "широкого" формата)

У меня есть data.frame, который выглядит так.

x a 1 
x b 2 
x c 3 
y a 3 
y b 3 
y c 2 

Я хочу, чтобы это было в матричной форме, поэтому я могу прокормить его, чтобы создать график. Результат должен выглядеть примерно так:

    a    b    c
x   1    2    3
y   3    3    2

Я попробовал cast из пакета reshape, и я попытался написать ручную функцию, чтобы сделать это, но я, похоже, не в состоянии понять это.

Ответ 1

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

tmp <- data.frame(x=gl(2,3, labels=letters[24:25]),
                  y=gl(3,1,6, labels=letters[1:3]), 
                  z=c(1,2,3,3,3,2))

Использование Tidyverse:

Новый крутой новый способ сделать это с pivot_wider из tidyr 1.0.0. Он возвращает фрейм данных, что, вероятно, и будет желать большинство читателей этого ответа. Однако для тепловой карты вам необходимо преобразовать ее в истинную матрицу.

library(tidyr)
pivot_wider(tmp, names_from = y, values_from = z)
## # A tibble: 2 x 4
## x         a     b     c
## <fct> <dbl> <dbl> <dbl>
## 1 x       1     2     3
## 2 y       3     3     2

Старый крутой новый способ сделать это с spread из tidyr. Точно так же возвращает фрейм данных.

library(tidyr)
spread(tmp, y, z)
##   x a b c
## 1 x 1 2 3
## 2 y 3 3 2

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

Одним из первых шагов на пути к Tidyverse стал пакет reshape2.

Чтобы получить матрицу, используйте acast:

library(reshape2)
acast(tmp, x~y, value.var="z")
##   a b c
## x 1 2 3
## y 3 3 2

Или чтобы получить фрейм данных, используйте dcast, как здесь: Измените данные для значений в одном столбце.

dcast(tmp, x~y, value.var="z")
##   x a b c
## 1 x 1 2 3
## 2 y 3 3 2

Использование plyr:

Между reshape2 и tidyverse появился plyr с функцией daply, как показано здесь: fooobar.com/questions/43126/...

library(plyr)
daply(tmp, .(x, y), function(x) x$z)
##    y
## x   a b c
##   x 1 2 3
##   y 3 3 2

Использование матричной индексации:

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

with(tmp, {
  out <- matrix(nrow=nlevels(x), ncol=nlevels(y),
                dimnames=list(levels(x), levels(y)))
  out[cbind(x, y)] <- z
  out
})

Используя xtabs:

xtabs(z~x+y, data=tmp)

Использование разреженной матрицы:

Также есть sparseMatrix в пакете Matrix, как показано здесь: R - преобразовать БОЛЬШУЮ таблицу в матрицу по именам столбцов

with(tmp, sparseMatrix(i = as.numeric(x), j=as.numeric(y), x=z,
                       dimnames=list(levels(x), levels(y))))
## 2 x 3 sparse Matrix of class "dgCMatrix"
##   a b c
## x 1 2 3
## y 3 3 2

Используя reshape:

Вы также можете использовать базовую функцию R reshape, как предлагается здесь: Преобразовать таблицу в матрицу по именам столбцов, хотя впоследствии вам придется немного поработать, чтобы удалить лишние столбцы и получить правильные имена ( не показано).

reshape(tmp, idvar="x", timevar="y", direction="wide")
##   x z.a z.b z.c
## 1 x   1   2   3
## 4 y   3   3   2

Ответ 2

Вопрос несколько лет, но, возможно, некоторые люди все еще интересуются альтернативными ответами.

Если вы не хотите загружать какие-либо пакеты, вы можете использовать эту функцию:

#' Converts three columns of a data.frame into a matrix -- e.g. to plot 
#' the data via image() later on. Two of the columns form the row and
#' col dimensions of the matrix. The third column provides values for
#' the matrix.
#' 
#' @param data data.frame: input data
#' @param rowtitle string: row-dimension; name of the column in data, which distinct values should be used as row names in the output matrix
#' @param coltitle string: col-dimension; name of the column in data, which distinct values should be used as column names in the output matrix
#' @param datatitle string: name of the column in data, which values should be filled into the output matrix
#' @param rowdecreasing logical: should the row names be in ascending (FALSE) or in descending (TRUE) order?
#' @param coldecreasing logical: should the col names be in ascending (FALSE) or in descending (TRUE) order?
#' @param default_value numeric: default value of matrix entries if no value exists in data.frame for the entries
#' @return matrix: matrix containing values of data[[datatitle]] with rownames data[[rowtitle]] and colnames data[coltitle]
#' @author Daniel Neumann
#' @date 2017-08-29
data.frame2matrix = function(data, rowtitle, coltitle, datatitle, 
                             rowdecreasing = FALSE, coldecreasing = FALSE,
                             default_value = NA) {

  # check, whether titles exist as columns names in the data.frame data
  if ( (!(rowtitle%in%names(data))) 
       || (!(coltitle%in%names(data))) 
       || (!(datatitle%in%names(data))) ) {
    stop('data.frame2matrix: bad row-, col-, or datatitle.')
  }

  # get number of rows in data
  ndata = dim(data)[1]

  # extract rownames and colnames for the matrix from the data.frame
  rownames = sort(unique(data[[rowtitle]]), decreasing = rowdecreasing)
  nrows = length(rownames)
  colnames = sort(unique(data[[coltitle]]), decreasing = coldecreasing)
  ncols = length(colnames)

  # initialize the matrix
  out_matrix = matrix(NA, 
                      nrow = nrows, ncol = ncols,
                      dimnames=list(rownames, colnames))

  # iterate rows of data
  for (i1 in 1:ndata) {
    # get matrix-row and matrix-column indices for the current data-row
    iR = which(rownames==data[[rowtitle]][i1])
    iC = which(colnames==data[[coltitle]][i1])

    # throw an error if the matrix entry (iR,iC) is already filled.
    if (!is.na(out_matrix[iR, iC])) stop('data.frame2matrix: double entry in data.frame')
    out_matrix[iR, iC] = data[[datatitle]][i1]
  }

  # set empty matrix entries to the default value
  out_matrix[is.na(out_matrix)] = default_value

  # return matrix
  return(out_matrix)

}

Как это работает:

myData = as.data.frame(list('dim1'=c('x', 'x', 'x', 'y','y','y'),
                            'dim2'=c('a','b','c','a','b','c'),
                            'values'=c(1,2,3,3,3,2))) 

myMatrix = data.frame2matrix(myData, 'dim1', 'dim2', 'values')

myMatrix
>   a b c
> x 1 2 3
> y 3 3 2

Ответ 3

база R, unstack

unstack(df, V3 ~ V2)
#   a b c
# 1 1 2 3
# 2 3 3 2

Это может не быть общим решением, но в этом случае хорошо работает.

данные

df<-structure(list(V1 = structure(c(1L, 1L, 1L, 2L, 2L, 2L), .Label = c("x", 
"y"), class = "factor"), V2 = structure(c(1L, 2L, 3L, 1L, 2L, 
3L), .Label = c("a", "b", "c"), class = "factor"), V3 = c(1L, 
2L, 3L, 3L, 3L, 2L)), .Names = c("V1", "V2", "V3"), class = "data.frame", row.names = c(NA, 
-6L))

Ответ 4

Для полноты, есть tapply().

with(d, tapply(z, list(x, y), sum))
#   a b c
# x 1 2 3
# y 3 3 2

Данные

d <- structure(list(x = structure(c(1L, 1L, 1L, 2L, 2L, 2L), .Label = c("x", 
"y"), class = "factor"), y = structure(c(1L, 2L, 3L, 1L, 2L, 
3L), .Label = c("a", "b", "c"), class = "factor"), z = c(1, 2, 
3, 3, 3, 2)), class = "data.frame", row.names = c(NA, -6L))

Ответ 5

Из tidyr 0.8.3.9000 вводится новая функция под названием pivot_wider(). По сути, это обновленная версия предыдущей функции spread() (которая, кроме того, больше не находится в активной разработке). Из поворотной виньетки:

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

В течение некоторого времени было очевидно, что есть что-то принципиально неправильно с дизайном распространения() и собирать(). Многие люди не находят имена интуитивно понятны и трудно вспомнить, в каком направлении соответствует распространению и какой сбор. Тоже кажется на удивление трудно запомнить аргументы этих функций, Это означает, что многие люди (включая меня!) должны проконсультироваться с документация каждый раз.

Как его использовать (используя данные @Aaron):

pivot_wider(data = tmp, names_from = y, values_from = z)

  x         a     b     c
  <fct> <dbl> <dbl> <dbl>
1 x         1     2     3
2 y         3     3     2

Или "полностью" tidyverse:

tmp %>% 
 pivot_wider(names_from = y, values_from = z)

Ответ 6

Пакет tidyr от tidyverse имеет отличную функцию, которая делает это.

Предполагая, что ваши переменные называются v1, v2 и v3, слева направо, а ваш фрейм данных называется dat:

dat %>% 
spread(key = v2,
       value = v3)

Та да!