Cómo remodelar datos de formato largo a ancho

263

Tengo problemas para reorganizar el siguiente marco de datos:

set.seed(45)
dat1 <- data.frame(
    name = rep(c("firstName", "secondName"), each=4),
    numbers = rep(1:4, 2),
    value = rnorm(8)
    )

dat1
       name  numbers      value
1  firstName       1  0.3407997
2  firstName       2 -0.7033403
3  firstName       3 -0.3795377
4  firstName       4 -0.7460474
5 secondName       1 -0.8981073
6 secondName       2 -0.3347941
7 secondName       3 -0.5013782
8 secondName       4 -0.1745357

Quiero remodelarlo para que cada variable única de "nombre" sea un nombre de fila, con los "valores" como observaciones a lo largo de esa fila y los "números" como colnames. Algo así como esto:

     name          1          2          3         4
1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
5 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

Lo he visto melty castalgunas otras cosas, pero ninguna parece hacer el trabajo.

Steve
fuente
3
posible duplicado del marco de datos de tres columnas Reshape a matriz
Frank
44
@ Frank: este es un título mucho mejor. largo forma y de toda la forma son los términos estándar utilizados. La otra respuesta no se puede encontrar buscando en esos términos.
smci
Una pregunta más: ¿cómo volver a cambiarlo?
HappyLiang

Respuestas:

257

Usando la reshapefunción:

reshape(dat1, idvar = "name", timevar = "numbers", direction = "wide")
Persecución
fuente
13
+1 y no necesita depender de paquetes externos, ya que reshapeviene con stats. ¡Sin mencionar que es más rápido! =)
aL3xa
@indra_patil: probablemente usaría el paquete reshape2 como se indica en una de las otras respuestas. Puede crear una nueva pregunta específica para su caso de uso y publicarla si no puede resolverla.
Chase
55
reshapees un excelente ejemplo para una horrible función API. Está muy cerca de inútil.
NoBackingDown
14
Los reshapecomentarios y nombres de argumentos similares no son tan útiles. Sin embargo, he descubierto que, de largo a ancho, debe proporcionar data =su data.frame, idvar= la variable que identifica sus grupos, v.names= las variables que se convertirán en columnas múltiples en formato ancho, timevar= la variable que contiene los valores que se agregarán a v.namesen formato ancho direction = wide, y sep = "_". ¿Bastante claro? ;)
Brian D
3
Yo diría que la base R todavía gana por voto en un factor de aproximadamente 2 a 1
vonjd
129

El nuevo tidyrpaquete (en 2014) también hace esto simplemente, con gather()/ spread()siendo los términos para melt/ cast.

Editar: Ahora, en 2019, tidyr v 1.0 ha puesto en marcha y conjunto spready gatheren un camino de desaprobación, prefiriendo en su lugar pivot_widery pivot_longer, donde puede encontrar descrito en esta respuesta . Siga leyendo si desea un breve vistazo a la breve vida de spread/gather.

library(tidyr)
spread(dat1, key = numbers, value = value)

De github ,

tidyres un nuevo reshape2diseño diseñado para acompañar el ordenado marco de datos, y para trabajar de la mano magrittry dplyrconstruir una tubería sólida para el análisis de datos.

Al igual que reshape2hizo menos que remodelar, tidyrhace menos que reshape2. Está diseñado específicamente para ordenar los datos, no la remodelación general que reshape2sí lo hace, o la agregación general que se modificó. En particular, los métodos integrados solo funcionan para marcos de datos y tidyrno proporcionan márgenes ni agregación.

Gregor Thomas
fuente
55
Solo quería agregar un enlace a la página R Cookbook que discute el uso de estas funciones desde tidyry reshape2. Proporciona buenos ejemplos y explicaciones.
Jake
71

Puede hacer esto con la reshape()función, o con las funciones melt()/ cast()en el paquete de remodelación. Para la segunda opción, el código de ejemplo es

library(reshape)
cast(dat1, name ~ numbers)

O usando reshape2

library(reshape2)
dcast(dat1, name ~ numbers)
Ista
fuente
2
Vale la pena señalar que simplemente usar casto dcastno funcionará bien si no tiene una columna clara de "valor". Intenta dat <- data.frame(id=c(1,1,2,2),blah=c(8,4,7,6),index=c(1,2,1,2)); dcast(dat, id ~ index); cast(dat, id ~ index)y no obtendrás lo que esperas. Debe anotar explícitamente el value/value.var- cast(dat, id ~ index, value="blah")y dcast(dat, id ~ index, value.var="blah")por ejemplo.
thelatemail
45

