Remodelando data.frame do formato largo para o formato longo
Pergunta
Eu tenho alguns problemas para converter meu data.frame
de uma mesa larga para uma mesa longa.No momento está 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.frame
em 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 o melt()
e a reshape()
Funções como algumas pessoas estavam sugerindo em perguntas semelhantes.No entanto, até agora só obtive resultados confusos.
Se for possível, gostaria de fazê-lo com o reshape()
Função, pois parece um pouco mais agradável de manusear.
Solução
reshape()
leva um tempo para se acostumar, assim como melt
/cast
. Aqui está uma solução com remodelamento, 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)
Outras dicas
Três soluções alternativas:
1: com reshape2
library(reshape2)
long <- melt(wide, id.vars = c("Code", "Country"))
dando:
Code Country variable 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 anotaçõ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))
2: com data.table
Você pode usar o mesmo melt
função como no reshape2
pacote (que é uma implementação estendida e aprimorada). melt
a partir de data.table
também tem mais parâmetros que o melt
-função de reshape2
. Você 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")
Algumas anotaçõ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")
3: com tidyr
library(tidyr)
long <- wide %>% gather(year, value, -c(Code, Country))
Algumas anotaçõ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`)
Se você quiser excluir NA
valores, você pode adicionar na.rm = TRUE
para o melt
assim como o gather
funções.
Outro problema com os dados é que os valores serão lidos por r como valores de caracteres (como resultado do ,
nos números). Você pode consertar isso com gsub
e as.numeric
:
long$value <- as.numeric(gsub(",", "", long$value))
Ou diretamente com data.table
ou 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)
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"]))
Já que esta resposta é marcada com R-faq, Senti que seria útil compartilhar outra alternativa da base r: stack
.
Nota, no entanto, que stack
não funciona com factor
s-ele só funciona se is.vector
é TRUE
, e da documentação para is.vector
, descobrimos que:
is.vector
retornaTRUE
Se x é um vetor do modo especificado sem atributos além de nomes. Ele retornaFALSE
por outro lado.
Estou usando os dados de amostra da resposta de @jaap, onde estão os valores nas colunas do ano factor
s.
Aqui está o stack
abordagem:
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
Aqui está outro exemplo mostrando o uso de gather
a partir de tidyr
. Você pode selecionar as colunas para gather
ou removendo -os individualmente (como eu faço aqui) ou incluindo os anos que você deseja explicitamente.
Observe que, para lidar com as vírgulas (e X é adicionado se check.names = FALSE
não está definido), eu também estou usando dplyr
é mutação com parse_number
a partir de readr
Para converter os valores de texto de volta aos números. Tudo isso faz parte do tidyverse
E assim pode ser carregado junto com library(tidyverse)
wide %>%
gather(Year, Value, -Code, -Country) %>%
mutate(Year = parse_number(Year)
, Value = parse_number(Value))
Retornos:
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
Aqui está um sqldf 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:
Graças a G.Grothendieck para 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 não acho isso PIVOT
e UNPIVOT
trabalharia para R
SQLite
.Se quiser escrever sua consulta de uma forma mais sofisticada, você também pode dar uma olhada nestes posts:
Usando sprintf
escrevendo consultas sql Ou Passe variáveis para sqldf
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))