Изменение формы data.frame из широкого в длинный формат

164

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

mropa
источник
2
Не знаю, была ли это проблема, но функции в пакете изменения формы расплавлены и отлиты (и переделаны)
Эдуардо Леони
1
И пакет изменения формы был заменен reshape2.
IRTFM
5
И теперь Reshape2 был заменен Tidyr.
Драгхен

Ответы:

93

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)
Анико
источник
153

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

1) с :

Вы можете использовать ту же meltфункцию, что и в reshape2пакете (это расширенная и улучшенная реализация). meltfrom data.tableтакже имеет больше параметров, чем melt-function из reshape2. Например, вы также можете указать имя переменной-столбца:

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

который дает:

> 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

Некоторые альтернативные обозначения:

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

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

3) с :

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

Некоторые альтернативные обозначения, которые дают тот же результат:

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

НОТЫ:

  • в отставке. Будут внесены только необходимые изменения, чтобы сохранить его в CRAN. ( источник )
  • Если вы хотите исключить 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)
Яап
источник
отличный ответ, еще одно крошечное напоминание: не помещайте никакие переменные, кроме idи timeв ваш фрейм данных, meltне можете сказать, что вы хотите сделать в этом случае.
Джейсон Гол
1
@JasonGoal Не могли бы вы уточнить это? Поскольку я интерпретирую ваш комментарий, это не должно быть проблемой. Просто укажите как id.varsи measure.vars.
Яап
тогда это хорошо для меня, не знаю, id.varsи это measure.varsможно указать в первом варианте, извините за беспорядок, это моя вина.
Джейсон Гол
Извините за некро этот пост - может кто-нибудь объяснить мне, почему 3 работает? Я проверил это, и оно работает, но я не понимаю, что делает dplyr, когда видит -c(var1, var2)...
1
@ReputableMisnomer Когда tidyr видит, -c(var1, var2)что эти переменные пропускаются при преобразовании данных из широкого в длинный формат.
Яап
35

Используя пакет изменения формы :

#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

С 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))
akrun
источник
1
Это требует больше голосов. Согласно Tidyverse, блог gather уходит на пенсию и pivot_longerсейчас является правильным способом для достижения этой цели.
Эван Росика
16

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

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

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

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

Вот 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
A5C1D2H2I1M1N2O1R2T1
источник
11

Вот еще один пример, показывающий использование gatherfrom tidyr. Вы можете выбрать столбцы gather, удалив их по отдельности (как я здесь), или указав нужные вам годы.

Обратите внимание, что для обработки запятых (и X добавляется, если check.names = FALSEне задано), я также использую dplyrmutate с parse_numberfrom 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
Марк Петерсон
источник
4

Вот решение:

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

M--
источник
0

Вы также можете использовать 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)

В настоящее время я изучаю этот пакет и нахожу его вполне доступным. Он предназначен для гораздо более сложных преобразований и включает в себя обратное преобразование. Существует учебник доступны.

Карстен В.
источник