Rimodellare data.frame da formato largo a lungo


164

Ho qualche problema a convertire il mio data.frameda una tabella ampia a una tabella lunga. Al momento sembra così:

Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246

Ora vorrei trasformarlo data.framein un lungo data.frame. Qualcosa come questo:

Code Country        Year    Value
AFG  Afghanistan    1950    20,249
AFG  Afghanistan    1951    21,352
AFG  Afghanistan    1952    22,532
AFG  Afghanistan    1953    23,557
AFG  Afghanistan    1954    24,555
ALB  Albania        1950    8,097
ALB  Albania        1951    8,986
ALB  Albania        1952    10,058
ALB  Albania        1953    11,123
ALB  Albania        1954    12,246

Ho esaminato e già provato a usare melt()le reshape()funzioni e come alcune persone stavano suggerendo in domande simili. Tuttavia, finora ottengo solo risultati disordinati.

Se è possibile, vorrei farlo con la reshape()funzione poiché sembra un po 'più bello da gestire.


2
Non so se questo fosse il problema, ma le funzioni nel pacchetto di risagoma sono fusione e cast (e rifusione)
Eduardo Leoni,

1
E il pacchetto reshape è stato sostituito da reshape2.
IRTFM,

5
E ora reshape2 è stato sostituito da tidyr.
drhagen,

Risposte:


93

reshape()ci vuole un po 'per abituarsi, proprio come melt/ cast. Ecco una soluzione con reshape, supponendo che il tuo frame di dati sia chiamato d:

reshape(d, 
        direction = "long",
        varying = list(names(d)[3:7]),
        v.names = "Value",
        idvar = c("Code", "Country"),
        timevar = "Year",
        times = 1950:1954)

153

Tre soluzioni alternative:

1) Con :

Puoi usare la stessa meltfunzione del reshape2pacchetto (che è un'implementazione estesa e migliorata). meltda data.tableha anche più parametri che la funzione meltda reshape2. Ad esempio, puoi anche specificare il nome della colonna variabile:

library(data.table)
long <- melt(setDT(wide), id.vars = c("Code","Country"), variable.name = "year")

che dà:

> long
    Code     Country year  value
 1:  AFG Afghanistan 1950 20,249
 2:  ALB     Albania 1950  8,097
 3:  AFG Afghanistan 1951 21,352
 4:  ALB     Albania 1951  8,986
 5:  AFG Afghanistan 1952 22,532
 6:  ALB     Albania 1952 10,058
 7:  AFG Afghanistan 1953 23,557
 8:  ALB     Albania 1953 11,123
 9:  AFG Afghanistan 1954 24,555
10:  ALB     Albania 1954 12,246

Alcune notazioni alternative:

melt(setDT(wide), id.vars = 1:2, variable.name = "year")
melt(setDT(wide), measure.vars = 3:7, variable.name = "year")
melt(setDT(wide), measure.vars = as.character(1950:1954), variable.name = "year")

2) Con :

library(tidyr)
long <- wide %>% gather(year, value, -c(Code, Country))

Alcune notazioni alternative:

wide %>% gather(year, value, -Code, -Country)
wide %>% gather(year, value, -1:-2)
wide %>% gather(year, value, -(1:2))
wide %>% gather(year, value, -1, -2)
wide %>% gather(year, value, 3:7)
wide %>% gather(year, value, `1950`:`1954`)

3) Con :

library(reshape2)
long <- melt(wide, id.vars = c("Code", "Country"))

Alcune notazioni alternative che danno lo stesso risultato:

# you can also define the id-variables by column number
melt(wide, id.vars = 1:2)

# as an alternative you can also specify the measure-variables
# all other variables will then be used as id-variables
melt(wide, measure.vars = 3:7)
melt(wide, measure.vars = as.character(1950:1954))

APPUNTI:

  • è in pensione. Verranno apportate solo le modifiche necessarie per mantenerlo su CRAN. ( fonte )
  • Se si desidera escludere NAi valori, è possibile aggiungere na.rm = TRUEalle meltoltre che le gatherfunzioni.

Un altro problema con i dati è che i valori saranno letti da R come valori-carattere (come risultato di ,nei numeri). Puoi ripararlo con gsube as.numeric:

long$value <- as.numeric(gsub(",", "", long$value))

O direttamente con data.tableo dplyr:

