Переформатування файлів 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()функцією, оскільки це виглядає трохи приємніше в обробці.


2
Не знаю, чи це було проблемою, але функції в пакеті переробок переплавляють і відкидають (і переробляють.)
Едуардо Леоні

1
І пакет переформатування замінено reshape2.
IRTFM

5
А тепер reshape2 витіснив tidyr.
drhagen

Відповіді:


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пакеті (що є розширеною та покращеною реалізацією). meltвід data.tableтакож мають більше параметрів , які melt-функція з 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))

1
Для цього потрібно більше коштів. За повідомленням блогу « Tidyverse»,gather йдеться на пенсію, і pivot_longerтепер це правильний спосіб досягти цього.
Еван Росіця

16

Оскільки ця відповідь позначена тегом Я відчував , що це було б корисно розділити іншу альтернативу від базової R: stack.

Однак слід зазначити, що stackне працює з factorс - це працює тільки , якщо 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

11

Ось ще один приклад показує використання 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

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


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)

Зараз я вивчаю цей пакет і вважаю його досить доступним. Він призначений для набагато складніших перетворень і включає зворотну трансформацію. Існує підручник доступні.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.