将三列数据框架重新形成矩阵(“长”到“宽”格式)

我有一个像这样的 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从重塑包和我已经尝试写一个手动函数这样做,但我似乎不能得到它的权利。

103023 次浏览

有很多方法可以做到这一点。这个答案开始什么是迅速成为标准的方法,但也包括老的方法和各种其他方法的答案类似的问题散布在这个网站。

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:

新的很酷的新方法是使用来自 tidyr 1.0.0pivot_wider。它返回一个数据框架,这可能是这个答案的大多数读者想要的。但是,对于热图,您需要将其转换为真正的矩阵。

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

旧的很酷的新方法是使用 tidyr中的 spread,它同样返回一个数据帧。

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

使用 respe2 :

迈向整洁宇宙的第一步就是重塑包装。

使用 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功能,如下所示: https://stackoverflow.com/a/7020101/210673

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)

使用稀疏矩阵:

Matrix包中还有 sparseMatrix,如图所示: 通过列名将 BIG 表转换为矩阵

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

这个问题已经有些年头了,但是也许有些人仍然对其他的答案感兴趣。

如果不想加载任何包,可以使用以下函数:

#' 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

来自 Tidyverse 的 Tidyr 软件包有一个很好的功能,可以做到这一点。

假设您的变量名为 v1、 v2和 v3,从左到右,您的数据帧名为 dat:

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

当当当!

基地 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))

为了完整起见,有一个 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))

tidyr 0.8.3.9000开始,引入了一个新的函数 pivot_wider()。它基本上是以前的 spread()功能 (此外,它已不再处于积极发展阶段)的升级版本。来自 旋转的小插曲:

这个小插图描述了新 pivot _ long ()和 Pivot _ wide ()函数 收集()和传播() ,并结合所发现的最先进的特性 在其他包裹里。

一段时间以来,很明显有一些根本性的东西 传播()和聚集()的设计错误。许多人没有发现 名称直观,发现很难记住哪个方向 对应于传播和收集。它也似乎 很难记住这些函数的参数, 这意味着许多人(包括我!)必须咨询 文件。

如何使用它(使用@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)