# data.table
long <- melt(setDT(wide),
             id.vars = c("Code","Country"),
             variable.name = "year")[, value := as.numeric(gsub(",", "", value))]

# tidyr and dplyr
long <- wide %>% gather(year, value, -c(Code,Country)) %>% 
  mutate(value = as.numeric(gsub(",", "", value)))

Dati:

wide <- read.table(text="Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246", header=TRUE, check.names=FALSE)

ottima risposta, solo un altro minuscolo promemoria: non inserire variabili diverse da ide timenel frame di dati, meltnon è possibile dire cosa si desidera fare in questo caso.
Jason Goal,

1
@JasonGoal Potresti approfondire questo? Mentre sto interpretando il tuo commento, non dovrebbe essere un problema. Basta specificare sia il id.varsche il measure.vars.
Jaap,

, allora va bene per me, non lo so id.varse measure.varspuò essere specificato nella prima alternativa, scusate il casino, è colpa mia.
Jason Goal,

Mi dispiace necro questo post - qualcuno potrebbe spiegarmi perché 3 funziona? L'ho provato e funziona, ma non capisco cosa stia facendo dplyr quando vede -c(var1, var2)...

1
@ReputableMisnomer Quando tidyr vede -c(var1, var2)che omette queste variabili durante la trasformazione dei dati da formato largo a lungo.
Jaap

35

Utilizzando il pacchetto di risagoma :

#data
x <- read.table(textConnection(
"Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246"), header=TRUE)

library(reshape)

x2 <- melt(x, id = c("Code", "Country"), variable_name = "Year")
x2[,"Year"] <- as.numeric(gsub("X", "" , x2[,"Year"]))

18

Con tidyr_1.0.0, un'altra opzione èpivot_longer

library(tidyr)
pivot_longer(df1, -c(Code, Country), values_to = "Value", names_to = "Year")
# A tibble: 10 x 4
#   Code  Country     Year  Value 
#   <fct> <fct>       <chr> <fct> 
# 1 AFG   Afghanistan 1950  20,249
# 2 AFG   Afghanistan 1951  21,352
# 3 AFG   Afghanistan 1952  22,532
# 4 AFG   Afghanistan 1953  23,557
# 5 AFG   Afghanistan 1954  24,555
# 6 ALB   Albania     1950  8,097 
# 7 ALB   Albania     1951  8,986 
# 8 ALB   Albania     1952  10,058
# 9 ALB   Albania     1953  11,123
#10 ALB   Albania     1954  12,246

dati

df1 <- structure(list(Code = structure(1:2, .Label = c("AFG", "ALB"), class = "factor"), 
    Country = structure(1:2, .Label = c("Afghanistan", "Albania"
    ), class = "factor"), `1950` = structure(1:2, .Label = c("20,249", 
    "8,097"), class = "factor"), `1951` = structure(1:2, .Label = c("21,352", 
    "8,986"), class = "factor"), `1952` = structure(2:1, .Label = c("10,058", 
    "22,532"), class = "factor"), `1953` = structure(2:1, .Label = c("11,123", 
    "23,557"), class = "factor"), `1954` = structure(2:1, .Label = c("12,246", 
    "24,555"), class = "factor")), class = "data.frame", row.names = c(NA, 
-2L))

1
Ciò richiede più voti. Secondo il Blog Tidyverse gather è in pensione ed pivot_longerè ora il modo corretto per farlo.
Evan Rosica,

16

Poiché questa risposta è taggata con , Ho sentito che sarebbe stato utile per condividere un'altra alternativa dalla base R: stack.

Nota, tuttavia, che stacknon funziona con factors - funziona solo se lo is.vectorè TRUE, e dalla documentazione per is.vector, troviamo che:

is.vectorritorna TRUEse x è un vettore della modalità specificata senza attributi diversi dai nomi . Ritorna FALSEaltrimenti.

Sto usando i dati di esempio dalla risposta di @ Jaap , dove i valori nelle colonne dell'anno sono factors.

Ecco l' stackapproccio:

cbind(wide[1:2], stack(lapply(wide[-c(1, 2)], as.character)))
##    Code     Country values  ind
## 1   AFG Afghanistan 20,249 1950
## 2   ALB     Albania  8,097 1950
## 3   AFG Afghanistan 21,352 1951
## 4   ALB     Albania  8,986 1951
## 5   AFG Afghanistan 22,532 1952
## 6   ALB     Albania 10,058 1952
## 7   AFG Afghanistan 23,557 1953
## 8   ALB     Albania 11,123 1953
## 9   AFG Afghanistan 24,555 1954
## 10  ALB     Albania 12,246 1954

