Come rimodellare i dati dal formato lungo a quello ampio


263

Ho problemi a riordinare il seguente frame di dati:

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

Voglio rimodellarlo in modo che ogni variabile "name" unica sia un rowname, con i "valori" come osservazioni lungo quella riga e i "numeri" come nomi di nomi. In questo modo:

     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

Ho visto melte castalcune altre cose, ma nessuna sembra fare il lavoro.



4
@Frank: questo è un titolo molto migliore. lungo-forma e livello di forma sono i termini standard utilizzati. L'altra risposta non può essere trovata cercando in quei termini.
smci,

un'altra domanda: come cambiarla?
HappyLiang

Risposte:


257

Utilizzando la reshapefunzione:

reshape(dat1, idvar = "name", timevar = "numbers", direction = "wide")

13
+1 e non è necessario fare affidamento su pacchetti esterni, poiché reshapeviene fornito con stats. Per non parlare del fatto che è più veloce! =)
aL3xa

@indra_patil - Probabilmente userò il pacchetto reshape2 come indicato in una delle altre risposte. Potresti creare una nuova domanda specifica per il tuo caso d'uso e pubblicarla se non riesci a capirlo.
Insegui il

5
reshapeè un esempio eccezionale di un'API di funzioni orribili. È molto vicino all'inutile.
NoBackingDown

14
I reshapecommenti e nomi di argomenti simili non sono poi così utili. Tuttavia, ho scoperto che per un lungo periodo devi fornire data =data.frame idvar= la variabile che identifica i tuoi gruppi, v.names= le variabili che diventeranno più colonne in formato largo, timevar= la variabile che contiene i valori che verranno aggiunti a v.namesin grande formato, direction = widee sep = "_". Abbastanza chiaro? ;)
Brian D,

3
Direi che la base R vince ancora dal punto di vista dei voti con un fattore di circa 2 a 1
vonjd

129

Anche il nuovo tidyrpacchetto (nel 2014) lo fa semplicemente, con gather()/ spread()essendo i termini per melt/ cast.

Modifica: ora, nel 2019, tidyr v 1.0 è stato lanciato e impostato spreade gathersu un percorso di deprecazione, preferendo invece pivot_widere pivot_longer, che puoi trovare descritto in questa risposta . Continua a leggere se vuoi dare una breve occhiata alla breve vita di spread/gather.

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

Da github ,

tidyrè una riformulazione reshape2progettata per accompagnare il quadro dati ordinato e per lavorare a stretto contatto con magrittre dplyrcostruire una solida pipeline per l'analisi dei dati.

Proprio come ha reshape2fatto meno di rimodellare, tidyrfa meno di reshape2. È progettato specificatamente per riordinare i dati, non per il rimodellamento generale che reshape2fa, o per l'aggregazione generale che ha fatto il rimodellamento. In particolare, i metodi integrati funzionano solo per i frame di dati e tidyrnon forniscono margini o aggregazioni.


5
Volevo solo aggiungere un link alla pagina R Cookbook che discute l'uso di queste funzioni da tidyre reshape2. Fornisce buoni esempi e spiegazioni.
Jake,

71

Puoi farlo con la reshape()funzione, o con le funzioni melt()/ cast()nel pacchetto reshape. Per la seconda opzione, il codice di esempio è

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

O usando reshape2

library(reshape2)
dcast(dat1, name ~ numbers)

2
Vale la pena notare che il solo utilizzo casto dcastnon funzionerà correttamente se non si dispone di una colonna "valore" chiara. Prova 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)e non otterrai ciò che ti aspetti. È necessario annotare esplicitamente il simbolo value/value.var- cast(dat, id ~ index, value="blah")e dcast(dat, id ~ index, value.var="blah")per esempio.
thelatemail,

45

Un'altra opzione se le prestazioni sono importanti è l'uso data.tabledell'estensione delle reshape2funzioni melt e dcast

