Изменение формы data.frame из широкого в длинный формат
У меня есть некоторые проблемы, чтобы преобразовать мой data.frame
от широкого стола к длинному столу. На данный момент это выглядит так:
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
Теперь мне нравится преобразовывать это data.frame
в длинный data.frame
, Что-то вроде этого:
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
Я посмотрел и попробовал это уже с melt()
и reshape()
функции, как некоторые люди предлагали аналогичные вопросы. Однако пока я получаю только грязные результаты.
Если это возможно, я хотел бы сделать это с reshape()
функция, так как она выглядит немного приятнее в обращении.
5 ответов
reshape()
требует времени, чтобы привыкнуть, так же, как melt
/cast
, Вот решение с изменением формы, если предположить, что ваш фрейм данных называется d
:
reshape(d, direction = "long", varying = list(names(d)[3:7]), v.names = "Value",
idvar = c("Code","Country"), timevar = "Year", times = 1950:1954)
Три альтернативных решения:
1: сreshape2
library(reshape2)
long <- melt(wide, id.vars = c("Code", "Country"))
давая:
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
Некоторые альтернативные обозначения, которые дают тот же результат:
# 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: сdata.table
Вы можете использовать то же самое melt
функционировать как в reshape2
пакет (который является расширенной и улучшенной реализацией). melt
от data.table
имеет также больше параметров, что melt
-функция от reshape2
, Например, вы также можете указать имя переменной-столбца:
library(data.table)
long <- melt(setDT(wide), id.vars = c("Code","Country"), variable.name = "year")
Некоторые альтернативные обозначения:
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: сtidyr
library(tidyr)
long <- wide %>% gather(year, value, -c(Code, Country))
Некоторые альтернативные обозначения:
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`)
Если вы хотите исключить NA
значения, вы можете добавить na.rm = TRUE
к melt
так же хорошо как gather
функции.
Другая проблема с данными состоит в том, что значения будут читаться R как символьные значения (в результате ,
в цифрах). Вы можете восстановить это с gsub
а также as.numeric
:
long$value <- as.numeric(gsub(",", "", long$value))
Или напрямую с data.table
или же 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)))
Данные:
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)
С участием tidyr_1.0.0
, другой вариант - 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
данные
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))
Используя пакет изменения формы:
#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"]))
Поскольку этот ответ помечен как r-faq, я подумал, что было бы полезно поделиться другой альтернативой из базы R: stack
,
Обратите внимание, однако, что stack
не работает с factor
s- это работает только если is.vector
является TRUE
и из документации по is.vector
мы находим, что:
is.vector
возвращаетсяTRUE
если x - вектор указанного режима, не имеющий никаких атрибутов, кроме имен. ВозвращаетсяFALSE
иначе.
Я использую пример данных из ответа @Jaap, где значения в столбцах года factor
s.
Вот stack
подход:
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
Вот еще один пример, показывающий использование gather
от tidyr
, Вы можете выбрать столбцы для gather
либо удалив их по отдельности (как я здесь), либо указав нужные вам годы.
Обратите внимание, что для обработки запятых (и X добавляется, если check.names = FALSE
не установлено) я тоже использую dplyr
мутирует с parse_number
от readr
преобразовать текстовые значения обратно в числа. Это все часть tidyverse
и так может быть загружен вместе с library(tidyverse)
wide %>%
gather(Year, Value, -Code, -Country) %>%
mutate(Year = parse_number(Year)
, Value = parse_number(Value))
Возвращает:
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
Вот решение sqldf:
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;")
Чтобы сделать запрос, не вводя все, вы можете использовать следующее:
Спасибо Г. Гротендику за его реализацию.
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
К сожалению, я так не думаю PIVOT
а также UNPIVOT
будет работать на R
SQLite
. Если вы хотите составить свой запрос более сложным образом, вы также можете взглянуть на эти сообщения:
С помощью sprintf
писать запросы sql или передавать переменные вsqldf
Вы также можете использовать cdata
пакет, в котором используется концепция таблицы управления (преобразования):
# 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)
Я сейчас изучаю этот пакет и считаю его вполне доступным. Он предназначен для гораздо более сложных преобразований и включает в себя обратное преобразование. Существует учебник доступны.
Вы также можете увидеть множество примеров в кулинарной книге R
olddata_wide <- read.table(header=TRUE, text='
subject sex control cond1 cond2
1 M 7.9 12.3 10.7
2 F 6.3 10.6 11.1
3 F 9.5 13.1 13.8
4 M 11.5 13.4 12.9
')
# Make sure the subject column is a factor
olddata_wide$subject <- factor(olddata_wide$subject)
olddata_long <- read.table(header=TRUE, text='
subject sex condition measurement
1 M control 7.9
1 M cond1 12.3
1 M cond2 10.7
2 F control 6.3
2 F cond1 10.6
2 F cond2 11.1
3 F control 9.5
3 F cond1 13.1
3 F cond2 13.8
4 M control 11.5
4 M cond1 13.4
4 M cond2 12.9
')
# Make sure the subject column is a factor
olddata_long$subject <- factor(olddata_long$subject)