Otra opción si el rendimiento es una preocupación es usar data.tablela extensión de reshape2las funciones melt & dcast de

( Referencia: remodelación eficiente usando data.tables )

library(data.table)

setDT(dat1)
dcast(dat1, name ~ numbers, value.var = "value")

#          name          1          2         3         4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814

Y, a partir de data.table v1.9.6 podemos lanzar en múltiples columnas

## add an extra column
dat1[, value2 := value * 2]

## cast multiple value columns
dcast(dat1, name ~ numbers, value.var = c("value", "value2"))

#          name    value_1    value_2   value_3   value_4   value2_1   value2_2 value2_3  value2_4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078  0.3672866 -1.6712572 3.190562 0.6590155
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814 -1.6409368  0.9748581 1.476649 1.1515627
SymbolixAU
fuente
55
data.tableenfoque es el mejor! muy eficiente ... ¡verás la diferencia cuando namehay una combinación de 30-40 columnas!
joel.wilson
¿Qué pasa si quisiera tomar el máximo?
T.Fung
@ T.Fung No entiendo lo que estás preguntando. ¿Sería mejor abrir una nueva pregunta?
SymbolixAU
@SymbolixAU en la pregunta de operación 'nombre' y 'números' son combinaciones únicas. ¿Qué pasaría si no lo fueran y quisiera obtener el valor máximo para cada combinación después de pivotar? No es un problema si es una pregunta demasiado complicada. Solo alimento para los pensamientos. Gracias.
T.Fung
Gran respuesta. Gracias. Para varias columnas, recibí "Error en .subset2 (x, i, exacto = exacto)", y podría solucionarlo forzando el uso de data.table dcast: consulte stackoverflow.com/a/44271092/190791
Timothée HENRY
26

Usando su marco de datos de ejemplo, podríamos:

xtabs(value ~ name + numbers, data = dat1)
Jim M.
fuente
2
este es bueno, pero el resultado es una tabla de formato que puede no ser tan fácil de manejar como data.frame o data.table, ambos tienen muchos paquetes
cloudcomputes
18

Otras dos opciones:

Paquete base:

df <- unstack(dat1, form = value ~ numbers)
rownames(df) <- unique(dat1$name)
df

sqldf paquete:

