Remodelando data.frame do formato amplo para o longo

164

Estou com alguns problemas para converter minha data.frametabela ampla para uma tabela longa. No momento, fica assim:

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

Agora eu gostaria de transformar isso data.frameem um longo data.frame. Algo assim:

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

Eu olhei e já tentei usar melt()as reshape()funções e como algumas pessoas sugeriam em perguntas semelhantes. No entanto, até agora eu só tenho resultados confusos.

Se for possível, eu gostaria de fazê-lo com a reshape()função, pois parece um pouco mais agradável de manusear.

mropa
fonte
2
Não sei se esse era o problema, mas as funções no pacote remodelar são aliviar e elenco (e reformulação.)
Eduardo Leoni
1
E o pacote de remodelação foi substituído pelo remodelado2.
IRTFM
5
E agora remodelar2 foi substituído pelo tidyr.
drhagen

Respostas:

93

reshape()leva um tempo para se acostumar, assim como melt/ cast. Aqui está uma solução com remodelagem, assumindo que seu quadro de dados seja chamado d:

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

Três soluções alternativas:

1) Com :

Você pode usar a mesma meltfunção do reshape2pacote (que é uma implementação estendida e aprimorada). meltfrom data.tabletambém possui mais parâmetros do que a meltfunção from reshape2. Você também pode, por exemplo, especificar o nome da coluna variável:

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

que 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

Algumas notações alternativas:

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

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

Algumas notações alternativas:

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

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

Algumas notações alternativas que dão o mesmo resultado:

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

NOTAS:

  • está aposentado. Somente as alterações necessárias para mantê-lo no CRAN serão feitas. ( fonte )
  • Se você quiser excluir NAvalores, você pode adicionar na.rm = TRUEaos meltbem como as gatherfunções.

Outro problema com os dados é que os valores serão lidos por R como valores de caracteres (como resultado dos ,números). Você pode reparar isso com gsube as.numeric:

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

Ou diretamente com data.tableou 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)))

Dados:

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)
Jaap
fonte
ótima resposta, apenas mais um pequeno lembrete: não coloque nenhuma variável além de ide timeno seu quadro de dados; meltnão poderia dizer o que você deseja fazer neste caso.
Jason Goal
1
@JasonGoal Você poderia elaborar isso? Como estou interpretando o seu comentário, não deve ser um problema. Apenas especifique o id.varse o measure.vars.
19417 Jaap
, então isso é bom para mim, não sei id.varse measure.varspode ser especificado na primeira alternativa, desculpe pela bagunça, a culpa é minha.
Jason Goal
Desculpe por necro este post - alguém poderia me explicar por que 3 funciona? Eu testei isso e ele funciona, mas eu não entendo o que dplyr está fazendo quando se vê -c(var1, var2)...
1
@ReputableMisnomer Quando o tidyr-c(var1, var2), omite essas variáveis ​​ao transformar os dados de formato amplo para longo.
Jaap
35

Usando remodelar pacote:

#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"]))
Shane
fonte
18

Com tidyr_1.0.0, outra opção é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

dados

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))
akrun
fonte
1
Isso precisa de mais votos. De acordo com o Tidyverse Blog, gather está sendo aposentado e pivot_longeragora é a maneira correta de fazer isso.
Evan Rosica
16

Como esta resposta está marcada com , Eu senti que seria útil para compartilhar uma outra alternativa da base R: stack.

Observe, no entanto, que stacknão funciona com factors - só funciona se is.vectorestiver TRUEe, a partir da documentação de is.vector, descobrimos que:

is.vectorretorna TRUEse x é um vetor do modo especificado que não possui atributos além de nomes . Retorna o FALSEcontrário.

Estou usando os dados de exemplo da resposta de @ Jaap , onde os valores nas colunas do ano são factors.

Aqui está a stackabordagem:

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
A5C1D2H2I1M1N2O1R2T1
fonte
11

Aqui está outro exemplo mostrando o uso de gatherfrom tidyr. Você pode selecionar as colunas gatherremovendo-as individualmente (como eu faço aqui) ou incluindo os anos que deseja explicitamente.

Observe que, para lidar com as vírgulas (e os X adicionados, se check.names = FALSEnão estiverem definidos), também estou usando dplyr's mutate with parse_numberfrom readrpara converter os valores de texto em números. Tudo isso faz parte tidyversee pode ser carregado junto com olibrary(tidyverse)

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

Devoluções:

   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
Mark Peterson
fonte
4

Aqui está um solução:

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

Para fazer a consulta sem digitar tudo, você pode usar o seguinte:

Agradecimentos a G. Grothendieck por implementá-lo.

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

Infelizmente, eu não acho isso PIVOTe UNPIVOTiria trabalhar para isso R SQLite. Se você deseja escrever sua consulta de maneira mais sofisticada, também pode dar uma olhada nessas postagens:

Usando a sprintfescrita de consultas sql    ou    passar variáveis ​​parasqldf

M--
fonte
0

Você também pode usar o cdatapacote, que usa o conceito de tabela de controle (transformação):

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

Atualmente, estou explorando esse pacote e o acho bastante acessível. Ele foi projetado para transformações muito mais complicadas e inclui a retro-transformação. Existe um tutorial disponível.

Karsten W.
fonte