web-dev-qa-db-fra.com

Remodelez le cadre de données de trois colonnes en matrice (format "long" à "large")

J'ai un data.frame ça ressemble à ça.

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

Je veux ceci sous forme de matrice afin que je puisse le nourrir à heatmap pour faire un tracé. Le résultat devrait ressembler à quelque chose comme:

    a    b    c
x   1    2    3
y   3    3    2

J'ai essayé cast à partir du paquetage reshape et j'ai essayé d'écrire une fonction manuelle pour le faire, mais je ne semble pas être capable de le faire correctement.

119
MalteseUnderdog

Il y a plusieurs façons de le faire. Cette réponse commence par mes méthodes préférées, mais recueille également diverses méthodes à partir de réponses à des questions similaires disséminées sur ce site.

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

Utilisation du tidyverse:

La nouvelle façon de procéder consiste à utiliser spread de Tidyr. Il retourne une trame de données, ce qui est probablement ce que la plupart des lecteurs de cette réponse voudront. Pour une carte thermique, cependant, vous auriez besoin de convertir cela en une vraie matrice.

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

Utilisation de reshape2 :

Le paquetage reshape2 a été l’un des premiers pas vers l’aide de Tidyverse. Je pense toujours que pour de nombreuses tâches de refaçonnage, les fonctions melt et *cast Sont plus propres et plus simples que les méthodes simples.

Pour obtenir une matrice, utilisez acast:

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

Ou pour obtenir un cadre de données, utilisez dcast, comme ici: Remodelez les données pour les valeurs dans une colonne .

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

Utiliser plyr :

Entre reshape2 et tidyverse, plyr, avec la fonction daply, comme indiqué ici: https://stackoverflow.com/a/7020101/21067

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

Utilisation de l'indexation matricielle:

C’est un peu la vieille école, mais c’est une belle démonstration de l’indexation matricielle, qui peut être très utile dans certaines situations.

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

Utilisation de xtabs:

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

En utilisant une matrice creuse:

Il y a aussi sparseMatrix dans le paquet Matrix, comme on le voit ici: R - convertir une table BIG en matrice par noms de colonnes

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

Utilisation de reshape:

Vous pouvez également utiliser la fonction de base R reshape, comme suggéré ici: Convertir un tableau en matrice par noms de colonnes , bien que vous deviez faire une petite manipulation par la suite pour supprimer des colonnes supplémentaires et obtenir les noms à droite (non représentés).

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
171
Aaron

La question a quelques années mais peut-être que certaines personnes sont toujours intéressées par des réponses alternatives.

Si vous ne voulez pas charger de paquet, vous pouvez utiliser cette fonction:

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

}

Comment ça marche:

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
2
daniel.neumann

Par souci d'exhaustivité, il existe une solution tapply() autour.

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

Données

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))
2
jay.sf

base R, unstack

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

Cela peut ne pas être une solution générale mais fonctionne bien dans ce cas.

les données

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))
2
lebatsnok

À partir de tidyr 0.8.3.9000, Une nouvelle fonction appelée pivot_wider() est introduite. Il s’agit en fait d’une version améliorée de la précédente spread() fonction (qui n’est d'ailleurs plus en développement actif) . De vignette pivotante :

Cette vignette décrit l'utilisation des nouvelles fonctions pivot_longer () et pivot_wider (). Leur objectif est d’améliorer la convivialité de collecte () et de propagation (), et d’incorporer des fonctionnalités de pointe trouvées dans d’autres packages.

Depuis quelque temps, il était évident qu’il y avait quelque chose de fondamentalement faux dans la conception de spread () et de apply (). Beaucoup de gens ne trouvent pas les noms intuitifs et ont du mal à se rappeler quelle direction correspond à la diffusion et quelle direction. Il semble également étonnamment difficile de retenir les arguments de ces fonctions, ce qui signifie que de nombreuses personnes (y compris moi!) Doivent consulter la documentation à chaque fois.

Comment l'utiliser (en utilisant les données de @Aaron):

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

Ou de manière "complète" tidyverse:

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

Le paquet tidyr de tidyverse a une excellente fonction pour ce faire.

En supposant que vos variables s'appellent v1, v2 et v3, de gauche à droite, et que votre cadre de données s'appelle dat:

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

Ta da!

1
Ahsen Omar Majid