( Riferimento: rimodellamento efficiente utilizzando 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

E, a partire da data.table v1.9.6, possiamo eseguire il cast su più colonne

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

5
data.tablel'approccio è il migliore! molto efficiente ... vedrai la differenza quando nameè una combinazione di 30-40 colonne !!
joel.wilson,

E se volessi prendere il massimo?
T.Fung

@ T.Fung Non capisco cosa stai chiedendo. Potrebbe essere meglio aprire una nuova domanda?
SymbolixAU

@SymbolixAU nella domanda di op 'nome' e 'numeri' sono combinazioni uniche. E se non lo fossero e volessi recuperare il valore massimo per ogni combinazione dopo il pivot? Non è un problema se una domanda troppo complicata. Solo cibo per i pensieri. Grazie.
T.Fung,

Bella risposta. Grazie. Per più colonne, ho ottenuto "Errore nel .subset2 (x, i, esatto = esatto)", e potrebbe risolvere il problema forzando l'uso di dcast data.table: vedo stackoverflow.com/a/44271092/190791
Timothée HENRY

26

Utilizzando il tuo esempio di frame di dati, potremmo:

xtabs(value ~ name + numbers, data = dat1)

2
questo è buono, ma il risultato è una tabella di formati che potrebbe non essere così facile da gestire come data.frame o data.table, entrambi hanno molti pacchetti
cloud calcola il

18

Altre due opzioni:

Pacchetto base:

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

sqldf pacchetto:

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

1
Invece di numeri hardcoding, la query può essere impostata in questo modo: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

Utilizzando la aggregatefunzione 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

11

Con la versione devel di tidyr ‘0.8.3.9000’, c'è pivot_widere pivot_longerche è generalizzato per fare il rimodellamento (rispettivamente long -> wide, wide -> long) da 1 a più colonne. Utilizzando i dati del PO

colonna singola lunga -> larga

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

-> ha creato un'altra colonna per mostrare la funzionalità

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

8

La reshapefunzione base funziona perfettamente:

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

Dove

  • idvar è la colonna di classi che separa le righe
  • timevar è la colonna di classi da diffondere
  • v.names è la colonna che contiene valori numerici
  • direction specifica il formato largo o lungo
  • l' separgomento facoltativo è il separatore utilizzato tra timevari nomi delle classi e v.namesnell'output data.frame.

Se non idvaresiste, creane uno prima di utilizzare la reshape()funzione:

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

Basta ricordare che idvarè richiesto! La timevare v.namesparte è facile. L'output di questa funzione è più prevedibile di alcuni degli altri, poiché tutto è esplicitamente definito.


7

C'è molto potente nuovo pacchetto da parte di scienziati di dati genio a Win-vettore (persone che hanno fatto vtreat, seplyre replyr) chiamato cdata. Implementa i principi dei "dati coordinati" descritti in questo documento e anche in questo post del blog . L'idea è che, indipendentemente da come si organizzano i dati, dovrebbe essere possibile identificare i singoli punti dati utilizzando un sistema di "coordinate dati". Ecco un estratto dal recente post sul blog di John Mount:

L'intero sistema si basa su due primitive o operatori cdata :: moveValuesToRowsD () e cdata :: moveValuesToColumnsD (). Questi operatori hanno codifica pivot, un-pivot, one-hot, trasposizione, spostamento di più righe e colonne e molte altre trasformazioni come semplici casi speciali.

È facile scrivere molte operazioni diverse in termini di primate cdata. Questi operatori possono lavorare in memoria o su larga scala di dati (con database e Apache Spark; per i big data usare le varianti cdata :: moveValuesToRowsN () e cdata :: moveValuesToColumnsN ()). Le trasformazioni sono controllate da una tabella di controllo che a sua volta è un diagramma (o immagine della) trasformazione.

Costruiremo prima la tabella di controllo (vedi post sul blog per i dettagli) e quindi eseguiremo lo spostamento dei dati da righe a colonne.

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

1

modo molto più semplice!

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

se vuoi tornare da largo a lungo, cambia solo da Largo a Lungo e nessun cambiamento negli oggetti.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.