11

Ecco un altro esempio che mostra l'uso di gatherfrom tidyr. Puoi selezionare le colonne gatherrimuovendole singolarmente (come faccio qui) o includendo esplicitamente gli anni che desideri.

Nota che, per gestire le virgole (e le X aggiunte se check.names = FALSEnon sono impostate), sto anche usando dplyrla mutazione con parse_numberda readrper convertire i valori del testo in numeri. Questi sono tutti parte del tidyversee quindi possono essere caricati insiemelibrary(tidyverse)

wide %>%
  gather(Year, Value, -Code, -Country) %>%
  mutate(Year = parse_number(Year)
         , Value = parse_number(Value))

Ritorna:

   Code     Country Year Value
1   AFG Afghanistan 1950 20249
2   ALB     Albania 1950  8097
3   AFG Afghanistan 1951 21352
4   ALB     Albania 1951  8986
5   AFG Afghanistan 1952 22532
6   ALB     Albania 1952 10058
7   AFG Afghanistan 1953 23557
8   ALB     Albania 1953 11123
9   AFG Afghanistan 1954 24555
10  ALB     Albania 1954 12246

4

Ecco un soluzione:

sqldf("Select Code, Country, '1950' As Year, `1950` As Value From wide
        Union All
       Select Code, Country, '1951' As Year, `1951` As Value From wide
        Union All
       Select Code, Country, '1952' As Year, `1952` As Value From wide
        Union All
       Select Code, Country, '1953' As Year, `1953` As Value From wide
        Union All
       Select Code, Country, '1954' As Year, `1954` As Value From wide;")

Per effettuare la query senza digitare tutto, è possibile utilizzare quanto segue:

Grazie a G. Grothendieck per averlo implementato.

ValCol <- tail(names(wide), -2)

s <- sprintf("Select Code, Country, '%s' As Year, `%s` As Value from wide", ValCol, ValCol)
mquery <- paste(s, collapse = "\n Union All\n")

cat(mquery) #just to show the query
 #> Select Code, Country, '1950' As Year, `1950` As Value from wide
 #>  Union All
 #> Select Code, Country, '1951' As Year, `1951` As Value from wide
 #>  Union All
 #> Select Code, Country, '1952' As Year, `1952` As Value from wide
 #>  Union All
 #> Select Code, Country, '1953' As Year, `1953` As Value from wide
 #>  Union All
 #> Select Code, Country, '1954' As Year, `1954` As Value from wide

sqldf(mquery)
 #>    Code     Country Year  Value
 #> 1   AFG Afghanistan 1950 20,249
 #> 2   ALB     Albania 1950  8,097
 #> 3   AFG Afghanistan 1951 21,352
 #> 4   ALB     Albania 1951  8,986
 #> 5   AFG Afghanistan 1952 22,532
 #> 6   ALB     Albania 1952 10,058
 #> 7   AFG Afghanistan 1953 23,557
 #> 8   ALB     Albania 1953 11,123
 #> 9   AFG Afghanistan 1954 24,555
 #> 10  ALB     Albania 1954 12,246

Sfortunatamente, non lo penso PIVOTe UNPIVOTfunzionerebbe R SQLite. Se vuoi scrivere la tua query in un modo più sofisticato, puoi anche dare un'occhiata a questi post:

Utilizzando la sprintfscrittura di query sql    o    passare le variabili asqldf


0

È inoltre possibile utilizzare il cdatapacchetto, che utilizza il concetto di tabella di controllo (trasformazione):

# data
wide <- read.table(text="Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246", header=TRUE, check.names=FALSE)

library(cdata)
# build control table
drec <- data.frame(
    Year=as.character(1950:1954),
    Value=as.character(1950:1954),
    stringsAsFactors=FALSE
)
drec <- cdata::rowrecs_to_blocks_spec(drec, recordKeys=c("Code", "Country"))

# apply control table
cdata::layout_by(drec, wide)

Attualmente sto esplorando quel pacchetto e lo trovo abbastanza accessibile. È progettato per trasformazioni molto più complicate e include la trasformazione posteriore. C'è un tutorial disponibile.

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.