Изменение формы данных с широкоформатного формата

У меня возникли проблемы с преобразованием моего 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() поскольку это выглядит немного приятнее в обращении.

Ответ 1

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)

Ответ 2

Три альтернативных решения:

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 -function из 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)

Ответ 3

Использование пакета reshape:

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

Ответ 4

Поскольку этот ответ отмечен , я чувствовал, что было бы полезно поделиться другой альтернативой с базой R: stack.

Обратите внимание, что stack не работает с factor s - он работает только в том случае, если is.vector есть TRUE, а из документации для is.vector мы находим, что:

is.vector возвращает TRUE, если x является вектором указанного режима, не имеющим атрибутов , отличных от имен. Он возвращает FALSE в противном случае.

Я использую образцы данных из ответа @Jaap, где значения в столбцах года факторкод> с.

Вот метод stack:

  cbind (широкий [1: 2], стек (lapply (широкий [-c (1, 2)], as.character)))
## Код Значения страны ind
## 1 AFG Афганистан 20 249 1950
## 2 ALB Албания 8,097 1950
## 3 AFG Афганистан 21 352 1951
## 4 ALB Албания 8 986 1951
## 5 AFG Афганистан 22 532 1952
## 6 ALB Албания 10 058 1952
## 7 AFG Афганистан 23 557 1953
## 8 ALB Албания 11 123 1953
## 9 AFG Афганистан 24 555 1954
## 10 ALB Албания 12 246 1954
Код>

Ответ 5

Вот еще один пример, показывающий использование 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

Ответ 6

Вот решение :

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 переменных в sqldf

Ответ 7

С 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

data

данные
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))