library(sqldf)
sqldf('SELECT name,
      MAX(CASE WHEN numbers = 1 THEN value ELSE NULL END) x1, 
      MAX(CASE WHEN numbers = 2 THEN value ELSE NULL END) x2,
      MAX(CASE WHEN numbers = 3 THEN value ELSE NULL END) x3,
      MAX(CASE WHEN numbers = 4 THEN value ELSE NULL END) x4
      FROM dat1
      GROUP BY name')
mpalanco
fuente
1
En lugar de codificar números, la consulta se puede configurar de esta manera:ValCol <- unique(dat1$numbers);s <- sprintf("MAX(CASE WHEN numbers = %s THEN value ELSE NULL END) `%s`,", ValCol, ValCol);mquerym <- gsub('.{1}$','',paste(s, collapse = "\n"));mquery <- paste("SELECT name,", mquerym, "FROM dat1", "GROUP BY name", sep = "\n");sqldf(mquery)
M--
13

Usando la aggregatefunción base R :

aggregate(value ~ name, dat1, I)

# name           value.1  value.2  value.3  value.4
#1 firstName      0.4145  -0.4747   0.0659   -0.5024
#2 secondName    -0.8259   0.1669  -0.8962    0.1681
Ronak Shah
fuente
11

Con la versión de desarrollo de tidyr ‘0.8.3.9000’, hay pivot_widerypivot_longer que se generaliza para hacer la remodelación (largo -> ancho, ancho -> largo, respectivamente) de 1 a varias columnas. Usando los datos del OP

-una columna larga -> ancha

library(dplyr)
library(tidyr)
dat1 %>% 
    pivot_wider(names_from = numbers, values_from = value)
# A tibble: 2 x 5
#  name          `1`    `2`    `3`    `4`
#  <fct>       <dbl>  <dbl>  <dbl>  <dbl>
#1 firstName   0.341 -0.703 -0.380 -0.746
#2 secondName -0.898 -0.335 -0.501 -0.175

-> creó otra columna para mostrar la funcionalidad

dat1 %>% 
    mutate(value2 = value * 2) %>% 
    pivot_wider(names_from = numbers, values_from = c("value", "value2"))
# A tibble: 2 x 9
#  name       value_1 value_2 value_3 value_4 value2_1 value2_2 value2_3 value2_4
#  <fct>        <dbl>   <dbl>   <dbl>   <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
#1 firstName    0.341  -0.703  -0.380  -0.746    0.682   -1.41    -0.759   -1.49 
#2 secondName  -0.898  -0.335  -0.501  -0.175   -1.80    -0.670   -1.00    -0.349
akrun
fuente
8

La reshapefunción base funciona perfectamente bien:

df <- data.frame(
  year   = c(rep(2000, 12), rep(2001, 12)),
  month  = rep(1:12, 2),
  values = rnorm(24)
)
df_wide <- reshape(df, idvar="year", timevar="month", v.names="values", direction="wide", sep="_")
df_wide

Dónde

  • idvar es la columna de clases que separa las filas
  • timevar es la columna de clases para emitir
  • v.names es la columna que contiene valores numéricos
  • direction especifica formato ancho o largo
  • El separgumento opcional es el separador utilizado entre timevarlos nombres de clase y v.namesen la salida data.frame.

Si no idvarexiste, cree uno antes de usar la reshape()función:

df$id   <- c(rep("year1", 12), rep("year2", 12))
df_wide <- reshape(df, idvar="id", timevar="month", v.names="values", direction="wide", sep="_")
df_wide

¡Solo recuerda que idvares obligatorio! La parte timevary v.nameses fácil. La salida de esta función es más predecible que algunas de las otras, ya que todo está explícitamente definido.

Adam Erickson
fuente
7

Hay nueva muy potente paquete de datos científicos genio en Win-vectorial (gente que hizo vtreat, seplyry replyr) se llama cdata. Implementa los principios de "datos coordinados" descritos en este documento y también en esta publicación de blog . La idea es que, independientemente de cómo organice sus datos, debería ser posible identificar puntos de datos individuales utilizando un sistema de "coordenadas de datos". Aquí hay un extracto de la reciente publicación de blog de John Mount:

Todo el sistema se basa en dos primitivas u operadores cdata :: moveValuesToRowsD () y cdata :: moveValuesToColumnsD (). Estos operadores tienen pivote, un-pivote, codificación en caliente, transposición, movimiento de múltiples filas y columnas, y muchas otras transformaciones como casos especiales simples.

Es fácil escribir muchas operaciones diferentes en términos de las primitivas cdata. Estos operadores pueden trabajar en la memoria o en una escala de datos grandes (con bases de datos y Apache Spark; para datos grandes, use las variantes cdata :: moveValuesToRowsN () y cdata :: moveValuesToColumnsN ()). Las transformaciones son controladas por una tabla de control que en sí misma es un diagrama de (o una imagen de) la transformación.

Primero construiremos la tabla de control (vea la publicación del blog para más detalles) y luego realizaremos el movimiento de datos de filas a columnas.

library(cdata)
# first build the control table
pivotControlTable <- buildPivotControlTableD(table = dat1, # reference to dataset
                        columnToTakeKeysFrom = 'numbers', # this will become column headers
                        columnToTakeValuesFrom = 'value', # this contains data
                        sep="_")                          # optional for making column names

# perform the move of data to columns
dat_wide <- moveValuesToColumnsD(tallTable =  dat1, # reference to dataset
                    keyColumns = c('name'),         # this(these) column(s) should stay untouched 
                    controlTable = pivotControlTable# control table above
                    ) 
dat_wide

#>         name  numbers_1  numbers_2  numbers_3  numbers_4
#> 1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
#> 2 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357
dmi3kno
fuente
1

mucho más fácil!

devtools::install_github("yikeshu0611/onetree") #install onetree package

library(onetree)
widedata=reshape_toWide(data = dat1,id = "name",j = "numbers",value.var.prefix = "value")
widedata

        name     value1     value2     value3     value4
   firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
  secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

si desea volver de ancho a largo, solo cambie Ancho a Largo, y no cambie los objetos.

reshape_toLong(data = widedata,id = "name",j = "numbers",value.var.prefix = "value")

        name numbers      value
   firstName       1  0.3407997
  secondName       1 -0.8981073
   firstName       2 -0.7033403
  secondName       2 -0.3347941
   firstName       3 -0.3795377
  secondName       3 -0.5013782
   firstName       4 -0.7460474
  secondName       4 -0.1745357
zhang jing
fuente